Files
openclaw/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+RunSnapshot.swift
T
Peter Steinberger e71fc902ee fix(gateway): make activeRunIds presence mean a complete exact run set (#126106)
* fix(gateway): make activeRunIds presence mean a complete exact run set

Session rows no longer emit activeRunIds: [] while hasActiveRun is
true. Presence now means the complete exact set of direct run ids;
omission means identities are unavailable (projected/embedded owners);
[] only ever represents proven idle. Consumers stop guessing:
soleActiveSessionRunId() replaces the arbitrary [0] fallbacks in the
observer digest, transcript cache key, activity inspector, and
stale-terminal reconciliation, each falling back to its owner fact.

Follows the maintainer direction from #125983: the field stays as
Gateway-owned exact facts; producer-side liveness/observer projections
are a named follow-up.

* fix(gateway): clear unavailable active run ids in events

* fix(gateway): preserve idle active run sets

* fix(clients): close active run id cache gaps

* test(android): isolate history run snapshot
2026-08-19 02:08:57 -07:00

71 lines
2.9 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 sessionInfo = payload.sessionInfo {
if let index = self.sessions.firstIndex(where: {
self.matchesCurrentSessionKey(incoming: $0.key, current: request.session.key)
}) {
var updated = self.sessions
updated[index].hasActiveRun = sessionInfo.hasActiveRun
updated[index].activeRunIds = sessionInfo.activeRunIds
self.sessions = updated
} else {
self.updateActiveSessionRunIDs(sessionInfo.activeRunIds ?? [])
}
}
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)")
}
}