diff --git a/apps/ios/Sources/Model/NodeAppModel.swift b/apps/ios/Sources/Model/NodeAppModel.swift index 455ec2fba24e..9efdc93e0dd7 100644 --- a/apps/ios/Sources/Model/NodeAppModel.swift +++ b/apps/ios/Sources/Model/NodeAppModel.swift @@ -6051,10 +6051,16 @@ extension NodeAppModel { let statusText = self.gatewayStatusText == "Connected" ? self.operatorStatusText : self.gatewayStatusText - if statusText != "Offline" { + switch statusText { + case "Connecting…": + return OpenClawWatchAppStatus(code: .gatewayConnecting) + case "Reconnecting…": + return OpenClawWatchAppStatus(code: .gatewayReconnecting) + case "Offline": + return OpenClawWatchAppStatus(code: .gatewayOffline) + default: return OpenClawWatchAppStatus(code: .legacy, verbatim: statusText) } - return OpenClawWatchAppStatus(code: .gatewayOffline) } private static func makeWatchGatewayProblemStatus( @@ -6100,6 +6106,12 @@ extension NodeAppModel { if self.talkMode.hasActivePushToTalkSession { return self.makeWatchTalkPresentationStatus() } + switch self.talkMode.watchPresentation { + case .localized, .verbatim: + return self.makeWatchTalkPresentationStatus() + case .phase: + break + } if !self.talkMode.isEnabled { return OpenClawWatchAppStatus(code: .talkOff) } diff --git a/apps/ios/Tests/NodeAppModelInvokeTests.swift b/apps/ios/Tests/NodeAppModelInvokeTests.swift index fca99fb99732..77f0f80bd650 100644 --- a/apps/ios/Tests/NodeAppModelInvokeTests.swift +++ b/apps/ios/Tests/NodeAppModelInvokeTests.swift @@ -4730,8 +4730,8 @@ private func overrideNotificationServingPreference(_ enabled: Bool) -> () -> Voi } let status = try #require(watchService.lastSentAppSnapshot?.gatewayStatus) - #expect(status.code == .legacy) - #expect(status.verbatim == "Connecting…") + #expect(status.code == .gatewayConnecting) + #expect(status.verbatim == nil) #expect(watchService.lastSentAppSnapshot?.gatewayStatusText == "Connecting…") } @@ -4780,6 +4780,29 @@ private func overrideNotificationServingPreference(_ enabled: Bool) -> () -> Voi #expect(watchService.lastSentAppSnapshot?.talkStatus.code == .talkThinking) } + @Test @MainActor func `watch app snapshot preserves terminal push to talk failure`() async { + let watchService = MockWatchMessagingService() + let appModel = NodeAppModel(watchMessagingService: watchService) + appModel.talkMode._test_handleRealtimeRelayStatus("Backend rejected realtime request") + + watchService.emitAppSnapshotRequest( + WatchAppSnapshotRequestEvent( + requestId: "app-snapshot-push-to-talk-failure", + sentAtMs: 123, + transport: "sendMessage")) + for _ in 0..<20 { + if watchService.lastSentAppSnapshot != nil { + break + } + try? await Task.sleep(nanoseconds: 50_000_000) + } + + #expect(watchService.lastSentAppSnapshot?.talkStatus.code == .talkFailure) + #expect( + watchService.lastSentAppSnapshot?.talkStatus.verbatim + == "Backend rejected realtime request") + } + @Test @MainActor func `watch app snapshot publishes online when operator reconnects`() async { let watchService = MockWatchMessagingService() let appModel = NodeAppModel(watchMessagingService: watchService) diff --git a/apps/ios/WatchApp/Sources/WatchInboxMessages.swift b/apps/ios/WatchApp/Sources/WatchInboxMessages.swift index 3308dcc85266..3564a6a3bebe 100644 --- a/apps/ios/WatchApp/Sources/WatchInboxMessages.swift +++ b/apps/ios/WatchApp/Sources/WatchInboxMessages.swift @@ -72,6 +72,7 @@ typealias WatchAppCommand = OpenClawWatchAppCommand enum WatchStatusLocalizationKey: String { case connected + case reconnecting case offline case gatewayProblemRequestIDFormat case ready @@ -117,6 +118,8 @@ enum WatchStatusLocalizationKey: String { switch self { case .connected: String(localized: "Connected") + case .reconnecting: + String(localized: "Reconnecting…") case .offline: String(localized: "Offline") case .gatewayProblemRequestIDFormat: @@ -878,6 +881,10 @@ extension OpenClawWatchAppStatus { return switch self.code { case .gatewayConnected: localize(.connected) + case .gatewayConnecting: + localize(.connecting) + case .gatewayReconnecting: + localize(.reconnecting) case .gatewayOffline: localize(.offline) case .gatewayProblem: diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/WatchCommands.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/WatchCommands.swift index 5b340ca0e8f9..c55d58d351ee 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/WatchCommands.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/WatchCommands.swift @@ -278,6 +278,8 @@ public struct OpenClawWatchChatCompletionMessage: Codable, Sendable, Equatable { public enum OpenClawWatchAppStatusCode: String, Codable, Sendable, Equatable { case gatewayConnected + case gatewayConnecting + case gatewayReconnecting case gatewayOffline case gatewayProblem case gatewayProblemWithRequestID @@ -611,6 +613,8 @@ public struct OpenClawWatchAppSnapshotMessage: Codable, Sendable, Equatable { } return switch status.code { case .gatewayConnected: "Connected" + case .gatewayConnecting: "Connecting…" + case .gatewayReconnecting: "Reconnecting…" case .gatewayOffline: "Offline" case .gatewayProblem, .gatewayProblemWithRequestID: "Gateway unavailable" case .talkOff: "Off"