@openclaw/gateway-client 2026.9.5 → 2026.9.7

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.
@@ -1,13 +1,12 @@
1
1
  import { DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS, clearGatewayConnectTimeout, resolveSafeTimeoutDelayMs, startGatewayConnectTimeout } from "./timeouts.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
5
  import { GATEWAY_ASSISTANT_ERROR_FALLBACK_TEXT } from "@openclaw/gateway-protocol/gateway-error-details";
5
6
  //#region packages/gateway-client/src/device-auth.ts
6
7
  function normalizeDeviceMetadataForAuth(value) {
7
8
  if (typeof value !== "string") return "";
8
- const trimmed = value.trim();
9
- if (!trimmed) return "";
10
- return trimmed.replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32));
9
+ return value.trim().replace(/[A-Z]/g, (char) => String.fromCharCode(char.charCodeAt(0) + 32));
11
10
  }
12
11
  function buildDeviceAuthPayload(params) {
13
12
  const scopes = params.scopes.join(",");
@@ -44,6 +43,63 @@ function buildDeviceAuthPayloadV3(params) {
44
43
  ].join("|");
45
44
  }
46
45
  //#endregion
46
+ //#region packages/normalization-core/src/record-coerce.ts
47
+ /** Type guard for non-array object records at browser-safe boundaries. */
48
+ function isRecord(value) {
49
+ return value !== null && typeof value === "object" && !Array.isArray(value);
50
+ }
51
+ /** Coerces object-like values to records, falling back to an empty record. */
52
+ function asRecord(value) {
53
+ return asOptionalObjectRecord(value) ?? {};
54
+ }
55
+ /** Returns a non-array record or null. */
56
+ function asNullableRecord(value) {
57
+ return isRecord(value) ? value : null;
58
+ }
59
+ /** Returns any object-backed record, including arrays, or undefined. */
60
+ function asOptionalObjectRecord(value) {
61
+ return value && typeof value === "object" ? value : void 0;
62
+ }
63
+ //#endregion
64
+ //#region packages/gateway-client/src/chat-stream-message.ts
65
+ /** A snapshot includes its delta; append-only frames require an existing baseline. */
66
+ function mergeChatStreamMessage(previous, payload) {
67
+ if (payload.message !== void 0) return payload.message;
68
+ const delta = payload.deltaText;
69
+ if (typeof delta !== "string") return previous;
70
+ const replace = payload.replace === true;
71
+ if (!delta && !replace) return previous;
72
+ if (!isRecord(previous) && !replace) return;
73
+ const message = isRecord(previous) ? previous : { role: "assistant" };
74
+ if (typeof message.content === "string") return {
75
+ ...message,
76
+ content: replace ? delta : message.content + delta
77
+ };
78
+ const content = Array.isArray(message.content) ? [...message.content] : [];
79
+ const isText = (block) => isRecord(block) && block.type === "text";
80
+ if (replace) return {
81
+ ...message,
82
+ content: [{
83
+ type: "text",
84
+ text: delta
85
+ }, ...content.filter((block) => !isText(block))]
86
+ };
87
+ const index = content.findLastIndex(isText);
88
+ const block = content[index];
89
+ if (isText(block)) content[index] = {
90
+ ...block,
91
+ text: `${typeof block.text === "string" ? block.text : ""}${delta}`
92
+ };
93
+ else content.push({
94
+ type: "text",
95
+ text: delta
96
+ });
97
+ return {
98
+ ...message,
99
+ content
100
+ };
101
+ }
102
+ //#endregion
47
103
  //#region packages/gateway-client/src/connect-auth.ts
48
104
  function normalized(value) {
49
105
  return typeof value === "string" ? value.trim() || void 0 : void 0;
@@ -114,21 +170,19 @@ var GatewayBrowserDeviceAuthLifecycle = class {
114
170
  deviceId: identity.deviceId,
115
171
  role: params.role
116
172
  }) : null;
117
- const storedValue = stored?.token;
118
173
  const selectedAuth = selectGatewayConnectAuth({
119
174
  token: params.token,
120
175
  bootstrapToken: params.bootstrapToken,
121
176
  password: params.password,
122
- storedToken: storedValue,
177
+ storedToken: stored?.token,
123
178
  storedScopes: stored?.scopes,
124
179
  pendingDeviceTokenRetry: params.pendingDeviceTokenRetry,
125
180
  trustedDeviceTokenRetry: params.trustedDeviceTokenRetry,
126
181
  preferBootstrapToken: params.preferBootstrapToken
127
182
  });
128
- const { usingStoredDeviceToken } = selectedAuth;
129
183
  const scopes = resolveGatewayConnectScopes({
130
184
  requestedScopes: selectedAuth.authBootstrapToken ? params.bootstrapScopes ? [...params.bootstrapScopes] : void 0 : void 0,
131
- usingStoredDeviceToken,
185
+ usingStoredDeviceToken: selectedAuth.usingStoredDeviceToken,
132
186
  storedScopes: selectedAuth.storedScopes,
133
187
  defaultScopes: params.defaultScopes
134
188
  });
