import { describe, expect, it } from "vitest"; import { completeViewBudget, type DialogBudget, renderPromptDialog, } from "#src/presentation/dialog-renderer"; import { makePromptPayload } from "#test/helpers/prompt-details-fixtures"; const WIDE = completeViewBudget(200); /** The rendered lines for a payload built from the shared structural fixture. */ function render( overrides: Parameters[0], ): readonly string[] { return renderPromptDialog(makePromptPayload(overrides), WIDE).lines; } /** A payload whose request facts override the fixture's defaults. */ function requestFacts( overrides: Partial["request"]>, ) { return { ...makePromptPayload().request, ...overrides }; } describe("renderPromptDialog", () => { describe("the invariant core", () => { it("renders a bash ask as one aligned fact per line", () => { expect( render({ kind: "bash", request: requestFacts({ requester: { agentName: "scout", forwarded: false, sessionId: null, }, surface: "bash", toolName: "bash", value: "cat /etc/hosts", matchedPattern: "*", }), }), ).toEqual([ "agent : scout", "tool : bash", "rule : *", "command : cat /etc/hosts", ]); }); it("names the gate surface when it differs from the tool", () => { expect( render({ kind: "external_directory", request: requestFacts({ surface: "external_directory", toolName: "write", value: "/etc/hosts", matchedPattern: "*", }), }), ).toEqual([ "tool : write", "surface : external_directory", "rule : *", "path : /etc/hosts", ]); }); it("omits an agent line for an unnamed local requester, and a surface the value label already names", () => { expect( render({ kind: "path", request: requestFacts({ surface: "path", toolName: "read", value: "/tmp/x", matchedPattern: null, }), }), ).toEqual(["tool : read", "path : /tmp/x"]); }); it("omits the value line when the tool line already carries it", () => { expect( render({ kind: "tool", request: requestFacts({ surface: "fetch", toolName: "fetch", value: "fetch", matchedPattern: "*", }), }), ).toEqual(["tool : fetch", "rule : *"]); }); it("labels an MCP ask's value as the target", () => { expect( render({ kind: "mcp", request: requestFacts({ surface: "mcp__github__create_issue", toolName: "mcp__github__create_issue", value: "github:create_issue", matchedPattern: "mcp__github__*", }), }), ).toEqual([ "tool : mcp__github__create_issue", "rule : mcp__github__*", "target : github:create_issue", ]); }); it("renders a skill ask without repeating the surface", () => { expect( render({ kind: "skill", request: requestFacts({ surface: "skill", toolName: null, value: "deploy", matchedPattern: null, }), }), ).toEqual(["skill : deploy"]); }); it("renders the executed unit of an unstrippable wrapper", () => { expect( render({ kind: "bash", request: requestFacts({ surface: "bash", toolName: "bash", value: "xargs grep foo", matchedPattern: "", executedUnit: "grep foo", }), }), ).toEqual([ "tool : bash", "rule : ", "command : xargs grep foo", "runs : grep foo", ]); }); it("names the nested context the offending unit ran in", () => { expect( render({ kind: "bash", request: requestFacts({ surface: "bash", toolName: "bash", value: "rm -rf /tmp/x", matchedPattern: "rm *", commandContext: "command_substitution", }), }), ).toEqual([ "tool : bash", "rule : rm *", "command : rm -rf /tmp/x", "context : command substitution", ]); }); it("names the invoked tool when a shell alias re-exposes bash", () => { expect( render({ kind: "bash", request: requestFacts({ surface: "bash", toolName: "bash", invokedToolName: "exec_command", value: "ls", matchedPattern: "*", }), }), ).toEqual([ "tool : bash (invoked as exec_command)", "rule : *", "command : ls", ]); }); }); describe("evidence", () => { it("renders each entry under the core, sharing the label column", () => { expect( render({ kind: "bash", request: requestFacts({ surface: "bash", toolName: "bash", value: "rm -rf build", matchedPattern: "rm *", }), evidence: [ { label: "full command", text: "npm run clean && rm -rf build", detail: null, }, ], }), ).toEqual([ "tool : bash", "rule : rm *", "command : rm -rf build", "full command : npm run clean && rm -rf build", ]); }); it("keeps an entry's detail on the entry's own line", () => { expect( render({ kind: "bash_external_directory", request: requestFacts({ surface: "external_directory", toolName: "bash", value: "cat /etc/hosts /tmp/x", matchedPattern: "*", }), evidence: [ { label: "working directory", text: "/repo", detail: null }, { label: "external path", text: "/etc/hosts", detail: "/private/etc/hosts", }, { label: "external path", text: "/tmp/x", detail: null }, ], }), ).toEqual([ "tool : bash", "surface : external_directory", "rule : *", "command : cat /etc/hosts /tmp/x", "working directory : /repo", "external path : /etc/hosts → /private/etc/hosts", "external path : /tmp/x", ]); }); it("renders a skill read's path", () => { expect( render({ kind: "skill_read", request: requestFacts({ surface: "skill", toolName: null, value: "deploy", matchedPattern: null, }), evidence: [ { label: "read path", text: "/skills/deploy/SKILL.md", detail: null, }, ], }), ).toEqual(["skill : deploy", "read path : /skills/deploy/SKILL.md"]); }); }); describe("the per-field width cap", () => { const NARROW: DialogBudget = { maxRows: Number.POSITIVE_INFINITY, fieldMaxWidth: 12, width: 200, }; /** A bash ask whose command is the pathological field. */ function bashAsk(command: string, fullCommand?: string) { return makePromptPayload({ kind: "bash", request: { ...makePromptPayload().request, toolName: "bash", surface: "bash", value: command, matchedPattern: "*", }, evidence: fullCommand === undefined ? [] : [{ label: "full command", text: fullCommand, detail: null }], }); } it("clips a core field and marks it, without stating a count", () => { const view = renderPromptDialog( bashAsk("echo the quick brown fox"), NARROW, ); expect(view.lines).toEqual([ "tool : bash", "rule : *", "command : echo the qui…", ]); expect(view.elided).toBe(true); }); it("clips an evidence field the same way", () => { const view = renderPromptDialog( bashAsk("echo hi", "echo hi && echo there"), NARROW, ); expect(view.lines).toEqual([ "tool : bash", "rule : *", "command : echo hi", "full command : echo hi && e…", ]); expect(view.elided).toBe(true); }); it("reproduces the field verbatim under the complete view", () => { const view = renderPromptDialog( bashAsk("echo the quick brown fox"), completeViewBudget(200), ); expect(view.lines).toContain("command : echo the quick brown fox"); expect(view.elided).toBe(false); }); it("indents a multi-line field under its label", () => { const view = renderPromptDialog( bashAsk("cat <<'EOF'\nfirst line\nsecond line\nEOF"), completeViewBudget(200), ); expect(view.lines).toEqual([ "tool : bash", "rule : *", "command : cat <<'EOF'", " first line", " second line", " EOF", ]); }); }); describe("the row budget", () => { /** A tool ask with `count` evidence entries, each one row wide. */ function askWithEvidence(count: number) { return makePromptPayload({ kind: "tool", request: { ...makePromptPayload().request, surface: "fetch", toolName: "fetch", value: "fetch", matchedPattern: "*", }, evidence: Array.from({ length: count }, (_, index) => ({ label: "input", text: `entry ${index}`, detail: null, })), }); } it("drops evidence that does not fit and marks the drop", () => { const view = renderPromptDialog(askWithEvidence(6), { maxRows: 5, fieldMaxWidth: 200, width: 200, }); expect(view.lines).toEqual([ "tool : fetch", "rule : *", "input : entry 0", "input : entry 1", "…", ]); expect(view.elided).toBe(true); }); it("marks nothing when every entry fits", () => { const view = renderPromptDialog(askWithEvidence(2), { maxRows: 5, fieldMaxWidth: 200, width: 200, }); expect(view.lines).toHaveLength(4); expect(view.elided).toBe(false); }); it("counts rows after wrapping to the width", () => { const payload = makePromptPayload({ kind: "tool", request: { ...makePromptPayload().request, surface: "fetch", toolName: "fetch", value: "fetch", matchedPattern: "*", }, evidence: [{ label: "input", text: "a".repeat(60), detail: null }], }); // The same entry is one row at a wide width and four once wrapped, so // the identical budget admits it only in the first case. expect( renderPromptDialog(payload, { maxRows: 5, fieldMaxWidth: 200, width: 200, }), ).toEqual({ lines: ["tool : fetch", "rule : *", `input : ${"a".repeat(60)}`], elided: false, }); const wrapped = renderPromptDialog(payload, { maxRows: 5, fieldMaxWidth: 200, width: 20, }); expect(wrapped.lines).toEqual(["tool : fetch", "rule : *", "…"]); expect(wrapped.elided).toBe(true); }); it("renders the whole core when it alone exceeds the budget", () => { const view = renderPromptDialog(askWithEvidence(3), { maxRows: 1, fieldMaxWidth: 200, width: 200, }); expect(view.lines).toEqual(["tool : fetch", "rule : *"]); expect(view.elided).toBe(true); }); it("bounds the reported forwarded here-string ask (#710)", () => { const body = Array.from( { length: 200 }, () => "- a finding line about some module in the codebase", ).join("\n"); const command = `@'\n${body}\n'@ | Out-File -FilePath report.md`; const budget = { maxRows: 24, fieldMaxWidth: 400, width: 120 }; const view = renderPromptDialog( makePromptPayload({ kind: "forwarded", request: { ...makePromptPayload().request, requester: { agentName: "scout", forwarded: true, sessionId: "abc123", }, surface: "bash", toolName: null, value: command, matchedPattern: null, }, evidence: [ { label: "requested", text: `Subagent 'scout' requested bash command '${command}'.`, detail: null, }, ], }), budget, ); expect(view.lines.length).toBeLessThanOrEqual(budget.maxRows); expect(view.lines[0]).toBe("subagent : scout · session abc123"); expect(view.lines[1]).toBe("surface : bash"); expect(view.lines[2]).toBe("command : @'"); expect(view.elided).toBe(true); }); it("bounds the same forwarded here-string ask carrying the child's own payload (#745)", () => { // The same ask, arriving as the child built it rather than as a relayed // sentence: `kind: "bash"` with the child's real evidence entries. A // different input to the same budget, so the row bound is measured here // rather than inferred from the `kind: "forwarded"` pin above. const body = Array.from( { length: 200 }, () => "- a finding line about some module in the codebase", ).join("\n"); const command = `@'\n${body}\n'@ | Out-File -FilePath report.md`; const budget = { maxRows: 24, fieldMaxWidth: 400, width: 120 }; const view = renderPromptDialog( makePromptPayload({ kind: "bash", request: { ...makePromptPayload().request, requester: { agentName: "scout", forwarded: true, sessionId: "abc123", }, surface: "bash", toolName: "bash", value: command, matchedPattern: "*", }, evidence: [ { label: "full command", text: command, detail: null }, { label: "working directory", text: "/repo", detail: null }, ], }), budget, ); expect(view.lines.length).toBeLessThanOrEqual(budget.maxRows); expect(view.elided).toBe(true); }); }); describe("highlighting the flagged element", () => { /** A visible stand-in for the theme's warning colour. */ const mark = (text: string) => `[${text}]`; it("paints the flagged value and its whole-token occurrences in evidence", () => { expect( renderPromptDialog( makePromptPayload({ kind: "bash", request: { ...makePromptPayload().request, surface: "bash", toolName: "bash", value: "ls", matchedPattern: "*", }, evidence: [ { label: "full command", text: "lsof | ls && /usr/bin/lsblk", detail: null, }, ], }), completeViewBudget(200), mark, ).lines, ).toEqual([ "tool : bash", "rule : *", "command : [ls]", "full command : lsof | [ls] && /usr/bin/lsblk", ]); }); it("paints each escaping path rather than the command that referenced them", () => { expect( renderPromptDialog( makePromptPayload({ kind: "bash_external_directory", request: { ...makePromptPayload().request, surface: "external_directory", toolName: "bash", value: "cat /etc/hosts /etc/hostsbackup", matchedPattern: "*", }, evidence: [ { label: "working directory", text: "/repo", detail: null }, { label: "external path", text: "/etc/hosts", detail: null }, ], }), completeViewBudget(200), mark, ).lines, ).toEqual([ "tool : bash", "surface : external_directory", "rule : *", "command : cat [/etc/hosts] /etc/hostsbackup", "working directory : /repo", "external path : [/etc/hosts]", ]); }); it("leaves the text untouched when no paint is supplied", () => { expect( render({ kind: "bash", request: requestFacts({ surface: "bash", toolName: "bash", value: "ls", matchedPattern: "*", }), }), ).toEqual(["tool : bash", "rule : *", "command : ls"]); }); }); describe("a forwarded ask", () => { it("names the requesting subagent and its session", () => { expect( render({ kind: "forwarded", request: requestFacts({ requester: { agentName: "scout", forwarded: true, sessionId: "abc123", }, surface: "bash", toolName: null, value: "cat /etc/hosts", matchedPattern: null, }), }), ).toEqual([ "subagent : scout · session abc123", "surface : bash", "command : cat /etc/hosts", ]); }); it("renders a version-skewed request that names neither agent nor session", () => { expect( render({ kind: "forwarded", request: requestFacts({ requester: { agentName: "", forwarded: true, sessionId: "" }, surface: "read", toolName: null, value: "/tmp/x", matchedPattern: null, }), }), ).toEqual(["subagent : unknown", "surface : read", "value : /tmp/x"]); }); }); });