ux(coding-agent): added ? fallback for %/window context in status/task
- Added formatContextUsage to render context as `%/window` with `?` fallback across status and task views. - Updated task renderCall to show dispatched agents as tree entries with `Tasks (2)` header and `#3` fallback. - Capped task preview collapse at 12 entries and added `... N more agents` overflow messaging. - Replaced hard-coded dot separators with `theme.sep.dot` in subagent cost and context output. - Added tests for streaming task preview rendering and updated nested-live expectations for percent/context output.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
||||
import { getThemeByName, setThemeInstance, type Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
||||
import type { TaskParams } from "@oh-my-pi/pi-coding-agent/task";
|
||||
import { taskToolRenderer } from "@oh-my-pi/pi-coding-agent/task/render";
|
||||
|
||||
describe("task renderer: streaming call preview", () => {
|
||||
let theme: Theme;
|
||||
|
||||
beforeAll(async () => {
|
||||
resetSettingsForTest();
|
||||
await Settings.init({ inMemory: true, cwd: process.cwd() });
|
||||
const resolved = await getThemeByName("dark");
|
||||
expect(resolved).toBeDefined();
|
||||
theme = resolved!;
|
||||
setThemeInstance(theme);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
resetSettingsForTest();
|
||||
});
|
||||
|
||||
function render(args: TaskParams, expanded = false): string {
|
||||
const component = taskToolRenderer.renderCall(args, { expanded, isPartial: true }, theme);
|
||||
return Bun.stripANSI(component.render(160).join("\n"));
|
||||
}
|
||||
|
||||
// The preview must surface each agent's id + ui description so the user can
|
||||
// see which agents are being dispatched, not a bare "N agents" count.
|
||||
it("lists each task's id and description instead of only a count", () => {
|
||||
const args: TaskParams = {
|
||||
agent: "reviewer",
|
||||
tasks: [
|
||||
{ id: "ReviewAuth", description: "Audit the auth module", assignment: "..." },
|
||||
{ id: "ReviewDb", description: "Audit the db layer", assignment: "..." },
|
||||
],
|
||||
};
|
||||
const out = render(args);
|
||||
|
||||
expect(out).toContain("ReviewAuth");
|
||||
expect(out).toContain("Audit the auth module");
|
||||
expect(out).toContain("ReviewDb");
|
||||
expect(out).toContain("Audit the db layer");
|
||||
// Count is kept as a compact header, but the old flat "N agents" line is gone.
|
||||
expect(out).toContain("Tasks (2)");
|
||||
expect(out).not.toContain("2 agents");
|
||||
});
|
||||
|
||||
it("renders a partially-streamed entry without a description and missing trailing entry", () => {
|
||||
const args = {
|
||||
agent: "task",
|
||||
// Trailing entry mimics streaming JSON: id arrived, description not yet,
|
||||
// plus a not-yet-materialized slot.
|
||||
tasks: [{ id: "First", description: "Do the first thing", assignment: "..." }, { id: "Second" }, undefined],
|
||||
} as unknown as TaskParams;
|
||||
|
||||
const out = render(args);
|
||||
|
||||
expect(out).toContain("First");
|
||||
expect(out).toContain("Do the first thing");
|
||||
expect(out).toContain("Second");
|
||||
// Missing-id slot falls back to a positional placeholder rather than crashing.
|
||||
expect(out).toContain("#3");
|
||||
expect(out).toContain("Tasks (3)");
|
||||
});
|
||||
|
||||
it("caps the collapsed list and reports the overflow as agents", () => {
|
||||
const tasks = Array.from({ length: 15 }, (_, i) => ({
|
||||
id: `Agent${i + 1}`,
|
||||
description: `Task ${i + 1}`,
|
||||
assignment: "...",
|
||||
}));
|
||||
const args: TaskParams = { agent: "task", tasks };
|
||||
|
||||
const collapsed = render(args, false);
|
||||
expect(collapsed).toContain("Agent1");
|
||||
expect(collapsed).toContain("Agent12");
|
||||
expect(collapsed).not.toContain("Agent13");
|
||||
expect(collapsed).toContain("3 more agents");
|
||||
|
||||
const expanded = render(args, true);
|
||||
expect(expanded).toContain("Agent13");
|
||||
expect(expanded).toContain("Agent15");
|
||||
expect(expanded).not.toContain("more agents");
|
||||
});
|
||||
|
||||
it("keeps the isolation flag as the final child after the task list", () => {
|
||||
const args: TaskParams = {
|
||||
agent: "task",
|
||||
isolated: true,
|
||||
tasks: [{ id: "Only", description: "Single task", assignment: "..." }],
|
||||
};
|
||||
const out = render(args);
|
||||
const lines = out.split("\n");
|
||||
|
||||
expect(out).toContain("Only");
|
||||
expect(out).toContain("Isolated");
|
||||
// Isolation flag is rendered last, after every task entry.
|
||||
expect(lines.at(-1)).toContain("Isolated");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user