@openclaw/gateway-client 2026.10.1 → 2026.10.5-beta.1

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/README.md CHANGED
@@ -9,9 +9,9 @@ The current wire protocol is version 4. General clients must advertise exactly v
9
9
  `minProtocol: 4` and `maxProtocol: 4`. See the
10
10
  [Gateway protocol specification](https://docs.openclaw.ai/gateway/protocol) for
11
11
  the complete handshake, authentication, role, scope, and method contracts.
12
- Exact node identities (`role: "node"` plus `mode: "node"`) and probe clients
12
+ Exact node identities (`role: "node"` plus `mode: "node"`) and diagnostic clients
13
13
  can use v3. The built-in node host starts with an exact v4 envelope, then retries
14
- an exact v3 envelope after a v3 Gateway rejects v4. If that legacy probe reaches
14
+ an exact v3 envelope after a v3 Gateway rejects v4. If that legacy connection attempt reaches
15
15
  an upgraded v4 Gateway, the client reconnects with the full v4 envelope before
16
16
  reporting readiness. Other exact node identities default to `[3, 4]`. Explicit
17
17
  bounds override these defaults; `[3, 4]` on the built-in node host selects the
@@ -187,10 +187,19 @@ pre-handshake values are documented in the
187
187
 
188
188
  Use the `./timeouts` entry point when a host must align readiness or watchdog
189
189
  budgets with these defaults. Use the `./readiness` entry point when startup must
190
- wait for an event-loop probe before opening the socket.
190
+ wait for an event-loop check before opening the socket.
191
191
 
192
192
  ## Streaming chat
193
193
 
194
+ The `runId` returned by `chat.send` identifies the submitted input even when a
195
+ steer falls back to a followup. Queue admission is nonterminal: keep observing
196
+ that ID until the consuming execution completes, or the input is rejected,
197
+ canceled, or dropped before consumption. Collected inputs each receive the
198
+ batch execution's terminal outcome under their original IDs. SDK run handles
199
+ use the same IDs for events, `wait()`, and cancellation; canceling an input before
200
+ consumption withdraws only that input. A steer accepted into the active turn
201
+ continues to complete after its transcript receipt while that turn remains active.
202
+
194
203
  Event callbacks receive the wire payload unchanged. A `chat` delta's optional
195
204
  `message` is an authoritative snapshot that already includes `deltaText`.
196
205
  Without `message`, append `deltaText` to the run's existing text. `replace: true`
@@ -229,7 +238,7 @@ reports `rebaselined`, `recovered`, or `unavailable`; unavailable full text omit
229
238
  have finite retention, and sessionless or unavailable transcript occurrences
230
239
  cannot be reconstructed; unknown outcomes remain unsettled with a recovery
231
240
  notice. Automatic recovery stops after four consecutive unavailable observations
232
- (initial probe plus three waits), releases retention protection, and stays stopped
241
+ (initial check plus three waits), releases retention protection, and stays stopped
233
242
  across reconnects. Confirmed activity resets that budget; late terminal frames can
234
243
  still settle the run. Queued replies use exponential backoff with the Gateway
235
244
  client's positive 20% jitter and a 25–30 second cap. ACP reports unavailable full
@@ -1,7 +1,7 @@
1
1
  import { A as normalizeSessionProjectionRunId, C as reduceSessionProjectionRunEvent, D as SessionMessageIdentity, E as SessionMessageEnvelope, F as gatewayOriginScope, I as mergeChatStreamMessage, L as buildDeviceAuthPayload, M as readSessionMessageIdentity, N as readSessionMessageSequence, O as SessionProjectionEntry, P as gatewayCredentialScope, R as buildDeviceAuthPayloadV3, S as SessionProjectionRunTransition, T as readSessionProjectionFinalMessageIdentity, _ as reconcileSessionProjectionSnapshot, a as GatewaySessionMessageSubscriptionOptions, b as projectSessionTerminalReplyMessage, c as resetGatewaySessionMessageSubscriptionCoordinator, d as SessionProjectionRunStatus, f as SessionProjectionScope, g as projectLiveSessionMessage, h as createSessionProjection, i as GatewaySessionMessageSubscriptionCoordinatorOptions, j as readAssistantStreamSegmentIdentity, k as isLocallyOptimisticSessionMessage, l as SessionProjectionEvent, m as SessionProjectionState, n as GatewaySessionMessageSubscription, o as getGatewaySessionMessageSubscriptionCoordinator, p as SessionProjectionSnapshotOptions, r as GatewaySessionMessageSubscriptionCoordinator, s as releaseGatewaySessionMessageSubscription, t as GatewaySessionMessageRequestClient, u as SessionProjectionRun, v as reduceSessionProjection, w as hasSessionProjectionAcceptedFinal, x as SessionProjectionGatewayRunEvent, y as isSessionProjectionErrorMessage, z as normalizeDeviceMetadataForAuth } from "./session-subscriptions-Bcq6_wtR.mjs";
2
2
  import { a as GatewayBrowserDeviceTokenStore, c as resolveGatewayConnectScopes, i as GatewayBrowserDeviceTokenRecord, l as selectGatewayConnectAuth, n as GatewayBrowserDeviceAuthPlan, o as GatewayConnectAuthSelection, r as GatewayBrowserDeviceIdentity, s as buildGatewayConnectAuth, t as GatewayBrowserDeviceAuthLifecycle, u as shouldRetryGatewayWithDeviceToken } from "./browser-device-auth-_UweR4aN.mjs";
3
3
  import { i as isGatewayProtocolResponseError, n as GatewayProtocolRequestOptions, r as GatewayProtocolRequestTimeoutError, t as GatewayProtocolRequestError } from "./protocol-request-CtVhe3Bj.mjs";
4
- import { a as GatewayProtocolConnectAuthority, c as GatewayProtocolTiming, i as GatewayProtocolCloseContext, l as GatewayProtocolRequestTiming, o as GatewayProtocolSocket, s as GatewayProtocolSocketHandlers, t as GatewayProtocolClient } from "./protocol-client-DT451Msj.mjs";
4
+ import { a as GatewayProtocolConnectAuthority, c as GatewayProtocolTiming, i as GatewayProtocolCloseContext, l as GatewayProtocolRequestTiming, o as GatewayProtocolSocket, s as GatewayProtocolSocketHandlers, t as GatewayProtocolClient } from "./protocol-client-CKMLpN-S.mjs";
5
5
  import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, resolveSafeTimeoutDelayMs } from "./timeouts.mjs";
6
6
  import { ConnectParams as ConnectParams$1, GATEWAY_SERVER_CAPS } from "@openclaw/gateway-protocol/frame-guards";
7
7
  import { ChannelsStatusResult, ConnectParams, ErrorShape, EventFrame, HelloOk, QuestionRecord } from "@openclaw/gateway-protocol";
package/dist/browser.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { A as resolveGatewayConnectScopes, B as buildDeviceAuthPayloadV3, C as isGatewayProtocolResponseError, D as gatewayOriginScope, E as gatewayCredentialScope, F as mergeChatStreamMessage, I as asNullableRecord, M as shouldRetryGatewayWithDeviceToken, O as GatewayBrowserDeviceAuthLifecycle, S as GatewayProtocolRequestTimeoutError, T as resolveModelCatalogConnect, V as normalizeDeviceMetadataForAuth, _ as readSessionMessageIdentity, a as createSessionProjection, b as GatewayProtocolClient, c as reduceSessionProjection, d as readSessionProjectionFinalMessageIdentity, f as isSessionProjectionErrorMessage, g as readAssistantStreamSegmentIdentity, h as normalizeSessionProjectionRunId, i as resetGatewaySessionMessageSubscriptionCoordinator, j as selectGatewayConnectAuth, k as buildGatewayConnectAuth, l as reduceSessionProjectionRunEvent, m as isLocallyOptimisticSessionMessage, n as getGatewaySessionMessageSubscriptionCoordinator, o as projectLiveSessionMessage, p as projectSessionTerminalReplyMessage, r as releaseGatewaySessionMessageSubscription, s as reconcileSessionProjectionSnapshot, t as GatewaySessionMessageSubscriptionCoordinator, u as hasSessionProjectionAcceptedFinal, v as readSessionMessageSequence, x as GatewayProtocolRequestError, y as shouldPauseGatewayReconnect, z as buildDeviceAuthPayload } from "./session-subscriptions-BmRgBWJk.mjs";
2
- import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, resolveSafeTimeoutDelayMs } from "./timeouts.mjs";
1
+ import { A as buildGatewayConnectAuth, B as normalizeDeviceMetadataForAuth, C as isGatewayProtocolResponseError, D as gatewayCredentialScope, E as resolveModelCatalogConnect, F as asNullableRecord, M as selectGatewayConnectAuth, N as shouldRetryGatewayWithDeviceToken, O as gatewayOriginScope, P as mergeChatStreamMessage, R as buildDeviceAuthPayload, S as GatewayProtocolRequestTimeoutError, _ as isSessionProjectionErrorMessage, a as createSessionProjection, b as GatewayProtocolClient, c as reduceSessionProjection, d as readSessionProjectionFinalMessageIdentity, f as isLocallyOptimisticSessionMessage, g as readSessionMessageSequence, h as readSessionMessageIdentity, i as resetGatewaySessionMessageSubscriptionCoordinator, j as resolveGatewayConnectScopes, k as GatewayBrowserDeviceAuthLifecycle, l as reduceSessionProjectionRunEvent, m as readAssistantStreamSegmentIdentity, n as getGatewaySessionMessageSubscriptionCoordinator, o as projectLiveSessionMessage, p as normalizeSessionProjectionRunId, r as releaseGatewaySessionMessageSubscription, s as reconcileSessionProjectionSnapshot, t as GatewaySessionMessageSubscriptionCoordinator, u as hasSessionProjectionAcceptedFinal, v as projectSessionTerminalReplyMessage, x as GatewayProtocolRequestError, y as shouldPauseGatewayReconnect, z as buildDeviceAuthPayloadV3 } from "./session-subscriptions-DE0w2upu.mjs";
2
+ import { n as DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, p as resolveSafeTimeoutDelayMs, t as DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS } from "./timeouts-CQTCye5U.mjs";
3
3
  import { GATEWAY_SERVER_CAPS } from "@openclaw/gateway-protocol/frame-guards";
4
4
  export * from "@openclaw/gateway-protocol/connect-error-details";
5
5
  export * from "@openclaw/gateway-protocol/gateway-error-details";
