From df22526a0d7cf6cc8e502c5eebecaf89217ec304 Mon Sep 17 00:00:00 2001 From: Hare Date: Wed, 16 Sep 2026 04:25:37 +0900 Subject: [PATCH] fix: render compaction on worker status --- crates/tui/src/app.rs | 28 +++++++------------ .../workspace/console/WorkerRunStatus.svelte | 8 +++++- .../src/lib/workspace/console/model.test.ts | 8 ++---- .../src/lib/workspace/console/model.ts | 23 ++++----------- .../workers/[workerRef]/console/+page.svelte | 1 + 5 files changed, 27 insertions(+), 41 deletions(-) diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index 13bf8ee9..938a205f 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -279,6 +279,7 @@ pub struct App { run_error_messages: Vec, /// Current compaction identity/revision used to fence snapshot/live updates. active_compaction: Option<(String, u64)>, + compaction_progress: Option, /// Presentation-only Internal Worker projections keyed by session identity. /// They are rendered in separate selectable views and never mixed into `blocks`. pub internal_workers: Vec, @@ -367,6 +368,7 @@ impl App { shutdown_confirm: None, blocks: Vec::new(), active_compaction: None, + compaction_progress: None, run_error_messages: Vec::new(), internal_workers: Vec::new(), selected_internal_worker_session_id: None, @@ -1401,17 +1403,7 @@ impl App { } } Event::CompactionProgress { compaction } => { - if compaction.is_some() { - if self.last_streaming_compact_mut().is_none() { - self.blocks.push(Block::Compact(CompactEvent::Streaming { - started_at: Instant::now(), - })); - } - } else if let Some(Block::Compact(CompactEvent::Streaming { .. })) = - self.blocks.last() - { - self.blocks.pop(); - } + self.compaction_progress = compaction; } Event::CompactStart { lifecycle } => { let should_apply = match &self.active_compaction { @@ -1702,11 +1694,7 @@ impl App { } } self.active_compaction = None; - if compaction.is_some() && self.last_streaming_compact_mut().is_none() { - self.blocks.push(Block::Compact(CompactEvent::Streaming { - started_at: Instant::now(), - })); - } + self.compaction_progress = compaction; } fn append_assistant_text(&mut self, text: &str) { @@ -4322,11 +4310,15 @@ mod completion_flow_tests { }), ..InFlightSnapshot::default() }); - assert_eq!(compact_block_count(&app), 1); + assert_eq!(compact_block_count(&app), 0); + assert_eq!( + app.compaction_progress.as_ref().map(|item| item.phase), + Some(protocol::CompactionPhase::Summarizing) + ); app.handle_worker_event(Event::CompactionProgress { compaction: None }); - assert_eq!(compact_block_count(&app), 0); + assert!(app.compaction_progress.is_none()); } #[test] diff --git a/web/workspace/src/lib/workspace/console/WorkerRunStatus.svelte b/web/workspace/src/lib/workspace/console/WorkerRunStatus.svelte index f8438c49..b91b08c1 100644 --- a/web/workspace/src/lib/workspace/console/WorkerRunStatus.svelte +++ b/web/workspace/src/lib/workspace/console/WorkerRunStatus.svelte @@ -7,9 +7,11 @@ requests: number; uploadTokens: number; outputTokens: number; + compaction?: { phase: string } | null; }; - let { startedAtMs, requests, uploadTokens, outputTokens }: Props = $props(); + let { startedAtMs, requests, uploadTokens, outputTokens, compaction = null }: Props = + $props(); let nowMs = $state(Date.now()); $effect(() => { @@ -27,6 +29,10 @@
+ {#if compaction} + Compacting · {compaction.phase} + + {/if} {elapsed} {requests} {requestLabel} diff --git a/web/workspace/src/lib/workspace/console/model.test.ts b/web/workspace/src/lib/workspace/console/model.test.ts index 4e5b118a..d8d27253 100644 --- a/web/workspace/src/lib/workspace/console/model.test.ts +++ b/web/workspace/src/lib/workspace/console/model.test.ts @@ -1092,11 +1092,9 @@ Deno.test("snapshot restores running compaction without staged content", () => { const projection = projectConsole([{ eventId: "snapshot", event: snapshot }]); - assertEquals(projection.lines.length, 1); - assertEquals(projection.lines[0].id, "compaction-runtime"); - assertEquals(projection.lines[0].streaming, true); - assertEquals(projection.lines[0].body, "compacting · summarizing"); - assertEquals(projection.lines[0].body.includes("staged"), false); + assertEquals(projection.lines.length, 0); + assertEquals(projection.compaction?.phase, "summarizing"); + assertEquals(projection.compaction?.trigger, "manual"); }); Deno.test("compaction service activity stays nested in one lifecycle item", () => { diff --git a/web/workspace/src/lib/workspace/console/model.ts b/web/workspace/src/lib/workspace/console/model.ts index 810ea3a9..7d4e0b55 100644 --- a/web/workspace/src/lib/workspace/console/model.ts +++ b/web/workspace/src/lib/workspace/console/model.ts @@ -173,6 +173,7 @@ export type ConsoleProjection = { taskNextId: number; status: string | null; workerState: WorkerStateSnapshot | null; + compaction: InFlightCompaction | null; usage: string | null; runActivity: RunActivityStats; cwd: string | null; @@ -246,6 +247,7 @@ export function emptyConsoleProjection(): ConsoleProjection { taskNextId: 1, status: null, workerState: null, + compaction: null, usage: null, runActivity: emptyRunActivityStats(), cwd: null, @@ -722,23 +724,7 @@ function applyInFlightCompaction( projection: ConsoleProjection, progress: InFlightCompaction | null, ): ConsoleProjection { - const id = "compaction-runtime"; - const lines = projection.lines.filter((line) => line.id !== id); - if (!progress) return { ...projection, lines }; - return { - ...projection, - lines: [ - ...lines, - { - id, - kind: "status", - title: "Compaction", - body: `compacting · ${progress.phase.replaceAll("_", " ")}`, - source: "event", - streaming: true, - }, - ], - }; + return { ...projection, compaction: progress }; } function applyCompactionLifecycle( @@ -863,6 +849,7 @@ export function applyProtocolEvent( taskNextId: projection.taskNextId, status: projection.status, workerState: projection.workerState, + compaction: projection.compaction, usage: projection.usage, runActivity: applyRunActivityEvent( projection.runActivity, @@ -1003,6 +990,7 @@ export function applyProtocolEvent( event.data.in_flight.compaction, ); next.lines = withCompaction.lines; + next.compaction = withCompaction.compaction; } for (const line of next.lines) { const compaction = line.compaction; @@ -2013,6 +2001,7 @@ function snapshotProjectionFromSession( taskNextId: 1, status: null, workerState: null, + compaction: null, usage: null, runActivity: emptyRunActivityStats(), cwd, diff --git a/web/workspace/src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte b/web/workspace/src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte index e6f205ab..2910d25c 100644 --- a/web/workspace/src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte +++ b/web/workspace/src/routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte @@ -1907,6 +1907,7 @@ requests={consoleProjection.runActivity.requests} uploadTokens={consoleProjection.runActivity.uploadTokens} outputTokens={consoleProjection.runActivity.outputTokens} + compaction={consoleProjection.compaction} /> {/if}