Core: update shared gateway models

This commit is contained in:
Mariano Belinky
2026-02-02 12:58:16 +00:00
parent ff6114599e
commit 37eaca719a
6 changed files with 31 additions and 14 deletions

View File

@@ -360,11 +360,12 @@ actor GatewayConnection {
await client.shutdown() await client.shutdown()
} }
self.lastSnapshot = nil self.lastSnapshot = nil
let resolvedSessionBox = self.sessionBox ?? Self.buildSessionBox(url: url)
self.client = GatewayChannelActor( self.client = GatewayChannelActor(
url: url, url: url,
token: token, token: token,
password: password, password: password,
session: self.sessionBox, session: resolvedSessionBox,
pushHandler: { [weak self] push in pushHandler: { [weak self] push in
await self?.handle(push: push) await self?.handle(push: push)
}) })
@@ -380,6 +381,21 @@ actor GatewayConnection {
private static func defaultConfigProvider() async throws -> Config { private static func defaultConfigProvider() async throws -> Config {
try await GatewayEndpointStore.shared.requireConfig() try await GatewayEndpointStore.shared.requireConfig()
} }
private static func buildSessionBox(url: URL) -> WebSocketSessionBox? {
guard url.scheme?.lowercased() == "wss" else { return nil }
let host = url.host ?? "gateway"
let port = url.port ?? 443
let stableID = "\(host):\(port)"
let stored = GatewayTLSStore.loadFingerprint(stableID: stableID)
let params = GatewayTLSParams(
required: true,
expectedFingerprint: stored,
allowTOFU: true,
storeKey: stableID)
let session = GatewayTLSPinningSession(params: params)
return WebSocketSessionBox(session: session)
}
} }
// MARK: - Typed gateway API // MARK: - Typed gateway API

View File

@@ -428,9 +428,7 @@ public actor GatewayChannelActor {
guard let self else { return } guard let self else { return }
await self.watchTicks() await self.watchTicks()
} }
if let pushHandler = self.pushHandler { await self.pushHandler?(.snapshot(ok))
Task { await pushHandler(.snapshot(ok)) }
}
} }
private func listen() { private func listen() {

View File

@@ -16,7 +16,6 @@ public actor GatewayNodeSession {
private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway") private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
private let decoder = JSONDecoder() private let decoder = JSONDecoder()
private let encoder = JSONEncoder() private let encoder = JSONEncoder()
private static let defaultInvokeTimeoutMs = 30_000
private var channel: GatewayChannelActor? private var channel: GatewayChannelActor?
private var activeURL: URL? private var activeURL: URL?
private var activeToken: String? private var activeToken: String?
@@ -36,8 +35,8 @@ public actor GatewayNodeSession {
) async -> BridgeInvokeResponse { ) async -> BridgeInvokeResponse {
let timeoutLogger = Logger(subsystem: "ai.openclaw", category: "node.gateway") let timeoutLogger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
let timeout: Int = { let timeout: Int = {
if let timeoutMs { return max(0, timeoutMs) } guard let timeoutMs else { return 0 }
return Self.defaultInvokeTimeoutMs return max(0, timeoutMs)
}() }()
guard timeout > 0 else { guard timeout > 0 else {
return await onInvoke(request) return await onInvoke(request)
@@ -154,8 +153,10 @@ public actor GatewayNodeSession {
do { do {
try await channel.connect() try await channel.connect()
_ = await self.waitForSnapshot(timeoutMs: 500) let snapshotReady = await self.waitForSnapshot(timeoutMs: 500)
await self.notifyConnectedIfNeeded() if snapshotReady {
await self.notifyConnectedIfNeeded()
}
} catch { } catch {
await onDisconnected(error.localizedDescription) await onDisconnected(error.localizedDescription)
throw error throw error

View File

@@ -73,6 +73,11 @@ public final class GatewayTLSPinningSession: NSObject, WebSocketSessioning, URLS
if let expected { if let expected {
if fingerprint == expected { if fingerprint == expected {
completionHandler(.useCredential, URLCredential(trust: trust)) completionHandler(.useCredential, URLCredential(trust: trust))
} else if params.allowTOFU {
if let storeKey = params.storeKey {
GatewayTLSStore.saveFingerprint(fingerprint, stableID: storeKey)
}
completionHandler(.useCredential, URLCredential(trust: trust))
} else { } else {
completionHandler(.cancelAuthenticationChallenge, nil) completionHandler(.cancelAuthenticationChallenge, nil)
} }

View File

@@ -454,7 +454,7 @@ export function createNodesTool(options?: {
invokeParams = JSON.parse(invokeParamsJson); invokeParams = JSON.parse(invokeParamsJson);
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : String(err); const message = err instanceof Error ? err.message : String(err);
throw new Error(`invokeParamsJson must be valid JSON: ${message}`); throw new Error(`invokeParamsJson must be valid JSON: ${message}`, { cause: err });
} }
} }
const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs); const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs);

View File

@@ -187,10 +187,7 @@ export const handlePTTCommand: CommandHandler = async (params, allowTextCommands
params: invokeParams, params: invokeParams,
config: cfg, config: cfg,
}); });
const payload = const payload = res.payload && typeof res.payload === "object" ? res.payload : {};
res.payload && typeof res.payload === "object"
? (res.payload as Record<string, unknown>)
: {};
const lines = [`PTT ${actionKey}${nodeId}`]; const lines = [`PTT ${actionKey}${nodeId}`];
if (typeof payload.status === "string") { if (typeof payload.status === "string") {