import { describe, expect, it, vi } from "vitest"; import englishCatalog from "../src/locales/en.json" with { type: "json" }; import { HELD_NOTE, approvalHeldNote, approvalHeldReason, approvalModeForOrigin, autoVerdict, deliverFullAccessApproval, delegatedApprovalMode, } from "./auto-approve.ts"; describe("Full access delivery", () => { it.each(["allowed-once", "rejected", "unavailable"] as const)("preserves %s without interrupting or inventing an approval", async outcome => { const adapter = { respondToRequest: vi.fn().mockResolvedValue(outcome), interruptTurn: vi.fn() }; expect(await deliverFullAccessApproval(adapter, "thread", "request", "turn")).toBe(outcome); expect(adapter.respondToRequest).toHaveBeenCalledWith("thread", "request", { behavior: "allow" }); expect(adapter.interruptTurn).not.toHaveBeenCalled(); }); it("reports transport failure and interrupts only the failed turn", async () => { const adapter = { respondToRequest: vi.fn().mockRejectedValue(new Error("connection lost")), interruptTurn: vi.fn().mockResolvedValue(undefined) }; expect(await deliverFullAccessApproval(adapter, "thread", "request", "original-turn", () => true)).toBe("failed"); expect(adapter.interruptTurn).toHaveBeenCalledWith("thread", "original-turn"); adapter.interruptTurn.mockClear(); expect(await deliverFullAccessApproval(adapter, "thread", "request")).toBe("failed"); expect(adapter.interruptTurn).not.toHaveBeenCalled(); expect(await deliverFullAccessApproval(adapter, "thread", "request", "old-turn", () => false)).toBe("failed"); expect(adapter.interruptTurn).not.toHaveBeenCalled(); expect(await deliverFullAccessApproval(undefined, "thread", "request")).toBe("failed"); }); }); describe("autoVerdict", () => { it("never uses a saved command grant to silently send on the person's behalf", () => { for (const mode of ["ask", "edits", "auto", "custom"] as const) { expect(autoVerdict(mode, "mcp__composio__GMAIL_SEND_DRAFT", { commandAllowed: true })).toEqual({ approve: null, source: "outbound-guard" }); } expect(autoVerdict("full", "GMAIL_SEND_EMAIL").source).toBe("full-access"); }); it("applies an explicit exact command grant without changing Full or answering questions/elevations", () => { for (const mode of ["ask", "edits", "auto", "custom"] as const) { expect(autoVerdict(mode, "Bash", { commandAllowed: true }).source).toBe("command-allowlist"); expect(autoVerdict(mode, "Bash", { commandAllowed: true }).approve).toBeTruthy(); expect(autoVerdict(mode, "Bash", { commandAllowed: true, requiresExplicitApproval: true }).approve).toBeNull(); expect(autoVerdict(mode, "AskUserQuestion", { commandAllowed: true }).approve).toBeNull(); } expect(autoVerdict("full", "Bash", { commandAllowed: true, requiresExplicitApproval: true }).source).toBe("full-access"); }); it("applies an exact saved MCP read grant without bypassing outbound or sandbox guards", () => { for (const mode of ["ask", "edits", "auto", "custom"] as const) { expect(autoVerdict(mode, "mcp__linear__list_issues", { toolAllowed: true })).toEqual({ approve: "approved mcp__linear__list_issues (always allowed)", source: "tool-allowlist", }); expect(autoVerdict(mode, "mcp__linear__list_issues", { toolAllowed: true, requiresExplicitApproval: true }).approve).toBeNull(); expect(autoVerdict(mode, "mcp__linear__create_comment", { toolAllowed: true }).approve).toBeNull(); expect(autoVerdict(mode, "AskUserQuestion", { toolAllowed: true }).approve).toBeNull(); } }); it("answers only for Full access, and then answers everything", () => { expect(autoVerdict("full", "Bash")).toEqual({ approve: "approved Bash (full access)", source: "full-access" }); expect(autoVerdict("full", "Bash", { requiresExplicitApproval: true })).toEqual({ approve: "approved Bash (full access)", source: "full-access", }); }); it("leaves an Auto or Custom request with the person as the provider's own reviewer did", () => { expect(autoVerdict("auto", "Bash")).toEqual({ approve: null, source: "native-approval" }); expect(autoVerdict("custom", "Bash")).toEqual({ approve: null, source: "native-approval" }); }); it("never judges the action itself: Ask and Edits card everything the provider asks about", () => { for (const summary of ["wc -l notes.md", "rm -rf build", "cat ~/.ssh/id_rsa"]) { expect(autoVerdict("ask", summary)).toEqual({ approve: null, source: "no-grant" }); expect(autoVerdict("edits", summary)).toEqual({ approve: null, source: "no-grant" }); } }); it("holds a sandbox widening for the person in every mode but Full", () => { for (const mode of ["ask", "edits", "auto", "custom"] as const) { expect(autoVerdict(mode, "shell", { requiresExplicitApproval: true })) .toEqual({ approve: null, source: "explicit-approval-block" }); } }); it("allows a web search under Approve for me, by the tool's bare name", () => { for (const tool of ["WebSearch", "web_search", "mcp__claude__WebSearch", "search_web"]) { expect(autoVerdict("auto", tool), tool).toEqual({ approve: `approved ${tool} (web search)`, source: "web-search", }); } }); it("still cards a fetch, a bare search, and a command under Approve for me", () => { for (const tool of ["WebFetch", "web_fetch", "fetch", "search", "Bash"]) { expect(autoVerdict("auto", tool), tool).toEqual({ approve: null, source: "native-approval" }); } }); it("does not grant a web search in Ask, Edits, or Custom", () => { expect(autoVerdict("ask", "WebSearch")).toEqual({ approve: null, source: "no-grant" }); expect(autoVerdict("edits", "WebSearch")).toEqual({ approve: null, source: "no-grant" }); expect(autoVerdict("custom", "WebSearch")).toEqual({ approve: null, source: "native-approval" }); }); it("lets Full access, a sandbox block, and a saved command beat the web-search grant", () => { expect(autoVerdict("full", "WebSearch")).toEqual({ approve: "approved WebSearch (full access)", source: "full-access", }); expect(autoVerdict("auto", "WebSearch", { requiresExplicitApproval: true })).toEqual({ approve: null, source: "explicit-approval-block", }); expect(autoVerdict("auto", "WebSearch", { commandAllowed: true })).toEqual({ approve: "approved WebSearch (saved command)", source: "command-allowlist", }); }); it("answers a bot's plain read of its own files at every level, but never a sandbox widening or a question", () => { for (const mode of ["ask", "edits", "auto", "custom"] as const) { expect(autoVerdict(mode, "Bash", { ownFilesRead: true }), mode).toEqual({ approve: "approved Bash (its own files)", source: "own-files" }); } expect(autoVerdict("ask", "Read", { ownFilesRead: true })).toEqual({ approve: "approved Read (its own files)", source: "own-files" }); expect(autoVerdict("ask", "Bash", { ownFilesRead: true, requiresExplicitApproval: true })).toEqual({ approve: null, source: "explicit-approval-block" }); expect(autoVerdict("ask", "AskUserQuestion", { ownFilesRead: true })).toEqual({ approve: null, source: "no-grant" }); expect(autoVerdict("full", "Read", { ownFilesRead: true }).source).toBe("full-access"); expect(autoVerdict("ask", "Bash", { ownFilesRead: false })).toEqual({ approve: null, source: "no-grant" }); }); }); describe("approvalModeForOrigin", () => { it("runs peer-started Custom turns as Auto and leaves every other mode alone", () => { expect(approvalModeForOrigin("custom", { peerInitiated: true })).toBe("auto"); expect(approvalModeForOrigin("custom", { peerInitiated: false })).toBe("custom"); for (const mode of ["ask", "edits", "auto", "full"] as const) { expect(approvalModeForOrigin(mode, { peerInitiated: true })).toBe(mode); } }); }); describe("held notes", () => { it("explains a provider's own request and a sandbox change, and nothing else", () => { expect(approvalHeldNote({ source: "native-approval", permission: true })).toBe("approval.held.native"); expect(approvalHeldNote({ source: "explicit-approval-block", permission: true })).toBe("approval.held.sandbox"); expect(approvalHeldNote({ source: "web-search", permission: true })).toBeUndefined(); expect(approvalHeldNote({ source: "no-grant", permission: true })).toBeUndefined(); expect(approvalHeldNote({ source: undefined, permission: true })).toBeUndefined(); expect(approvalHeldNote({ source: "native-approval", permission: false })).toBeUndefined(); expect(approvalHeldReason({ source: "native-approval", permission: true })) .toBe("The provider requires your approval for this action."); }); // Both ways: the client translates every note by key, and a catalog note the server no longer sends describes behaviour that has gone. it("the catalog's held notes are exactly the ones the server sends", () => { const catalog = Object.fromEntries(Object.entries(englishCatalog).filter(([key]) => key.startsWith("approval.held."))); expect(catalog).toEqual(HELD_NOTE); }); }); describe("tools that ask a person", () => { // The backstop for a question arriving typed as a permission: approving runs the tool with no answers, and the model is // told "The user did not answer the questions." const modes = ["ask", "edits", "auto", "custom", "full"] as const; it("never answers AskUserQuestion for the person, even under Full access", () => { for (const mode of modes) { expect(autoVerdict(mode, "AskUserQuestion"), mode).toEqual({ approve: null, source: "no-grant" }); } }); it("never answers ask_user, bare or MCP-prefixed", () => { for (const mode of modes) { expect(autoVerdict(mode, "ask_user").approve, mode).toBeNull(); expect(autoVerdict(mode, "mcp__ogb__ask_user").approve, mode).toBeNull(); } }); it("still answers an ordinary tool under Full access", () => { expect(autoVerdict("full", "Read").approve).toBeTruthy(); }); }); describe("delegatedApprovalMode", () => { const base = { senderIsChief: true, senderMode: "full" as const, sameBot: false, recipientMode: "ask" as const, recipientDriverKind: "claudeAgent" }; it("passes a Full-access Chief's access to the teammate it delegates to", () => { expect(delegatedApprovalMode(base)).toBe("full"); for (const recipientDriverKind of ["codex", "claudeAgent", "antigravityAgent", "cursorAgent", "grokAgent", "opencodeGo"]) { expect(delegatedApprovalMode({ ...base, recipientDriverKind })).toBe("full"); } }); it("passes an Approve-for-me or Auto-accept-edits Chief's level on too", () => { expect(delegatedApprovalMode({ ...base, senderMode: "auto" })).toBe("auto"); expect(delegatedApprovalMode({ ...base, senderMode: "edits" })).toBe("edits"); expect(delegatedApprovalMode({ ...base, senderMode: "custom", recipientDriverKind: "codex" })).toBe("auto"); }); it("passes nothing on from an ordinary bot, a Chief on Ask, or a bot to itself", () => { expect(delegatedApprovalMode({ ...base, senderIsChief: false })).toBeNull(); expect(delegatedApprovalMode({ ...base, senderMode: "ask" })).toBeNull(); expect(delegatedApprovalMode({ ...base, sameBot: true })).toBeNull(); }); it("never lowers a teammate, and leaves one on Custom with its own config", () => { expect(delegatedApprovalMode({ ...base, senderMode: "auto", recipientMode: "full" })).toBeNull(); expect(delegatedApprovalMode({ ...base, senderMode: "auto", recipientMode: "auto" })).toBeNull(); expect(delegatedApprovalMode({ ...base, senderMode: "full", recipientMode: "auto" })).toBe("full"); expect(delegatedApprovalMode({ ...base, senderMode: "edits", recipientMode: "auto" })).toBeNull(); expect(delegatedApprovalMode({ ...base, recipientMode: "custom", recipientDriverKind: "codex" })).toBeNull(); }); it("steps down to the next level the teammate's engine has", () => { expect(delegatedApprovalMode({ ...base, recipientDriverKind: "hermesAgent" })).toBe("full"); expect(delegatedApprovalMode({ ...base, recipientDriverKind: undefined })).toBe("full"); // Codex has no Auto-accept edits (its Ask already writes the workspace). expect(delegatedApprovalMode({ ...base, senderMode: "edits", recipientDriverKind: "codex" })).toBeNull(); expect(delegatedApprovalMode({ ...base, senderMode: "auto", recipientDriverKind: "codex" })).toBe("auto"); }); });