fix(coding-agent): align activity visibility contract

This commit is contained in:
Daniel Anderson-Little
2026-08-11 17:26:33 -04:00
parent cbd7dc2e70
commit d50e47d2af
10 changed files with 41 additions and 42 deletions
@@ -19,13 +19,13 @@ afterAll(() => {
interface Fixture {
ctx: InteractiveModeContext;
controller: EventController;
showToolActivityWarning: Mock<InteractiveModeContext["showToolActivityWarning"]>;
showWarning: Mock<InteractiveModeContext["showWarning"]>;
/** Components the controller committed to the transcript, in order. */
blocks: unknown[];
}
function createFixture(): Fixture {
const showToolActivityWarning = vi.fn();
const showWarning = vi.fn();
const blocks: unknown[] = [];
const ctx = {
isInitialized: true,
@@ -55,9 +55,9 @@ function createFixture(): Fixture {
toolOutputExpanded: false,
setTodos: vi.fn(),
present: vi.fn(),
showToolActivityWarning,
showWarning,
} as unknown as InteractiveModeContext;
return { ctx, controller: new EventController(ctx), showToolActivityWarning, blocks };
return { ctx, controller: new EventController(ctx), showWarning, blocks };
}
/** A cumulative `message_update` whose content carries the streamed todo toolCall block. */
@@ -106,8 +106,8 @@ describe("EventController + Cursor todo bridge", () => {
todoFailure(`\u001b[31mrejected:\u001b[0m\tid 4\r\n\tconflicts with ${"x".repeat(400)}`),
);
expect(f.showToolActivityWarning).toHaveBeenCalledTimes(1);
const message = f.showToolActivityWarning.mock.calls[0]![0] as string;
expect(f.showWarning).toHaveBeenCalledTimes(1);
const message = f.showWarning.mock.calls[0]![0] as string;
expect(message).not.toContain("\t");
expect(message).not.toContain("\n");
// ANSI and other C0/C1 controls reach the terminal verbatim through
@@ -117,6 +117,7 @@ describe("EventController + Cursor todo bridge", () => {
// The prefix is ours and fixed; only the untrusted tail is bounded.
expect(message.startsWith("Todo update failed: ")).toBe(true);
expect(Bun.stringWidth(message.slice("Todo update failed: ".length))).toBeLessThanOrEqual(TRUNCATE_LENGTHS.LINE);
expect(f.showWarning.mock.calls[0]![1]).toEqual({ toolActivity: true });
});
it("keeps the standalone hint when the failure carries no text", async () => {
@@ -126,9 +127,9 @@ describe("EventController + Cursor todo bridge", () => {
await f.controller.handleEvent(todoFailure(""));
expect(f.showToolActivityWarning).toHaveBeenCalledWith(
"Todo update failed. Progress may be stale until todo succeeds.",
);
expect(f.showWarning).toHaveBeenCalledWith("Todo update failed. Progress may be stale until todo succeeds.", {
toolActivity: true,
});
});
it("settles a card whose completion arrived before the streamed block created it", async () => {
@@ -165,13 +166,13 @@ describe("EventController + Cursor todo bridge", () => {
const f = createFixture();
await f.controller.handleEvent(todoFailure("boom"));
expect(f.showToolActivityWarning).toHaveBeenCalledTimes(1);
expect(f.showWarning).toHaveBeenCalledTimes(1);
await f.controller.handleEvent(streamedTodoBlock("todo-1"));
expect(f.blocks).toHaveLength(1);
expect(f.ctx.pendingTools.size).toBe(0);
expect(f.showToolActivityWarning).toHaveBeenCalledTimes(1);
expect(f.showWarning).toHaveBeenCalledTimes(1);
});
it("refreshes the panel exactly once when a successful completion is replayed", async () => {