/** * Container config types and materialization. * * Source of truth is the `container_configs` table in the central DB. * This module provides: * - Type definitions for the file shape (read by the container runner) * - `materializeContainerJson()` — writes `groups//container.json` * from the DB at spawn time * - `configFromDb()` — builds a `ContainerConfig` from a DB row + agent group */ import fs from 'fs'; import path from 'path'; import { GROUPS_DIR } from './config.js'; import { getContainerConfig } from './db/container-configs.js'; import { getAgentGroup } from './db/agent-groups.js'; import type { AgentGroup, ContainerConfigRow } from './types.js'; export interface McpServerConfig { command: string; args?: string[]; env?: Record; instructions?: string; } export interface AdditionalMountConfig { hostPath: string; containerPath: string; readonly?: boolean; } /** Shape of the materialized `container.json` file read by the container runner. */ export interface ContainerConfig { mcpServers: Record; packages: { apt: string[]; npm: string[] }; imageTag?: string; additionalMounts: AdditionalMountConfig[]; skills: string[] | 'all'; provider?: string; groupName?: string; assistantName?: string; agentGroupId?: string; maxMessagesPerPrompt?: number; model?: string; effort?: string; } /** Build a `ContainerConfig` from a DB row + agent group identity. */ export function configFromDb(row: ContainerConfigRow, group: AgentGroup): ContainerConfig { return { mcpServers: JSON.parse(row.mcp_servers) as Record, packages: { apt: JSON.parse(row.packages_apt) as string[], npm: JSON.parse(row.packages_npm) as string[], }, imageTag: row.image_tag ?? undefined, additionalMounts: JSON.parse(row.additional_mounts) as AdditionalMountConfig[], skills: JSON.parse(row.skills) as string[] | 'all', provider: row.provider ?? undefined, groupName: group.name, assistantName: row.assistant_name ?? group.name, agentGroupId: group.id, maxMessagesPerPrompt: row.max_messages_per_prompt ?? undefined, model: row.model ?? undefined, effort: row.effort ?? undefined, }; } /** * Materialize `container.json` from the DB. Called at spawn time so the * container always sees fresh config. Returns the `ContainerConfig` for * use by the caller (buildMounts, buildContainerArgs, etc.). */ export function materializeContainerJson(agentGroupId: string): ContainerConfig { const group = getAgentGroup(agentGroupId); if (!group) throw new Error(`Agent group not found: ${agentGroupId}`); const row = getContainerConfig(agentGroupId); if (!row) throw new Error(`Container config not found for agent group: ${agentGroupId}`); const config = configFromDb(row, group); const p = path.join(GROUPS_DIR, group.folder, 'container.json'); const dir = path.dirname(p); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(p, JSON.stringify(config, null, 2) + '\n'); return config; }