/** * Runner config — reads /workspace/agent/container.json at startup. * * This file is mounted read-only inside the container. The host writes it; * the runner only reads. All NanoClaw-specific configuration lives here * instead of environment variables. */ import fs from 'fs'; const CONFIG_PATH = '/workspace/agent/container.json'; export interface RunnerConfig { provider: string; assistantName: string; groupName: string; agentGroupId: string; maxMessagesPerPrompt: number; mcpServers: Record }>; model?: string; effort?: string; } const DEFAULT_MAX_MESSAGES = 10; let _config: RunnerConfig | null = null; /** * Load config from container.json. Called once at startup. * Falls back to sensible defaults for any missing field. */ export function loadConfig(): RunnerConfig { if (_config) return _config; let raw: Record = {}; try { raw = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); } catch { console.error(`[config] Failed to read ${CONFIG_PATH}, using defaults`); } _config = { provider: (raw.provider as string) || 'claude', assistantName: (raw.assistantName as string) || '', groupName: (raw.groupName as string) || '', agentGroupId: (raw.agentGroupId as string) || '', maxMessagesPerPrompt: (raw.maxMessagesPerPrompt as number) || DEFAULT_MAX_MESSAGES, mcpServers: (raw.mcpServers as RunnerConfig['mcpServers']) || {}, model: (raw.model as string) || undefined, effort: (raw.effort as string) || undefined, }; return _config; } /** Get the loaded config. Throws if loadConfig() hasn't been called. */ export function getConfig(): RunnerConfig { if (!_config) throw new Error('Config not loaded — call loadConfig() first'); return _config; }