@openclaw/gateway-client 2026.8.1-beta.1 → 2026.8.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,13 +1,14 @@
1
- import { C as buildDeviceAuthPayload, S as shouldRetryGatewayWithDeviceToken, T as normalizeDeviceMetadataForAuth, _ as GatewayProtocolRequestError, a as createSessionProjection, b as resolveGatewayConnectScopes, c as normalizeSessionProjectionRunId, d as readSessionMessageSequence, f as reconcileSessionProjectionSnapshot, g as GatewayProtocolClient, h as shouldPauseGatewayReconnect, i as resetGatewaySessionMessageSubscriptionCoordinator, l as projectLiveSessionMessage, m as reduceSessionProjectionRunEvent, n as getGatewaySessionMessageSubscriptionCoordinator, o as hasSessionProjectionAcceptedFinal, p as reduceSessionProjection, r as releaseGatewaySessionMessageSubscription, s as isLocallyOptimisticSessionMessage, t as GatewaySessionMessageSubscriptionCoordinator, u as readSessionMessageIdentity, v as GatewayBrowserDeviceAuthLifecycle, w as buildDeviceAuthPayloadV3, x as selectGatewayConnectAuth, y as buildGatewayConnectAuth } from "./session-subscriptions-D4Xv60cN.mjs";
1
+ import { C as resolveGatewayConnectScopes, D as buildDeviceAuthPayloadV3, E as buildDeviceAuthPayload, O as normalizeDeviceMetadataForAuth, S as buildGatewayConnectAuth, T as shouldRetryGatewayWithDeviceToken, _ as GatewayProtocolRequestError, a as createSessionProjection, b as gatewayOriginScope, c as normalizeSessionProjectionRunId, d as readSessionMessageSequence, f as reconcileSessionProjectionSnapshot, g as GatewayProtocolClient, h as shouldPauseGatewayReconnect, i as resetGatewaySessionMessageSubscriptionCoordinator, l as projectLiveSessionMessage, m as reduceSessionProjectionRunEvent, n as getGatewaySessionMessageSubscriptionCoordinator, o as hasSessionProjectionAcceptedFinal, p as reduceSessionProjection, r as releaseGatewaySessionMessageSubscription, s as isLocallyOptimisticSessionMessage, t as GatewaySessionMessageSubscriptionCoordinator, u as readSessionMessageIdentity, v as GatewayProtocolRequestTimeoutError, w as selectGatewayConnectAuth, x as GatewayBrowserDeviceAuthLifecycle, y as gatewayCredentialScope } from "./session-subscriptions-uKmTyc3P.mjs";
2
2
  import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, getConnectChallengeTimeoutMsFromEnv, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, startGatewayConnectTimeout } from "./timeouts.mjs";
3
+ import { rawDataToString } from "./websocket-data.mjs";
3
4
  import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-CrWxd4TI.mjs";
5
+ import { GatewayScopeUpgrade } from "./scope-upgrade.mjs";
4
6
  import { ConnectErrorDetailCodes, formatConnectErrorMessage, readConnectErrorDetailCode } from "@openclaw/gateway-protocol/connect-error-details";
5
7
  import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "@openclaw/gateway-protocol/client-info";
6
8
  import { resolveGatewayStartupRetryAfterMs } from "@openclaw/gateway-protocol/startup-unavailable";
7
- import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
9
+ import { MIN_CLIENT_PROTOCOL_VERSION, MIN_NODE_PROTOCOL_VERSION, MIN_PROBE_PROTOCOL_VERSION, PROTOCOL_VERSION } from "@openclaw/gateway-protocol/version";
8
10
  import { randomUUID } from "node:crypto";
9
11
  import { WebSocket } from "ws";
10
- import { Buffer as Buffer$1 } from "node:buffer";
11
12
  //#region \0rolldown/runtime.js
12
13
  var __create = Object.create;
13
14
  var __defProp = Object.defineProperty;
@@ -31,6 +32,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
32
  enumerable: true
32
33
  }) : target, mod));
33
34
  //#endregion
35
+ //#region packages/normalization-core/src/string-coerce.ts
36
+ /** Trims string input and returns null for non-strings or empty strings. */
37
+ function normalizeNullableString(value) {
38
+ if (typeof value !== "string") return null;
39
+ const trimmed = value.trim();
40
+ return trimmed ? trimmed : null;
41
+ }
42
+ /** Trims string input and returns undefined for non-strings or empty strings. */
43
+ function normalizeOptionalString(value) {
44
+ return normalizeNullableString(value) ?? void 0;
45
+ }
46
+ /** Lowercases a normalized optional string. */
47
+ function normalizeOptionalLowercaseString(value) {
48
+ return normalizeOptionalString(value)?.toLowerCase();
49
+ }
50
+ /** Lowercases a normalized string or returns an empty string when absent. */
51
+ function normalizeLowercaseStringOrEmpty(value) {
52
+ return normalizeOptionalLowercaseString(value) ?? "";
53
+ }
54
+ //#endregion
34
55
  //#region packages/net-policy/src/ip.ts
35
56
  var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
36
57
  (function(root) {
@@ -990,13 +1011,6 @@ var import_ipaddr = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
990
1011
  else root.ipaddr = ipaddr;
991
1012
  })(exports);
992
1013
  })))(), 1);
993
- function normalizeOptionalString(value) {
994
- if (typeof value !== "string") return;
995
- return value.trim() || void 0;
996
- }
997
- function normalizeLowercaseStringOrEmpty$1(value) {
998
- return typeof value === "string" ? value.trim().toLowerCase() : "";
999
- }
1000
1014
  import_ipaddr.default.IPv4.parse("198.18.0.0");
