From e7e562f982c7db8783ad799b092daf086854e7c0 Mon Sep 17 00:00:00 2001 From: Josh Lehman Date: Wed, 1 Apr 2026 12:30:55 -0700 Subject: [PATCH] fix(ui): gate compaction retry status on success --- ui/src/ui/app-tool-stream.node.test.ts | 44 ++++++++++++++++++++++++-- ui/src/ui/app-tool-stream.ts | 9 ++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/ui/src/ui/app-tool-stream.node.test.ts b/ui/src/ui/app-tool-stream.node.test.ts index d41fa3b1314..ab6a2fe8c5d 100644 --- a/ui/src/ui/app-tool-stream.node.test.ts +++ b/ui/src/ui/app-tool-stream.node.test.ts @@ -166,7 +166,7 @@ describe("app-tool-stream fallback lifecycle handling", () => { stream: "compaction", ts: Date.now(), sessionKey: "main", - data: { phase: "end", willRetry: true }, + data: { phase: "end", willRetry: true, completed: true }, }); expect(host.compactionStatus).toEqual({ @@ -236,7 +236,7 @@ describe("app-tool-stream fallback lifecycle handling", () => { stream: "compaction", ts: Date.now(), sessionKey: "main", - data: { phase: "end", willRetry: true }, + data: { phase: "end", willRetry: true, completed: true }, }); expect(host.compactionStatus).toEqual({ @@ -269,4 +269,44 @@ describe("app-tool-stream fallback lifecycle handling", () => { vi.useRealTimers(); }); + + it("does not surface retrying or complete when retry compaction failed", () => { + vi.useFakeTimers(); + const host = createHost(); + + handleAgentEvent(host, { + runId: "run-1", + seq: 1, + stream: "compaction", + ts: Date.now(), + sessionKey: "main", + data: { phase: "start" }, + }); + + handleAgentEvent(host, { + runId: "run-1", + seq: 2, + stream: "compaction", + ts: Date.now(), + sessionKey: "main", + data: { phase: "end", willRetry: true, completed: false }, + }); + + expect(host.compactionStatus).toBeNull(); + expect(host.compactionClearTimer).toBeNull(); + + handleAgentEvent(host, { + runId: "run-1", + seq: 3, + stream: "lifecycle", + ts: Date.now(), + sessionKey: "main", + data: { phase: "error", error: "boom" }, + }); + + expect(host.compactionStatus).toBeNull(); + expect(host.compactionClearTimer).toBeNull(); + + vi.useRealTimers(); + }); }); diff --git a/ui/src/ui/app-tool-stream.ts b/ui/src/ui/app-tool-stream.ts index 33ad90bba50..0db0c356079 100644 --- a/ui/src/ui/app-tool-stream.ts +++ b/ui/src/ui/app-tool-stream.ts @@ -298,6 +298,7 @@ function setCompactionComplete(host: CompactionHost, runId: string) { export function handleCompactionEvent(host: CompactionHost, payload: AgentEventPayload) { const data = payload.data ?? {}; const phase = typeof data.phase === "string" ? data.phase : ""; + const completed = data.completed === true; clearCompactionTimer(host); @@ -311,7 +312,7 @@ export function handleCompactionEvent(host: CompactionHost, payload: AgentEventP return; } if (phase === "end") { - if (data.willRetry === true) { + if (data.willRetry === true && completed) { // Compaction already succeeded, but the run is still retrying. // Keep that distinct state until the matching lifecycle end arrives. host.compactionStatus = { @@ -322,7 +323,11 @@ export function handleCompactionEvent(host: CompactionHost, payload: AgentEventP }; return; } - setCompactionComplete(host, payload.runId); + if (completed) { + setCompactionComplete(host, payload.runId); + return; + } + host.compactionStatus = null; } }