import Foundation import OpenClawKit enum GatewayConnectionIssue: Equatable { case none case tokenMissing case unauthorized case pairingRequired(requestId: String?) case network case unknown(String) var requestId: String? { if case let .pairingRequired(requestId) = self { return requestId } return nil } var needsAuthToken: Bool { switch self { case .tokenMissing, .unauthorized: return true default: return false } } var needsPairing: Bool { if case .pairingRequired = self { return true } return false } static func detect(problem: GatewayConnectionProblem?) -> Self { guard let problem else { return .none } if problem.needsPairingApproval { return .pairingRequired(requestId: problem.requestId) } if problem.needsCredentialUpdate { return problem.kind == .gatewayAuthTokenMissing ? .tokenMissing : .unauthorized } switch problem.kind { case .deviceIdentityRequired, .deviceSignatureExpired, .deviceNonceRequired, .deviceNonceMismatch, .deviceSignatureInvalid, .devicePublicKeyInvalid, .deviceIdMismatch, .tailscaleIdentityMissing, .tailscaleProxyMissing, .tailscaleWhoisFailed, .tailscaleIdentityMismatch, .authRateLimited: return .unauthorized case .timeout, .connectionRefused, .reachabilityFailed, .websocketCancelled: return .network case .unknown: return .unknown(problem.message) default: return .none } } static func detect(from statusText: String) -> Self { let trimmed = statusText.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return .none } let lower = trimmed.lowercased() if lower.contains("pairing required") || lower.contains("not_paired") || lower.contains("not paired") { return .pairingRequired(requestId: self.extractRequestId(from: trimmed)) } if lower.contains("gateway token missing") { return .tokenMissing } if lower.contains("unauthorized") { return .unauthorized } if lower.contains("connection refused") || lower.contains("timed out") || lower.contains("network is unreachable") || lower.contains("cannot find host") || lower.contains("could not connect") { return .network } if lower.hasPrefix("gateway error:") { return .unknown(trimmed) } return .none } private static func extractRequestId(from statusText: String) -> String? { let marker = "requestId:" guard let range = statusText.range(of: marker) else { return nil } let suffix = statusText[range.upperBound...] let trimmed = suffix.trimmingCharacters(in: .whitespacesAndNewlines) let end = trimmed.firstIndex(where: { ch in ch == ")" || ch.isWhitespace || ch == "," || ch == ";" }) ?? trimmed.endIndex let id = String(trimmed[..