From bd508188677dee5ee95df6c25e58dcec9091ebcd Mon Sep 17 00:00:00 2001 From: Mariano Belinky Date: Mon, 16 Feb 2026 15:21:52 +0000 Subject: [PATCH] chore: scope gateway stability pack to iOS app files --- .../OpenClawChatUI/ChatViewModel.swift | 70 ++----------------- .../Sources/OpenClawKit/GatewayChannel.swift | 36 +--------- 2 files changed, 8 insertions(+), 98 deletions(-) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift index ca49f078d5..4dc8b9d8b1 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift @@ -190,7 +190,7 @@ public final class OpenClawChatViewModel { let decoded = raw.compactMap { item in (try? ChatPayloadDecoding.decode(item, as: OpenClawChatMessage.self)) } - return Self.filterHeartbeatNoise(Self.dedupeMessages(decoded)) + return Self.dedupeMessages(decoded) } private static func messageIdentityKey(for message: OpenClawChatMessage) -> String? { @@ -275,56 +275,6 @@ public final class OpenClawChatViewModel { return result } - private static func filterHeartbeatNoise(_ messages: [OpenClawChatMessage]) -> [OpenClawChatMessage] { - messages.filter { !Self.isHeartbeatNoiseMessage($0) } - } - - private static func isHeartbeatNoiseMessage(_ message: OpenClawChatMessage) -> Bool { - let role = message.role.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - let text = message.content.compactMap(\.text).joined(separator: "\n") - .trimmingCharacters(in: .whitespacesAndNewlines) - guard !text.isEmpty else { return false } - - if role == "assistant", Self.isHeartbeatAckText(text) { - return true - } - if role == "user", Self.isHeartbeatPollText(text) { - return true - } - // Some models occasionally echo the heartbeat prompt text as an assistant reply. - if role == "assistant", Self.isHeartbeatPollText(text) { - return true - } - return false - } - - private static func isHeartbeatPollText(_ text: String) -> Bool { - let lower = text.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - // Match the default heartbeat prompt without requiring the entire multi-sentence string. - return lower.hasPrefix("read heartbeat.md if it exists") - } - - private static func isHeartbeatAckText(_ text: String) -> Bool { - // Heartbeat acks are intended to be internal. Treat common markup wrappers as equivalent. - var t = text.trimmingCharacters(in: .whitespacesAndNewlines) - if t.isEmpty { return false } - - // Strip a few common wrappers (markdown/HTML) so **HEARTBEAT_OK** or HEARTBEAT_OK still matches. - let wrappers = ["**", "__", "`", "", "", "", ""] - for w in wrappers { - t = t.replacingOccurrences(of: w, with: "") - } - t = t.trimmingCharacters(in: .whitespacesAndNewlines) - - // Allow a tiny amount of padding (some channels append a marker/emoji). - if t == "HEARTBEAT_OK" { return true } - if t.hasPrefix("HEARTBEAT_OK") { - let rest = t.dropFirst("HEARTBEAT_OK".count) - return rest.trimmingCharacters(in: .whitespacesAndNewlines).count <= 10 - } - return false - } - private static func dedupeKey(for message: OpenClawChatMessage) -> String? { guard let timestamp = message.timestamp else { return nil } let text = message.content.compactMap(\.text).joined(separator: "\n") @@ -334,23 +284,15 @@ public final class OpenClawChatViewModel { } private func performSend() async { - if self.isSending { - chatUILogger.info("performSend ignored: already sending") - return - } + guard !self.isSending else { return } let trimmed = self.input.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.isEmpty && self.attachments.isEmpty { - chatUILogger.info("performSend ignored: empty input and no attachments") + guard !trimmed.isEmpty || !self.attachments.isEmpty else { return } + + guard self.healthOK else { + self.errorText = "Gateway health not OK; cannot send" return } - // Health checks are best-effort. If they fail (or the gateway doesn't implement them), - // we still attempt to send and let the RPC result determine success. - if !self.healthOK { - self.errorText = "Gateway health unknown; attempting send anyway" - } - chatUILogger.info("performSend sending len=\(trimmed.count, privacy: .public) attachments=\(self.attachments.count, privacy: .public) sessionKey=\(self.sessionKey, privacy: .public)") - self.isSending = true self.errorText = nil let runId = UUID().uuidString diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift index 37f3de3142..a255fc7a81 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift @@ -399,40 +399,8 @@ public actor GatewayChannelActor { role: String ) async throws { if res.ok == false { - let code = res.error?["code"]?.value as? String - let msg = res.error?["message"]?.value as? String - let requestId: String? = { - guard let detailsAny = res.error?["details"]?.value else { return nil } - if let dict = detailsAny as? [String: ProtoAnyCodable], - let id = dict["requestId"]?.value as? String - { - return id - } - if let dict = detailsAny as? [String: Any], - let id = dict["requestId"] as? String - { - return id - } - if let dict = detailsAny as? [AnyHashable: Any], - let id = dict["requestId"] as? String - { - return id - } - return nil - }() - let details: [String: AnyCodable] = (res.error ?? [:]).reduce(into: [:]) { acc, pair in - acc[pair.key] = AnyCodable(pair.value.value) - } - let decoratedMessage: String? = { - guard code == "NOT_PAIRED", let requestId, !requestId.isEmpty else { return msg } - let base = (msg ?? "gateway connect failed") - return "\(base) (requestId: \(requestId))" - }() - throw GatewayResponseError( - method: "connect", - code: code, - message: decoratedMessage ?? msg, - details: details) + let msg = (res.error?["message"]?.value as? String) ?? "gateway connect failed" + throw NSError(domain: "Gateway", code: 1008, userInfo: [NSLocalizedDescriptionKey: msg]) } guard let payload = res.payload else { throw NSError(