Files
openclaw/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+RunSnapshot.swift
Peter Steinberger 60920998c0 feat(apps): migrate iOS/macOS plan surface to the durable progress card (#125442)
* feat(apps): migrate iOS/macOS plan surface to the durable progress card

Replace the legacy stream:"plan" agent-event pipeline (runId-scoped state,
run-gated pill) with the sessionKey-scoped progress-card store: the shared
chat surface now renders progressCard.get snapshots, refetches on
progressCard.changed pokes with revision dedupe, clears on null-revision
pokes, and persists the card after the run completes. The card renders
markdown through the shared markdown view plus typed steps. Legacy Apple-side
plan handling (agent-event case, run-snapshot plan reconciliation,
OpenClawChatPlanStep parsing) is deleted; gateway emission stays for Android.
Removes the ios progressCard.changed coverage allowlist entry so the check
enforces the handler.

* chore(i18n): refresh native inventory for the progress-card rename

* fix(apps): keep the last progress card when a refresh fails

A transient progressCard.get failure no longer clears an already-rendered
durable card; only a successful null fetch or a null-revision poke clears it.
2026-08-17 18:07:17 -07:00

64 lines
2.6 KiB
Swift

import OpenClawKit
/// In-flight run adoption shared by history replay and live transport events.
extension OpenClawChatViewModel {
func applyInFlightRunSnapshot(
_ payload: OpenClawChatHistoryPayload,
for request: HistoryRequest)
{
guard request.runOwnershipGeneration == self.runOwnershipGeneration,
request.id >= self.latestAppliedRunSnapshotRequestID
else {
return
}
self.latestAppliedRunSnapshotRequestID = request.id
if let activeRunIDs = payload.sessionInfo?.activeRunIds {
self.updateActiveSessionRunIDs(activeRunIDs)
} else if payload.sessionInfo?.hasActiveRun == false {
self.updateActiveSessionRunIDs([])
}
guard let snapshot = payload.inFlightRun,
let runId = Self.normalizedRunID(snapshot.runId),
self.liveRunStateByRunID[runId]?.terminal != true
else {
return
}
self.isApplyingRunSnapshot = true
defer { self.isApplyingRunSnapshot = false }
self.updateActiveSessionRunWithoutChatSnapshot(false)
self.adoptRunState(runId: runId, bufferedText: snapshot.text)
}
func adoptRun(runId: String, bufferedText: String) {
self.adoptRunState(runId: runId, bufferedText: bufferedText)
}
private func adoptRunState(runId: String, bufferedText: String) {
// A terminal ID stays retired until an authoritative session snapshot
// explicitly removes it; late deltas/history cannot resurrect the run.
guard self.liveRunStateByRunID[runId]?.terminal != true else { return }
let replacedRun = self.pendingRuns.count != 1 || !self.pendingRuns.contains(runId)
if replacedRun {
// Gateway snapshots and live deltas are canonical for this session.
// Replace stale local ownership so only that run consumes later events.
clearPendingRuns(reason: nil)
self.pendingRuns.insert(runId)
self.pendingToolCallsById = [:]
self.updateStreamingAssistantText(nil)
}
if self.runMessageScopesByRunID[runId] == nil {
self.runMessageScopesByRunID[runId] = currentRunMessageScope()
}
if self.pendingRunOwnerArmIDs[runId] == nil {
armPendingRunOwner(runId: runId)
}
if !bufferedText.isEmpty {
self.updateStreamingAssistantText(bufferedText)
}
self.logDiagnostic(
"chat.ui adopted in-flight run sessionKey=\(self.sessionKey) "
+ "runId=\(runId) bufferedTextLen=\(bufferedText.count)")
}
}