mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
feat: vendor permission system source
This commit is contained in:
@@ -0,0 +1,645 @@
|
||||
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<typeof makePromptPayload>[0],
|
||||
): readonly string[] {
|
||||
return renderPromptDialog(makePromptPayload(overrides), WIDE).lines;
|
||||
}
|
||||
|
||||
/** A payload whose request facts override the fixture's defaults. */
|
||||
function requestFacts(
|
||||
overrides: Partial<ReturnType<typeof makePromptPayload>["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: "<indirection-bash-wrapper>",
|
||||
executedUnit: "grep foo",
|
||||
}),
|
||||
}),
|
||||
).toEqual([
|
||||
"tool : bash",
|
||||
"rule : <indirection-bash-wrapper>",
|
||||
"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"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user