@openclaw/gateway-client 2026.8.1-beta.2 → 2026.9.1-beta.1
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-device-auth-Dz6H7eXy.d.mts → browser-device-auth-DoPo-sPB.d.mts} +0 -1
- package/dist/browser.d.mts +5 -6
- package/dist/browser.mjs +1 -1
- package/dist/index.d.mts +14 -4
- package/dist/index.mjs +360 -989
- package/dist/{protocol-client-BbN-I582.d.mts → protocol-client-C-viHJgO.d.mts} +2 -2
- package/dist/{protocol-request-D4mVhLxE.d.mts → protocol-request-r0jkQ1XE.d.mts} +0 -1
- package/dist/{readiness--KX7i8Mq.d.mts → readiness-CJ1QsCtR.d.mts} +10 -3
- package/dist/readiness.d.mts +1 -1
- package/dist/scope-upgrade.d.mts +2 -3
- package/dist/{session-subscriptions-BTNpXybo.d.mts → session-subscriptions-DNKXLO-Z.d.mts} +12 -10
- package/dist/{session-subscriptions-uKmTyc3P.mjs → session-subscriptions-Uxa34zU5.mjs} +130 -93
- package/dist/websocket-data.d.mts +0 -1
- package/package.json +7 -4
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { n as GatewayProtocolRequestOptions, t as GatewayProtocolRequestError } from "./protocol-request-
|
|
1
|
+
import { n as GatewayProtocolRequestOptions, t as GatewayProtocolRequestError } from "./protocol-request-r0jkQ1XE.mjs";
|
|
2
2
|
import { ErrorShape, EventFrame, HelloOk } from "@openclaw/gateway-protocol";
|
|
3
|
-
|
|
4
3
|
//#region packages/gateway-client/src/pending-request.d.ts
|
|
5
4
|
type GatewayProtocolRequestTiming = {
|
|
6
5
|
id: string;
|
|
@@ -86,6 +85,7 @@ type GatewayProtocolClientOptions<TPlan> = {
|
|
|
86
85
|
notifyStoppedClose?: boolean;
|
|
87
86
|
onConnectError?: (error: Error) => void;
|
|
88
87
|
onSocketFactoryError?: (error: Error) => void;
|
|
88
|
+
onReconnectStopped?: (error: Error) => void;
|
|
89
89
|
onParseError?: (error: unknown) => void;
|
|
90
90
|
onEvent?: (event: EventFrame) => void;
|
|
91
91
|
onGap?: (info: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as GatewayProtocolRequestOptions, r as GatewayProtocolRequestTimeoutError, t as GatewayProtocolRequestError } from "./protocol-request-
|
|
1
|
+
import { n as GatewayProtocolRequestOptions, r as GatewayProtocolRequestTimeoutError, t as GatewayProtocolRequestError } from "./protocol-request-r0jkQ1XE.mjs";
|
|
2
|
+
import "./protocol-client-C-viHJgO.mjs";
|
|
2
3
|
import { ConnectParams, ErrorShape, EventFrame, HelloOk } from "@openclaw/gateway-protocol/frame-guards";
|
|
3
4
|
import { GatewayClientMode, GatewayClientName } from "@openclaw/gateway-protocol/client-info";
|
|
4
|
-
|
|
5
5
|
//#region packages/gateway-client/src/request-error.d.ts
|
|
6
6
|
declare class GatewayClientRequestError extends GatewayProtocolRequestError {
|
|
7
7
|
constructor(error: Partial<ErrorShape>);
|
|
@@ -70,6 +70,8 @@ declare function isGatewayConnectAssemblyError(value: unknown): value is Error;
|
|
|
70
70
|
type GatewayClientOptions = {
|
|
71
71
|
url?: string;
|
|
72
72
|
origin?: string;
|
|
73
|
+
/** Already-resolved edge-proxy auth headers (identity-aware proxy in front of the Gateway). */
|
|
74
|
+
edgeAuthHeaders?: Readonly<Record<string, string>>;
|
|
73
75
|
connectChallengeTimeoutMs?: number;
|
|
74
76
|
/**
|
|
75
77
|
* Server-side pre-auth handshake budget. Config-derived local clients use
|
|
@@ -80,7 +82,8 @@ type GatewayClientOptions = {
|
|
|
80
82
|
tickWatchTimeoutMs?: number;
|
|
81
83
|
requestTimeoutMs?: number;
|
|
82
84
|
token?: string;
|
|
83
|
-
bootstrapToken?: string;
|
|
85
|
+
bootstrapToken?: string;
|
|
86
|
+
/** Prefer one setup credential for the first successful device-auth exchange. */
|
|
84
87
|
preferBootstrapToken?: boolean;
|
|
85
88
|
deviceToken?: string;
|
|
86
89
|
password?: string;
|
|
@@ -99,6 +102,7 @@ type GatewayClientOptions = {
|
|
|
99
102
|
caps?: string[];
|
|
100
103
|
commands?: string[];
|
|
101
104
|
computerUse?: ConnectParams["computerUse"];
|
|
105
|
+
/** @deprecated Compatibility for the shipped v1 node-host connect envelope. */
|
|
102
106
|
workerRuns?: ConnectParams["workerRuns"];
|
|
103
107
|
permissions?: Record<string, boolean>;
|
|
104
108
|
pathEnv?: string;
|
|
@@ -112,6 +116,8 @@ type GatewayClientOptions = {
|
|
|
112
116
|
onHelloOk?: (hello: HelloOk) => void;
|
|
113
117
|
onConnectError?: (err: Error) => void;
|
|
114
118
|
onReconnectPaused?: (info: GatewayReconnectPausedInfo) => void;
|
|
119
|
+
/** Report retryable startup closes for clients that present connection progress. */
|
|
120
|
+
notifyOnStartupRetry?: boolean;
|
|
115
121
|
onClose?: (code: number, reason: string, info?: GatewayClientCloseInfo) => void;
|
|
116
122
|
onGap?: (info: {
|
|
117
123
|
expected: number;
|
|
@@ -150,6 +156,7 @@ declare class GatewayClient {
|
|
|
150
156
|
caps: string[];
|
|
151
157
|
commands: string[];
|
|
152
158
|
computerUse?: ConnectParams["computerUse"];
|
|
159
|
+
/** @deprecated Compatibility for the shipped v1 node-host connect envelope. */
|
|
153
160
|
workerRuns?: ConnectParams["workerRuns"];
|
|
154
161
|
}): void;
|
|
155
162
|
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-CJ1QsCtR.mjs";
|
|
2
2
|
export { EventLoopReadyWaiter, GatewayClientStartReadinessOptions, GatewayClientStartable, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };
|
package/dist/scope-upgrade.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { a as GatewayBrowserDeviceTokenStore } from "./browser-device-auth-
|
|
2
|
-
import { n as GatewayProtocolRequestOptions } from "./protocol-request-
|
|
3
|
-
|
|
1
|
+
import { a as GatewayBrowserDeviceTokenStore } from "./browser-device-auth-DoPo-sPB.mjs";
|
|
2
|
+
import { n as GatewayProtocolRequestOptions } from "./protocol-request-r0jkQ1XE.mjs";
|
|
4
3
|
//#region packages/gateway-client/src/scope-upgrade.d.ts
|
|
5
4
|
type ScopeUpgradeBinding = {
|
|
6
5
|
clientId: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { n as GatewayProtocolRequestOptions } from "./protocol-request-r0jkQ1XE.mjs";
|
|
1
2
|
//#region packages/gateway-client/src/device-auth.d.ts
|
|
2
3
|
declare function normalizeDeviceMetadataForAuth(value?: string | null): string;
|
|
3
4
|
type DeviceAuthPayloadParams = {
|
|
@@ -23,8 +24,7 @@ declare function gatewayOriginScope(gatewayUrl: string): string;
|
|
|
23
24
|
/** Normalizes the gateway URL scope used for browser credential records. */
|
|
24
25
|
declare function gatewayCredentialScope(gatewayUrl: string): string;
|
|
25
26
|
//#endregion
|
|
26
|
-
//#region packages/gateway-client/src/session-projection.d.ts
|
|
27
|
-
/** Browser-safe identity and replay rules shared by Gateway conversation clients. */
|
|
27
|
+
//#region packages/gateway-client/src/session-projection-message-identity.d.ts
|
|
28
28
|
type SessionMessageEnvelope = {
|
|
29
29
|
messageId?: unknown;
|
|
30
30
|
messageSeq?: unknown;
|
|
@@ -41,6 +41,14 @@ type SessionMessageIdentity = {
|
|
|
41
41
|
isImported: boolean;
|
|
42
42
|
externalSource: string | null;
|
|
43
43
|
};
|
|
44
|
+
/** History and status markers carry transcript order even when they have no chat role. */
|
|
45
|
+
declare function readSessionMessageSequence(message: unknown, envelope?: SessionMessageEnvelope): number | null;
|
|
46
|
+
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
47
|
+
declare function normalizeSessionProjectionRunId(value: unknown): string | null;
|
|
48
|
+
/** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
|
|
49
|
+
declare function readSessionMessageIdentity(message: unknown, envelope?: SessionMessageEnvelope): SessionMessageIdentity | null;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region packages/gateway-client/src/session-projection.d.ts
|
|
44
52
|
type SessionProjectionScope = {
|
|
45
53
|
sessionKey?: string;
|
|
46
54
|
sessionId?: string;
|
|
@@ -122,12 +130,6 @@ type SessionProjectionEvent = ScopedSessionProjectionEvent & ({
|
|
|
122
130
|
} | {
|
|
123
131
|
type: "reconnected";
|
|
124
132
|
});
|
|
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
133
|
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
132
134
|
declare function isLocallyOptimisticSessionMessage(message: unknown): boolean;
|
|
133
135
|
declare function createSessionProjection(scope?: SessionProjectionScope, messages?: readonly unknown[]): SessionProjectionState;
|
|
@@ -143,7 +145,7 @@ declare function reduceSessionProjectionRunEvent(projection: SessionProjectionSt
|
|
|
143
145
|
//#endregion
|
|
144
146
|
//#region packages/gateway-client/src/session-subscriptions.d.ts
|
|
145
147
|
type GatewaySessionMessageRequestClient = {
|
|
146
|
-
request<T = unknown>(method: string, params: Record<string, unknown
|
|
148
|
+
request<T = unknown>(method: string, params: Record<string, unknown>, options?: GatewayProtocolRequestOptions): Promise<T>;
|
|
147
149
|
};
|
|
148
150
|
type GatewaySessionMessageSubscription = {
|
|
149
151
|
key: string;
|
|
@@ -175,4 +177,4 @@ declare function getGatewaySessionMessageSubscriptionCoordinator(client: Gateway
|
|
|
175
177
|
declare function resetGatewaySessionMessageSubscriptionCoordinator(client: GatewaySessionMessageRequestClient): void;
|
|
176
178
|
declare function releaseGatewaySessionMessageSubscription(subscription: GatewaySessionMessageSubscription): Promise<void>;
|
|
177
179
|
//#endregion
|
|
178
|
-
export { gatewayCredentialScope as A,
|
|
180
|
+
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;
|
|
@@ -564,36 +562,36 @@ var GatewayPendingRequests = class {
|
|
|
564
562
|
pending.reject(this.opts.createRequestError?.(frame.error ?? {}) ?? new GatewayProtocolRequestError(frame.error ?? {}));
|
|
565
563
|
}
|
|
566
564
|
flush(error) {
|
|
567
|
-
|
|
568
|
-
|
|
565
|
+
const retired = [...this.pending];
|
|
566
|
+
this.pending.clear();
|
|
567
|
+
this.requestSequence = 0;
|
|
568
|
+
for (const [id, pending] of retired) {
|
|
569
569
|
pending.cleanup?.();
|
|
570
|
+
this.finishTiming(id, pending, false, "CLIENT_CLOSED");
|
|
570
571
|
pending.reject(error);
|
|
571
572
|
}
|
|
572
|
-
this.pending.clear();
|
|
573
|
-
this.retiredIds.clear();
|
|
574
|
-
this.collisionSuffix = 0;
|
|
575
573
|
}
|
|
576
574
|
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;
|
|
575
|
+
this.requestSequence += 1;
|
|
576
|
+
return `${this.requestSequence}:${this.opts.createRequestId()}`;
|
|
585
577
|
}
|
|
586
578
|
finishTiming(id, pending, ok, errorCode) {
|
|
587
579
|
const endedAtMs = this.opts.nowMs();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
580
|
+
try {
|
|
581
|
+
const onTiming = this.opts.onTiming;
|
|
582
|
+
if (onTiming === void 0 || onTiming === null) return;
|
|
583
|
+
Reflect.apply(onTiming, this.opts, [{
|
|
584
|
+
id,
|
|
585
|
+
method: pending.method,
|
|
586
|
+
ok,
|
|
587
|
+
durationMs: Math.max(0, endedAtMs - pending.startedAtMs),
|
|
588
|
+
startedAtMs: pending.startedAtMs,
|
|
589
|
+
endedAtMs,
|
|
590
|
+
errorCode
|
|
591
|
+
}]);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
this.opts.onCallbackError?.("request timing", error);
|
|
594
|
+
}
|
|
597
595
|
}
|
|
598
596
|
invoke(label, callback) {
|
|
599
597
|
try {
|
|
@@ -733,8 +731,12 @@ var GatewayProtocolClient = class {
|
|
|
733
731
|
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
734
732
|
this.opts.onSocketFactoryError?.(normalized);
|
|
735
733
|
this.opts.onConnectError?.(normalized);
|
|
736
|
-
if (this.opts.rethrowSocketFactoryError?.(normalized))
|
|
734
|
+
if (this.opts.rethrowSocketFactoryError?.(normalized)) {
|
|
735
|
+
if (this.generation > 0 && !this.stopped && !this.socket && !this.reconnectSignal) this.opts.onReconnectStopped?.(normalized);
|
|
736
|
+
throw normalized;
|
|
737
|
+
}
|
|
737
738
|
if (this.opts.shouldRetrySocketFactoryError?.(normalized) && !this.stopped && !this.socket && !this.reconnectSignal) this.scheduleReconnect();
|
|
739
|
+
else if (this.generation > 0 && !this.stopped && !this.socket && !this.reconnectSignal) this.opts.onReconnectStopped?.(normalized);
|
|
738
740
|
return;
|
|
739
741
|
}
|
|
740
742
|
this.generation = generation;
|
|
@@ -927,7 +929,7 @@ var GatewayProtocolClient = class {
|
|
|
927
929
|
const decision = this.opts.resolveClose(context);
|
|
928
930
|
this.requests.flush(decision.pendingError ?? context.connectFailure?.error ?? /* @__PURE__ */ new Error(`gateway closed (${code}): ${reason}`));
|
|
929
931
|
this.invoke("close", () => this.opts.onClose?.(context, decision));
|
|
930
|
-
if (decision.retry && !this.stopped) this.scheduleReconnect(decision.reconnectDelayMs ?? context.connectFailure?.reconnectDelayMs);
|
|
932
|
+
if (decision.retry && !this.stopped && !this.socket && !this.reconnectSignal) this.scheduleReconnect(decision.reconnectDelayMs ?? context.connectFailure?.reconnectDelayMs);
|
|
931
933
|
}
|
|
932
934
|
handleSocketError(socket, generation, error) {
|
|
933
935
|
if (!this.isActive(socket, generation) || this.connectSent) return;
|
|
@@ -942,7 +944,7 @@ var GatewayProtocolClient = class {
|
|
|
942
944
|
sleepWithAbort(retry.delayMs, retry.signal).then(() => {
|
|
943
945
|
if (this.reconnectSignal !== retry.signal) return;
|
|
944
946
|
this.reconnectSignal = null;
|
|
945
|
-
this.connect();
|
|
947
|
+
this.invoke("reconnect", () => this.connect());
|
|
946
948
|
}, () => {
|
|
947
949
|
if (this.reconnectSignal === retry.signal) this.reconnectSignal = null;
|
|
948
950
|
});
|
|
@@ -983,6 +985,7 @@ const NON_RECOVERABLE_AUTH_ERRORS = /* @__PURE__ */ new Set([
|
|
|
983
985
|
ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
|
|
984
986
|
ConnectErrorDetailCodes.AUTH_DEVICE_TOKEN_MISMATCH,
|
|
985
987
|
ConnectErrorDetailCodes.AUTH_SCOPE_MISMATCH,
|
|
988
|
+
ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED,
|
|
986
989
|
ConnectErrorDetailCodes.CONTROL_UI_BUILD_MISMATCH,
|
|
987
990
|
ConnectErrorDetailCodes.PAIRING_REQUIRED,
|
|
988
991
|
ConnectErrorDetailCodes.CONTROL_UI_DEVICE_IDENTITY_REQUIRED,
|
|
@@ -994,15 +997,74 @@ function shouldPauseGatewayReconnect(params) {
|
|
|
994
997
|
const pairing = readPairingConnectErrorDetails(params.details);
|
|
995
998
|
if (code === ConnectErrorDetailCodes.PAIRING_REQUIRED && (pairing?.pauseReconnect === false || pairing?.recommendedNextStep === "wait_then_retry")) return false;
|
|
996
999
|
if (code === ConnectErrorDetailCodes.AUTH_TOKEN_MISMATCH) return params.tokenMismatchIsTerminal === true && !params.deviceTokenRetryPending;
|
|
1000
|
+
if (code === ConnectErrorDetailCodes.AUTH_IDENTITY_HEADER_REQUIRED) return !params.deviceTokenRetryPending;
|
|
997
1001
|
return NON_RECOVERABLE_AUTH_ERRORS.has(code) || params.protocolMismatchIsTerminal === true && code === ConnectErrorDetailCodes.PROTOCOL_MISMATCH || params.clientVersionMismatchIsTerminal === true && code === ConnectErrorDetailCodes.CLIENT_VERSION_MISMATCH;
|
|
998
1002
|
}
|
|
999
1003
|
//#endregion
|
|
1004
|
+
//#region packages/normalization-core/src/record-coerce.ts
|
|
1005
|
+
/** Type guard for non-array object records at browser-safe boundaries. */
|
|
1006
|
+
function isRecord(value) {
|
|
1007
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1008
|
+
}
|
|
1009
|
+
/** Returns a non-array record or null. */
|
|
1010
|
+
function asNullableRecord(value) {
|
|
1011
|
+
return isRecord(value) ? value : null;
|
|
1012
|
+
}
|
|
1013
|
+
//#endregion
|
|
1014
|
+
//#region packages/gateway-client/src/session-projection-message-identity.ts
|
|
1015
|
+
function readSessionProjectionString(value) {
|
|
1016
|
+
return typeof value === "string" ? value.trim() || null : null;
|
|
1017
|
+
}
|
|
1018
|
+
function readPositiveSafeInteger(value) {
|
|
1019
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
1020
|
+
}
|
|
1021
|
+
/** History and status markers carry transcript order even when they have no chat role. */
|
|
1022
|
+
function readSessionMessageSequence(message, envelope) {
|
|
1023
|
+
return readPositiveSafeInteger(asNullableRecord(asNullableRecord(message)?.["__openclaw"])?.seq) ?? readPositiveSafeInteger(envelope?.messageSeq);
|
|
1024
|
+
}
|
|
1025
|
+
/** Run ownership normalizes a user-turn suffix without changing its persisted send key. */
|
|
1026
|
+
function normalizeSessionProjectionRunId(value) {
|
|
1027
|
+
const runId = readSessionProjectionString(value);
|
|
1028
|
+
return runId?.endsWith(":user") ? runId.slice(0, -5) || null : runId;
|
|
1029
|
+
}
|
|
1030
|
+
/** Persisted row facts win; assistant run ownership comes from its authoritative producer. */
|
|
1031
|
+
function readSessionMessageIdentity(message, envelope) {
|
|
1032
|
+
const record = asNullableRecord(message);
|
|
1033
|
+
const role = readSessionProjectionString(record?.role)?.toLowerCase();
|
|
1034
|
+
if (!record || !role) return null;
|
|
1035
|
+
const metadata = asNullableRecord(record["__openclaw"]);
|
|
1036
|
+
const importedFrom = readSessionProjectionString(metadata?.importedFrom);
|
|
1037
|
+
const cliSessionId = readSessionProjectionString(metadata?.cliSessionId);
|
|
1038
|
+
const externalId = readSessionProjectionString(metadata?.externalId);
|
|
1039
|
+
const idempotencyKey = readSessionProjectionString(metadata?.idempotencyKey) ?? readSessionProjectionString(record.idempotencyKey) ?? readSessionProjectionString(envelope?.idempotencyKey) ?? readSessionProjectionString(envelope?.clientRunId);
|
|
1040
|
+
const persistedRunId = normalizeSessionProjectionRunId(idempotencyKey);
|
|
1041
|
+
const envelopeRunId = normalizeSessionProjectionRunId(envelope?.runId);
|
|
1042
|
+
const metadataRunId = normalizeSessionProjectionRunId(metadata?.runId);
|
|
1043
|
+
const mirroredMessage = readSessionProjectionString(metadata?.mirrorOrigin) !== null;
|
|
1044
|
+
const isCliAssistant = role === "assistant" && readSessionProjectionString(record.api)?.toLowerCase() === "cli";
|
|
1045
|
+
const canonicalPersistedRunId = isCliAssistant && persistedRunId?.startsWith("cli-assistant:") ? readSessionProjectionString(persistedRunId.slice(14)) : persistedRunId;
|
|
1046
|
+
const optimisticRunId = metadata && Object.keys(metadata).every((key) => key === "idempotencyKey") ? canonicalPersistedRunId : null;
|
|
1047
|
+
return {
|
|
1048
|
+
role,
|
|
1049
|
+
id: readSessionProjectionString(metadata?.id) ?? readSessionProjectionString(envelope?.messageId),
|
|
1050
|
+
sequence: readSessionMessageSequence(message, envelope),
|
|
1051
|
+
idempotencyKey,
|
|
1052
|
+
runId: role === "assistant" ? metadataRunId ?? envelopeRunId ?? (isCliAssistant || !mirroredMessage ? canonicalPersistedRunId : null) ?? optimisticRunId : metadataRunId ?? canonicalPersistedRunId ?? envelopeRunId,
|
|
1053
|
+
isImported: Boolean(importedFrom || cliSessionId || externalId),
|
|
1054
|
+
externalSource: importedFrom && cliSessionId && externalId ? JSON.stringify([
|
|
1055
|
+
importedFrom,
|
|
1056
|
+
cliSessionId,
|
|
1057
|
+
externalId
|
|
1058
|
+
]) : null
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
//#endregion
|
|
1000
1062
|
//#region packages/gateway-client/src/session-projection-run-event.ts
|
|
1001
|
-
function readNonemptyString
|
|
1063
|
+
function readNonemptyString(value) {
|
|
1002
1064
|
return typeof value === "string" ? value.trim() || null : null;
|
|
1003
1065
|
}
|
|
1004
1066
|
function reduceSessionProjectionRunEventImpl(projection, event, scope = {}) {
|
|
1005
|
-
const runId = readNonemptyString
|
|
1067
|
+
const runId = readNonemptyString(event.runId);
|
|
1006
1068
|
if (!runId || typeof event.state !== "string" || ![
|
|
1007
1069
|
"delta",
|
|
1008
1070
|
"final",
|
|
@@ -1010,9 +1072,9 @@ function reduceSessionProjectionRunEventImpl(projection, event, scope = {}) {
|
|
|
1010
1072
|
"aborted"
|
|
1011
1073
|
].includes(event.state)) return null;
|
|
1012
1074
|
const message = event.message;
|
|
1013
|
-
const messageStopReason =
|
|
1014
|
-
const stopReason = readNonemptyString
|
|
1015
|
-
const errorKind = readNonemptyString
|
|
1075
|
+
const messageStopReason = isRecord(message) ? readNonemptyString(message.stopReason) : null;
|
|
1076
|
+
const stopReason = readNonemptyString(event.stopReason) ?? messageStopReason;
|
|
1077
|
+
const errorKind = readNonemptyString(event.errorKind);
|
|
1016
1078
|
const base = {
|
|
1017
1079
|
runId,
|
|
1018
1080
|
...message === void 0 ? {} : { message },
|
|
@@ -1047,53 +1109,11 @@ const SESSION_PROJECTION_SCOPE_KEYS = [
|
|
|
1047
1109
|
"lifecycleRevision",
|
|
1048
1110
|
"activeLeafEntryId"
|
|
1049
1111
|
];
|
|
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
1112
|
/** Local turns have no durable transcript metadata beyond their own optional send key. */
|
|
1093
1113
|
function isLocallyOptimisticSessionMessage(message) {
|
|
1094
1114
|
const identity = readSessionMessageIdentity(message);
|
|
1095
1115
|
if (!identity || identity.role !== "user" && identity.role !== "assistant") return false;
|
|
1096
|
-
const metadata =
|
|
1116
|
+
const metadata = asNullableRecord(asNullableRecord(message)?.["__openclaw"]);
|
|
1097
1117
|
return !metadata || Object.keys(metadata).every((key) => key === "idempotencyKey");
|
|
1098
1118
|
}
|
|
1099
1119
|
function createEntry(message, options) {
|
|
@@ -1153,10 +1173,12 @@ function entryMatches(left, right, allowSnapshotPromotion = false) {
|
|
|
1153
1173
|
if (sameTranscriptIdentity(left.identity, right.identity)) return true;
|
|
1154
1174
|
const durableEntry = left.identity?.id ? left : right.identity?.id ? right : null;
|
|
1155
1175
|
const provisionalEntry = durableEntry === left ? right : durableEntry === right ? left : null;
|
|
1156
|
-
|
|
1176
|
+
const durableMetadata = asNullableRecord(asNullableRecord(durableEntry?.message)?.["__openclaw"]);
|
|
1177
|
+
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
1178
|
const persisted = left.identity;
|
|
1158
1179
|
const observed = right.identity;
|
|
1159
|
-
|
|
1180
|
+
const persistedMetadata = asNullableRecord(asNullableRecord(left.message)?.["__openclaw"]);
|
|
1181
|
+
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
1182
|
if (left.pending && right.pending) return Boolean(left.identity?.role === right.identity?.role && left.pendingRunId && left.pendingRunId === right.pendingRunId);
|
|
1161
1183
|
const pending = left.pending ? left : right.pending ? right : null;
|
|
1162
1184
|
const authoritative = pending === left ? right : pending === right ? left : null;
|
|
@@ -1197,6 +1219,7 @@ function projectLiveSessionMessage(state, message, envelope, scope = {}) {
|
|
|
1197
1219
|
if (existingIndex < 0) return withEntries(state, insertEntry(state.entries, incoming, state.runs));
|
|
1198
1220
|
const existing = state.entries[existingIndex];
|
|
1199
1221
|
if (existing && existing.message === message && existing.live && !existing.pending) return state;
|
|
1222
|
+
if (existing && !existing.pending && existing.identity?.id && !incoming.identity.id) return state;
|
|
1200
1223
|
if (existing?.pending && incoming.identity.sequence !== null) {
|
|
1201
1224
|
const sequence = incoming.identity.sequence;
|
|
1202
1225
|
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 +1250,13 @@ function reconcileSessionProjectionSnapshot(state, messages, scope = {}, options
|
|
|
1227
1250
|
}
|
|
1228
1251
|
function hasDisplayableSessionMessage(message) {
|
|
1229
1252
|
if (typeof message === "string") return message.trim().length > 0;
|
|
1230
|
-
const record =
|
|
1253
|
+
const record = asNullableRecord(message);
|
|
1231
1254
|
if (!record) return false;
|
|
1232
1255
|
const displayableBlocks = Array.isArray(record.content) && record.content.some((block) => {
|
|
1233
|
-
const entry =
|
|
1234
|
-
return entry ? entry.type !== "text" ||
|
|
1256
|
+
const entry = asNullableRecord(block);
|
|
1257
|
+
return entry ? entry.type !== "text" || readSessionProjectionString(entry.text) !== null : typeof block === "string" && block.trim().length > 0;
|
|
1235
1258
|
});
|
|
1236
|
-
const media =
|
|
1259
|
+
const media = asNullableRecord(record["__openclaw"])?.media;
|
|
1237
1260
|
return Boolean(typeof record.content === "string" && record.content.trim() || displayableBlocks || Array.isArray(media) && media.length > 0);
|
|
1238
1261
|
}
|
|
1239
1262
|
function readSessionProjectionFinalMessageIdentity(message) {
|
|
@@ -1242,8 +1265,8 @@ function readSessionProjectionFinalMessageIdentity(message) {
|
|
|
1242
1265
|
if (identity?.externalSource) return `import:${identity.role}:${identity.externalSource}`;
|
|
1243
1266
|
if (identity?.id && !identity.isImported) return `id:${identity.role}:${identity.id}`;
|
|
1244
1267
|
if (identity?.sequence !== null && identity?.sequence !== void 0) return `seq:${identity.role}:${identity.sequence}`;
|
|
1245
|
-
const record =
|
|
1246
|
-
const metadata =
|
|
1268
|
+
const record = asNullableRecord(message);
|
|
1269
|
+
const metadata = asNullableRecord(record?.["__openclaw"]);
|
|
1247
1270
|
try {
|
|
1248
1271
|
return `content:${JSON.stringify([
|
|
1249
1272
|
identity?.role ?? "assistant",
|
|
@@ -1274,7 +1297,7 @@ function retainSessionProjectionRuns(runs) {
|
|
|
1274
1297
|
return Object.fromEntries([...active, ...retainedTerminal]);
|
|
1275
1298
|
}
|
|
1276
1299
|
function updateRun(state, incoming) {
|
|
1277
|
-
const incomingErrorMessage =
|
|
1300
|
+
const incomingErrorMessage = readSessionProjectionString(incoming.errorMessage);
|
|
1278
1301
|
const normalizedIncoming = { ...incoming };
|
|
1279
1302
|
if (incomingErrorMessage) normalizedIncoming.errorMessage = incomingErrorMessage;
|
|
1280
1303
|
else delete normalizedIncoming.errorMessage;
|
|
@@ -1285,7 +1308,7 @@ function updateRun(state, incoming) {
|
|
|
1285
1308
|
const canRecoverFinal = !hasDisplayableSessionMessage(current.message) || (current.acceptedFinalMessageIdentities?.length ?? 0) > 0;
|
|
1286
1309
|
const acceptFinal = incomingIsFinal && (current.status === incoming.status || canRecoverFinal) && incomingFinalIdentity !== null && !hasSessionProjectionAcceptedFinal(current, incoming.message);
|
|
1287
1310
|
const recoverMessage = acceptFinal && !hasDisplayableSessionMessage(current.message);
|
|
1288
|
-
const recoverError =
|
|
1311
|
+
const recoverError = readSessionProjectionString(current.errorMessage) === null && incomingErrorMessage !== null;
|
|
1289
1312
|
if (!acceptFinal && !recoverError) return state;
|
|
1290
1313
|
const firstFinalIdentity = readSessionProjectionFinalMessageIdentity(current.message);
|
|
1291
1314
|
const previousFinalIdentities = current.acceptedFinalMessageIdentities ?? (firstFinalIdentity ? [firstFinalIdentity] : []);
|
|
@@ -1433,7 +1456,7 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1433
1456
|
if (this.#retired) throw new Error("Session message subscription belongs to a replaced Gateway connection");
|
|
1434
1457
|
const existing = [...this.#entries].find((candidate) => candidate.agentId === agentId && (this.#areKeysEquivalent(candidate.key, normalizedKey) || [...candidate.requestedKeys].some((requestedKey) => this.#areKeysEquivalent(requestedKey, normalizedKey))));
|
|
1435
1458
|
if (!existing) {
|
|
1436
|
-
const provisional = [...this.#entries].find((candidate) => candidate.agentId === agentId && !candidate.canonicalSettled);
|
|
1459
|
+
const provisional = [...this.#entries].find((candidate) => candidate.agentId === agentId && !candidate.canonicalSettled && this.#couldShareCanonicalIdentity(candidate.key, normalizedKey));
|
|
1437
1460
|
if (provisional) {
|
|
1438
1461
|
await (provisional.plainFallback ?? provisional.ready).catch(() => void 0);
|
|
1439
1462
|
continue;
|
|
@@ -1489,7 +1512,7 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1489
1512
|
entry.release = tracked;
|
|
1490
1513
|
return tracked;
|
|
1491
1514
|
}
|
|
1492
|
-
const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId)).then(() => {
|
|
1515
|
+
const tracked = this.#client.request("sessions.messages.unsubscribe", sessionSubscriptionParams(entry.key, entry.agentId), { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).then(() => {
|
|
1493
1516
|
this.#finishRelease(subscription, owner, true);
|
|
1494
1517
|
}).finally(() => {
|
|
1495
1518
|
if (entry.release === tracked) entry.release = null;
|
|
@@ -1564,9 +1587,18 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1564
1587
|
return upgrade;
|
|
1565
1588
|
}
|
|
1566
1589
|
async #requestSubscribe(entry, includeApprovals) {
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
...
|
|
1590
|
+
const params = sessionSubscriptionParams(entry.key, entry.agentId);
|
|
1591
|
+
const result = await this.#client.request("sessions.messages.subscribe", includeApprovals ? {
|
|
1592
|
+
...params,
|
|
1593
|
+
includeApprovals: true
|
|
1594
|
+
} : params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS }).catch(async (error) => {
|
|
1595
|
+
if (!(error instanceof GatewayProtocolRequestTimeoutError) || !error.requestSent || this.#retired) throw error;
|
|
1596
|
+
try {
|
|
1597
|
+
await this.#client.request(entry.handles.size > 0 ? "sessions.messages.subscribe" : "sessions.messages.unsubscribe", params, { timeoutMs: DEFAULT_GATEWAY_REQUEST_TIMEOUT_MS });
|
|
1598
|
+
} catch (recoveryError) {
|
|
1599
|
+
if (!this.#retired) throw new AggregateError([error, recoveryError], "session message subscription recovery failed", { cause: recoveryError });
|
|
1600
|
+
}
|
|
1601
|
+
throw error;
|
|
1570
1602
|
});
|
|
1571
1603
|
const response = result && typeof result === "object" ? result : null;
|
|
1572
1604
|
const responseKey = response && "key" in response ? response.key : void 0;
|
|
@@ -1584,6 +1616,11 @@ var GatewaySessionMessageSubscriptionCoordinator = class {
|
|
|
1584
1616
|
#areKeysEquivalent(left, right) {
|
|
1585
1617
|
return left === right || this.#keysEquivalent?.(left, right) === true;
|
|
1586
1618
|
}
|
|
1619
|
+
#couldShareCanonicalIdentity(left, right) {
|
|
1620
|
+
const leftBody = left.replace(/^agent:[^:]+:/i, "").toLowerCase();
|
|
1621
|
+
const rightBody = right.replace(/^agent:[^:]+:/i, "").toLowerCase();
|
|
1622
|
+
return leftBody === rightBody || leftBody === "main" || rightBody === "main" || leftBody === "global" || rightBody === "global";
|
|
1623
|
+
}
|
|
1587
1624
|
};
|
|
1588
1625
|
const sessionMessageSubscriptionOwners = /* @__PURE__ */ new WeakMap();
|
|
1589
1626
|
const sessionMessageSubscriptionCoordinators = /* @__PURE__ */ new WeakMap();
|
|
@@ -1602,4 +1639,4 @@ function releaseGatewaySessionMessageSubscription(subscription) {
|
|
|
1602
1639
|
return sessionMessageSubscriptionOwners.get(subscription)?.coordinator.release(subscription) ?? Promise.resolve();
|
|
1603
1640
|
}
|
|
1604
1641
|
//#endregion
|
|
1605
|
-
export {
|
|
1642
|
+
export { buildGatewayConnectAuth as C, buildDeviceAuthPayload as D, shouldRetryGatewayWithDeviceToken as E, buildDeviceAuthPayloadV3 as O, GatewayBrowserDeviceAuthLifecycle as S, selectGatewayConnectAuth as T, GatewayProtocolClient as _, createSessionProjection as a, gatewayCredentialScope as b, projectLiveSessionMessage as c, reduceSessionProjectionRunEvent as d, normalizeSessionProjectionRunId as f, shouldPauseGatewayReconnect as g, isRecord as h, resetGatewaySessionMessageSubscriptionCoordinator as i, normalizeDeviceMetadataForAuth as k, 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, GatewayProtocolRequestError as v, resolveGatewayConnectScopes as w, gatewayOriginScope as x, GatewayProtocolRequestTimeoutError as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/gateway-client",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.9.1-beta.1",
|
|
4
4
|
"description": "Reference WebSocket client for the OpenClaw Gateway protocol",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
@@ -62,9 +62,12 @@
|
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"ipaddr.js": "2.
|
|
66
|
-
"ws": "8.21.
|
|
67
|
-
"@openclaw/gateway-protocol": "2026.
|
|
65
|
+
"ipaddr.js": "2.5.0",
|
|
66
|
+
"ws": "8.21.3",
|
|
67
|
+
"@openclaw/gateway-protocol": "2026.9.1-beta.1"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@openclaw/normalization-core": "0.0.0-private"
|
|
68
71
|
},
|
|
69
72
|
"engines": {
|
|
70
73
|
"node": ">=22.19.0"
|