package/dist/index.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { A as normalizeSessionProjectionRunId, C as reduceSessionProjectionRunEvent, D as SessionMessageIdentity, E as SessionMessageEnvelope, F as gatewayOriginScope, I as mergeChatStreamMessage, L as buildDeviceAuthPayload, M as readSessionMessageIdentity, N as readSessionMessageSequence, O as SessionProjectionEntry, P as gatewayCredentialScope, R as buildDeviceAuthPayloadV3, S as SessionProjectionRunTransition, T as readSessionProjectionFinalMessageIdentity, _ as reconcileSessionProjectionSnapshot, a as GatewaySessionMessageSubscriptionOptions, b as projectSessionTerminalReplyMessage, c as resetGatewaySessionMessageSubscriptionCoordinator, d as SessionProjectionRunStatus, f as SessionProjectionScope, g as projectLiveSessionMessage, h as createSessionProjection, i as GatewaySessionMessageSubscriptionCoordinatorOptions, j as readAssistantStreamSegmentIdentity, k as isLocallyOptimisticSessionMessage, l as SessionProjectionEvent, m as SessionProjectionState, n as GatewaySessionMessageSubscription, o as getGatewaySessionMessageSubscriptionCoordinator, p as SessionProjectionSnapshotOptions, r as GatewaySessionMessageSubscriptionCoordinator, s as releaseGatewaySessionMessageSubscription, t as GatewaySessionMessageRequestClient, u as SessionProjectionRun, v as reduceSessionProjection, w as hasSessionProjectionAcceptedFinal, x as SessionProjectionGatewayRunEvent, y as isSessionProjectionErrorMessage, z as normalizeDeviceMetadataForAuth } from "./session-subscriptions-Bcq6_wtR.mjs";
2
2
  import { a as GatewayBrowserDeviceTokenStore, c as resolveGatewayConnectScopes, i as GatewayBrowserDeviceTokenRecord, l as selectGatewayConnectAuth, n as GatewayBrowserDeviceAuthPlan, o as GatewayConnectAuthSelection, r as GatewayBrowserDeviceIdentity, s as buildGatewayConnectAuth, t as GatewayBrowserDeviceAuthLifecycle, u as shouldRetryGatewayWithDeviceToken } from "./browser-device-auth-_UweR4aN.mjs";
3
3
  import { i as isGatewayProtocolResponseError } from "./protocol-request-CtVhe3Bj.mjs";
4
- import { n as GatewayClientCloseInfo, r as GatewayClientConnectionMetadata } from "./protocol-client-DT451Msj.mjs";
4
+ import { n as GatewayClientCloseInfo, r as GatewayClientConnectionMetadata } from "./protocol-client-CKMLpN-S.mjs";
5
5
  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";
6
- import { _ as isGatewayConnectAssemblyError, a as startGatewayClientWithReadinessWait, c as waitForEventLoopReady, d as GatewayClientHostDeps, f as GatewayClientOptions, g as GatewayClientRequestError, h as GatewayReconnectPausedInfo, i as startGatewayClientWhenEventLoopReady, l as DeviceIdentity, m as GatewayClientRequestTimeoutError, n as GatewayClientStartReadinessOptions, o as EventLoopReadyOptions, p as GatewayClientRequestOptions, r as GatewayClientStartable, s as EventLoopReadyResult, t as EventLoopReadyWaiter, u as GatewayClient, v as DeviceAuthTokenRecord } from "./readiness-CLaFwfd4.mjs";
6
+ import { _ as isGatewayConnectAssemblyError, a as startGatewayClientWithReadinessWait, c as waitForEventLoopReady, d as GatewayClientHostDeps, f as GatewayClientOptions, g as GatewayClientRequestError, h as GatewayReconnectPausedInfo, i as startGatewayClientWhenEventLoopReady, l as DeviceIdentity, m as GatewayClientRequestTimeoutError, n as GatewayClientStartReadinessOptions, o as EventLoopReadyOptions, p as GatewayClientRequestOptions, r as GatewayClientStartable, s as EventLoopReadyResult, t as EventLoopReadyWaiter, u as GatewayClient, v as DeviceAuthTokenRecord } from "./readiness-BbuB7IzG.mjs";
7
7
  import { GatewayScopeUpgrade, ScopeUpgradeBinding, ScopeUpgradeOptions, ScopeUpgradeOutcome } from "./scope-upgrade.mjs";
8
8
  //#region packages/gateway-client/src/run-recovery-text.d.ts
9
9
  export type RecoveryRequest = (method: string, params: Record<string, unknown>, signal: AbortSignal) => Promise<unknown>;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { A as resolveGatewayConnectScopes, B as buildDeviceAuthPayloadV3, C as isGatewayProtocolResponseError, D as gatewayOriginScope, E as gatewayCredentialScope, F as mergeChatStreamMessage, L as asRecord, M as shouldRetryGatewayWithDeviceToken, N as normalizeLowercaseStringOrEmpty, O as GatewayBrowserDeviceAuthLifecycle, P as normalizeOptionalString, R as isRecord, S as GatewayProtocolRequestTimeoutError, T as resolveModelCatalogConnect, V as normalizeDeviceMetadataForAuth, _ as readSessionMessageIdentity, a as createSessionProjection, b as GatewayProtocolClient, c as reduceSessionProjection, d as readSessionProjectionFinalMessageIdentity, f as isSessionProjectionErrorMessage, g as readAssistantStreamSegmentIdentity, h as normalizeSessionProjectionRunId, i as resetGatewaySessionMessageSubscriptionCoordinator, j as selectGatewayConnectAuth, k as buildGatewayConnectAuth, l as reduceSessionProjectionRunEvent, m as isLocallyOptimisticSessionMessage, n as getGatewaySessionMessageSubscriptionCoordinator, o as projectLiveSessionMessage, p as projectSessionTerminalReplyMessage, r as releaseGatewaySessionMessageSubscription, s as reconcileSessionProjectionSnapshot, t as GatewaySessionMessageSubscriptionCoordinator, u as hasSessionProjectionAcceptedFinal, v as readSessionMessageSequence, w as raceWithTimeout, x as GatewayProtocolRequestError, y as shouldPauseGatewayReconnect, z as buildDeviceAuthPayload } from "./session-subscriptions-BmRgBWJk.mjs";
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";
1
+ import { A as buildGatewayConnectAuth, B as normalizeDeviceMetadataForAuth, C as isGatewayProtocolResponseError, D as gatewayCredentialScope, E as resolveModelCatalogConnect, I as asRecord, L as isRecord, M as selectGatewayConnectAuth, N as shouldRetryGatewayWithDeviceToken, O as gatewayOriginScope, P as mergeChatStreamMessage, R as buildDeviceAuthPayload, S as GatewayProtocolRequestTimeoutError, T as raceWithTimeout, _ as isSessionProjectionErrorMessage, a as createSessionProjection, b as GatewayProtocolClient, c as reduceSessionProjection, d as readSessionProjectionFinalMessageIdentity, f as isLocallyOptimisticSessionMessage, g as readSessionMessageSequence, h as readSessionMessageIdentity, i as resetGatewaySessionMessageSubscriptionCoordinator, j as resolveGatewayConnectScopes, k as GatewayBrowserDeviceAuthLifecycle, l as reduceSessionProjectionRunEvent, m as readAssistantStreamSegmentIdentity, n as getGatewaySessionMessageSubscriptionCoordinator, o as projectLiveSessionMessage, p as normalizeSessionProjectionRunId, r as releaseGatewaySessionMessageSubscription, s as reconcileSessionProjectionSnapshot, t as GatewaySessionMessageSubscriptionCoordinator, u as hasSessionProjectionAcceptedFinal, v as projectSessionTerminalReplyMessage, w as createAbortError, x as GatewayProtocolRequestError, y as shouldPauseGatewayReconnect, z as buildDeviceAuthPayloadV3 } from "./session-subscriptions-DE0w2upu.mjs";
2
+ import { _ as normalizeOptionalString, a as MIN_CONNECT_CHALLENGE_TIMEOUT_MS, c as clearGatewayConnectTimeout, d as resolveFiniteTimeoutDelayMs, f as resolvePreauthHandshakeTimeoutMs, h as normalizeLowercaseStringOrEmpty, i as MAX_SAFE_TIMEOUT_DELAY_MS, l as getConnectChallengeTimeoutMsFromEnv, m as startGatewayConnectTimeout, n as DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, o as addSafeTimeoutDelayGraceMs, p as resolveSafeTimeoutDelayMs, r as MAX_CONNECT_CHALLENGE_TIMEOUT_MS, s as clampConnectChallengeTimeoutMs, t as DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, u as resolveConnectChallengeTimeoutMs } from "./timeouts-CQTCye5U.mjs";
3
3
  import { rawDataToString } from "./websocket-data.mjs";
4
- import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-DnB0NLBq.mjs";
4
+ import { n as startGatewayClientWithReadinessWait, r as waitForEventLoopReady, t as startGatewayClientWhenEventLoopReady } from "./readiness-rG6_5Ynk.mjs";
5
5
  import { GatewayScopeUpgrade } from "./scope-upgrade.mjs";
6
6
  import { createRequire } from "node:module";
7
7
  import { ConnectErrorDetailCodes, formatConnectErrorMessage, readConnectErrorDetailCode } from "@openclaw/gateway-protocol/connect-error-details";
@@ -78,9 +78,7 @@ function normalizeUrlQueryParamName(name) {
78
78
  }
