diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts index f1a70a736..d38c5bf3f 100644 --- a/src/agents/pi-tools.test.ts +++ b/src/agents/pi-tools.test.ts @@ -195,4 +195,12 @@ describe("createClawdbotCodingTools", () => { expect(tools.some((tool) => tool.name === "read")).toBe(false); expect(tools.some((tool) => tool.name === "browser")).toBe(false); }); + + it("filters tools by agent tool policy even without sandbox", () => { + const tools = createClawdbotCodingTools({ + config: { agent: { tools: { deny: ["browser"] } } }, + }); + expect(tools.some((tool) => tool.name === "bash")).toBe(true); + expect(tools.some((tool) => tool.name === "browser")).toBe(false); + }); }); diff --git a/src/agents/pi-tools.ts b/src/agents/pi-tools.ts index e0db78b9f..646e53195 100644 --- a/src/agents/pi-tools.ts +++ b/src/agents/pi-tools.ts @@ -509,8 +509,14 @@ export function createClawdbotCodingTools(options?: { if (tool.name === "slack") return allowSlack; return true; }); + const globallyFiltered = + options?.config?.agent?.tools && + (options.config.agent.tools.allow?.length || + options.config.agent.tools.deny?.length) + ? filterToolsByPolicy(filtered, options.config.agent.tools) + : filtered; const sandboxed = sandbox - ? filterToolsByPolicy(filtered, sandbox.tools) - : filtered; + ? filterToolsByPolicy(globallyFiltered, sandbox.tools) + : globallyFiltered; return sandboxed.map(normalizeToolParameters); } diff --git a/src/config/types.ts b/src/config/types.ts index cbd174710..be7458e35 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -816,6 +816,11 @@ export type ClawdbotConfig = { maxAgeDays?: number; }; }; + /** Global tool allow/deny policy for all providers (deny wins). */ + tools?: { + allow?: string[]; + deny?: string[]; + }; }; routing?: RoutingConfig; messages?: MessagesConfig; diff --git a/src/config/zod-schema.ts b/src/config/zod-schema.ts index 6c607b081..8ae6b56e9 100644 --- a/src/config/zod-schema.ts +++ b/src/config/zod-schema.ts @@ -370,6 +370,12 @@ export const ClawdbotSchema = z.object({ modelFallbacks: z.array(z.string()).optional(), imageModelFallbacks: z.array(z.string()).optional(), contextTokens: z.number().int().positive().optional(), + tools: z + .object({ + allow: z.array(z.string()).optional(), + deny: z.array(z.string()).optional(), + }) + .optional(), thinkingDefault: z .union([ z.literal("off"),