1001
1015
  function stripIpv6Brackets(value) {
1002
1016
  if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
@@ -1026,7 +1040,7 @@ function parseCanonicalIpAddress(raw) {
1026
1040
  function normalizeIpAddress(raw) {
1027
1041
  const parsed = parseCanonicalIpAddress(raw);
1028
1042
  if (!parsed) return;
1029
- return normalizeLowercaseStringOrEmpty$1(normalizeIpv4MappedAddress(parsed).toString());
1043
+ return normalizeLowercaseStringOrEmpty(normalizeIpv4MappedAddress(parsed).toString());
1030
1044
  }
1031
1045
  /** True when a canonical IP literal is loopback, including IPv4-mapped IPv6. */
1032
1046
  function isLoopbackIpAddress(raw) {
@@ -1035,21 +1049,8 @@ function isLoopbackIpAddress(raw) {
1035
1049
  return normalizeIpv4MappedAddress(parsed).range() === "loopback";
1036
1050
  }
1037
1051
  //#endregion
1038
- //#region packages/net-policy/src/url-protocol.ts
1039
- function parseUrl(value) {
1040
- if (value instanceof URL) return value;
1041
- try {
1042
- return new URL(value);
1043
- } catch {
1044
- return null;
1045
- }
1046
- }
1047
- function isWssUrl(value) {
1048
- return parseUrl(value)?.protocol === "wss:";
1049
- }
1050
- //#endregion
1051
1052
  //#region packages/gateway-client/src/client-address-utils.ts
1052
- function normalizeLowercaseStringOrEmpty(value) {
1053
+ function normalizeGatewayErrorText(value) {
1053
1054
  return typeof value === "string" ? value.trim().toLowerCase() : "";
1054
1055
  }
1055
1056
  function isSensitiveUrlQueryParamName(key) {
@@ -1076,34 +1077,34 @@ function parseGatewayIpAddress(host) {
1076
1077
  return normalized ? parseCanonicalIpAddress(normalized) : void 0;
1077
1078
  }
1078
1079
  //#endregion
1079
- //#region packages/gateway-client/src/websocket-data.ts
1080
- function rawDataToString(data) {
1081
- if (Array.isArray(data)) return Buffer$1.concat(data).toString("utf8");
1082
- return data instanceof ArrayBuffer ? Buffer$1.from(data).toString("utf8") : data.toString("utf8");
1083
- }
1084
- //#endregion
1085
- //#region packages/gateway-client/src/client.ts
1086
- const DEFAULT_HOST_DEPS = {
1087
- loadOrCreateDeviceIdentity: () => void 0,
1088
- signDevicePayload: () => {
1089
- throw new Error("GatewayClient device signature dependency is not configured");
1090
- },
1091
- publicKeyRawBase64UrlFromPem: () => {
1092
- throw new Error("GatewayClient public key dependency is not configured");
1093
- },
1094
- loadDeviceAuthToken: () => null,
1095
- storeDeviceAuthToken: () => {},
1096
- clearDeviceAuthToken: () => {},
1097
- beforeConnect: () => {},
1098
- registerGatewayLoopbackBypass: () => void 0,
1099
- logDebug: () => {},
1100
- logError: () => {},
1101
- redactForLog: (message) => message,
1102
- normalizeTlsFingerprint: normalizeFingerprint
1080
+ //#region packages/gateway-client/src/request-error.ts
1081
+ var GatewayClientRequestError = class extends GatewayProtocolRequestError {
1082
+ constructor(error) {
1083
+ super({
1084
+ ...error,
1085
+ message: formatConnectErrorMessage({
1086
+ message: error.message,
1087
+ details: error.details
1088
+ })
1089
+ });
1090
+ this.name = "GatewayClientRequestError";
1091
+ }
1103
1092
  };
1104
- function resolveHostDeps(overrides) {
1105
- return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
1093
+ //#endregion
1094
+ //#region packages/net-policy/src/url-protocol.ts
1095
+ function parseUrl(value) {
1096
+ if (value instanceof URL) return value;
1097
+ try {
1098
+ return new URL(value);
1099
+ } catch {
1100
+ return null;
1101
+ }
1102
+ }
1103
+ function isWssUrl(value) {
1104
+ return parseUrl(value)?.protocol === "wss:";
1106
1105
  }
1106
+ //#endregion
1107
+ //#region packages/gateway-client/src/websocket-transport.ts
1107
1108
  const PRIVATE_OR_LOOPBACK_IPV4_RANGES = /* @__PURE__ */ new Set([
1108
1109
  "loopback",
1109
1110
  "private",
@@ -1119,19 +1120,16 @@ const PRIVATE_OR_LOOPBACK_IPV6_RANGES = /* @__PURE__ */ new Set([
1119
1120
  function isPrivateOrLoopbackIpAddress(address) {
1120
1121
  return (address.kind() === "ipv4" ? PRIVATE_OR_LOOPBACK_IPV4_RANGES : PRIVATE_OR_LOOPBACK_IPV6_RANGES).has(address.range());
1121
1122
  }
1122
- function isLoopbackHost(host) {
1123
+ function isGatewayLoopbackHost(host) {
1123
1124
  const parsed = parseHostForAddressChecks(host);
1124
- if (!parsed) return false;
1125
- if (parsed.isLocalhost) return true;
1126
- return isLoopbackIpAddress(parsed.unbracketedHost);
1125
+ return Boolean(parsed && (parsed.isLocalhost || isLoopbackIpAddress(parsed.unbracketedHost)));
1127
1126
  }
1128
1127
  function isPrivateOrLoopbackHost(host) {
1129
1128
  const parsed = parseHostForAddressChecks(host);
1130
1129
  if (!parsed) return false;
1131
1130
  if (parsed.isLocalhost) return true;
1132
1131
  const address = parseGatewayIpAddress(parsed.unbracketedHost);
1133
- if (!address) return false;
1134
- return isPrivateOrLoopbackIpAddress(address);
1132
+ return Boolean(address && isPrivateOrLoopbackIpAddress(address));
1135
1133
  }
1136
1134
  function isTrustedPlaintextWebSocketHost(hostname) {
1137
1135
  if (isPrivateOrLoopbackHost(hostname)) return true;
@@ -1144,7 +1142,7 @@ function isSecureWebSocketUrl(rawUrl, options) {
1144
1142
  const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
1145
1143
  if (protocol === "wss:") return true;
1146
1144
  if (protocol !== "ws:") return false;
1147
- if (isLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
1145
+ if (isGatewayLoopbackHost(url.hostname) || isTrustedPlaintextWebSocketHost(url.hostname)) return true;
1148
1146
  if (options?.allowPrivateWs === true) {
1149
1147
  const hostForIpCheck = url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname;
1150
1148
  return isPrivateOrLoopbackHost(url.hostname) || parseGatewayIpAddress(hostForIpCheck) === void 0;
@@ -1154,37 +1152,117 @@ function isSecureWebSocketUrl(rawUrl, options) {
1154
1152
  return false;
1155
1153
  }
1156
1154
  }
1157
- const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
1158
- const DEFAULT_CLIENT_VERSION = "0.0.0";
1159
- var GatewayClientRequestError = class extends GatewayProtocolRequestError {
1160
- constructor(error) {
1161
- super({
1162
- ...error,
1163
- message: formatConnectErrorMessage({
1164
- message: error.message,
1165
- details: error.details
1166
- })
1167
- });
1168
- this.name = "GatewayClientRequestError";
1155
+ var GatewayWebSocketTransportConfigurationError = class extends Error {};
1156
+ function resolveGatewayWebSocketTransport(params) {
1157
+ const usesTls = isWssUrl(params.url);
1158
+ if (params.tlsFingerprint && !usesTls) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint requires wss:// gateway url");
1159
+ const allowPrivateWs = (params.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
1160
+ if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) {
1161
+ let displayHost = params.url;
1162
+ try {
1163
+ displayHost = new URL(params.url).hostname || params.url;
1164
+ } catch {}
1165
+ throw new GatewayWebSocketTransportConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
1166
+ }
1167
+ const normalize = params.normalizeTlsFingerprint ?? normalizeFingerprint;
1168
+ const options = { ...params.options };
1169
+ if (usesTls && params.tlsFingerprint) {
1170
+ options.rejectUnauthorized = false;
1171
+ options.checkServerIdentity = (_hostValue, cert) => {
1172
+ const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
1173
+ const fingerprint = normalize(typeof fingerprintValue === "string" ? fingerprintValue : "");
1174
+ const expected = normalize(params.tlsFingerprint);
1175
+ if (!expected) return;
1176
+ if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
1177
+ if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
1178
+ };
1169
1179
  }
1180
+ return {
1181
+ options,
1182
+ validateSocket: (socket) => {
1183
+ if (!params.tlsFingerprint) return null;
1184
+ const expected = normalize(params.tlsFingerprint);
1185
+ if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
1186
+ const rawSocket = socket["_socket"];
1187
+ if (!rawSocket || typeof rawSocket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1188
+ const cert = rawSocket.getPeerCertificate();
1189
+ const fingerprint = normalize(cert?.fingerprint256 ?? "");
1190
+ if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1191
+ return fingerprint === expected ? null : /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
1192
+ }
1193
+ };
1194
+ }
1195
+ //#endregion
1196
+ //#region packages/gateway-client/src/client.ts
1197
+ const DEFAULT_HOST_DEPS = {
1198
+ loadOrCreateDeviceIdentity: () => void 0,
1199
+ signDevicePayload: () => {
1200
+ throw new Error("GatewayClient device signature dependency is not configured");
1201
+ },
1202
+ publicKeyRawBase64UrlFromPem: () => {
1203
+ throw new Error("GatewayClient public key dependency is not configured");
1204
+ },
1205
+ loadDeviceAuthToken: () => null,
1206
+ storeDeviceAuthToken: () => {},
1207
+ clearDeviceAuthToken: () => {},
1208
+ beforeConnect: () => {},
1209
+ registerGatewayLoopbackBypass: () => void 0,
1210
+ logDebug: () => {},
1211
+ logError: () => {},
1212
+ redactForLog: (message) => message,
1213
+ normalizeTlsFingerprint: normalizeFingerprint
1170
1214
  };
1171
- var GatewayClientRequestTimeoutError = class extends Error {
1215
+ function resolveHostDeps(overrides) {
1216
+ return Object.fromEntries(Object.entries(DEFAULT_HOST_DEPS).map(([key, fallback]) => [key, overrides?.[key] ?? fallback]));
1217
+ }
1218
+ const DEFAULT_GATEWAY_CLIENT_URL = "ws://127.0.0.1:18789";
1219
+ const DEFAULT_CLIENT_VERSION = "0.0.0";
1220
+ const MAX_UPGRADE_ERROR_BODY_BYTES = 2 * 1024;
1221
+ const UPGRADE_ERROR_BODY_TIMEOUT_MS = 1e3;
1222
+ async function readUpgradeErrorBody(response) {
1223
+ return await new Promise((resolve) => {
1224
+ const chunks = [];
1225
+ let totalBytes = 0;
1226
+ let settled = false;
1227
+ const finish = () => {
1228
+ if (settled) return;
1229
+ settled = true;
1230
+ clearTimeout(timer);
1231
+ response.off("data", onData);
1232
+ response.off("end", finish);
1233
+ response.off("error", finish);
1234
+ response.off("aborted", finish);
1235
+ resolve(Buffer.concat(chunks, totalBytes).toString("utf8").replace(/\s+/gu, " ").trim());
1236
+ };
1237
+ const stop = () => {
1238
+ finish();
1239
+ response.destroy();
1240
+ };
1241
+ const onData = (chunk) => {
1242
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
1243
+ const remaining = MAX_UPGRADE_ERROR_BODY_BYTES - totalBytes;
1244
+ if (remaining > 0) {
1245
+ const prefix = buffer.subarray(0, remaining);
1246
+ chunks.push(prefix);
1247
+ totalBytes += prefix.byteLength;
1248
+ }
1249
+ if (buffer.byteLength >= remaining) stop();
1250
+ };
1251
+ const timer = setTimeout(stop, UPGRADE_ERROR_BODY_TIMEOUT_MS);
1252
+ timer.unref?.();
1253
+ response.on("data", onData);
1254
+ response.once("end", finish);
1255
+ response.once("error", finish);
1256
+ response.once("aborted", finish);
1257
+ });
1258
+ }
1259
+ var GatewayClientRequestTimeoutError = class extends GatewayProtocolRequestTimeoutError {
1172
1260
  constructor(params) {
1173
- super(`gateway request timeout for ${params.method}`);
1261
+ super(params, `gateway request timeout for ${params.method}`);
1174
1262
  this.name = "GatewayClientRequestTimeoutError";
1175
- this.method = params.method;
1176
- this.timeoutMs = params.timeoutMs;
1177
- this.requestSent = params.requestSent;
1178
- }
1179
- };
1180
- var GatewayClientTransientPreHelloCloseError = class extends Error {
1181
- constructor() {
1182
- super("gateway transient pre-hello clean close");
1183
- this.name = "GatewayClientTransientPreHelloCloseError";
1184
1263
  }
1185
1264
  };
1186
- var GatewayClientSocketFactoryConfigurationError = class extends Error {};
1187
- var GatewayClientTransportPolicyError = class extends GatewayClientSocketFactoryConfigurationError {};
1265
+ var GatewayClientTransportPolicyError = class extends GatewayWebSocketTransportConfigurationError {};
1188
1266
  const GATEWAY_CONNECT_ASSEMBLY_ERROR = Symbol("gateway.connectAssemblyError");
1189
1267
  function markGatewayConnectAssemblyError(error) {
1190
1268
  Object.defineProperty(error, GATEWAY_CONNECT_ASSEMBLY_ERROR, {
@@ -1206,10 +1284,20 @@ function formatGatewayClientErrorForLog(err) {
1206
1284
  const FORCE_STOP_TERMINATE_GRACE_MS = 250;
1207
1285
  const STOP_AND_WAIT_TIMEOUT_MS = 1e3;
1208
1286
  const MAX_SUPPRESSED_TRANSIENT_PRE_HELLO_CLEAN_CLOSES = 1;
1287
+ function resolveLegacyNodePlatform(platform) {
1288
+ switch (platform) {
1289
+ case "macos": return "darwin";
1290
+ case "windows": return "win32";
1291
+ default: return;
1292
+ }
1293
+ }
1209
1294
  var GatewayClient = class {
1210
1295
  constructor(opts) {
1211
1296
  this.ws = null;
1212
1297
  this.stopped = false;
1298
+ this.useLegacyNodeProtocolEnvelope = false;
1299
+ this.nodeProtocolTransitionPending = false;
1300
+ this.suppressNextHelloCallback = false;
1213
1301
  this.pendingDeviceTokenRetry = false;
1214
1302
  this.deviceTokenRetryBudgetUsed = false;
1215
1303
  this.approvalRuntimeTokenCompatibilityDisabled = false;
@@ -1225,7 +1313,7 @@ var GatewayClient = class {
1225
1313
  ...opts,
1226
1314
  deviceIdentity: opts.deviceIdentity === null ? void 0 : opts.deviceIdentity ?? this.deps.loadOrCreateDeviceIdentity()
1227
1315
  };
1228
- this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? resolveSafeTimeoutDelayMs(opts.requestTimeoutMs, { minMs: 0 }) : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
1316
+ this.requestTimeoutMs = typeof opts.requestTimeoutMs === "number" && Number.isFinite(opts.requestTimeoutMs) ? opts.requestTimeoutMs : DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS;
1229
1317
  const connectChallengeTimeoutMs = resolveConnectChallengeTimeoutMs(this.opts.connectChallengeTimeoutMs, {
1230
1318
  env: this.opts.env,
1231
1319
  configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
@@ -1264,7 +1352,13 @@ var GatewayClient = class {
1264
1352
  };
1265
1353
  },
1266
1354
  onConnectHello: (hello, context) => this.handleConnectHello(hello, context.plan),
1267
- onHello: (hello) => this.opts.onHelloOk?.(hello),
1355
+ onHello: (hello) => {
1356
+ if (this.suppressNextHelloCallback) {
1357
+ this.suppressNextHelloCallback = false;
1358
+ return;
1359
+ }
1360
+ this.opts.onHelloOk?.(hello);
1361
+ },
1268
1362
  onConnectFailure: (error, context) => this.handleConnectRequestFailure(error, context.plan),
1269
1363
  resolveClose: (context) => this.resolveClose(context),
1270
1364
  onClose: (context, decision) => {
@@ -1294,7 +1388,7 @@ var GatewayClient = class {
1294
1388
  maxMs: 3e4
1295
1389
  },
1296
1390
  requestTimeoutMs: this.requestTimeoutMs,
1297
- shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayClientSocketFactoryConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
1391
+ shouldRetrySocketFactoryError: (error) => !(error instanceof GatewayWebSocketTransportConfigurationError) && !(error instanceof SyntaxError) && !(error instanceof TypeError) && !(error instanceof RangeError),
1298
1392
  rethrowSocketFactoryError: (error) => error instanceof GatewayClientTransportPolicyError
1299
1393
  });
1300
1394
  }
@@ -1310,7 +1404,9 @@ var GatewayClient = class {
1310
1404
  this.opts = {
1311
1405
  ...this.opts,
1312
1406
  caps: [...manifest.caps],
1313
- commands: [...manifest.commands]
1407
+ commands: [...manifest.commands],
1408
+ computerUse: manifest.computerUse === void 0 ? void 0 : structuredClone(manifest.computerUse),
1409
+ workerRuns: manifest.workerRuns ? structuredClone(manifest.workerRuns) : void 0
1314
1410
  };
1315
1411
  if (!this.stopped) this.protocol.closeSocket(1012, "node manifest changed");
1316
1412
  }
@@ -1320,36 +1416,22 @@ var GatewayClient = class {
1320
1416
  }
1321
1417
  createSocket(handlers) {
1322
1418
  const url = this.opts.url ?? DEFAULT_GATEWAY_CLIENT_URL;
1323
- const usesTls = isWssUrl(url);
1324
- if (this.opts.tlsFingerprint && !usesTls) throw new GatewayClientSocketFactoryConfigurationError("gateway tls fingerprint requires wss:// gateway url");
1325
- const allowPrivateWs = (this.opts.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
1326
- if (!isSecureWebSocketUrl(url, { allowPrivateWs })) {
1327
- let displayHost = url;
1328
- try {
1329
- displayHost = new URL(url).hostname || url;
1330
- } catch {}
1331
- throw new GatewayClientSocketFactoryConfigurationError(`SECURITY ERROR: Cannot connect to "${displayHost}" over plaintext ws://. Both credentials and chat data would be exposed to network interception. Use wss:// for remote URLs. Safe defaults: keep gateway.bind=loopback and connect via SSH tunnel (ssh -N -L 18789:127.0.0.1:18789 user@gateway-host), or use Tailscale Serve/Funnel. ` + (allowPrivateWs ? "" : "Break-glass (trusted private networks only): set OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1. ") + "Run `openclaw doctor --fix` for guidance.");
1332
- }
1419
+ const handshakeTimeoutMs = resolvePreauthHandshakeTimeoutMs({
1420
+ env: this.opts.env,
1421
+ configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
1422
+ });
1423
+ const transport = resolveGatewayWebSocketTransport({
1424
+ url,
1425
+ tlsFingerprint: this.opts.tlsFingerprint,
1426
+ env: this.opts.env,
1427
+ normalizeTlsFingerprint: this.deps.normalizeTlsFingerprint,
1428
+ options: {
1429
+ maxPayload: 25 * 1024 * 1024,
1430
+ handshakeTimeout: handshakeTimeoutMs,
1431
+ ...this.opts.origin ? { origin: this.opts.origin } : {}
1432
+ }
1433
+ });
1333
1434
  this.deps.beforeConnect();
1334
- const wsOptions = {
1335
- maxPayload: 25 * 1024 * 1024,
1336
- handshakeTimeout: resolvePreauthHandshakeTimeoutMs({
1337
- env: this.opts.env,
1338
- configuredTimeoutMs: this.opts.preauthHandshakeTimeoutMs
1339
- }),
1340
- ...this.opts.origin ? { origin: this.opts.origin } : {}
1341
- };
1342
- if (usesTls && this.opts.tlsFingerprint) {
1343
- wsOptions.rejectUnauthorized = false;
1344
- wsOptions.checkServerIdentity = (_hostValue, cert) => {
1345
- const fingerprintValue = typeof cert === "object" && cert && "fingerprint256" in cert ? cert.fingerprint256 ?? "" : "";
1346
- const fingerprint = this.deps.normalizeTlsFingerprint(typeof fingerprintValue === "string" ? fingerprintValue : "");
1347
- const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint ?? "");
1348
- if (!expected) return;
1349
- if (!fingerprint) return /* @__PURE__ */ new Error("Missing server TLS fingerprint");
1350
- if (fingerprint !== expected) return /* @__PURE__ */ new Error("Server TLS fingerprint mismatch");
1351
- };
1352
- }
1353
1435
  let ws;
1354
1436
  let unregisterGatewayLoopbackBypass;
1355
1437
  try {
@@ -1358,7 +1440,7 @@ var GatewayClient = class {
1358
1440
  throw new GatewayClientTransportPolicyError(error instanceof Error ? error.message : String(error));
1359
1441
  }
1360
1442
  try {
1361
- ws = new WebSocket(url, wsOptions);
1443
+ ws = new WebSocket(url, transport.options);
1362
1444
  ws.binaryType = "nodebuffer";
1363
1445
  } catch (error) {
1364
1446
  throw error instanceof Error ? error : new Error(String(error));
@@ -1367,15 +1449,14 @@ var GatewayClient = class {
1367
1449
  }
1368
1450
  this.ws = ws;
1369
1451
  this.transportValidated = false;
1452
+ let upgradeError;
1370
1453
  ws.on("open", () => {
1371
1454
  handlers.open();
1372
- if (usesTls && this.opts.tlsFingerprint) {
1373
- const tlsError = this.validateTlsFingerprint();
1374
- if (tlsError) {
1375
- handlers.error(tlsError);
1376
- ws.close(1008, tlsError.message);
1377
- return;
1378
- }
1455
+ const tlsError = transport.validateSocket(ws);
1456
+ if (tlsError) {
1457
+ handlers.error(tlsError);
1458
+ ws.close(1008, tlsError.message);
1459
+ return;
1379
1460
  }
1380
1461
  this.transportValidated = true;
1381
1462
  });
@@ -1386,7 +1467,25 @@ var GatewayClient = class {
1386
1467
  this.resolvePendingStop(ws);
1387
1468
  handlers.close(code, reasonText);
1388
1469
  });
1470
+ ws.on("unexpected-response", (request, response) => {
1471
+ readUpgradeErrorBody(response).then((body) => {
1472
+ const statusCode = response.statusCode;
1473
+ upgradeError = new GatewayClientRequestError({
1474
+ code: "UNAVAILABLE",
1475
+ message: `gateway rejected websocket upgrade (HTTP ${statusCode ?? "unknown"})${body ? `: ${body}` : ""}`,
1476
+ retryable: true,
1477
+ details: {
1478
+ reason: "websocket-upgrade-rejected",
1479
+ ...statusCode === void 0 ? {} : { httpStatus: statusCode }
1480
+ }
1481
+ });
1482
+ handlers.error(upgradeError);
1483
+ request.destroy();
1484
+ ws.close();
1485
+ });
1486
+ });
1389
1487
  ws.on("error", (err) => {
1488
+ if (upgradeError) return;
1390
1489
  this.logDebug(`gateway client error: ${formatGatewayClientErrorForLog(err)}`);
1391
1490
  handlers.error(err instanceof Error ? err : new Error(String(err)));
1392
1491
  });
@@ -1486,22 +1585,34 @@ var GatewayClient = class {
1486
1585
  storedScopes,
1487
1586
  defaultScopes: ["operator.admin"]
1488
1587
  });
1489
- const platform = this.opts.platform ?? process.platform;
1588
+ const clientMode = this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND;
1589
+ const clientId = this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT;
1590
+ const isBuiltInNodeHost = role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE && clientId === GATEWAY_CLIENT_NAMES.NODE_HOST;
1591
+ const negotiatesNodeProtocol = this.shouldNegotiateLegacyNodeProtocol();
1592
+ const useLegacyNodeProtocolEnvelope = isBuiltInNodeHost && (this.useLegacyNodeProtocolEnvelope || this.opts.maxProtocol === MIN_NODE_PROTOCOL_VERSION && (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) <= MIN_NODE_PROTOCOL_VERSION);
1593
+ const minProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.minProtocol ?? (clientMode === GATEWAY_CLIENT_MODES.PROBE ? MIN_PROBE_PROTOCOL_VERSION : role === "node" && clientMode === GATEWAY_CLIENT_MODES.NODE ? MIN_NODE_PROTOCOL_VERSION : MIN_CLIENT_PROTOCOL_VERSION);
1594
+ const maxProtocol = useLegacyNodeProtocolEnvelope ? MIN_NODE_PROTOCOL_VERSION : negotiatesNodeProtocol ? PROTOCOL_VERSION : this.opts.maxProtocol ?? PROTOCOL_VERSION;
1595
+ const configuredPlatform = this.opts.platform ?? process.platform;
1596
+ const platform = useLegacyNodeProtocolEnvelope ? resolveLegacyNodePlatform(configuredPlatform) ?? configuredPlatform : configuredPlatform;
1597
+ const deviceFamily = useLegacyNodeProtocolEnvelope ? void 0 : this.opts.deviceFamily;
1490
1598
  return {
1491
1599
  params: {
1492
- minProtocol: this.opts.minProtocol ?? MIN_CLIENT_PROTOCOL_VERSION,
1493
- maxProtocol: this.opts.maxProtocol ?? PROTOCOL_VERSION,
1600
+ minProtocol,
1601
+ maxProtocol,
1494
1602
  client: {
1495
- id: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
1603
+ id: clientId,
1496
1604
  displayName: this.opts.clientDisplayName,
1497
1605
  version: this.opts.clientVersion ?? DEFAULT_CLIENT_VERSION,
1606
+ buildId: this.opts.clientBuildId,
1498
1607
  platform,
1499
- deviceFamily: this.opts.deviceFamily,
1500
- mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
1608
+ deviceFamily,
1609
+ mode: clientMode,
1501
1610
  instanceId: this.opts.instanceId
1502
1611
  },
1503
1612
  caps: Array.isArray(this.opts.caps) ? this.opts.caps : [],
1504
1613
  commands: Array.isArray(this.opts.commands) ? this.opts.commands : void 0,
1614
+ computerUse: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.computerUse,
1615
+ workerRuns: useLegacyNodeProtocolEnvelope ? void 0 : this.opts.workerRuns,
1505
1616
  permissions: this.opts.permissions && typeof this.opts.permissions === "object" ? this.opts.permissions : void 0,
1506
1617
  pathEnv: this.opts.pathEnv,
1507
1618
  auth,
@@ -1513,30 +1624,47 @@ var GatewayClient = class {
1513
1624
  scopes,
1514
1625
  signatureToken,
1515
1626
  signedAtMs,
1516
- platform
1627
+ platform,
1628
+ deviceFamily,
1629
+ clientMode
1517
1630
  })
1518
1631
  },
1519
1632
  authApprovalRuntimeToken,
1520
1633
  authAgentRuntimeIdentityToken,
1521
1634
  resolvedDeviceToken,
1635
+ storedScopes,
1522
1636
  storedToken,
1523
1637
  usingStoredDeviceToken
1524
1638
  };
1525
1639
  }
1640
+ shouldNegotiateLegacyNodeProtocol() {
1641
+ if (this.opts.role !== "node" || this.opts.mode !== GATEWAY_CLIENT_MODES.NODE || this.opts.clientName !== GATEWAY_CLIENT_NAMES.NODE_HOST) return false;
1642
+ return (this.opts.minProtocol ?? MIN_NODE_PROTOCOL_VERSION) === MIN_NODE_PROTOCOL_VERSION && (this.opts.maxProtocol ?? PROTOCOL_VERSION) === PROTOCOL_VERSION;
1643
+ }
1644
+ shouldRetryWithLegacyNodeProtocol(error) {
1645
+ if (this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
1646
+ const detailCode = readConnectErrorDetailCode(error.details);
1647
+ return error.details?.expectedProtocol === MIN_NODE_PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
1648
+ }
1649
+ shouldRetryWithCurrentNodeProtocol(error) {
1650
+ if (!this.useLegacyNodeProtocolEnvelope || !this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
1651
+ const detailCode = readConnectErrorDetailCode(error.details);
1652
+ return error.details?.expectedProtocol === PROTOCOL_VERSION && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
1653
+ }
1526
1654
  buildDeviceConnectParams(params) {
1527
1655
  if (!this.opts.deviceIdentity) return;
1528
- const { nonce, role, scopes, signatureToken, signedAtMs, platform } = params;
1656
+ const { nonce, role, scopes, signatureToken, signedAtMs, platform, deviceFamily, clientMode } = params;
1529
1657
  const payload = buildDeviceAuthPayloadV3({
1530
1658
  deviceId: this.opts.deviceIdentity.deviceId,
1531
1659
  clientId: this.opts.clientName ?? GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT,
1532
- clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.BACKEND,
1660
+ clientMode,
1533
1661
  role,
1534
1662
  scopes,
1535
1663
  signedAtMs,
1536
1664
  token: signatureToken ?? null,
1537
1665
  nonce,
1538
1666
  platform,
1539
- deviceFamily: this.opts.deviceFamily
1667
+ deviceFamily
1540
1668
  });
1541
1669
  const signature = this.deps.signDevicePayload(this.opts.deviceIdentity.privateKeyPem, payload);
1542
1670
  return {
@@ -1548,24 +1676,66 @@ var GatewayClient = class {
1548
1676
  };
1549
1677
  }
1550
1678
  handleConnectHello(helloOk, assembled) {
1679
+ const reconnectWithCurrentNodeProtocol = this.useLegacyNodeProtocolEnvelope && this.shouldNegotiateLegacyNodeProtocol() && helloOk.protocol > MIN_NODE_PROTOCOL_VERSION;
1680
+ if (reconnectWithCurrentNodeProtocol) this.useLegacyNodeProtocolEnvelope = false;
1681
+ this.nodeProtocolTransitionPending = false;
1551
1682
  this.pendingDeviceTokenRetry = false;
1552
1683
  this.deviceTokenRetryBudgetUsed = false;
1553
1684
  this.suppressedTransientPreHelloCleanCloses = 0;
1554
1685
  const role = this.opts.role ?? "operator";
1555
1686
  const authInfo = helloOk.auth;
1556
- if (authInfo?.deviceToken && this.opts.deviceIdentity) this.deps.storeDeviceAuthToken({
1557
- deviceId: this.opts.deviceIdentity.deviceId,
1558
- role: authInfo.role ?? role,
1559
- token: authInfo.deviceToken,
1560
- scopes: authInfo.scopes ?? [],
1561
- env: this.opts.env
1562
- });
1687
+ if (authInfo?.deviceToken && this.opts.deviceIdentity) {
1688
+ const tokenRole = authInfo.role ?? role;
1689
+ const scopes = tokenRole === role && authInfo.deviceToken === assembled.storedToken ? assembled.storedScopes ?? authInfo.scopes ?? [] : authInfo.scopes ?? [];
1690
+ this.deps.storeDeviceAuthToken({
1691
+ deviceId: this.opts.deviceIdentity.deviceId,
1692
+ role: tokenRole,
1693
+ token: authInfo.deviceToken,
1694
+ scopes,
1695
+ env: this.opts.env
1696
+ });
1697
+ }
1698
+ if (this.opts.preferBootstrapToken) {
1699
+ this.opts.token = void 0;
1700
+ this.opts.bootstrapToken = void 0;
1701
+ this.opts.password = void 0;
1702
+ this.opts.preferBootstrapToken = false;
1703
+ }
1563
1704
  this.tickIntervalMs = typeof helloOk.policy?.tickIntervalMs === "number" ? helloOk.policy.tickIntervalMs : 3e4;
1705
+ if (reconnectWithCurrentNodeProtocol) {
1706
+ this.suppressNextHelloCallback = true;
1707
+ this.protocol.resetReconnectBackoff(250);
1708
+ this.protocol.closeSocket(1012, "gateway protocol upgraded");
1709
+ return;
1710
+ }
1564
1711
  this.lastTick = Date.now();
1565
1712
  this.startTickWatch();
1566
1713
  }
1567
1714
  handleConnectRequestFailure(error, assembled) {
1715
+ if (this.shouldRetryWithCurrentNodeProtocol(error)) {
1716
+ const resetBackoff = !this.nodeProtocolTransitionPending;
1717
+ this.useLegacyNodeProtocolEnvelope = false;
1718
+ this.nodeProtocolTransitionPending = true;
1719
+ if (resetBackoff) this.protocol.resetReconnectBackoff(250);
1720
+ this.logDebug("gateway rejected protocol v3; retrying node host with protocol v4");
1721
+ return {
1722
+ closeCode: 1008,
1723
+ closeReason: "connect retry"
1724
+ };
1725
+ }
1726
+ if (this.shouldRetryWithLegacyNodeProtocol(error)) {
1727
+ const resetBackoff = !this.nodeProtocolTransitionPending;
1728
+ this.useLegacyNodeProtocolEnvelope = true;
1729
+ this.nodeProtocolTransitionPending = true;
1730
+ if (resetBackoff) this.protocol.resetReconnectBackoff(250);
1731
+ this.logDebug("gateway rejected protocol v4; retrying node host with protocol v3");
1732
+ return {
1733
+ closeCode: 1008,
1734
+ closeReason: "connect retry"
1735
+ };
1736
+ }
1568
1737
  const role = this.opts.role ?? "operator";
1738
+ const detailCode = error instanceof GatewayClientRequestError ? readConnectErrorDetailCode(error.details) : null;
1569
1739
  const shouldRetryWithDeviceToken = shouldRetryGatewayWithDeviceToken({
1570
1740
  retryBudgetUsed: this.deviceTokenRetryBudgetUsed,
1571
1741
  currentDeviceToken: assembled.resolvedDeviceToken,
@@ -1574,7 +1744,7 @@ var GatewayClient = class {
1574
1744
  trustedEndpoint: this.isTrustedDeviceRetryEndpoint(),
1575
1745
  errorDetails: error instanceof GatewayClientRequestError ? error.details : void 0
1576
1746
  });
1577
- if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && error instanceof GatewayClientRequestError && readConnectErrorDetailCode(error.details) === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
1747
+ if (this.opts.deviceIdentity && assembled.usingStoredDeviceToken && detailCode === ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH) {
1578
1748
  const deviceId = this.opts.deviceIdentity.deviceId;
1579
1749
  try {
1580
1750
  this.deps.clearDeviceAuthToken({
@@ -1630,7 +1800,7 @@ var GatewayClient = class {
1630
1800
  }
1631
1801
  this.notifyConnectError(error);
1632
1802
  const message = `gateway connect failed: ${formatGatewayClientErrorForLog(error)}`;
1633
- if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error)) this.logDebug(message);
1803
+ if (this.opts.mode === GATEWAY_CLIENT_MODES.PROBE || isGatewayClientStoppedError(error) || detailCode === ConnectErrorDetailCodes.AUTH_RATE_LIMITED) this.logDebug(message);
1634
1804
  else this.logError(message);
1635
1805
  return {
1636
1806
  closeCode: 1008,
@@ -1651,7 +1821,7 @@ var GatewayClient = class {
1651
1821
  return {
1652
1822
  retry: true,
1653
1823
  notify: true,
1654
- pendingError: new GatewayClientTransientPreHelloCloseError()
1824
+ pendingError: /* @__PURE__ */ new Error("gateway transient pre-hello clean close")
1655
1825
  };
1656
1826
  }
1657
1827
  if (info.transientPreHelloCleanClose || context.connectRequestSent && !context.helloReceived && !context.connectFailure) {
@@ -1687,11 +1857,13 @@ var GatewayClient = class {
1687
1857
  phase: context.helloReceived ? "post-hello" : "pre-hello",
1688
1858
  socketOpened: context.socketOpened,
1689
1859
  transportValidated: this.transportValidated,
1690
- transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === ""
1860
+ connectRequestSent: context.connectRequestSent,
1861
+ transientPreHelloCleanClose: !context.helloReceived && context.code === 1e3 && context.reason === "",
1862
+ ...context.connectFailure?.error ? { connectError: context.connectFailure.error } : {}
1691
1863
  };
1692
1864
  }
1693
1865
  clearStaleDeviceTokenForClose(code, reason) {
1694
- if (code !== 1008 || !normalizeLowercaseStringOrEmpty(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
1866
+ if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || this.opts.token || this.opts.password || !this.opts.deviceIdentity) return;
1695
1867
  const deviceId = this.opts.deviceIdentity.deviceId;
1696
1868
  const role = this.opts.role ?? "operator";
1697
1869
  try {
@@ -1724,14 +1896,14 @@ var GatewayClient = class {
1724
1896
  if (!params.authApprovalRuntimeToken) return false;
1725
1897
  if (!(params.error instanceof GatewayClientRequestError)) return false;
1726
1898
  if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
1727
- const message = normalizeLowercaseStringOrEmpty(params.error.message);
1899
+ const message = normalizeGatewayErrorText(params.error.message);
1728
1900
  return message.includes("invalid connect params") && message.includes("approvalruntimetoken");
1729
1901
  }
1730
1902
  shouldFailClosedForUnsupportedAgentRuntimeIdentity(params) {
1731
1903
  if (!params.authAgentRuntimeIdentityToken) return false;
1732
1904
  if (!(params.error instanceof GatewayClientRequestError)) return false;
1733
1905
  if (params.error.gatewayCode !== "INVALID_REQUEST") return false;
1734
- const message = normalizeLowercaseStringOrEmpty(params.error.message);
1906
+ const message = normalizeGatewayErrorText(params.error.message);
1735
1907
  return message.includes("invalid connect params") && message.includes("agentruntimeidentitytoken");
1736
1908
  }
1737
1909
  isTrustedDeviceRetryEndpoint() {
@@ -1739,7 +1911,7 @@ var GatewayClient = class {
1739
1911
  try {
1740
1912
  const parsed = new URL(rawUrl);
1741
1913
  const protocol = parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol;
1742
- if (isLoopbackHost(parsed.hostname)) return true;
1914
+ if (isGatewayLoopbackHost(parsed.hostname)) return true;
1743
1915
  return protocol === "wss:" && Boolean(this.opts.tlsFingerprint?.trim());
1744
1916
  } catch {
1745
1917
  return false;
@@ -1754,6 +1926,7 @@ var GatewayClient = class {
1754
1926
  return selectGatewayConnectAuth({
1755
1927
  token: this.opts.token,
1756
1928
  bootstrapToken: this.opts.bootstrapToken,
1929
+ preferBootstrapToken: this.opts.preferBootstrapToken,
1757
1930
  deviceToken: this.opts.deviceToken,
1758
1931
  password: this.opts.password,
1759
1932
  approvalRuntimeToken: this.approvalRuntimeTokenCompatibilityDisabled ? void 0 : this.opts.approvalRuntimeToken,
@@ -1778,21 +1951,9 @@ var GatewayClient = class {
1778
1951
  if (gap > (typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) ? Math.max(1, rawTimeoutMs) : this.tickIntervalMs * 2)) this.protocol.closeSocket(4e3, "tick timeout");
1779
1952
  }, interval);
1780
1953
  }
1781
- validateTlsFingerprint() {
1782
- if (!this.opts.tlsFingerprint || !this.ws) return null;
1783
- const expected = this.deps.normalizeTlsFingerprint(this.opts.tlsFingerprint);
1784
- if (!expected) return /* @__PURE__ */ new Error("gateway tls fingerprint missing");
1785
- const socket = this.ws["_socket"];
1786
- if (!socket || typeof socket.getPeerCertificate !== "function") return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1787
- const cert = socket.getPeerCertificate();
1788
- const fingerprint = this.deps.normalizeTlsFingerprint(cert?.fingerprint256 ?? "");
1789
- if (!fingerprint) return /* @__PURE__ */ new Error("gateway tls fingerprint unavailable");
1790
- if (fingerprint !== expected) return /* @__PURE__ */ new Error("gateway tls fingerprint mismatch");
1791
- return null;
1792
- }
1793
1954
  async request(method, params, opts) {
1794
1955
  const expectFinal = opts?.expectFinal === true;
1795
- const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? resolveSafeTimeoutDelayMs(opts.timeoutMs, { minMs: 0 }) : expectFinal ? null : this.requestTimeoutMs;
1956
+ const timeoutMs = opts?.timeoutMs === null ? null : typeof opts?.timeoutMs === "number" && Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : expectFinal ? null : this.requestTimeoutMs;
1796
1957
  return this.protocol.request(method, params, {
1797
1958
  expectFinal,
1798
1959
  timeoutMs,
@@ -1808,4 +1969,4 @@ function createGatewayRequestAbortError(method) {
1808
1969
  return err;
1809
1970
  }
1810
1971
  //#endregion
1811
- export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };
1972
+ export { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, GatewayBrowserDeviceAuthLifecycle, GatewayClient, GatewayClientRequestError, GatewayClientRequestTimeoutError, GatewayScopeUpgrade, GatewaySessionMessageSubscriptionCoordinator, MAX_CONNECT_CHALLENGE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_DELAY_MS, MIN_CONNECT_CHALLENGE_TIMEOUT_MS, addSafeTimeoutDelayGraceMs, buildDeviceAuthPayload, buildDeviceAuthPayloadV3, buildGatewayConnectAuth, clampConnectChallengeTimeoutMs, clearGatewayConnectTimeout, createSessionProjection, gatewayCredentialScope, gatewayOriginScope, getConnectChallengeTimeoutMsFromEnv, getGatewaySessionMessageSubscriptionCoordinator, hasSessionProjectionAcceptedFinal, isGatewayConnectAssemblyError, isLocallyOptimisticSessionMessage, normalizeDeviceMetadataForAuth, normalizeSessionProjectionRunId, projectLiveSessionMessage, readSessionMessageIdentity, readSessionMessageSequence, reconcileSessionProjectionSnapshot, reduceSessionProjection, reduceSessionProjectionRunEvent, releaseGatewaySessionMessageSubscription, resetGatewaySessionMessageSubscriptionCoordinator, resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs, resolveGatewayConnectScopes, resolvePreauthHandshakeTimeoutMs, resolveSafeTimeoutDelayMs, selectGatewayConnectAuth, shouldRetryGatewayWithDeviceToken, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait, startGatewayConnectTimeout, waitForEventLoopReady };