From 2b8fe12f0bb4b76c4daeb6b8ba106f2743bf8af8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 24 Aug 2026 06:49:50 -0700 Subject: [PATCH] fix(apple): surface rejected chat session settings (#128737) --- .../OpenClawChatUI/ChatViewModel+ModelControls.swift | 4 ++++ .../OpenClawChatUI/ChatViewModel+Thinking.swift | 2 ++ .../Tests/OpenClawKitTests/ChatViewModelTests.swift | 12 ++++++++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+ModelControls.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+ModelControls.swift index 478f9b519b9f..2529979e6ed5 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+ModelControls.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+ModelControls.swift @@ -85,6 +85,7 @@ extension OpenClawChatViewModel { guard clearsOverride ? baselineSessionLevel != nil : Self.normalizedVerboseLevel(baselineSessionLevel) != next else { return } + self.errorText = nil if self.acceptedVerboseLevelsByTarget[target] == nil { self.acceptedVerboseLevelsByTarget[target] = baselineSessionLevel.map(VerboseLevelState.value) ?? VerboseLevelState.none @@ -133,6 +134,7 @@ extension OpenClawChatViewModel { self.acceptedVerboseLevelsByTarget[target]?.level, sessionKey: state.key, exactMatchOnly: state.exactMatchOnly) + if !state.exactMatchOnly { self.errorText = error.localizedDescription } } } } @@ -177,6 +179,7 @@ extension OpenClawChatViewModel { let baselineEffectiveFastMode = self.currentSessionEntry()?.effectiveFastMode guard baselineFastMode != next else { return } + self.errorText = nil if self.acceptedFastModesByTarget[target] == nil { self.acceptedFastModesByTarget[target] = FastModeState( override: baselineFastMode, @@ -225,6 +228,7 @@ extension OpenClawChatViewModel { effective: accepted?.effective, sessionKey: state.key, exactMatchOnly: state.exactMatchOnly) + if !state.exactMatchOnly { self.errorText = error.localizedDescription } } } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Thinking.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Thinking.swift index eda5f3e27665..203c363a421e 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Thinking.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel+Thinking.swift @@ -25,6 +25,7 @@ extension OpenClawChatViewModel { guard next != preferredThinkingLevel || self.thinkingOverrideIsInherited else { return } } + self.errorText = nil let sessionKey = self.sessionKey let acceptedBaseline = Self.normalizedThinkingLevel(currentSessionEntry()?.thinkingLevel) ?? Self.normalizedThinkingLevel(thinkingLevel) @@ -159,6 +160,7 @@ extension OpenClawChatViewModel { self.updateCurrentSessionThinkingLevel( self.acceptedThinkingOverrideClearedByTarget[target] == true ? nil : rollbackLevel, sessionKey: sessionKey) + self.errorText = error.localizedDescription } } } diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift index dccea1e74b9b..4bc5fae19bb5 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift @@ -2748,13 +2748,17 @@ struct ChatViewModelTests { viewModel.sessions = [running] let omitted = try JSONDecoder().decode( OpenClawSessionMessageEventPayload.self, - from: Data(#"{"sessionKey":"main","hasActiveRun":true,"messageId":"message-1","message":{"role":"assistant","content":[{"type":"text","text":"working"}],"timestamp":2}}"#.utf8)) + from: Data( + #"{"sessionKey":"main","hasActiveRun":true,"messageId":"message-1","message":{"role":"assistant","content":[{"type":"text","text":"working"}],"timestamp":2}}"# + .utf8)) viewModel.handleTransportEvent(.sessionMessage(omitted)) #expect(viewModel.currentSessionEntry()?.activeRunIds == ["run-stale"]) let payload = try JSONDecoder().decode( OpenClawSessionMessageEventPayload.self, - from: Data(#"{"sessionKey":"main","hasActiveRun":true,"activeRunIds":null,"messageId":"message-2","message":{"role":"assistant","content":[{"type":"text","text":"still working"}],"timestamp":3}}"#.utf8)) + from: Data( + #"{"sessionKey":"main","hasActiveRun":true,"activeRunIds":null,"messageId":"message-2","message":{"role":"assistant","content":[{"type":"text","text":"still working"}],"timestamp":3}}"# + .utf8)) viewModel.handleTransportEvent(.sessionMessage(payload)) @@ -10408,6 +10412,7 @@ struct ChatViewModelTests { #expect(await MainActor.run { vm.sessions.first(where: { $0.key == "main" })?.thinkingLevel } == "high") + #expect(await MainActor.run { vm.errorText } == nil) } @Test func `older pending thinking choice becomes preference fallback`() async throws { @@ -10486,12 +10491,14 @@ struct ChatViewModelTests { try await loadAndWaitBootstrap(vm: vm, sessionId: "sess-main") await MainActor.run { vm.selectFastMode("off") } await vm.waitForPendingSessionSettings(in: "main") + #expect(await MainActor.run { vm.errorText } == "rejected") #expect(await MainActor.run { vm.fastModeSelectionID } == OpenClawChatViewModel.inheritedThinkingSelectionID) #expect(await MainActor.run { vm.sessions.first?.fastMode } == nil) #expect(await MainActor.run { vm.sessions.first?.effectiveFastMode } == .on) await MainActor.run { vm.selectVerboseLevel("full") } await vm.waitForPendingSessionSettings(in: "main") + #expect(await MainActor.run { vm.errorText } == "rejected") #expect(await MainActor.run { vm.verboseLevel } == OpenClawChatViewModel.inheritedThinkingSelectionID) #expect(await MainActor.run { vm.sessions.first?.verboseLevel } == nil) } @@ -10763,6 +10770,7 @@ struct ChatViewModelTests { #expect(await MainActor.run { vm.preferredThinkingLevel } == "off") #expect(await MainActor.run { !vm.prefersExplicitThinkingLevel }) #expect(await MainActor.run { callbackState.values } == ["medium", "off"]) + #expect(await MainActor.run { vm.errorText } == "rejected") } @Test func `two failed queued thinking patches restore the confirmed level`() async throws {