79
79
  function looksLikeNestedUrlValue(value) {
80
80
  if (URL_SCHEME_RE.test(value)) return true;
81
- const forwardAuthorityIndex = value.indexOf("//");
82
- const backwardAuthorityIndex = value.indexOf("\\\\");
83
- const authorityIndex = forwardAuthorityIndex < 0 ? backwardAuthorityIndex : backwardAuthorityIndex < 0 ? forwardAuthorityIndex : Math.min(forwardAuthorityIndex, backwardAuthorityIndex);
81
+ const authorityIndex = value.search(/\/\/|\\\\/u);
84
82
  if (authorityIndex >= 0 && value.includes("@", authorityIndex + 2)) return true;
85
83
  const queryIndex = value.search(/[?&]/u);
86
84
  if (queryIndex >= 0 && value.includes("=", queryIndex + 1)) return true;
@@ -89,7 +87,7 @@ function looksLikeNestedUrlValue(value) {
89
87
  return /%[\da-f]{2}/iu.test(value);
90
88
  }
91
89
  /** True for auth-like URL query parameter names that should be redacted. */
92
- function isSensitiveUrlQueryParamName$1(name) {
90
+ function isSensitiveUrlQueryParamName(name) {
93
91
  const normalized = normalizeUrlQueryParamName(name);
94
92
  return normalized.unresolvedEncoding || SENSITIVE_URL_QUERY_PARAM_NAMES.has(normalized.value) || SUFFIXED_OR_SCOPED_TOKEN_QUERY_PARAM_RE.test(normalized.value);
95
93
  }
@@ -107,7 +105,7 @@ function redactDirectSensitiveUrl(value) {
107
105
  parsed.password = parsed.password ? "***" : "";
108
106
  mutated = true;
109
107
  }
110
- for (const key of Array.from(parsed.searchParams.keys())) if (isSensitiveUrlQueryParamName$1(key)) {
108
+ for (const key of Array.from(parsed.searchParams.keys())) if (isSensitiveUrlQueryParamName(key)) {
111
109
  parsed.searchParams.set(key, "***");
112
110
  mutated = true;
113
111
  }
@@ -118,12 +116,11 @@ function redactDirectSensitiveUrl(value) {
118
116
  }
119
117
  function redactQueryString(value, depth) {
120
118
  const params = new URLSearchParams(value);
121
- const entries = Array.from(params.entries());
122
119
  const redactedEntries = [];
123
120
  const seenSensitiveKeys = /* @__PURE__ */ new Set();
124
121
  let mutated = false;
125
- for (const [key, entryValue] of entries) {
126
- if (isSensitiveUrlQueryParamName$1(key)) {
122
+ for (const [key, entryValue] of params) {
123
+ if (isSensitiveUrlQueryParamName(key)) {
127
124
  mutated = true;
128
125
  if (!seenSensitiveKeys.has(key)) {
129
126
  seenSensitiveKeys.add(key);
@@ -140,7 +137,7 @@ function redactQueryString(value, depth) {
140
137
  return new URLSearchParams(redactedEntries).toString();
141
138
  }
142
139
  function redactUrlLikeFallback(value) {
143
- return redactSensitiveUrlPath(redactEmbeddedUrlUserInfo(value).replace(/([?&])([^=&]+)=([^&]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName$1(key) ? `${prefix}${key}=***` : match));
140
+ return redactSensitiveUrlPath(redactEmbeddedUrlUserInfo(value).replace(/([?&])([^=&]+)=([^&]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=***` : match));
144
141
  }
145
142
  function redactAuthorityUserInfo(candidate, authorityStart) {
146
143
  const authority = candidate.slice(authorityStart);
@@ -312,10 +309,6 @@ function redactSensitiveUrlLikeString(value) {
312
309
  return redactSensitiveUrlLikeStringAtDepth(value, 0).value;
313
310
  }
314
311
  ipaddr.IPv4.parse("198.18.0.0");
315
- function stripIpv6Brackets(value) {
316
- if (value.startsWith("[") && value.endsWith("]")) return value.slice(1, -1);
317
- return value;
318
- }
319
312
  /** Type guard for parsed IPv6 addresses. */
320
313
  function isIpv6Address(address) {
321
314
  return address.kind() === "ipv6";
@@ -326,7 +319,7 @@ function normalizeIpv4MappedAddress(address) {
326
319
  function normalizeIpParseInput(raw) {
327
320
  const trimmed = normalizeOptionalString(raw);
328
321
  if (!trimmed) return;
329
- return stripIpv6Brackets(trimmed);
322
+ return trimmed.startsWith("[") && trimmed.endsWith("]") ? trimmed.slice(1, -1) : trimmed;
330
323
  }
331
324
  /** Parses canonical IPv4/IPv6 literals, rejecting legacy IPv4 shorthand forms. */
332
325
  function parseCanonicalIpAddress(raw) {
@@ -349,18 +342,12 @@ function isLoopbackIpAddress(raw) {
349
342
  }
350
343
  //#endregion
351
344
  //#region packages/gateway-client/src/client-address-utils.ts
352
- function normalizeGatewayErrorText(value) {
353
- return typeof value === "string" ? value.trim().toLowerCase() : "";
354
- }
355
- function isSensitiveUrlQueryParamName(key) {
356
- return /(?:token|password|secret|key|auth|credential)/iu.test(key);
357
- }
358
345
  function isGatewayClientStoppedError(err) {
359
346
  const message = err instanceof Error ? err.message : String(err);
360
347
  return message === "gateway client stopped" || message === "Error: gateway client stopped";
361
348
  }
362
349
  function formatGatewayClientErrorForLog(err) {
363
- return String(err).replace(/\/\/([^@/?#\s]+)@/g, "//***:***@").replace(/(Authorization:\s*Bearer\s+)[^\s]+/giu, "$1***").replace(/([?&])([^=&\s]+)=([^&#\s"'<>)]*)/g, (match, prefix, key) => isSensitiveUrlQueryParamName(key) ? `${prefix}${key}=***` : match);
350
+ return String(err).replace(/\/\/([^@/?#\s]+)@/g, "//***:***@").replace(/(Authorization:\s*Bearer\s+)[^\s]+/giu, "$1***").replace(/([?&])([^=&\s]+)=([^&#\s"'<>)]*)/g, (match, prefix, key) => /(?:token|password|secret|key|auth|credential)/iu.test(key) ? `${prefix}${key}=***` : match);
364
351
  }
365
352
  const SHA256_HEX_FINGERPRINT = /^[a-fA-F0-9]{64}$/u;
366
353
  const SHA256_COLON_FINGERPRINT = /^(?:[a-fA-F0-9]{2}:){31}[a-fA-F0-9]{2}$/u;
@@ -523,12 +510,7 @@ function isGatewayConnectAssemblyError(value) {
523
510
  //#endregion
524
511
  //#region packages/net-policy/src/url-protocol.ts
525
512
  function parseUrl(value) {
526
- if (value instanceof URL) return value;
527
- try {
528
- return new URL(value);
529
- } catch {
530
- return null;
531
- }
513
+ return value instanceof URL ? value : URL.parse(value);
532
514
  }
533
515
  function isWssUrl(value) {
534
516
  return parseUrl(value)?.protocol === "wss:";
@@ -567,17 +549,14 @@ function isTrustedPlaintextWebSocketHost(hostname) {
567
549
  return normalized.endsWith(".local") || normalized.endsWith(".ts.net");
568
550
  }
569
551
  function isSecureWebSocketUrl(rawUrl, options) {
570
- try {
571
- const url = new URL(rawUrl);
572
- const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
573
- if (protocol === "wss:") return true;
574
- if (protocol !== "ws:") return false;
575
- if (isTrustedPlaintextWebSocketHost(url.hostname)) return true;
576
- if (options?.allowPrivateWs === true) return parseGatewayIpAddress(url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname) === void 0;
577
- return false;
578
- } catch {
579
- return false;
580
- }
552
+ const url = URL.parse(rawUrl);
553
+ if (!url) return false;
554
+ const protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
555
+ if (protocol === "wss:") return true;
556
+ if (protocol !== "ws:") return false;
557
+ if (isTrustedPlaintextWebSocketHost(url.hostname)) return true;
558
+ if (options?.allowPrivateWs === true) return parseGatewayIpAddress(url.hostname.startsWith("[") && url.hostname.endsWith("]") ? url.hostname.slice(1, -1) : url.hostname) === void 0;
559
+ return false;
581
560
  }
582
561
  var GatewayWebSocketTransportConfigurationError = class extends Error {};
583
562
  var GatewayWebSocketTlsPinError = class extends Error {};
@@ -585,13 +564,7 @@ function resolveGatewayWebSocketTransport(params) {
585
564
  const usesTls = isWssUrl(params.url);
586
565
  if (params.tlsFingerprint && !usesTls) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint requires wss:// gateway url");
587
566
  const allowPrivateWs = (params.env ?? process.env).OPENCLAW_ALLOW_INSECURE_PRIVATE_WS === "1";
588
- if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) {
589
- let displayHost = params.url;
590
- try {
591
- displayHost = new URL(params.url).hostname || params.url;
592
- } catch {}
593
- 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.");
594
- }
567
+ if (!isSecureWebSocketUrl(params.url, { allowPrivateWs })) throw new GatewayWebSocketTransportConfigurationError(`SECURITY ERROR: Cannot connect to "${URL.parse(params.url)?.hostname || params.url}" 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.");
595
568
  const normalize = params.normalizeTlsFingerprint ?? normalizeTlsFingerprint;
596
569
  const expectedFingerprint = params.tlsFingerprint ? normalizeTlsFingerprint(params.tlsFingerprint) : void 0;
597
570
  if (params.tlsFingerprint && !expectedFingerprint) throw new GatewayWebSocketTransportConfigurationError("gateway tls fingerprint must be a SHA-256 fingerprint");
@@ -719,7 +692,7 @@ var GatewayClient = class {
719
692
  timeoutMs,
720
693
  requestSent
721
694
  }),
722
- createRequestAbortError: createGatewayRequestAbortError,
695
+ createRequestAbortError: (method) => createAbortError(`gateway request aborted for ${method}`),
723
696
  buildConnectPlan: ({ nonce, challengeTs, serverCapabilities, generation, ...authority }) => {
724
697
  if (!nonce) throw new Error("gateway connect challenge missing nonce");
725
698
  if (this.opts.deviceIdentity && challengeTs == null) throw new Error("gateway connect challenge timestamp invalid");
@@ -954,7 +927,15 @@ var GatewayClient = class {
954
927
  const ws = this.ws;
955
928
  this.ws = null;
956
929
  if (ws) {
957
- const pendingStop = this.createPendingStop(ws);
930
+ let resolve = () => {};
931
+ const pendingStop = {
932
+ ws,
933
+ promise: new Promise((done) => {
934
+ resolve = done;
935
+ }),
936
+ resolve
937
+ };
938
+ this.pendingStop = pendingStop;
958
939
  const forceTerminateTimer = setTimeout(() => {
959
940
  try {
960
941
  ws.terminate();
@@ -975,18 +956,6 @@ var GatewayClient = class {
975
956
  this.protocol.stop();
976
957
  return null;
977
958
  }
978
- createPendingStop(ws) {
979
- let resolve = () => {};
980
- const promise = new Promise((done) => {
981
- resolve = done;
982
- });
983
- this.pendingStop = {
984
- ws,
985
- promise,
986
- resolve
987
- };
988
- return this.pendingStop;
989
- }
990
959
  resolvePendingStop(ws) {
991
960
  if (this.pendingStop?.ws !== ws) return;
992
961
  const { resolve, terminateTimer } = this.pendingStop;
@@ -1076,7 +1045,7 @@ var GatewayClient = class {
1076
1045
  shouldRetryWithAlternateNodeProtocol(error) {
1077
1046
  if (!this.shouldNegotiateLegacyNodeProtocol() || !(error instanceof GatewayClientRequestError)) return false;
1078
1047
  const detailCode = readConnectErrorDetailCode(error.details);
1079
- return error.details?.expectedProtocol === (this.useLegacyNodeProtocolEnvelope ? PROTOCOL_VERSION : MIN_NODE_PROTOCOL_VERSION) && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeGatewayErrorText(error.message).includes("protocol mismatch"));
1048
+ return error.details?.expectedProtocol === (this.useLegacyNodeProtocolEnvelope ? PROTOCOL_VERSION : MIN_NODE_PROTOCOL_VERSION) && (detailCode === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || normalizeLowercaseStringOrEmpty(error.message).includes("protocol mismatch"));
1080
1049
  }
1081
1050
  buildDeviceConnectParams(params) {
1082
1051
  if (!this.opts.deviceIdentity) return;
@@ -1292,7 +1261,7 @@ var GatewayClient = class {
1292
1261
  }
1293
1262
  clearStaleDeviceTokenForClose(context) {
1294
1263
  const { code, reason, generation } = context;
1295
- if (code !== 1008 || !normalizeGatewayErrorText(reason).includes("device token mismatch") || !this.opts.deviceIdentity) return;
1264
+ if (code !== 1008 || !normalizeLowercaseStringOrEmpty(reason).includes("device token mismatch") || !this.opts.deviceIdentity) return;
1296
1265
  const observation = this.connectionStoredToken?.generation === generation ? this.connectionStoredToken : { token: void 0 };
1297
1266
  this.clearDeviceToken(this.opts.deviceIdentity.deviceId, observation, () => !this.opts.token && !this.opts.password);
1298
1267
  }
@@ -1334,7 +1303,7 @@ var GatewayClient = class {
1334
1303
  }
1335
1304
  isUnsupportedConnectAuthField(error, field) {
1336
1305
  if (!(error instanceof GatewayClientRequestError) || error.gatewayCode !== "INVALID_REQUEST") return false;
1337
- const message = normalizeGatewayErrorText(error.message);
1306
+ const message = normalizeLowercaseStringOrEmpty(error.message);
1338
1307
  return message.includes("invalid connect params") && message.includes(field);
1339
1308
  }
1340
1309
  isTrustedDeviceRetryEndpoint() {
@@ -1389,11 +1358,6 @@ var GatewayClient = class {
1389
1358
  });
1390
1359
  }
1391
1360
  };
1392
- function createGatewayRequestAbortError(method) {
1393
- const err = /* @__PURE__ */ new Error(`gateway request aborted for ${method}`);
1394
- err.name = "AbortError";
1395
- return err;
1396
- }
1397
1361
  //#endregion
1398
1362
  //#region packages/gateway-client/src/run-recovery-text.ts
1399
1363
  async function recoverTerminalReply(params) {
@@ -65,6 +65,7 @@ type GatewayProtocolConnectDecision = {
65
65
  closeReason: string;
66
66
  reconnectDelayMs?: number;
67
67
  stop?: boolean;
68
+ keepOpen?: boolean;
68
69
  error?: Error;
69
70
  };
70
71
  type GatewayProtocolCloseDecision = {
@@ -1,5 +1,5 @@
1
1
  import { n as GatewayProtocolRequestOptions, r as GatewayProtocolRequestTimeoutError, t as GatewayProtocolRequestError } from "./protocol-request-CtVhe3Bj.mjs";
2
- import { l as GatewayProtocolRequestTiming, n as GatewayClientCloseInfo, r as GatewayClientConnectionMetadata } from "./protocol-client-DT451Msj.mjs";
2
+ import { l as GatewayProtocolRequestTiming, n as GatewayClientCloseInfo, r as GatewayClientConnectionMetadata } from "./protocol-client-CKMLpN-S.mjs";
3
3
  import { ConnectParams, ErrorShape, EventFrame, HelloOk } from "@openclaw/gateway-protocol/frame-guards";
4
4
  import { GatewayClientMode, GatewayClientName } from "@openclaw/gateway-protocol/client-info";
5
5
  import "ws";
@@ -171,7 +171,6 @@ declare class GatewayClient {
171
171
  timeoutMs?: number;
172
172
  }): Promise<void>;
173
173
  private beginStop;
174
- private createPendingStop;
175
174
  private resolvePendingStop;
176
175
  private logDebug;
177
176
  private logError;
@@ -1,4 +1,4 @@
1
- import { resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs } from "./timeouts.mjs";
1
+ import { d as resolveFiniteTimeoutDelayMs, u as resolveConnectChallengeTimeoutMs } from "./timeouts-CQTCye5U.mjs";
2
2
  //#region packages/gateway-client/src/event-loop-ready.ts
3
3
  const DEFAULT_MAX_WAIT_MS = 1e4;
4
4
  const DEFAULT_INTERVAL_MS = 1;
@@ -1,2 +1,2 @@
1
- import { a as startGatewayClientWithReadinessWait, i as startGatewayClientWhenEventLoopReady, n as GatewayClientStartReadinessOptions, r as GatewayClientStartable, t as EventLoopReadyWaiter } from "./readiness-CLaFwfd4.mjs";
1
+ import { a as startGatewayClientWithReadinessWait, i as startGatewayClientWhenEventLoopReady, n as GatewayClientStartReadinessOptions, r as GatewayClientStartable, t as EventLoopReadyWaiter } from "./readiness-BbuB7IzG.mjs";
2
2
  export { EventLoopReadyWaiter, GatewayClientStartReadinessOptions, GatewayClientStartable, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };
@@ -1,3 +1,2 @@
1
- import "./timeouts.mjs";
2
- import { n as startGatewayClientWithReadinessWait, t as startGatewayClientWhenEventLoopReady } from "./readiness-DnB0NLBq.mjs";
1
+ import { n as startGatewayClientWithReadinessWait, t as startGatewayClientWhenEventLoopReady } from "./readiness-rG6_5Ynk.mjs";
3
2
  export { startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };
@@ -1,7 +1,7 @@
1
- import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, clearGatewayConnectTimeout, resolveSafeTimeoutDelayMs, startGatewayConnectTimeout } from "./timeouts.mjs";
1
+ import { _ as normalizeOptionalString, c as clearGatewayConnectTimeout, g as normalizeNullableString, m as startGatewayConnectTimeout, p as resolveSafeTimeoutDelayMs, t as DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS } from "./timeouts-CQTCye5U.mjs";
2
2
  import { ConnectErrorDetailCodes, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readPairingConnectErrorDetails } from "@openclaw/gateway-protocol/connect-error-details";
3
3
  import { isGatewayEventFrame, isGatewayResponseFrame } from "@openclaw/gateway-protocol/frame-guards";
4
- import { GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS, isGatewaySuspendUnavailableError } from "@openclaw/gateway-protocol/restart-unavailable";
4
+ import { GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS, isGatewayRestartUnavailableError, isGatewaySuspendUnavailableError } from "@openclaw/gateway-protocol/restart-unavailable";
5
5
  import { GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT } from "@openclaw/gateway-protocol/gateway-error-details";
6
6
  //#region packages/gateway-client/src/device-auth.ts
7
7
  function normalizeDeviceMetadataForAuth(value) {
@@ -100,26 +100,6 @@ function mergeChatStreamMessage(previous, payload) {
100
100
  };
101
101
  }
102
102
  //#endregion
103
- //#region packages/normalization-core/src/string-coerce.ts
104
- /** Trims string input and returns null for non-strings or empty strings. */
105
- function normalizeNullableString(value) {
106
- if (typeof value !== "string") return null;
107
- const trimmed = value.trim();
108
- return trimmed ? trimmed : null;
109
- }
110
- /** Trims string input and returns undefined for non-strings or empty strings. */
111
- function normalizeOptionalString(value) {
112
- return normalizeNullableString(value) ?? void 0;
113
- }
114
- /** Lowercases a normalized optional string. */
115
- function normalizeOptionalLowercaseString(value) {
116
- return normalizeOptionalString(value)?.toLowerCase();
117
- }
118
- /** Lowercases a normalized string or returns an empty string when absent. */
119
- function normalizeLowercaseStringOrEmpty(value) {
120
- return normalizeOptionalLowercaseString(value) ?? "";
121
- }
122
- //#endregion
123
103
  //#region packages/gateway-client/src/connect-auth.ts
124
104
  function selectGatewayConnectAuth(params) {
125
105
  const authToken = normalizeOptionalString(params.token);
@@ -267,15 +247,12 @@ var GatewayBrowserDeviceAuthLifecycle = class {
267
247
  function normalizeGatewayScope(gatewayUrl, includeSearch) {
268
248
  const trimmed = gatewayUrl.trim();
269
249
  if (!trimmed) return "default";
270
- try {
271
- const browserLocation = globalThis.location;
272
- const base = browserLocation ? `${browserLocation.protocol}//${browserLocation.host}${browserLocation.pathname || "/"}` : void 0;
273
- const parsed = base ? new URL(trimmed, base) : new URL(trimmed);
274
- const pathname = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
275
- return `${parsed.protocol}//${parsed.host}${pathname}${includeSearch ? parsed.search : ""}`;
276
- } catch {
277
- return trimmed;
278
- }
250
+ const browserLocation = globalThis.location;
251
+ const base = browserLocation ? `${browserLocation.protocol}//${browserLocation.host}${browserLocation.pathname || "/"}` : void 0;
252
+ const parsed = URL.parse(trimmed, base);
253
+ if (!parsed) return trimmed;
254
+ const pathname = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
255
+ return `${parsed.protocol}//${parsed.host}${pathname}${includeSearch ? parsed.search : ""}`;
279
256
  }
280
257
  /** Normalizes the gateway URL scope used for origin-bound device tokens. */
281
258
  function gatewayOriginScope(gatewayUrl) {
@@ -596,17 +573,35 @@ var GatewayPendingRequests = class {
596
573
  this.requestSequence = 0;
597
574
  }
598
575
  setSuspensionPhase(phase) {
599
- if (phase === "accepting") this.resumeIdentityRequests();
600
- else if (phase === "preparing" || phase === "draining" || phase === "prepared") this.pauseIdentityRequests();
601
- }
602
- pauseIdentityRequests(retryAfterMs = GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS) {
603
- clearTimeout(this.suspensionTimer);
604
- this.suspensionTimer = setTimeout(() => this.resumeIdentityRequests(), resolveSafeTimeoutDelayMs(Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS));
605
- this.suspensionTimer.unref?.();
606
- }
607
- resumeIdentityRequests() {
608
- clearTimeout(this.suspensionTimer);
609
- this.suspensionTimer = void 0;
576
+ if (phase === "accepting") this.resumeBootstrapRequests();
577
+ else if (phase === "preparing" || phase === "draining" || phase === "prepared") this.pauseBootstrapRequests(void 0, true);
578
+ }
579
+ pauseBootstrapRequests(retryAfterMs = GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS, announced = false) {
580
+ const nowMs = this.opts.nowMs();
581
+ const delayMs = resolveSafeTimeoutDelayMs(Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS);
582
+ const pause = this.bootstrapPause ??= {
583
+ retryAtMs: nowMs,
584
+ revision: 0,
585
+ announced
586
+ };
587
+ pause.announced ||= announced;
588
+ pause.revision += 1;
589
+ pause.retryAtMs = Math.max(pause.retryAtMs, nowMs + delayMs);
590
+ clearTimeout(pause.timer);
591
+ pause.timer = void 0;
592
+ if (pause.announced) return;
593
+ pause.timer = setTimeout(() => {
594
+ pause.timer = void 0;
595
+ this.drainBootstrapRequests();
596
+ }, resolveSafeTimeoutDelayMs(pause.retryAtMs - nowMs));
597
+ pause.timer.unref?.();
598
+ }
599
+ resumeBootstrapRequests() {
600
+ clearTimeout(this.bootstrapPause?.timer);
601
+ this.bootstrapPause = void 0;
602
+ this.drainBootstrapRequests();
603
+ }
604
+ drainBootstrapRequests() {
610
605
  for (const pending of this.pending.values()) if (pending.waitingForResume) pending.resend?.();
611
606
  }
612
607
  get hasPending() {
@@ -641,6 +636,10 @@ var GatewayPendingRequests = class {
641
636
  const cleanup = () => {
642
637
  if (timeout !== void 0) clearTimeout(timeout);
643
638
  options?.signal?.removeEventListener("abort", onAbort);
639
+ if (this.bootstrapPause?.probe?.pending === pending) {
640
+ this.bootstrapPause.probe = void 0;
641
+ this.drainBootstrapRequests();
642
+ }
644
643
  };
645
644
  const retire = (errorCode) => {
646
645
  if (this.pending.get(id) !== pending) return false;
@@ -679,8 +678,13 @@ var GatewayPendingRequests = class {
679
678
  params
680
679
  });
681
680
  const send = () => {
682
- if (this.pending.get(id) !== pending) return;
683
- pending.waitingForResume = Boolean(pending.resend && this.suspensionTimer);
681
+ if (this.pending.get(id) !== pending || options?.signal?.aborted || pending.waitingForResume && timeoutMs !== void 0 && this.opts.nowMs() - pending.startedAtMs >= timeoutMs) return;
682
+ const pause = pending.resend ? this.bootstrapPause : void 0;
683
+ if (pause && !pause.announced && !pause.timer && !pause.probe) pause.probe = {
684
+ pending,
685
+ revision: pause.revision
686
+ };
687
+ pending.waitingForResume = Boolean(pause && (pause.announced || pause.probe?.pending !== pending));
684
688
  if (pending.waitingForResume) return;
685
689
  try {
686
690
  sender.send(frame);
@@ -691,7 +695,7 @@ var GatewayPendingRequests = class {
691
695
  if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
692
696
  }
693
697
  };
694
- if (method === "agent.identity.get") pending.resend = send;
698
+ if (method === "agent.identity.get" || method === "sessions.subscribe" || method === "sessions.groups.list" || method === "question.list" || method === "sessions.list") pending.resend = send;
695
699
  send();
696
700
  } catch (error) {
697
701
  if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
@@ -709,12 +713,14 @@ var GatewayPendingRequests = class {
709
713
  }
710
714
  return;
711
715
  }
712
- if (!frame.ok && pending.resend && !pending.acceptedNotified && frame.error?.code === "UNAVAILABLE" && frame.error.retryable === true && isGatewaySuspendUnavailableError(frame.error)) {
713
- this.pauseIdentityRequests(frame.error.retryAfterMs);
716
+ if (!frame.ok && pending.resend && !pending.acceptedNotified && frame.error?.code === "UNAVAILABLE" && frame.error.retryable === true && (isGatewaySuspendUnavailableError(frame.error) || isGatewayRestartUnavailableError(frame.error))) {
717
+ if (this.bootstrapPause?.probe?.pending === pending) this.bootstrapPause.probe = void 0;
718
+ this.pauseBootstrapRequests(isGatewayRestartUnavailableError(frame.error) ? Math.ceil(Math.max(GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS, frame.error.retryAfterMs ?? 0) * (1 + Math.random() * .2)) : frame.error.retryAfterMs);
714
719
  pending.waitingForResume = true;
715
720
  return;
716
721
  }
717
722
  this.pending.delete(frame.id);
723
+ if (frame.ok && this.bootstrapPause?.probe?.pending === pending && this.bootstrapPause.probe.revision === this.bootstrapPause.revision) this.resumeBootstrapRequests();
718
724
  pending.cleanup?.();
719
725
  if (frame.ok) {
720
726
  this.finishTiming(frame.id, pending, true);
@@ -727,8 +733,8 @@ var GatewayPendingRequests = class {
727
733
  pending.reject(error);
728
734
  }
729
735
  flush(error) {
730
- clearTimeout(this.suspensionTimer);
731
- this.suspensionTimer = void 0;
736
+ clearTimeout(this.bootstrapPause?.timer);
737
+ this.bootstrapPause = void 0;
732
738
  const retired = this.pending;
733
739
  this.pending = /* @__PURE__ */ new Map();
734
740
  this.requestSequence = 0;
@@ -1023,6 +1029,10 @@ var GatewayProtocolClient = class {
1023
1029
  error: requestError,
1024
1030
  reconnectDelayMs: decision.reconnectDelayMs
1025
1031
  };
1032
+ if (decision.keepOpen) {
1033
+ this.clearHandshakeTimer();
1034
+ return;
1035
+ }
1026
1036
  if (decision.stop) this.stopped = true;
1027
1037
  this.connectionAbort?.abort();
1028
1038
  socket.close(decision.closeCode, decision.closeReason);
@@ -1065,8 +1075,8 @@ var GatewayProtocolClient = class {
1065
1075
  this.sendConnect(socket, generation);
1066
1076
  return;
1067
1077
  }
1068
- const seq = typeof parsed.seq === "number" ? parsed.seq : null;
1069
- if (seq !== null) {
1078
+ const seq = parsed.seq;
1079
+ if (seq !== void 0) {
1070
1080
  if (this.lastSeq !== null && seq > this.lastSeq + 1) {
1071
1081
  const expected = this.lastSeq + 1;
1072
1082
  const state = asRecord(parsed.payload).state;
@@ -1091,6 +1101,7 @@ var GatewayProtocolClient = class {
1091
1101
  dispatchEvent(socket, generation, event) {
1092
1102
  const listeners = this.listeners.snapshot();
1093
1103
  if (event.event === "gateway.suspension" && typeof event.payload === "object" && event.payload !== null && "phase" in event.payload) this.requests.setSuspensionPhase(event.payload.phase);
1104
+ else if (event.event === "shutdown" && typeof asRecord(event.payload).restartExpectedMs === "number") this.requests.setSuspensionPhase("draining");
1094
1105
  this.invoke("event", () => this.opts.onEvent?.(event));
1095
1106
  for (const [listener, subscription] of listeners) {
1096
1107
  if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
@@ -1214,6 +1225,8 @@ const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
1214
1225
  ConnectErrorDetailCodes.AUTH_VERIFIED_USER_REQUIRED,
1215
1226
  ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
1216
1227
  ConnectErrorDetailCodes.PAIRING_REQUIRED,
1228
+ ConnectErrorDetailCodes.PAIRING_REJECTED,
1229
+ ConnectErrorDetailCodes.PAIRING_EXPIRED,
1217
1230
  ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
1218
1231
  ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED
1219
1232
  ]);
@@ -1309,10 +1322,59 @@ function compareStableStrings(left, right) {
1309
1322
  return left < right ? -1 : left > right ? 1 : 0;
1310
1323
  }
1311
1324
  //#endregion
1312
- //#region packages/gateway-client/src/session-projection-message-identity.ts
1313
- function readSessionProjectionString(value) {
1314
- return normalizeNullableString(value);
1325
+ //#region packages/gateway-client/src/session-projection-message-content.ts
1326
+ function readSessionMessageDisplayContent(message) {
1327
+ const record = asNullableRecord(message);
1328
+ const content = typeof message === "string" ? message : record?.content;
1329
+ const media = asNullableRecord(record?.["__openclaw"])?.media;
1330
+ let hasNonText = Array.isArray(media) && media.length > 0;
1331
+ const texts = [];
1332
+ for (const block of Array.isArray(content) ? content : [content]) {
1333
+ const entry = asNullableRecord(block);
1334
+ if (entry && entry.type !== "text" && entry.type !== "input_text" && entry.type !== "output_text") {
1335
+ hasNonText ||= entry.type !== "thinking" && entry.type !== "reasoning" && entry.type !== "redacted_thinking";
1336
+ continue;
1337
+ }
1338
+ const text = normalizeNullableString(entry ? entry.text : block);
1339
+ if (text) texts.push(text);
1340
+ }
1341
+ const fallback = texts.length === 0 ? normalizeNullableString(record?.text) : null;
1342
+ return {
1343
+ text: fallback ?? texts.join("\n"),
1344
+ hasNonText,
1345
+ usesFallbackText: fallback !== null
1346
+ };
1315
1347
  }
1348
+ /** Status notices remain visible but do not define the terminal reply they accompany. */
1349
+ function projectSessionTerminalReplyMessage(message) {
1350
+ const record = asNullableRecord(message);
1351
+ if (!record || !Array.isArray(record.content)) return message;
1352
+ const content = record.content.filter((block) => asNullableRecord(block)?.openclawStatusNotice !== true);
1353
+ if (content.length === record.content.length || content.length === 0) return message;
1354
+ return {
1355
+ ...record,
1356
+ content,
1357
+ text: void 0
1358
+ };
1359
+ }
1360
+ /** Check whether a projected message has text or another displayable block. */
1361
+ function hasDisplayableSessionMessage(message) {
1362
+ const { text, hasNonText } = readSessionMessageDisplayContent(message);
1363
+ return Boolean(text) || hasNonText;
1364
+ }
1365
+ function normalizeChatErrorComparisonText(text) {
1366
+ return text.trim().replace(/^⚠️\s*/u, "").replace(/^Error:\s*/iu, "").replace(/\s+/gu, " ").trim();
1367
+ }
1368
+ /** Diagnostic-only error projections can be retired when their run resumes. */
1369
+ function isSessionProjectionErrorMessage(message, errorMessage) {
1370
+ const role = asNullableRecord(message)?.role;
1371
+ if (typeof role === "string" && role.trim().toLowerCase() !== "assistant") return false;
1372
+ const { text, hasNonText } = readSessionMessageDisplayContent(message);
1373
+ const normalizedText = normalizeChatErrorComparisonText(text);
1374
+ return !hasNonText && (!normalizedText || normalizedText === "[assistant turn failed before producing content]" || normalizedText === GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT || normalizedText === normalizeChatErrorComparisonText(errorMessage ?? ""));
1375
+ }
1376
+ //#endregion
1377
+ //#region packages/gateway-client/src/session-projection-message-identity.ts
1316
1378
  function readPositiveSafeInteger(value) {
1317
1379
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1318
1380
  }
@@ -1322,33 +1384,33 @@ function readSessionMessageSequence(message, envelope) {
1322
1384
  }
1323
1385
  /** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
1324
1386
  function normalizeSessionProjectionRunId(value) {
1325
- const runId = readSessionProjectionString(value);
1387
+ const runId = normalizeNullableString(value);
1326
1388
  return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
1327
1389
  }
1328
1390
  /** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
1329
1391
  function readSessionMessageIdentity(message, envelope) {
1330
1392
  const record = asNullableRecord(message);
1331
- const role = readSessionProjectionString(record?.role)?.toLowerCase();
1393
+ const role = normalizeNullableString(record?.role)?.toLowerCase();
1332
1394
  if (!record || !role) return null;
1333
1395
  const metadata = asNullableRecord(record["__openclaw"]);
1334
- const importedFrom = readSessionProjectionString(metadata?.importedFrom);
1335
- const cliSessionId = readSessionProjectionString(metadata?.cliSessionId);
1336
- const externalId = readSessionProjectionString(metadata?.externalId);
1396
+ const importedFrom = normalizeNullableString(metadata?.importedFrom);
1397
+ const cliSessionId = normalizeNullableString(metadata?.cliSessionId);
1398
+ const externalId = normalizeNullableString(metadata?.externalId);
1337
1399
  const position = asNullableRecord(metadata?.transcriptPosition);
1338
- const positionSource = readSessionProjectionString(position?.source);
1400
+ const positionSource = normalizeNullableString(position?.source);
1339
1401
  const isImported = !(positionSource !== null && positionSource.length <= 128 && typeof position?.rawSeq === "number" && Number.isSafeInteger(position.rawSeq) && position.rawSeq >= 0) && Boolean(importedFrom || cliSessionId || externalId);
1340
- const idempotencyKey = readSessionProjectionString(metadata?.idempotencyKey) ?? readSessionProjectionString(record.idempotencyKey) ?? readSessionProjectionString(envelope?.idempotencyKey) ?? readSessionProjectionString(envelope?.clientRunId);
1402
+ const idempotencyKey = normalizeNullableString(metadata?.idempotencyKey) ?? normalizeNullableString(record.idempotencyKey) ?? normalizeNullableString(envelope?.idempotencyKey) ?? normalizeNullableString(envelope?.clientRunId);
1341
1403
  const persistedRunId = normalizeSessionProjectionRunId(idempotencyKey);
1342
1404
  const envelopeRunId = normalizeSessionProjectionRunId(envelope?.runId);
1343
1405
  const metadataRunId = normalizeSessionProjectionRunId(metadata?.runId);
1344
1406
  const fallbackRunId = normalizeSessionProjectionRunId(asNullableRecord(record.openclawStreamFallback)?.runId);
1345
- const mirroredMessage = readSessionProjectionString(metadata?.mirrorOrigin) !== null;
1346
- const isCliAssistant = role === "assistant" && readSessionProjectionString(record.api)?.toLowerCase() === "cli";
1347
- const canonicalPersistedRunId = isCliAssistant && persistedRunId?.startsWith("cli-assistant:") ? readSessionProjectionString(persistedRunId.slice(14)) : persistedRunId;
1407
+ const mirroredMessage = normalizeNullableString(metadata?.mirrorOrigin) !== null;
1408
+ const isCliAssistant = role === "assistant" && normalizeNullableString(record.api)?.toLowerCase() === "cli";
1409
+ const canonicalPersistedRunId = isCliAssistant && persistedRunId?.startsWith("cli-assistant:") ? normalizeNullableString(persistedRunId.slice(14)) : persistedRunId;
1348
1410
  const runId = role === "assistant" ? metadataRunId ?? envelopeRunId ?? fallbackRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId;
1349
1411
  return {
1350
1412
  role,
1351
- id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
1413
+ id: normalizeNullableString(metadata?.id) ?? normalizeNullableString(envelope?.messageId),
1352
1414
  sequence: readSessionMessageSequence(message, envelope),
1353
1415
  idempotencyKey,
1354
1416
  sendId: role === "user" ? persistedRunId ?? runId : null,
@@ -1364,11 +1426,11 @@ function readSessionMessageIdentity(message, envelope) {
1364
1426
  /** A commentary item's display identity is separate from the transcript row that later owns it. */
1365
1427
  function readAssistantStreamSegmentIdentity(message) {
1366
1428
  const record = asNullableRecord(message);
1367
- if (readSessionProjectionString(record?.role)?.toLowerCase() !== "assistant") return;
1429
+ if (normalizeNullableString(record?.role)?.toLowerCase() !== "assistant") return;
1368
1430
  const fallback = asNullableRecord(record?.openclawStreamFallback);
1369
- const itemId = readSessionProjectionString(fallback?.itemId);
1431
+ const itemId = normalizeNullableString(fallback?.itemId);
1370
1432
  if (!itemId) return;
1371
- const runId = readSessionMessageIdentity(message)?.runId ?? readSessionProjectionString(record?.runId) ?? readSessionProjectionString(fallback?.runId);
1433
+ const runId = readSessionMessageIdentity(message)?.runId ?? normalizeNullableString(record?.runId) ?? normalizeNullableString(fallback?.runId);
1372
1434
  return {
1373
1435
  itemId,
1374
1436
  ...runId ? { runId } : {}
@@ -1381,7 +1443,7 @@ function sameAssistantPersistenceReceipt(left, right) {
1381
1443
  /** Local turns have no durable transcript metadata beyond their own optional send key. */
1382
1444
  function isLocallyOptimisticSessionMessage(message) {
1383
1445
  const record = asNullableRecord(message);
1384
- const role = readSessionProjectionString(record?.role)?.toLowerCase();
1446
+ const role = normalizeNullableString(record?.role)?.toLowerCase();
1385
1447
  if (role !== "user" && role !== "assistant") return false;
1386
1448
  if (asNullableRecord(record?.openclawStreamFallback)) return false;
1387
1449
  const metadata = asNullableRecord(record?.["__openclaw"]);
@@ -1414,58 +1476,6 @@ function createSessionProjectionEntry(message, options) {
1414
1476
  };
1415
1477
  }
1416
1478
  //#endregion
1417
- //#region packages/gateway-client/src/session-projection-message-content.ts
1418
- function readSessionMessageDisplayContent(message) {
1419
- const record = asNullableRecord(message);
1420
- const content = typeof message === "string" ? message : record?.content;
1421
- const media = asNullableRecord(record?.["__openclaw"])?.media;
1422
- let hasNonText = Array.isArray(media) && media.length > 0;
1423
- const texts = [];
1424
- for (const block of Array.isArray(content) ? content : [content]) {
1425
- const entry = asNullableRecord(block);
1426
- if (entry && entry.type !== "text" && entry.type !== "input_text" && entry.type !== "output_text") {
1427
- hasNonText ||= entry.type !== "thinking" && entry.type !== "reasoning" && entry.type !== "redacted_thinking";
1428
- continue;
1429
- }
1430
- const text = readSessionProjectionString(entry ? entry.text : block);
1431
- if (text) texts.push(text);
1432
- }
1433
- const fallback = texts.length === 0 ? readSessionProjectionString(record?.text) : null;
1434
- return {
1435
- text: fallback ?? texts.join("\n"),
1436
- hasNonText,
1437
- usesFallbackText: fallback !== null
1438
- };
1439
- }
1440
- /** Status notices remain visible but do not define the terminal reply they accompany. */
1441
- function projectSessionTerminalReplyMessage(message) {
1442
- const record = asNullableRecord(message);
1443
- if (!record || !Array.isArray(record.content)) return message;
1444
- const content = record.content.filter((block) => asNullableRecord(block)?.openclawStatusNotice !== true);
1445
- if (content.length === record.content.length || content.length === 0) return message;
1446
- return {
1447
- ...record,
1448
- content,
1449
- text: void 0
1450
- };
1451
- }
1452
- /** Check whether a projected message has text or another displayable block. */
1453
- function hasDisplayableSessionMessage(message) {
1454
- const { text, hasNonText } = readSessionMessageDisplayContent(message);
1455
- return Boolean(text) || hasNonText;
1456
- }
1457
- function normalizeChatErrorComparisonText(text) {
1458
- return text.trim().replace(/^⚠️\s*/u, "").replace(/^Error:\s*/iu, "").replace(/\s+/gu, " ").trim();
1459
- }
1460
- /** Diagnostic-only error projections can be retired when their run resumes. */
1461
- function isSessionProjectionErrorMessage(message, errorMessage) {
1462
- const role = asNullableRecord(message)?.role;
1463
- if (typeof role === "string" && role.trim().toLowerCase() !== "assistant") return false;
1464
- const { text, hasNonText } = readSessionMessageDisplayContent(message);
1465
- const normalizedText = normalizeChatErrorComparisonText(text);
1466
- return !hasNonText && (!normalizedText || normalizedText === "[assistant turn failed before producing content]" || normalizedText === GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT || normalizedText === normalizeChatErrorComparisonText(errorMessage ?? ""));
1467
- }
1468
- //#endregion
1469
1479
  //#region packages/gateway-client/src/session-projection-final-identity.ts
1470
1480
  /** Terminal identity rules used to reconcile live and durable assistant projections. */
1471
1481
  /** A run's selected text reply can inherit its terminal tool stop reason. */
@@ -1605,7 +1615,7 @@ function retainSessionProjectionRuns(runs) {
1605
1615
  //#region packages/gateway-client/src/session-projection-run-event.ts
1606
1616
  /** Normalizes Gateway run envelopes once for every browser and terminal adapter. */
1607
1617
  function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
1608
- const runId = readSessionProjectionString(event.runId);
1618
+ const runId = normalizeNullableString(event.runId);
1609
1619
  if (!runId || typeof event.state !== "string" || ![
1610
1620
  "delta",
1611
1621
  "final",
@@ -1613,9 +1623,9 @@ function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
1613
1623
  "aborted"
1614
1624
  ].includes(event.state)) return null;
1615
1625
  const message = event.state === "delta" ? mergeChatStreamMessage(projection.runs[runId]?.message, event) : event.message;
1616
- const messageStopReason = isRecord(message) ? readSessionProjectionString(message.stopReason) : null;
1617
- const stopReason = readSessionProjectionString(event.stopReason) ?? messageStopReason;
1618
- const errorKind = readSessionProjectionString(event.errorKind);
1626
+ const messageStopReason = isRecord(message) ? normalizeNullableString(message.stopReason) : null;
1627
+ const stopReason = normalizeNullableString(event.stopReason) ?? messageStopReason;
1628
+ const errorKind = normalizeNullableString(event.errorKind);
1619
1629
  const base = {
1620
1630
  runId,
1621
1631
  seq: typeof event.seq === "number" ? event.seq : void 0,
@@ -1694,7 +1704,7 @@ function entryMatches(left, right, allowSnapshotPromotion = false) {
1694
1704
  const durableSegment = durableEntry === left ? leftSegment : rightSegment;
1695
1705
  const provisionalSegment = durableEntry === left ? rightSegment : leftSegment;
1696
1706
  if (provisionalEntry.identity.sequence === null && durableSegment?.runId && durableSegment.runId === provisionalSegment?.runId && durableSegment.itemId === provisionalSegment.itemId) return true;
1697
- if (provisionalEntry.live && !durableSegment && !isSessionProjectionToolContinuation(durableEntry.message) && (!isToolUsePersistedFinalRow(durableEntry.message) || readFinalContentIdentity(durableEntry.message) === readFinalContentIdentity(provisionalEntry.message)) && provisionalEntry.identity.sequence === null && (provisionalEntry.afterSequence === void 0 || provisionalEntry.afterSequence !== null && durableEntry.identity.sequence !== null && durableEntry.identity.sequence > provisionalEntry.afterSequence) && durableEntry.identity.runId && durableEntry.identity.runId === provisionalEntry.identity.runId && (readSessionProjectionString(durableMetadata?.mirrorOrigin) === null || durableMetadata?.runTerminal === true)) return true;
1707
+ if (provisionalEntry.live && !durableSegment && !isSessionProjectionToolContinuation(durableEntry.message) && (!isToolUsePersistedFinalRow(durableEntry.message) || readFinalContentIdentity(durableEntry.message) === readFinalContentIdentity(provisionalEntry.message)) && provisionalEntry.identity.sequence === null && (provisionalEntry.afterSequence === void 0 || provisionalEntry.afterSequence !== null && durableEntry.identity.sequence !== null && durableEntry.identity.sequence > provisionalEntry.afterSequence) && durableEntry.identity.runId && durableEntry.identity.runId === provisionalEntry.identity.runId && (normalizeNullableString(durableMetadata?.mirrorOrigin) === null || durableMetadata?.runTerminal === true)) return true;
1698
1708
  }
1699
1709
  const persisted = left.identity;
1700
1710
  const observed = right.identity;
@@ -1838,7 +1848,7 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
1838
1848
  };
1839
1849
  }
1840
1850
  function updateRun(state, incoming) {
1841
- const incomingErrorMessage = readSessionProjectionString(incoming.errorMessage);
1851
+ const incomingErrorMessage = normalizeNullableString(incoming.errorMessage);
1842
1852
  const incomingSeq = typeof incoming.seq === "number" && Number.isSafeInteger(incoming.seq) && incoming.seq >= 0 ? incoming.seq : void 0;
1843
1853
  const normalizedIncoming = {
1844
1854
  ...incoming,
@@ -1855,7 +1865,7 @@ function updateRun(state, incoming) {
1855
1865
  const canAcceptFinal = currentHasDisplayableMessage ? current.status === incoming.status || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0 : canRecoverSessionProjectionFinal(current.message, incoming.message);
1856
1866
  const acceptFinal = incomingIsFinal && canAcceptFinal && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
1857
1867
  const recoverMessage = acceptFinal && !currentHasDisplayableMessage;
1858
- const recoverError = readSessionProjectionString(current.errorMessage) === null && incomingErrorMessage !== null;
1868
+ const recoverError = normalizeNullableString(current.errorMessage) === null && incomingErrorMessage !== null;
1859
1869
  const updateTerminalSequence = incoming.status !== "streaming" && (incomingSeq === void 0 ? current.seq !== void 0 : current.seq === void 0 || incomingSeq > current.seq);
1860
1870
  if (!acceptFinal && !recoverError && !updateTerminalSequence) return state;
1861
1871
  const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
@@ -2299,4 +2309,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
2299
2309
  return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
2300
2310
  }
2301
2311
  //#endregion
2302
- export { resolveGatewayConnectScopes as A, buildDeviceAuthPayloadV3 as B, isGatewayProtocolResponseError as C, gatewayOriginScope as D, gatewayCredentialScope as E, mergeChatStreamMessage as F, asNullableRecord as I, asRecord as L, shouldRetryGatewayWithDeviceToken as M, normalizeLowercaseStringOrEmpty as N, GatewayBrowserDeviceAuthLifecycle as O, normalizeOptionalString as P, isRecord as R, GatewayProtocolRequestTimeoutError as S, resolveModelCatalogConnect as T, normalizeDeviceMetadataForAuth as V, readSessionMessageIdentity as _, createSessionProjection as a, GatewayProtocolClient as b, reduceSessionProjection as c, readSessionProjectionFinalMessageIdentity as d, isSessionProjectionErrorMessage as f, readAssistantStreamSegmentIdentity as g, normalizeSessionProjectionRunId as h, resetGatewaySessionMessageSubscriptionCoordinator as i, selectGatewayConnectAuth as j, buildGatewayConnectAuth as k, reduceSessionProjectionRunEvent as l, isLocallyOptimisticSessionMessage as m, getGatewaySessionMessageSubscriptionCoordinator as n, projectLiveSessionMessage as o, projectSessionTerminalReplyMessage as p, releaseGatewaySessionMessageSubscription as r, reconcileSessionProjectionSnapshot as s, GatewaySessionMessageSubscriptionCoordinator as t, hasSessionProjectionAcceptedFinal as u, readSessionMessageSequence as v, raceWithTimeout as w, GatewayProtocolRequestError as x, shouldPauseGatewayReconnect as y, buildDeviceAuthPayload as z };
2312
+ export { buildGatewayConnectAuth as A, normalizeDeviceMetadataForAuth as B, isGatewayProtocolResponseError as C, gatewayCredentialScope as D, resolveModelCatalogConnect as E, asNullableRecord as F, asRecord as I, isRecord as L, selectGatewayConnectAuth as M, shouldRetryGatewayWithDeviceToken as N, gatewayOriginScope as O, mergeChatStreamMessage as P, buildDeviceAuthPayload as R, GatewayProtocolRequestTimeoutError as S, raceWithTimeout as T, isSessionProjectionErrorMessage as _, createSessionProjection as a, GatewayProtocolClient as b, reduceSessionProjection as c, readSessionProjectionFinalMessageIdentity as d, isLocallyOptimisticSessionMessage as f, readSessionMessageSequence as g, readSessionMessageIdentity as h, resetGatewaySessionMessageSubscriptionCoordinator as i, resolveGatewayConnectScopes as j, GatewayBrowserDeviceAuthLifecycle as k, reduceSessionProjectionRunEvent as l, readAssistantStreamSegmentIdentity as m, getGatewaySessionMessageSubscriptionCoordinator as n, projectLiveSessionMessage as o, normalizeSessionProjectionRunId as p, releaseGatewaySessionMessageSubscription as r, reconcileSessionProjectionSnapshot as s, GatewaySessionMessageSubscriptionCoordinator as t, hasSessionProjectionAcceptedFinal as u, projectSessionTerminalReplyMessage as v, createAbortError as w, GatewayProtocolRequestError as x, shouldPauseGatewayReconnect as y, buildDeviceAuthPayloadV3 as z };
@@ -0,0 +1,116 @@
1
+ //#region packages/normalization-core/src/string-coerce.ts
2
+ /** Trims string input and returns null for non-strings or empty strings. */
3
+ function normalizeNullableString(value) {
4
+ if (typeof value !== "string") return null;
5
+ const trimmed = value.trim();
6
+ return trimmed ? trimmed : null;
7
+ }
8
+ /** Trims string input and returns undefined for non-strings or empty strings. */
9
+ function normalizeOptionalString(value) {
10
+ return normalizeNullableString(value) ?? void 0;
11
+ }
12
+ /** Lowercases a normalized optional string. */
13
+ function normalizeOptionalLowercaseString(value) {
14
+ return normalizeOptionalString(value)?.toLowerCase();
15
+ }
16
+ /** Lowercases a normalized string or returns an empty string when absent. */
17
+ function normalizeLowercaseStringOrEmpty(value) {
18
+ return normalizeOptionalLowercaseString(value) ?? "";
19
+ }
20
+ //#endregion
21
+ //#region packages/normalization-core/src/number-coercion.ts
22
+ /** Parses only safe integer numbers or base-10 integer strings. */
23
+ function parseStrictInteger(value) {
24
+ if (typeof value === "number") return Number.isSafeInteger(value) ? value : void 0;
25
+ const normalized = normalizeOptionalString(value);
26
+ if (!normalized || !/^[+-]?\d+$/.test(normalized)) return;
27
+ const parsed = Number(normalized);
28
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
29
+ }
30
+ function parseStrictPositiveInteger(value) {
31
+ const parsed = parseStrictInteger(value);
32
+ return parsed !== void 0 && parsed > 0 ? parsed : void 0;
33
+ }
34
+ //#endregion
35
+ //#region packages/gateway-client/src/timeouts.ts
36
+ function isTestRuntimeEnv(env) {
37
+ return env.VITEST === "true" || env.VITEST === "1" || env.VITEST_POOL_ID !== void 0 || env.VITEST_WORKER_ID !== void 0 || env.NODE_ENV === "test" || env !== process.env && isTestRuntimeEnv(process.env);
38
+ }
39
+ /** Maximum delay Node timers can represent without overflow warnings. */
40
+ const MAX_SAFE_TIMEOUT_DELAY_MS = 2147483647;
41
+ /** Default server-side window for gateway preauth handshakes. */
42
+ const DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 15e3;
43
+ /** Starts the browser-safe deadline that covers Gateway connect preparation and hello. */
44
+ function startGatewayConnectTimeout(onTimeout) {
45
+ const timer = setTimeout(onTimeout, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS);
46
+ timer.unref?.();
47
+ return timer;
48
+ }
49
+ /** Clears either pending Gateway handshake phase without retaining its timer. */
50
+ function clearGatewayConnectTimeout(timer) {
51
+ if (timer !== null) clearTimeout(timer);
52
+ return null;
53
+ }
54
+ /** Default deadline for a single non-streaming Gateway request. */
55
+ const DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS = 3e4;
56
+ /** Minimum client watchdog delay for connect challenge setup. */
57
+ const MIN_CONNECT_CHALLENGE_TIMEOUT_MS = 250;
58
+ /** Default maximum client watchdog delay, aligned with the preauth server timeout. */
59
+ const MAX_CONNECT_CHALLENGE_TIMEOUT_MS = DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
60
+ /** Clamps arbitrary timer delays to Node's safe range and an optional floor. */
61
+ function resolveSafeTimeoutDelayMs(delayMs, opts) {
62
+ const rawMinMs = opts?.minMs ?? 1;
63
+ const minMs = Math.min(MAX_SAFE_TIMEOUT_DELAY_MS, Math.max(0, Number.isFinite(rawMinMs) ? Math.floor(rawMinMs) : 1));
64
+ return Math.min(MAX_SAFE_TIMEOUT_DELAY_MS, Math.max(minMs, Number.isFinite(delayMs) ? Math.floor(delayMs) : minMs));
65
+ }
66
+ /** Adds grace time while preserving safe timer bounds if inputs overflow or are invalid. */
67
+ function addSafeTimeoutDelayGraceMs(delayMs, graceMs, opts) {
68
+ if (!Number.isFinite(delayMs) || !Number.isFinite(graceMs)) return resolveSafeTimeoutDelayMs(MAX_SAFE_TIMEOUT_DELAY_MS, opts);
69
+ const withGrace = delayMs + graceMs;
70
+ return resolveSafeTimeoutDelayMs(Number.isFinite(withGrace) ? withGrace : MAX_SAFE_TIMEOUT_DELAY_MS, opts);
71
+ }
72
+ /** Resolves optional timeout values through a fallback and safe timer clamp. */
73
+ function resolveFiniteTimeoutDelayMs(delayMs, fallbackMs, opts) {
74
+ return resolveSafeTimeoutDelayMs(typeof delayMs === "number" && Number.isFinite(delayMs) ? delayMs : fallbackMs, opts);
75
+ }
76
+ /** Clamps connect challenge watchdog timeouts to the gateway-supported range. */
77
+ function clampConnectChallengeTimeoutMs(timeoutMs, maxTimeoutMs = MAX_CONNECT_CHALLENGE_TIMEOUT_MS) {
78
+ return Math.max(250, Math.min(Math.max(250, maxTimeoutMs), timeoutMs));
79
+ }
80
+ /** Reads the connect challenge watchdog override from the process environment. */
81
+ function getConnectChallengeTimeoutMsFromEnv(env = process.env) {
82
+ const raw = env.OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS;
83
+ if (raw) {
84
+ const parsed = parseStrictPositiveInteger(raw);
85
+ if (parsed !== void 0) return resolveSafeTimeoutDelayMs(parsed);
86
+ }
87
+ }
88
+ function normalizePositiveTimeoutMs(timeoutMs) {
89
+ return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? resolveSafeTimeoutDelayMs(timeoutMs) : void 0;
90
+ }
91
+ /** Resolves the client watchdog timeout using explicit, env, then preauth defaults. */
92
+ function resolveConnectChallengeTimeoutMs(timeoutMs, params) {
93
+ const configuredPreauthTimeoutMs = resolvePreauthHandshakeTimeoutMs({
94
+ env: params?.env,
95
+ configuredTimeoutMs: params?.configuredTimeoutMs
96
+ });
97
+ const maxTimeoutMs = Math.max(DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, configuredPreauthTimeoutMs);
98
+ if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) return clampConnectChallengeTimeoutMs(timeoutMs, maxTimeoutMs);
99
+ const envOverride = getConnectChallengeTimeoutMsFromEnv(params?.env);
100
+ if (envOverride !== void 0) return clampConnectChallengeTimeoutMs(envOverride, Math.max(maxTimeoutMs, envOverride));
101
+ return clampConnectChallengeTimeoutMs(configuredPreauthTimeoutMs, maxTimeoutMs);
102
+ }
103
+ /** Resolves the server preauth timeout from env, explicit config, or default. */
104
+ function resolvePreauthHandshakeTimeoutMs(params) {
105
+ const env = params?.env ?? process.env;
106
+ const configuredTimeout = env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || (isTestRuntimeEnv(env) ? env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS : void 0);
107
+ if (configuredTimeout) {
108
+ const parsed = parseStrictPositiveInteger(configuredTimeout);
109
+ if (parsed !== void 0) return resolveSafeTimeoutDelayMs(parsed);
110
+ }
111
+ const configured = normalizePositiveTimeoutMs(params?.configuredTimeoutMs);
112
+ if (configured !== void 0) return configured;
113
+ return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
114
+ }
115
+ //#endregion
116
+ export { normalizeOptionalString as _, MIN_CONNECT_CHALLENGE_TIMEOUT_MS as a, clearGatewayConnectTimeout as c, resolveFiniteTimeoutDelayMs as d, resolvePreauthHandshakeTimeoutMs as f, normalizeNullableString as g, normalizeLowercaseStringOrEmpty as h, MAX_SAFE_TIMEOUT_DELAY_MS as i, getConnectChallengeTimeoutMsFromEnv as l, startGatewayConnectTimeout as m, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS as n, addSafeTimeoutDelayGraceMs as o, resolveSafeTimeoutDelayMs as p, MAX_CONNECT_CHALLENGE_TIMEOUT_MS as r, clampConnectChallengeTimeoutMs as s, DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS as t, resolveConnectChallengeTimeoutMs as u };
package/dist/timeouts.mjs CHANGED
@@ -1,88 +1,2 @@
1
- //#region packages/gateway-client/src/timeouts.ts
2
- function parsePositiveTimeoutSetting(value) {
3
- const trimmed = value.trim();
4
- if (!/^\+?\d+$/u.test(trimmed)) return;
5
- const parsed = Number(trimmed);
6
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
7
- }
8
- function isTestRuntimeEnv(env) {
9
- return env.VITEST === "true" || env.VITEST === "1" || env.VITEST_POOL_ID !== void 0 || env.VITEST_WORKER_ID !== void 0 || env.NODE_ENV === "test" || env !== process.env && isTestRuntimeEnv(process.env);
10
- }
11
- /** Maximum delay Node timers can represent without overflow warnings. */
12
- const MAX_SAFE_TIMEOUT_DELAY_MS = 2147483647;
13
- /** Default server-side window for gateway preauth handshakes. */
14
- const DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS = 15e3;
15
- /** Starts the browser-safe deadline that covers Gateway connect preparation and hello. */
16
- function startGatewayConnectTimeout(onTimeout) {
17
- const timer = setTimeout(onTimeout, DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS);
18
- timer.unref?.();
19
- return timer;
20
- }
21
- /** Clears either pending Gateway handshake phase without retaining its timer. */
22
- function clearGatewayConnectTimeout(timer) {
23
- if (timer !== null) clearTimeout(timer);
24
- return null;
25
- }
26
- /** Default deadline for a single non-streaming Gateway request. */
27
- const DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS = 3e4;
28
- /** Minimum client watchdog delay for connect challenge setup. */
29
- const MIN_CONNECT_CHALLENGE_TIMEOUT_MS = 250;
30
- /** Default maximum client watchdog delay, aligned with the preauth server timeout. */
31
- const MAX_CONNECT_CHALLENGE_TIMEOUT_MS = DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
32
- /** Clamps arbitrary timer delays to Node's safe range and an optional floor. */
33
- function resolveSafeTimeoutDelayMs(delayMs, opts) {
34
- const rawMinMs = opts?.minMs ?? 1;
35
- const minMs = Math.min(MAX_SAFE_TIMEOUT_DELAY_MS, Math.max(0, Number.isFinite(rawMinMs) ? Math.floor(rawMinMs) : 1));
36
- return Math.min(MAX_SAFE_TIMEOUT_DELAY_MS, Math.max(minMs, Number.isFinite(delayMs) ? Math.floor(delayMs) : minMs));
37
- }
38
- /** Adds grace time while preserving safe timer bounds if inputs overflow or are invalid. */
39
- function addSafeTimeoutDelayGraceMs(delayMs, graceMs, opts) {
40
- if (!Number.isFinite(delayMs) || !Number.isFinite(graceMs)) return resolveSafeTimeoutDelayMs(MAX_SAFE_TIMEOUT_DELAY_MS, opts);
41
- const withGrace = delayMs + graceMs;
42
- return resolveSafeTimeoutDelayMs(Number.isFinite(withGrace) ? withGrace : MAX_SAFE_TIMEOUT_DELAY_MS, opts);
43
- }
44
- /** Resolves optional timeout values through a fallback and safe timer clamp. */
45
- function resolveFiniteTimeoutDelayMs(delayMs, fallbackMs, opts) {
46
- return resolveSafeTimeoutDelayMs(typeof delayMs === "number" && Number.isFinite(delayMs) ? delayMs : fallbackMs, opts);
47
- }
48
- /** Clamps connect challenge watchdog timeouts to the gateway-supported range. */
49
- function clampConnectChallengeTimeoutMs(timeoutMs, maxTimeoutMs = MAX_CONNECT_CHALLENGE_TIMEOUT_MS) {
50
- return Math.max(250, Math.min(Math.max(250, maxTimeoutMs), timeoutMs));
51
- }
52
- /** Reads the connect challenge watchdog override from the process environment. */
53
- function getConnectChallengeTimeoutMsFromEnv(env = process.env) {
54
- const raw = env.OPENCLAW_CONNECT_CHALLENGE_TIMEOUT_MS;
55
- if (raw) {
56
- const parsed = parsePositiveTimeoutSetting(raw);
57
- if (parsed !== void 0) return resolveSafeTimeoutDelayMs(parsed);
58
- }
59
- }
60
- function normalizePositiveTimeoutMs(timeoutMs) {
61
- return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? resolveSafeTimeoutDelayMs(timeoutMs) : void 0;
62
- }
63
- /** Resolves the client watchdog timeout using explicit, env, then preauth defaults. */
64
- function resolveConnectChallengeTimeoutMs(timeoutMs, params) {
65
- const configuredPreauthTimeoutMs = resolvePreauthHandshakeTimeoutMs({
66
- env: params?.env,
67
- configuredTimeoutMs: params?.configuredTimeoutMs
68
- });
69
- const maxTimeoutMs = Math.max(DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, configuredPreauthTimeoutMs);
70
- if (typeof timeoutMs === "number" && Number.isFinite(timeoutMs)) return clampConnectChallengeTimeoutMs(timeoutMs, maxTimeoutMs);
71
- const envOverride = getConnectChallengeTimeoutMsFromEnv(params?.env);
72
- if (envOverride !== void 0) return clampConnectChallengeTimeoutMs(envOverride, Math.max(maxTimeoutMs, envOverride));
73
- return clampConnectChallengeTimeoutMs(configuredPreauthTimeoutMs, maxTimeoutMs);
74
- }
75
- /** Resolves the server preauth timeout from env, explicit config, or default. */
76
- function resolvePreauthHandshakeTimeoutMs(params) {
77
- const env = params?.env ?? process.env;
78
- const configuredTimeout = env.OPENCLAW_HANDSHAKE_TIMEOUT_MS || (isTestRuntimeEnv(env) ? env.OPENCLAW_TEST_HANDSHAKE_TIMEOUT_MS : void 0);
79
- if (configuredTimeout) {
80
- const parsed = parsePositiveTimeoutSetting(configuredTimeout);
81
- if (parsed !== void 0) return resolveSafeTimeoutDelayMs(parsed);
82
- }
83
- const configured = normalizePositiveTimeoutMs(params?.configuredTimeoutMs);
84
- if (configured !== void 0) return configured;
85
- return DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS;
86
- }
87
- //#endregion
1
+ import { a as MIN_CONNECT_CHALLENGE_TIMEOUT_MS, c as clearGatewayConnectTimeout, d as resolveFiniteTimeoutDelayMs, f as resolvePreauthHandshakeTimeoutMs, i as MAX_SAFE_TIMEOUT_DELAY_MS, l as getConnectChallengeTimeoutMsFromEnv, m as startGatewayConnectTimeout, n as DEFAULT_PREAUTH_HANDSHAKE_TIMEOUT_MS, o as addSafeTimeoutDelayGraceMs, p as resolveSafeTimeoutDelayMs, r as MAX_CONNECT_CHALLENGE_TIMEOUT_MS, s as clampConnectChallengeTimeoutMs, t as DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, u as resolveConnectChallengeTimeoutMs } from "./timeouts-CQTCye5U.mjs";
88
2
  export { 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/gateway-client",
3
- "version": "2026.10.1",
3
+ "version": "2026.10.5-beta.1",
4
4
  "description": "Reference WebSocket client for the OpenClaw Gateway protocol",
5
5
  "keywords": [
6
6
  "client",
@@ -62,9 +62,9 @@
62
62
  }
63
63
  },
64
64
  "dependencies": {
65
- "@openclaw/gateway-protocol": "2026.10.1",
65
+ "@openclaw/gateway-protocol": "2026.10.5-beta.1",
66
66
  "ipaddr.js": "2.5.0",
67
- "ws": "8.21.3"
67
+ "ws": "8.22.0"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@openclaw/normalization-core": "0.0.0-private"