149 lines
4.9 KiB
TypeScript
149 lines
4.9 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
|
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import type { EvalStatusEvent, EvalToolDetails } from "@oh-my-pi/pi-coding-agent/eval/types";
|
|
import { getThemeByName, setThemeInstance, type Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import { evalToolRenderer } from "@oh-my-pi/pi-coding-agent/tools/eval";
|
|
|
|
/**
|
|
* Defends the contract that `agent()` calls inside an eval cell surface as a
|
|
* live, Task-tool-style progress tree drawn *below* the notebook (code cell
|
|
* box) — not buried inside the box's collapsed "Status" list, and not deferred
|
|
* to the final result.
|
|
*/
|
|
describe("eval renderer: agent() progress below the cell box", () => {
|
|
let theme: Theme;
|
|
|
|
beforeAll(async () => {
|
|
resetSettingsForTest();
|
|
await Settings.init({ inMemory: true, cwd: process.cwd() });
|
|
theme = (await getThemeByName("dark"))!;
|
|
expect(theme).toBeDefined();
|
|
setThemeInstance(theme);
|
|
});
|
|
|
|
afterAll(() => {
|
|
resetSettingsForTest();
|
|
});
|
|
|
|
function render(statusEvents: EvalStatusEvent[], status: "running" | "complete" = "running"): string[] {
|
|
const details: EvalToolDetails = {
|
|
language: "python",
|
|
languages: ["python"],
|
|
cells: [
|
|
{
|
|
index: 0,
|
|
title: "Investigate",
|
|
code: "results = parallel([...])",
|
|
language: "python",
|
|
output: "",
|
|
status,
|
|
statusEvents,
|
|
},
|
|
],
|
|
};
|
|
const component = evalToolRenderer.renderResult(
|
|
{ content: [{ type: "text", text: "" }], details },
|
|
{ expanded: false, isPartial: status === "running", spinnerFrame: 0 },
|
|
theme,
|
|
);
|
|
return Bun.stripANSI(component.render(120).join("\n")).split("\n");
|
|
}
|
|
|
|
/** Index of the box's closing border (bottom-right corner glyph). */
|
|
function boxBottomIndex(lines: string[]): number {
|
|
return lines.findIndex(line => line.includes(theme.boxRound.bottomRight));
|
|
}
|
|
|
|
it("draws a running subagent below the box with its current tool and intent", () => {
|
|
const event: EvalStatusEvent = {
|
|
op: "agent",
|
|
id: "0-Scout",
|
|
agent: "task",
|
|
status: "running",
|
|
currentTool: "read",
|
|
currentToolArgs: "config.ts",
|
|
lastIntent: "Reading config",
|
|
taskPreview: "investigate the bug",
|
|
toolCount: 4,
|
|
contextTokens: 5000,
|
|
contextWindow: 200000,
|
|
cost: 0.03,
|
|
durationMs: 800,
|
|
model: "p/model",
|
|
};
|
|
|
|
const lines = render([event]);
|
|
const bottom = boxBottomIndex(lines);
|
|
expect(bottom).toBeGreaterThanOrEqual(0);
|
|
|
|
const idLine = lines.findIndex(line => line.includes("0-Scout"));
|
|
// The subagent id renders strictly *below* the closing box border.
|
|
expect(idLine).toBeGreaterThan(bottom);
|
|
|
|
const below = lines.slice(bottom + 1).join("\n");
|
|
const inside = lines.slice(0, bottom + 1).join("\n");
|
|
expect(below).toContain("0-Scout");
|
|
expect(below).toContain("read");
|
|
expect(below).toContain("Reading config");
|
|
// Agent progress is NOT folded into the box's Status section.
|
|
expect(inside).not.toContain("0-Scout");
|
|
expect(inside).not.toContain("Reading config");
|
|
});
|
|
|
|
it("keeps full stats on a completed subagent below the box", () => {
|
|
const event: EvalStatusEvent = {
|
|
op: "agent",
|
|
id: "0-Scout",
|
|
agent: "task",
|
|
status: "completed",
|
|
toolCount: 7,
|
|
contextTokens: 8000,
|
|
contextWindow: 200000,
|
|
cost: 0.06,
|
|
durationMs: 1500,
|
|
model: "p/model",
|
|
};
|
|
|
|
const lines = render([event], "complete");
|
|
const bottom = boxBottomIndex(lines);
|
|
const idLine = lines.findIndex(line => line.includes("0-Scout"));
|
|
expect(idLine).toBeGreaterThan(bottom);
|
|
|
|
const below = lines.slice(bottom + 1).join("\n");
|
|
// Cost stat survives the completed snapshot.
|
|
expect(below).toContain("$0.06");
|
|
});
|
|
|
|
it("renders one line per subagent for a parallel fan-out", () => {
|
|
const events: EvalStatusEvent[] = [
|
|
{ op: "agent", id: "0-Alpha", agent: "task", status: "running", lastIntent: "scanning" },
|
|
{ op: "agent", id: "1-Beta", agent: "task", status: "completed", toolCount: 3, durationMs: 900 },
|
|
{ op: "agent", id: "2-Gamma", agent: "task", status: "running", currentTool: "search" },
|
|
];
|
|
|
|
const lines = render(events);
|
|
const below = lines.slice(boxBottomIndex(lines) + 1).join("\n");
|
|
expect(below).toContain("0-Alpha");
|
|
expect(below).toContain("1-Beta");
|
|
expect(below).toContain("2-Gamma");
|
|
});
|
|
|
|
it("still folds non-agent status events into the box Status section", () => {
|
|
const events: EvalStatusEvent[] = [
|
|
{ op: "read", path: "/tmp/file.ts", chars: 1200 },
|
|
{ op: "agent", id: "0-Scout", agent: "task", status: "running", lastIntent: "thinking" },
|
|
];
|
|
|
|
const lines = render(events);
|
|
const bottom = boxBottomIndex(lines);
|
|
const inside = lines.slice(0, bottom + 1).join("\n");
|
|
const below = lines.slice(bottom + 1).join("\n");
|
|
|
|
// Discrete ops stay inside the box; agent progress renders below it.
|
|
expect(inside).toContain("read");
|
|
expect(inside).toContain("file.ts");
|
|
expect(inside).not.toContain("0-Scout");
|
|
expect(below).toContain("0-Scout");
|
|
});
|
|
});
|