import { describe, expect, it } from "vitest"; import type { ClawdbotConfig } from "../config/config.js"; import { createClawdbotCodingTools } from "./pi-tools.js"; import type { SandboxDockerConfig } from "./sandbox.js"; describe("Agent-specific tool filtering", () => { it("should apply global tool policy when no agent-specific policy exists", () => { const cfg: ClawdbotConfig = { tools: { allow: ["read", "write"], deny: ["bash"], }, agents: { list: [ { id: "main", workspace: "~/clawd", }, ], }, }; const tools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:main:main", workspaceDir: "/tmp/test", agentDir: "/tmp/agent", }); const toolNames = tools.map((t) => t.name); expect(toolNames).toContain("Read"); expect(toolNames).toContain("Write"); expect(toolNames).not.toContain("Bash"); }); it("should apply agent-specific tool policy", () => { const cfg: ClawdbotConfig = { tools: { allow: ["read", "write", "bash"], deny: [], }, agents: { list: [ { id: "restricted", workspace: "~/clawd-restricted", tools: { allow: ["read"], // Agent override: only read deny: ["bash", "write", "edit"], }, }, ], }, }; const tools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:restricted:main", workspaceDir: "/tmp/test-restricted", agentDir: "/tmp/agent-restricted", }); const toolNames = tools.map((t) => t.name); expect(toolNames).toContain("Read"); expect(toolNames).not.toContain("Bash"); expect(toolNames).not.toContain("Write"); expect(toolNames).not.toContain("Edit"); }); it("should allow different tool policies for different agents", () => { const cfg: ClawdbotConfig = { agents: { list: [ { id: "main", workspace: "~/clawd", // No tools restriction - all tools available }, { id: "family", workspace: "~/clawd-family", tools: { allow: ["read"], deny: ["bash", "write", "edit", "process"], }, }, ], }, }; // main agent: all tools const mainTools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:main:main", workspaceDir: "/tmp/test-main", agentDir: "/tmp/agent-main", }); const mainToolNames = mainTools.map((t) => t.name); expect(mainToolNames).toContain("Bash"); expect(mainToolNames).toContain("Write"); expect(mainToolNames).toContain("Edit"); // family agent: restricted const familyTools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:family:whatsapp:group:123", workspaceDir: "/tmp/test-family", agentDir: "/tmp/agent-family", }); const familyToolNames = familyTools.map((t) => t.name); expect(familyToolNames).toContain("Read"); expect(familyToolNames).not.toContain("Bash"); expect(familyToolNames).not.toContain("Write"); expect(familyToolNames).not.toContain("Edit"); }); it("should prefer agent-specific tool policy over global", () => { const cfg: ClawdbotConfig = { tools: { deny: ["browser"], // Global deny }, agents: { list: [ { id: "work", workspace: "~/clawd-work", tools: { deny: ["bash", "process"], // Agent deny (override) }, }, ], }, }; const tools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:work:slack:dm:user123", workspaceDir: "/tmp/test-work", agentDir: "/tmp/agent-work", }); const toolNames = tools.map((t) => t.name); // Agent policy overrides global: browser is allowed again expect(toolNames).toContain("browser"); expect(toolNames).not.toContain("Bash"); expect(toolNames).not.toContain("process"); }); it("should work with sandbox tools filtering", () => { const cfg: ClawdbotConfig = { agents: { defaults: { sandbox: { mode: "all", scope: "agent", }, }, list: [ { id: "restricted", workspace: "~/clawd-restricted", sandbox: { mode: "all", scope: "agent", }, tools: { allow: ["read"], // Agent further restricts to only read deny: ["bash", "write"], }, }, ], }, tools: { sandbox: { tools: { allow: ["read", "write", "bash"], // Sandbox allows these deny: [], }, }, }, }; const tools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:restricted:main", workspaceDir: "/tmp/test-restricted", agentDir: "/tmp/agent-restricted", sandbox: { enabled: true, sessionKey: "agent:restricted:main", workspaceDir: "/tmp/sandbox", agentWorkspaceDir: "/tmp/test-restricted", workspaceAccess: "none", containerName: "test-container", containerWorkdir: "/workspace", docker: { image: "test-image", containerPrefix: "test-", workdir: "/workspace", readOnlyRoot: true, tmpfs: [], network: "none", capDrop: [], } satisfies SandboxDockerConfig, tools: { allow: ["read", "write", "bash"], deny: [], }, }, }); const toolNames = tools.map((t) => t.name); // Agent policy should be applied first, then sandbox // Agent allows only "read", sandbox allows ["read", "write", "bash"] // Result: only "read" (most restrictive wins) expect(toolNames).toContain("Read"); expect(toolNames).not.toContain("Bash"); expect(toolNames).not.toContain("Write"); }); it("should run bash synchronously when process is denied", async () => { const cfg: ClawdbotConfig = { tools: { deny: ["process"], }, }; const tools = createClawdbotCodingTools({ config: cfg, sessionKey: "agent:main:main", workspaceDir: "/tmp/test-main", agentDir: "/tmp/agent-main", }); const bash = tools.find((tool) => tool.name === "Bash"); expect(bash).toBeDefined(); const result = await bash?.execute("call1", { command: "echo done", yieldMs: 10, }); expect(result?.details.status).toBe("completed"); }); });