Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
183 lines
5.3 KiB
TypeScript
183 lines
5.3 KiB
TypeScript
// Gateway Ws Client script supports OpenClaw repository automation.
|
|
import { randomUUID } from "node:crypto";
|
|
import WebSocket from "ws";
|
|
|
|
export type GatewayReqFrame = { type: "req"; id: string; method: string; params?: unknown };
|
|
export type GatewayResFrame = {
|
|
type: "res";
|
|
id: string;
|
|
ok: boolean;
|
|
payload?: unknown;
|
|
error?: unknown;
|
|
};
|
|
export type GatewayEventFrame = { type: "event"; event: string; seq?: number; payload?: unknown };
|
|
export type GatewayFrame =
|
|
| GatewayReqFrame
|
|
| GatewayResFrame
|
|
| GatewayEventFrame
|
|
| { type: string; [key: string]: unknown };
|
|
|
|
export function createArgReader(argv = process.argv.slice(2)) {
|
|
const get = (flag: string) => {
|
|
const idx = argv.indexOf(flag);
|
|
if (idx !== -1 && idx + 1 < argv.length) {
|
|
return argv[idx + 1];
|
|
}
|
|
return undefined;
|
|
};
|
|
const has = (flag: string) => argv.includes(flag);
|
|
return { argv, get, has };
|
|
}
|
|
|
|
export function resolveGatewayUrl(urlRaw: string): URL {
|
|
const url = new URL(urlRaw.includes("://") ? urlRaw : `wss://${urlRaw}`);
|
|
if (!url.port) {
|
|
url.port = url.protocol === "wss:" ? "443" : "80";
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function toText(data: WebSocket.RawData): string {
|
|
if (typeof data === "string") {
|
|
return data;
|
|
}
|
|
if (data instanceof ArrayBuffer) {
|
|
return Buffer.from(data).toString("utf8");
|
|
}
|
|
if (Array.isArray(data)) {
|
|
return Buffer.concat(data.map((chunk) => Buffer.from(chunk))).toString("utf8");
|
|
}
|
|
return Buffer.from(data as Buffer).toString("utf8");
|
|
}
|
|
|
|
export function createGatewayWsClient(params: {
|
|
url: string;
|
|
handshakeTimeoutMs?: number;
|
|
openTimeoutMs?: number;
|
|
openTimeoutMessage?: string;
|
|
onEvent?: (evt: GatewayEventFrame) => void;
|
|
}) {
|
|
const ws = new WebSocket(params.url, { handshakeTimeout: params.handshakeTimeoutMs ?? 8000 });
|
|
const pending = new Map<
|
|
string,
|
|
{
|
|
resolve: (res: GatewayResFrame) => void;
|
|
reject: (err: Error) => void;
|
|
timeout: ReturnType<typeof setTimeout>;
|
|
}
|
|
>();
|
|
|
|
const rejectPending = (error: Error) => {
|
|
for (const waiter of pending.values()) {
|
|
clearTimeout(waiter.timeout);
|
|
waiter.reject(error);
|
|
}
|
|
pending.clear();
|
|
};
|
|
|
|
const request = (method: string, paramsObj?: unknown, timeoutMs = 12_000) =>
|
|
new Promise<GatewayResFrame>((resolve, reject) => {
|
|
if (ws.readyState !== WebSocket.OPEN) {
|
|
reject(new Error(`gateway websocket is not open for ${method}`));
|
|
return;
|
|
}
|
|
const id = randomUUID();
|
|
const frame: GatewayReqFrame = { type: "req", id, method, params: paramsObj };
|
|
const timeout = setTimeout(() => {
|
|
pending.delete(id);
|
|
reject(new Error(`timeout waiting for ${method}`));
|
|
}, timeoutMs);
|
|
pending.set(id, { resolve, reject, timeout });
|
|
try {
|
|
ws.send(JSON.stringify(frame), (err) => {
|
|
if (!err) {
|
|
return;
|
|
}
|
|
const waiter = pending.get(id);
|
|
if (!waiter) {
|
|
return;
|
|
}
|
|
pending.delete(id);
|
|
clearTimeout(waiter.timeout);
|
|
waiter.reject(err instanceof Error ? err : new Error(String(err)));
|
|
});
|
|
} catch (err) {
|
|
pending.delete(id);
|
|
clearTimeout(timeout);
|
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
}
|
|
});
|
|
|
|
const waitOpen = () =>
|
|
new Promise<void>((resolve, reject) => {
|
|
const cleanup = () => {
|
|
clearTimeout(t);
|
|
ws.off("open", onOpen);
|
|
ws.off("error", onError);
|
|
ws.off("close", onClose);
|
|
};
|
|
const onOpen = () => {
|
|
cleanup();
|
|
resolve();
|
|
};
|
|
const onError = (err: Error) => {
|
|
cleanup();
|
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
};
|
|
const onClose = (code: number, reason: Buffer) => {
|
|
cleanup();
|
|
const suffix = reason.length > 0 ? `: ${reason.toString("utf8")}` : "";
|
|
reject(new Error(`closed before open (${code})${suffix}`));
|
|
};
|
|
const t = setTimeout(() => {
|
|
cleanup();
|
|
ws.terminate();
|
|
reject(new Error(params.openTimeoutMessage ?? "ws open timeout"));
|
|
}, params.openTimeoutMs ?? 8000);
|
|
ws.once("open", onOpen);
|
|
ws.once("error", onError);
|
|
ws.once("close", onClose);
|
|
});
|
|
|
|
ws.on("message", (data) => {
|
|
const text = toText(data);
|
|
let frame: GatewayFrame | null;
|
|
try {
|
|
frame = JSON.parse(text) as GatewayFrame;
|
|
} catch {
|
|
return;
|
|
}
|
|
if (!frame || typeof frame !== "object" || !("type" in frame)) {
|
|
return;
|
|
}
|
|
if (frame.type === "res") {
|
|
const res = frame as GatewayResFrame;
|
|
const waiter = pending.get(res.id);
|
|
if (waiter) {
|
|
pending.delete(res.id);
|
|
clearTimeout(waiter.timeout);
|
|
waiter.resolve(res);
|
|
}
|
|
return;
|
|
}
|
|
if (frame.type === "event") {
|
|
const evt = frame as GatewayEventFrame;
|
|
params.onEvent?.(evt);
|
|
}
|
|
});
|
|
ws.on("close", (code, reason) => {
|
|
const suffix = reason.length > 0 ? `: ${reason.toString("utf8")}` : "";
|
|
rejectPending(new Error(`gateway websocket closed (${code})${suffix}`));
|
|
});
|
|
ws.on("error", (err) => {
|
|
rejectPending(err instanceof Error ? err : new Error(String(err)));
|
|
});
|
|
|
|
const close = () => {
|
|
rejectPending(new Error("gateway websocket client closed"));
|
|
ws.close();
|
|
};
|
|
|
|
return { ws, request, waitOpen, close };
|
|
}
|