@@ -143,10 +197,6 @@ var GatewayBrowserDeviceAuthLifecycle = class {
143
197
  const signedAtMs = params.challengeTs === void 0 ? this.deps.nowMs?.() ?? Date.now() : params.challengeTs;
144
198
  if (typeof signedAtMs !== "number" || !Number.isSafeInteger(signedAtMs) || signedAtMs < 0) throw new Error("gateway connect challenge timestamp invalid");
145
199
  const nonce = params.nonce ?? "";
146
- const { authBootstrapToken: primary, signatureToken: signed } = selectedAuth;
147
- let token = null;
148
- if (primary) token = primary;
149
- else if (signed) token = signed;
150
200
  const payload = buildDeviceAuthPayloadV3({
151
201
  deviceId: identity.deviceId,
152
202
  clientId: params.client.id,
@@ -154,7 +204,7 @@ var GatewayBrowserDeviceAuthLifecycle = class {
154
204
  role: params.role,
155
205
  scopes,
156
206
  signedAtMs,
157
- token,
207
+ token: selectedAuth.authBootstrapToken || selectedAuth.signatureToken || null,
158
208
  nonce,
159
209
  platform: params.client.platform,
160
210
  deviceFamily: params.client.deviceFamily
@@ -245,9 +295,12 @@ function computeBackoff(policy, attempt) {
245
295
  const jitter = base * policy.jitter * Math.random();
246
296
  return Math.min(policy.maxMs, Math.round(base + jitter));
247
297
  }
298
+ function resolveSleepDelayMs(ms) {
299
+ return Number.isFinite(ms) && ms > 0 ? Math.min(Math.max(Math.floor(ms), 1), MAX_TIMER_TIMEOUT_MS) : 0;
300
+ }
248
301
  async function sleepWithAbort(ms, abortSignal, options = {}) {
249
- if (!Number.isFinite(ms) || ms <= 0) return;
250
- const delayMs = Math.min(Math.max(Math.floor(ms), 1), MAX_TIMER_TIMEOUT_MS);
302
+ const delayMs = resolveSleepDelayMs(ms);
303
+ if (delayMs === 0) return;
251
304
  await new Promise((resolve, reject) => {
252
305
  let settled = false;
253
306
  let timer = null;
@@ -330,21 +383,18 @@ const defaultSleep = async (ms) => {
330
383
  } while (remainingMs > 0);
331
384
  };
332
385
  function clampNumber(value, fallback, min, max) {
333
- const next = Number.isFinite(value) ? value : void 0;
334
- if (next === void 0) return fallback;
335
- return Math.min(Math.max(next, min ?? Number.NEGATIVE_INFINITY), max ?? Number.POSITIVE_INFINITY);
386
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
387
+ return Math.min(Math.max(value, min ?? Number.NEGATIVE_INFINITY), max ?? Number.POSITIVE_INFINITY);
336
388
  }
337
389
  function resolveAttemptCount(value, fallback) {
338
- return Math.max(1, Math.round(Number.isFinite(value) ? value : fallback));
390
+ return Math.max(1, Math.round(clampNumber(value, fallback)));
339
391
  }
340
392
  function resolveRetryDelayMs(value) {
341
- const finite = value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS : Number.isFinite(value) ? value : 0;
342
- return Math.min(Math.max(Math.round(finite), 0), MAX_TIMER_TIMEOUT_MS);
393
+ return Math.min(Math.max(Math.round(value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS : Number.isFinite(value) ? value : 0), 0), MAX_TIMER_TIMEOUT_MS);
343
394
  }
344
395
  function resolveJitterConfig(value, fallback) {
345
396
  if (value === "full") return "full";
346
- const fraction = Number.isFinite(value) ? value : void 0;
347
- return fraction === void 0 ? fallback : Math.min(Math.max(fraction, 0), 1);
397
+ return typeof value === "number" && Number.isFinite(value) ? Math.min(Math.max(value, 0), 1) : fallback;
348
398
  }
349
399
  function resolveRetryConfig(defaults = DEFAULT_RETRY_CONFIG, overrides) {
350
400
  const attempts = resolveAttemptCount(overrides?.attempts, defaults.attempts);
@@ -499,6 +549,20 @@ var GatewayPendingRequests = class {
499
549
  this.pending = /* @__PURE__ */ new Map();
500
550
  this.requestSequence = 0;
501
551
  }
552
+ setSuspensionPhase(phase) {
553
+ if (phase === "accepting") this.resumeIdentityRequests();
554
+ else if (phase === "preparing" || phase === "draining" || phase === "prepared") this.pauseIdentityRequests();
555
+ }
556
+ pauseIdentityRequests(retryAfterMs = GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS) {
557
+ clearTimeout(this.suspensionTimer);
558
+ this.suspensionTimer = setTimeout(() => this.resumeIdentityRequests(), resolveSafeTimeoutDelayMs(Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : GATEWAY_SUSPEND_IDENTITY_RETRY_AFTER_MS));
559
+ this.suspensionTimer.unref?.();
560
+ }
561
+ resumeIdentityRequests() {
562
+ clearTimeout(this.suspensionTimer);
563
+ this.suspensionTimer = void 0;
564
+ for (const pending of this.pending.values()) if (pending.waitingForResume) pending.resend?.();
565
+ }
502
566
  get hasPending() {
503
567
  return this.pending.size > 0;
504
568
  }
@@ -562,15 +626,27 @@ var GatewayPendingRequests = class {
562
626
  options?.signal?.addEventListener("abort", onAbort, { once: true });
563
627
  this.pending.set(id, pending);
564
628
  try {
565
- sender.send(JSON.stringify({
629
+ const frame = JSON.stringify({
566
630
  type: "req",
567
631
  id,
568
632
  method,
569
633
  params
570
- }));
571
- if (this.pending.get(id) !== pending) return;
572
- requestSent = true;
573
- this.invoke("sent", () => options?.onSent?.());
634
+ });
635
+ const send = () => {
636
+ if (this.pending.get(id) !== pending) return;
637
+ pending.waitingForResume = Boolean(pending.resend && this.suspensionTimer);
638
+ if (pending.waitingForResume) return;
639
+ try {
640
+ sender.send(frame);
641
+ if (this.pending.get(id) !== pending) return;
642
+ requestSent = true;
643
+ this.invoke("sent", () => options?.onSent?.(id));
644
+ } catch (error) {
645
+ if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
646
+ }
647
+ };
648
+ if (method === "agent.identity.get") pending.resend = send;
649
+ send();
574
650
  } catch (error) {
575
651
  if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
576
652
  }
@@ -587,6 +663,11 @@ var GatewayPendingRequests = class {
587
663
  }
588
664
  return;
589
665
  }
666
+ if (!frame.ok && pending.resend && !pending.acceptedNotified && frame.error?.code === "UNAVAILABLE" && frame.error.retryable === true && isGatewaySuspendUnavailableError(frame.error)) {
667
+ this.pauseIdentityRequests(frame.error.retryAfterMs);
668
+ pending.waitingForResume = true;
669
+ return;
670
+ }
590
671
  this.pending.delete(frame.id);
591
672
  pending.cleanup?.();
592
673
  if (frame.ok) {
@@ -600,6 +681,8 @@ var GatewayPendingRequests = class {
600
681
  pending.reject(error);
601
682
  }
602
683
  flush(error) {
684
+ clearTimeout(this.suspensionTimer);
685
+ this.suspensionTimer = void 0;
603
686
  const retired = this.pending;
604
687
  this.pending = /* @__PURE__ */ new Map();
605
688
  this.requestSequence = 0;
@@ -615,19 +698,15 @@ var GatewayPendingRequests = class {
615
698
  }
616
699
  finishTiming(id, pending, ok, errorCode) {
617
700
  const endedAtMs = this.opts.nowMs();
618
- try {
619
- this.opts.onTiming?.({
620
- id,
621
- method: pending.method,
622
- ok,
623
- durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
624
- startedAtMs: pending.startedAtMs,
625
- endedAtMs,
626
- errorCode
627
- });
628
- } catch (error) {
629
- this.opts.onCallbackError?.("request timing", error);
630
- }
701
+ this.invoke("request timing", () => this.opts.onTiming?.({
702
+ id,
703
+ method: pending.method,
704
+ ok,
705
+ durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
706
+ startedAtMs: pending.startedAtMs,
707
+ endedAtMs,
708
+ errorCode
709
+ }));
631
710
  }
632
711
  invoke(label, callback) {
633
712
  try {
@@ -872,6 +951,7 @@ var GatewayProtocolClient = class {
872
951
  this.request("connect", this.opts.buildConnectParams(plan)).then((hello) => {
873
952
  if (!this.isConnectCurrent(socket, generation)) return;
874
953
  this.helloReceived = true;
954
+ this.requests.setSuspensionPhase(hello.snapshot?.suspension?.phase);
875
955
  this.clearHandshakeTimer();
876
956
  this.connectFailure = void 0;
877
957
  this.reconnectSupervisor.reset();
@@ -910,7 +990,7 @@ var GatewayProtocolClient = class {
910
990
  });
911
991
  }
912
992
  handleMessage(socket, generation, raw) {
913
- if (!this.isActive(socket, generation)) return;
993
+ if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
914
994
  let parsed;
915
995
  try {
916
996
  parsed = JSON.parse(raw);
@@ -943,26 +1023,34 @@ var GatewayProtocolClient = class {
943
1023
  if (seq !== null) {
944
1024
  if (this.lastSeq !== null && seq > this.lastSeq + 1) {
945
1025
  const expected = this.lastSeq + 1;
1026
+ const state = asRecord(parsed.payload).state;
1027
+ if (parsed.event === "chat" && (state === "final" || state === "error" || state === "aborted")) this.dispatchEvent(socket, generation, parsed);
1028
+ if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
946
1029
  this.invoke("gap", () => this.opts.onGap?.({
947
1030
  expected,
948
1031
  received: seq
949
1032
  }));
950
- if (!this.isActive(socket, generation)) return;
1033
+ if (this.isActive(socket, generation) && !this.connectionAbort?.signal.aborted) this.closeSocket(4e3, "event sequence gap");
1034
+ return;
951
1035
  }
952
1036
  this.lastSeq = seq;
953
1037
  }
954
- const listeners = this.listeners.snapshot();
955
- this.invoke("event", () => this.opts.onEvent?.(parsed));
956
- for (const [listener, subscription] of listeners) {
957
- if (!this.isActive(socket, generation)) return;
958
- if (this.listeners.isCurrent(listener, subscription)) this.invoke("event listener", () => listener(parsed));
959
- }
1038
+ this.dispatchEvent(socket, generation, parsed);
960
1039
  return;
961
1040
  }
962
1041
  if (!isGatewayResponseFrame(parsed)) return;
963
1042
  this.opts.onActivity?.();
964
1043
  this.requests.handleResponse(parsed);
965
1044
  }
1045
+ dispatchEvent(socket, generation, event) {
1046
+ const listeners = this.listeners.snapshot();
1047
+ if (event.event === "gateway.suspension" && typeof event.payload === "object" && event.payload !== null && "phase" in event.payload) this.requests.setSuspensionPhase(event.payload.phase);
1048
+ this.invoke("event", () => this.opts.onEvent?.(event));
1049
+ for (const [listener, subscription] of listeners) {
1050
+ if (!this.isActive(socket, generation) || this.connectionAbort?.signal.aborted) return;
1051
+ if (this.listeners.isCurrent(listener, subscription)) this.invoke("event listener", () => listener(event));
1052
+ }
1053
+ }
966
1054
  handleClose(socket, generation, code, reason) {
967
1055
  if (this.socket !== socket) {
968
1056
  if (this.stoppedSocket?.socket === socket) {
@@ -1008,13 +1096,23 @@ var GatewayProtocolClient = class {
1008
1096
  const retry = this.reconnectSupervisor.next();
1009
1097
  if (!retry) return;
1010
1098
  this.reconnectSignal = retry.signal;
1011
- sleepWithAbort(overrideMs ?? Math.max(retry.delayMs, minimumMs), retry.signal).then(() => {
1099
+ let delayMs = overrideMs;
1100
+ if (delayMs === void 0) {
1101
+ const base = Math.max(retry.delayMs, minimumMs);
1102
+ const ceiling = Math.max(this.opts.reconnect.maxMs, Math.min(Number.MAX_VALUE, minimumMs * 1.2));
1103
+ const lower = Math.max(minimumMs, Math.min(base, ceiling / 1.2));
1104
+ const upper = Math.min(base * 1.2, ceiling);
1105
+ delayMs = Math.ceil(lower + Math.random() * (upper - lower));
1106
+ }
1107
+ delayMs = resolveSleepDelayMs(delayMs);
1108
+ sleepWithAbort(delayMs, retry.signal).then(() => {
1012
1109
  if (this.reconnectSignal !== retry.signal) return;
1013
1110
  this.reconnectSignal = null;
1014
1111
  this.invoke("reconnect", () => this.connect());
1015
1112
  }, () => {
1016
1113
  if (this.reconnectSignal === retry.signal) this.reconnectSignal = null;
1017
1114
  });
1115
+ this.invoke("reconnect scheduled", () => this.opts.onReconnectScheduled?.(delayMs, retry.signal));
1018
1116
  }
1019
1117
  closeContext() {
1020
1118
  return {
@@ -1083,23 +1181,13 @@ function shouldPauseGatewayReconnect(params) {
1083
1181
  return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
1084
1182
  }
1085
1183
  //#endregion
1086
- //#region packages/normalization-core/src/record-coerce.ts
1087
- /** Type guard for non-array object records at browser-safe boundaries. */
1088
- function isRecord(value) {
1089
- return value !== null && typeof value === "object" && !Array.isArray(value);
1090
- }
1091
- /** Returns a non-array record or null. */
1092
- function asNullableRecord(value) {
1093
- return isRecord(value) ? value : null;
1094
- }
1095
- //#endregion
1096
1184
  //#region packages/normalization-core/src/stable-stringify.ts
1097
1185
  const preserveString = (value) => value;
1098
1186
  /** Deterministically stringifies values, optionally normalizing strings before key ordering. */
1099
1187
  function stableStringify(value, normalizeString = preserveString) {
1100
1188
  return stringifyStableValue(value, /* @__PURE__ */ new WeakSet(), normalizeString);
1101
1189
  }
1102
- function stringifyStableValue(value, stack, normalizeString) {
1190
+ function stringifyStableValue(value, stack, normalizeString, write) {
1103
1191
  if (value === null || value === void 0) return String(value);
1104
1192
  if (typeof value === "number" && !Number.isFinite(value)) return JSON.stringify(String(value));
1105
1193
  if (typeof value === "bigint") return JSON.stringify(value.toString());
@@ -1108,22 +1196,33 @@ function stringifyStableValue(value, stack, normalizeString) {
1108
1196
  if (stack.has(value)) return JSON.stringify("[Circular]");
1109
1197
  stack.add(value);
1110
1198
  try {
1111
- return stringifyObjectValue(value, stack, normalizeString);
1199
+ return stringifyObjectValue(value, stack, normalizeString, write);
1112
1200
  } finally {
1113
1201
  stack.delete(value);
1114
1202
  }
1115
1203
  }
1116
- function stringifyObjectValue(value, stack, normalizeString) {
1204
+ function stringifyObjectValue(value, stack, normalizeString, write) {
1117
1205
  if (value instanceof Error) return stringifyStableValue({
1118
1206
  name: value.name,
1119
1207
  message: value.message,
1120
1208
  stack: value.stack
1121
- }, stack, normalizeString);
1209
+ }, stack, normalizeString, write);
1122
1210
  if (value instanceof Uint8Array) return stringifyStableValue({
1123
1211
  type: "Uint8Array",
1124
1212
  data: encodeBase64(value)
1125
- }, stack, normalizeString);
1213
+ }, stack, normalizeString, write);
1126
1214
  if (Array.isArray(value)) {
1215
+ if (write) {
1216
+ write("[");
1217
+ let separator = "";
1218
+ for (const entry of value) {
1219
+ write(separator);
1220
+ write(stringifyStableValue(entry, stack, normalizeString, write));
1221
+ separator = ",";
1222
+ }
1223
+ write("]");
1224
+ return "";
1225
+ }
1127
1226
  const serializedEntries = [];
1128
1227
  for (const entry of value) serializedEntries.push(stringifyStableValue(entry, stack, normalizeString));
1129
1228
  return `[${serializedEntries.join(",")}]`;
@@ -1131,6 +1230,17 @@ function stringifyObjectValue(value, stack, normalizeString) {
1131
1230
  const record = value;
1132
1231
  if (normalizeString === preserveString) {
1133
1232
  const fields = Object.keys(record).sort();
1233
+ if (write) {
1234
+ write("{");
1235
+ let separator = "";
1236
+ for (const key of fields) {
1237
+ write(`${separator}${JSON.stringify(key)}:`);
1238
+ write(stringifyStableValue(record[key], stack, normalizeString, write));
1239
+ separator = ",";
1240
+ }
1241
+ write("}");
1242
+ return "";
1243
+ }
1134
1244
  let fieldIndex = 0;
1135
1245
  for (const key of fields) fields[fieldIndex++] = `${JSON.stringify(key)}:${stringifyStableValue(record[key], stack, normalizeString)}`;
1136
1246
  return `{${fields.join(",")}}`;
@@ -1142,6 +1252,17 @@ function stringifyObjectValue(value, stack, normalizeString) {
1142
1252
  return compareStableStrings(left.normalizedKey, right.normalizedKey) || compareStableStrings(left.key, right.key);
1143
1253
  });
1144
1254
  const serializedFields = [];
1255
+ if (write) {
1256
+ write("{");
1257
+ let separator = "";
1258
+ for (const { key, normalizedKey } of entries) {
1259
+ write(`${separator}${JSON.stringify(normalizedKey)}:`);
1260
+ write(stringifyStableValue(record[key], stack, normalizeString, write));
1261
+ separator = ",";
1262
+ }
1263
+ write("}");
1264
+ return "";
1265
+ }
1145
1266
  for (const { key, normalizedKey } of entries) serializedFields.push(`${JSON.stringify(normalizedKey)}:${stringifyStableValue(record[key], stack, normalizeString)}`);
1146
1267
  return `{${serializedFields.join(",")}}`;
1147
1268
  }
@@ -1282,6 +1403,18 @@ function readSessionMessageDisplayContent(message) {
1282
1403
  usesFallbackText: fallback !== null
1283
1404
  };
1284
1405
  }
1406
+ /** Status notices remain visible but do not define the terminal reply they accompany. */
1407
+ function projectSessionTerminalReplyMessage(message) {
1408
+ const record = asNullableRecord(message);
1409
+ if (!record || !Array.isArray(record.content)) return message;
1410
+ const content = record.content.filter((block) => asNullableRecord(block)?.openclawStatusNotice !== true);
1411
+ if (content.length === record.content.length || content.length === 0) return message;
1412
+ return {
1413
+ ...record,
1414
+ content,
1415
+ text: void 0
1416
+ };
1417
+ }
1285
1418
  /** Check whether a projected message has text or another displayable block. */
1286
1419
  function hasDisplayableSessionMessage(message) {
1287
1420
  const { text, hasNonText } = readSessionMessageDisplayContent(message);
@@ -1301,12 +1434,7 @@ function isSessionProjectionErrorMessage(message, errorMessage) {
1301
1434
  //#endregion
1302
1435
  //#region packages/gateway-client/src/session-projection-final-identity.ts
1303
1436
  /** Terminal identity rules used to reconcile live and durable assistant projections. */
1304
- /**
1305
- * The class of rows the #148297 relaxation newly admits as finals: persisted
1306
- * with the run's terminal tool stop reason while carrying no tool-call
1307
- * content. Both adoption directions and every position rule scope to this
1308
- * predicate, so pre-existing match semantics stay untouched.
1309
- */
1437
+ /** A run's selected text reply can inherit its terminal tool stop reason. */
1310
1438
  function isToolUsePersistedFinalRow(message) {
1311
1439
  return asNullableRecord(message)?.["stopReason"] === "toolUse" && !isSessionProjectionToolContinuation(message);
1312
1440
  }
@@ -1341,10 +1469,11 @@ function hasCompatiblePersistedFinalIdentity(currentMessage, incomingMessage) {
1341
1469
  return current.sequence !== null && incoming.sequence !== null && current.sequence === incoming.sequence;
1342
1470
  }
1343
1471
  function readFinalContentIdentity(message) {
1344
- const display = readSessionMessageDisplayContent(message);
1472
+ const terminalMessage = projectSessionTerminalReplyMessage(message);
1473
+ const display = readSessionMessageDisplayContent(terminalMessage);
1345
1474
  if (!display.text && !display.hasNonText) return null;
1346
1475
  const identity = readSessionMessageIdentity(message);
1347
- const record = asNullableRecord(message);
1476
+ const record = asNullableRecord(terminalMessage);
1348
1477
  const metadata = asNullableRecord(record?.["__openclaw"]);
1349
1478
  try {
1350
1479
  return `content:${stableStringify([
@@ -1402,22 +1531,15 @@ function findUniqueSnapshotTerminalMatch(current, matches, run, snapshot) {
1402
1531
  });
1403
1532
  snapshotIndexes = indexes;
1404
1533
  };
1405
- const hasCompletedRunSnapshotContext = (entry) => {
1406
- const runId = current.identity?.runId;
1407
- if (!runId || entry.identity?.runId !== runId) return false;
1408
- ensureRunIndexes();
1409
- const entryIndex = snapshotIndexes?.get(entry);
1410
- return entryIndex !== void 0 && firstUserIndex >= 0 && firstUserIndex < entryIndex && lastAssistantIndex <= entryIndex;
1411
- };
1412
- const isLastSameRunAssistantRow = (entry) => {
1534
+ const hasTerminalSnapshotPosition = (entry, requireUser) => {
1413
1535
  const runId = current.identity?.runId;
1414
1536
  if (!runId || entry.identity?.runId !== runId) return false;
1415
1537
  ensureRunIndexes();
1416
1538
  const entryIndex = snapshotIndexes?.get(entry);
1417
- return entryIndex !== void 0 && lastAssistantIndex >= 0 && entryIndex >= lastAssistantIndex;
1539
+ return entryIndex !== void 0 && lastAssistantIndex <= entryIndex && (requireUser ? firstUserIndex >= 0 && firstUserIndex < entryIndex : lastAssistantIndex >= 0);
1418
1540
  };
1419
1541
  const durableTerminalMatches = matches.filter((entry) => {
1420
- return (asNullableRecord(asNullableRecord(entry.message)?.["__openclaw"])?.runTerminal === true || entry.identity?.runId === current.identity?.runId && (hasTerminalStopReason(entry.message) || asNullableRecord(entry.message)?.["stopReason"] === "toolUse" && !isSessionProjectionToolContinuation(entry.message) && isLastSameRunAssistantRow(entry)) || hasCompletedRunSnapshotContext(entry)) && readFinalContentIdentity(entry.message) === terminalContent;
1542
+ return (asNullableRecord(asNullableRecord(entry.message)?.["__openclaw"])?.runTerminal === true || entry.identity?.runId === current.identity?.runId && (hasTerminalStopReason(entry.message) || isToolUsePersistedFinalRow(entry.message) && hasTerminalSnapshotPosition(entry, false)) || hasTerminalSnapshotPosition(entry, true)) && readFinalContentIdentity(entry.message) === terminalContent;
1421
1543
  });
1422
1544
  const entry = durableTerminalMatches.length === 1 ? durableTerminalMatches[0] : void 0;
1423
1545
  if (!entry) return null;
@@ -1447,22 +1569,19 @@ function retainSessionProjectionRuns(runs) {
1447
1569
  }
1448
1570
  //#endregion
1449
1571
  //#region packages/gateway-client/src/session-projection-run-event.ts
1450
- function readNonemptyString(value) {
1451
- return typeof value === "string" ? value.trim() || null : null;
1452
- }
1453
1572
  /** Normalizes Gateway run envelopes once for every browser and terminal adapter. */
1454
1573
  function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
1455
- const runId = readNonemptyString(event.runId);
1574
+ const runId = readSessionProjectionString(event.runId);
1456
1575
  if (!runId || typeof event.state !== "string" || ![
1457
1576
  "delta",
1458
1577
  "final",
1459
1578
  "error",
1460
1579
  "aborted"
1461
1580
  ].includes(event.state)) return null;
1462
- const message = event.message;
1463
- const messageStopReason = isRecord(message) ? readNonemptyString(message.stopReason) : null;
1464
- const stopReason = readNonemptyString(event.stopReason) ?? messageStopReason;
1465
- const errorKind = readNonemptyString(event.errorKind);
1581
+ const message = event.state === "delta" ? mergeChatStreamMessage(projection.runs[runId]?.message, event) : event.message;
1582
+ const messageStopReason = isRecord(message) ? readSessionProjectionString(message.stopReason) : null;
1583
+ const stopReason = readSessionProjectionString(event.stopReason) ?? messageStopReason;
1584
+ const errorKind = readSessionProjectionString(event.errorKind);
1466
1585
  const base = {
1467
1586
  runId,
1468
1587
  seq: typeof event.seq === "number" ? event.seq : void 0,
@@ -1815,16 +1934,67 @@ function reduceSessionProjection(state, event) {
1815
1934
  }
1816
1935
  }
1817
1936
  //#endregion
1818
- //#region packages/gateway-client/src/session-subscriptions.ts
1819
- function sessionSubscriptionParams(key, agentId) {
1937
+ //#region packages/normalization-core/src/result.ts
1938
+ /** Create a successful {@link Result}. */
1939
+ function ok(value) {
1940
+ return {
1941
+ ok: true,
1942
+ value
1943
+ };
1944
+ }
1945
+ /** Create a failed {@link Result}. */
1946
+ function err(error) {
1820
1947
  return {
1821
- key: key.trim(),
1822
- ...agentId ? { agentId } : {}
1948
+ ok: false,
1949
+ error
1823
1950
  };
1824
1951
  }
1952
+ //#endregion
1953
+ //#region packages/normalization-core/src/agent-id.ts
1954
+ const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
1955
+ const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
1956
+ const LEADING_DASH_RE = /^-+/;
1957
+ const TRAILING_DASH_RE = /-+$/;
1958
+ /** Normalizes an explicitly supplied agent id without falling back to the default agent. */
1959
+ function normalizeAgentIdStrict(value) {
1960
+ const trimmed = (value ?? "").trim();
1961
+ const normalized = trimmed.toLowerCase();
1962
+ if (VALID_ID_RE.test(trimmed)) return ok(normalized);
1963
+ const agentId = normalized.replace(INVALID_CHARS_RE, "-").replace(LEADING_DASH_RE, "").replace(TRAILING_DASH_RE, "").slice(0, 64);
1964
+ return agentId ? ok(agentId) : err("unrepresentable");
1965
+ }
1966
+ //#endregion
1967
+ //#region packages/normalization-core/src/uuid.ts
1968
+ let warnedWeakCrypto = false;
1969
+ function warnWeakCryptoOnce() {
1970
+ if (warnedWeakCrypto) return;
1971
+ warnedWeakCrypto = true;
1972
+ console.warn("[uuid] crypto API missing; refusing insecure UUID generation");
1973
+ }
1974
+ function generateUUID(cryptoLike = globalThis.crypto) {
1975
+ if (cryptoLike && typeof cryptoLike.randomUUID === "function") return cryptoLike.randomUUID();
1976
+ if (cryptoLike && typeof cryptoLike.getRandomValues === "function") {
1977
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
1978
+ cryptoLike.getRandomValues(bytes);
1979
+ const view = new DataView(bytes.buffer);
1980
+ view.setUint8(6, view.getUint8(6) & 15 | 64);
1981
+ view.setUint8(8, view.getUint8(8) & 63 | 128);
1982
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1983
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1984
+ }
1985
+ warnWeakCryptoOnce();
1986
+ throw new Error("Web Crypto is required for UUID generation");
1987
+ }
1988
+ //#endregion
1989
+ //#region packages/gateway-client/src/session-subscriptions.ts
1990
+ function normalizedAgentScope(agentId) {
1991
+ if (!agentId) return null;
1992
+ const normalized = normalizeAgentIdStrict(agentId);
1993
+ return normalized.ok ? normalized.value : agentId;
1994
+ }
1825
1995
  /**
1826
- * One Gateway connection owns one targeted observer per canonical session.
1827
- * Approval delivery is an upgrade of that observer, never a second observer.
1996
+ * Shared leases retain one wire observer ID. The Gateway combines independently
1997
+ * addressed observers; approval delivery upgrades the same owner.
1828
1998
  */
1829
1999
  var GatewaySessionMessageSubscriptionCoordinator = class {
1830
2000
  #client;
@@ -1846,17 +2016,24 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1846
2016
  const normalizedKey = key.trim();
1847
2017
  if (!normalizedKey) throw new Error("Session message subscription requires a session key");
1848
2018
  const agentId = options.agentId?.trim() || null;
2019
+ const scopeAgentId = normalizedAgentScope(agentId);
2020
+ const narration = options.mode === "narration";
2021
+ const includeApprovals = options.includeApprovals === true;
1849
2022
  let entry;
2023
+ let created = false;
1850
2024
  while (true) {
1851
2025
  if (this.#retired) throw new Error("Session message subscription belongs to a replaced Gateway connection");
1852
- const existing = [...this.#entries].find((candidate) => candidate.agentId === agentId && (this.#areKeysEquivalent(candidate.key, normalizedKey) || [...candidate.requestedKeys].some((requestedKey) => this.#areKeysEquivalent(requestedKey, normalizedKey))));
2026
+ const existing = [...this.#entries].find((candidate) => {
2027
+ return candidate.scopeAgentId === scopeAgentId && ([...candidate.requestedKeys].some((requestedKey) => this.#areKeysEquivalent(requestedKey, normalizedKey)) || candidate.key !== "global" && this.#areKeysEquivalent(candidate.key, normalizedKey));
2028
+ });
1853
2029
  if (!existing) {
1854
- const provisional = [...this.#entries].find((candidate) => candidate.agentId === agentId && !candidate.canonicalSettled && this.#couldShareCanonicalIdentity(candidate.key, normalizedKey));
2030
+ const provisional = [...this.#entries].find((candidate) => candidate.scopeAgentId === scopeAgentId && !candidate.canonicalSettled && this.#couldShareCanonicalIdentity(candidate.key, normalizedKey));
1855
2031
  if (provisional) {
1856
2032
  await (provisional.plainFallback ?? provisional.ready).catch(() => void 0);
1857
2033
  continue;
1858
2034
  }
1859
- entry = this.#createEntry(normalizedKey, agentId, options.includeApprovals === true);
2035
+ entry = this.#createEntry(normalizedKey, agentId);
2036
+ created = true;
1860
2037
  break;
1861
2038
  }
1862
2039
  if (!existing.release) {
@@ -1867,13 +2044,31 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1867
2044
  await existing.release.catch(() => void 0);
1868
2045
  }
1869
2046
  entry.pendingOwners += 1;
2047
+ if (!narration) entry.pendingFullOwners += 1;
1870
2048
  try {
1871
- const result = await this.#acquireCapability(entry, options.includeApprovals === true);
2049
+ if (created) {
2050
+ entry.ready = this.#requestSubscribe(entry, includeApprovals);
2051
+ if (includeApprovals) entry.ready = this.#trackApprovalRequest(entry, entry.ready);
2052
+ entry.ready.catch(() => void 0);
2053
+ }
2054
+ if (entry.refreshRequired) {
2055
+ entry.refreshRequired = false;
2056
+ entry.plainFallback = null;
2057
+ entry.approvalRequest = null;
2058
+ const retainedApprovals = [...entry.handles].some((handle) => handle.includeApprovals);
2059
+ entry.ready = this.#requestSubscribe(entry, retainedApprovals).catch((error) => {
2060
+ entry.refreshRequired = true;
2061
+ throw error;
2062
+ });
2063
+ }
2064
+ const result = await this.#acquireCapability(entry, includeApprovals);
2065
+ if (!narration && entry.mode === "narration") await this.#requestSubscribe(entry, false, "full");
1872
2066
  if (this.#retired) throw new Error("Session message subscription completed on a replaced Gateway connection");
1873
2067
  const subscription = {
1874
2068
  key: result.key,
1875
2069
  agentId,
1876
- ...options.includeApprovals === true ? {
2070
+ ...narration ? { mode: "narration" } : {},
2071
+ ...includeApprovals ? {
1877
2072
  includeApprovals: true,
1878
2073
  ...result.approvalReplay !== void 0 ? { approvalReplay: result.approvalReplay } : {}
1879
2074
  } : {}
@@ -1886,6 +2081,7 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1886
2081
  return subscription;
1887
2082
  } finally {
1888
2083
  entry.pendingOwners -= 1;
2084
+ if (!narration) entry.pendingFullOwners -= 1;
1889
2085
  if (entry.pendingOwners === 0 && entry.handles.size === 0 && !entry.release) this.#entries.delete(entry);
1890
2086
  }
1891
2087
  }
@@ -1893,26 +2089,34 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1893
2089
  const owner = sessionMessageSubscriptionOwners.get(subscription);
1894
2090
  if (!owner || owner.coordinator !== this) return Promise.resolve();
1895
2091
  const { entry } = owner;
1896
- if (this.#retired || entry.handles.size > 1) {
2092
+ if (entry.release) return entry.releasing === subscription ? entry.release : entry.release.catch(() => void 0).then(() => this.release(subscription));
2093
+ const releasesLastFullOwner = subscription.mode !== "narration" && [...entry.handles].every((handle) => handle === subscription || handle.mode === "narration");
2094
+ if (this.#retired || entry.handles.size > 1 && !releasesLastFullOwner) {
1897
2095
  this.#finishRelease(subscription, owner);
1898
2096
  return Promise.resolve();
1899
2097
  }
1900
- if (entry.release) return entry.release;
1901
2098
  if (entry.pendingOwners > 0) {
1902
- const pending = [entry.ready, ...entry.approvalRequest ? [entry.approvalRequest] : []];
1903
- const tracked = Promise.allSettled(pending).then(() => {
2099
+ const tracked = Promise.allSettled([
2100
+ entry.ready,
2101
+ entry.approvalRequest,
2102
+ entry.wireRequest
2103
+ ]).then(() => {
1904
2104
  if (entry.release === tracked) entry.release = null;
1905
2105
  return this.release(subscription);
1906
2106
  });
1907
2107
  entry.release = tracked;
2108
+ entry.releasing = subscription;
1908
2109
  return tracked;
1909
2110
  }
1910
- const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId), { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).then(() => {
1911
- this.#finishRelease(subscription, owner, true);
1912
- }).finally(() => {
2111
+ const removeEntry = entry.handles.size === 1;
2112
+ const tracked = (removeEntry ? this.#requestMessages(entry).catch((error) => {
2113
+ if (error instanceof GatewayProtocolRequestTimeoutError && error.requestSent) entry.refreshRequired = true;
2114
+ throw error;
2115
+ }) : this.#requestSubscribe(entry, false, "narration")).then(() => this.#finishRelease(subscription, owner, removeEntry)).finally(() => {
1913
2116
  if (entry.release === tracked) entry.release = null;
1914
2117
  });
1915
2118
  entry.release = tracked;
2119
+ entry.releasing = subscription;
1916
2120
  return tracked;
1917
2121
  }
1918
2122
  /** A reconnect retires leases without touching the next connection's observers. */
@@ -1924,22 +2128,26 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1924
2128
  }
1925
2129
  this.#entries.clear();
1926
2130
  }
1927
- #createEntry(key, agentId, includeApprovals) {
2131
+ #createEntry(key, agentId) {
1928
2132
  const entry = {
2133
+ subscriptionId: generateUUID(),
1929
2134
  key,
1930
2135
  requestedKeys: /* @__PURE__ */ new Set([key]),
1931
2136
  agentId,
2137
+ scopeAgentId: normalizedAgentScope(agentId),
2138
+ ownerAgentId: null,
1932
2139
  ready: Promise.resolve({ key }),
1933
2140
  approvalRequest: null,
1934
2141
  plainFallback: null,
2142
+ wireRequest: null,
2143
+ includeApprovals: false,
1935
2144
  canonicalSettled: false,
2145
+ refreshRequired: false,
1936
2146
  handles: /* @__PURE__ */ new Set(),
1937
2147
  pendingOwners: 0,
2148
+ pendingFullOwners: 0,
1938
2149
  release: null
1939
2150
  };
1940
- entry.ready = this.#requestSubscribe(entry, includeApprovals);
1941
- if (includeApprovals) entry.ready = this.#trackApprovalRequest(entry, entry.ready);
1942
- entry.ready.catch(() => void 0);
1943
2151
  this.#entries.add(entry);
1944
2152
  return entry;
1945
2153
  }
@@ -1970,19 +2178,43 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1970
2178
  entry.approvalRequest = tracked;
1971
2179
  return tracked;
1972
2180
  }
1973
- async #requestSubscribe(entry, includeApprovals) {
1974
- const params = sessionSubscriptionParams(entry.key, entry.agentId);
1975
- const result = await this.#client.request("sessions.messages.subscribe", includeApprovals ? {
1976
- ...params,
1977
- includeApprovals: true
1978
- } : params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).catch(async (error) => {
2181
+ #requestSubscribe(entry, includeApprovals, mode) {
2182
+ const send = () => this.#sendSubscribe(entry, includeApprovals, mode);
2183
+ const tracked = (entry.wireRequest ? entry.wireRequest.then(send, send) : send()).finally(() => {
2184
+ if (entry.wireRequest === tracked) entry.wireRequest = null;
2185
+ });
2186
+ entry.wireRequest = tracked;
2187
+ return tracked;
2188
+ }
2189
+ #requestMessages(entry, subscription) {
2190
+ const agentId = entry.key === "global" ? entry.ownerAgentId ?? entry.agentId : entry.agentId;
2191
+ return this.#client.request(subscription ? "sessions.messages.subscribe" : "sessions.messages.unsubscribe", {
2192
+ subscriptionId: entry.subscriptionId,
2193
+ key: entry.key,
2194
+ ...agentId ? { agentId } : {},
2195
+ ...subscription?.mode ? { mode: subscription.mode } : {},
2196
+ ...subscription?.includeApprovals ? { includeApprovals: true } : {}
2197
+ }, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS });
2198
+ }
2199
+ async #sendSubscribe(entry, requestedApprovals, requestedMode) {
2200
+ if (this.#retired) throw new Error("Session message subscription belongs to a replaced Gateway connection");
2201
+ if (requestedMode === "full" && entry.mode !== "narration") return { key: entry.key };
2202
+ const includeApprovals = requestedApprovals || requestedMode !== void 0 && entry.includeApprovals;
2203
+ const mode = requestedMode === "narration" || requestedMode !== "full" && entry.pendingFullOwners === 0 && [...entry.handles].every((handle) => handle.mode === "narration") ? "narration" : void 0;
2204
+ const result = await this.#requestMessages(entry, {
2205
+ mode,
2206
+ includeApprovals
2207
+ }).catch(async (error) => {
1979
2208
  if (!(error instanceof GatewayProtocolRequestTimeoutError) || !error.requestSent || this.#retired) throw error;
1980
2209
  try {
1981
2210
  const retainedApprovals = [...entry.handles].some((handle) => handle.includeApprovals);
1982
- await this.#client.request(entry.handles.size > 0 ? "sessions.messages.subscribe" : "sessions.messages.unsubscribe", retainedApprovals ? {
1983
- ...params,
1984
- includeApprovals: true
1985
- } : params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS });
2211
+ const retainedMode = entry.handles.size > 0 && [...entry.handles].every((handle) => handle.mode === "narration") ? "narration" : void 0;
2212
+ await this.#requestMessages(entry, entry.handles.size > 0 ? {
2213
+ mode: retainedMode,
2214
+ includeApprovals: retainedApprovals
2215
+ } : void 0);
2216
+ entry.mode = retainedMode;
2217
+ entry.includeApprovals = retainedApprovals;
1986
2218
  } catch (recoveryError) {
1987
2219
  if (!this.#retired) throw new AggregateError([error, recoveryError], "session message subscription recovery failed", { cause: recoveryError });
1988
2220
  }
@@ -1992,6 +2224,10 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
1992
2224
  const responseKey = response && "key" in response ? response.key : void 0;
1993
2225
  entry.key = typeof responseKey === "string" && responseKey.trim() ? responseKey.trim() : entry.key;
1994
2226
  entry.canonicalSettled = true;
2227
+ const responseAgentId = response && "agentId" in response ? response.agentId : void 0;
2228
+ entry.ownerAgentId = normalizedAgentScope((typeof responseAgentId === "string" ? responseAgentId : null) ?? entry.ownerAgentId ?? entry.scopeAgentId);
2229
+ entry.mode = mode;
2230
+ entry.includeApprovals = includeApprovals;
1995
2231
  return {
1996
2232
  key: entry.key,
1997
2233
  ...response && "approvalReplay" in response ? { approvalReplay: response.approvalReplay } : {}
@@ -2029,4 +2265,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
2029
2265
  return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
2030
2266
  }
2031
2267
  //#endregion
2032
- export { selectGatewayConnectAuth as A, isGatewayProtocolResponseError as C, GatewayBrowserDeviceAuthLifecycle as D, gatewayOriginScope as E, buildDeviceAuthPayload as M, buildDeviceAuthPayloadV3 as N, buildGatewayConnectAuth as O, normalizeDeviceMetadataForAuth as P, GatewayProtocolRequestTimeoutError as S, gatewayCredentialScope as T, readSessionMessageSequence as _, createSessionProjection as a, GatewayProtocolClient as b, reduceSessionProjection as c, readSessionProjectionFinalMessageIdentity as d, isSessionProjectionErrorMessage as f, readSessionMessageIdentity as g, readAssistantStreamSegmentIdentity as h, resetGatewaySessionMessageSubscriptionCoordinator as i, shouldRetryGatewayWithDeviceToken as j, resolveGatewayConnectScopes as k, reduceSessionProjectionRunEvent as l, normalizeSessionProjectionRunId as m, getGatewaySessionMessageSubscriptionCoordinator as n, projectLiveSessionMessage as o, isLocallyOptimisticSessionMessage as p, releaseGatewaySessionMessageSubscription as r, reconcileSessionProjectionSnapshot as s, GatewaySessionMessageSubscriptionCoordinator as t, hasSessionProjectionAcceptedFinal as u, isRecord as v, resolveModelCatalogConnect as w, GatewayProtocolRequestError as x, shouldPauseGatewayReconnect as y };
2268
+ export { selectGatewayConnectAuth as A, isGatewayProtocolResponseError as C, GatewayBrowserDeviceAuthLifecycle as D, gatewayOriginScope as E, isRecord as F, buildDeviceAuthPayload as I, buildDeviceAuthPayloadV3 as L, mergeChatStreamMessage as M, asNullableRecord as N, buildGatewayConnectAuth as O, asRecord as P, normalizeDeviceMetadataForAuth as R, GatewayProtocolRequestTimeoutError as S, gatewayCredentialScope as T, 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, shouldRetryGatewayWithDeviceToken as j, resolveGatewayConnectScopes 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, resolveModelCatalogConnect as w, GatewayProtocolRequestError as x, shouldPauseGatewayReconnect as y };