@openclaw/gateway-client 2026.10.1-beta.2 → 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-D5CpDUGt.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-D5CpDUGt.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) {
@@ -309,20 +286,39 @@ function createAbortError(message, options) {
309
286
  /** Bounds observation without cancelling work; a factory starts only after the timer is armed. */
310
287
  async function raceWithTimeout(operation, timeoutMs, onTimeout, options = {}) {
311
288
  let timer;
289
+ let onAbort;
312
290
  try {
313
291
  const deadline = new Promise((resolve, reject) => {
314
- timer = setTimeout(() => {
292
+ const settle = (result) => {
315
293
  try {
316
- resolve(onTimeout());
294
+ resolve(result());
317
295
  } catch (error) {
318
296
  reject(error);
319
297
  }
320
- }, timeoutMs);
298
+ };
299
+ timer = setTimeout(() => settle(onTimeout), timeoutMs);
321
300
  if (options.ref === false) timer.unref?.();
301
+ const signal = options.signal;
302
+ if (signal) {
303
+ onAbort = () => settle(() => {
304
+ if (options.onAbort) return options.onAbort(signal);
305
+ throw createAbortError("Operation aborted", { cause: signal.reason });
306
+ });
307
+ signal.addEventListener("abort", onAbort, { once: true });
308
+ if (signal.aborted) onAbort();
309
+ }
322
310
  });
323
- return await Promise.race([typeof operation === "function" ? operation() : operation, deadline]);
311
+ let source;
312
+ try {
313
+ source = typeof operation === "function" ? operation() : operation;
314
+ } catch (error) {
315
+ deadline.catch(() => {});
316
+ throw error;
317
+ }
318
+ return await Promise.race([source, deadline]);
324
319
  } finally {
325
320
  clearTimeout(timer);
321
+ if (onAbort) options.signal?.removeEventListener("abort", onAbort);
326
322
  }
327
323
  }
328
324
  function computeBackoff(policy, attempt) {
@@ -577,17 +573,35 @@ var GatewayPendingRequests = class {
577
573
  this.requestSequence = 0;
578
574
  }
579
575
  setSuspensionPhase(phase) {
580
- if (phase === "accepting") this.resumeIdentityRequests();
581
- else if (phase === "preparing" || phase === "draining" || phase === "prepared") this.pauseIdentityRequests();
582
- }
583
- pauseIdentityRequests(retryAfterMs = GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS) {
584
- clearTimeout(this.suspensionTimer);
585
- this.suspensionTimer = setTimeout(() => this.resumeIdentityRequests(), resolveSafeTimeoutDelayMs(Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS));
586
- this.suspensionTimer.unref?.();
587
- }
588
- resumeIdentityRequests() {
589
- clearTimeout(this.suspensionTimer);
590
- 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() {
591
605
  for (const pending of this.pending.values()) if (pending.waitingForResume) pending.resend?.();
592
606
  }
593
607
  get hasPending() {
@@ -622,6 +636,10 @@ var GatewayPendingRequests = class {
622
636
  const cleanup = () => {
623
637
  if (timeout !== void 0) clearTimeout(timeout);
624
638
  options?.signal?.removeEventListener("abort", onAbort);
639
+ if (this.bootstrapPause?.probe?.pending === pending) {
640
+ this.bootstrapPause.probe = void 0;
641
+ this.drainBootstrapRequests();
642
+ }
625
643
  };
626
644
  const retire = (errorCode) => {
627
645
  if (this.pending.get(id) !== pending) return false;
@@ -660,8 +678,13 @@ var GatewayPendingRequests = class {
660
678
  params
661
679
  });
662
680
  const send = () => {
663
- if (this.pending.get(id) !== pending) return;
664
- 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));
665
688
  if (pending.waitingForResume) return;
666
689
  try {
667
690
  sender.send(frame);
@@ -672,7 +695,7 @@ var GatewayPendingRequests = class {
672
695
  if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
673
696
  }
674
697
  };
675
- 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;
676
699
  send();
677
700
  } catch (error) {
678
701
  if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
@@ -690,12 +713,14 @@ var GatewayPendingRequests = class {
690
713
  }
691
714
  return;
692
715
  }
693
- if (!frame.ok && pending.resend && !pending.acceptedNotified && frame.error?.code === "UNAVAILABLE" && frame.error.retryable === true && isGatewaySuspendUnavailableError(frame.error)) {
694
- 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);
695
719
  pending.waitingForResume = true;
696
720
  return;
697
721
  }
698
722
  this.pending.delete(frame.id);
723
+ if (frame.ok && this.bootstrapPause?.probe?.pending === pending && this.bootstrapPause.probe.revision === this.bootstrapPause.revision) this.resumeBootstrapRequests();
699
724
  pending.cleanup?.();
700
725
  if (frame.ok) {
701
726
  this.finishTiming(frame.id, pending, true);
@@ -708,8 +733,8 @@ var GatewayPendingRequests = class {
708
733
  pending.reject(error);
709
734
  }
710
735
  flush(error) {
711
- clearTimeout(this.suspensionTimer);
712
- this.suspensionTimer = void 0;
736
+ clearTimeout(this.bootstrapPause?.timer);
737
+ this.bootstrapPause = void 0;
713
738
  const retired = this.pending;
714
739
  this.pending = /* @__PURE__ */ new Map();
715
740
  this.requestSequence = 0;
@@ -1004,6 +1029,10 @@ var GatewayProtocolClient = class {
1004
1029
  error: requestError,
1005
1030
  reconnectDelayMs: decision.reconnectDelayMs
1006
1031
  };
1032
+ if (decision.keepOpen) {
1033
+ this.clearHandshakeTimer();
1034
+ return;
1035
+ }
1007
1036
  if (decision.stop) this.stopped = true;
1008
1037
  this.connectionAbort?.abort();
1009
1038
  socket.close(decision.closeCode, decision.closeReason);
@@ -1046,8 +1075,8 @@ var GatewayProtocolClient = class {
1046
1075
  this.sendConnect(socket, generation);
1047
1076
  return;
1048
1077
  }
1049
- const seq = typeof parsed.seq === "number" ? parsed.seq : null;
1050
- if (seq !== null) {
1078
+ const seq = parsed.seq;
1079
+ if (seq !== void 0) {
1051
1080
  if (this.lastSeq !== null && seq > this.lastSeq + 1) {
1052
1081
  const expected = this.lastSeq + 1;
1053
1082
  const state = asRecord(parsed.payload).state;
@@ -1072,6 +1101,7 @@ var GatewayProtocolClient = class {
1072
1101
  dispatchEvent(socket, generation, event) {
1073
1102
  const listeners = this.listeners.snapshot();
1074
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");
1075
1105
  this.invoke("event", () => this.opts.onEvent?.(event));
1076
1106
  for (const [listener, subscription] of listeners) {
1077
1107
  if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
@@ -1195,6 +1225,8 @@ const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
1195
1225
  ConnectErrorDetailCodes.AUTH_VERIFIED_USER_REQUIRED,
1196
1226
  ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
1197
1227
  ConnectErrorDetailCodes.PAIRING_REQUIRED,
1228
+ ConnectErrorDetailCodes.PAIRING_REJECTED,
1229
+ ConnectErrorDetailCodes.PAIRING_EXPIRED,
1198
1230
  ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
1199
1231
  ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED
1200
1232
  ]);
@@ -1290,10 +1322,59 @@ function compareStableStrings(left, right) {
1290
1322
  return left < right ? -1 : left > right ? 1 : 0;
1291
1323
  }
1292
1324
  //#endregion
1293
- //#region packages/gateway-client/src/session-projection-message-identity.ts
1294
- function readSessionProjectionString(value) {
1295
- 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
+ };
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 ?? ""));
1296
1375
  }
1376
+ //#endregion
1377
+ //#region packages/gateway-client/src/session-projection-message-identity.ts
1297
1378
  function readPositiveSafeInteger(value) {
1298
1379
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1299
1380
  }
@@ -1303,33 +1384,33 @@ function readSessionMessageSequence(message, envelope) {
1303
1384
  }
1304
1385
  /** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
1305
1386
  function normalizeSessionProjectionRunId(value) {
1306
- const runId = readSessionProjectionString(value);
1387
+ const runId = normalizeNullableString(value);
1307
1388
  return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
1308
1389
  }
1309
1390
  /** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
1310
1391
  function readSessionMessageIdentity(message, envelope) {
1311
1392
  const record = asNullableRecord(message);
1312
- const role = readSessionProjectionString(record?.role)?.toLowerCase();
1393
+ const role = normalizeNullableString(record?.role)?.toLowerCase();
1313
1394
  if (!record || !role) return null;
1314
1395
  const metadata = asNullableRecord(record["__openclaw"]);
1315
- const importedFrom = readSessionProjectionString(metadata?.importedFrom);
1316
- const cliSessionId = readSessionProjectionString(metadata?.cliSessionId);
1317
- const externalId = readSessionProjectionString(metadata?.externalId);
1396
+ const importedFrom = normalizeNullableString(metadata?.importedFrom);
1397
+ const cliSessionId = normalizeNullableString(metadata?.cliSessionId);
1398
+ const externalId = normalizeNullableString(metadata?.externalId);
1318
1399
  const position = asNullableRecord(metadata?.transcriptPosition);
1319
- const positionSource = readSessionProjectionString(position?.source);
1400
+ const positionSource = normalizeNullableString(position?.source);
1320
1401
  const isImported = !(positionSource !== null && positionSource.length <= 128 && typeof position?.rawSeq === "number" && Number.isSafeInteger(position.rawSeq) && position.rawSeq >= 0) && Boolean(importedFrom || cliSessionId || externalId);
1321
- 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);
1322
1403
  const persistedRunId = normalizeSessionProjectionRunId(idempotencyKey);
1323
1404
  const envelopeRunId = normalizeSessionProjectionRunId(envelope?.runId);
1324
1405
  const metadataRunId = normalizeSessionProjectionRunId(metadata?.runId);
1325
1406
  const fallbackRunId = normalizeSessionProjectionRunId(asNullableRecord(record.openclawStreamFallback)?.runId);
1326
- const mirroredMessage = readSessionProjectionString(metadata?.mirrorOrigin) !== null;
1327
- const isCliAssistant = role === "assistant" && readSessionProjectionString(record.api)?.toLowerCase() === "cli";
1328
- 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;
1329
1410
  const runId = role === "assistant" ? metadataRunId ?? envelopeRunId ?? fallbackRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId;
1330
1411
  return {
1331
1412
  role,
1332
- id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
1413
+ id: normalizeNullableString(metadata?.id) ?? normalizeNullableString(envelope?.messageId),
1333
1414
  sequence: readSessionMessageSequence(message, envelope),
1334
1415
  idempotencyKey,
1335
1416
  sendId: role === "user" ? persistedRunId ?? runId : null,
@@ -1345,11 +1426,11 @@ function readSessionMessageIdentity(message, envelope) {
1345
1426
  /** A commentary item's display identity is separate from the transcript row that later owns it. */
1346
1427
  function readAssistantStreamSegmentIdentity(message) {
1347
1428
  const record = asNullableRecord(message);
1348
- if (readSessionProjectionString(record?.role)?.toLowerCase() !== "assistant") return;
1429
+ if (normalizeNullableString(record?.role)?.toLowerCase() !== "assistant") return;
1349
1430
  const fallback = asNullableRecord(record?.openclawStreamFallback);
1350
- const itemId = readSessionProjectionString(fallback?.itemId);
1431
+ const itemId = normalizeNullableString(fallback?.itemId);
1351
1432
  if (!itemId) return;
1352
- const runId = readSessionMessageIdentity(message)?.runId ?? readSessionProjectionString(record?.runId) ?? readSessionProjectionString(fallback?.runId);
1433
+ const runId = readSessionMessageIdentity(message)?.runId ?? normalizeNullableString(record?.runId) ?? normalizeNullableString(fallback?.runId);
1353
1434
  return {
1354
1435
  itemId,
1355
1436
  ...runId ? { runId } : {}
@@ -1362,7 +1443,7 @@ function sameAssistantPersistenceReceipt(left, right) {
1362
1443
  /** Local turns have no durable transcript metadata beyond their own optional send key. */
1363
1444
  function isLocallyOptimisticSessionMessage(message) {
1364
1445
  const record = asNullableRecord(message);
1365
- const role = readSessionProjectionString(record?.role)?.toLowerCase();
1446
+ const role = normalizeNullableString(record?.role)?.toLowerCase();
1366
1447
  if (role !== "user" && role !== "assistant") return false;
1367
1448
  if (asNullableRecord(record?.openclawStreamFallback)) return false;
1368
1449
  const metadata = asNullableRecord(record?.["__openclaw"]);
@@ -1395,58 +1476,6 @@ function createSessionProjectionEntry(message, options) {
1395
1476
  };
1396
1477
  }
1397
1478
  //#endregion
1398
- //#region packages/gateway-client/src/session-projection-message-content.ts
1399
- function readSessionMessageDisplayContent(message) {
1400
- const record = asNullableRecord(message);
1401
- const content = typeof message === "string" ? message : record?.content;
1402
- const media = asNullableRecord(record?.["__openclaw"])?.media;
1403
- let hasNonText = Array.isArray(media) && media.length > 0;
1404
- const texts = [];
1405
- for (const block of Array.isArray(content) ? content : [content]) {
1406
- const entry = asNullableRecord(block);
1407
- if (entry && entry.type !== "text" && entry.type !== "input_text" && entry.type !== "output_text") {
1408
- hasNonText ||= entry.type !== "thinking" && entry.type !== "reasoning" && entry.type !== "redacted_thinking";
1409
- continue;
1410
- }
1411
- const text = readSessionProjectionString(entry ? entry.text : block);
1412
- if (text) texts.push(text);
1413
- }
1414
- const fallback = texts.length === 0 ? readSessionProjectionString(record?.text) : null;
1415
- return {
1416
- text: fallback ?? texts.join("\n"),
1417
- hasNonText,
1418
- usesFallbackText: fallback !== null
1419
- };
1420
- }
1421
- /** Status notices remain visible but do not define the terminal reply they accompany. */
1422
- function projectSessionTerminalReplyMessage(message) {
1423
- const record = asNullableRecord(message);
1424
- if (!record || !Array.isArray(record.content)) return message;
1425
- const content = record.content.filter((block) => asNullableRecord(block)?.openclawStatusNotice !== true);
1426
- if (content.length === record.content.length || content.length === 0) return message;
1427
- return {
1428
- ...record,
1429
- content,
1430
- text: void 0
1431
- };
1432
- }
1433
- /** Check whether a projected message has text or another displayable block. */
1434
- function hasDisplayableSessionMessage(message) {
1435
- const { text, hasNonText } = readSessionMessageDisplayContent(message);
1436
- return Boolean(text) || hasNonText;
1437
- }
1438
- function normalizeChatErrorComparisonText(text) {
1439
- return text.trim().replace(/^⚠️\s*/u, "").replace(/^Error:\s*/iu, "").replace(/\s+/gu, " ").trim();
1440
- }
1441
- /** Diagnostic-only error projections can be retired when their run resumes. */
1442
- function isSessionProjectionErrorMessage(message, errorMessage) {
1443
- const role = asNullableRecord(message)?.role;
1444
- if (typeof role === "string" && role.trim().toLowerCase() !== "assistant") return false;
1445
- const { text, hasNonText } = readSessionMessageDisplayContent(message);
1446
- const normalizedText = normalizeChatErrorComparisonText(text);
1447
- return !hasNonText && (!normalizedText || normalizedText === "[assistant turn failed before producing content]" || normalizedText === GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT || normalizedText === normalizeChatErrorComparisonText(errorMessage ?? ""));
1448
- }
1449
- //#endregion
1450
1479
  //#region packages/gateway-client/src/session-projection-final-identity.ts
1451
1480
  /** Terminal identity rules used to reconcile live and durable assistant projections. */
1452
1481
  /** A run's selected text reply can inherit its terminal tool stop reason. */
@@ -1586,7 +1615,7 @@ function retainSessionProjectionRuns(runs) {
1586
1615
  //#region packages/gateway-client/src/session-projection-run-event.ts
1587
1616
  /** Normalizes Gateway run envelopes once for every browser and terminal adapter. */
1588
1617
  function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
1589
- const runId = readSessionProjectionString(event.runId);
1618
+ const runId = normalizeNullableString(event.runId);
1590
1619
  if (!runId || typeof event.state !== "string" || ![
1591
1620
  "delta",
1592
1621
  "final",
@@ -1594,9 +1623,9 @@ function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
1594
1623
  "aborted"
1595
1624
  ].includes(event.state)) return null;
1596
1625
  const message = event.state === "delta" ? mergeChatStreamMessage(projection.runs[runId]?.message, event) : event.message;
1597
- const messageStopReason = isRecord(message) ? readSessionProjectionString(message.stopReason) : null;
1598
- const stopReason = readSessionProjectionString(event.stopReason) ?? messageStopReason;
1599
- 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);
1600
1629
  const base = {
1601
1630
  runId,
1602
1631
  seq: typeof event.seq === "number" ? event.seq : void 0,
@@ -1675,7 +1704,7 @@ function entryMatches(left, right, allowSnapshotPromotion = false) {
1675
1704
  const durableSegment = durableEntry === left ? leftSegment : rightSegment;
1676
1705
  const provisionalSegment = durableEntry === left ? rightSegment : leftSegment;
1677
1706
  if (provisionalEntry.identity.sequence === null && durableSegment?.runId && durableSegment.runId === provisionalSegment?.runId && durableSegment.itemId === provisionalSegment.itemId) return true;
1678
- 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;
1679
1708
  }
1680
1709
  const persisted = left.identity;
1681
1710
  const observed = right.identity;
@@ -1819,7 +1848,7 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
1819
1848
  };
1820
1849
  }
1821
1850
  function updateRun(state, incoming) {
1822
- const incomingErrorMessage = readSessionProjectionString(incoming.errorMessage);
1851
+ const incomingErrorMessage = normalizeNullableString(incoming.errorMessage);
1823
1852
  const incomingSeq = typeof incoming.seq === "number" && Number.isSafeInteger(incoming.seq) && incoming.seq >= 0 ? incoming.seq : void 0;
1824
1853
  const normalizedIncoming = {
1825
1854
  ...incoming,
@@ -1836,7 +1865,7 @@ function updateRun(state, incoming) {
1836
1865
  const canAcceptFinal = currentHasDisplayableMessage ? current.status === incoming.status || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0 : canRecoverSessionProjectionFinal(current.message, incoming.message);
1837
1866
  const acceptFinal = incomingIsFinal && canAcceptFinal && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
1838
1867
  const recoverMessage = acceptFinal && !currentHasDisplayableMessage;
1839
- const recoverError = readSessionProjectionString(current.errorMessage) === null && incomingErrorMessage !== null;
1868
+ const recoverError = normalizeNullableString(current.errorMessage) === null && incomingErrorMessage !== null;
1840
1869
  const updateTerminalSequence = incoming.status !== "streaming" && (incomingSeq === void 0 ? current.seq !== void 0 : current.seq === void 0 || incomingSeq > current.seq);
1841
1870
  if (!acceptFinal && !recoverError && !updateTerminalSequence) return state;
1842
1871
  const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
@@ -2280,4 +2309,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
2280
2309
  return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
2281
2310
  }
2282
2311
  //#endregion
2283
- 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-beta.2",
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-beta.2",
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"