@openclaw/gateway-client 2026.8.1-beta.1 → 2026.8.1-beta.3
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 +15 -3
- package/dist/browser-device-auth-Dz6H7eXy.d.mts +112 -0
- package/dist/browser.d.mts +5 -3
- package/dist/browser.mjs +2 -2
- package/dist/index.d.mts +16 -3
- package/dist/index.mjs +653 -1136
- package/dist/{protocol-client-U142L0j3.d.mts → protocol-client-BbN-I582.d.mts} +15 -30
- package/dist/protocol-request-D4mVhLxE.d.mts +32 -0
- package/dist/{readiness-uSDrqxDM.d.mts → readiness-JllUDKLg.d.mts} +28 -14
- package/dist/readiness.d.mts +1 -1
- package/dist/scope-upgrade.d.mts +38 -0
- package/dist/scope-upgrade.mjs +73 -0
- package/dist/{session-subscriptions-Das1PATe.d.mts → session-subscriptions-CKF7acYN.d.mts} +17 -119
- package/dist/{session-subscriptions-D4Xv60cN.mjs → session-subscriptions-OJnDJHPL.mjs} +336 -191
- package/dist/timeouts.mjs +3 -3
- package/dist/websocket-data.d.mts +6 -0
- package/dist/websocket-data.mjs +8 -0
- package/package.json +16 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clearGatewayConnectTimeout, startGatewayConnectTimeout } from "./timeouts.mjs";
|
|
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
4
|
//#region packages/gateway-client/src/device-auth.ts
|
|
@@ -59,7 +59,7 @@ function selectGatewayConnectAuth(params) {
|
|
|
59
59
|
};
|
|
60
60
|
if (params.preferBootstrapToken && bootstrapToken) return {
|
|
61
61
|
authBootstrapToken: bootstrapToken,
|
|
62
|
-
|
|
62
|
+
signatureToken: bootstrapToken,
|
|
63
63
|
...stored
|
|
64
64
|
};
|
|
65
65
|
const useRetryToken = params.pendingDeviceTokenRetry === true && !explicitDeviceToken && Boolean(authToken && storedToken && params.trustedDeviceTokenRetry);
|
|
@@ -177,12 +177,18 @@ var GatewayBrowserDeviceAuthLifecycle = class {
|
|
|
177
177
|
async acceptHello(hello, plan) {
|
|
178
178
|
const token = hello.auth?.deviceToken?.trim();
|
|
179
179
|
if (!token || !plan.identity) return;
|
|
180
|
+
const role = hello.auth?.role ?? plan.role;
|
|
181
|
+
const stored = await this.deps.tokenStore.load({
|
|
182
|
+
clientId: plan.clientId,
|
|
183
|
+
deviceId: plan.identity.deviceId,
|
|
184
|
+
role
|
|
185
|
+
});
|
|
180
186
|
await this.deps.tokenStore.store({
|
|
181
187
|
clientId: plan.clientId,
|
|
182
188
|
deviceId: plan.identity.deviceId,
|
|
183
|
-
role
|
|
189
|
+
role,
|
|
184
190
|
token,
|
|
185
|
-
scopes: hello.auth?.scopes ?? []
|
|
191
|
+
scopes: stored?.token === token ? stored.scopes : hello.auth?.scopes ?? []
|
|
186
192
|
});
|
|
187
193
|
}
|
|
188
194
|
async clearStoredToken(plan) {
|
|
@@ -195,6 +201,29 @@ var GatewayBrowserDeviceAuthLifecycle = class {
|
|
|
195
201
|
}
|
|
196
202
|
};
|
|
197
203
|
//#endregion
|
|
204
|
+
//#region packages/gateway-client/src/gateway-origin-scope.ts
|
|
205
|
+
function normalizeGatewayScope(gatewayUrl, includeSearch) {
|
|
206
|
+
const trimmed = gatewayUrl.trim();
|
|
207
|
+
if (!trimmed) return "default";
|
|
208
|
+
try {
|
|
209
|
+
const browserLocation = globalThis.location;
|
|
210
|
+
const base = browserLocation ? `${browserLocation.protocol}//${browserLocation.host}${browserLocation.pathname || "/"}` : void 0;
|
|
211
|
+
const parsed = base ? new URL(trimmed, base) : new URL(trimmed);
|
|
212
|
+
const pathname = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "") || parsed.pathname;
|
|
213
|
+
return `${parsed.protocol}//${parsed.host}${pathname}${includeSearch ? parsed.search : ""}`;
|
|
214
|
+
} catch {
|
|
215
|
+
return trimmed;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** Normalizes the gateway URL scope used for origin-bound device tokens. */
|
|
219
|
+
function gatewayOriginScope(gatewayUrl) {
|
|
220
|
+
return normalizeGatewayScope(gatewayUrl, false);
|
|
221
|
+
}
|
|
222
|
+
/** Normalizes the gateway URL scope used for browser credential records. */
|
|
223
|
+
function gatewayCredentialScope(gatewayUrl) {
|
|
224
|
+
return normalizeGatewayScope(gatewayUrl, true);
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
198
227
|
//#region packages/retry/src/index.ts
|
|
199
228
|
const MAX_TIMER_TIMEOUT_MS = 2147e6;
|
|
200
229
|
function computeBackoff(policy, attempt) {
|
|
@@ -215,7 +244,9 @@ async function sleepWithAbort(ms, abortSignal, options = {}) {
|
|
|
215
244
|
if (timer) clearTimeout(timer);
|
|
216
245
|
timer = null;
|
|
217
246
|
cleanup();
|
|
218
|
-
|
|
247
|
+
const error = new Error("aborted", { cause: abortSignal?.reason ?? /* @__PURE__ */ new Error("aborted") });
|
|
248
|
+
error.name = "AbortError";
|
|
249
|
+
reject(error);
|
|
219
250
|
};
|
|
220
251
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
221
252
|
if (abortSignal?.aborted) {
|
|
@@ -277,24 +308,21 @@ const DEFAULT_RETRY_CONFIG = {
|
|
|
277
308
|
const defaultSleep = (ms) => new Promise((resolve) => {
|
|
278
309
|
setTimeout(resolve, ms);
|
|
279
310
|
});
|
|
280
|
-
function asFiniteNumber(value) {
|
|
281
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
282
|
-
}
|
|
283
311
|
function clampNumber(value, fallback, min, max) {
|
|
284
|
-
const next =
|
|
312
|
+
const next = Number.isFinite(value) ? value : void 0;
|
|
285
313
|
if (next === void 0) return fallback;
|
|
286
314
|
return Math.min(Math.max(next, min ?? Number.NEGATIVE_INFINITY), max ?? Number.POSITIVE_INFINITY);
|
|
287
315
|
}
|
|
288
316
|
function resolveAttemptCount(value, fallback) {
|
|
289
|
-
return Math.max(1, Math.round(
|
|
317
|
+
return Math.max(1, Math.round(Number.isFinite(value) ? value : fallback));
|
|
290
318
|
}
|
|
291
319
|
function resolveRetryDelayMs(value) {
|
|
292
|
-
const finite = value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS :
|
|
320
|
+
const finite = value === Number.POSITIVE_INFINITY ? MAX_TIMER_TIMEOUT_MS : Number.isFinite(value) ? value : 0;
|
|
293
321
|
return Math.min(Math.max(Math.round(finite), 0), MAX_TIMER_TIMEOUT_MS);
|
|
294
322
|
}
|
|
295
323
|
function resolveJitterConfig(value, fallback) {
|
|
296
324
|
if (value === "full") return "full";
|
|
297
|
-
const fraction =
|
|
325
|
+
const fraction = Number.isFinite(value) ? value : void 0;
|
|
298
326
|
return fraction === void 0 ? fallback : Math.min(Math.max(fraction, 0), 1);
|
|
299
327
|
}
|
|
300
328
|
function resolveRetryConfig(defaults = DEFAULT_RETRY_CONFIG, overrides) {
|
|
@@ -416,6 +444,162 @@ var GatewayProtocolRequestError = class extends Error {
|
|
|
416
444
|
this.retryAfterMs = error.retryAfterMs;
|
|
417
445
|
}
|
|
418
446
|
};
|
|
447
|
+
/** A local transport deadline, distinct from a Gateway's authoritative rejection. */
|
|
448
|
+
var GatewayProtocolRequestTimeoutError = class extends Error {
|
|
449
|
+
constructor(params, message = `gateway request timed out after ${params.timeoutMs}ms: ${params.method}`) {
|
|
450
|
+
super(message);
|
|
451
|
+
this.code = "CLIENT_TIMEOUT";
|
|
452
|
+
this.name = "GatewayProtocolRequestTimeoutError";
|
|
453
|
+
this.method = params.method;
|
|
454
|
+
this.timeoutMs = params.timeoutMs;
|
|
455
|
+
this.requestSent = params.requestSent;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region packages/gateway-client/src/pending-request.ts
|
|
460
|
+
/** Owns request deadlines, correlation, settlement, and generation-scoped IDs. */
|
|
461
|
+
var GatewayPendingRequests = class {
|
|
462
|
+
constructor(opts) {
|
|
463
|
+
this.opts = opts;
|
|
464
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
465
|
+
this.requestSequence = 0;
|
|
466
|
+
}
|
|
467
|
+
get hasPending() {
|
|
468
|
+
return this.pending.size > 0;
|
|
469
|
+
}
|
|
470
|
+
get hasUnboundedPending() {
|
|
471
|
+
return [...this.pending.values()].some((pending) => pending.unbounded);
|
|
472
|
+
}
|
|
473
|
+
request(sender, method, params, options) {
|
|
474
|
+
let id;
|
|
475
|
+
try {
|
|
476
|
+
id = this.allocateRequestId();
|
|
477
|
+
} catch (error) {
|
|
478
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
479
|
+
}
|
|
480
|
+
const requestedTimeoutMs = options?.timeoutMs === null ? void 0 : options?.timeoutMs ?? this.opts.requestTimeoutMs;
|
|
481
|
+
const timeoutMs = typeof requestedTimeoutMs === "number" && Number.isFinite(requestedTimeoutMs) ? resolveSafeTimeoutDelayMs(requestedTimeoutMs, { minMs: 0 }) : void 0;
|
|
482
|
+
return new Promise((resolve, reject) => {
|
|
483
|
+
let timeout;
|
|
484
|
+
let requestSent = false;
|
|
485
|
+
const pending = {
|
|
486
|
+
resolve: (value) => resolve(value),
|
|
487
|
+
reject,
|
|
488
|
+
expectFinal: options?.expectFinal === true,
|
|
489
|
+
acceptedNotified: false,
|
|
490
|
+
onAccepted: options?.onAccepted,
|
|
491
|
+
unbounded: timeoutMs === void 0,
|
|
492
|
+
method,
|
|
493
|
+
startedAtMs: this.opts.nowMs()
|
|
494
|
+
};
|
|
495
|
+
const cleanup = () => {
|
|
496
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
497
|
+
options?.signal?.removeEventListener("abort", onAbort);
|
|
498
|
+
};
|
|
499
|
+
const retire = (errorCode) => {
|
|
500
|
+
if (this.pending.get(id) !== pending) return false;
|
|
501
|
+
this.pending.delete(id);
|
|
502
|
+
cleanup();
|
|
503
|
+
this.finishTiming(id, pending, false, errorCode);
|
|
504
|
+
return true;
|
|
505
|
+
};
|
|
506
|
+
const onAbort = () => {
|
|
507
|
+
if (!retire("CLIENT_ABORTED")) return;
|
|
508
|
+
reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
|
|
509
|
+
};
|
|
510
|
+
if (options?.signal?.aborted) {
|
|
511
|
+
reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
pending.cleanup = cleanup;
|
|
515
|
+
if (timeoutMs !== void 0) {
|
|
516
|
+
timeout = setTimeout(() => {
|
|
517
|
+
if (!retire("CLIENT_TIMEOUT")) return;
|
|
518
|
+
reject(this.opts.createRequestTimeoutError?.(method, timeoutMs, requestSent) ?? new GatewayProtocolRequestTimeoutError({
|
|
519
|
+
method,
|
|
520
|
+
timeoutMs,
|
|
521
|
+
requestSent
|
|
522
|
+
}));
|
|
523
|
+
}, timeoutMs);
|
|
524
|
+
timeout.unref?.();
|
|
525
|
+
}
|
|
526
|
+
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
527
|
+
this.pending.set(id, pending);
|
|
528
|
+
try {
|
|
529
|
+
sender.send(JSON.stringify({
|
|
530
|
+
type: "req",
|
|
531
|
+
id,
|
|
532
|
+
method,
|
|
533
|
+
params
|
|
534
|
+
}));
|
|
535
|
+
if (this.pending.get(id) !== pending) return;
|
|
536
|
+
requestSent = true;
|
|
537
|
+
this.invoke("sent", () => options?.onSent?.());
|
|
538
|
+
} catch (error) {
|
|
539
|
+
if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
handleResponse(frame) {
|
|
544
|
+
const pending = this.pending.get(frame.id);
|
|
545
|
+
if (!pending) return;
|
|
546
|
+
const status = frame.payload?.status;
|
|
547
|
+
if (pending.expectFinal && status === "accepted") {
|
|
548
|
+
if (!pending.acceptedNotified) {
|
|
549
|
+
pending.acceptedNotified = true;
|
|
550
|
+
this.invoke("accepted", () => pending.onAccepted?.(frame.payload));
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
this.pending.delete(frame.id);
|
|
555
|
+
pending.cleanup?.();
|
|
556
|
+
if (frame.ok) {
|
|
557
|
+
this.finishTiming(frame.id, pending, true);
|
|
558
|
+
pending.resolve(frame.payload);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.finishTiming(frame.id, pending, false, frame.error?.code);
|
|
562
|
+
pending.reject(this.opts.createRequestError?.(frame.error ?? {}) ?? new GatewayProtocolRequestError(frame.error ?? {}));
|
|
563
|
+
}
|
|
564
|
+
flush(error) {
|
|
565
|
+
for (const [id, pending] of this.pending) {
|
|
566
|
+
this.finishTiming(id, pending, false, "CLIENT_CLOSED");
|
|
567
|
+
pending.cleanup?.();
|
|
568
|
+
pending.reject(error);
|
|
569
|
+
}
|
|
570
|
+
this.pending.clear();
|
|
571
|
+
this.requestSequence = 0;
|
|
572
|
+
}
|
|
573
|
+
allocateRequestId() {
|
|
574
|
+
this.requestSequence += 1;
|
|
575
|
+
return `${this.requestSequence}:${this.opts.createRequestId()}`;
|
|
576
|
+
}
|
|
577
|
+
finishTiming(id, pending, ok, errorCode) {
|
|
578
|
+
const endedAtMs = this.opts.nowMs();
|
|
579
|
+
try {
|
|
580
|
+
const onTiming = this.opts.onTiming;
|
|
581
|
+
if (onTiming === void 0 || onTiming === null) return;
|
|
582
|
+
Reflect.apply(onTiming, this.opts, [{
|
|
583
|
+
id,
|
|
584
|
+
method: pending.method,
|
|
585
|
+
ok,
|
|
586
|
+
durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
|
|
587
|
+
startedAtMs: pending.startedAtMs,
|
|
588
|
+
endedAtMs,
|
|
589
|
+
errorCode
|
|
590
|
+
}]);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
this.opts.onCallbackError?.("request timing", error);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
invoke(label, callback) {
|
|
596
|
+
try {
|
|
597
|
+
callback();
|
|
598
|
+
} catch (error) {
|
|
599
|
+
this.opts.onCallbackError?.(label, error);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
};
|
|
419
603
|
//#endregion
|
|
420
604
|
//#region packages/gateway-client/src/protocol-client.ts
|
|
421
605
|
/**
|
|
@@ -426,7 +610,6 @@ var GatewayProtocolClient = class {
|
|
|
426
610
|
constructor(opts) {
|
|
427
611
|
this.opts = opts;
|
|
428
612
|
this.socket = null;
|
|
429
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
430
613
|
this.listeners = new GatewayEventListeners();
|
|
431
614
|
this.stopped = true;
|
|
432
615
|
this.generation = 0;
|
|
@@ -445,18 +628,28 @@ var GatewayProtocolClient = class {
|
|
|
445
628
|
factor: opts.reconnect.multiplier,
|
|
446
629
|
jitter: 0
|
|
447
630
|
});
|
|
631
|
+
this.requests = new GatewayPendingRequests({
|
|
632
|
+
createRequestId: opts.createRequestId,
|
|
633
|
+
createRequestError: opts.createRequestError,
|
|
634
|
+
createRequestTimeoutError: opts.createRequestTimeoutError,
|
|
635
|
+
createRequestAbortError: opts.createRequestAbortError,
|
|
636
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
637
|
+
nowMs: () => this.nowMs(),
|
|
638
|
+
onTiming: opts.onRequestTiming,
|
|
639
|
+
onCallbackError: opts.onCallbackError
|
|
640
|
+
});
|
|
448
641
|
}
|
|
449
642
|
get connected() {
|
|
450
643
|
return this.socket?.isOpen() ?? false;
|
|
451
644
|
}
|
|
452
645
|
get hasPendingRequests() {
|
|
453
|
-
return this.
|
|
646
|
+
return this.requests.hasPending;
|
|
454
647
|
}
|
|
455
648
|
get connecting() {
|
|
456
649
|
return this.connectSent && !this.helloReceived;
|
|
457
650
|
}
|
|
458
651
|
get hasUnboundedPendingRequests() {
|
|
459
|
-
return
|
|
652
|
+
return this.requests.hasUnboundedPending;
|
|
460
653
|
}
|
|
461
654
|
start() {
|
|
462
655
|
if (this.socket || this.reconnectSignal) return;
|
|
@@ -477,71 +670,14 @@ var GatewayProtocolClient = class {
|
|
|
477
670
|
this.socket = null;
|
|
478
671
|
this.connectFailure = void 0;
|
|
479
672
|
this.connectTiming = null;
|
|
480
|
-
this.
|
|
673
|
+
this.requests.flush(/* @__PURE__ */ new Error("gateway client stopped"));
|
|
481
674
|
socket?.close();
|
|
482
675
|
}
|
|
483
676
|
request(method, params, options) {
|
|
484
677
|
const socket = this.socket;
|
|
485
678
|
if (!socket?.isOpen()) return Promise.reject(/* @__PURE__ */ new Error("gateway not connected"));
|
|
486
679
|
if (typeof method !== "string" || method.length === 0) return Promise.reject(/* @__PURE__ */ new Error("invalid request frame: method must be a non-empty string"));
|
|
487
|
-
|
|
488
|
-
const timeoutMs = options?.timeoutMs === null ? void 0 : options?.timeoutMs ?? this.opts.requestTimeoutMs;
|
|
489
|
-
return new Promise((resolve, reject) => {
|
|
490
|
-
let timeout;
|
|
491
|
-
let requestSent = false;
|
|
492
|
-
const pending = {
|
|
493
|
-
resolve: (value) => resolve(value),
|
|
494
|
-
reject,
|
|
495
|
-
expectFinal: options?.expectFinal === true,
|
|
496
|
-
acceptedNotified: false,
|
|
497
|
-
onAccepted: options?.onAccepted,
|
|
498
|
-
unbounded: timeoutMs === void 0,
|
|
499
|
-
method,
|
|
500
|
-
startedAtMs: this.nowMs()
|
|
501
|
-
};
|
|
502
|
-
const onAbort = () => {
|
|
503
|
-
this.pending.delete(id);
|
|
504
|
-
pending.cleanup?.();
|
|
505
|
-
this.finishRequestTiming(id, pending, false, "CLIENT_ABORTED");
|
|
506
|
-
reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
|
|
507
|
-
};
|
|
508
|
-
const cleanup = () => {
|
|
509
|
-
if (timeout) clearTimeout(timeout);
|
|
510
|
-
options?.signal?.removeEventListener("abort", onAbort);
|
|
511
|
-
};
|
|
512
|
-
if (options?.signal?.aborted) {
|
|
513
|
-
reject(this.opts.createRequestAbortError?.(method) ?? /* @__PURE__ */ new Error(`gateway request aborted for ${method}`));
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
pending.cleanup = cleanup;
|
|
517
|
-
if (timeoutMs !== void 0 && timeoutMs >= 0) {
|
|
518
|
-
timeout = setTimeout(() => {
|
|
519
|
-
if (this.pending.get(id) !== pending) return;
|
|
520
|
-
this.pending.delete(id);
|
|
521
|
-
options?.signal?.removeEventListener("abort", onAbort);
|
|
522
|
-
this.finishRequestTiming(id, pending, false, "CLIENT_TIMEOUT");
|
|
523
|
-
reject(this.opts.createRequestTimeoutError?.(method, timeoutMs, requestSent) ?? /* @__PURE__ */ new Error(`gateway request timed out after ${timeoutMs}ms: ${method}`));
|
|
524
|
-
}, timeoutMs);
|
|
525
|
-
timeout.unref?.();
|
|
526
|
-
}
|
|
527
|
-
options?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
528
|
-
this.pending.set(id, pending);
|
|
529
|
-
try {
|
|
530
|
-
socket.send(JSON.stringify({
|
|
531
|
-
type: "req",
|
|
532
|
-
id,
|
|
533
|
-
method,
|
|
534
|
-
params
|
|
535
|
-
}));
|
|
536
|
-
requestSent = true;
|
|
537
|
-
this.invoke("sent", () => options?.onSent?.());
|
|
538
|
-
} catch (error) {
|
|
539
|
-
this.pending.delete(id);
|
|
540
|
-
cleanup();
|
|
541
|
-
this.finishRequestTiming(id, pending, false, "CLIENT_SEND_ERROR");
|
|
542
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
543
|
-
}
|
|
544
|
-
});
|
|
680
|
+
return this.requests.request(socket, method, params, options);
|
|
545
681
|
}
|
|
546
682
|
addEventListener(listener) {
|
|
547
683
|
return this.listeners.add(listener);
|
|
@@ -758,28 +894,7 @@ var GatewayProtocolClient = class {
|
|
|
758
894
|
}
|
|
759
895
|
if (!isGatewayResponseFrame(parsed)) return;
|
|
760
896
|
this.opts.onActivity?.();
|
|
761
|
-
this.handleResponse(parsed);
|
|
762
|
-
}
|
|
763
|
-
handleResponse(frame) {
|
|
764
|
-
const pending = this.pending.get(frame.id);
|
|
765
|
-
if (!pending) return;
|
|
766
|
-
const status = frame.payload?.status;
|
|
767
|
-
if (pending.expectFinal && status === "accepted") {
|
|
768
|
-
if (!pending.acceptedNotified) {
|
|
769
|
-
pending.acceptedNotified = true;
|
|
770
|
-
this.invoke("accepted", () => pending.onAccepted?.(frame.payload));
|
|
771
|
-
}
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
this.pending.delete(frame.id);
|
|
775
|
-
pending.cleanup?.();
|
|
776
|
-
if (frame.ok) {
|
|
777
|
-
this.finishRequestTiming(frame.id, pending, true);
|
|
778
|
-
pending.resolve(frame.payload);
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
this.finishRequestTiming(frame.id, pending, false, frame.error?.code);
|
|
782
|
-
pending.reject(this.opts.createRequestError?.(frame.error ?? {}) ?? new GatewayProtocolRequestError(frame.error ?? {}));
|
|
897
|
+
this.requests.handleResponse(parsed);
|
|
783
898
|
}
|
|
784
899
|
handleClose(socket, generation, code, reason) {
|
|
785
900
|
if (this.socket !== socket) {
|
|
@@ -807,34 +922,15 @@ var GatewayProtocolClient = class {
|
|
|
807
922
|
};
|
|
808
923
|
this.connectFailure = void 0;
|
|
809
924
|
const decision = this.opts.resolveClose(context);
|
|
810
|
-
this.
|
|
925
|
+
this.requests.flush(decision.pendingError ?? context.connectFailure?.error ?? /* @__PURE__ */ new Error(`gateway closed (${code}): ${reason}`));
|
|
811
926
|
this.invoke("close", () => this.opts.onClose?.(context, decision));
|
|
812
927
|
if (decision.retry && !this.stopped) this.scheduleReconnect(decision.reconnectDelayMs ?? context.connectFailure?.reconnectDelayMs);
|
|
813
928
|
}
|
|
814
929
|
handleSocketError(socket, generation, error) {
|
|
815
930
|
if (!this.isActive(socket, generation) || this.connectSent) return;
|
|
931
|
+
this.connectFailure = { error };
|
|
816
932
|
this.opts.onConnectError?.(error);
|
|
817
933
|
}
|
|
818
|
-
flushRequests(error) {
|
|
819
|
-
for (const [id, pending] of this.pending) {
|
|
820
|
-
this.finishRequestTiming(id, pending, false, "CLIENT_CLOSED");
|
|
821
|
-
pending.cleanup?.();
|
|
822
|
-
pending.reject(error);
|
|
823
|
-
}
|
|
824
|
-
this.pending.clear();
|
|
825
|
-
}
|
|
826
|
-
finishRequestTiming(id, pending, ok, errorCode) {
|
|
827
|
-
const endedAtMs = this.nowMs();
|
|
828
|
-
this.invoke("request timing", () => this.opts.onRequestTiming?.({
|
|
829
|
-
id,
|
|
830
|
-
method: pending.method,
|
|
831
|
-
ok,
|
|
832
|
-
durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
|
|
833
|
-
startedAtMs: pending.startedAtMs,
|
|
834
|
-
endedAtMs,
|
|
835
|
-
errorCode
|
|
836
|
-
}));
|
|
837
|
-
}
|
|
838
934
|
scheduleReconnect(overrideMs) {
|
|
839
935
|
if (overrideMs !== void 0) this.reconnectSupervisor.nextDelayOverrideMs = overrideMs;
|
|
840
936
|
const retry = this.reconnectSupervisor.next();
|
|
@@ -884,6 +980,8 @@ const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
|
884
980
|
ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
|
|
885
981
|
ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH,
|
|
886
982
|
ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH,
|
|
983
|
+
ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED,
|
|
984
|
+
ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
|
|
887
985
|
ConnectErrorDetailCodes.PAIRING_REQUIRED,
|
|
888
986
|
ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
|
|
889
987
|
ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED
|
|
@@ -894,23 +992,22 @@ function shouldPauseGatewayReconnect(params) {
|
|
|
894
992
|
const pairing = readPairingConnectErrorDetails(params.details);
|
|
895
993
|
if (code === ConnectErrorDetailCodes.PAIRING_REQUIRED && (pairing?.pauseReconnect === false || pairing?.recommendedNextStep === "wait_then_retry")) return false;
|
|
896
994
|
if (code === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH) return params.tokenMismatchIsTerminal === true && !params.deviceTokenRetryPending;
|
|
995
|
+
if (code === ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED) return !params.deviceTokenRetryPending;
|
|
897
996
|
return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
|
|
898
997
|
}
|
|
899
998
|
//#endregion
|
|
900
|
-
//#region packages/
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
"sessionKey",
|
|
905
|
-
"sessionId",
|
|
906
|
-
"agentId",
|
|
907
|
-
"lifecycleRevision",
|
|
908
|
-
"activeLeafEntryId"
|
|
909
|
-
];
|
|
910
|
-
function readRecord(value) {
|
|
911
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
999
|
+
//#region packages/normalization-core/src/record-coerce.ts
|
|
1000
|
+
/** Type guard for non-array object records at browser-safe boundaries. */
|
|
1001
|
+
function isRecord(value) {
|
|
1002
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
912
1003
|
}
|
|
913
|
-
|
|
1004
|
+
/** Returns a non-array record or null. */
|
|
1005
|
+
function asNullableRecord(value) {
|
|
1006
|
+
return isRecord(value) ? value : null;
|
|
1007
|
+
}
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region packages/gateway-client/src/session-projection-message-identity.ts
|
|
1010
|
+
function readSessionProjectionString(value) {
|
|
914
1011
|
return typeof value === "string" ? value.trim() || null : null;
|
|
915
1012
|
}
|
|
916
1013
|
function readPositiveSafeInteger(value) {
|
|
@@ -918,29 +1015,36 @@ function readPositiveSafeInteger(value) {
|
|
|
918
1015
|
}
|
|
919
1016
|
/** History and status markers carry transcript order even when they have no chat role. */
|
|
920
1017
|
function readSessionMessageSequence(message, envelope) {
|
|
921
|
-
return readPositiveSafeInteger(
|
|
1018
|
+
return readPositiveSafeInteger(asNullableRecord(asNullableRecord(message)?.["__openclaw"])?.seq) ?? readPositiveSafeInteger(envelope?.messageSeq);
|
|
922
1019
|
}
|
|
923
1020
|
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
924
1021
|
function normalizeSessionProjectionRunId(value) {
|
|
925
|
-
const runId =
|
|
1022
|
+
const runId = readSessionProjectionString(value);
|
|
926
1023
|
return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
|
|
927
1024
|
}
|
|
928
|
-
/** Persisted
|
|
1025
|
+
/** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
|
|
929
1026
|
function readSessionMessageIdentity(message, envelope) {
|
|
930
|
-
const record =
|
|
931
|
-
const role =
|
|
1027
|
+
const record = asNullableRecord(message);
|
|
1028
|
+
const role = readSessionProjectionString(record?.role)?.toLowerCase();
|
|
932
1029
|
if (!record || !role) return null;
|
|
933
|
-
const metadata =
|
|
934
|
-
const importedFrom =
|
|
935
|
-
const cliSessionId =
|
|
936
|
-
const externalId =
|
|
937
|
-
const idempotencyKey =
|
|
1030
|
+
const metadata = asNullableRecord(record["__openclaw"]);
|
|
1031
|
+
const importedFrom = readSessionProjectionString(metadata?.importedFrom);
|
|
1032
|
+
const cliSessionId = readSessionProjectionString(metadata?.cliSessionId);
|
|
1033
|
+
const externalId = readSessionProjectionString(metadata?.externalId);
|
|
1034
|
+
const idempotencyKey = readSessionProjectionString(metadata?.idempotencyKey) ?? readSessionProjectionString(record.idempotencyKey) ?? readSessionProjectionString(envelope?.idempotencyKey) ?? readSessionProjectionString(envelope?.clientRunId);
|
|
1035
|
+
const persistedRunId = normalizeSessionProjectionRunId(idempotencyKey);
|
|
1036
|
+
const envelopeRunId = normalizeSessionProjectionRunId(envelope?.runId);
|
|
1037
|
+
const metadataRunId = normalizeSessionProjectionRunId(metadata?.runId);
|
|
1038
|
+
const mirroredMessage = readSessionProjectionString(metadata?.mirrorOrigin) !== null;
|
|
1039
|
+
const isCliAssistant = role === "assistant" && readSessionProjectionString(record.api)?.toLowerCase() === "cli";
|
|
1040
|
+
const canonicalPersistedRunId = isCliAssistant && persistedRunId?.startsWith("cli-assistant:") ? readSessionProjectionString(persistedRunId.slice(14)) : persistedRunId;
|
|
1041
|
+
const optimisticRunId = metadata && Object.keys(metadata).every((key) => key === "idempotencyKey") ? canonicalPersistedRunId : null;
|
|
938
1042
|
return {
|
|
939
1043
|
role,
|
|
940
|
-
id:
|
|
1044
|
+
id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
|
|
941
1045
|
sequence: readSessionMessageSequence(message, envelope),
|
|
942
1046
|
idempotencyKey,
|
|
943
|
-
runId:
|
|
1047
|
+
runId: role === "assistant" ? metadataRunId ?? envelopeRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) ?? optimisticRunId : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId,
|
|
944
1048
|
isImported: Boolean(importedFrom || cliSessionId || externalId),
|
|
945
1049
|
externalSource: importedFrom && cliSessionId && externalId ? JSON.stringify([
|
|
946
1050
|
importedFrom,
|
|
@@ -949,11 +1053,62 @@ function readSessionMessageIdentity(message, envelope) {
|
|
|
949
1053
|
]) : null
|
|
950
1054
|
};
|
|
951
1055
|
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region packages/gateway-client/src/session-projection-run-event.ts
|
|
1058
|
+
function readNonemptyString(value) {
|
|
1059
|
+
return typeof value === "string" ? value.trim() || null : null;
|
|
1060
|
+
}
|
|
1061
|
+
function reduceSessionProjectionRunEventImpl(projection, event, scope = {}) {
|
|
1062
|
+
const runId = readNonemptyString(event.runId);
|
|
1063
|
+
if (!runId || typeof event.state !== "string" || ![
|
|
1064
|
+
"delta",
|
|
1065
|
+
"final",
|
|
1066
|
+
"error",
|
|
1067
|
+
"aborted"
|
|
1068
|
+
].includes(event.state)) return null;
|
|
1069
|
+
const message = event.message;
|
|
1070
|
+
const messageStopReason = isRecord(message) ? readNonemptyString(message.stopReason) : null;
|
|
1071
|
+
const stopReason = readNonemptyString(event.stopReason) ?? messageStopReason;
|
|
1072
|
+
const errorKind = readNonemptyString(event.errorKind);
|
|
1073
|
+
const base = {
|
|
1074
|
+
runId,
|
|
1075
|
+
...message === void 0 ? {} : { message },
|
|
1076
|
+
scope
|
|
1077
|
+
};
|
|
1078
|
+
const next = reduceSessionProjection(projection, event.state === "delta" ? {
|
|
1079
|
+
type: "runDelta",
|
|
1080
|
+
...base
|
|
1081
|
+
} : {
|
|
1082
|
+
type: "runTerminal",
|
|
1083
|
+
...base,
|
|
1084
|
+
status: event.state === "aborted" ? "aborted" : event.state === "error" ? errorKind === "timeout" ? "timeout" : "error" : event.yielded === true && stopReason === "end_turn" ? "yielded" : stopReason === "error" ? "error" : "completed",
|
|
1085
|
+
...stopReason === null ? {} : { stopReason },
|
|
1086
|
+
...errorKind === null ? {} : { errorKind },
|
|
1087
|
+
...typeof event.errorMessage === "string" ? { errorMessage: event.errorMessage } : {}
|
|
1088
|
+
});
|
|
1089
|
+
return {
|
|
1090
|
+
projection: next,
|
|
1091
|
+
previousRun: projection.runs[runId],
|
|
1092
|
+
currentRun: next.runs[runId]
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
//#endregion
|
|
1096
|
+
//#region packages/gateway-client/src/session-projection.ts
|
|
1097
|
+
/** Browser-safe identity and replay rules shared by Gateway conversation clients. */
|
|
1098
|
+
const MAX_TRACKED_SESSION_RUNS = 200;
|
|
1099
|
+
const RETAINED_SESSION_RUNS = 150;
|
|
1100
|
+
const SESSION_PROJECTION_SCOPE_KEYS = [
|
|
1101
|
+
"sessionKey",
|
|
1102
|
+
"sessionId",
|
|
1103
|
+
"agentId",
|
|
1104
|
+
"lifecycleRevision",
|
|
1105
|
+
"activeLeafEntryId"
|
|
1106
|
+
];
|
|
952
1107
|
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
953
1108
|
function isLocallyOptimisticSessionMessage(message) {
|
|
954
1109
|
const identity = readSessionMessageIdentity(message);
|
|
955
1110
|
if (!identity || identity.role !== "user" && identity.role !== "assistant") return false;
|
|
956
|
-
const metadata =
|
|
1111
|
+
const metadata = asNullableRecord(asNullableRecord(message)?.["__openclaw"]);
|
|
957
1112
|
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
958
1113
|
}
|
|
959
1114
|
function createEntry(message, options) {
|
|
@@ -1011,13 +1166,18 @@ function sameTranscriptIdentity(left, right) {
|
|
|
1011
1166
|
}
|
|
1012
1167
|
function entryMatches(left, right, allowSnapshotPromotion = false) {
|
|
1013
1168
|
if (sameTranscriptIdentity(left.identity, right.identity)) return true;
|
|
1169
|
+
const durableEntry = left.identity?.id ? left : right.identity?.id ? right : null;
|
|
1170
|
+
const provisionalEntry = durableEntry === left ? right : durableEntry === right ? left : null;
|
|
1171
|
+
const durableMetadata = asNullableRecord(asNullableRecord(durableEntry?.message)?.["__openclaw"]);
|
|
1172
|
+
if (durableEntry?.live && provisionalEntry?.live && durableEntry.identity?.role === "assistant" && provisionalEntry.identity?.role === "assistant" && !durableEntry.identity.isImported && !provisionalEntry.identity.isImported && !provisionalEntry.identity.id && durableEntry.identity.runId && durableEntry.identity.runId === provisionalEntry.identity.runId && (readSessionProjectionString(durableMetadata?.mirrorOrigin) === null || durableMetadata?.runTerminal === true)) return true;
|
|
1014
1173
|
const persisted = left.identity;
|
|
1015
1174
|
const observed = right.identity;
|
|
1016
|
-
|
|
1175
|
+
const persistedMetadata = asNullableRecord(asNullableRecord(left.message)?.["__openclaw"]);
|
|
1176
|
+
if (allowSnapshotPromotion && right.live && persisted && observed && persisted.role === observed.role && !persisted.isImported && !observed.isImported && persisted.id && !observed.id && (persisted.sequence !== null && persisted.sequence === observed.sequence || persisted.role === "assistant" && observed.sequence === null && persisted.runId !== null && persisted.runId === observed.runId && (readSessionProjectionString(persistedMetadata?.mirrorOrigin) === null || persistedMetadata?.runTerminal === true))) return true;
|
|
1017
1177
|
if (left.pending && right.pending) return Boolean(left.identity?.role === right.identity?.role && left.pendingRunId && left.pendingRunId === right.pendingRunId);
|
|
1018
1178
|
const pending = left.pending ? left : right.pending ? right : null;
|
|
1019
1179
|
const authoritative = pending === left ? right : pending === right ? left : null;
|
|
1020
|
-
return Boolean(pending && authoritative && pending.identity
|
|
1180
|
+
return Boolean(pending && authoritative && pending.identity && authoritative.identity && pending.identity.role === authoritative.identity.role && !pending.identity.isImported && !authoritative.identity.isImported && pending.pendingRunId && pending.pendingRunId === authoritative.identity.runId && (pending.identity.sequence === null || authoritative.identity.sequence === null || pending.identity.sequence === authoritative.identity.sequence));
|
|
1021
1181
|
}
|
|
1022
1182
|
function withEntries(state, entries) {
|
|
1023
1183
|
return {
|
|
@@ -1054,6 +1214,7 @@ function projectLiveSessionMessage(state, message, envelope, scope = {}) {
|
|
|
1054
1214
|
if (existingIndex < 0) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1055
1215
|
const existing = state.entries[existingIndex];
|
|
1056
1216
|
if (existing && existing.message === message && existing.live && !existing.pending) return state;
|
|
1217
|
+
if (existing && !existing.pending && existing.identity?.id && !incoming.identity.id) return state;
|
|
1057
1218
|
if (existing?.pending && incoming.identity.sequence !== null) {
|
|
1058
1219
|
const sequence = incoming.identity.sequence;
|
|
1059
1220
|
return withEntries(state, state.entries.some(({ identity }, index) => identity?.sequence != null && (index < existingIndex ? identity.sequence > sequence : identity.sequence < sequence)) ? insertEntry(state.entries.filter((_, index) => index !== existingIndex), incoming, state.runs) : state.entries.toSpliced(existingIndex, 1, incoming));
|
|
@@ -1084,13 +1245,13 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
|
|
|
1084
1245
|
}
|
|
1085
1246
|
function hasDisplayableSessionMessage(message) {
|
|
1086
1247
|
if (typeof message === "string") return message.trim().length > 0;
|
|
1087
|
-
const record =
|
|
1248
|
+
const record = asNullableRecord(message);
|
|
1088
1249
|
if (!record) return false;
|
|
1089
1250
|
const displayableBlocks = Array.isArray(record.content) && record.content.some((block) => {
|
|
1090
|
-
const entry =
|
|
1091
|
-
return entry ? entry.type !== "text" ||
|
|
1251
|
+
const entry = asNullableRecord(block);
|
|
1252
|
+
return entry ? entry.type !== "text" || readSessionProjectionString(entry.text) !== null : typeof block === "string" && block.trim().length > 0;
|
|
1092
1253
|
});
|
|
1093
|
-
const media =
|
|
1254
|
+
const media = asNullableRecord(record["__openclaw"])?.media;
|
|
1094
1255
|
return Boolean(typeof record.content === "string" && record.content.trim() || displayableBlocks || Array.isArray(media) && media.length > 0);
|
|
1095
1256
|
}
|
|
1096
1257
|
function readSessionProjectionFinalMessageIdentity(message) {
|
|
@@ -1099,8 +1260,8 @@ function readSessionProjectionFinalMessageIdentity(message) {
|
|
|
1099
1260
|
if (identity?.externalSource) return `import:${identity.role}:${identity.externalSource}`;
|
|
1100
1261
|
if (identity?.id && !identity.isImported) return `id:${identity.role}:${identity.id}`;
|
|
1101
1262
|
if (identity?.sequence !== null && identity?.sequence !== void 0) return `seq:${identity.role}:${identity.sequence}`;
|
|
1102
|
-
const record =
|
|
1103
|
-
const metadata =
|
|
1263
|
+
const record = asNullableRecord(message);
|
|
1264
|
+
const metadata = asNullableRecord(record?.["__openclaw"]);
|
|
1104
1265
|
try {
|
|
1105
1266
|
return `content:${JSON.stringify([
|
|
1106
1267
|
identity?.role ?? "assistant",
|
|
@@ -1131,7 +1292,7 @@ function retainSessionProjectionRuns(runs) {
|
|
|
1131
1292
|
return Object.fromEntries([...active, ...retainedTerminal]);
|
|
1132
1293
|
}
|
|
1133
1294
|
function updateRun(state, incoming) {
|
|
1134
|
-
const incomingErrorMessage =
|
|
1295
|
+
const incomingErrorMessage = readSessionProjectionString(incoming.errorMessage);
|
|
1135
1296
|
const normalizedIncoming = { ...incoming };
|
|
1136
1297
|
if (incomingErrorMessage) normalizedIncoming.errorMessage = incomingErrorMessage;
|
|
1137
1298
|
else delete normalizedIncoming.errorMessage;
|
|
@@ -1142,7 +1303,7 @@ function updateRun(state, incoming) {
|
|
|
1142
1303
|
const canRecoverFinal = !hasDisplayableSessionMessage(current.message) || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0;
|
|
1143
1304
|
const acceptFinal = incomingIsFinal && (current.status === incoming.status || canRecoverFinal) && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
|
|
1144
1305
|
const recoverMessage = acceptFinal && !hasDisplayableSessionMessage(current.message);
|
|
1145
|
-
const recoverError =
|
|
1306
|
+
const recoverError = readSessionProjectionString(current.errorMessage) === null && incomingErrorMessage !== null;
|
|
1146
1307
|
if (!acceptFinal && !recoverError) return state;
|
|
1147
1308
|
const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
|
|
1148
1309
|
const previousFinalIdentities = current.acceptedFinalMessageIdentities ?? (firstFinalIdentity ? [firstFinalIdentity] : []);
|
|
@@ -1199,7 +1360,13 @@ function reduceSessionProjection(state, event) {
|
|
|
1199
1360
|
const pendingRunId = normalizeSessionProjectionRunId(event.idempotencyKey ?? event.runId);
|
|
1200
1361
|
const incoming = createEntry(event.message, { pendingRunId });
|
|
1201
1362
|
if (!pendingRunId || !incoming.identity) return state;
|
|
1202
|
-
|
|
1363
|
+
const seed = state.entries.find((entry) => entry.message === event.message);
|
|
1364
|
+
if (seed && !seed.pending && incoming.identity.id === null && !incoming.identity.isImported && incoming.identity.runId === pendingRunId) return withEntries(state, state.entries.map((entry) => entry === seed ? {
|
|
1365
|
+
...seed,
|
|
1366
|
+
pending: true,
|
|
1367
|
+
pendingRunId
|
|
1368
|
+
} : entry));
|
|
1369
|
+
return seed || state.entries.some((entry) => entryMatches(entry, incoming)) ? state : withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1203
1370
|
}
|
|
1204
1371
|
case "sendAcknowledged": {
|
|
1205
1372
|
const runId = normalizeSessionProjectionRunId(event.idempotencyKey ?? event.runId);
|
|
@@ -1245,38 +1412,7 @@ function reduceSessionProjection(state, event) {
|
|
|
1245
1412
|
}
|
|
1246
1413
|
/** Normalizes Gateway run envelopes once for every browser and terminal adapter. */
|
|
1247
1414
|
function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
|
|
1248
|
-
|
|
1249
|
-
const eventState = event.state;
|
|
1250
|
-
if (!runId || typeof eventState !== "string" || ![
|
|
1251
|
-
"delta",
|
|
1252
|
-
"final",
|
|
1253
|
-
"error",
|
|
1254
|
-
"aborted"
|
|
1255
|
-
].includes(eventState)) return null;
|
|
1256
|
-
const message = event.message;
|
|
1257
|
-
const stopReason = readNonemptyString(event.stopReason) ?? readNonemptyString(readRecord(message)?.stopReason);
|
|
1258
|
-
const errorKind = readNonemptyString(event.errorKind);
|
|
1259
|
-
const base = {
|
|
1260
|
-
runId,
|
|
1261
|
-
...message === void 0 ? {} : { message },
|
|
1262
|
-
scope
|
|
1263
|
-
};
|
|
1264
|
-
const next = reduceSessionProjection(projection, eventState === "delta" ? {
|
|
1265
|
-
type: "runDelta",
|
|
1266
|
-
...base
|
|
1267
|
-
} : {
|
|
1268
|
-
type: "runTerminal",
|
|
1269
|
-
...base,
|
|
1270
|
-
status: eventState === "aborted" ? "aborted" : eventState === "error" ? errorKind === "timeout" ? "timeout" : "error" : event.yielded === true && stopReason === "end_turn" ? "yielded" : stopReason === "error" ? "error" : "completed",
|
|
1271
|
-
...stopReason === null ? {} : { stopReason },
|
|
1272
|
-
...errorKind === null ? {} : { errorKind },
|
|
1273
|
-
...typeof event.errorMessage === "string" ? { errorMessage: event.errorMessage } : {}
|
|
1274
|
-
});
|
|
1275
|
-
return {
|
|
1276
|
-
projection: next,
|
|
1277
|
-
previousRun: projection.runs[runId],
|
|
1278
|
-
currentRun: next.runs[runId]
|
|
1279
|
-
};
|
|
1415
|
+
return reduceSessionProjectionRunEventImpl(projection, event, scope);
|
|
1280
1416
|
}
|
|
1281
1417
|
//#endregion
|
|
1282
1418
|
//#region packages/gateway-client/src/session-subscriptions.ts
|
|
@@ -1371,7 +1507,7 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1371
1507
|
entry.release = tracked;
|
|
1372
1508
|
return tracked;
|
|
1373
1509
|
}
|
|
1374
|
-
const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId)).then(() => {
|
|
1510
|
+
const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId), { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).then(() => {
|
|
1375
1511
|
this.#finishRelease(subscription, owner, true);
|
|
1376
1512
|
}).finally(() => {
|
|
1377
1513
|
if (entry.release === tracked) entry.release = null;
|
|
@@ -1446,9 +1582,18 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1446
1582
|
return upgrade;
|
|
1447
1583
|
}
|
|
1448
1584
|
async #requestSubscribe(entry, includeApprovals) {
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
...
|
|
1585
|
+
const params = sessionSubscriptionParams(entry.key, entry.agentId);
|
|
1586
|
+
const result = await this.#client.request("sessions.messages.subscribe", includeApprovals ? {
|
|
1587
|
+
...params,
|
|
1588
|
+
includeApprovals: true
|
|
1589
|
+
} : params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).catch(async (error) => {
|
|
1590
|
+
if (!(error instanceof GatewayProtocolRequestTimeoutError) || !error.requestSent || this.#retired) throw error;
|
|
1591
|
+
try {
|
|
1592
|
+
await this.#client.request(entry.handles.size > 0 ? "sessions.messages.subscribe" : "sessions.messages.unsubscribe", params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS });
|
|
1593
|
+
} catch (recoveryError) {
|
|
1594
|
+
if (!this.#retired) throw new AggregateError([error, recoveryError], "session message subscription recovery failed", { cause: recoveryError });
|
|
1595
|
+
}
|
|
1596
|
+
throw error;
|
|
1452
1597
|
});
|
|
1453
1598
|
const response = result && typeof result === "object" ? result : null;
|
|
1454
1599
|
const responseKey = response && "key" in response ? response.key : void 0;
|
|
@@ -1484,4 +1629,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
|
|
|
1484
1629
|
return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
|
|
1485
1630
|
}
|
|
1486
1631
|
//#endregion
|
|
1487
|
-
export {
|
|
1632
|
+
export { resolveGatewayConnectScopes as C, buildDeviceAuthPayloadV3 as D, buildDeviceAuthPayload as E, normalizeDeviceMetadataForAuth as O, buildGatewayConnectAuth as S, shouldRetryGatewayWithDeviceToken as T, GatewayProtocolRequestError as _, createSessionProjection as a, gatewayOriginScope as b, projectLiveSessionMessage as c, reduceSessionProjectionRunEvent as d, normalizeSessionProjectionRunId as f, GatewayProtocolClient as g, shouldPauseGatewayReconnect as h, resetGatewaySessionMessageSubscriptionCoordinator as i, reconcileSessionProjectionSnapshot as l, readSessionMessageSequence as m, getGatewaySessionMessageSubscriptionCoordinator as n, hasSessionProjectionAcceptedFinal as o, readSessionMessageIdentity as p, releaseGatewaySessionMessageSubscription as r, isLocallyOptimisticSessionMessage as s, GatewaySessionMessageSubscriptionCoordinator as t, reduceSessionProjection as u, GatewayProtocolRequestTimeoutError as v, selectGatewayConnectAuth as w, GatewayBrowserDeviceAuthLifecycle as x, gatewayCredentialScope as y };
|