Source of truth for container runtime config moves from
groups/<folder>/container.json to a new container_configs table.
The file becomes a materialized view written at spawn time.
- New container_configs table with scalar columns (provider, model,
effort, image_tag, assistant_name, max_messages_per_prompt) and
JSON columns (mcp_servers, packages_apt, packages_npm, skills,
additional_mounts)
- Startup backfill seeds DB from existing container.json files
- materializeContainerJson() replaces readContainerConfig + ensureRuntimeFields
- Self-mod handlers (install_packages, add_mcp_server) write to DB
- Provider cascade simplified: session -> container_configs -> 'claude'
- ncl groups config-{get,update,add-mcp-server,remove-mcp-server,
add-package,remove-package} custom operations
- restartAgentGroupContainers() helper for config change propagation
- Container side unchanged (still reads /workspace/agent/container.json)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import type { ContainerConfigRow } from '../types.js';
|
|
import { getDb } from './connection.js';
|
|
|
|
export function getContainerConfig(agentGroupId: string): ContainerConfigRow | undefined {
|
|
return getDb().prepare('SELECT * FROM container_configs WHERE agent_group_id = ?').get(agentGroupId) as
|
|
| ContainerConfigRow
|
|
| undefined;
|
|
}
|
|
|
|
export function getAllContainerConfigs(): ContainerConfigRow[] {
|
|
return getDb().prepare('SELECT * FROM container_configs').all() as ContainerConfigRow[];
|
|
}
|
|
|
|
/** Insert a new config row. Caller must supply all JSON fields (use defaults for empty). */
|
|
export function createContainerConfig(config: ContainerConfigRow): void {
|
|
getDb()
|
|
.prepare(
|
|
`INSERT INTO container_configs (
|
|
agent_group_id, provider, model, effort, image_tag, assistant_name,
|
|
max_messages_per_prompt, skills, mcp_servers, packages_apt, packages_npm,
|
|
additional_mounts, updated_at
|
|
) VALUES (
|
|
@agent_group_id, @provider, @model, @effort, @image_tag, @assistant_name,
|
|
@max_messages_per_prompt, @skills, @mcp_servers, @packages_apt, @packages_npm,
|
|
@additional_mounts, @updated_at
|
|
)`,
|
|
)
|
|
.run(config);
|
|
}
|
|
|
|
/** Create an empty config row with sensible defaults. Idempotent — no-ops if row exists. */
|
|
export function ensureContainerConfig(agentGroupId: string): void {
|
|
getDb()
|
|
.prepare(
|
|
`INSERT OR IGNORE INTO container_configs (agent_group_id, updated_at)
|
|
VALUES (?, ?)`,
|
|
)
|
|
.run(agentGroupId, new Date().toISOString());
|
|
}
|
|
|
|
/** Update scalar fields on a config row. Only touches fields present in `updates`. */
|
|
export function updateContainerConfigScalars(
|
|
agentGroupId: string,
|
|
updates: Partial<
|
|
Pick<
|
|
ContainerConfigRow,
|
|
'provider' | 'model' | 'effort' | 'image_tag' | 'assistant_name' | 'max_messages_per_prompt'
|
|
>
|
|
>,
|
|
): void {
|
|
const fields: string[] = [];
|
|
const values: Record<string, unknown> = { agent_group_id: agentGroupId };
|
|
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (value !== undefined) {
|
|
fields.push(`${key} = @${key}`);
|
|
values[key] = value;
|
|
}
|
|
}
|
|
if (fields.length === 0) return;
|
|
|
|
fields.push('updated_at = @updated_at');
|
|
values.updated_at = new Date().toISOString();
|
|
|
|
getDb()
|
|
.prepare(`UPDATE container_configs SET ${fields.join(', ')} WHERE agent_group_id = @agent_group_id`)
|
|
.run(values);
|
|
}
|
|
|
|
/** Overwrite a JSON column wholesale. Used for skills, mcp_servers, packages_*, additional_mounts. */
|
|
export function updateContainerConfigJson(
|
|
agentGroupId: string,
|
|
column: 'skills' | 'mcp_servers' | 'packages_apt' | 'packages_npm' | 'additional_mounts',
|
|
value: unknown,
|
|
): void {
|
|
const now = new Date().toISOString();
|
|
getDb()
|
|
.prepare(`UPDATE container_configs SET ${column} = ?, updated_at = ? WHERE agent_group_id = ?`)
|
|
.run(JSON.stringify(value), now, agentGroupId);
|
|
}
|
|
|
|
export function deleteContainerConfig(agentGroupId: string): void {
|
|
getDb().prepare('DELETE FROM container_configs WHERE agent_group_id = ?').run(agentGroupId);
|
|
}
|