import { describe, expect, test } from "vitest"; import { findEvidence } from "#src/presentation/prompt-payload"; import { buildToolAskPayload, type ToolAskFacts, } from "#src/presentation/tool-ask-payload"; import type { ToolInputFormatterLookup } from "#src/tool-input-formatter-registry"; import type { PermissionCheckResult } from "#src/types"; import { makePermissionCheckResult, makeToolPreviewFormatter, } from "#test/helpers/presentation-fixtures"; function makeFormatter(lookup?: ToolInputFormatterLookup) { return makeToolPreviewFormatter({}, lookup); } function makeMcpLookup(preview: string): ToolInputFormatterLookup { return { get: (name) => (name === "mcp" ? () => preview : undefined) }; } function toolResult( toolName: string, overrides: Partial = {}, ): PermissionCheckResult { return makePermissionCheckResult(toolName, overrides); } function mcpResult( target: string, overrides: Partial = {}, ): PermissionCheckResult { return makePermissionCheckResult("mcp", { target, ...overrides }); } /** Build a payload the way the per-tool gate does, defaulting the surface. */ function buildPayload( facts: Omit & { surface?: string; agentName?: string | null; }, ) { return buildToolAskPayload({ agentName: null, surface: facts.check.toolName, ...facts, }); } describe("buildToolAskPayload", () => { describe("the invariant core", () => { test("carries the gate surface, matched rule, and offending command", () => { const payload = buildPayload({ check: toolResult("bash", { command: "rm -rf foo", matchedPattern: "rm *", commandContext: "command_substitution", }), surface: "bash", }); expect(payload.kind).toBe("bash"); expect(payload.request).toEqual({ requester: { agentName: null, forwarded: false, sessionId: null }, surface: "bash", toolName: "bash", invokedToolName: null, value: "rm -rf foo", matchedPattern: "rm *", commandContext: "command_substitution", executedUnit: null, }); }); test("carries the executed unit of a wrapper (#713)", () => { const payload = buildPayload({ check: toolResult("bash", { command: "xargs grep foo", matchedPattern: "", executedUnit: "grep foo", }), surface: "bash", }); expect(payload.request.executedUnit).toBe("grep foo"); }); test("names the invoked tool when a shell alias re-exposes bash (#574)", () => { const payload = buildPayload({ check: toolResult("bash", { command: "ls" }), surface: "bash", invokedToolName: "exec_command", }); expect(payload.request.toolName).toBe("bash"); expect(payload.request.invokedToolName).toBe("exec_command"); }); test("omits the invoked tool when it repeats the gated one", () => { const payload = buildPayload({ check: toolResult("read"), invokedToolName: "read", }); expect(payload.request.invokedToolName).toBeNull(); }); test("leaves the value empty for a bash check with no resolved command", () => { expect(buildPayload({ check: toolResult("bash") }).request.value).toBe( "", ); }); test("lands the annotations slot empty", () => { expect(buildPayload({ check: toolResult("read") }).annotations).toEqual( [], ); }); }); describe("the bash evidence", () => { test("names the agent when one is known", () => { expect( buildPayload({ check: toolResult("read"), agentName: "my-agent", input: { path: "/src" }, formatter: makeFormatter(), }).request.requester.agentName, ).toBe("my-agent"); }); test("carries the enclosing command when the gated unit is only part of it", () => { expect( findEvidence( buildPayload({ check: toolResult("bash", { command: "rm -rf ." }), input: { command: 'echo "hello" && rm -rf .' }, formatter: makeFormatter(), }), "full command", ), ).toEqual({ label: "full command", text: 'echo "hello" && rm -rf .', detail: null, }); }); test("omits the enclosing command when it is the gated unit", () => { expect( findEvidence( buildPayload({ check: toolResult("bash", { command: "git push" }), input: { command: "git push" }, formatter: makeFormatter(), }), "full command", ), ).toBeUndefined(); }); test.each([ ["input is undefined", undefined], ["input has no command field", { unrelated: "value" }], ["input command is empty", { command: "" }], ])("omits the enclosing command when %s", (_case, input) => { expect( findEvidence( buildPayload({ check: toolResult("bash", { command: "git push" }), input, formatter: makeFormatter(), }), "full command", ), ).toBeUndefined(); }); test("adds no input preview, since the command is the value", () => { expect( findEvidence( buildPayload({ check: toolResult("bash", { command: "git status" }), input: { command: "git status" }, formatter: makeFormatter(), }), "input", ), ).toBeUndefined(); }); test("carries a registered shell alias preview as authorization evidence", () => { const formatter = makeFormatter({ get: (name) => name === "ssh_bash" ? () => "SSH target 'packaging-server' in remote cwd '/srv/build'" : undefined, }); const payload = buildPayload({ check: toolResult("bash", { command: "rm -rf dist" }), surface: "bash", invokedToolName: "ssh_bash", input: { command: "rm -rf dist" }, formatter, }); expect(findEvidence(payload, "input")).toEqual({ label: "input", text: "SSH target 'packaging-server' in remote cwd '/srv/build'", detail: null, }); }); }); describe("mcp", () => { test("makes the target the decision-relevant value", () => { const payload = buildPayload({ check: mcpResult("server:query", { matchedPattern: "server:*" }), formatter: makeFormatter(), }); expect(payload.kind).toBe("mcp"); expect(payload.request.value).toBe("server:query"); expect(payload.request.matchedPattern).toBe("server:*"); }); test("carries the argument summary a registered formatter produced", () => { expect( findEvidence( buildPayload({ check: mcpResult("exa:search"), input: { tool: "exa:search", arguments: { query: "typescript" } }, formatter: makeFormatter(makeMcpLookup('with query: "typescript"')), }), "input", ), ).toEqual({ label: "input", text: 'with query: "typescript"', detail: null, }); }); test("carries no evidence when the registered formatter declines", () => { const noArgsLookup: ToolInputFormatterLookup = { get: (name) => (name === "mcp" ? () => undefined : undefined), }; expect( buildPayload({ check: mcpResult("exa:search"), input: { tool: "exa:search" }, formatter: makeFormatter(noArgsLookup), }).evidence, ).toEqual([]); }); test("carries no evidence when no formatter is provided", () => { expect( buildPayload({ check: mcpResult("exa:search"), input: { tool: "exa:search", arguments: { query: "test" } }, }).evidence, ).toEqual([]); }); }); describe("generic tools", () => { test("carries the real input preview as evidence", () => { expect( findEvidence( buildPayload({ check: toolResult("read"), input: { path: "/src/foo.ts" }, formatter: makeFormatter(), }), "input", )?.text, ).toContain("path '/src/foo.ts'"); }); test("carries no evidence when the formatter produces nothing", () => { expect( buildPayload({ check: toolResult("task"), input: {}, formatter: makeFormatter(), }).evidence, ).toEqual([]); }); test("carries no evidence when no formatter is provided", () => { expect( buildPayload({ check: toolResult("task"), input: { path: "/src" } }) .evidence, ).toEqual([]); }); }); });