@openclaw/gateway-client 2026.8.1-beta.2 → 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/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +1 -1
- package/dist/index.d.mts +14 -3
- package/dist/index.mjs +345 -989
- package/dist/{readiness--KX7i8Mq.d.mts → readiness-JllUDKLg.d.mts} +6 -4
- package/dist/readiness.d.mts +1 -1
- package/dist/{session-subscriptions-BTNpXybo.d.mts → session-subscriptions-CKF7acYN.d.mts} +13 -10
- package/dist/{session-subscriptions-uKmTyc3P.mjs → session-subscriptions-OJnDJHPL.mjs} +113 -86
- package/package.json +5 -2
|
@@ -69,7 +69,8 @@ declare class GatewayClientRequestTimeoutError extends GatewayProtocolRequestTim
|
|
|
69
69
|
declare function isGatewayConnectAssemblyError(value: unknown): value is Error;
|
|
70
70
|
type GatewayClientOptions = {
|
|
71
71
|
url?: string;
|
|
72
|
-
origin?: string;
|
|
72
|
+
origin?: string; /** Already-resolved edge-proxy auth headers (identity-aware proxy in front of the Gateway). */
|
|
73
|
+
edgeAuthHeaders?: Readonly<Record<string, string>>;
|
|
73
74
|
connectChallengeTimeoutMs?: number;
|
|
74
75
|
/**
|
|
75
76
|
* Server-side pre-auth handshake budget. Config-derived local clients use
|
|
@@ -98,7 +99,7 @@ type GatewayClientOptions = {
|
|
|
98
99
|
scopes?: string[];
|
|
99
100
|
caps?: string[];
|
|
100
101
|
commands?: string[];
|
|
101
|
-
computerUse?: ConnectParams["computerUse"];
|
|
102
|
+
computerUse?: ConnectParams["computerUse"]; /** @deprecated Compatibility for the shipped v1 node-host connect envelope. */
|
|
102
103
|
workerRuns?: ConnectParams["workerRuns"];
|
|
103
104
|
permissions?: Record<string, boolean>;
|
|
104
105
|
pathEnv?: string;
|
|
@@ -111,7 +112,8 @@ type GatewayClientOptions = {
|
|
|
111
112
|
onEvent?: (evt: EventFrame) => void;
|
|
112
113
|
onHelloOk?: (hello: HelloOk) => void;
|
|
113
114
|
onConnectError?: (err: Error) => void;
|
|
114
|
-
onReconnectPaused?: (info: GatewayReconnectPausedInfo) => void;
|
|
115
|
+
onReconnectPaused?: (info: GatewayReconnectPausedInfo) => void; /** Report retryable startup closes for clients that present connection progress. */
|
|
116
|
+
notifyOnStartupRetry?: boolean;
|
|
115
117
|
onClose?: (code: number, reason: string, info?: GatewayClientCloseInfo) => void;
|
|
116
118
|
onGap?: (info: {
|
|
117
119
|
expected: number;
|
|
@@ -149,7 +151,7 @@ declare class GatewayClient {
|
|
|
149
151
|
updateNodeManifest(manifest: {
|
|
150
152
|
caps: string[];
|
|
151
153
|
commands: string[];
|
|
152
|
-
computerUse?: ConnectParams["computerUse"];
|
|
154
|
+
computerUse?: ConnectParams["computerUse"]; /** @deprecated Compatibility for the shipped v1 node-host connect envelope. */
|
|
153
155
|
workerRuns?: ConnectParams["workerRuns"];
|
|
154
156
|
}): void;
|
|
155
157
|
start(): void;
|
package/dist/readiness.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as startGatewayClientWithReadinessWait, i as startGatewayClientWhenEventLoopReady, n as GatewayClientStartReadinessOptions, r as GatewayClientStartable, t as EventLoopReadyWaiter } from "./readiness
|
|
1
|
+
import { a as startGatewayClientWithReadinessWait, i as startGatewayClientWhenEventLoopReady, n as GatewayClientStartReadinessOptions, r as GatewayClientStartable, t as EventLoopReadyWaiter } from "./readiness-JllUDKLg.mjs";
|
|
2
2
|
export { EventLoopReadyWaiter, GatewayClientStartReadinessOptions, GatewayClientStartable, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { n as GatewayProtocolRequestOptions } from "./protocol-request-D4mVhLxE.mjs";
|
|
2
|
+
|
|
1
3
|
//#region packages/gateway-client/src/device-auth.d.ts
|
|
2
4
|
declare function normalizeDeviceMetadataForAuth(value?: string | null): string;
|
|
3
5
|
type DeviceAuthPayloadParams = {
|
|
@@ -23,8 +25,7 @@ declare function gatewayOriginScope(gatewayUrl: string): string;
|
|
|
23
25
|
/** Normalizes the gateway URL scope used for browser credential records. */
|
|
24
26
|
declare function gatewayCredentialScope(gatewayUrl: string): string;
|
|
25
27
|
//#endregion
|
|
26
|
-
//#region packages/gateway-client/src/session-projection.d.ts
|
|
27
|
-
/** Browser-safe identity and replay rules shared by Gateway conversation clients. */
|
|
28
|
+
//#region packages/gateway-client/src/session-projection-message-identity.d.ts
|
|
28
29
|
type SessionMessageEnvelope = {
|
|
29
30
|
messageId?: unknown;
|
|
30
31
|
messageSeq?: unknown;
|
|
@@ -41,6 +42,14 @@ type SessionMessageIdentity = {
|
|
|
41
42
|
isImported: boolean;
|
|
42
43
|
externalSource: string | null;
|
|
43
44
|
};
|
|
45
|
+
/** History and status markers carry transcript order even when they have no chat role. */
|
|
46
|
+
declare function readSessionMessageSequence(message: unknown, envelope?: SessionMessageEnvelope): number | null;
|
|
47
|
+
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
48
|
+
declare function normalizeSessionProjectionRunId(value: unknown): string | null;
|
|
49
|
+
/** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
|
|
50
|
+
declare function readSessionMessageIdentity(message: unknown, envelope?: SessionMessageEnvelope): SessionMessageIdentity | null;
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region packages/gateway-client/src/session-projection.d.ts
|
|
44
53
|
type SessionProjectionScope = {
|
|
45
54
|
sessionKey?: string;
|
|
46
55
|
sessionId?: string;
|
|
@@ -122,12 +131,6 @@ type SessionProjectionEvent = ScopedSessionProjectionEvent & ({
|
|
|
122
131
|
} | {
|
|
123
132
|
type: "reconnected";
|
|
124
133
|
});
|
|
125
|
-
/** History and status markers carry transcript order even when they have no chat role. */
|
|
126
|
-
declare function readSessionMessageSequence(message: unknown, envelope?: SessionMessageEnvelope): number | null;
|
|
127
|
-
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
128
|
-
declare function normalizeSessionProjectionRunId(value: unknown): string | null;
|
|
129
|
-
/** Persisted transcript facts win over envelope projections and provider-local import IDs. */
|
|
130
|
-
declare function readSessionMessageIdentity(message: unknown, envelope?: SessionMessageEnvelope): SessionMessageIdentity | null;
|
|
131
134
|
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
132
135
|
declare function isLocallyOptimisticSessionMessage(message: unknown): boolean;
|
|
133
136
|
declare function createSessionProjection(scope?: SessionProjectionScope, messages?: readonly unknown[]): SessionProjectionState;
|
|
@@ -143,7 +146,7 @@ declare function reduceSessionProjectionRunEvent(projection: SessionProjectionSt
|
|
|
143
146
|
//#endregion
|
|
144
147
|
//#region packages/gateway-client/src/session-subscriptions.d.ts
|
|
145
148
|
type GatewaySessionMessageRequestClient = {
|
|
146
|
-
request<T = unknown>(method: string, params: Record<string, unknown
|
|
149
|
+
request<T = unknown>(method: string, params: Record<string, unknown>, options?: GatewayProtocolRequestOptions): Promise<T>;
|
|
147
150
|
};
|
|
148
151
|
type GatewaySessionMessageSubscription = {
|
|
149
152
|
key: string;
|
|
@@ -175,4 +178,4 @@ declare function getGatewaySessionMessageSubscriptionCoordinator(client: Gateway
|
|
|
175
178
|
declare function resetGatewaySessionMessageSubscriptionCoordinator(client: GatewaySessionMessageRequestClient): void;
|
|
176
179
|
declare function releaseGatewaySessionMessageSubscription(subscription: GatewaySessionMessageSubscription): Promise<void>;
|
|
177
180
|
//#endregion
|
|
178
|
-
export { gatewayCredentialScope as A,
|
|
181
|
+
export { gatewayCredentialScope as A, reduceSessionProjection as C, normalizeSessionProjectionRunId as D, SessionMessageIdentity as E, buildDeviceAuthPayload as M, buildDeviceAuthPayloadV3 as N, readSessionMessageIdentity as O, normalizeDeviceMetadataForAuth as P, reconcileSessionProjectionSnapshot as S, SessionMessageEnvelope as T, SessionProjectionState as _, GatewaySessionMessageSubscriptionOptions as a, isLocallyOptimisticSessionMessage as b, resetGatewaySessionMessageSubscriptionCoordinator as c, SessionProjectionGatewayRunEvent as d, SessionProjectionRun as f, SessionProjectionSnapshotOptions as g, SessionProjectionScope as h, GatewaySessionMessageSubscriptionCoordinatorOptions as i, gatewayOriginScope as j, readSessionMessageSequence as k, SessionProjectionEntry as l, SessionProjectionRunTransition as m, GatewaySessionMessageSubscription as n, getGatewaySessionMessageSubscriptionCoordinator as o, SessionProjectionRunStatus as p, GatewaySessionMessageSubscriptionCoordinator as r, releaseGatewaySessionMessageSubscription as s, GatewaySessionMessageRequestClient as t, SessionProjectionEvent as u, createSessionProjection as v, reduceSessionProjectionRunEvent as w, projectLiveSessionMessage as x, hasSessionProjectionAcceptedFinal as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clearGatewayConnectTimeout, resolveSafeTimeoutDelayMs, 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
|
|
@@ -462,8 +462,7 @@ var GatewayPendingRequests = class {
|
|
|
462
462
|
constructor(opts) {
|
|
463
463
|
this.opts = opts;
|
|
464
464
|
this.pending = /* @__PURE__ */ new Map();
|
|
465
|
-
this.
|
|
466
|
-
this.collisionSuffix = 0;
|
|
465
|
+
this.requestSequence = 0;
|
|
467
466
|
}
|
|
468
467
|
get hasPending() {
|
|
469
468
|
return this.pending.size > 0;
|
|
@@ -500,7 +499,6 @@ var GatewayPendingRequests = class {
|
|
|
500
499
|
const retire = (errorCode) => {
|
|
501
500
|
if (this.pending.get(id) !== pending) return false;
|
|
502
501
|
this.pending.delete(id);
|
|
503
|
-
this.retiredIds.add(id);
|
|
504
502
|
cleanup();
|
|
505
503
|
this.finishTiming(id, pending, false, errorCode);
|
|
506
504
|
return true;
|
|
@@ -570,30 +568,29 @@ var GatewayPendingRequests = class {
|
|
|
570
568
|
pending.reject(error);
|
|
571
569
|
}
|
|
572
570
|
this.pending.clear();
|
|
573
|
-
this.
|
|
574
|
-
this.collisionSuffix = 0;
|
|
571
|
+
this.requestSequence = 0;
|
|
575
572
|
}
|
|
576
573
|
allocateRequestId() {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
let uniqueId;
|
|
580
|
-
do {
|
|
581
|
-
this.collisionSuffix += 1;
|
|
582
|
-
uniqueId = `${id}:${this.collisionSuffix}`;
|
|
583
|
-
} while (this.pending.has(uniqueId) || this.retiredIds.has(uniqueId));
|
|
584
|
-
return uniqueId;
|
|
574
|
+
this.requestSequence += 1;
|
|
575
|
+
return `${this.requestSequence}:${this.opts.createRequestId()}`;
|
|
585
576
|
}
|
|
586
577
|
finishTiming(id, pending, ok, errorCode) {
|
|
587
578
|
const endedAtMs = this.opts.nowMs();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
+
}
|
|
597
594
|
}
|
|
598
595
|
invoke(label, callback) {
|
|
599
596
|
try {
|
|
@@ -983,6 +980,7 @@ const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
|
983
980
|
ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
|
|
984
981
|
ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH,
|
|
985
982
|
ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH,
|
|
983
|
+
ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED,
|
|
986
984
|
ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
|
|
987
985
|
ConnectErrorDetailCodes.PAIRING_REQUIRED,
|
|
988
986
|
ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
|
|
@@ -994,15 +992,74 @@ function shouldPauseGatewayReconnect(params) {
|
|
|
994
992
|
const pairing = readPairingConnectErrorDetails(params.details);
|
|
995
993
|
if (code === ConnectErrorDetailCodes.PAIRING_REQUIRED && (pairing?.pauseReconnect === false || pairing?.recommendedNextStep === "wait_then_retry")) return false;
|
|
996
994
|
if (code === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH) return params.tokenMismatchIsTerminal === true && !params.deviceTokenRetryPending;
|
|
995
|
+
if (code === ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED) return !params.deviceTokenRetryPending;
|
|
997
996
|
return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
|
|
998
997
|
}
|
|
999
998
|
//#endregion
|
|
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);
|
|
1003
|
+
}
|
|
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) {
|
|
1011
|
+
return typeof value === "string" ? value.trim() || null : null;
|
|
1012
|
+
}
|
|
1013
|
+
function readPositiveSafeInteger(value) {
|
|
1014
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
1015
|
+
}
|
|
1016
|
+
/** History and status markers carry transcript order even when they have no chat role. */
|
|
1017
|
+
function readSessionMessageSequence(message, envelope) {
|
|
1018
|
+
return readPositiveSafeInteger(asNullableRecord(asNullableRecord(message)?.["__openclaw"])?.seq) ?? readPositiveSafeInteger(envelope?.messageSeq);
|
|
1019
|
+
}
|
|
1020
|
+
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
1021
|
+
function normalizeSessionProjectionRunId(value) {
|
|
1022
|
+
const runId = readSessionProjectionString(value);
|
|
1023
|
+
return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
|
|
1024
|
+
}
|
|
1025
|
+
/** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
|
|
1026
|
+
function readSessionMessageIdentity(message, envelope) {
|
|
1027
|
+
const record = asNullableRecord(message);
|
|
1028
|
+
const role = readSessionProjectionString(record?.role)?.toLowerCase();
|
|
1029
|
+
if (!record || !role) return null;
|
|
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;
|
|
1042
|
+
return {
|
|
1043
|
+
role,
|
|
1044
|
+
id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
|
|
1045
|
+
sequence: readSessionMessageSequence(message, envelope),
|
|
1046
|
+
idempotencyKey,
|
|
1047
|
+
runId: role === "assistant" ? metadataRunId ?? envelopeRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) ?? optimisticRunId : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId,
|
|
1048
|
+
isImported: Boolean(importedFrom || cliSessionId || externalId),
|
|
1049
|
+
externalSource: importedFrom && cliSessionId && externalId ? JSON.stringify([
|
|
1050
|
+
importedFrom,
|
|
1051
|
+
cliSessionId,
|
|
1052
|
+
externalId
|
|
1053
|
+
]) : null
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1000
1057
|
//#region packages/gateway-client/src/session-projection-run-event.ts
|
|
1001
|
-
function readNonemptyString
|
|
1058
|
+
function readNonemptyString(value) {
|
|
1002
1059
|
return typeof value === "string" ? value.trim() || null : null;
|
|
1003
1060
|
}
|
|
1004
1061
|
function reduceSessionProjectionRunEventImpl(projection, event, scope = {}) {
|
|
1005
|
-
const runId = readNonemptyString
|
|
1062
|
+
const runId = readNonemptyString(event.runId);
|
|
1006
1063
|
if (!runId || typeof event.state !== "string" || ![
|
|
1007
1064
|
"delta",
|
|
1008
1065
|
"final",
|
|
@@ -1010,9 +1067,9 @@ function reduceSessionProjectionRunEventImpl(projection, event, scope = {}) {
|
|
|
1010
1067
|
"aborted"
|
|
1011
1068
|
].includes(event.state)) return null;
|
|
1012
1069
|
const message = event.message;
|
|
1013
|
-
const messageStopReason =
|
|
1014
|
-
const stopReason = readNonemptyString
|
|
1015
|
-
const errorKind = readNonemptyString
|
|
1070
|
+
const messageStopReason = isRecord(message) ? readNonemptyString(message.stopReason) : null;
|
|
1071
|
+
const stopReason = readNonemptyString(event.stopReason) ?? messageStopReason;
|
|
1072
|
+
const errorKind = readNonemptyString(event.errorKind);
|
|
1016
1073
|
const base = {
|
|
1017
1074
|
runId,
|
|
1018
1075
|
...message === void 0 ? {} : { message },
|
|
@@ -1047,53 +1104,11 @@ const SESSION_PROJECTION_SCOPE_KEYS = [
|
|
|
1047
1104
|
"lifecycleRevision",
|
|
1048
1105
|
"activeLeafEntryId"
|
|
1049
1106
|
];
|
|
1050
|
-
function readRecord(value) {
|
|
1051
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1052
|
-
}
|
|
1053
|
-
function readNonemptyString(value) {
|
|
1054
|
-
return typeof value === "string" ? value.trim() || null : null;
|
|
1055
|
-
}
|
|
1056
|
-
function readPositiveSafeInteger(value) {
|
|
1057
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
1058
|
-
}
|
|
1059
|
-
/** History and status markers carry transcript order even when they have no chat role. */
|
|
1060
|
-
function readSessionMessageSequence(message, envelope) {
|
|
1061
|
-
return readPositiveSafeInteger(readRecord(readRecord(message)?.["__openclaw"])?.seq) ?? readPositiveSafeInteger(envelope?.messageSeq);
|
|
1062
|
-
}
|
|
1063
|
-
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
1064
|
-
function normalizeSessionProjectionRunId(value) {
|
|
1065
|
-
const runId = readNonemptyString(value);
|
|
1066
|
-
return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
|
|
1067
|
-
}
|
|
1068
|
-
/** Persisted transcript facts win over envelope projections and provider-local import IDs. */
|
|
1069
|
-
function readSessionMessageIdentity(message, envelope) {
|
|
1070
|
-
const record = readRecord(message);
|
|
1071
|
-
const role = readNonemptyString(record?.role)?.toLowerCase();
|
|
1072
|
-
if (!record || !role) return null;
|
|
1073
|
-
const metadata = readRecord(record["__openclaw"]);
|
|
1074
|
-
const importedFrom = readNonemptyString(metadata?.importedFrom);
|
|
1075
|
-
const cliSessionId = readNonemptyString(metadata?.cliSessionId);
|
|
1076
|
-
const externalId = readNonemptyString(metadata?.externalId);
|
|
1077
|
-
const idempotencyKey = readNonemptyString(metadata?.idempotencyKey) ?? readNonemptyString(record.idempotencyKey) ?? readNonemptyString(envelope?.idempotencyKey) ?? readNonemptyString(envelope?.clientRunId);
|
|
1078
|
-
return {
|
|
1079
|
-
role,
|
|
1080
|
-
id: readNonemptyString(metadata?.id) ?? readNonemptyString(envelope?.messageId),
|
|
1081
|
-
sequence: readSessionMessageSequence(message, envelope),
|
|
1082
|
-
idempotencyKey,
|
|
1083
|
-
runId: normalizeSessionProjectionRunId(idempotencyKey) ?? normalizeSessionProjectionRunId(envelope?.runId),
|
|
1084
|
-
isImported: Boolean(importedFrom || cliSessionId || externalId),
|
|
1085
|
-
externalSource: importedFrom && cliSessionId && externalId ? JSON.stringify([
|
|
1086
|
-
importedFrom,
|
|
1087
|
-
cliSessionId,
|
|
1088
|
-
externalId
|
|
1089
|
-
]) : null
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
1107
|
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
1093
1108
|
function isLocallyOptimisticSessionMessage(message) {
|
|
1094
1109
|
const identity = readSessionMessageIdentity(message);
|
|
1095
1110
|
if (!identity || identity.role !== "user" && identity.role !== "assistant") return false;
|
|
1096
|
-
const metadata =
|
|
1111
|
+
const metadata = asNullableRecord(asNullableRecord(message)?.["__openclaw"]);
|
|
1097
1112
|
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
1098
1113
|
}
|
|
1099
1114
|
function createEntry(message, options) {
|
|
@@ -1153,10 +1168,12 @@ function entryMatches(left, right, allowSnapshotPromotion = false) {
|
|
|
1153
1168
|
if (sameTranscriptIdentity(left.identity, right.identity)) return true;
|
|
1154
1169
|
const durableEntry = left.identity?.id ? left : right.identity?.id ? right : null;
|
|
1155
1170
|
const provisionalEntry = durableEntry === left ? right : durableEntry === right ? left : null;
|
|
1156
|
-
|
|
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;
|
|
1157
1173
|
const persisted = left.identity;
|
|
1158
1174
|
const observed = right.identity;
|
|
1159
|
-
|
|
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;
|
|
1160
1177
|
if (left.pending && right.pending) return Boolean(left.identity?.role === right.identity?.role && left.pendingRunId && left.pendingRunId === right.pendingRunId);
|
|
1161
1178
|
const pending = left.pending ? left : right.pending ? right : null;
|
|
1162
1179
|
const authoritative = pending === left ? right : pending === right ? left : null;
|
|
@@ -1197,6 +1214,7 @@ function projectLiveSessionMessage(state, message, envelope, scope = {}) {
|
|
|
1197
1214
|
if (existingIndex < 0) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1198
1215
|
const existing = state.entries[existingIndex];
|
|
1199
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;
|
|
1200
1218
|
if (existing?.pending && incoming.identity.sequence !== null) {
|
|
1201
1219
|
const sequence = incoming.identity.sequence;
|
|
1202
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));
|
|
@@ -1227,13 +1245,13 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
|
|
|
1227
1245
|
}
|
|
1228
1246
|
function hasDisplayableSessionMessage(message) {
|
|
1229
1247
|
if (typeof message === "string") return message.trim().length > 0;
|
|
1230
|
-
const record =
|
|
1248
|
+
const record = asNullableRecord(message);
|
|
1231
1249
|
if (!record) return false;
|
|
1232
1250
|
const displayableBlocks = Array.isArray(record.content) && record.content.some((block) => {
|
|
1233
|
-
const entry =
|
|
1234
|
-
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;
|
|
1235
1253
|
});
|
|
1236
|
-
const media =
|
|
1254
|
+
const media = asNullableRecord(record["__openclaw"])?.media;
|
|
1237
1255
|
return Boolean(typeof record.content === "string" && record.content.trim() || displayableBlocks || Array.isArray(media) && media.length > 0);
|
|
1238
1256
|
}
|
|
1239
1257
|
function readSessionProjectionFinalMessageIdentity(message) {
|
|
@@ -1242,8 +1260,8 @@ function readSessionProjectionFinalMessageIdentity(message) {
|
|
|
1242
1260
|
if (identity?.externalSource) return `import:${identity.role}:${identity.externalSource}`;
|
|
1243
1261
|
if (identity?.id && !identity.isImported) return `id:${identity.role}:${identity.id}`;
|
|
1244
1262
|
if (identity?.sequence !== null && identity?.sequence !== void 0) return `seq:${identity.role}:${identity.sequence}`;
|
|
1245
|
-
const record =
|
|
1246
|
-
const metadata =
|
|
1263
|
+
const record = asNullableRecord(message);
|
|
1264
|
+
const metadata = asNullableRecord(record?.["__openclaw"]);
|
|
1247
1265
|
try {
|
|
1248
1266
|
return `content:${JSON.stringify([
|
|
1249
1267
|
identity?.role ?? "assistant",
|
|
@@ -1274,7 +1292,7 @@ function retainSessionProjectionRuns(runs) {
|
|
|
1274
1292
|
return Object.fromEntries([...active, ...retainedTerminal]);
|
|
1275
1293
|
}
|
|
1276
1294
|
function updateRun(state, incoming) {
|
|
1277
|
-
const incomingErrorMessage =
|
|
1295
|
+
const incomingErrorMessage = readSessionProjectionString(incoming.errorMessage);
|
|
1278
1296
|
const normalizedIncoming = { ...incoming };
|
|
1279
1297
|
if (incomingErrorMessage) normalizedIncoming.errorMessage = incomingErrorMessage;
|
|
1280
1298
|
else delete normalizedIncoming.errorMessage;
|
|
@@ -1285,7 +1303,7 @@ function updateRun(state, incoming) {
|
|
|
1285
1303
|
const canRecoverFinal = !hasDisplayableSessionMessage(current.message) || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0;
|
|
1286
1304
|
const acceptFinal = incomingIsFinal && (current.status === incoming.status || canRecoverFinal) && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
|
|
1287
1305
|
const recoverMessage = acceptFinal && !hasDisplayableSessionMessage(current.message);
|
|
1288
|
-
const recoverError =
|
|
1306
|
+
const recoverError = readSessionProjectionString(current.errorMessage) === null && incomingErrorMessage !== null;
|
|
1289
1307
|
if (!acceptFinal && !recoverError) return state;
|
|
1290
1308
|
const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
|
|
1291
1309
|
const previousFinalIdentities = current.acceptedFinalMessageIdentities ?? (firstFinalIdentity ? [firstFinalIdentity] : []);
|
|
@@ -1489,7 +1507,7 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1489
1507
|
entry.release = tracked;
|
|
1490
1508
|
return tracked;
|
|
1491
1509
|
}
|
|
1492
|
-
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(() => {
|
|
1493
1511
|
this.#finishRelease(subscription, owner, true);
|
|
1494
1512
|
}).finally(() => {
|
|
1495
1513
|
if (entry.release === tracked) entry.release = null;
|
|
@@ -1564,9 +1582,18 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1564
1582
|
return upgrade;
|
|
1565
1583
|
}
|
|
1566
1584
|
async #requestSubscribe(entry, includeApprovals) {
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
...
|
|
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;
|
|
1570
1597
|
});
|
|
1571
1598
|
const response = result && typeof result === "object" ? result : null;
|
|
1572
1599
|
const responseKey = response && "key" in response ? response.key : void 0;
|
|
@@ -1602,4 +1629,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
|
|
|
1602
1629
|
return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
|
|
1603
1630
|
}
|
|
1604
1631
|
//#endregion
|
|
1605
|
-
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,
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/gateway-client",
|
|
3
|
-
"version": "2026.8.1-beta.
|
|
3
|
+
"version": "2026.8.1-beta.3",
|
|
4
4
|
"description": "Reference WebSocket client for the OpenClaw Gateway protocol",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
@@ -64,7 +64,10 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"ipaddr.js": "2.4.0",
|
|
66
66
|
"ws": "8.21.1",
|
|
67
|
-
"@openclaw/gateway-protocol": "2026.8.1-beta.
|
|
67
|
+
"@openclaw/gateway-protocol": "2026.8.1-beta.3"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@openclaw/normalization-core": "0.0.0-private"
|
|
68
71
|
},
|
|
69
72
|
"engines": {
|
|
70
73
|
"node": ">=22.19.0"
|