From 50726e4cf3b035535a0c17a21dcc60f3bcde8dfe Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 5 Aug 2026 03:36:50 +0900 Subject: [PATCH] web: show system items in worker console --- .../src/lib/workspace/console/model.test.ts | 54 +++++++++++++++++-- .../src/lib/workspace/console/model.ts | 16 +++++- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/web/workspace/src/lib/workspace/console/model.test.ts b/web/workspace/src/lib/workspace/console/model.test.ts index d1c5253c..8c2487eb 100644 --- a/web/workspace/src/lib/workspace/console/model.test.ts +++ b/web/workspace/src/lib/workspace/console/model.test.ts @@ -718,7 +718,7 @@ Deno.test("projectConsole preserves in-progress assistant protocol stream", () = ); }); -Deno.test("projectConsole keeps protocol lifecycle events out of the console surface", () => { +Deno.test("projectConsole hides lifecycle events and renders system items", () => { const projection = projectConsole([ { eventId: "30", @@ -743,12 +743,24 @@ Deno.test("projectConsole keeps protocol lifecycle events out of the console sur eventId: "34", event: { event: "system_item", - data: { item: { kind: "note", content: "internal" } }, + data: { + item: { + kind: "notification", + message: "Ticket queued", + body: "Reread Ticket 00001KZ6TSGG5 before acting.", + }, + }, } satisfies Event, }, ]); - assertEquals(projection.lines, []); + assertEquals(projection.lines.length, 1); + assertEquals(projection.lines[0].kind, "system"); + assertEquals(projection.lines[0].title, "System · notification"); + assertEquals( + projection.lines[0].body, + "Reread Ticket 00001KZ6TSGG5 before acting.", + ); assertEquals(projection.status, "running"); }); @@ -859,6 +871,42 @@ Deno.test("projectConsole renders snapshot entries and in-flight output", () => ); }); +Deno.test("projectConsole restores system items from snapshot entries", () => { + const projection = projectConsole([{ + eventId: "system-snapshot", + event: { + event: "snapshot", + data: { + entries: [{ + kind: "system_item", + ts: 1, + item: { + kind: "notification", + message: "Worker completed", + body: "Child Worker coder-1 completed.", + }, + }], + greeting: { + worker_name: "Worker", + cwd: "/repo", + provider: "provider", + model: "model", + scope_summary: "bounded", + tools: [], + context_window: 100, + context_tokens: 20, + }, + status: "idle", + }, + } satisfies Event, + }]); + + assertEquals(projection.lines.length, 1); + assertEquals(projection.lines[0].kind, "system"); + assertEquals(projection.lines[0].title, "System · notification"); + assertEquals(projection.lines[0].body, "Child Worker coder-1 completed."); +}); + Deno.test("projectConsole reseeds visible rows from segment rotation", () => { const projection = projectConsole([ { diff --git a/web/workspace/src/lib/workspace/console/model.ts b/web/workspace/src/lib/workspace/console/model.ts index abfa3d52..3b1ec47e 100644 --- a/web/workspace/src/lib/workspace/console/model.ts +++ b/web/workspace/src/lib/workspace/console/model.ts @@ -208,7 +208,7 @@ export function applyProtocolEvent( ); break; case "system_item": - // System items are protocol/internal context, not console output. + next.lines.push(systemItemLine(envelope.eventId, event.data.item)); break; case "text_delta": appendStreaming( @@ -397,6 +397,17 @@ function line( }; } +function systemItemLine(eventId: string, item: unknown): ConsoleLine { + if (!isRecord(item)) { + return line(eventId, "system", "System item", jsonPreview(item)); + } + const itemKind = stringField(item, "kind") ?? "item"; + const title = `System · ${itemKind.replaceAll("_", " ")}`; + const body = stringField(item, "body") ?? stringField(item, "message") ?? + stringField(item, "content") ?? jsonPreview(item); + return line(eventId, "system", title, body); +} + function upsertStatusLine( projection: ConsoleProjection, id: string, @@ -1140,6 +1151,9 @@ function applyLogEntry( ), ); break; + case "system_item": + projection.lines.push(systemItemLine(eventId, entry["item"])); + break; case "assistant_item": case "tool_result": applyLoggedItem(projection, eventId, entry["item"]);