c224ef4df5
- Standardized elision markers across all tool outputs and filters to use cohesive `[...N [type] elided...]`, `[...Nln elided...]`, and `[...xB elided...]` syntax. - Updated documentation, prompts, and test expectations to reflect the unified elision format. - Improved transcript viewer robustness by preventing content aliasing through path-inclusive signature hashing. - Added logic to clear stale transcript content when associated session files are deleted, accompanied by verifying test cases.
172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
/**
|
|
* Regression: the fullscreen transcript viewer must align the header, body, and
|
|
* footer on a single shared gutter. The transcript components carry their own
|
|
* 1-column left pad, so the viewer must NOT add a second outer gutter to body
|
|
* rows — doing so shifted the content one column right of the "Agent Hub" title
|
|
* (the reported "first char off / title shift"). Scrolling must also move the
|
|
* visible window.
|
|
*/
|
|
import { beforeEach, describe, expect, it } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { AgentTranscriptViewer } from "@oh-my-pi/pi-coding-agent/modes/components/agent-transcript-viewer";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import { AgentRegistry } from "@oh-my-pi/pi-coding-agent/registry/agent-registry";
|
|
import { CURRENT_SESSION_VERSION } from "@oh-my-pi/pi-coding-agent/session/session-entries";
|
|
|
|
const TS = new Date().toISOString();
|
|
|
|
function buildJsonl(): string {
|
|
const usage = {
|
|
input: 1,
|
|
output: 1,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
totalTokens: 2,
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
};
|
|
const lines = [
|
|
JSON.stringify({ type: "session", version: CURRENT_SESSION_VERSION, id: "adv", timestamp: TS, cwd: "/tmp" }),
|
|
];
|
|
lines.push(
|
|
JSON.stringify({
|
|
type: "message",
|
|
id: "u0",
|
|
parentId: null,
|
|
timestamp: TS,
|
|
message: { role: "user", synthetic: true, attribution: "agent", content: "PROMPTMARKER", timestamp: 0 },
|
|
}),
|
|
);
|
|
for (let i = 0; i < 40; i++) {
|
|
lines.push(
|
|
JSON.stringify({
|
|
type: "message",
|
|
id: `a${i}`,
|
|
parentId: null,
|
|
timestamp: TS,
|
|
message: {
|
|
role: "assistant",
|
|
content: [{ type: "text", text: `Reviewing step ${i}.` }],
|
|
api: "anthropic-messages",
|
|
provider: "anthropic",
|
|
model: "gpt-5.5",
|
|
usage,
|
|
stopReason: "stop",
|
|
timestamp: i,
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
return `${lines.join("\n")}\n`;
|
|
}
|
|
|
|
function makeViewer(file: string) {
|
|
const agents = new AgentRegistry();
|
|
agents.register({
|
|
id: "Main/advisor",
|
|
displayName: "advisor",
|
|
kind: "advisor",
|
|
parentId: "Main",
|
|
session: null,
|
|
sessionFile: file,
|
|
status: "parked",
|
|
});
|
|
return new AgentTranscriptViewer({
|
|
agentId: "Main/advisor",
|
|
registry: agents,
|
|
ui: { requestRender: () => {}, requestComponentRender: () => {} } as never,
|
|
cwd: "/tmp",
|
|
expandKeys: ["ctrl+o"],
|
|
hubKeys: ["ctrl+s"],
|
|
requestRender: () => {},
|
|
onClose: () => {},
|
|
onHubClose: () => {},
|
|
});
|
|
}
|
|
|
|
/** Leading-space count of a stripped line (its content gutter). */
|
|
function gutter(line: string): number {
|
|
const stripped = Bun.stripANSI(line);
|
|
return stripped.length - stripped.trimStart().length;
|
|
}
|
|
|
|
function withViewer(fn: (viewer: AgentTranscriptViewer) => void): void {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "adv-view-"));
|
|
const file = path.join(dir, "__advisor.jsonl");
|
|
fs.writeFileSync(file, buildJsonl());
|
|
try {
|
|
fn(makeViewer(file));
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
describe("AgentTranscriptViewer", () => {
|
|
beforeEach(async () => {
|
|
resetSettingsForTest();
|
|
await Settings.init({ inMemory: true });
|
|
initTheme();
|
|
});
|
|
|
|
it("aligns the title and body content on the same gutter", () => {
|
|
withViewer(viewer => {
|
|
viewer.render(80); // populate the scroll view before navigating
|
|
viewer.handleInput("g"); // scroll to top so the first message is visible
|
|
const lines = viewer.render(80).map(l => Bun.stripANSI(l));
|
|
const titleLine = lines.find(l => l.includes("Agent Hub"));
|
|
const bodyLine = lines.find(l => l.includes("PROMPTMARKER"));
|
|
expect(titleLine).toBeDefined();
|
|
expect(bodyLine).toBeDefined();
|
|
// The body must not sit one column right of the title.
|
|
expect(gutter(bodyLine!)).toBe(gutter(titleLine!));
|
|
});
|
|
});
|
|
|
|
it("scrolls the visible window with j/k and g/G", () => {
|
|
withViewer(viewer => {
|
|
const atBottom = viewer
|
|
.render(80)
|
|
.map(l => Bun.stripANSI(l))
|
|
.join("\n");
|
|
viewer.handleInput("g");
|
|
const atTop = viewer
|
|
.render(80)
|
|
.map(l => Bun.stripANSI(l))
|
|
.join("\n");
|
|
expect(atTop).not.toEqual(atBottom);
|
|
expect(atTop).toContain("PROMPTMARKER");
|
|
expect(atBottom).not.toContain("PROMPTMARKER");
|
|
});
|
|
});
|
|
|
|
it("clears stale content when the transcript file is deleted while open", async () => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "adv-view-"));
|
|
const file = path.join(dir, "__advisor.jsonl");
|
|
fs.writeFileSync(file, buildJsonl());
|
|
const viewer = makeViewer(file);
|
|
const body = () =>
|
|
viewer
|
|
.render(80)
|
|
.map(l => Bun.stripANSI(l))
|
|
.join("\n");
|
|
try {
|
|
viewer.render(80);
|
|
viewer.handleInput("g");
|
|
expect(body()).toContain("PROMPTMARKER");
|
|
|
|
fs.rmSync(file);
|
|
// Poll until the viewer's own poll timer re-stats and clears (deadline-bounded).
|
|
const deadline = Date.now() + 5000;
|
|
while (body().includes("PROMPTMARKER") && Date.now() < deadline) {
|
|
await Bun.sleep(50);
|
|
}
|
|
expect(body()).not.toContain("PROMPTMARKER");
|
|
} finally {
|
|
viewer.dispose();
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|