From efec26b2dfab49845b45edcdc47281f2574328a0 Mon Sep 17 00:00:00 2001 From: yuvraj thakur Date: Wed, 29 Jul 2026 08:54:22 +0530 Subject: [PATCH] fix(ios): Apple Watch chat inherits thinking defaults (#111301) * fix(thinking): fall back instead of erroring on unsupported adaptive level (#109351) The adaptive level is the auto sentinel (normalizeThinkLevel maps auto -> adaptive) meaning 'pick an appropriate level', not 'force this exact level'. Some clients (e.g. the Apple Watch client) send it as a transport default while the iOS app sends none, so hard-erroring on an unsupported adaptive override rejected otherwise-valid turns from those clients. Now the auto sentinel always falls back to a supported level; explicit concrete levels (e.g. an explicit /think xhigh) still hard-error as before. Closes #109351 * docs(proof): add real-behavior-proof screenshot + run log for #111301 Captured run of the real get-reply-run.media-only test (101 passed) showing the adaptive-thinking fallback fix verified. Co-Authored-By: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com> * fix(ios): inherit Watch chat thinking defaults Co-authored-by: yuvraj thakur <284412773+yuvrajlaptop2008-byte@users.noreply.github.com> * chore(ios): sync native i18n inventory --------- Co-authored-by: yuvrajlaptop2008-byte <284412773+yuvrajlaptop2008-byte@users.noreply.github.com> Co-authored-by: Peter Steinberger --- apps/.i18n/native-source.json | 10 +++++----- .../Sources/Chat/IOSGatewayChatTransport.swift | 2 +- apps/ios/Sources/Model/NodeAppModel.swift | 18 +++++++++++++++--- apps/ios/Tests/NodeAppModelInvokeTests.swift | 5 +++++ .../ChatGatewayRequestTests.swift | 12 ++++++++++++ 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json index 4801fe443cc5..f8c17c39e820 100644 --- a/apps/.i18n/native-source.json +++ b/apps/.i18n/native-source.json @@ -25931,7 +25931,7 @@ }, { "kind": "conditional-branch", - "line": 8825, + "line": 8837, "path": "apps/ios/Sources/Model/NodeAppModel.swift", "source": "Approval", "surface": "apple", @@ -25939,7 +25939,7 @@ }, { "kind": "conditional-branch", - "line": 8825, + "line": 8837, "path": "apps/ios/Sources/Model/NodeAppModel.swift", "source": "This approval was already", "surface": "apple", @@ -25947,7 +25947,7 @@ }, { "kind": "conditional-branch", - "line": 8831, + "line": 8843, "path": "apps/ios/Sources/Model/NodeAppModel.swift", "source": "This approval was already set to Always Allow.", "surface": "apple", @@ -25955,7 +25955,7 @@ }, { "kind": "conditional-branch", - "line": 8832, + "line": 8844, "path": "apps/ios/Sources/Model/NodeAppModel.swift", "source": "Approval set to Always Allow.", "surface": "apple", @@ -25963,7 +25963,7 @@ }, { "kind": "conditional-branch", - "line": 10202, + "line": 10214, "path": "apps/ios/Sources/Model/NodeAppModel.swift", "source": "\\(urlText.prefix(500))…", "surface": "apple", diff --git a/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift b/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift index dad97033bfe1..f9c249b478c0 100644 --- a/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift +++ b/apps/ios/Sources/Chat/IOSGatewayChatTransport.swift @@ -684,7 +684,7 @@ struct IOSGatewayChatTransport: OpenClawChatTransport { agentID: String? = nil, expectedSessionRoutingContract: String? = nil, message: String, - thinking: String, + thinking: String?, idempotencyKey: String, attachments: [OpenClawChatAttachmentPayload], ifCurrentRoute expectedRoute: GatewayNodeSessionRoute?, diff --git a/apps/ios/Sources/Model/NodeAppModel.swift b/apps/ios/Sources/Model/NodeAppModel.swift index a9925dbf58ed..2939e12ecc2b 100644 --- a/apps/ios/Sources/Model/NodeAppModel.swift +++ b/apps/ios/Sources/Model/NodeAppModel.swift @@ -6736,6 +6736,17 @@ extension NodeAppModel { event.messageKind ?? .chat } + nonisolated static func watchThinkingOverride(for messageKind: WatchMessageKind) -> String? { + // Free-form Watch chat has no one-turn thinking control, so it must not + // invent an override. Quick replies intentionally use a cheap level. + switch messageKind { + case .chat: + nil + case .quickReply: + "low" + } + } + private func forwardWatchMessage( _ event: WatchAppCommandEvent, requeueOnFailure: Bool) async -> WatchMessageSendOutcome @@ -6758,7 +6769,7 @@ extension NodeAppModel { if messageKind == .chat { self.focusChatSession(sessionKey) } - let thinking = messageKind == .quickReply ? "low" : "auto" + let thinkingOverride = Self.watchThinkingOverride(for: messageKind) do { let submittedAtMs = Int64(Date().timeIntervalSince1970 * 1000) @@ -6766,7 +6777,8 @@ extension NodeAppModel { let response = try await appleReviewDemoChatTransport.sendMessage( sessionKey: sessionKey, message: text, - thinking: thinking, + // Demo mode has no Gateway session from which to resolve an omitted level. + thinking: thinkingOverride ?? "auto", idempotencyKey: event.commandId, attachments: []) if messageKind == .quickReply { @@ -6810,7 +6822,7 @@ extension NodeAppModel { let response = try await transport.sendMessage( sessionKey: sessionKey, message: text, - thinking: thinking, + thinking: thinkingOverride, idempotencyKey: event.commandId, attachments: [], ifCurrentRoute: operatorRoute) diff --git a/apps/ios/Tests/NodeAppModelInvokeTests.swift b/apps/ios/Tests/NodeAppModelInvokeTests.swift index 0e9d8717c606..c3082fa10b54 100644 --- a/apps/ios/Tests/NodeAppModelInvokeTests.swift +++ b/apps/ios/Tests/NodeAppModelInvokeTests.swift @@ -6165,6 +6165,11 @@ private func overrideNotificationServingPreference(_ enabled: Bool) -> () -> Voi #expect(outbox.nextQueuedMessage(isAvailable: true, gatewayStableID: "gateway-a") == reply) } + @Test func `watch messages only override thinking for quick replies`() { + #expect(NodeAppModel.watchThinkingOverride(for: .chat) == nil) + #expect(NodeAppModel.watchThinkingOverride(for: .quickReply) == "low") + } + @Test func `watch message outbox discards permanent gateway failures`() { #expect(NodeAppModel._test_shouldDiscardFailedWatchMessage(code: "INVALID_REQUEST")) #expect(!NodeAppModel._test_shouldDiscardFailedWatchMessage( diff --git a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatGatewayRequestTests.swift b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatGatewayRequestTests.swift index 7cba3353cbfe..f9cc7ff122da 100644 --- a/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatGatewayRequestTests.swift +++ b/apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatGatewayRequestTests.swift @@ -313,6 +313,18 @@ struct ChatGatewayRequestTests { #expect(String(decoding: encoded, as: UTF8.self).contains("a.png")) } + @Test func `send request omits inherited thinking override`() { + let inherited = OpenClawChatGatewayRequests.sendMessage( + sessionKey: "global", + agentID: nil, + expectedSessionRoutingContract: nil, + message: "inherit", + thinking: nil, + idempotencyKey: "send-inherit", + attachments: []) + #expect(inherited.params["thinking"] == nil) + } + @Test func `question resolve request preserves answer arrays`() throws { let request = OpenClawChatGatewayRequests.resolveQuestion( id: "ask_123",