diff --git a/CHANGELOG.md b/CHANGELOG.md index b93fcac89..fea4952e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot ### Changes - Exec: add host/security/ask routing for gateway + node exec. +- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). - macOS: migrate exec approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists and skill auto-allow toggle. - macOS: add approvals socket UI server + node exec lifecycle events. - Slash commands: replace `/cost` with `/usage off|tokens|full` to control per-response usage footer; `/usage` no longer aliases `/status`. (Supersedes #1140) — thanks @Nachx639. diff --git a/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts b/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts index a1f15cdf3..cb2c75ec2 100644 --- a/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts +++ b/src/commands/doctor.falls-back-legacy-sandbox-image-missing.test.ts @@ -55,6 +55,7 @@ beforeEach(() => { killed: false, }); ensureAuthProfileStore.mockReset().mockReturnValue({ version: 1, profiles: {} }); + loadClawdbotPlugins.mockReset().mockReturnValue({ plugins: [], diagnostics: [] }); migrateLegacyConfig.mockReset().mockImplementation((raw: unknown) => ({ config: raw as Record, changes: ["Moved routing.allowFrom → channels.whatsapp.allowFrom."], @@ -131,6 +132,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({ }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); +const loadClawdbotPlugins = vi.fn().mockReturnValue({ plugins: [], diagnostics: [] }); const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ path: "/tmp/clawdbot.json", @@ -173,9 +175,8 @@ vi.mock("../agents/skills-status.js", () => ({ })); vi.mock("../plugins/loader.js", () => ({ - loadClawdbotPlugins: () => ({ plugins: [], diagnostics: [] }), + loadClawdbotPlugins, })); - vi.mock("../config/config.js", async (importOriginal) => { const actual = await importOriginal(); return { diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index d2be8d111..25768aaf2 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -387,3 +387,32 @@ export type ToolsConfig = { }; }; }; + +export type ExecToolConfig = { + /** Exec host routing (default: sandbox). */ + host?: "sandbox" | "gateway" | "node"; + /** Exec security mode (default: deny). */ + security?: "deny" | "allowlist" | "full"; + /** Exec ask mode (default: on-miss). */ + ask?: "off" | "on-miss" | "always"; + /** Default node binding for exec.host=node (node id/name). */ + node?: string; + /** Default time (ms) before an exec command auto-backgrounds. */ + backgroundMs?: number; + /** Default timeout (seconds) before auto-killing exec commands. */ + timeoutSec?: number; + /** How long to keep finished sessions in memory (ms). */ + cleanupMs?: number; + /** Emit a system event and heartbeat when a backgrounded exec exits. */ + notifyOnExit?: boolean; + /** apply_patch subtool configuration (experimental). */ + applyPatch?: { + /** Enable apply_patch for OpenAI models (default: false). */ + enabled?: boolean; + /** + * Optional allowlist of model ids that can use apply_patch. + * Accepts either raw ids (e.g. "gpt-5.2") or full ids (e.g. "openai/gpt-5.2"). + */ + allowModels?: string[]; + }; +}; diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 48107b88c..33d647ea2 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -183,6 +183,24 @@ export const AgentToolsSchema = z allowFrom: ElevatedAllowFromSchema, }) .optional(), + exec: z + .object({ + host: z.enum(["sandbox", "gateway", "node"]).optional(), + security: z.enum(["deny", "allowlist", "full"]).optional(), + ask: z.enum(["off", "on-miss", "always"]).optional(), + node: z.string().optional(), + backgroundMs: z.number().int().positive().optional(), + timeoutSec: z.number().int().positive().optional(), + cleanupMs: z.number().int().positive().optional(), + notifyOnExit: z.boolean().optional(), + applyPatch: z + .object({ + enabled: z.boolean().optional(), + allowModels: z.array(z.string()).optional(), + }) + .optional(), + }) + .optional(), sandbox: z .object({ tools: ToolPolicySchema, diff --git a/vitest.config.ts b/vitest.config.ts index 28bc8ab70..6dd8ed0f2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ }, }, test: { - testTimeout: 20_000, + testTimeout: 30_000, include: [ "src/**/*.test.ts", "extensions/**/*.test.ts",