Replace the per-group agent-runner-src copy model with a single shared read-only mount. Source and skills are now RO + shared; personality, config, working files, and Claude state stay RW + per-group. Key changes: - Mount container/agent-runner/src/ RO at /app/src (all groups share one copy) - Mount container/skills/ RO at /app/skills; per-group skill selection via symlinks in .claude-shared/skills/ based on container.json "skills" field - Mount container.json as nested RO bind on top of RW group dir - Move all NANOCLAW_* env vars to container.json (runner reads at startup) - New runner config.ts module replaces process.env reads - Move command gate (filtered/admin) from container to host router - Dockerfile: remove source COPY, split CLI installs (claude-code last), move agent-runner deps above CLIs for better layer caching - Add writeOutboundDirect for router denial responses - Design doc at docs/shared-src.md Not included (follow-up): DB migration to drop agent_provider columns, cleanup of orphaned agent-runner-src directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
/**
|
|
* 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<string, { command: string; args: string[]; env: Record<string, 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<string, unknown> = {};
|
|
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']) || {},
|
|
};
|
|
|
|
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;
|
|
}
|