mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
Some checks are pending
ClawSweeper Dispatch / dispatch (push) Waiting to run
CodeQL / Security High (actions) (push) Waiting to run
CodeQL / Security High (channel-runtime-boundary) (push) Waiting to run
CodeQL / Security High (core-auth-secrets) (push) Waiting to run
CodeQL / Security High (mcp-process-tool-boundary) (push) Waiting to run
CodeQL / Security High (network-ssrf-boundary) (push) Waiting to run
CodeQL / Security High (plugin-trust-boundary) (push) Waiting to run
CodeQL / Security High (process-exec-boundary) (push) Waiting to run
Control UI Locale Refresh / resolve-base (push) Waiting to run
Control UI Locale Refresh / Verify generated PR App permissions (push) Blocked by required conditions
Control UI Locale Refresh / Refresh (push) Blocked by required conditions
Control UI Locale Refresh / Commit control UI locale refresh (push) Blocked by required conditions
Docs Sync Publish Repo / sync-publish-repo (push) Waiting to run
Docs / docs (push) Waiting to run
Native App Locale Refresh / Refresh native th (push) Blocked by required conditions
Native App Locale Refresh / Refresh native tr (push) Blocked by required conditions
Native App Locale Refresh / Refresh native uk (push) Blocked by required conditions
Native App Locale Refresh / Refresh native vi (push) Blocked by required conditions
Native App Locale Refresh / Refresh native zh-CN (push) Blocked by required conditions
Native App Locale Refresh / Refresh native zh-TW (push) Blocked by required conditions
Native App Locale Refresh / Commit native locale refresh (push) Blocked by required conditions
Native App Locale Refresh / Refresh native nl (push) Blocked by required conditions
Native App Locale Refresh / Refresh native pl (push) Blocked by required conditions
Native App Locale Refresh / Refresh native pt-BR (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ru (push) Blocked by required conditions
Native App Locale Refresh / Refresh native sv (push) Blocked by required conditions
Native App Locale Refresh / resolve-base (push) Waiting to run
Native App Locale Refresh / Verify generated PR App permissions (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ar (push) Blocked by required conditions
Native App Locale Refresh / Refresh native de (push) Blocked by required conditions
Native App Locale Refresh / Refresh native es (push) Blocked by required conditions
Native App Locale Refresh / Refresh native fa (push) Blocked by required conditions
Native App Locale Refresh / Refresh native fr (push) Blocked by required conditions
Native App Locale Refresh / Refresh native hi (push) Blocked by required conditions
Native App Locale Refresh / Refresh native id (push) Blocked by required conditions
Native App Locale Refresh / Refresh native it (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ja-JP (push) Blocked by required conditions
Native App Locale Refresh / Refresh native ko (push) Blocked by required conditions
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Waiting to run
Plugin NPM Release / Validate release publish approval (push) Blocked by required conditions
Plugin NPM Release / preview_plugin_pack (push) Blocked by required conditions
Plugin NPM Release / Preflight plugin npm package () (push) Blocked by required conditions
Plugin NPM Release / Seal prepared plugin npm release (push) Blocked by required conditions
Plugin NPM Release / Trusted publisher OIDC exchange (push) Blocked by required conditions
Workflow Sanity / actionlint (push) Waiting to run
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Blocked by required conditions
Plugin Init Scaffold Validation / Validate provider scaffold (push) Waiting to run
Plugin NPM Release / preview_plugins_npm (push) Waiting to run
Plugin NPM Release / publish_plugins_npm (push) Blocked by required conditions
Plugin NPM Release / verify_plugins_npm (push) Blocked by required conditions
Vitest Cache Warm / warm (push) Waiting to run
Workflow Sanity / no-tabs (push) Waiting to run
Workflow Sanity / generated-doc-baselines (push) Waiting to run
185 lines
6.9 KiB
JavaScript
185 lines
6.9 KiB
JavaScript
import { isSupportedOpenClawNodeVersion, parseNodeReleaseVersion } from "./node-version.mjs";
|
|
|
|
// Self-contained so a different runtime can execute exactly the same probe with -e.
|
|
function probeSqlite(DatabaseSync) {
|
|
const result = { available: false, version: null, text: false, blob: false, json: false };
|
|
let database;
|
|
try {
|
|
database = new DatabaseSync(":memory:");
|
|
result.available = true;
|
|
const version = database.prepare("SELECT sqlite_version() AS version").get()?.version;
|
|
result.version = typeof version === "string" ? version : null;
|
|
const text = "a\u0000b\u0000";
|
|
const bytes = Buffer.from(text, "utf8");
|
|
const json = JSON.stringify({ value: text });
|
|
database.exec("CREATE TABLE probe (text_value TEXT, blob_value BLOB, json_value TEXT)");
|
|
database.prepare("INSERT INTO probe VALUES (?, ?, ?)").run(text, bytes, json);
|
|
const row = database.prepare("SELECT text_value, blob_value, json_value FROM probe").get();
|
|
result.text =
|
|
typeof row?.text_value === "string" &&
|
|
row.text_value.length === text.length &&
|
|
Buffer.from(row.text_value, "utf8").equals(bytes);
|
|
result.blob =
|
|
row?.blob_value instanceof Uint8Array && Buffer.from(row.blob_value).equals(bytes);
|
|
result.json = row?.json_value === json && JSON.parse(row.json_value).value === text;
|
|
} catch (error) {
|
|
result.error = error instanceof Error ? error.message : String(error);
|
|
} finally {
|
|
database?.close();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// The launcher and bundled runtime chunks must share one process-local result.
|
|
const capabilityCacheKey = Symbol.for("openclaw.sqliteCapabilities");
|
|
function unavailableSqliteCapabilities(error) {
|
|
return {
|
|
available: false,
|
|
version: null,
|
|
text: false,
|
|
blob: false,
|
|
json: false,
|
|
error: error instanceof Error ? error.message : String(error),
|
|
};
|
|
}
|
|
|
|
function isSqliteCapabilities(value) {
|
|
return (
|
|
value !== null &&
|
|
typeof value === "object" &&
|
|
typeof value.available === "boolean" &&
|
|
(value.version === null || typeof value.version === "string") &&
|
|
typeof value.text === "boolean" &&
|
|
typeof value.blob === "boolean" &&
|
|
typeof value.json === "boolean" &&
|
|
(value.error === undefined || typeof value.error === "string")
|
|
);
|
|
}
|
|
|
|
async function probeCurrentSqlite() {
|
|
let worker;
|
|
try {
|
|
const DatabaseSync = process.getBuiltinModule?.("node:sqlite")?.DatabaseSync;
|
|
if (typeof DatabaseSync !== "function") {
|
|
return unavailableSqliteCapabilities(new Error("node:sqlite is unavailable"));
|
|
}
|
|
// Keep the real capability check when Node's permission policy forbids workers.
|
|
if (!process.versions.bun && process.permission?.has("worker") === false) {
|
|
return probeSqlite(DatabaseSync);
|
|
}
|
|
const env = Object.fromEntries(
|
|
Object.entries(process.env).filter(([name]) => !/^(NODE_OPTIONS|BUN_OPTIONS)$/i.test(name)),
|
|
);
|
|
env.NODE_NO_WARNINGS = "1";
|
|
// Older diagnostic runtimes need SQLite enabled, but must not replay entry preloads.
|
|
const execArgv =
|
|
!process.versions.bun && process.allowedNodeEnvironmentFlags?.has("--experimental-sqlite")
|
|
? ["--experimental-sqlite"]
|
|
: [];
|
|
const { Worker } = await import("node:worker_threads");
|
|
worker = new Worker(
|
|
`const { parentPort } = require("node:worker_threads");
|
|
parentPort.postMessage(${SQLITE_CAPABILITY_PROBE});
|
|
parentPort.close();`,
|
|
{ eval: true, env, execArgv },
|
|
);
|
|
} catch (error) {
|
|
return unavailableSqliteCapabilities(error);
|
|
}
|
|
return new Promise((resolve) => {
|
|
let result;
|
|
let failure;
|
|
let retirement;
|
|
const fail = (error) => {
|
|
failure ??= { error };
|
|
retirement ??= Promise.resolve()
|
|
.then(() => worker.terminate())
|
|
.catch((error) => {
|
|
failure ??= { error };
|
|
});
|
|
};
|
|
const onMessage = (value) => {
|
|
if (result !== undefined || !isSqliteCapabilities(value)) {
|
|
fail(new Error("SQLite capability worker returned an invalid result"));
|
|
return;
|
|
}
|
|
result = value;
|
|
};
|
|
const onExit = (code) => {
|
|
worker.off("message", onMessage);
|
|
worker.off("error", fail);
|
|
worker.off("messageerror", fail);
|
|
if (failure) {
|
|
resolve(unavailableSqliteCapabilities(failure.error));
|
|
} else if (code !== 0 || result === undefined) {
|
|
resolve(
|
|
unavailableSqliteCapabilities(
|
|
new Error(`SQLite capability worker exited without a complete result (code ${code})`),
|
|
),
|
|
);
|
|
} else {
|
|
// The scratch database and worker must both close before startup is admitted.
|
|
resolve(result);
|
|
}
|
|
};
|
|
worker.on("message", onMessage);
|
|
worker.on("error", fail);
|
|
worker.on("messageerror", fail);
|
|
worker.once("exit", onExit);
|
|
});
|
|
}
|
|
|
|
export function detectCurrentSqliteCapabilities() {
|
|
// Publish the Promise before Worker construction can notify another startup caller.
|
|
globalThis[capabilityCacheKey] ??= Promise.resolve().then(probeCurrentSqlite);
|
|
return Promise.resolve(globalThis[capabilityCacheKey]);
|
|
}
|
|
|
|
export function isSqliteWalResetSafeVersion(value) {
|
|
const match = /^(\d+)\.(\d+)\.(\d+)$/u.exec(value.trim());
|
|
if (!match) {
|
|
return false;
|
|
}
|
|
const [major, minor, patch] = match.slice(1).map(Number);
|
|
if (![major, minor, patch].every(Number.isSafeInteger)) {
|
|
return false;
|
|
}
|
|
return (
|
|
major > 3 ||
|
|
(major === 3 &&
|
|
(minor > 51 ||
|
|
(minor === 51 && patch >= 3) ||
|
|
(minor === 50 && patch >= 7) ||
|
|
(minor === 44 && patch >= 6)))
|
|
);
|
|
}
|
|
|
|
export function nodeRuntimeFailure(version, probe) {
|
|
const label = `Node ${version ?? "unknown"}`;
|
|
if (!probe.available) {
|
|
return `${label}: node:sqlite is unavailable; use 24.16+/26.1+ or a build with the fix.`;
|
|
}
|
|
if (!probe.text && !probe.error) {
|
|
return `${label}: node:sqlite truncates TEXT at embedded NUL (nodejs/node#61954); use 24.16+/26.1+ or a build with the fix`;
|
|
}
|
|
if (probe.error || !probe.text || !probe.blob || !probe.json) {
|
|
return `${label}: node:sqlite NUL round-trip capability probe failed; use 24.16+/26.1+ or a build with the fix.`;
|
|
}
|
|
if (!probe.version || !isSqliteWalResetSafeVersion(probe.version)) {
|
|
return `${label}: SQLite ${probe.version ?? "unknown"} is not WAL-reset-safe; use SQLite 3.51.3+, 3.50.7+, or 3.44.6+ on its patched release line.`;
|
|
}
|
|
// Node 22/23 remain excluded until their separate compatibility work is complete.
|
|
const release = parseNodeReleaseVersion(version);
|
|
if (!release || release.major < 24) {
|
|
return `${label}: openclaw requires Node >=24.16.0 <25, or >=26.1.0.`;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function nodeRuntimeNote(version, probe) {
|
|
return !nodeRuntimeFailure(version, probe) && !isSupportedOpenClawNodeVersion(version)
|
|
? `Node ${version}: unsupported version, capability probe passed. Supported releases: 24.16+/26.1+.`
|
|
: null;
|
|
}
|
|
|
|
export const SQLITE_CAPABILITY_PROBE = `(${probeSqlite.toString()})(require("node:sqlite").DatabaseSync)`;
|