diff --git a/src/config.ts b/src/config.ts index b5a0f91..73a1ed6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -116,7 +116,7 @@ export function hasAgentConfig(): boolean { export function loadAgentConfig(): AgentConfig { const { configPath, agentHome } = resolveConfig(); - // Load file if available, otherwise empty object + // Load agent.json (connection config) let file: Record = {}; if (configPath) { if (!fs.existsSync(configPath)) { @@ -125,6 +125,17 @@ export function loadAgentConfig(): AgentConfig { file = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } + // Load config.json (LLM config) — separate file, optional + let llm: Record = {}; + const llmConfigPath = configPath + ? path.join(path.dirname(configPath), 'config.json') + : agentHome + ? path.join(agentHome, 'config.json') + : null; + if (llmConfigPath && fs.existsSync(llmConfigPath)) { + llm = JSON.parse(fs.readFileSync(llmConfigPath, 'utf-8')); + } + const trackerUrl = process.env.TRACKER_URL || (file.tracker_url as string) || ''; const token = process.env.AGENT_TOKEN || (file.token as string) || ''; @@ -144,22 +155,23 @@ export function loadAgentConfig(): AgentConfig { const wsUrl = process.env.AGENT_WS_URL || (file.ws_url as string) || ''; + // Priority: env > config.json (LLM) > agent.json (legacy compat) > defaults return { name: (file.name as string) || process.env.AGENT_NAME || 'Agent', slug: (file.slug as string) || process.env.AGENT_SLUG || 'agent', - prompt: (file.prompt as string) || process.env.AGENT_PROMPT || '', + prompt: (llm.prompt as string) || (file.prompt as string) || process.env.AGENT_PROMPT || '', trackerUrl, wsUrl, token, - transport: (process.env.AGENT_TRANSPORT || (file.transport as string) || 'http') as 'http' | 'ws', + transport: (process.env.AGENT_TRANSPORT || (file.transport as string) || 'ws') as 'http' | 'ws', listenPort: parseInt(process.env.AGENT_PORT || String(file.listen_port || '3200'), 10), workDir, agentHome: resolvedHome || workDir, capabilities: (file.capabilities as string[]) || ['coding'], - maxConcurrentTasks: (file.max_concurrent_tasks as number) || 2, - model: process.env.PICOGENT_MODEL || (file.model as string) || 'sonnet', - provider: process.env.PICOGENT_PROVIDER || (file.provider as string) || 'anthropic', - apiKey: process.env.PICOGENT_API_KEY || process.env.ANTHROPIC_API_KEY || (file.api_key as string) || '', + maxConcurrentTasks: (llm.max_concurrent_tasks as number) || (file.max_concurrent_tasks as number) || 2, + model: process.env.PICOGENT_MODEL || (llm.model as string) || (file.model as string) || 'sonnet', + provider: process.env.PICOGENT_PROVIDER || (llm.provider as string) || (file.provider as string) || 'anthropic', + apiKey: process.env.PICOGENT_API_KEY || process.env.ANTHROPIC_API_KEY || (llm.api_key as string) || (file.api_key as string) || '', heartbeatIntervalSec: (file.heartbeat_interval_sec as number) || 30, allowedPaths: (file.allowed_paths as string[]) || [], sessionId: ensureSessionId(file, configPath), diff --git a/src/index.ts b/src/index.ts index d3560d7..0c88273 100644 --- a/src/index.ts +++ b/src/index.ts @@ -51,6 +51,25 @@ async function startAgentWs(config: AgentConfig, client: TrackerClient): Promise router.setProjectMappings(wsTransport.projects); } + // Merge remote config (Tracker is source of truth, local config.json overrides) + if (wsTransport.remoteConfig) { + const rc = wsTransport.remoteConfig; + // Only apply remote values if local config.json didn't set them + // (loadAgentConfig already prioritized config.json > agent.json) + if (rc.model && !process.env.PICOGENT_MODEL && config.model === 'sonnet') { + config.model = rc.model; + logger.info('Applied remote model: %s', rc.model); + } + if (rc.provider && !process.env.PICOGENT_PROVIDER && config.provider === 'anthropic') { + config.provider = rc.provider; + logger.info('Applied remote provider: %s', rc.provider); + } + if (rc.prompt && !config.prompt) { + config.prompt = rc.prompt; + logger.info('Applied remote prompt (%d chars)', rc.prompt.length); + } + } + const shutdown = () => { logger.info('Shutting down agent (ws)...'); wsTransport.stop().then(() => { diff --git a/src/transport/ws-client.ts b/src/transport/ws-client.ts index 9b69fc1..e69784a 100644 --- a/src/transport/ws-client.ts +++ b/src/transport/ws-client.ts @@ -36,6 +36,14 @@ export class WsClientTransport implements TaskTracker { projects: Array<{ id: string; slug: string; name: string; chat_id?: string }> = []; /** Online members from auth.ok */ online: string[] = []; + /** Remote agent config from auth.ok (if agent) */ + remoteConfig: { + model?: string; + provider?: string; + prompt?: string; + max_concurrent_tasks?: number; + capabilities?: string[]; + } | null = null; constructor(private config: AgentConfig) {} @@ -205,11 +213,25 @@ export class WsClientTransport implements TaskTracker { this.projects = (data.projects as Array<{ id: string; slug: string; name: string; chat_id?: string }>) || []; this.online = (data.online as string[]) || (data.agents_online as string[]) || []; + // Remote agent config (if present) + const agentConfig = data.agent_config as Record | undefined; + if (agentConfig) { + this.remoteConfig = { + model: agentConfig.model as string | undefined, + provider: agentConfig.provider as string | undefined, + prompt: agentConfig.prompt as string | undefined, + max_concurrent_tasks: agentConfig.max_concurrent_tasks as number | undefined, + capabilities: agentConfig.capabilities as string[] | undefined, + }; + } + this.log.info('━━━ AUTH OK ━━━'); this.log.info(' Lobby chat: %s', this.lobbyChatId || '(none)'); this.log.info(' Projects: %s', this.projects.map(p => `${p.slug}(${p.id})`).join(', ') || '(none)'); this.log.info(' Online: %s', this.online.join(', ') || '(nobody)'); - this.log.info(' Full auth data: %s', JSON.stringify(data, null, 2)); + if (this.remoteConfig) { + this.log.info(' Remote config: model=%s provider=%s', this.remoteConfig.model || '-', this.remoteConfig.provider || '-'); + } this.authenticated = true; this.reconnectDelay = 1000;