@openclaw/gateway-client 0.0.0 → 2026.7.2-beta.4

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.
@@ -0,0 +1,180 @@
1
+ import { ErrorShape, EventFrame, HelloOk } from "@openclaw/gateway-protocol";
2
+
3
+ //#region packages/gateway-client/src/protocol-client.d.ts
4
+ type GatewayProtocolSocket = {
5
+ isOpen: () => boolean;
6
+ send: (data: string) => void;
7
+ close: (code?: number, reason?: string) => void;
8
+ };
9
+ type GatewayProtocolSocketHandlers = {
10
+ open: () => void;
11
+ message: (data: string) => void;
12
+ close: (code: number, reason: string) => void;
13
+ error: (error: Error) => void;
14
+ };
15
+ type GatewayProtocolRequestOptions = {
16
+ timeoutMs?: number | null;
17
+ expectFinal?: boolean;
18
+ onSent?: () => void;
19
+ onAccepted?: (payload: unknown) => void;
20
+ signal?: AbortSignal;
21
+ };
22
+ type GatewayProtocolConnectContext<TPlan> = {
23
+ generation: number;
24
+ nonce: string | null;
25
+ plan: TPlan;
26
+ };
27
+ type GatewayProtocolCloseContext = {
28
+ code: number;
29
+ reason: string;
30
+ generation: number;
31
+ socketOpened: boolean;
32
+ helloReceived: boolean;
33
+ connectRequestSent: boolean;
34
+ connectFailure?: {
35
+ error: Error;
36
+ reconnectDelayMs?: number;
37
+ };
38
+ };
39
+ type GatewayProtocolConnectDecision = {
40
+ closeCode: number;
41
+ closeReason: string;
42
+ reconnectDelayMs?: number;
43
+ stop?: boolean;
44
+ error?: Error;
45
+ };
46
+ type GatewayProtocolCloseDecision = {
47
+ retry: boolean;
48
+ notify: boolean;
49
+ reconnectDelayMs?: number;
50
+ pendingError?: Error;
51
+ };
52
+ type GatewayProtocolTiming<TPlan> = {
53
+ phase: "socket-open" | "challenge" | "fallback" | "device-identity-ready" | "connect-plan-ready" | "request-sent" | "hello" | "failed";
54
+ generation: number;
55
+ durationMs: number;
56
+ phaseDurationMs: number;
57
+ hasChallenge: boolean;
58
+ usedFallback: boolean;
59
+ plan?: TPlan;
60
+ detail?: unknown;
61
+ };
62
+ type GatewayProtocolRequestTiming = {
63
+ id: string;
64
+ method: string;
65
+ ok: boolean;
66
+ durationMs: number;
67
+ startedAtMs: number;
68
+ endedAtMs: number;
69
+ errorCode?: string;
70
+ };
71
+ type GatewayProtocolClientOptions<TPlan> = {
72
+ createSocket: (handlers: GatewayProtocolSocketHandlers) => GatewayProtocolSocket;
73
+ createRequestId: () => string;
74
+ createRequestError?: (error: Partial<ErrorShape>) => GatewayProtocolRequestError;
75
+ createRequestTimeoutError?: (method: string, timeoutMs: number) => Error;
76
+ createRequestAbortError?: (method: string) => Error;
77
+ buildConnectPlan: (params: {
78
+ nonce: string | null;
79
+ generation: number;
80
+ }) => TPlan | Promise<TPlan>;
81
+ buildConnectParams: (plan: TPlan) => unknown;
82
+ onConnectPlanError?: (error: Error) => GatewayProtocolConnectDecision;
83
+ onConnectHello?: (hello: HelloOk, context: GatewayProtocolConnectContext<TPlan>) => void;
84
+ onHello?: (hello: HelloOk) => void;
85
+ onConnectFailure?: (error: GatewayProtocolRequestError, context: GatewayProtocolConnectContext<TPlan>) => GatewayProtocolConnectDecision;
86
+ resolveClose: (context: GatewayProtocolCloseContext) => GatewayProtocolCloseDecision;
87
+ onClose?: (context: GatewayProtocolCloseContext, decision: GatewayProtocolCloseDecision) => void;
88
+ notifyStoppedClose?: boolean;
89
+ onConnectError?: (error: Error) => void;
90
+ onSocketFactoryError?: (error: Error) => void;
91
+ onParseError?: (error: unknown) => void;
92
+ onEvent?: (event: EventFrame) => void;
93
+ onGap?: (info: {
94
+ expected: number;
95
+ received: number;
96
+ }) => void;
97
+ onActivity?: () => void;
98
+ onTiming?: (timing: GatewayProtocolTiming<TPlan>) => void;
99
+ onRequestTiming?: (timing: GatewayProtocolRequestTiming) => void;
100
+ onCallbackError?: (label: string, error: unknown) => void;
101
+ handshake: {
102
+ mode: "fallback";
103
+ timeoutMs: number;
104
+ } | {
105
+ mode: "require-challenge";
106
+ timeoutMs: number;
107
+ timeoutMessage?: (elapsedMs: number) => string;
108
+ };
109
+ reconnect: {
110
+ initialMs: number;
111
+ multiplier: number;
112
+ maxMs: number;
113
+ };
114
+ requestTimeoutMs?: number;
115
+ nowMs?: () => number;
116
+ rethrowSocketFactoryError?: (error: Error) => boolean;
117
+ };
118
+ declare class GatewayProtocolRequestError extends Error {
119
+ readonly code: string;
120
+ readonly gatewayCode: string;
121
+ readonly details?: unknown;
122
+ readonly retryable: boolean;
123
+ readonly retryAfterMs?: number;
124
+ constructor(error: Partial<ErrorShape>);
125
+ }
126
+ /**
127
+ * Browser-safe gateway wire client. Environment adapters own transport and auth
128
+ * policy; this class owns the single socket/handshake/reconnect/frame state machine.
129
+ */
130
+ declare class GatewayProtocolClient<TPlan> {
131
+ private readonly opts;
132
+ private socket;
133
+ private readonly pending;
134
+ private listeners;
135
+ private stopped;
136
+ private generation;
137
+ private lastSeq;
138
+ private connectNonce;
139
+ private connectSent;
140
+ private connectRequestSent;
141
+ private handshakeTimer;
142
+ private readonly reconnectSupervisor;
143
+ private socketOpened;
144
+ private helloReceived;
145
+ private connectFailure;
146
+ private connectTiming;
147
+ private stoppedSocket?;
148
+ constructor(opts: GatewayProtocolClientOptions<TPlan>);
149
+ get connected(): boolean;
150
+ get hasPendingRequests(): boolean;
151
+ get connecting(): boolean;
152
+ get hasUnboundedPendingRequests(): boolean;
153
+ start(): void;
154
+ stop(): void;
155
+ request<T = unknown>(method: string, params?: unknown, options?: GatewayProtocolRequestOptions): Promise<T>;
156
+ addEventListener(listener: (event: EventFrame) => void): () => void;
157
+ closeSocket(code?: number, reason?: string): void;
158
+ resetReconnectBackoff(initialMs: number): void;
159
+ recordTiming(phase: GatewayProtocolTiming<TPlan>["phase"], generation: number, plan?: TPlan, detail?: unknown): void;
160
+ private connect;
161
+ private handleOpen;
162
+ private armHandshakeTimer;
163
+ private sendConnect;
164
+ private handleConnectPlanError;
165
+ private sendConnectPlan;
166
+ private handleMessage;
167
+ private handleResponse;
168
+ private handleClose;
169
+ private handleSocketError;
170
+ private flushRequests;
171
+ private finishRequestTiming;
172
+ private scheduleReconnect;
173
+ private closeContext;
174
+ private isActive;
175
+ private nowMs;
176
+ private clearHandshakeTimer;
177
+ private invoke;
178
+ }
179
+ //#endregion
180
+ export { GatewayProtocolRequestTiming as a, GatewayProtocolTiming as c, GatewayProtocolRequestOptions as i, GatewayProtocolCloseContext as n, GatewayProtocolSocket as o, GatewayProtocolRequestError as r, GatewayProtocolSocketHandlers as s, GatewayProtocolClient as t };
@@ -0,0 +1,201 @@
1
+ import { i as GatewayProtocolRequestOptions, r as GatewayProtocolRequestError } from "./protocol-client-Cj1WAoMt.mjs";
2
+ import { ErrorShape, EventFrame, HelloOk } from "@openclaw/gateway-protocol/frame-guards";
3
+ import { GatewayClientMode, GatewayClientName } from "@openclaw/gateway-protocol/client-info";
4
+
5
+ //#region packages/gateway-client/src/client.d.ts
6
+ type DeviceIdentity = {
7
+ deviceId: string;
8
+ privateKeyPem: string;
9
+ publicKeyPem: string;
10
+ };
11
+ type DeviceAuthTokenRecord = {
12
+ token?: string;
13
+ scopes?: string[];
14
+ };
15
+ type GatewayClientHostDeps = {
16
+ loadOrCreateDeviceIdentity?: () => DeviceIdentity | undefined;
17
+ signDevicePayload?: (privateKeyPem: string, payload: string) => string;
18
+ publicKeyRawBase64UrlFromPem?: (publicKeyPem: string) => string;
19
+ loadDeviceAuthToken?: (params: {
20
+ deviceId: string;
21
+ role: string;
22
+ env?: NodeJS.ProcessEnv;
23
+ }) => DeviceAuthTokenRecord | null;
24
+ storeDeviceAuthToken?: (params: {
25
+ deviceId: string;
26
+ role: string;
27
+ token: string;
28
+ scopes: string[];
29
+ env?: NodeJS.ProcessEnv;
30
+ }) => void;
31
+ clearDeviceAuthToken?: (params: {
32
+ deviceId: string;
33
+ role: string;
34
+ env?: NodeJS.ProcessEnv;
35
+ }) => void;
36
+ beforeConnect?: () => void;
37
+ registerGatewayLoopbackBypass?: (url: string) => (() => void) | undefined;
38
+ logDebug?: (message: string) => void;
39
+ logError?: (message: string) => void;
40
+ redactForLog?: (message: string) => string;
41
+ normalizeTlsFingerprint?: (fingerprint: string | undefined) => string;
42
+ };
43
+ type GatewayClientRequestOptions = GatewayProtocolRequestOptions;
44
+ type GatewayReconnectPausedInfo = {
45
+ code: number;
46
+ reason: string;
47
+ detailCode: string | null;
48
+ };
49
+ type GatewayClientCloseInfo = {
50
+ phase: "pre-hello" | "post-hello";
51
+ socketOpened: boolean;
52
+ transportValidated: boolean;
53
+ transientPreHelloCleanClose: boolean;
54
+ };
55
+ declare class GatewayClientRequestError extends GatewayProtocolRequestError {
56
+ constructor(error: Partial<ErrorShape>);
57
+ }
58
+ declare function isGatewayConnectAssemblyError(value: unknown): value is Error;
59
+ type GatewayClientOptions = {
60
+ url?: string;
61
+ origin?: string;
62
+ connectChallengeTimeoutMs?: number;
63
+ /**
64
+ * Server-side pre-auth handshake budget. Config-derived local clients use
65
+ * this to keep the connect-challenge watchdog aligned with the gateway.
66
+ */
67
+ preauthHandshakeTimeoutMs?: number;
68
+ tickWatchMinIntervalMs?: number;
69
+ tickWatchTimeoutMs?: number;
70
+ requestTimeoutMs?: number;
71
+ token?: string;
72
+ bootstrapToken?: string;
73
+ deviceToken?: string;
74
+ password?: string;
75
+ approvalRuntimeToken?: string;
76
+ agentRuntimeIdentityToken?: string;
77
+ instanceId?: string;
78
+ clientName?: GatewayClientName;
79
+ clientDisplayName?: string;
80
+ clientVersion?: string;
81
+ platform?: string;
82
+ deviceFamily?: string;
83
+ mode?: GatewayClientMode;
84
+ role?: string;
85
+ scopes?: string[];
86
+ caps?: string[];
87
+ commands?: string[];
88
+ permissions?: Record<string, boolean>;
89
+ pathEnv?: string;
90
+ env?: NodeJS.ProcessEnv;
91
+ deviceIdentity?: DeviceIdentity | null;
92
+ hostDeps?: GatewayClientHostDeps;
93
+ minProtocol?: number;
94
+ maxProtocol?: number;
95
+ tlsFingerprint?: string;
96
+ onEvent?: (evt: EventFrame) => void;
97
+ onHelloOk?: (hello: HelloOk) => void;
98
+ onConnectError?: (err: Error) => void;
99
+ onReconnectPaused?: (info: GatewayReconnectPausedInfo) => void;
100
+ onClose?: (code: number, reason: string, info?: GatewayClientCloseInfo) => void;
101
+ onGap?: (info: {
102
+ expected: number;
103
+ received: number;
104
+ }) => void;
105
+ };
106
+ type GatewayClientConnectionMetadata = {
107
+ clientName?: GatewayClientName;
108
+ hasDeviceIdentity: boolean;
109
+ mode?: GatewayClientMode;
110
+ preauthHandshakeTimeoutMs?: number;
111
+ };
112
+ declare class GatewayClient {
113
+ private readonly protocol;
114
+ private ws;
115
+ private opts;
116
+ private deps;
117
+ private stopped;
118
+ private pendingDeviceTokenRetry;
119
+ private deviceTokenRetryBudgetUsed;
120
+ private approvalRuntimeTokenCompatibilityDisabled;
121
+ private approvalRuntimeTokenRetryBudgetUsed;
122
+ private lastTick;
123
+ private tickIntervalMs;
124
+ private tickTimer;
125
+ private readonly requestTimeoutMs;
126
+ private pendingStop;
127
+ private transportValidated;
128
+ private suppressedTransientPreHelloCleanCloses;
129
+ constructor(opts: GatewayClientOptions);
130
+ getConnectionMetadata(): GatewayClientConnectionMetadata;
131
+ updateNodeManifest(manifest: {
132
+ caps: string[];
133
+ commands: string[];
134
+ }): void;
135
+ start(): void;
136
+ private createSocket;
137
+ stop(): void;
138
+ stopAndWait(opts?: {
139
+ timeoutMs?: number;
140
+ }): Promise<void>;
141
+ private beginStop;
142
+ private createPendingStop;
143
+ private resolvePendingStop;
144
+ private logDebug;
145
+ private logError;
146
+ private assembleConnectParams;
147
+ private buildDeviceConnectParams;
148
+ private handleConnectHello;
149
+ private handleConnectRequestFailure;
150
+ private resolveClose;
151
+ private closeInfo;
152
+ private clearStaleDeviceTokenForClose;
153
+ private notifyConnectError;
154
+ private notifyReconnectPaused;
155
+ private shouldRetryWithoutApprovalRuntimeToken;
156
+ private shouldFailClosedForUnsupportedAgentRuntimeIdentity;
157
+ private isTrustedDeviceRetryEndpoint;
158
+ private selectConnectAuth;
159
+ private startTickWatch;
160
+ private validateTlsFingerprint;
161
+ request<T = Record<string, unknown>>(method: string, params?: unknown, opts?: GatewayClientRequestOptions): Promise<T>;
162
+ }
163
+ //#endregion
164
+ //#region packages/gateway-client/src/event-loop-ready.d.ts
165
+ /** Readiness probe outcome with timing data for diagnosing event-loop stalls. */
166
+ type EventLoopReadyResult = {
167
+ ready: boolean;
168
+ elapsedMs: number;
169
+ maxDriftMs: number;
170
+ checks: number;
171
+ aborted: boolean;
172
+ };
173
+ /** Controls how aggressively the client waits for low-drift timer checks before starting IO. */
174
+ type EventLoopReadyOptions = {
175
+ maxWaitMs?: number;
176
+ intervalMs?: number;
177
+ driftThresholdMs?: number;
178
+ consecutiveReadyChecks?: number;
179
+ signal?: AbortSignal;
180
+ };
181
+ /** Waits until timer drift stays low for consecutive checks, or aborts/times out. */
182
+ declare function waitForEventLoopReady(options?: EventLoopReadyOptions): Promise<EventLoopReadyResult>;
183
+ //#endregion
184
+ //#region packages/gateway-client/src/readiness.d.ts
185
+ type GatewayClientStartable = {
186
+ start(): void;
187
+ };
188
+ /** Injectable readiness waiter used by tests and alternate event-loop probes. */
189
+ type EventLoopReadyWaiter = (options?: EventLoopReadyOptions) => Promise<EventLoopReadyResult>;
190
+ /** Timeout and abort controls for delaying client start until the loop can process IO. */
191
+ type GatewayClientStartReadinessOptions = {
192
+ timeoutMs?: number;
193
+ clientOptions?: Pick<GatewayClientOptions, "connectChallengeTimeoutMs" | "env" | "preauthHandshakeTimeoutMs">;
194
+ signal?: AbortSignal;
195
+ };
196
+ /** Starts a gateway client only after the supplied readiness probe succeeds. */
197
+ declare function startGatewayClientWithReadinessWait(waitForReady: EventLoopReadyWaiter, client: GatewayClientStartable, options?: GatewayClientStartReadinessOptions): Promise<EventLoopReadyResult>;
198
+ /** Starts a gateway client after the default event-loop readiness probe succeeds. */
199
+ declare function startGatewayClientWhenEventLoopReady(client: GatewayClientStartable, options?: GatewayClientStartReadinessOptions): Promise<EventLoopReadyResult>;
200
+ //#endregion
201
+ export { GatewayClientRequestOptions as _, startGatewayClientWithReadinessWait as a, waitForEventLoopReady as c, GatewayClient as d, GatewayClientCloseInfo as f, GatewayClientRequestError as g, GatewayClientOptions as h, startGatewayClientWhenEventLoopReady as i, DeviceAuthTokenRecord as l, GatewayClientHostDeps as m, GatewayClientStartReadinessOptions as n, EventLoopReadyOptions as o, GatewayClientConnectionMetadata as p, GatewayClientStartable as r, EventLoopReadyResult as s, EventLoopReadyWaiter as t, DeviceIdentity as u, GatewayReconnectPausedInfo as v, isGatewayConnectAssemblyError as y };
@@ -0,0 +1,105 @@
1
+ import { resolveConnectChallengeTimeoutMs, resolveFiniteTimeoutDelayMs } from "./timeouts.mjs";
2
+ //#region packages/gateway-client/src/event-loop-ready.ts
3
+ const DEFAULT_MAX_WAIT_MS = 1e4;
4
+ const DEFAULT_INTERVAL_MS = 1;
5
+ const DEFAULT_DRIFT_THRESHOLD_MS = 200;
6
+ const DEFAULT_CONSECUTIVE_READY_CHECKS = 2;
7
+ function resolvePositiveInteger(value, fallback) {
8
+ return Number.isFinite(value) && value !== void 0 ? Math.max(1, Math.floor(value)) : fallback;
9
+ }
10
+ /** Waits until timer drift stays low for consecutive checks, or aborts/times out. */
11
+ async function waitForEventLoopReady(options = {}) {
12
+ const maxWaitMs = resolveFiniteTimeoutDelayMs(options.maxWaitMs, DEFAULT_MAX_WAIT_MS, { minMs: 0 });
13
+ const intervalMs = resolveFiniteTimeoutDelayMs(options.intervalMs, DEFAULT_INTERVAL_MS);
14
+ const driftThresholdMs = resolvePositiveInteger(options.driftThresholdMs, DEFAULT_DRIFT_THRESHOLD_MS);
15
+ const consecutiveReadyChecks = resolvePositiveInteger(options.consecutiveReadyChecks, DEFAULT_CONSECUTIVE_READY_CHECKS);
16
+ const signal = options.signal;
17
+ const startedAt = Date.now();
18
+ let readyChecks = 0;
19
+ let checks = 0;
20
+ let maxDriftMs = 0;
21
+ return await new Promise((resolve) => {
22
+ let settled = false;
23
+ let timer = null;
24
+ const clearTimer = () => {
25
+ if (timer) {
26
+ clearTimeout(timer);
27
+ timer = null;
28
+ }
29
+ };
30
+ const finish = (ready, aborted = false) => {
31
+ if (settled) return;
32
+ settled = true;
33
+ clearTimer();
34
+ signal?.removeEventListener("abort", onAbort);
35
+ resolve({
36
+ ready,
37
+ elapsedMs: Math.max(0, Date.now() - startedAt),
38
+ maxDriftMs,
39
+ checks,
40
+ aborted
41
+ });
42
+ };
43
+ const onAbort = () => {
44
+ finish(false, true);
45
+ };
46
+ if (signal?.aborted) {
47
+ finish(false, true);
48
+ return;
49
+ }
50
+ signal?.addEventListener("abort", onAbort, { once: true });
51
+ const scheduleNext = () => {
52
+ if (signal?.aborted) {
53
+ finish(false, true);
54
+ return;
55
+ }
56
+ const elapsedMs = Math.max(0, Date.now() - startedAt);
57
+ const remainingMs = maxWaitMs - elapsedMs;
58
+ if (remainingMs <= 0) {
59
+ finish(false);
60
+ return;
61
+ }
62
+ const delayMs = Math.min(intervalMs, remainingMs);
63
+ const scheduledAt = Date.now();
64
+ timer = setTimeout(() => {
65
+ timer = null;
66
+ checks += 1;
67
+ const driftMs = Math.max(0, Date.now() - scheduledAt - delayMs);
68
+ maxDriftMs = Math.max(maxDriftMs, driftMs);
69
+ if (driftMs > driftThresholdMs) readyChecks = 0;
70
+ else readyChecks += 1;
71
+ if (readyChecks >= consecutiveReadyChecks) {
72
+ finish(true);
73
+ return;
74
+ }
75
+ scheduleNext();
76
+ }, delayMs);
77
+ };
78
+ scheduleNext();
79
+ });
80
+ }
81
+ //#endregion
82
+ //#region packages/gateway-client/src/readiness.ts
83
+ function resolveGatewayClientStartReadinessTimeoutMs(options = {}) {
84
+ if (typeof options.timeoutMs === "number" && Number.isFinite(options.timeoutMs)) return options.timeoutMs;
85
+ const clientOptions = options.clientOptions ?? {};
86
+ return resolveConnectChallengeTimeoutMs(clientOptions.connectChallengeTimeoutMs, {
87
+ env: clientOptions.env,
88
+ configuredTimeoutMs: clientOptions.preauthHandshakeTimeoutMs
89
+ });
90
+ }
91
+ /** Starts a gateway client only after the supplied readiness probe succeeds. */
92
+ async function startGatewayClientWithReadinessWait(waitForReady, client, options = {}) {
93
+ const readiness = await waitForReady({
94
+ maxWaitMs: resolveGatewayClientStartReadinessTimeoutMs(options),
95
+ signal: options.signal
96
+ });
97
+ if (readiness.ready && !readiness.aborted && options.signal?.aborted !== true) client.start();
98
+ return readiness;
99
+ }
100
+ /** Starts a gateway client after the default event-loop readiness probe succeeds. */
101
+ async function startGatewayClientWhenEventLoopReady(client, options = {}) {
102
+ return startGatewayClientWithReadinessWait(waitForEventLoopReady, client, options);
103
+ }
104
+ //#endregion
105
+ export { startGatewayClientWithReadinessWait as n, waitForEventLoopReady as r, startGatewayClientWhenEventLoopReady as t };
@@ -0,0 +1,2 @@
1
+ import { a as startGatewayClientWithReadinessWait, i as startGatewayClientWhenEventLoopReady, n as GatewayClientStartReadinessOptions, r as GatewayClientStartable, t as EventLoopReadyWaiter } from "./readiness-CPiKyi1s.mjs";
2
+ export { EventLoopReadyWaiter, GatewayClientStartReadinessOptions, GatewayClientStartable, startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };
@@ -0,0 +1,3 @@
1
+ import "./timeouts.mjs";
2
+ import { n as startGatewayClientWithReadinessWait, t as startGatewayClientWhenEventLoopReady } from "./readiness-CrWxd4TI.mjs";
3
+ export { startGatewayClientWhenEventLoopReady, startGatewayClientWithReadinessWait };