@openclaw/gateway-client 2026.9.4 → 2026.9.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/README.md +35 -0
- package/dist/browser.d.mts +13 -4
- package/dist/browser.mjs +3 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +243 -127
- package/dist/{protocol-client-DbX7wQED.d.mts → protocol-client-D6mJuY1_.d.mts} +15 -6
- package/dist/{protocol-request-B5gBmLgq.d.mts → protocol-request-CtVhe3Bj.d.mts} +1 -1
- package/dist/{readiness-DabX0O0A.d.mts → readiness-ChxbBSMj.d.mts} +42 -26
- package/dist/readiness.d.mts +1 -1
- package/dist/scope-upgrade.d.mts +1 -1
- package/dist/{session-subscriptions-BSQIANvX.d.mts → session-subscriptions-C91CEXwY.d.mts} +15 -13
- package/dist/{session-subscriptions-DIcpqS8A.mjs → session-subscriptions-DUpRfWJb.mjs} +277 -85
- package/package.json +2 -2
|
@@ -225,6 +225,19 @@ function gatewayCredentialScope(gatewayUrl) {
|
|
|
225
225
|
return normalizeGatewayScope(gatewayUrl, true);
|
|
226
226
|
}
|
|
227
227
|
//#endregion
|
|
228
|
+
//#region packages/gateway-client/src/model-catalog-connect.ts
|
|
229
|
+
const MODEL_CATALOG_SNAPSHOT = "model-catalog-snapshot";
|
|
230
|
+
/** A requested snapshot opts in only after the server advertises its connect field. */
|
|
231
|
+
function resolveModelCatalogConnect(params) {
|
|
232
|
+
const modelCatalog = params.serverCapabilities.includes(MODEL_CATALOG_SNAPSHOT) ? params.modelCatalog : void 0;
|
|
233
|
+
const caps = params.caps?.filter((cap) => cap !== MODEL_CATALOG_SNAPSHOT);
|
|
234
|
+
if (modelCatalog === void 0) return { caps };
|
|
235
|
+
return {
|
|
236
|
+
modelCatalog,
|
|
237
|
+
caps: [...caps ?? [], MODEL_CATALOG_SNAPSHOT]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
228
241
|
//#region packages/retry/src/index.ts
|
|
229
242
|
const MAX_TIMER_TIMEOUT_MS = 2147e6;
|
|
230
243
|
function computeBackoff(policy, attempt) {
|
|
@@ -306,9 +319,16 @@ const DEFAULT_RETRY_CONFIG = {
|
|
|
306
319
|
maxDelayMs: 3e4,
|
|
307
320
|
jitter: 0
|
|
308
321
|
};
|
|
309
|
-
const defaultSleep = (ms) =>
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
const defaultSleep = async (ms) => {
|
|
323
|
+
let remainingMs = ms;
|
|
324
|
+
do {
|
|
325
|
+
const delayMs = Math.min(remainingMs, MAX_TIMER_TIMEOUT_MS);
|
|
326
|
+
await new Promise((resolve) => {
|
|
327
|
+
setTimeout(resolve, delayMs);
|
|
328
|
+
});
|
|
329
|
+
remainingMs -= delayMs;
|
|
330
|
+
} while (remainingMs > 0);
|
|
331
|
+
};
|
|
312
332
|
function clampNumber(value, fallback, min, max) {
|
|
313
333
|
const next = Number.isFinite(value) ? value : void 0;
|
|
314
334
|
if (next === void 0) return fallback;
|
|
@@ -550,7 +570,7 @@ var GatewayPendingRequests = class {
|
|
|
550
570
|
}));
|
|
551
571
|
if (this.pending.get(id) !== pending) return;
|
|
552
572
|
requestSent = true;
|
|
553
|
-
this.invoke("sent", () => options?.onSent?.());
|
|
573
|
+
this.invoke("sent", () => options?.onSent?.(id));
|
|
554
574
|
} catch (error) {
|
|
555
575
|
if (retire("CLIENT_SEND_ERROR")) reject(error instanceof Error ? error : new Error(String(error)));
|
|
556
576
|
}
|
|
@@ -630,8 +650,10 @@ var GatewayProtocolClient = class {
|
|
|
630
650
|
this.listeners = new GatewayEventListeners();
|
|
631
651
|
this.stopped = true;
|
|
632
652
|
this.generation = 0;
|
|
653
|
+
this.connectionAbort = null;
|
|
633
654
|
this.lastSeq = null;
|
|
634
655
|
this.connectNonce = null;
|
|
656
|
+
this.serverCapabilities = [];
|
|
635
657
|
this.connectSent = false;
|
|
636
658
|
this.connectRequestSent = false;
|
|
637
659
|
this.handshakeTimer = null;
|
|
@@ -676,6 +698,7 @@ var GatewayProtocolClient = class {
|
|
|
676
698
|
}
|
|
677
699
|
stop() {
|
|
678
700
|
this.stopped = true;
|
|
701
|
+
this.connectionAbort?.abort();
|
|
679
702
|
this.clearHandshakeTimer();
|
|
680
703
|
this.reconnectSignal = null;
|
|
681
704
|
this.reconnectSupervisor.reset();
|
|
@@ -700,6 +723,7 @@ var GatewayProtocolClient = class {
|
|
|
700
723
|
return this.listeners.add(listener);
|
|
701
724
|
}
|
|
702
725
|
closeSocket(code, reason) {
|
|
726
|
+
this.connectionAbort?.abort();
|
|
703
727
|
this.socket?.close(code, reason);
|
|
704
728
|
}
|
|
705
729
|
resetReconnectBackoff(initialMs) {
|
|
@@ -731,6 +755,7 @@ var GatewayProtocolClient = class {
|
|
|
731
755
|
this.lastSeq = null;
|
|
732
756
|
this.connectNonce = null;
|
|
733
757
|
this.connectChallengeTs = void 0;
|
|
758
|
+
this.serverCapabilities = [];
|
|
734
759
|
this.connectSent = this.connectRequestSent = false;
|
|
735
760
|
this.socketOpened = false;
|
|
736
761
|
this.helloReceived = false;
|
|
@@ -756,6 +781,7 @@ var GatewayProtocolClient = class {
|
|
|
756
781
|
return;
|
|
757
782
|
}
|
|
758
783
|
this.generation = generation;
|
|
784
|
+
this.connectionAbort = new AbortController();
|
|
759
785
|
this.socket = socket;
|
|
760
786
|
const now = this.nowMs();
|
|
761
787
|
this.connectTiming = {
|
|
@@ -806,7 +832,9 @@ var GatewayProtocolClient = class {
|
|
|
806
832
|
planOrPromise = this.opts.buildConnectPlan({
|
|
807
833
|
nonce: this.connectNonce,
|
|
808
834
|
challengeTs: this.connectChallengeTs,
|
|
809
|
-
|
|
835
|
+
serverCapabilities: this.serverCapabilities,
|
|
836
|
+
generation,
|
|
837
|
+
...this.connectAuthority(socket, generation)
|
|
810
838
|
});
|
|
811
839
|
} catch (error) {
|
|
812
840
|
this.handleConnectPlanError(socket, generation, error);
|
|
@@ -819,7 +847,7 @@ var GatewayProtocolClient = class {
|
|
|
819
847
|
this.sendConnectPlan(socket, generation, planOrPromise);
|
|
820
848
|
}
|
|
821
849
|
handleConnectPlanError(socket, generation, error) {
|
|
822
|
-
if (!this.
|
|
850
|
+
if (!this.isConnectCurrent(socket, generation)) return;
|
|
823
851
|
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
824
852
|
const outcome = this.opts.onConnectPlanError?.(normalized) ?? {
|
|
825
853
|
closeCode: 1008,
|
|
@@ -830,8 +858,9 @@ var GatewayProtocolClient = class {
|
|
|
830
858
|
socket.close(outcome.closeCode, outcome.closeReason);
|
|
831
859
|
}
|
|
832
860
|
sendConnectPlan(socket, generation, plan) {
|
|
833
|
-
if (!this.
|
|
861
|
+
if (!this.isConnectCurrent(socket, generation)) return;
|
|
834
862
|
const context = {
|
|
863
|
+
...this.connectAuthority(socket, generation),
|
|
835
864
|
generation,
|
|
836
865
|
nonce: this.connectNonce,
|
|
837
866
|
challengeTs: this.connectChallengeTs,
|
|
@@ -841,27 +870,43 @@ var GatewayProtocolClient = class {
|
|
|
841
870
|
this.recordTiming("request-sent", generation, plan);
|
|
842
871
|
this.connectRequestSent = true;
|
|
843
872
|
this.request("connect", this.opts.buildConnectParams(plan)).then((hello) => {
|
|
844
|
-
if (!this.
|
|
873
|
+
if (!this.isConnectCurrent(socket, generation)) return;
|
|
845
874
|
this.helloReceived = true;
|
|
846
875
|
this.clearHandshakeTimer();
|
|
847
876
|
this.connectFailure = void 0;
|
|
848
877
|
this.reconnectSupervisor.reset();
|
|
849
878
|
this.recordTiming("hello", generation, plan);
|
|
850
|
-
|
|
851
|
-
|
|
879
|
+
const publishHello = () => {
|
|
880
|
+
if (!this.isConnectCurrent(socket, generation)) return;
|
|
881
|
+
this.invoke("hello", () => this.opts.onHello?.(hello));
|
|
882
|
+
};
|
|
883
|
+
const accepted = this.opts.onConnectHello?.(hello, context);
|
|
884
|
+
if (accepted instanceof Promise) return accepted.then(publishHello);
|
|
885
|
+
return publishHello();
|
|
852
886
|
}).catch((error) => {
|
|
853
887
|
if (!this.isActive(socket, generation)) return;
|
|
854
888
|
const requestError = error instanceof GatewayProtocolRequestError ? error : new GatewayProtocolRequestError({ message: String(error) });
|
|
889
|
+
this.connectFailure = { error: requestError };
|
|
855
890
|
const outcome = this.opts.onConnectFailure?.(requestError, context) ?? {
|
|
856
891
|
closeCode: 1008,
|
|
857
892
|
closeReason: "connect failed"
|
|
858
893
|
};
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
894
|
+
const applyFailure = (decision) => {
|
|
895
|
+
if (!this.isActive(socket, generation)) return;
|
|
896
|
+
this.connectFailure = {
|
|
897
|
+
error: requestError,
|
|
898
|
+
reconnectDelayMs: decision.reconnectDelayMs
|
|
899
|
+
};
|
|
900
|
+
if (decision.stop) this.stopped = true;
|
|
901
|
+
this.connectionAbort?.abort();
|
|
902
|
+
socket.close(decision.closeCode, decision.closeReason);
|
|
862
903
|
};
|
|
863
|
-
if (outcome
|
|
864
|
-
|
|
904
|
+
if (outcome instanceof Promise) return outcome.then(applyFailure);
|
|
905
|
+
return applyFailure(outcome);
|
|
906
|
+
}).catch((error) => {
|
|
907
|
+
if (!this.isConnectCurrent(socket, generation)) return;
|
|
908
|
+
this.opts.onConnectError?.(error instanceof Error ? error : new Error(String(error)));
|
|
909
|
+
this.closeSocket(1008, "connect failed");
|
|
865
910
|
});
|
|
866
911
|
}
|
|
867
912
|
handleMessage(socket, generation, raw) {
|
|
@@ -887,6 +932,7 @@ var GatewayProtocolClient = class {
|
|
|
887
932
|
return;
|
|
888
933
|
}
|
|
889
934
|
this.connectNonce = nonce;
|
|
935
|
+
this.serverCapabilities = Array.isArray(payload?.capabilities) ? payload.capabilities.filter((value) => typeof value === "string") : [];
|
|
890
936
|
const challengeTs = payload?.ts;
|
|
891
937
|
this.connectChallengeTs = typeof challengeTs === "number" && Number.isSafeInteger(challengeTs) && challengeTs >= 0 ? challengeTs : null;
|
|
892
938
|
this.recordTiming("challenge", generation);
|
|
@@ -934,6 +980,7 @@ var GatewayProtocolClient = class {
|
|
|
934
980
|
return;
|
|
935
981
|
}
|
|
936
982
|
this.socket = null;
|
|
983
|
+
this.connectionAbort?.abort();
|
|
937
984
|
this.clearHandshakeTimer();
|
|
938
985
|
const context = {
|
|
939
986
|
...this.closeContext(),
|
|
@@ -978,6 +1025,20 @@ var GatewayProtocolClient = class {
|
|
|
978
1025
|
connectFailure: this.connectFailure
|
|
979
1026
|
};
|
|
980
1027
|
}
|
|
1028
|
+
isConnectCurrent(socket, generation) {
|
|
1029
|
+
return this.isActive(socket, generation) && socket.isOpen() && !this.connectionAbort?.signal.aborted;
|
|
1030
|
+
}
|
|
1031
|
+
connectAuthority(socket, generation) {
|
|
1032
|
+
const signal = this.connectionAbort?.signal;
|
|
1033
|
+
if (!signal) throw new Error("gateway connection authority is unavailable");
|
|
1034
|
+
return {
|
|
1035
|
+
signal,
|
|
1036
|
+
assertCurrent: () => {
|
|
1037
|
+
signal.throwIfAborted();
|
|
1038
|
+
if (!this.isConnectCurrent(socket, generation)) throw new Error("gateway connection retired");
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
981
1042
|
isActive(socket, generation) {
|
|
982
1043
|
return !this.stopped && this.socket === socket && this.generation === generation;
|
|
983
1044
|
}
|
|
@@ -1038,7 +1099,7 @@ const preserveString = (value) => value;
|
|
|
1038
1099
|
function stableStringify(value, normalizeString = preserveString) {
|
|
1039
1100
|
return stringifyStableValue(value, /* @__PURE__ */ new WeakSet(), normalizeString);
|
|
1040
1101
|
}
|
|
1041
|
-
function stringifyStableValue(value, stack, normalizeString) {
|
|
1102
|
+
function stringifyStableValue(value, stack, normalizeString, write) {
|
|
1042
1103
|
if (value === null || value === void 0) return String(value);
|
|
1043
1104
|
if (typeof value === "number" && !Number.isFinite(value)) return JSON.stringify(String(value));
|
|
1044
1105
|
if (typeof value === "bigint") return JSON.stringify(value.toString());
|
|
@@ -1047,39 +1108,73 @@ function stringifyStableValue(value, stack, normalizeString) {
|
|
|
1047
1108
|
if (stack.has(value)) return JSON.stringify("[Circular]");
|
|
1048
1109
|
stack.add(value);
|
|
1049
1110
|
try {
|
|
1050
|
-
return stringifyObjectValue(value, stack, normalizeString);
|
|
1111
|
+
return stringifyObjectValue(value, stack, normalizeString, write);
|
|
1051
1112
|
} finally {
|
|
1052
1113
|
stack.delete(value);
|
|
1053
1114
|
}
|
|
1054
1115
|
}
|
|
1055
|
-
function stringifyObjectValue(value, stack, normalizeString) {
|
|
1116
|
+
function stringifyObjectValue(value, stack, normalizeString, write) {
|
|
1056
1117
|
if (value instanceof Error) return stringifyStableValue({
|
|
1057
1118
|
name: value.name,
|
|
1058
1119
|
message: value.message,
|
|
1059
1120
|
stack: value.stack
|
|
1060
|
-
}, stack, normalizeString);
|
|
1121
|
+
}, stack, normalizeString, write);
|
|
1061
1122
|
if (value instanceof Uint8Array) return stringifyStableValue({
|
|
1062
1123
|
type: "Uint8Array",
|
|
1063
1124
|
data: encodeBase64(value)
|
|
1064
|
-
}, stack, normalizeString);
|
|
1125
|
+
}, stack, normalizeString, write);
|
|
1065
1126
|
if (Array.isArray(value)) {
|
|
1127
|
+
if (write) {
|
|
1128
|
+
write("[");
|
|
1129
|
+
let separator = "";
|
|
1130
|
+
for (const entry of value) {
|
|
1131
|
+
write(separator);
|
|
1132
|
+
write(stringifyStableValue(entry, stack, normalizeString, write));
|
|
1133
|
+
separator = ",";
|
|
1134
|
+
}
|
|
1135
|
+
write("]");
|
|
1136
|
+
return "";
|
|
1137
|
+
}
|
|
1066
1138
|
const serializedEntries = [];
|
|
1067
1139
|
for (const entry of value) serializedEntries.push(stringifyStableValue(entry, stack, normalizeString));
|
|
1068
1140
|
return `[${serializedEntries.join(",")}]`;
|
|
1069
1141
|
}
|
|
1070
1142
|
const record = value;
|
|
1071
1143
|
if (normalizeString === preserveString) {
|
|
1072
|
-
const fields =
|
|
1073
|
-
|
|
1144
|
+
const fields = Object.keys(record).sort();
|
|
1145
|
+
if (write) {
|
|
1146
|
+
write("{");
|
|
1147
|
+
let separator = "";
|
|
1148
|
+
for (const key of fields) {
|
|
1149
|
+
write(`${separator}${JSON.stringify(key)}:`);
|
|
1150
|
+
write(stringifyStableValue(record[key], stack, normalizeString, write));
|
|
1151
|
+
separator = ",";
|
|
1152
|
+
}
|
|
1153
|
+
write("}");
|
|
1154
|
+
return "";
|
|
1155
|
+
}
|
|
1156
|
+
let fieldIndex = 0;
|
|
1157
|
+
for (const key of fields) fields[fieldIndex++] = `${JSON.stringify(key)}:${stringifyStableValue(record[key], stack, normalizeString)}`;
|
|
1074
1158
|
return `{${fields.join(",")}}`;
|
|
1075
1159
|
}
|
|
1076
1160
|
const entries = Object.keys(record).map((key) => ({
|
|
1077
1161
|
key,
|
|
1078
1162
|
normalizedKey: normalizeString(key)
|
|
1079
|
-
})).
|
|
1163
|
+
})).sort((left, right) => {
|
|
1080
1164
|
return compareStableStrings(left.normalizedKey, right.normalizedKey) || compareStableStrings(left.key, right.key);
|
|
1081
1165
|
});
|
|
1082
1166
|
const serializedFields = [];
|
|
1167
|
+
if (write) {
|
|
1168
|
+
write("{");
|
|
1169
|
+
let separator = "";
|
|
1170
|
+
for (const { key, normalizedKey } of entries) {
|
|
1171
|
+
write(`${separator}${JSON.stringify(normalizedKey)}:`);
|
|
1172
|
+
write(stringifyStableValue(record[key], stack, normalizeString, write));
|
|
1173
|
+
separator = ",";
|
|
1174
|
+
}
|
|
1175
|
+
write("}");
|
|
1176
|
+
return "";
|
|
1177
|
+
}
|
|
1083
1178
|
for (const { key, normalizedKey } of entries) serializedFields.push(`${JSON.stringify(normalizedKey)}:${stringifyStableValue(record[key], stack, normalizeString)}`);
|
|
1084
1179
|
return `{${serializedFields.join(",")}}`;
|
|
1085
1180
|
}
|
|
@@ -1124,11 +1219,11 @@ function readSessionMessageIdentity(message, envelope) {
|
|
|
1124
1219
|
const persistedRunId = normalizeSessionProjectionRunId(idempotencyKey);
|
|
1125
1220
|
const envelopeRunId = normalizeSessionProjectionRunId(envelope?.runId);
|
|
1126
1221
|
const metadataRunId = normalizeSessionProjectionRunId(metadata?.runId);
|
|
1222
|
+
const fallbackRunId = normalizeSessionProjectionRunId(asNullableRecord(record.openclawStreamFallback)?.runId);
|
|
1127
1223
|
const mirroredMessage = readSessionProjectionString(metadata?.mirrorOrigin) !== null;
|
|
1128
1224
|
const isCliAssistant = role === "assistant" && readSessionProjectionString(record.api)?.toLowerCase() === "cli";
|
|
1129
1225
|
const canonicalPersistedRunId = isCliAssistant && persistedRunId?.startsWith("cli-assistant:") ? readSessionProjectionString(persistedRunId.slice(14)) : persistedRunId;
|
|
1130
|
-
const
|
|
1131
|
-
const runId = role === "assistant" ? metadataRunId ?? envelopeRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) ?? optimisticRunId : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId;
|
|
1226
|
+
const runId = role === "assistant" ? metadataRunId ?? envelopeRunId ?? fallbackRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId;
|
|
1132
1227
|
return {
|
|
1133
1228
|
role,
|
|
1134
1229
|
id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
|
|
@@ -1150,11 +1245,51 @@ function readAssistantStreamSegmentIdentity(message) {
|
|
|
1150
1245
|
if (readSessionProjectionString(record?.role)?.toLowerCase() !== "assistant") return;
|
|
1151
1246
|
const fallback = asNullableRecord(record?.openclawStreamFallback);
|
|
1152
1247
|
const itemId = readSessionProjectionString(fallback?.itemId);
|
|
1248
|
+
if (!itemId) return;
|
|
1153
1249
|
const runId = readSessionMessageIdentity(message)?.runId ?? readSessionProjectionString(record?.runId) ?? readSessionProjectionString(fallback?.runId);
|
|
1154
|
-
return
|
|
1250
|
+
return {
|
|
1155
1251
|
itemId,
|
|
1156
1252
|
...runId ? { runId } : {}
|
|
1157
|
-
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
/** A saved occurrence can enrich its live projection, never another durable row. */
|
|
1256
|
+
function sameAssistantPersistenceReceipt(left, right) {
|
|
1257
|
+
return Boolean(left?.role === "assistant" && right?.role === "assistant" && !left.isImported && !right.isImported && left.idempotencyKey && left.idempotencyKey === right.idempotencyKey && (!left.id && left.sequence === null || !right.id && right.sequence === null));
|
|
1258
|
+
}
|
|
1259
|
+
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
1260
|
+
function isLocallyOptimisticSessionMessage(message) {
|
|
1261
|
+
const record = asNullableRecord(message);
|
|
1262
|
+
const role = readSessionProjectionString(record?.role)?.toLowerCase();
|
|
1263
|
+
if (role !== "user" && role !== "assistant") return false;
|
|
1264
|
+
if (asNullableRecord(record?.openclawStreamFallback)) return false;
|
|
1265
|
+
const metadata = asNullableRecord(record?.["__openclaw"]);
|
|
1266
|
+
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
1267
|
+
}
|
|
1268
|
+
function sameTranscriptIdentity(left, right) {
|
|
1269
|
+
if (!left || !right || left.role !== right.role) return false;
|
|
1270
|
+
if (left.isImported || right.isImported) {
|
|
1271
|
+
if (!left.isImported || !right.isImported) return false;
|
|
1272
|
+
if (left.externalSource || right.externalSource) return Boolean(left.externalSource && left.externalSource === right.externalSource);
|
|
1273
|
+
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
1274
|
+
}
|
|
1275
|
+
if (left.id || right.id) return Boolean(left.id && right.id && left.id === right.id);
|
|
1276
|
+
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
1277
|
+
}
|
|
1278
|
+
/** Normalize a message into its live, durable, or pending projection entry. */
|
|
1279
|
+
function createSessionProjectionEntry(message, options) {
|
|
1280
|
+
const identity = readSessionMessageIdentity(message, options?.envelope);
|
|
1281
|
+
const fallback = asNullableRecord(asNullableRecord(message)?.openclawStreamFallback);
|
|
1282
|
+
const provisionalFallback = Boolean(fallback && identity?.role === "assistant" && !identity.id && identity.sequence === null);
|
|
1283
|
+
const inferredPendingRunId = options?.live !== true && isLocallyOptimisticSessionMessage(message) ? identity?.runId : null;
|
|
1284
|
+
const pendingRunId = normalizeSessionProjectionRunId(options?.pendingRunId ?? inferredPendingRunId);
|
|
1285
|
+
return {
|
|
1286
|
+
message,
|
|
1287
|
+
identity,
|
|
1288
|
+
afterSequence: options?.envelope?.afterSequence !== void 0 ? options.envelope.afterSequence : provisionalFallback && typeof fallback?.afterSequence === "number" ? fallback.afterSequence : void 0,
|
|
1289
|
+
live: options?.live === true || provisionalFallback,
|
|
1290
|
+
pending: pendingRunId !== null,
|
|
1291
|
+
pendingRunId
|
|
1292
|
+
};
|
|
1158
1293
|
}
|
|
1159
1294
|
//#endregion
|
|
1160
1295
|
//#region packages/gateway-client/src/session-projection-message-content.ts
|
|
@@ -1180,6 +1315,18 @@ function readSessionMessageDisplayContent(message) {
|
|
|
1180
1315
|
usesFallbackText: fallback !== null
|
|
1181
1316
|
};
|
|
1182
1317
|
}
|
|
1318
|
+
/** Status notices remain visible but do not define the terminal reply they accompany. */
|
|
1319
|
+
function projectSessionTerminalReplyMessage(message) {
|
|
1320
|
+
const record = asNullableRecord(message);
|
|
1321
|
+
if (!record || !Array.isArray(record.content)) return message;
|
|
1322
|
+
const content = record.content.filter((block) => asNullableRecord(block)?.openclawStatusNotice !== true);
|
|
1323
|
+
if (content.length === record.content.length || content.length === 0) return message;
|
|
1324
|
+
return {
|
|
1325
|
+
...record,
|
|
1326
|
+
content,
|
|
1327
|
+
text: void 0
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1183
1330
|
/** Check whether a projected message has text or another displayable block. */
|
|
1184
1331
|
function hasDisplayableSessionMessage(message) {
|
|
1185
1332
|
const { text, hasNonText } = readSessionMessageDisplayContent(message);
|
|
@@ -1199,11 +1346,30 @@ function isSessionProjectionErrorMessage(message, errorMessage) {
|
|
|
1199
1346
|
//#endregion
|
|
1200
1347
|
//#region packages/gateway-client/src/session-projection-final-identity.ts
|
|
1201
1348
|
/** Terminal identity rules used to reconcile live and durable assistant projections. */
|
|
1349
|
+
/**
|
|
1350
|
+
* The class of rows the #148297 relaxation newly admits as finals: persisted
|
|
1351
|
+
* with the run's terminal tool stop reason while carrying no tool-call
|
|
1352
|
+
* content. Both adoption directions and every position rule scope to this
|
|
1353
|
+
* predicate, so pre-existing match semantics stay untouched.
|
|
1354
|
+
*/
|
|
1355
|
+
function isToolUsePersistedFinalRow(message) {
|
|
1356
|
+
return asNullableRecord(message)?.["stopReason"] === "toolUse" && !isSessionProjectionToolContinuation(message);
|
|
1357
|
+
}
|
|
1358
|
+
/** Tool-bearing assistant rows are continuations even without a tool stop reason. */
|
|
1359
|
+
function isSessionProjectionToolContinuation(message) {
|
|
1360
|
+
const record = asNullableRecord(message);
|
|
1361
|
+
if (Array.isArray(record?.content) && record.content.some((block) => {
|
|
1362
|
+
const type = asNullableRecord(block)?.type;
|
|
1363
|
+
return type === "toolCall" || type === "toolUse" || type === "functionCall";
|
|
1364
|
+
})) return true;
|
|
1365
|
+
return record?.stopReason === "toolUse" && !hasDisplayableSessionMessage(message);
|
|
1366
|
+
}
|
|
1202
1367
|
function readPersistedFinalIdentity(message) {
|
|
1203
1368
|
const identity = readSessionMessageIdentity(message);
|
|
1204
1369
|
if (identity?.externalSource) return `import:${identity.role}:${identity.externalSource}`;
|
|
1205
1370
|
if (identity?.id && !identity.isImported) return `id:${identity.role}:${identity.id}`;
|
|
1206
1371
|
if (identity?.sequence !== null && identity?.sequence !== void 0) return `seq:${identity.role}:${identity.sequence}`;
|
|
1372
|
+
if (identity?.role === "assistant" && !identity.isImported && identity.idempotencyKey) return `key:assistant:${identity.idempotencyKey}`;
|
|
1207
1373
|
return null;
|
|
1208
1374
|
}
|
|
1209
1375
|
function hasCompatiblePersistedFinalIdentity(currentMessage, incomingMessage) {
|
|
@@ -1215,14 +1381,16 @@ function hasCompatiblePersistedFinalIdentity(currentMessage, incomingMessage) {
|
|
|
1215
1381
|
if (current.externalSource && incoming.externalSource) return current.externalSource === incoming.externalSource;
|
|
1216
1382
|
return current.sequence !== null && incoming.sequence !== null && current.sequence === incoming.sequence;
|
|
1217
1383
|
}
|
|
1384
|
+
if (sameAssistantPersistenceReceipt(current, incoming)) return true;
|
|
1218
1385
|
if (current.id && incoming.id) return current.id === incoming.id;
|
|
1219
1386
|
return current.sequence !== null && incoming.sequence !== null && current.sequence === incoming.sequence;
|
|
1220
1387
|
}
|
|
1221
1388
|
function readFinalContentIdentity(message) {
|
|
1222
|
-
const
|
|
1389
|
+
const terminalMessage = projectSessionTerminalReplyMessage(message);
|
|
1390
|
+
const display = readSessionMessageDisplayContent(terminalMessage);
|
|
1223
1391
|
if (!display.text && !display.hasNonText) return null;
|
|
1224
1392
|
const identity = readSessionMessageIdentity(message);
|
|
1225
|
-
const record = asNullableRecord(
|
|
1393
|
+
const record = asNullableRecord(terminalMessage);
|
|
1226
1394
|
const metadata = asNullableRecord(record?.["__openclaw"]);
|
|
1227
1395
|
try {
|
|
1228
1396
|
return `content:${stableStringify([
|
|
@@ -1244,14 +1412,6 @@ function hasTerminalStopReason(message) {
|
|
|
1244
1412
|
const stopReason = asNullableRecord(message)?.stopReason;
|
|
1245
1413
|
return stopReason === "stop" || stopReason === "length" || stopReason === "error" || stopReason === "aborted" || stopReason === "end_turn";
|
|
1246
1414
|
}
|
|
1247
|
-
function hasCompletedRunSnapshotContext(entry, snapshot, runId) {
|
|
1248
|
-
if (!runId || entry.identity?.runId !== runId) return false;
|
|
1249
|
-
const entryIndex = snapshot.indexOf(entry);
|
|
1250
|
-
if (entryIndex < 0) return false;
|
|
1251
|
-
const hasEarlierUser = snapshot.slice(0, entryIndex).some((candidate) => candidate.identity?.role === "user" && candidate.identity.runId === runId);
|
|
1252
|
-
const hasLaterAssistant = snapshot.slice(entryIndex + 1).some((candidate) => candidate.identity?.role === "assistant" && candidate.identity.runId === runId);
|
|
1253
|
-
return hasEarlierUser && !hasLaterAssistant;
|
|
1254
|
-
}
|
|
1255
1415
|
/** Read stable persisted identity first, falling back to canonical display content. */
|
|
1256
1416
|
function readSessionProjectionFinalMessageIdentity(message) {
|
|
1257
1417
|
if (!hasDisplayableSessionMessage(message)) return null;
|
|
@@ -1269,11 +1429,41 @@ function hasSessionProjectionAcceptedFinal(run, message) {
|
|
|
1269
1429
|
}
|
|
1270
1430
|
/** Match an unsequenced live terminal to exactly one durable same-run terminal row. */
|
|
1271
1431
|
function findUniqueSnapshotTerminalMatch(current, matches, run, snapshot) {
|
|
1272
|
-
if (!current.live || current.identity?.role !== "assistant" || current.identity.id || current.identity.sequence !== null || !run || run.status === "streaming") return null;
|
|
1432
|
+
if (!current.live || current.identity?.role !== "assistant" || current.identity.id || current.identity.sequence !== null || !run || run.status === "streaming" || matches.length === 0) return null;
|
|
1273
1433
|
const terminalContent = readFinalContentIdentity(current.message);
|
|
1274
1434
|
if (!terminalContent || readFinalContentIdentity(run.message) !== terminalContent) return null;
|
|
1435
|
+
let snapshotIndexes;
|
|
1436
|
+
let firstUserIndex = -1;
|
|
1437
|
+
let lastAssistantIndex = -1;
|
|
1438
|
+
const ensureRunIndexes = () => {
|
|
1439
|
+
if (snapshotIndexes) return;
|
|
1440
|
+
const runId = current.identity?.runId;
|
|
1441
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
1442
|
+
if (runId) snapshot.forEach((candidate, index) => {
|
|
1443
|
+
if (candidate.identity?.runId === runId) {
|
|
1444
|
+
if (!indexes.has(candidate)) indexes.set(candidate, index);
|
|
1445
|
+
if (candidate.identity.role === "user" && firstUserIndex < 0) firstUserIndex = index;
|
|
1446
|
+
else if (candidate.identity.role === "assistant") lastAssistantIndex = index;
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
snapshotIndexes = indexes;
|
|
1450
|
+
};
|
|
1451
|
+
const hasCompletedRunSnapshotContext = (entry) => {
|
|
1452
|
+
const runId = current.identity?.runId;
|
|
1453
|
+
if (!runId || entry.identity?.runId !== runId) return false;
|
|
1454
|
+
ensureRunIndexes();
|
|
1455
|
+
const entryIndex = snapshotIndexes?.get(entry);
|
|
1456
|
+
return entryIndex !== void 0 && firstUserIndex >= 0 && firstUserIndex < entryIndex && lastAssistantIndex <= entryIndex;
|
|
1457
|
+
};
|
|
1458
|
+
const isLastSameRunAssistantRow = (entry) => {
|
|
1459
|
+
const runId = current.identity?.runId;
|
|
1460
|
+
if (!runId || entry.identity?.runId !== runId) return false;
|
|
1461
|
+
ensureRunIndexes();
|
|
1462
|
+
const entryIndex = snapshotIndexes?.get(entry);
|
|
1463
|
+
return entryIndex !== void 0 && lastAssistantIndex >= 0 && entryIndex >= lastAssistantIndex;
|
|
1464
|
+
};
|
|
1275
1465
|
const durableTerminalMatches = matches.filter((entry) => {
|
|
1276
|
-
return (asNullableRecord(asNullableRecord(entry.message)?.["__openclaw"])?.runTerminal === true || entry.identity?.runId === current.identity?.runId && hasTerminalStopReason(entry.message) ||
|
|
1466
|
+
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;
|
|
1277
1467
|
});
|
|
1278
1468
|
const entry = durableTerminalMatches.length === 1 ? durableTerminalMatches[0] : void 0;
|
|
1279
1469
|
if (!entry) return null;
|
|
@@ -1352,35 +1542,15 @@ const SESSION_PROJECTION_SCOPE_KEYS = [
|
|
|
1352
1542
|
"lifecycleRevision",
|
|
1353
1543
|
"activeLeafEntryId"
|
|
1354
1544
|
];
|
|
1355
|
-
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
1356
|
-
function isLocallyOptimisticSessionMessage(message) {
|
|
1357
|
-
const identity = readSessionMessageIdentity(message);
|
|
1358
|
-
if (!identity || identity.role !== "user" && identity.role !== "assistant") return false;
|
|
1359
|
-
const metadata = asNullableRecord(asNullableRecord(message)?.["__openclaw"]);
|
|
1360
|
-
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
1361
|
-
}
|
|
1362
|
-
function createEntry(message, options) {
|
|
1363
|
-
const identity = readSessionMessageIdentity(message, options?.envelope);
|
|
1364
|
-
const inferredPendingRunId = options?.live !== true && isLocallyOptimisticSessionMessage(message) ? identity?.runId : null;
|
|
1365
|
-
const pendingRunId = normalizeSessionProjectionRunId(options?.pendingRunId ?? inferredPendingRunId);
|
|
1366
|
-
return {
|
|
1367
|
-
message,
|
|
1368
|
-
identity,
|
|
1369
|
-
afterSequence: options?.envelope?.afterSequence,
|
|
1370
|
-
live: options?.live === true,
|
|
1371
|
-
pending: pendingRunId !== null,
|
|
1372
|
-
pendingRunId
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
1545
|
function createProjectionEntries(messages) {
|
|
1376
1546
|
let pendingUserRunId = null;
|
|
1377
1547
|
return messages.map((message) => {
|
|
1378
|
-
const entry =
|
|
1548
|
+
const entry = createSessionProjectionEntry(message);
|
|
1379
1549
|
if (entry.identity?.role === "user") {
|
|
1380
1550
|
pendingUserRunId = entry.pending ? entry.pendingRunId : null;
|
|
1381
1551
|
return entry;
|
|
1382
1552
|
}
|
|
1383
|
-
if (pendingUserRunId && entry.identity?.role === "assistant" && !entry.pending && isLocallyOptimisticSessionMessage(message)) return
|
|
1553
|
+
if (pendingUserRunId && entry.identity?.role === "assistant" && !entry.pending && isLocallyOptimisticSessionMessage(message)) return createSessionProjectionEntry(message, { pendingRunId: pendingUserRunId });
|
|
1384
1554
|
if (!isLocallyOptimisticSessionMessage(message)) pendingUserRunId = null;
|
|
1385
1555
|
return entry;
|
|
1386
1556
|
});
|
|
@@ -1403,26 +1573,21 @@ function readEventScope(event) {
|
|
|
1403
1573
|
for (const key of SESSION_PROJECTION_SCOPE_KEYS) if (event[key] !== void 0) Object.assign(scope, { [key]: event[key] });
|
|
1404
1574
|
return scope;
|
|
1405
1575
|
}
|
|
1406
|
-
function sameTranscriptIdentity(left, right) {
|
|
1407
|
-
if (!left || !right || left.role !== right.role) return false;
|
|
1408
|
-
if (left.isImported || right.isImported) {
|
|
1409
|
-
if (!left.isImported || !right.isImported) return false;
|
|
1410
|
-
if (left.externalSource || right.externalSource) return Boolean(left.externalSource && left.externalSource === right.externalSource);
|
|
1411
|
-
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
1412
|
-
}
|
|
1413
|
-
if (left.id || right.id) return Boolean(left.id && right.id && left.id === right.id);
|
|
1414
|
-
return left.sequence !== null && right.sequence !== null && left.sequence === right.sequence;
|
|
1415
|
-
}
|
|
1416
1576
|
function entryMatches(left, right, allowSnapshotPromotion = false) {
|
|
1577
|
+
const leftSegment = readAssistantStreamSegmentIdentity(left.message);
|
|
1578
|
+
const rightSegment = readAssistantStreamSegmentIdentity(right.message);
|
|
1579
|
+
if (leftSegment?.itemId !== rightSegment?.itemId) return false;
|
|
1417
1580
|
if (sameTranscriptIdentity(left.identity, right.identity)) return true;
|
|
1581
|
+
if (sameAssistantPersistenceReceipt(left.identity, right.identity)) return true;
|
|
1582
|
+
if (left.identity?.role === "assistant" && right.identity?.role === "assistant" && left.identity.idempotencyKey && right.identity.idempotencyKey && left.identity.idempotencyKey !== right.identity.idempotencyKey) return false;
|
|
1418
1583
|
const durableEntry = left.identity?.id ? left : right.identity?.id ? right : null;
|
|
1419
1584
|
const provisionalEntry = durableEntry === left ? right : durableEntry === right ? left : null;
|
|
1420
1585
|
const durableMetadata = asNullableRecord(asNullableRecord(durableEntry?.message)?.["__openclaw"]);
|
|
1421
1586
|
if (durableEntry?.identity?.role === "assistant" && provisionalEntry?.identity?.role === "assistant" && !durableEntry.identity.isImported && !provisionalEntry.identity.isImported && !provisionalEntry.identity.id) {
|
|
1422
|
-
const durableSegment =
|
|
1423
|
-
const provisionalSegment =
|
|
1587
|
+
const durableSegment = durableEntry === left ? leftSegment : rightSegment;
|
|
1588
|
+
const provisionalSegment = durableEntry === left ? rightSegment : leftSegment;
|
|
1424
1589
|
if (provisionalEntry.identity.sequence === null && durableSegment?.runId && durableSegment.runId === provisionalSegment?.runId && durableSegment.itemId === provisionalSegment.itemId) return true;
|
|
1425
|
-
if (provisionalEntry.live && provisionalEntry.identity.sequence === null && (provisionalEntry.afterSequence === void 0 || provisionalEntry.afterSequence !== null && durableEntry.identity.sequence !== null && durableEntry.identity.sequence > provisionalEntry.afterSequence) && durableEntry.identity.runId && durableEntry.identity.runId === provisionalEntry.identity.runId && (readSessionProjectionString(durableMetadata?.mirrorOrigin) === null || durableMetadata?.runTerminal === true)) return true;
|
|
1590
|
+
if (provisionalEntry.live && !durableSegment && !isSessionProjectionToolContinuation(durableEntry.message) && (!isToolUsePersistedFinalRow(durableEntry.message) || readFinalContentIdentity(durableEntry.message) === readFinalContentIdentity(provisionalEntry.message)) && provisionalEntry.identity.sequence === null && (provisionalEntry.afterSequence === void 0 || provisionalEntry.afterSequence !== null && durableEntry.identity.sequence !== null && durableEntry.identity.sequence > provisionalEntry.afterSequence) && durableEntry.identity.runId && durableEntry.identity.runId === provisionalEntry.identity.runId && (readSessionProjectionString(durableMetadata?.mirrorOrigin) === null || durableMetadata?.runTerminal === true)) return true;
|
|
1426
1591
|
}
|
|
1427
1592
|
const persisted = left.identity;
|
|
1428
1593
|
const observed = right.identity;
|
|
@@ -1470,28 +1635,54 @@ function insertEntry(entries, incoming, runs) {
|
|
|
1470
1635
|
...entries.slice(nextIndex)
|
|
1471
1636
|
];
|
|
1472
1637
|
}
|
|
1473
|
-
function projectLiveSessionMessage(
|
|
1638
|
+
function projectLiveSessionMessage(initialState, message, envelope, scope = {}) {
|
|
1639
|
+
let state = initialState;
|
|
1474
1640
|
if (!scopesMatch(state.scope, scope)) return state;
|
|
1475
|
-
const incoming =
|
|
1641
|
+
const incoming = createSessionProjectionEntry(message, {
|
|
1476
1642
|
envelope,
|
|
1477
1643
|
live: true
|
|
1478
1644
|
});
|
|
1479
1645
|
if (!incoming.identity) return state;
|
|
1480
1646
|
const matches = state.entries.filter((entry) => entryMatches(entry, incoming));
|
|
1481
1647
|
const existing = matches.find((entry) => sameTranscriptIdentity(entry.identity, incoming.identity)) ?? (matches.length === 1 ? matches[0] : void 0);
|
|
1648
|
+
if (existing && !sameTranscriptIdentity(existing.identity, incoming.identity) && !sameAssistantPersistenceReceipt(existing.identity, incoming.identity)) {
|
|
1649
|
+
const durable = existing.identity?.id ? existing : incoming.identity.id ? incoming : null;
|
|
1650
|
+
const provisional = durable === existing ? incoming : existing;
|
|
1651
|
+
if (durable && isToolUsePersistedFinalRow(durable.message)) {
|
|
1652
|
+
const history = state.entries.filter((entry) => entry !== provisional);
|
|
1653
|
+
const snapshot = durable === incoming ? insertEntry(history, durable) : history;
|
|
1654
|
+
const runId = provisional.identity?.runId;
|
|
1655
|
+
const run = runId ? state.runs[runId] : void 0;
|
|
1656
|
+
const terminalMatch = findUniqueSnapshotTerminalMatch(provisional, [durable], run, snapshot);
|
|
1657
|
+
if (!terminalMatch) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1658
|
+
if (terminalMatch.inferred && durable.identity && runId && run) {
|
|
1659
|
+
const inferredSnapshotTerminal = {
|
|
1660
|
+
entry: provisional,
|
|
1661
|
+
matchedIdentity: durable.identity
|
|
1662
|
+
};
|
|
1663
|
+
state = {
|
|
1664
|
+
...state,
|
|
1665
|
+
runs: {
|
|
1666
|
+
...state.runs,
|
|
1667
|
+
[runId]: {
|
|
1668
|
+
...run,
|
|
1669
|
+
inferredSnapshotTerminal
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1482
1676
|
if (!existing) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1483
1677
|
const existingIndex = state.entries.indexOf(existing);
|
|
1484
1678
|
if (existing.message === message && existing.live && !existing.pending) return state;
|
|
1485
1679
|
if (!existing.pending && existing.identity?.id && !incoming.identity.id) return state;
|
|
1486
1680
|
if (incoming.identity.sequence !== null && (existing.pending || existing.identity?.sequence === null)) {
|
|
1487
1681
|
const sequence = incoming.identity.sequence;
|
|
1488
|
-
|
|
1682
|
+
const violatesOrder = state.entries.some(({ identity }, index) => identity?.sequence != null && (index < existingIndex ? identity.sequence > sequence : identity.sequence < sequence));
|
|
1683
|
+
return withEntries(state, violatesOrder ? insertEntry(state.entries.filter((_, index) => index !== existingIndex), incoming, state.runs) : state.entries.toSpliced(existingIndex, 1, incoming));
|
|
1489
1684
|
}
|
|
1490
|
-
return withEntries(state,
|
|
1491
|
-
...state.entries.slice(0, existingIndex),
|
|
1492
|
-
incoming,
|
|
1493
|
-
...state.entries.slice(existingIndex + 1)
|
|
1494
|
-
]);
|
|
1685
|
+
return withEntries(state, state.entries.toSpliced(existingIndex, 1, incoming));
|
|
1495
1686
|
}
|
|
1496
1687
|
/** Only observed live events and this client's pending turns may survive an older snapshot. */
|
|
1497
1688
|
function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options = {}) {
|
|
@@ -1502,9 +1693,10 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
|
|
|
1502
1693
|
for (const current of state.entries) {
|
|
1503
1694
|
if (!current.live && !current.pending || options.shouldIncludeMessage?.(current.message) === false) continue;
|
|
1504
1695
|
const matches = entries.filter((entry) => entryMatches(entry, current, true));
|
|
1696
|
+
const uniqueMatch = matches.length === 1 ? matches[0] : void 0;
|
|
1505
1697
|
const run = current.identity?.runId ? runs[current.identity.runId] : void 0;
|
|
1506
1698
|
const terminalMatch = findUniqueSnapshotTerminalMatch(current, matches, run, entries);
|
|
1507
|
-
if (
|
|
1699
|
+
if (uniqueMatch && (sameAssistantPersistenceReceipt(uniqueMatch.identity, current.identity) || !isUnsequencedLiveTerminal(current, run)) || terminalMatch) {
|
|
1508
1700
|
if (terminalMatch?.inferred && terminalMatch.entry.identity && current.identity?.runId && run) runs[current.identity.runId] = {
|
|
1509
1701
|
...run,
|
|
1510
1702
|
inferredSnapshotTerminal: {
|
|
@@ -1614,7 +1806,7 @@ function reduceSessionProjection(state, event) {
|
|
|
1614
1806
|
case "messagePersisted": return projectLiveSessionMessage(state, event.message, event.envelope ?? event, scope);
|
|
1615
1807
|
case "sendPending": {
|
|
1616
1808
|
const pendingRunId = normalizeSessionProjectionRunId(event.idempotencyKey ?? event.runId);
|
|
1617
|
-
const incoming =
|
|
1809
|
+
const incoming = createSessionProjectionEntry(event.message, { pendingRunId });
|
|
1618
1810
|
if (!pendingRunId || !incoming.identity) return state;
|
|
1619
1811
|
const seed = state.entries.find((entry) => entry.message === event.message);
|
|
1620
1812
|
if (seed && !seed.pending && incoming.identity.id === null && !incoming.identity.isImported && incoming.identity.runId === pendingRunId) return withEntries(state, state.entries.map((entry) => entry === seed ? {
|
|
@@ -1883,4 +2075,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
|
|
|
1883
2075
|
return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
|
|
1884
2076
|
}
|
|
1885
2077
|
//#endregion
|
|
1886
|
-
export {
|
|
2078
|
+
export { resolveGatewayConnectScopes as A, GatewayProtocolRequestTimeoutError as C, gatewayOriginScope as D, gatewayCredentialScope as E, normalizeDeviceMetadataForAuth as F, shouldRetryGatewayWithDeviceToken as M, buildDeviceAuthPayload as N, GatewayBrowserDeviceAuthLifecycle as O, buildDeviceAuthPayloadV3 as P, GatewayProtocolRequestError as S, resolveModelCatalogConnect as T, readSessionMessageIdentity as _, createSessionProjection as a, shouldPauseGatewayReconnect as b, reduceSessionProjection as c, readSessionProjectionFinalMessageIdentity as d, isSessionProjectionErrorMessage as f, readAssistantStreamSegmentIdentity as g, normalizeSessionProjectionRunId as h, resetGatewaySessionMessageSubscriptionCoordinator as i, selectGatewayConnectAuth as j, buildGatewayConnectAuth as k, reduceSessionProjectionRunEvent as l, isLocallyOptimisticSessionMessage as m, getGatewaySessionMessageSubscriptionCoordinator as n, projectLiveSessionMessage as o, projectSessionTerminalReplyMessage as p, releaseGatewaySessionMessageSubscription as r, reconcileSessionProjectionSnapshot as s, GatewaySessionMessageSubscriptionCoordinator as t, hasSessionProjectionAcceptedFinal as u, readSessionMessageSequence as v, isGatewayProtocolResponseError as w, GatewayProtocolClient as x, isRecord as y };
|