diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceAuthStore.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceAuthStore.swift index 80ff20c3f35..4840840d528 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceAuthStore.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceAuthStore.swift @@ -64,6 +64,11 @@ public enum DeviceAuthStore { writeStore(store) } + public static func reset() { + // Best-effort: forget stored per-device tokens so a fresh pairing/approval is required. + try? FileManager.default.removeItem(at: fileURL()) + } + private static func normalizeRole(_ role: String) -> String { role.trimmingCharacters(in: .whitespacesAndNewlines) } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceIdentity.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceIdentity.swift index a992bc58f29..14ec77e11ca 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceIdentity.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/DeviceIdentity.swift @@ -53,6 +53,12 @@ public enum DeviceIdentityStore { return identity } + public static func reset() { + // Best-effort: forget local device identity so the gateway sees a new device and requires pairing again. + let url = self.fileURL() + try? FileManager.default.removeItem(at: url) + } + public static func signPayload(_ payload: String, identity: DeviceIdentity) -> String? { guard let privateKeyData = Data(base64Encoded: identity.privateKey) else { return nil } do { diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift index 0b2e70471c3..01f0da53e8b 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift @@ -384,7 +384,31 @@ public actor GatewayChannelActor { ) async throws { if res.ok == false { let msg = (res.error?["message"]?.value as? String) ?? "gateway connect failed" - throw NSError(domain: "Gateway", code: 1008, userInfo: [NSLocalizedDescriptionKey: msg]) + let code = (res.error?["code"]?.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 decorated = (code == "NOT_PAIRED" && requestId?.isEmpty == false) + ? "\(msg) (requestId: \(requestId ?? ""))" + : msg + throw NSError(domain: "Gateway", code: 1008, userInfo: [NSLocalizedDescriptionKey: decorated]) } guard let payload = res.payload else { throw NSError(