
























@@ -72,6 +72,11 @@ public final class OpenClawChatViewModel {
7272private var lastCompactAt: Date?
7373private let compactCooldown: TimeInterval = 60
747475+private enum SessionSwitchIntent {
76+case userInitiated
77+case externalSync
78+}
79+7580private var pendingToolCallsById: [String: OpenClawChatPendingToolCall] = [:] {
7681 didSet {
7782self.pendingToolCalls = self.pendingToolCallsById.values
@@ -151,7 +156,11 @@ public final class OpenClawChatViewModel {
151156}
152157153158public func switchSession(to sessionKey: String) {
154-Task { await self.performSwitchSession(to: sessionKey) }
159+self.applySessionSwitch(to: sessionKey, intent: .userInitiated)
160+}
161+162+public func syncSession(to sessionKey: String) {
163+self.applySessionSwitch(to: sessionKey, intent: .externalSync)
155164}
156165157166public func selectThinkingLevel(_ level: String) {
@@ -261,23 +270,34 @@ public final class OpenClawChatViewModel {
261270self.diagnosticsLog?(message)
262271}
263272264-private func bootstrap() async {
273+private func bootstrap(sessionKey requestedSessionKey: String? = nil) async {
274+let sessionKey = requestedSessionKey ?? self.sessionKey
275+guard sessionKey == self.sessionKey else { return }
265276self.isLoading = true
266277self.errorText = nil
267278self.healthOK = false
268279self.clearPendingRuns(reason: nil)
269280self.pendingToolCallsById = [:]
270281self.streamingAssistantText = nil
271282self.sessionId = nil
272-defer { self.isLoading = false }
283+defer {
284+if self.sessionKey == sessionKey {
285+self.isLoading = false
286+}
287+}
273288do {
274289do {
275-try await self.transport.setActiveSessionKey(self.sessionKey)
290+try await self.transport.setActiveSessionKey(sessionKey)
276291} catch {
277292 // Best-effort only; history/send/health still work without push events.
278293}
294+guard self.sessionKey == sessionKey else {
295+await self.restoreActiveSessionAfterStaleBootstrap(staleSessionKey: sessionKey)
296+return
297+}
279298280-let payload = try await self.transport.requestHistory(sessionKey: self.sessionKey)
299+let payload = try await self.transport.requestHistory(sessionKey: sessionKey)
300+guard self.sessionKey == sessionKey else { return }
281301self.messages = Self.reconcileMessageIDs(
282302 previous: self.messages,
283303 incoming: Self.decodeMessages(payload.messages ?? []))
@@ -290,15 +310,37 @@ public final class OpenClawChatViewModel {
290310}
291311self.syncThinkingLevelOptions()
292312await self.pollHealthIfNeeded(force: true)
313+guard self.sessionKey == sessionKey else { return }
293314await self.fetchSessions(limit: 50)
315+guard self.sessionKey == sessionKey else { return }
294316await self.fetchModels()
317+guard self.sessionKey == sessionKey else { return }
295318self.errorText = nil
296319} catch {
320+guard self.sessionKey == sessionKey else { return }
297321self.errorText = error.localizedDescription
298322 chatUILogger.error("bootstrap failed \(error.localizedDescription, privacy: .public)")
299323}
300324}
301325326+private func restoreActiveSessionAfterStaleBootstrap(staleSessionKey: String) async {
327+var lastSubscribedSessionKey = staleSessionKey
328+while true {
329+let currentSessionKey = self.sessionKey
330+guard currentSessionKey != lastSubscribedSessionKey else { return }
331+do {
332+ // A stale bootstrap may complete its subscribe side effect after the winning switch.
333+ // Reassert and recheck so push events stay aligned with the visible session.
334+try await self.transport.setActiveSessionKey(currentSessionKey)
335+} catch {
336+ // Best-effort only; the current bootstrap still owns history/send/health.
337+return
338+}
339+guard self.sessionKey != currentSessionKey else { return }
340+ lastSubscribedSessionKey = currentSessionKey
341+}
342+}
343+302344private func refreshPendingRunAfterForeground() async {
303345guard self.pendingRunCount > 0 else { return }
304346self.logDiagnostic(
@@ -757,14 +799,16 @@ public final class OpenClawChatViewModel {
757799}
758800}
759801760-private func performSwitchSession(to sessionKey: String) async {
802+private func applySessionSwitch(to sessionKey: String, intent: SessionSwitchIntent) {
761803let next = sessionKey.trimmingCharacters(in: .whitespacesAndNewlines)
762804guard !next.isEmpty else { return }
763805guard next != self.sessionKey else { return }
764806self.sessionKey = next
765-self.onSessionChanged?(next)
807+if intent == .userInitiated {
808+self.onSessionChanged?(next)
809+}
766810self.modelSelectionID = Self.defaultModelSelectionID
767-await self.bootstrap()
811+Task { await self.bootstrap(sessionKey: next) }
768812}
769813770814private func performStartNewSession() async {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。