From ef5f448900cffb0142602e964c3b1831e09751f0 Mon Sep 17 00:00:00 2001 From: fschrhunt <264049687+fschrhunt@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:06:30 -0500 Subject: [PATCH] fix(app): clear stale automated status rows --- .../Halos/Stores/MissionControlStore.swift | 21 ++++++++++++++++++- .../HalosUITests/GatewayStreamingTests.swift | 16 ++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Sources/Halos/Stores/MissionControlStore.swift b/Sources/Halos/Stores/MissionControlStore.swift index cb7ba98..e7c4e3a 100644 --- a/Sources/Halos/Stores/MissionControlStore.swift +++ b/Sources/Halos/Stores/MissionControlStore.swift @@ -59,6 +59,7 @@ public final class MissionControlStore: ObservableObject { private var lastAssistantTextByRunID: [String: String] = [:] private var statusMessageIDsByRunID: [String: String] = [:] private var runtimeStatusMessageIDsByRunID: [String: String] = [:] + private var automatedRuntimeMessageIDsByRunID: [String: String] = [:] private var workingMessageIDsByRunID: [String: String] = [:] private var pluginMessageIDsByRunID: [String: [String: String]] = [:] private var runStartedAtByRunID: [String: Date] = [:] @@ -1119,6 +1120,7 @@ public final class MissionControlStore: ObservableObject { lastAssistantTextByRunID.removeAll() statusMessageIDsByRunID.removeAll() runtimeStatusMessageIDsByRunID.removeAll() + automatedRuntimeMessageIDsByRunID.removeAll() workingMessageIDsByRunID.removeAll() pluginMessageIDsByRunID.removeAll() runStartedAtByRunID.removeAll() @@ -1170,6 +1172,7 @@ public final class MissionControlStore: ObservableObject { lastAssistantTextByRunID.removeAll() statusMessageIDsByRunID.removeAll() runtimeStatusMessageIDsByRunID.removeAll() + automatedRuntimeMessageIDsByRunID.removeAll() workingMessageIDsByRunID.removeAll() pluginMessageIDsByRunID.removeAll() runStartedAtByRunID.removeAll() @@ -2631,8 +2634,24 @@ public final class MissionControlStore: ObservableObject { private func appendAutomatedRuntimeMessageIfNeeded(_ text: String, runID: String, state: MessageStreamState) -> Bool { guard isAutomatedRuntimeMessage(text) else { return false } + if let id = automatedRuntimeMessageIDsByRunID[runID], + let index = messages.firstIndex(where: { $0.id == id }) { + messages[index] = ActivityMessage( + id: id, + kind: .system, + title: automatedRuntimeTitle(for: text), + body: automatedRuntimeBody(for: text), + createdAt: messages[index].createdAt, + runId: runID, + streamState: state + ) + return true + } + + let id = "\(Date().timeIntervalSince1970)-system-\(runID)" + automatedRuntimeMessageIDsByRunID[runID] = id append(ActivityMessage( - id: "\(Date().timeIntervalSince1970)-system-\(runID)", + id: id, kind: .system, title: automatedRuntimeTitle(for: text), body: automatedRuntimeBody(for: text), diff --git a/Tests/HalosUITests/GatewayStreamingTests.swift b/Tests/HalosUITests/GatewayStreamingTests.swift index 26dce13..1d1116b 100644 --- a/Tests/HalosUITests/GatewayStreamingTests.swift +++ b/Tests/HalosUITests/GatewayStreamingTests.swift @@ -693,6 +693,22 @@ final class GatewayStreamingTests: XCTestCase { XCTAssertEqual(store.messages.first?.body.hasPrefix("An async command"), true) } + func testAutomatedRuntimeFinalUpdatesStreamingNotice() { + let store = makeStore() + let automatedMessage = """ + System: Set it. + Set it. + """ + + store.ingestGatewayEventForTesting(event: "chat", payload: chatPayload(runID: "run-1", state: "delta", text: automatedMessage, role: "user")) + store.ingestGatewayEventForTesting(event: "chat", payload: chatPayload(runID: "run-1", state: "final", text: automatedMessage, role: "user")) + + let systemMessages = store.messages.filter { $0.kind == .system } + XCTAssertEqual(systemMessages.count, 1) + XCTAssertEqual(systemMessages.first?.streamState, .final) + XCTAssertEqual(store.responseMarkerStatus.isActive, false) + } + func testPlainSystemRuntimeMessageIsSystemNotice() { let store = makeStore() let systemMessage = """