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 = """