diff --git a/src/cli/cron-cli.ts b/src/cli/cron-cli.ts index 072676121..3b2958f28 100644 --- a/src/cli/cron-cli.ts +++ b/src/cli/cron-cli.ts @@ -1,8 +1,8 @@ import chalk from "chalk"; import type { Command } from "commander"; +import type { CronJob, CronSchedule } from "../cron/types.js"; import { danger } from "../globals.js"; import { defaultRuntime } from "../runtime.js"; -import type { CronJob, CronSchedule } from "../cron/types.js"; import type { GatewayRpcOpts } from "./gateway-rpc.js"; import { addGatewayClientOptions, callGatewayFromCli } from "./gateway-rpc.js"; @@ -122,8 +122,11 @@ const formatStatus = (job: CronJob) => { return job.state.lastStatus ?? "idle"; }; -const colorize = (rich: boolean, color: (msg: string) => string, msg: string) => - rich ? color(msg) : msg; +const colorize = ( + rich: boolean, + color: (msg: string) => string, + msg: string, +) => (rich ? color(msg) : msg); function printCronList(jobs: CronJob[], runtime = defaultRuntime) { if (jobs.length === 0) { diff --git a/src/commands/doctor-sandbox.ts b/src/commands/doctor-sandbox.ts index 23289591a..f94a3822f 100644 --- a/src/commands/doctor-sandbox.ts +++ b/src/commands/doctor-sandbox.ts @@ -7,6 +7,7 @@ import { DEFAULT_SANDBOX_BROWSER_IMAGE, DEFAULT_SANDBOX_COMMON_IMAGE, DEFAULT_SANDBOX_IMAGE, + resolveSandboxScope, } from "../agents/sandbox.js"; import type { ClawdbotConfig } from "../config/config.js"; import { runCommandWithTimeout, runExec } from "../process/exec.js"; @@ -248,3 +249,44 @@ export async function maybeRepairSandboxImages( return next; } + +export function noteSandboxScopeWarnings(cfg: ClawdbotConfig) { + const globalSandbox = cfg.agent?.sandbox; + const agents = cfg.routing?.agents ?? {}; + const warnings: string[] = []; + + for (const [agentId, agent] of Object.entries(agents)) { + const agentSandbox = agent.sandbox; + if (!agentSandbox) continue; + + const scope = resolveSandboxScope({ + scope: agentSandbox.scope ?? globalSandbox?.scope, + perSession: agentSandbox.perSession ?? globalSandbox?.perSession, + }); + + if (scope !== "shared") continue; + + const overrides: string[] = []; + if (agentSandbox.docker && Object.keys(agentSandbox.docker).length > 0) { + overrides.push("docker"); + } + if (agentSandbox.browser && Object.keys(agentSandbox.browser).length > 0) { + overrides.push("browser"); + } + if (agentSandbox.prune && Object.keys(agentSandbox.prune).length > 0) { + overrides.push("prune"); + } + + if (overrides.length === 0) continue; + + warnings.push( + `- routing.agents.${agentId}.sandbox: ${overrides.join( + "/", + )} overrides ignored (scope resolves to "shared").`, + ); + } + + if (warnings.length > 0) { + note(warnings.join("\n"), "Sandbox"); + } +} diff --git a/src/commands/doctor.test.ts b/src/commands/doctor.test.ts index 0144c603b..61c32d14e 100644 --- a/src/commands/doctor.test.ts +++ b/src/commands/doctor.test.ts @@ -47,7 +47,6 @@ afterEach(() => { const readConfigFileSnapshot = vi.fn(); const confirm = vi.fn().mockResolvedValue(true); -const note = vi.fn(); const select = vi.fn().mockResolvedValue("node"); const note = vi.fn(); const writeConfigFile = vi.fn().mockResolvedValue(undefined); diff --git a/src/commands/doctor.ts b/src/commands/doctor.ts index 93115377a..9dcd0a7fc 100644 --- a/src/commands/doctor.ts +++ b/src/commands/doctor.ts @@ -22,7 +22,10 @@ import { normalizeLegacyConfigValues, } from "./doctor-legacy-config.js"; import { createDoctorPrompter, type DoctorOptions } from "./doctor-prompter.js"; -import { maybeRepairSandboxImages } from "./doctor-sandbox.js"; +import { + maybeRepairSandboxImages, + noteSandboxScopeWarnings, +} from "./doctor-sandbox.js"; import { noteSecurityWarnings } from "./doctor-security.js"; import { noteStateIntegrity, @@ -124,6 +127,7 @@ export async function doctorCommand( await noteStateIntegrity(cfg, prompter); cfg = await maybeRepairSandboxImages(cfg, runtime, prompter); + noteSandboxScopeWarnings(cfg); await maybeMigrateLegacyGatewayService( cfg,