Add cli_scope column to container_configs with three levels: - disabled: agent never learns about ncl (instructions excluded from CLAUDE.md) and host dispatch rejects any cli_request - group (default): agent can only access groups, sessions, destinations, and members resources, scoped to its own agent group with auto-filled --id/--agent_group_id/--group args. Help output reflects the scope. - global: unrestricted access (current behavior) Enforcement is host-side only — no image rebuild or env var needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
/**
|
|
* One-time backfill: seed `container_configs` rows from existing
|
|
* `groups/<folder>/container.json` files and `agent_groups.agent_provider`.
|
|
*
|
|
* Runs after migrations, before channel adapters start. Idempotent — skips
|
|
* groups that already have a config row.
|
|
*/
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
import { GROUPS_DIR } from './config.js';
|
|
import type { McpServerConfig, AdditionalMountConfig } from './container-config.js';
|
|
import { getAllAgentGroups } from './db/agent-groups.js';
|
|
import { getContainerConfig, createContainerConfig } from './db/container-configs.js';
|
|
import { log } from './log.js';
|
|
import type { ContainerConfigRow } from './types.js';
|
|
|
|
interface LegacyContainerJson {
|
|
mcpServers?: Record<string, McpServerConfig>;
|
|
packages?: { apt?: string[]; npm?: string[] };
|
|
imageTag?: string;
|
|
additionalMounts?: AdditionalMountConfig[];
|
|
skills?: string[] | 'all';
|
|
provider?: string;
|
|
assistantName?: string;
|
|
maxMessagesPerPrompt?: number;
|
|
}
|
|
|
|
export function backfillContainerConfigs(): void {
|
|
const groups = getAllAgentGroups();
|
|
let backfilled = 0;
|
|
|
|
for (const group of groups) {
|
|
// Skip if already has a config row
|
|
if (getContainerConfig(group.id)) continue;
|
|
|
|
// Read legacy container.json from disk
|
|
const filePath = path.join(GROUPS_DIR, group.folder, 'container.json');
|
|
let legacy: LegacyContainerJson = {};
|
|
if (fs.existsSync(filePath)) {
|
|
try {
|
|
legacy = JSON.parse(fs.readFileSync(filePath, 'utf8')) as LegacyContainerJson;
|
|
} catch (err) {
|
|
log.warn('Backfill: failed to parse container.json, using defaults', {
|
|
folder: group.folder,
|
|
err: String(err),
|
|
});
|
|
}
|
|
}
|
|
|
|
// DB agent_provider wins over file provider (matches old cascade)
|
|
const provider = group.agent_provider || legacy.provider || null;
|
|
|
|
const row: ContainerConfigRow = {
|
|
agent_group_id: group.id,
|
|
provider,
|
|
model: null,
|
|
effort: null,
|
|
image_tag: legacy.imageTag ?? null,
|
|
assistant_name: legacy.assistantName ?? null,
|
|
max_messages_per_prompt: legacy.maxMessagesPerPrompt ?? null,
|
|
skills: JSON.stringify(legacy.skills ?? 'all'),
|
|
mcp_servers: JSON.stringify(legacy.mcpServers ?? {}),
|
|
packages_apt: JSON.stringify(legacy.packages?.apt ?? []),
|
|
packages_npm: JSON.stringify(legacy.packages?.npm ?? []),
|
|
additional_mounts: JSON.stringify(legacy.additionalMounts ?? []),
|
|
cli_scope: 'group',
|
|
updated_at: new Date().toISOString(),
|
|
};
|
|
|
|
createContainerConfig(row);
|
|
backfilled++;
|
|
}
|
|
|
|
if (backfilled > 0) {
|
|
log.info('Backfilled container_configs from disk', { count: backfilled });
|
|
}
|
|
}
|