@openclaw/gateway-client 2026.7.2-beta.4 → 2026.7.2-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.mts +4 -4
- package/dist/browser.mjs +3 -3
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +8 -6
- package/dist/{protocol-client-Cj1WAoMt.d.mts → protocol-client-BfBHwA5H.d.mts} +3 -1
- package/dist/{readiness-CPiKyi1s.d.mts → readiness-BJT_oUZE.d.mts} +1 -1
- package/dist/readiness.d.mts +1 -1
- package/dist/{reconnect-policy-CkzLe1_K.mjs → session-subscriptions-BV7MxYiL.mjs} +638 -12
- package/dist/session-subscriptions-DrhzqL6v.d.mts +282 -0
- package/dist/timeouts.d.mts +5 -1
- package/dist/timeouts.mjs +12 -1
- package/package.json +2 -3
- package/dist/browser-device-auth-Bp297Rtf.d.mts +0 -130
- package/npm-shrinkwrap.json +0 -50
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearGatewayConnectTimeout, startGatewayConnectTimeout } from "./timeouts.mjs";
|
|
1
2
|
import { ConnectErrorDetailCodes, readConnectErrorDetailCode, readConnectErrorRecoveryAdvice, readPairingConnectErrorDetails } from "@openclaw/gateway-protocol/connect-error-details";
|
|
2
3
|
import { isGatewayEventFrame, isGatewayResponseFrame } from "@openclaw/gateway-protocol/frame-guards";
|
|
3
4
|
//#region packages/gateway-client/src/device-auth.ts
|
|
@@ -381,6 +382,27 @@ function createRetryRunner(runtime = {}) {
|
|
|
381
382
|
}
|
|
382
383
|
createRetryRunner();
|
|
383
384
|
//#endregion
|
|
385
|
+
//#region packages/gateway-client/src/event-listeners.ts
|
|
386
|
+
/** Subscription identity prevents old frames and disposers from reviving callbacks. */
|
|
387
|
+
var GatewayEventListeners = class {
|
|
388
|
+
constructor() {
|
|
389
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
390
|
+
}
|
|
391
|
+
add(listener) {
|
|
392
|
+
const subscription = this.listeners.get(listener) ?? {};
|
|
393
|
+
this.listeners.set(listener, subscription);
|
|
394
|
+
return () => {
|
|
395
|
+
if (this.listeners.get(listener) === subscription) this.listeners.delete(listener);
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
snapshot() {
|
|
399
|
+
return [...this.listeners];
|
|
400
|
+
}
|
|
401
|
+
isCurrent(listener, subscription) {
|
|
402
|
+
return this.listeners.get(listener) === subscription;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
//#endregion
|
|
384
406
|
//#region packages/gateway-client/src/protocol-client.ts
|
|
385
407
|
var GatewayProtocolRequestError = class extends Error {
|
|
386
408
|
constructor(error) {
|
|
@@ -402,7 +424,7 @@ var GatewayProtocolClient = class {
|
|
|
402
424
|
this.opts = opts;
|
|
403
425
|
this.socket = null;
|
|
404
426
|
this.pending = /* @__PURE__ */ new Map();
|
|
405
|
-
this.listeners =
|
|
427
|
+
this.listeners = new GatewayEventListeners();
|
|
406
428
|
this.stopped = true;
|
|
407
429
|
this.generation = 0;
|
|
408
430
|
this.lastSeq = null;
|
|
@@ -410,6 +432,7 @@ var GatewayProtocolClient = class {
|
|
|
410
432
|
this.connectSent = false;
|
|
411
433
|
this.connectRequestSent = false;
|
|
412
434
|
this.handshakeTimer = null;
|
|
435
|
+
this.reconnectSignal = null;
|
|
413
436
|
this.socketOpened = false;
|
|
414
437
|
this.helloReceived = false;
|
|
415
438
|
this.connectTiming = null;
|
|
@@ -433,6 +456,7 @@ var GatewayProtocolClient = class {
|
|
|
433
456
|
return [...this.pending.values()].some((pending) => pending.unbounded);
|
|
434
457
|
}
|
|
435
458
|
start() {
|
|
459
|
+
if (this.socket || this.reconnectSignal) return;
|
|
436
460
|
this.stopped = false;
|
|
437
461
|
this.reconnectSupervisor.cancel();
|
|
438
462
|
this.connect();
|
|
@@ -440,6 +464,7 @@ var GatewayProtocolClient = class {
|
|
|
440
464
|
stop() {
|
|
441
465
|
this.stopped = true;
|
|
442
466
|
this.clearHandshakeTimer();
|
|
467
|
+
this.reconnectSignal = null;
|
|
443
468
|
this.reconnectSupervisor.reset();
|
|
444
469
|
const socket = this.socket;
|
|
445
470
|
if (socket && this.opts.notifyStoppedClose) this.stoppedSocket = {
|
|
@@ -513,13 +538,13 @@ var GatewayProtocolClient = class {
|
|
|
513
538
|
});
|
|
514
539
|
}
|
|
515
540
|
addEventListener(listener) {
|
|
516
|
-
this.listeners.add(listener);
|
|
517
|
-
return () => this.listeners.delete(listener);
|
|
541
|
+
return this.listeners.add(listener);
|
|
518
542
|
}
|
|
519
543
|
closeSocket(code, reason) {
|
|
520
544
|
this.socket?.close(code, reason);
|
|
521
545
|
}
|
|
522
546
|
resetReconnectBackoff(initialMs) {
|
|
547
|
+
this.reconnectSignal = null;
|
|
523
548
|
this.reconnectSupervisor.reset(initialMs);
|
|
524
549
|
}
|
|
525
550
|
recordTiming(phase, generation, plan, detail) {
|
|
@@ -544,9 +569,9 @@ var GatewayProtocolClient = class {
|
|
|
544
569
|
connect() {
|
|
545
570
|
if (this.stopped) return;
|
|
546
571
|
const generation = this.generation + 1;
|
|
572
|
+
this.lastSeq = null;
|
|
547
573
|
this.connectNonce = null;
|
|
548
|
-
this.connectSent = false;
|
|
549
|
-
this.connectRequestSent = false;
|
|
574
|
+
this.connectSent = this.connectRequestSent = false;
|
|
550
575
|
this.socketOpened = false;
|
|
551
576
|
this.helloReceived = false;
|
|
552
577
|
this.connectFailure = void 0;
|
|
@@ -563,6 +588,7 @@ var GatewayProtocolClient = class {
|
|
|
563
588
|
this.opts.onSocketFactoryError?.(normalized);
|
|
564
589
|
this.opts.onConnectError?.(normalized);
|
|
565
590
|
if (this.opts.rethrowSocketFactoryError?.(normalized)) throw normalized;
|
|
591
|
+
if (this.opts.shouldRetrySocketFactoryError?.(normalized) && !this.stopped && !this.socket && !this.reconnectSignal) this.scheduleReconnect();
|
|
566
592
|
return;
|
|
567
593
|
}
|
|
568
594
|
this.generation = generation;
|
|
@@ -608,6 +634,9 @@ var GatewayProtocolClient = class {
|
|
|
608
634
|
if (!this.isActive(socket, generation) || !socket.isOpen() || this.connectSent) return;
|
|
609
635
|
this.connectSent = true;
|
|
610
636
|
this.clearHandshakeTimer();
|
|
637
|
+
this.handshakeTimer = startGatewayConnectTimeout(() => {
|
|
638
|
+
if (this.isActive(socket, generation) && !this.helloReceived) socket.close(4e3, "connect timeout");
|
|
639
|
+
});
|
|
611
640
|
let planOrPromise;
|
|
612
641
|
try {
|
|
613
642
|
planOrPromise = this.opts.buildConnectPlan({
|
|
@@ -648,6 +677,7 @@ var GatewayProtocolClient = class {
|
|
|
648
677
|
this.request("connect", this.opts.buildConnectParams(plan)).then((hello) => {
|
|
649
678
|
if (!this.isActive(socket, generation)) return;
|
|
650
679
|
this.helloReceived = true;
|
|
680
|
+
this.clearHandshakeTimer();
|
|
651
681
|
this.connectFailure = void 0;
|
|
652
682
|
this.reconnectSupervisor.reset();
|
|
653
683
|
this.recordTiming("hello", generation, plan);
|
|
@@ -703,11 +733,16 @@ var GatewayProtocolClient = class {
|
|
|
703
733
|
expected,
|
|
704
734
|
received: seq
|
|
705
735
|
}));
|
|
736
|
+
if (!this.isActive(socket, generation)) return;
|
|
706
737
|
}
|
|
707
738
|
this.lastSeq = seq;
|
|
708
739
|
}
|
|
740
|
+
const listeners = this.listeners.snapshot();
|
|
709
741
|
this.invoke("event", () => this.opts.onEvent?.(parsed));
|
|
710
|
-
for (const listener of
|
|
742
|
+
for (const [listener, subscription] of listeners) {
|
|
743
|
+
if (!this.isActive(socket, generation)) return;
|
|
744
|
+
if (this.listeners.isCurrent(listener, subscription)) this.invoke("event listener", () => listener(parsed));
|
|
745
|
+
}
|
|
711
746
|
return;
|
|
712
747
|
}
|
|
713
748
|
if (!isGatewayResponseFrame(parsed)) return;
|
|
@@ -793,7 +828,14 @@ var GatewayProtocolClient = class {
|
|
|
793
828
|
if (overrideMs !== void 0) this.reconnectSupervisor.nextDelayOverrideMs = overrideMs;
|
|
794
829
|
const retry = this.reconnectSupervisor.next();
|
|
795
830
|
if (!retry) return;
|
|
796
|
-
|
|
831
|
+
this.reconnectSignal = retry.signal;
|
|
832
|
+
sleepWithAbort(retry.delayMs, retry.signal).then(() => {
|
|
833
|
+
if (this.reconnectSignal !== retry.signal) return;
|
|
834
|
+
this.reconnectSignal = null;
|
|
835
|
+
this.connect();
|
|
836
|
+
}, () => {
|
|
837
|
+
if (this.reconnectSignal === retry.signal) this.reconnectSignal = null;
|
|
838
|
+
});
|
|
797
839
|
}
|
|
798
840
|
closeContext() {
|
|
799
841
|
return {
|
|
@@ -811,10 +853,7 @@ var GatewayProtocolClient = class {
|
|
|
811
853
|
return this.opts.nowMs?.() ?? Date.now();
|
|
812
854
|
}
|
|
813
855
|
clearHandshakeTimer() {
|
|
814
|
-
|
|
815
|
-
clearTimeout(this.handshakeTimer);
|
|
816
|
-
this.handshakeTimer = null;
|
|
817
|
-
}
|
|
856
|
+
this.handshakeTimer = clearGatewayConnectTimeout(this.handshakeTimer);
|
|
818
857
|
}
|
|
819
858
|
invoke(label, callback) {
|
|
820
859
|
try {
|
|
@@ -847,4 +886,591 @@ function shouldPauseGatewayReconnect(params) {
|
|
|
847
886
|
return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
|
|
848
887
|
}
|
|
849
888
|
//#endregion
|
|
850
|
-
|
|
889
|
+
//#region packages/gateway-client/src/session-projection.ts
|
|
890
|
+
const MAX_TRACKED_SESSION_RUNS = 200;
|
|
891
|
+
const RETAINED_SESSION_RUNS = 150;
|
|
892
|
+
const SESSION_PROJECTION_SCOPE_KEYS = [
|
|
893
|
+
"sessionKey",
|
|
894
|
+
"sessionId",
|
|
895
|
+
"agentId",
|
|
896
|
+
"lifecycleRevision",
|
|
897
|
+
"activeLeafEntryId"
|
|
898
|
+
];
|
|
899
|
+
function readRecord(value) {
|
|
900
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
901
|
+
}
|
|
902
|
+
function readNonemptyString(value) {
|
|
903
|
+
return typeof value === "string" ? value.trim() || null : null;
|
|
904
|
+
}
|
|
905
|
+
function readPositiveSafeInteger(value) {
|
|
906
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
907
|
+
}
|
|
908
|
+
/** History and status markers carry transcript order even when they have no chat role. */
|
|
909
|
+
function readSessionMessageSequence(message, envelope) {
|
|
910
|
+
return readPositiveSafeInteger(readRecord(readRecord(message)?.["__openclaw"])?.seq) ?? readPositiveSafeInteger(envelope?.messageSeq);
|
|
911
|
+
}
|
|
912
|
+
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
913
|
+
function normalizeSessionProjectionRunId(value) {
|
|
914
|
+
const runId = readNonemptyString(value);
|
|
915
|
+
return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
|
|
916
|
+
}
|
|
917
|
+
/** Persisted transcript facts win over envelope projections and provider-local import IDs. */
|
|
918
|
+
function readSessionMessageIdentity(message, envelope) {
|
|
919
|
+
const record = readRecord(message);
|
|
920
|
+
const role = readNonemptyString(record?.role)?.toLowerCase();
|
|
921
|
+
if (!record || !role) return null;
|
|
922
|
+
const metadata = readRecord(record["__openclaw"]);
|
|
923
|
+
const importedFrom = readNonemptyString(metadata?.importedFrom);
|
|
924
|
+
const cliSessionId = readNonemptyString(metadata?.cliSessionId);
|
|
925
|
+
const externalId = readNonemptyString(metadata?.externalId);
|
|
926
|
+
const idempotencyKey = readNonemptyString(metadata?.idempotencyKey) ?? readNonemptyString(record.idempotencyKey) ?? readNonemptyString(envelope?.idempotencyKey) ?? readNonemptyString(envelope?.clientRunId);
|
|
927
|
+
return {
|
|
928
|
+
role,
|
|
929
|
+
id: readNonemptyString(metadata?.id) ?? readNonemptyString(envelope?.messageId),
|
|
930
|
+
sequence: readSessionMessageSequence(message, envelope),
|
|
931
|
+
idempotencyKey,
|
|
932
|
+
runId: normalizeSessionProjectionRunId(idempotencyKey) ?? normalizeSessionProjectionRunId(envelope?.runId),
|
|
933
|
+
isImported: Boolean(importedFrom || cliSessionId || externalId),
|
|
934
|
+
externalSource: importedFrom && cliSessionId && externalId ? JSON.stringify([
|
|
935
|
+
importedFrom,
|
|
936
|
+
cliSessionId,
|
|
937
|
+
externalId
|
|
938
|
+
]) : null
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
942
|
+
function isLocallyOptimisticSessionMessage(message) {
|
|
943
|
+
const identity = readSessionMessageIdentity(message);
|
|
944
|
+
if (!identity || identity.role !== "user" && identity.role !== "assistant") return false;
|
|
945
|
+
const metadata = readRecord(readRecord(message)?.["__openclaw"]);
|
|
946
|
+
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
947
|
+
}
|
|
948
|
+
function createEntry(message, options) {
|
|
949
|
+
const identity = readSessionMessageIdentity(message, options?.envelope);
|
|
950
|
+
const inferredPendingRunId = options?.live !== true && isLocallyOptimisticSessionMessage(message) ? identity?.runId : null;
|
|
951
|
+
const pendingRunId = normalizeSessionProjectionRunId(options?.pendingRunId ?? inferredPendingRunId);
|
|
952
|
+
return {
|
|
953
|
+
message,
|
|
954
|
+
identity,
|
|
955
|
+
live: options?.live === true,
|
|
956
|
+
pending: pendingRunId !== null,
|
|
957
|
+
pendingRunId
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
function createProjectionEntries(messages) {
|
|
961
|
+
let pendingUserRunId = null;
|
|
962
|
+
return messages.map((message) => {
|
|
963
|
+
const entry = createEntry(message);
|
|
964
|
+
if (entry.identity?.role === "user") {
|
|
965
|
+
pendingUserRunId = entry.pending ? entry.pendingRunId : null;
|
|
966
|
+
return entry;
|
|
967
|
+
}
|
|
968
|
+
if (pendingUserRunId && entry.identity?.role === "assistant" && !entry.pending && isLocallyOptimisticSessionMessage(message)) return createEntry(message, { pendingRunId: pendingUserRunId });
|
|
969
|
+
if (!isLocallyOptimisticSessionMessage(message)) pendingUserRunId = null;
|
|
970
|
+
return entry;
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function createSessionProjection(scope = {}, messages = []) {
|
|
974
|
+
const entries = createProjectionEntries(messages);
|
|
975
|
+
return {
|
|
976
|
+
scope: { ...scope },
|
|
977
|
+
entries,
|
|
978
|
+
messages: entries.map((entry) => entry.message),
|
|
979
|
+
runs: {},
|
|
980
|
+
hasTransportGap: false
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function scopesMatch(left, right) {
|
|
984
|
+
return SESSION_PROJECTION_SCOPE_KEYS.every((key) => left[key] === void 0 || right[key] === void 0 || left[key] === right[key]);
|
|
985
|
+
}
|
|
986
|
+
function readEventScope(event) {
|
|
987
|
+
const scope = { ...event.scope };
|
|
988
|
+
for (const key of SESSION_PROJECTION_SCOPE_KEYS) if (event[key] !== void 0) Object.assign(scope, { [key]: event[key] });
|
|
989
|
+
return scope;
|
|
990
|
+
}
|
|
991
|
+
function sameTranscriptIdentity(left, right) {
|
|
992
|
+
if (!left || !right || left.role !== right.role) return false;
|
|
993
|
+
if (left.isImported || right.isImported) {
|
|
994
|
+
if (!left.isImported || !right.isImported) return false;
|
|
995
|
+
if (left.externalSource || right.externalSource) return Boolean(left.externalSource && left.externalSource === right.externalSource);
|
|
996
|
+
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
997
|
+
}
|
|
998
|
+
if (left.id || right.id) return Boolean(left.id && right.id && left.id === right.id);
|
|
999
|
+
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
1000
|
+
}
|
|
1001
|
+
function entryMatches(left, right, allowSnapshotPromotion = false) {
|
|
1002
|
+
if (sameTranscriptIdentity(left.identity, right.identity)) return true;
|
|
1003
|
+
const persisted = left.identity;
|
|
1004
|
+
const observed = right.identity;
|
|
1005
|
+
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)) return true;
|
|
1006
|
+
if (left.pending && right.pending) return Boolean(left.identity?.role === right.identity?.role && left.pendingRunId && left.pendingRunId === right.pendingRunId);
|
|
1007
|
+
const pending = left.pending ? left : right.pending ? right : null;
|
|
1008
|
+
const authoritative = pending === left ? right : pending === right ? left : null;
|
|
1009
|
+
return Boolean(pending && authoritative && pending.identity?.role === authoritative.identity?.role && !pending.identity?.isImported && !authoritative.identity?.isImported && pending.pendingRunId && pending.pendingRunId === authoritative.identity?.runId);
|
|
1010
|
+
}
|
|
1011
|
+
function withEntries(state, entries) {
|
|
1012
|
+
return {
|
|
1013
|
+
...state,
|
|
1014
|
+
entries,
|
|
1015
|
+
messages: entries.map((entry) => entry.message)
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
function insertEntry(entries, incoming, runs) {
|
|
1019
|
+
const sequence = incoming.identity?.sequence;
|
|
1020
|
+
let nextIndex = sequence === void 0 || sequence === null ? -1 : entries.findIndex((entry) => {
|
|
1021
|
+
const candidate = entry.identity?.sequence;
|
|
1022
|
+
return candidate !== void 0 && candidate !== null && candidate > sequence;
|
|
1023
|
+
});
|
|
1024
|
+
if (nextIndex < 0 && incoming.identity?.role === "user" && incoming.identity.runId) {
|
|
1025
|
+
const runId = incoming.identity.runId;
|
|
1026
|
+
const terminalMessage = runs?.[runId]?.message;
|
|
1027
|
+
nextIndex = entries.findIndex((entry) => entry.identity?.role === "assistant" && (entry.identity.runId === runId || entry.message === terminalMessage));
|
|
1028
|
+
}
|
|
1029
|
+
return nextIndex < 0 ? [...entries, incoming] : [
|
|
1030
|
+
...entries.slice(0, nextIndex),
|
|
1031
|
+
incoming,
|
|
1032
|
+
...entries.slice(nextIndex)
|
|
1033
|
+
];
|
|
1034
|
+
}
|
|
1035
|
+
function projectLiveSessionMessage(state, message, envelope, scope = {}) {
|
|
1036
|
+
if (!scopesMatch(state.scope, scope)) return state;
|
|
1037
|
+
const incoming = createEntry(message, {
|
|
1038
|
+
envelope,
|
|
1039
|
+
live: true
|
|
1040
|
+
});
|
|
1041
|
+
if (!incoming.identity) return state;
|
|
1042
|
+
const existingIndex = state.entries.findIndex((entry) => entryMatches(entry, incoming));
|
|
1043
|
+
if (existingIndex < 0) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1044
|
+
const existing = state.entries[existingIndex];
|
|
1045
|
+
if (existing && existing.message === message && existing.live && !existing.pending) return state;
|
|
1046
|
+
if (existing?.pending && incoming.identity.sequence !== null) {
|
|
1047
|
+
const sequence = incoming.identity.sequence;
|
|
1048
|
+
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));
|
|
1049
|
+
}
|
|
1050
|
+
return withEntries(state, [
|
|
1051
|
+
...state.entries.slice(0, existingIndex),
|
|
1052
|
+
incoming,
|
|
1053
|
+
...state.entries.slice(existingIndex + 1)
|
|
1054
|
+
]);
|
|
1055
|
+
}
|
|
1056
|
+
/** Only observed live events and this client's pending turns may survive an older snapshot. */
|
|
1057
|
+
function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options = {}) {
|
|
1058
|
+
const visibleMessages = options.shouldIncludeMessage ? messages.filter(options.shouldIncludeMessage) : messages;
|
|
1059
|
+
if (!scopesMatch(state.scope, scope)) return createSessionProjection(scope, visibleMessages);
|
|
1060
|
+
let entries = createProjectionEntries(visibleMessages);
|
|
1061
|
+
for (const current of state.entries) {
|
|
1062
|
+
if (!current.live && !current.pending || options.shouldIncludeMessage?.(current.message) === false || entries.filter((entry) => entryMatches(entry, current, true)).length === 1) continue;
|
|
1063
|
+
entries = insertEntry(entries, current, state.runs);
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
...withEntries(state, entries),
|
|
1067
|
+
scope: {
|
|
1068
|
+
...state.scope,
|
|
1069
|
+
...scope
|
|
1070
|
+
},
|
|
1071
|
+
hasTransportGap: false
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function hasDisplayableSessionMessage(message) {
|
|
1075
|
+
if (typeof message === "string") return message.trim().length > 0;
|
|
1076
|
+
const record = readRecord(message);
|
|
1077
|
+
if (!record) return false;
|
|
1078
|
+
const displayableBlocks = Array.isArray(record.content) && record.content.some((block) => {
|
|
1079
|
+
const entry = readRecord(block);
|
|
1080
|
+
return entry ? entry.type !== "text" || readNonemptyString(entry.text) !== null : typeof block === "string" && block.trim().length > 0;
|
|
1081
|
+
});
|
|
1082
|
+
const media = readRecord(record["__openclaw"])?.media;
|
|
1083
|
+
return Boolean(typeof record.content === "string" && record.content.trim() || displayableBlocks || Array.isArray(media) && media.length > 0);
|
|
1084
|
+
}
|
|
1085
|
+
function readSessionProjectionFinalMessageIdentity(message) {
|
|
1086
|
+
if (!hasDisplayableSessionMessage(message)) return null;
|
|
1087
|
+
const identity = readSessionMessageIdentity(message);
|
|
1088
|
+
if (identity?.externalSource) return `import:${identity.role}:${identity.externalSource}`;
|
|
1089
|
+
if (identity?.id && !identity.isImported) return `id:${identity.role}:${identity.id}`;
|
|
1090
|
+
if (identity?.sequence !== null && identity?.sequence !== void 0) return `seq:${identity.role}:${identity.sequence}`;
|
|
1091
|
+
const record = readRecord(message);
|
|
1092
|
+
const metadata = readRecord(record?.["__openclaw"]);
|
|
1093
|
+
try {
|
|
1094
|
+
return `content:${JSON.stringify([
|
|
1095
|
+
identity?.role ?? "assistant",
|
|
1096
|
+
typeof message === "string" ? message : record?.content ?? null,
|
|
1097
|
+
metadata?.media ?? null,
|
|
1098
|
+
identity?.isImported ? [
|
|
1099
|
+
metadata?.importedFrom ?? null,
|
|
1100
|
+
metadata?.cliSessionId ?? null,
|
|
1101
|
+
metadata?.externalId ?? null
|
|
1102
|
+
] : null
|
|
1103
|
+
])}`;
|
|
1104
|
+
} catch {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/** Replayed finals are recognized against this run's bounded canonical terminal history. */
|
|
1109
|
+
function hasSessionProjectionAcceptedFinal(run, message) {
|
|
1110
|
+
const identity = readSessionProjectionFinalMessageIdentity(message);
|
|
1111
|
+
return Boolean(identity && run && (run.acceptedFinalMessageIdentities?.includes(identity) || readSessionProjectionFinalMessageIdentity(run.message) === identity));
|
|
1112
|
+
}
|
|
1113
|
+
function retainSessionProjectionRuns(runs) {
|
|
1114
|
+
const entries = Object.entries(runs);
|
|
1115
|
+
if (entries.length <= MAX_TRACKED_SESSION_RUNS) return runs;
|
|
1116
|
+
const active = entries.filter(([, run]) => run.status === "streaming");
|
|
1117
|
+
const terminal = entries.filter(([, run]) => run.status !== "streaming");
|
|
1118
|
+
const terminalLimit = Math.max(0, RETAINED_SESSION_RUNS - active.length);
|
|
1119
|
+
const retainedTerminal = terminalLimit > 0 ? terminal.slice(-terminalLimit) : [];
|
|
1120
|
+
return Object.fromEntries([...active, ...retainedTerminal]);
|
|
1121
|
+
}
|
|
1122
|
+
function updateRun(state, incoming) {
|
|
1123
|
+
const incomingErrorMessage = readNonemptyString(incoming.errorMessage);
|
|
1124
|
+
const normalizedIncoming = { ...incoming };
|
|
1125
|
+
if (incomingErrorMessage) normalizedIncoming.errorMessage = incomingErrorMessage;
|
|
1126
|
+
else delete normalizedIncoming.errorMessage;
|
|
1127
|
+
const current = state.runs[incoming.runId];
|
|
1128
|
+
if (current && current.status !== "streaming") {
|
|
1129
|
+
const incomingFinalIdentity = readSessionProjectionFinalMessageIdentity(incoming.message);
|
|
1130
|
+
const incomingIsFinal = incoming.status === "completed" || incoming.status === "yielded";
|
|
1131
|
+
const canRecoverFinal = !hasDisplayableSessionMessage(current.message) || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0;
|
|
1132
|
+
const acceptFinal = incomingIsFinal && (current.status === incoming.status || canRecoverFinal) && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
|
|
1133
|
+
const recoverMessage = acceptFinal && !hasDisplayableSessionMessage(current.message);
|
|
1134
|
+
const recoverError = readNonemptyString(current.errorMessage) === null && incomingErrorMessage !== null;
|
|
1135
|
+
if (!acceptFinal && !recoverError) return state;
|
|
1136
|
+
const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
|
|
1137
|
+
const previousFinalIdentities = current.acceptedFinalMessageIdentities ?? (firstFinalIdentity ? [firstFinalIdentity] : []);
|
|
1138
|
+
return {
|
|
1139
|
+
...state,
|
|
1140
|
+
runs: {
|
|
1141
|
+
...state.runs,
|
|
1142
|
+
[incoming.runId]: {
|
|
1143
|
+
...current,
|
|
1144
|
+
...recoverMessage ? { message: incoming.message } : {},
|
|
1145
|
+
...acceptFinal && incomingFinalIdentity ? { acceptedFinalMessageIdentities: [...previousFinalIdentities, incomingFinalIdentity].slice(-32) } : {},
|
|
1146
|
+
...recoverError && incomingErrorMessage ? {
|
|
1147
|
+
errorMessage: incomingErrorMessage,
|
|
1148
|
+
...incoming.errorKind ? { errorKind: incoming.errorKind } : {}
|
|
1149
|
+
} : {}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const previousRuns = current && current.status === "streaming" && incoming.status !== "streaming" ? Object.fromEntries(Object.entries(state.runs).filter(([runId]) => runId !== incoming.runId)) : state.runs;
|
|
1155
|
+
const acceptedFinalIdentity = incoming.status === "completed" || incoming.status === "yielded" ? readSessionProjectionFinalMessageIdentity(incoming.message) : null;
|
|
1156
|
+
return {
|
|
1157
|
+
...state,
|
|
1158
|
+
runs: retainSessionProjectionRuns({
|
|
1159
|
+
...previousRuns,
|
|
1160
|
+
[incoming.runId]: {
|
|
1161
|
+
...current,
|
|
1162
|
+
...normalizedIncoming,
|
|
1163
|
+
...acceptedFinalIdentity ? { acceptedFinalMessageIdentities: [acceptedFinalIdentity] } : {},
|
|
1164
|
+
...incoming.message === void 0 && current?.message !== void 0 ? { message: current.message } : {}
|
|
1165
|
+
}
|
|
1166
|
+
})
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
/** Reduces durable events, snapshots, and transport lifecycle without client-specific policy. */
|
|
1170
|
+
function reduceSessionProjection(state, event) {
|
|
1171
|
+
const scope = readEventScope(event);
|
|
1172
|
+
if (event.type === "snapshotLoaded") return scopesMatch(state.scope, scope) ? reconcileSessionProjectionSnapshot(state, event.messages, scope, event.options) : state;
|
|
1173
|
+
if (event.type === "sessionReset") {
|
|
1174
|
+
const { sessionKey, sessionId, agentId } = state.scope;
|
|
1175
|
+
return scopesMatch({
|
|
1176
|
+
sessionKey,
|
|
1177
|
+
sessionId,
|
|
1178
|
+
agentId
|
|
1179
|
+
}, scope) ? createSessionProjection({
|
|
1180
|
+
...state.scope,
|
|
1181
|
+
...scope
|
|
1182
|
+
}) : state;
|
|
1183
|
+
}
|
|
1184
|
+
if (!scopesMatch(state.scope, scope)) return state;
|
|
1185
|
+
switch (event.type) {
|
|
1186
|
+
case "messagePersisted": return projectLiveSessionMessage(state, event.message, event.envelope ?? event, scope);
|
|
1187
|
+
case "sendPending": {
|
|
1188
|
+
const pendingRunId = normalizeSessionProjectionRunId(event.idempotencyKey ?? event.runId);
|
|
1189
|
+
const incoming = createEntry(event.message, { pendingRunId });
|
|
1190
|
+
if (!pendingRunId || !incoming.identity) return state;
|
|
1191
|
+
return state.entries.findIndex((entry) => entryMatches(entry, incoming)) < 0 ? withEntries(state, insertEntry(state.entries, incoming, state.runs)) : state;
|
|
1192
|
+
}
|
|
1193
|
+
case "sendAcknowledged": {
|
|
1194
|
+
const runId = normalizeSessionProjectionRunId(event.idempotencyKey ?? event.runId);
|
|
1195
|
+
const previousRunId = normalizeSessionProjectionRunId(event.previousRunId);
|
|
1196
|
+
if (!runId || !previousRunId || previousRunId === runId) return state;
|
|
1197
|
+
let changed = false;
|
|
1198
|
+
const entries = state.entries.flatMap((entry) => {
|
|
1199
|
+
if (!entry.pending || entry.pendingRunId !== previousRunId) return [entry];
|
|
1200
|
+
changed = true;
|
|
1201
|
+
const rekeyed = {
|
|
1202
|
+
...entry,
|
|
1203
|
+
pendingRunId: runId
|
|
1204
|
+
};
|
|
1205
|
+
return state.entries.some((candidate) => !candidate.pending && entryMatches(rekeyed, candidate)) ? [] : [rekeyed];
|
|
1206
|
+
});
|
|
1207
|
+
return changed ? withEntries(state, entries) : state;
|
|
1208
|
+
}
|
|
1209
|
+
case "sendFailed": {
|
|
1210
|
+
const runId = normalizeSessionProjectionRunId(event.runId);
|
|
1211
|
+
const entries = state.entries.filter((entry) => !entry.pending || entry.pendingRunId !== runId);
|
|
1212
|
+
return entries.length === state.entries.length ? state : withEntries(state, entries);
|
|
1213
|
+
}
|
|
1214
|
+
case "runDelta": return updateRun(state, {
|
|
1215
|
+
runId: event.runId,
|
|
1216
|
+
status: "streaming",
|
|
1217
|
+
...event.message === void 0 ? {} : { message: event.message }
|
|
1218
|
+
});
|
|
1219
|
+
case "runTerminal": return updateRun(state, {
|
|
1220
|
+
runId: event.runId,
|
|
1221
|
+
status: event.status,
|
|
1222
|
+
...event.message === void 0 ? {} : { message: event.message },
|
|
1223
|
+
...event.stopReason === void 0 ? {} : { stopReason: event.stopReason },
|
|
1224
|
+
...event.errorKind === void 0 ? {} : { errorKind: event.errorKind },
|
|
1225
|
+
...event.errorMessage === void 0 ? {} : { errorMessage: event.errorMessage }
|
|
1226
|
+
});
|
|
1227
|
+
case "transportGap": return state.hasTransportGap ? state : {
|
|
1228
|
+
...state,
|
|
1229
|
+
hasTransportGap: true
|
|
1230
|
+
};
|
|
1231
|
+
case "reconnected": return state;
|
|
1232
|
+
default: return state;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/** Normalizes Gateway run envelopes once for every browser and terminal adapter. */
|
|
1236
|
+
function reduceSessionProjectionRunEvent(projection, event, scope = {}) {
|
|
1237
|
+
const runId = readNonemptyString(event.runId);
|
|
1238
|
+
const eventState = event.state;
|
|
1239
|
+
if (!runId || typeof eventState !== "string" || ![
|
|
1240
|
+
"delta",
|
|
1241
|
+
"final",
|
|
1242
|
+
"error",
|
|
1243
|
+
"aborted"
|
|
1244
|
+
].includes(eventState)) return null;
|
|
1245
|
+
const message = event.message;
|
|
1246
|
+
const stopReason = readNonemptyString(event.stopReason) ?? readNonemptyString(readRecord(message)?.stopReason);
|
|
1247
|
+
const errorKind = readNonemptyString(event.errorKind);
|
|
1248
|
+
const base = {
|
|
1249
|
+
runId,
|
|
1250
|
+
...message === void 0 ? {} : { message },
|
|
1251
|
+
scope
|
|
1252
|
+
};
|
|
1253
|
+
const next = reduceSessionProjection(projection, eventState === "delta" ? {
|
|
1254
|
+
type: "runDelta",
|
|
1255
|
+
...base
|
|
1256
|
+
} : {
|
|
1257
|
+
type: "runTerminal",
|
|
1258
|
+
...base,
|
|
1259
|
+
status: eventState === "aborted" ? "aborted" : eventState === "error" ? errorKind === "timeout" ? "timeout" : "error" : event.yielded === true && stopReason === "end_turn" ? "yielded" : stopReason === "error" ? "error" : "completed",
|
|
1260
|
+
...stopReason === null ? {} : { stopReason },
|
|
1261
|
+
...errorKind === null ? {} : { errorKind },
|
|
1262
|
+
...typeof event.errorMessage === "string" ? { errorMessage: event.errorMessage } : {}
|
|
1263
|
+
});
|
|
1264
|
+
return {
|
|
1265
|
+
projection: next,
|
|
1266
|
+
previousRun: projection.runs[runId],
|
|
1267
|
+
currentRun: next.runs[runId]
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region packages/gateway-client/src/session-subscriptions.ts
|
|
1272
|
+
function sessionSubscriptionParams(key, agentId) {
|
|
1273
|
+
return {
|
|
1274
|
+
key: key.trim(),
|
|
1275
|
+
...agentId ? { agentId } : {}
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* One Gateway connection owns one targeted observer per canonical session.
|
|
1280
|
+
* Approval delivery is an upgrade of that observer, never a second observer.
|
|
1281
|
+
*/
|
|
1282
|
+
var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
1283
|
+
#client;
|
|
1284
|
+
#keysEquivalent;
|
|
1285
|
+
#entries = /* @__PURE__ */ new Set();
|
|
1286
|
+
#retired = false;
|
|
1287
|
+
constructor(client, options = {}) {
|
|
1288
|
+
this.#client = client;
|
|
1289
|
+
this.#keysEquivalent = options.keysEquivalent;
|
|
1290
|
+
}
|
|
1291
|
+
configure(options = {}) {
|
|
1292
|
+
const matcher = options.keysEquivalent;
|
|
1293
|
+
if (!matcher || matcher === this.#keysEquivalent) return this;
|
|
1294
|
+
if (this.#keysEquivalent || this.#entries.size > 0) throw new Error("Session message key equivalence cannot change for an active connection");
|
|
1295
|
+
this.#keysEquivalent = matcher;
|
|
1296
|
+
return this;
|
|
1297
|
+
}
|
|
1298
|
+
async acquire(key, options = {}) {
|
|
1299
|
+
const normalizedKey = key.trim();
|
|
1300
|
+
if (!normalizedKey) throw new Error("Session message subscription requires a session key");
|
|
1301
|
+
const agentId = options.agentId?.trim() || null;
|
|
1302
|
+
let entry;
|
|
1303
|
+
while (true) {
|
|
1304
|
+
if (this.#retired) throw new Error("Session message subscription belongs to a replaced Gateway connection");
|
|
1305
|
+
const existing = [...this.#entries].find((candidate) => candidate.agentId === agentId && (this.#areKeysEquivalent(candidate.key, normalizedKey) || [...candidate.requestedKeys].some((requestedKey) => this.#areKeysEquivalent(requestedKey, normalizedKey))));
|
|
1306
|
+
if (!existing) {
|
|
1307
|
+
const provisional = [...this.#entries].find((candidate) => candidate.agentId === agentId && !candidate.canonicalSettled);
|
|
1308
|
+
if (provisional) {
|
|
1309
|
+
await (provisional.plainFallback ?? provisional.ready).catch(() => void 0);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
entry = this.#createEntry(normalizedKey, agentId, options.includeApprovals === true);
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
if (!existing.release) {
|
|
1316
|
+
entry = existing;
|
|
1317
|
+
entry.requestedKeys.add(normalizedKey);
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
await existing.release.catch(() => void 0);
|
|
1321
|
+
}
|
|
1322
|
+
entry.pendingOwners += 1;
|
|
1323
|
+
try {
|
|
1324
|
+
const result = await this.#acquireCapability(entry, options.includeApprovals === true);
|
|
1325
|
+
if (this.#retired) throw new Error("Session message subscription completed on a replaced Gateway connection");
|
|
1326
|
+
const subscription = {
|
|
1327
|
+
key: result.key,
|
|
1328
|
+
agentId,
|
|
1329
|
+
...options.includeApprovals === true ? {
|
|
1330
|
+
includeApprovals: true,
|
|
1331
|
+
...result.approvalReplay !== void 0 ? { approvalReplay: result.approvalReplay } : {}
|
|
1332
|
+
} : {}
|
|
1333
|
+
};
|
|
1334
|
+
entry.handles.add(subscription);
|
|
1335
|
+
sessionMessageSubscriptionOwners.set(subscription, {
|
|
1336
|
+
coordinator: this,
|
|
1337
|
+
entry
|
|
1338
|
+
});
|
|
1339
|
+
return subscription;
|
|
1340
|
+
} finally {
|
|
1341
|
+
entry.pendingOwners -= 1;
|
|
1342
|
+
if (entry.pendingOwners === 0 && entry.handles.size === 0 && !entry.release) this.#entries.delete(entry);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
release(subscription) {
|
|
1346
|
+
const owner = sessionMessageSubscriptionOwners.get(subscription);
|
|
1347
|
+
if (!owner || owner.coordinator !== this) return Promise.resolve();
|
|
1348
|
+
const { entry } = owner;
|
|
1349
|
+
if (this.#retired || entry.handles.size > 1) {
|
|
1350
|
+
this.#finishRelease(subscription, owner);
|
|
1351
|
+
return Promise.resolve();
|
|
1352
|
+
}
|
|
1353
|
+
if (entry.release) return entry.release;
|
|
1354
|
+
if (entry.pendingOwners > 0) {
|
|
1355
|
+
const pending = [entry.ready, ...entry.approvalRequest ? [entry.approvalRequest] : []];
|
|
1356
|
+
const tracked = Promise.allSettled(pending).then(() => {
|
|
1357
|
+
if (entry.release === tracked) entry.release = null;
|
|
1358
|
+
return this.release(subscription);
|
|
1359
|
+
});
|
|
1360
|
+
entry.release = tracked;
|
|
1361
|
+
return tracked;
|
|
1362
|
+
}
|
|
1363
|
+
const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId)).then(() => {
|
|
1364
|
+
this.#finishRelease(subscription, owner, true);
|
|
1365
|
+
}).finally(() => {
|
|
1366
|
+
if (entry.release === tracked) entry.release = null;
|
|
1367
|
+
});
|
|
1368
|
+
entry.release = tracked;
|
|
1369
|
+
return tracked;
|
|
1370
|
+
}
|
|
1371
|
+
/** A reconnect retires leases without touching the next connection's observers. */
|
|
1372
|
+
reset() {
|
|
1373
|
+
this.#retired = true;
|
|
1374
|
+
for (const entry of this.#entries) for (const subscription of entry.handles) {
|
|
1375
|
+
const owner = sessionMessageSubscriptionOwners.get(subscription);
|
|
1376
|
+
if (owner?.coordinator === this) this.#finishRelease(subscription, owner);
|
|
1377
|
+
}
|
|
1378
|
+
this.#entries.clear();
|
|
1379
|
+
}
|
|
1380
|
+
#createEntry(key, agentId, includeApprovals) {
|
|
1381
|
+
const entry = {
|
|
1382
|
+
key,
|
|
1383
|
+
requestedKeys: /* @__PURE__ */ new Set([key]),
|
|
1384
|
+
agentId,
|
|
1385
|
+
ready: Promise.resolve({ key }),
|
|
1386
|
+
approvalRequest: null,
|
|
1387
|
+
approvalResponse: null,
|
|
1388
|
+
plainFallback: null,
|
|
1389
|
+
canonicalSettled: false,
|
|
1390
|
+
handles: /* @__PURE__ */ new Set(),
|
|
1391
|
+
pendingOwners: 0,
|
|
1392
|
+
release: null
|
|
1393
|
+
};
|
|
1394
|
+
entry.ready = this.#requestSubscribe(entry, includeApprovals).then((result) => {
|
|
1395
|
+
entry.key = result.key;
|
|
1396
|
+
entry.canonicalSettled = true;
|
|
1397
|
+
if (includeApprovals) entry.approvalResponse = result;
|
|
1398
|
+
return result;
|
|
1399
|
+
});
|
|
1400
|
+
if (includeApprovals) entry.approvalRequest = entry.ready;
|
|
1401
|
+
entry.ready.catch(() => void 0);
|
|
1402
|
+
this.#entries.add(entry);
|
|
1403
|
+
return entry;
|
|
1404
|
+
}
|
|
1405
|
+
#acquireCapability(entry, includeApprovals) {
|
|
1406
|
+
if (!includeApprovals) {
|
|
1407
|
+
if (entry.approvalRequest === entry.ready && !entry.approvalResponse) {
|
|
1408
|
+
if (!entry.plainFallback) {
|
|
1409
|
+
const approvalRequest = entry.ready;
|
|
1410
|
+
entry.plainFallback = approvalRequest.catch(async (error) => {
|
|
1411
|
+
if (this.#retired) throw error;
|
|
1412
|
+
const result = await this.#requestSubscribe(entry, false);
|
|
1413
|
+
entry.key = result.key;
|
|
1414
|
+
entry.canonicalSettled = true;
|
|
1415
|
+
entry.ready = Promise.resolve(result);
|
|
1416
|
+
if (entry.approvalRequest === approvalRequest) entry.approvalRequest = null;
|
|
1417
|
+
return result;
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
return entry.plainFallback;
|
|
1421
|
+
}
|
|
1422
|
+
return entry.ready;
|
|
1423
|
+
}
|
|
1424
|
+
if (entry.approvalResponse) return Promise.resolve(entry.approvalResponse);
|
|
1425
|
+
if (entry.approvalRequest) return entry.approvalRequest;
|
|
1426
|
+
const upgrade = entry.ready.then(() => this.#requestSubscribe(entry, true)).then((result) => {
|
|
1427
|
+
entry.key = result.key;
|
|
1428
|
+
entry.approvalResponse = result;
|
|
1429
|
+
return result;
|
|
1430
|
+
});
|
|
1431
|
+
entry.approvalRequest = upgrade;
|
|
1432
|
+
upgrade.catch(() => {
|
|
1433
|
+
if (entry.approvalRequest === upgrade) entry.approvalRequest = null;
|
|
1434
|
+
});
|
|
1435
|
+
return upgrade;
|
|
1436
|
+
}
|
|
1437
|
+
async #requestSubscribe(entry, includeApprovals) {
|
|
1438
|
+
const result = await this.#client.request("sessions.messages.subscribe", {
|
|
1439
|
+
...sessionSubscriptionParams(entry.key, entry.agentId),
|
|
1440
|
+
...includeApprovals ? { includeApprovals: true } : {}
|
|
1441
|
+
});
|
|
1442
|
+
const response = result && typeof result === "object" ? result : null;
|
|
1443
|
+
const responseKey = response && "key" in response ? response.key : void 0;
|
|
1444
|
+
return {
|
|
1445
|
+
key: typeof responseKey === "string" && responseKey.trim() ? responseKey.trim() : entry.key,
|
|
1446
|
+
...response && "approvalReplay" in response ? { approvalReplay: response.approvalReplay } : {}
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
#finishRelease(subscription, owner, removeEntry = false) {
|
|
1450
|
+
if (sessionMessageSubscriptionOwners.get(subscription) !== owner) return;
|
|
1451
|
+
sessionMessageSubscriptionOwners.delete(subscription);
|
|
1452
|
+
owner.entry.handles.delete(subscription);
|
|
1453
|
+
if (removeEntry) this.#entries.delete(owner.entry);
|
|
1454
|
+
}
|
|
1455
|
+
#areKeysEquivalent(left, right) {
|
|
1456
|
+
return left === right || this.#keysEquivalent?.(left, right) === true;
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
const sessionMessageSubscriptionOwners = /* @__PURE__ */ new WeakMap();
|
|
1460
|
+
const sessionMessageSubscriptionCoordinators = /* @__PURE__ */ new WeakMap();
|
|
1461
|
+
function getGatewaySessionMessageSubscriptionCoordinator(client, options = {}) {
|
|
1462
|
+
const existing = sessionMessageSubscriptionCoordinators.get(client);
|
|
1463
|
+
if (existing) return existing.configure(options);
|
|
1464
|
+
const coordinator = new GatewaySessionMessageSubscriptionCoordinator(client, options);
|
|
1465
|
+
sessionMessageSubscriptionCoordinators.set(client, coordinator);
|
|
1466
|
+
return coordinator;
|
|
1467
|
+
}
|
|
1468
|
+
function resetGatewaySessionMessageSubscriptionCoordinator(client) {
|
|
1469
|
+
sessionMessageSubscriptionCoordinators.get(client)?.reset();
|
|
1470
|
+
sessionMessageSubscriptionCoordinators.delete(client);
|
|
1471
|
+
}
|
|
1472
|
+
function releaseGatewaySessionMessageSubscription(subscription) {
|
|
1473
|
+
return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
|
|
1474
|
+
}
|
|
1475
|
+
//#endregion
|
|
1476
|
+
export { buildDeviceAuthPayload as C, shouldRetryGatewayWithDeviceToken as S, normalizeDeviceMetadataForAuth as T, GatewayProtocolRequestError as _, createSessionProjection as a, resolveGatewayConnectScopes as b, normalizeSessionProjectionRunId as c, readSessionMessageSequence as d, reconcileSessionProjectionSnapshot as f, GatewayProtocolClient as g, shouldPauseGatewayReconnect as h, resetGatewaySessionMessageSubscriptionCoordinator as i, projectLiveSessionMessage as l, reduceSessionProjectionRunEvent as m, getGatewaySessionMessageSubscriptionCoordinator as n, hasSessionProjectionAcceptedFinal as o, reduceSessionProjection as p, releaseGatewaySessionMessageSubscription as r, isLocallyOptimisticSessionMessage as s, GatewaySessionMessageSubscriptionCoordinator as t, readSessionMessageIdentity as u, GatewayBrowserDeviceAuthLifecycle as v, buildDeviceAuthPayloadV3 as w, selectGatewayConnectAuth as x, buildGatewayConnectAuth as y };
|