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 <steipete@gmail.com>
This commit is contained in:
yuvraj thakur
2026-07-29 08:54:22 +05:30
committed by GitHub
parent e2a76333d8
commit efec26b2df
5 changed files with 38 additions and 9 deletions

View File

@@ -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",

View File

@@ -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?,

View File

@@ -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)

View File

@@ -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(

View File

@@ -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",