mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
658 lines
21 KiB
TypeScript
658 lines
21 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { join } from "node:path";
|
|
|
|
import { cloneDefaultConfig, mock, runTest } from "./test-helpers.test.ts";
|
|
|
|
const TEST_AGENT_DIR = "/tmp/.pi/agent";
|
|
|
|
mock.module("@earendil-works/pi-coding-agent", {
|
|
namedExports: {
|
|
getAgentDir: () => TEST_AGENT_DIR,
|
|
},
|
|
});
|
|
|
|
const { compactToolResult } = await import("./output-compactor.ts");
|
|
|
|
function buildReadContent(lineCount: number): string {
|
|
const lines: string[] = [];
|
|
for (let index = 0; index < lineCount; index += 1) {
|
|
if (index % 2 === 0) {
|
|
lines.push(`// comment ${index}`);
|
|
} else {
|
|
lines.push(`const value${index} = ${index};`);
|
|
}
|
|
}
|
|
return `${lines.join("\n")}\n`;
|
|
}
|
|
|
|
function setReadCompaction(config: ReturnType<typeof cloneDefaultConfig>, enabled: boolean): void {
|
|
config.outputCompaction.readCompaction = { enabled };
|
|
}
|
|
|
|
function firstTextBlock(content: unknown[] | undefined): string {
|
|
if (!Array.isArray(content) || content.length === 0) {
|
|
return "";
|
|
}
|
|
const first = content[0] as { type?: string; text?: string };
|
|
if (first?.type !== "text" || typeof first.text !== "string") {
|
|
return "";
|
|
}
|
|
return first.text;
|
|
}
|
|
|
|
const OUTPUT_EMOJI_MARKERS = ["✓", "✔", "❌", "⚠️", "⚠", "📋", "📄", "🔍", "✅", "⏭️", "📌", "📝", "❓", "•"];
|
|
|
|
function compactBashOutput(command: string, text: string): string {
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "bash",
|
|
input: { command },
|
|
content: [{ type: "text", text }],
|
|
},
|
|
cloneDefaultConfig(),
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
return firstTextBlock(result.content);
|
|
}
|
|
|
|
function assertNoOutputEmoji(text: string): void {
|
|
for (const marker of OUTPUT_EMOJI_MARKERS) {
|
|
assert.equal(text.includes(marker), false, `Unexpected output emoji marker: ${marker}`);
|
|
}
|
|
}
|
|
|
|
function assertNoPartialHashlineAnchors(text: string): void {
|
|
for (const line of text.split(/\r?\n/)) {
|
|
if (/^\s*\d+\s*#[A-Za-z0-9_-]{2,32}:/.test(line)) {
|
|
assert.equal(line.endsWith("..."), false, `Anchor line was partially truncated: ${line}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
runTest("ssh_bash output uses Bash compaction while remote search output remains untouched", () => {
|
|
const config = cloneDefaultConfig();
|
|
const sshBash = compactToolResult(
|
|
{
|
|
toolName: "ssh_bash",
|
|
input: { command: "npm test" },
|
|
content: [{ type: "text", text: "\x1B[32mTests: 2 passed\x1B[0m\n" }],
|
|
},
|
|
config,
|
|
);
|
|
assert.equal(sshBash.changed, true);
|
|
assert.ok(sshBash.techniques.includes("ansi"));
|
|
assert.equal(firstTextBlock(sshBash.content).includes("\x1B"), false);
|
|
|
|
for (const toolName of ["ssh_find", "ssh_grep"]) {
|
|
const search = compactToolResult(
|
|
{
|
|
toolName,
|
|
input: { pattern: "TODO" },
|
|
content: [{ type: "text", text: "\x1B[31mremote result\x1B[0m\n" }],
|
|
},
|
|
config,
|
|
);
|
|
assert.equal(search.changed, false);
|
|
assert.deepEqual(search.techniques, []);
|
|
}
|
|
});
|
|
|
|
runTest("precision read with offset keeps exact output (no source/smart/hard truncation)", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts", offset: 1 },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("precision read with limit keeps exact output", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts", limit: 200 },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("default read output stays exact when read compaction is disabled by default", () => {
|
|
const config = cloneDefaultConfig();
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "aggressive";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("normal read compacts and adds banner when read compaction is enabled", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.ok(compacted.startsWith("[RTK compacted output:"));
|
|
assert.ok(compacted.includes("source:minimal"));
|
|
});
|
|
|
|
runTest("line-anchor read output compacts without corrupting LINE#HASH anchors", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 5000;
|
|
|
|
const content = Array.from({ length: 120 }, (_value, index) => {
|
|
const lineNumber = index + 1;
|
|
const sourceLine = lineNumber % 2 === 0 ? `const value${lineNumber} = ${lineNumber};` : `// comment ${lineNumber}`;
|
|
return `${String(lineNumber).padStart(3, " ")}#ZP:${sourceLine}`;
|
|
}).join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.ok(compacted.startsWith("[RTK compacted output:"));
|
|
assert.ok(compacted.includes("source:minimal"));
|
|
assert.match(compacted, /\n\s*2#ZP:const value2 = 2;/);
|
|
assert.equal(compacted.includes("#ZP:// comment"), false);
|
|
assertNoPartialHashlineAnchors(compacted);
|
|
});
|
|
|
|
runTest("colon-pipe anchor read output compacts without requiring hashline extension", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 5000;
|
|
|
|
const content = [
|
|
"Read sample.ts: 120 lines",
|
|
"",
|
|
...Array.from({ length: 120 }, (_value, index) => {
|
|
const lineNumber = index + 1;
|
|
const sourceLine = lineNumber % 2 === 0 ? `const value${lineNumber} = ${lineNumber};` : `// comment ${lineNumber}`;
|
|
return `${lineNumber}:${(lineNumber % 256).toString(16).padStart(2, "0")}|${sourceLine}`;
|
|
}),
|
|
].join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.ok(compacted.includes("Read sample.ts: 120 lines"));
|
|
assert.match(compacted, /\n2:02\|const value2 = 2;/);
|
|
assert.equal(compacted.includes("|// comment"), false);
|
|
});
|
|
|
|
runTest("compact LINEHASH pipe anchors from oh-my-pi style reads", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 5000;
|
|
|
|
const content = Array.from({ length: 120 }, (_value, index) => {
|
|
const lineNumber = index + 1;
|
|
const hash = lineNumber % 2 === 0 ? "sr" : "ab";
|
|
const sourceLine = lineNumber % 2 === 0 ? `const value${lineNumber} = ${lineNumber};` : `// comment ${lineNumber}`;
|
|
return `${lineNumber}${hash}|${sourceLine}`;
|
|
}).join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.match(compacted, /\n2sr\|const value2 = 2;/);
|
|
assert.equal(compacted.includes("|// comment"), false);
|
|
});
|
|
|
|
runTest("compact hashline-tools file wrapper while preserving non-anchor wrapper lines", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 5000;
|
|
|
|
const content = [
|
|
"<file>",
|
|
...Array.from({ length: 120 }, (_value, index) => {
|
|
const lineNumber = index + 1;
|
|
const sourceLine = lineNumber % 2 === 0 ? `const value${lineNumber} = ${lineNumber};` : `// comment ${lineNumber}`;
|
|
return `${lineNumber}#ZM:${sourceLine}`;
|
|
}),
|
|
"",
|
|
"(End of file - 120 total lines)",
|
|
"</file>",
|
|
].join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.ok(compacted.includes("<file>"));
|
|
assert.ok(compacted.includes("(End of file - 120 total lines)"));
|
|
assert.ok(compacted.includes("</file>"));
|
|
assert.match(compacted, /\n2#ZM:const value2 = 2;/);
|
|
assert.equal(compacted.includes("#ZM:// comment"), false);
|
|
});
|
|
|
|
runTest("anchor-safe read hard truncation preserves whole hashline anchors", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = false;
|
|
config.outputCompaction.smartTruncate.enabled = false;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 350;
|
|
|
|
const content = Array.from({ length: 120 }, (_value, index) => {
|
|
const lineNumber = index + 1;
|
|
return `${lineNumber}#ZP:const value${lineNumber} = "${"x".repeat(40)}";`;
|
|
}).join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("truncate"));
|
|
|
|
const compacted = firstTextBlock(result.content);
|
|
assert.ok(compacted.includes("anchor-safe truncate"));
|
|
assertNoPartialHashlineAnchors(compacted);
|
|
});
|
|
|
|
runTest("incidental single anchor-like line does not disable normal read compaction", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = [`1#ZP:not an anchored read`, buildReadContent(120)].join("\n");
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal") || result.techniques.includes("smart-truncate"));
|
|
});
|
|
|
|
runTest("short read output stays exact below threshold", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
const content = buildReadContent(40);
|
|
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("read output stays exact at the 80-line boundary with trailing newline", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(80);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("read output compacts once the content exceeds the 80-line exactness threshold when read compaction is enabled", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(81);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, true);
|
|
assert.ok(result.techniques.includes("source:minimal") || result.techniques.includes("smart-truncate"));
|
|
});
|
|
|
|
runTest("source file reads skip lossy source filtering when truncation safeguards are not needed", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.sourceCodeFilteringEnabled = true;
|
|
config.outputCompaction.sourceCodeFiltering = "minimal";
|
|
config.outputCompaction.smartTruncate.enabled = false;
|
|
config.outputCompaction.truncate.enabled = false;
|
|
|
|
const content = buildReadContent(120);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "sample.ts" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
assert.equal(firstTextBlock(result.content), "");
|
|
});
|
|
|
|
runTest("skill reads stay exact when preserveExactSkillReads is enabled for user skills", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.preserveExactSkillReads = true;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: join(TEST_AGENT_DIR, "skills", "example", "SKILL.md") },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("project .pi skill reads stay exact when preserveExactSkillReads is enabled", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.preserveExactSkillReads = true;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: ".pi/skills/example/SKILL.md" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("ancestor .agents skill reads stay exact when preserveExactSkillReads is enabled", () => {
|
|
const config = cloneDefaultConfig();
|
|
setReadCompaction(config, true);
|
|
config.outputCompaction.preserveExactSkillReads = true;
|
|
config.outputCompaction.truncate.enabled = true;
|
|
config.outputCompaction.truncate.maxChars = 500;
|
|
config.outputCompaction.smartTruncate.enabled = true;
|
|
config.outputCompaction.smartTruncate.maxLines = 40;
|
|
|
|
const content = buildReadContent(220);
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "read",
|
|
input: { path: "../.agents/skills/example/SKILL.md" },
|
|
content: [{ type: "text", text: content }],
|
|
},
|
|
config,
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("build output uses plain-text status markers", () => {
|
|
const compacted = compactBashOutput("npm run build", "Compiling app v0.1.0\n");
|
|
|
|
assert.equal(compacted, "[OK] Build successful (1 units compiled)");
|
|
assertNoOutputEmoji(compacted);
|
|
});
|
|
|
|
runTest("git status output uses plain-text labels", () => {
|
|
const compacted = compactBashOutput(
|
|
"git status --short --branch",
|
|
"## main...origin/main\nM staged.ts\n M modified.ts\n?? new.ts\nUU conflict.ts\n",
|
|
);
|
|
|
|
assert.ok(compacted.startsWith("Branch: main\n"));
|
|
assert.ok(compacted.includes("Staged: 1 files\n staged.ts\n"));
|
|
assert.ok(compacted.includes("Modified: 1 files\n modified.ts\n"));
|
|
assert.ok(compacted.includes("Untracked: 1 files\n new.ts\n"));
|
|
assert.ok(compacted.includes("Conflicts: 1 files"));
|
|
assertNoOutputEmoji(compacted);
|
|
});
|
|
|
|
runTest("git diff output uses plain-text file markers", () => {
|
|
const compacted = compactBashOutput(
|
|
"git diff",
|
|
"diff --git a/src/example.ts b/src/example.ts\n@@ -1 +1 @@\n-oldValue\n+newValue\n",
|
|
);
|
|
|
|
assert.ok(compacted.includes("\n> src/example.ts\n"));
|
|
assertNoOutputEmoji(compacted);
|
|
});
|
|
|
|
runTest("linter success output uses plain-text status markers", () => {
|
|
const compacted = compactBashOutput("npx eslint .", "");
|
|
|
|
assert.equal(compacted, "[OK] ESLint: No issues found");
|
|
assertNoOutputEmoji(compacted);
|
|
});
|
|
|
|
runTest("test output uses plain-text labels and bullets", () => {
|
|
const compacted = compactBashOutput(
|
|
"bun test",
|
|
"3 passed, 1 failed, 2 skipped\nFAIL src/example.test.ts\n Expected: true\n Received: false\n\n\n",
|
|
);
|
|
|
|
assert.ok(compacted.includes("Test Results:"));
|
|
assert.ok(compacted.includes("PASS: 3 passed"));
|
|
assert.ok(compacted.includes("FAIL: 1 failed"));
|
|
assert.ok(compacted.includes("SKIP: 2 skipped"));
|
|
assert.ok(compacted.includes(" - FAIL src/example.test.ts"));
|
|
assertNoOutputEmoji(compacted);
|
|
});
|
|
|
|
runTest("all grep output stays intact because FFF owns search", () => {
|
|
const text = "src/a.ts [modified in git]\n 1: const match = true;\n\n[Continue with cursor=\"fff_c1\"]";
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "grep",
|
|
input: { pattern: "match" },
|
|
content: [{ type: "text", text }],
|
|
},
|
|
cloneDefaultConfig(),
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.equal(result.content, undefined);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("git diff compaction skips already-compacted RTK-shaped output", () => {
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "bash",
|
|
input: { command: "git diff -- agent/extensions/pi-mcp-adapter/package.json" },
|
|
content: [{ type: "text", text: "agent/extensions/pi-mcp-adapter/package.json | 2 +-\n\n--- Changes ---\n\n> agent/extensions/pi-mcp-adapter/package.json\n @@ -38,7 +38,7 @@\n - \"@earendil-works/pi-coding-agent\": \"^0.58.1\",\n" }],
|
|
},
|
|
cloneDefaultConfig(),
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.equal(result.content, undefined);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("non-hook RTK warnings are preserved verbatim", () => {
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "bash",
|
|
input: { command: "FOO=1 rtk git status" },
|
|
content: [{ type: "text", text: "[rtk] warning: builtin filters: parse failure\n\nworking tree clean\n" }],
|
|
},
|
|
cloneDefaultConfig(),
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.equal(result.content, undefined);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
runTest("quoted hook warning text is preserved as payload", () => {
|
|
const quotedHookText = 'const warning = "No hook installed — run `rtk init -g` for automatic token savings";\n';
|
|
const result = compactToolResult(
|
|
{
|
|
toolName: "bash",
|
|
input: { command: "echo probe" },
|
|
content: [{ type: "text", text: quotedHookText }],
|
|
},
|
|
cloneDefaultConfig(),
|
|
);
|
|
|
|
assert.equal(result.changed, false);
|
|
assert.equal(result.content, undefined);
|
|
assert.deepEqual(result.techniques, []);
|
|
});
|
|
|
|
console.log("All output-compactor tests passed.");
|