openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,185 lines • 57.5 kB
JavaScript
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, t as LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH } from "./package-lifecycle-marker-DhfhzqrA.mjs";
import { O as walkDirectory, w as root } from "./fs-safe-CS1DrPTK.mjs";
import { r as racePromiseWithAbortSignal } from "./abort-signal-Z3A36sLL.mjs";
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-BSb3HetY.mjs";
import { i as collectPackageDistInventory, t as PACKAGE_DIST_INVENTORY_RELATIVE_PATH } from "./package-dist-inventory-BxF3ZNuT.mjs";
import { c as sha256File } from "./directory-durability-B6uYEZLt.mjs";
import { t as _usingCtx } from "./usingCtx-DwOBYOJf.mjs";
import { t as runTasksWithConcurrency } from "./run-with-concurrency-Dtu208ef.mjs";
import { t as isRecord$1 } from "./record-shared-D7QlEuV3.mjs";
import { a as compareWorkerBundlePaths, o as hashWorkerBundleManifest } from "./worker-bundle-hash-D72fYtVD.mjs";
import { n as MAX_WORKER_BUNDLE_ARCHIVE_BYTES, t as DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS } from "./worker-bundle-limits-CF239rpc.mjs";
import { n as readWorkerBundleArchiveManifest } from "./worker-bundle-archive-BEdR_oRt.mjs";
import { createRequire } from "node:module";
import fs, { constants, createWriteStream } from "node:fs";
import path, { isAbsolute, join, win32 } from "node:path";
import fs$1 from "node:fs/promises";
import { isDeepStrictEqual } from "node:util";
import { createHash } from "node:crypto";
import { readFileHandleBounded } from "@openclaw/fs-safe/advanced";
import { satisfies, valid } from "semver";
import { Parser } from "acorn";
import { finished, pipeline } from "node:stream/promises";
import * as tar from "tar";
//#region scripts/lib/package-bundled-mcp.mts
const PATCHED_MCP_NAME = "chrome-devtools-mcp";
const PATCHED_MCP_CLI = "build/src/bin/chrome-devtools-mcp.js";
const MCP_FILE_HASHES = /* @__PURE__ */ new Map([["1.8.0", /* @__PURE__ */ new Map([
[PATCHED_MCP_CLI, "9f380d06e1ac05b257e27e708c0cc4b4ba190e285ed6eb6c8aa50978d98a12c5"],
["build/src/bin/chrome-devtools-mcp-main.js", "fc383cb3e5db5f18cf1e8c49221212c669825248874ba91c90ba9035e175f5b4"],
["LICENSE", "58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd"],
["build/src/third_party/THIRD_PARTY_NOTICES", "8f10277934fe6888173f41f7cbbd9112d208c8c931bf163db59110f69f119e53"],
["build/src/TextSnapshot.js", "299833ad0e4cfc171a417afaec41df594e4862fe53a7ada6ba160409f979788b"],
["build/src/McpPage.js", "b9e791d758e4d28589525e2d427600e24b271d365a5879893a392043a11cf426"],
["build/src/third_party/index.js", "a8f5cb1e02405d347117114141b58572f71c083861fb50ab31a27511e3a279bf"],
["build/src/OPENCLAW_PATCH_NOTICE.md", "8f5a32aaedf4bb6f8ad39f226bd343bf804132c11ebc6c3c19f667669856287c"]
])], ["1.9.0", /* @__PURE__ */ new Map([
[PATCHED_MCP_CLI, "9f380d06e1ac05b257e27e708c0cc4b4ba190e285ed6eb6c8aa50978d98a12c5"],
["build/src/bin/chrome-devtools-mcp-main.js", "5ca0e81196ab6b6e1481629a2dcae7f416b3b38f74555e3138ad5cdbb055e809"],
["LICENSE", "58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd"],
["build/src/third_party/THIRD_PARTY_NOTICES", "975febf536bb1c83c031a7888b90bdf1ffe8dc46000e0698eac364491dca8ac4"],
["build/src/TextSnapshot.js", "299833ad0e4cfc171a417afaec41df594e4862fe53a7ada6ba160409f979788b"],
["build/src/McpPage.js", "6d83dbd4d79c913664fbfa428d138b22fe4246612ab4dfcd93e9d8e62d5d6b51"],
["build/src/third_party/index.js", "7609bb6c575c7c1152b3f4233ad4b98d97885c62ccff7bd9ee29257ca8ffc83f"],
["build/src/OPENCLAW_PATCH_NOTICE.md", "0e53a04f337a3760f2f1adab9c20e3b4f07019795f503266c0b68e0f46d55a6c"]
])]]);
const REQUIRED_MCP_FILES = [
"build/src/third_party/devtools-formatter-worker.js",
"build/src/third_party/devtools-heap-snapshot-worker.js",
"build/src/third_party/lighthouse-devtools-mcp-bundle.js",
"build/src/third_party/bundled-packages.json"
];
/** The published MCP bundles contain optional UMD/BiDi imports; exact patches and assets own their artifact contract. */
function collectPatchedMcpArtifactErrors({ declaredVersion = "1.9.0", manifest, files, sha256 }) {
const hashes = typeof declaredVersion === "string" ? MCP_FILE_HASHES.get(declaredVersion) : void 0;
if (typeof declaredVersion !== "string" || !hashes) return [`package.json dependencies.${PATCHED_MCP_NAME} must be pinned to a supported patched version`];
const errors = [];
if (!isRecord$1(manifest) || manifest.version !== declaredVersion || manifest.type !== "module") errors.push(`bundled ${PATCHED_MCP_NAME} must be ESM version ${declaredVersion}`);
if (!isRecord$1(manifest) || !isRecord$1(manifest.bin) || manifest.bin["chrome-devtools-mcp"] !== `./build/src/bin/chrome-devtools-mcp.js`) errors.push(`bundled ${PATCHED_MCP_NAME} must expose CLI ${PATCHED_MCP_CLI}`);
for (const file of [...REQUIRED_MCP_FILES, ...hashes.keys()]) {
if (!files.has(file)) {
errors.push(`bundled ${PATCHED_MCP_NAME} is missing required runtime entry ${file}`);
continue;
}
const expectedHash = hashes.get(file);
if (expectedHash && sha256(file) !== expectedHash) errors.push(`bundled ${PATCHED_MCP_NAME} has unpatched or changed runtime entry ${file}`);
}
if (![...files].some((entry) => entry.startsWith("build/src/third_party/issue-descriptions/") && entry.endsWith(".md"))) errors.push(`bundled ${PATCHED_MCP_NAME} is missing third-party issue descriptions`);
return errors;
}
//#endregion
//#region scripts/lib/javascript-statements.mjs
var AppendOnlyScopeNames = class extends Array {
firstIndices = /* @__PURE__ */ new Map();
push(...names) {
const start = this.length;
const length = super.push(...names);
for (let index = 0; index < names.length; index += 1) {
const name = names[index];
if (!this.firstIndices.has(name)) this.firstIndices.set(name, start + index);
}
return length;
}
indexOf(name, fromIndex = 0) {
return fromIndex === 0 ? this.firstIndices.get(name) ?? -1 : super.indexOf(name, fromIndex);
}
};
const ArtifactParser = Parser.extend((BaseParser) => class extends BaseParser {
enterScope(flags) {
super.enterScope(flags);
const scope = this.currentScope();
scope.lexical = new AppendOnlyScopeNames();
scope.var = new AppendOnlyScopeNames();
scope.functions = new AppendOnlyScopeNames();
}
});
/**
* Visit completed statements without retaining the full Program AST.
* @param {string} source
* @param {{ sourceType: "script" | "module", allowHashBang?: boolean, allowReturnOutsideFunction?: boolean }} options
* @param {(statements: import("acorn").Program["body"]) => void} visit
*/
function visitJavaScriptStatements(source, options, visit) {
/** @type {import("acorn").Program} */
const program = {
type: "Program",
start: 0,
end: 0,
sourceType: options.sourceType,
body: []
};
const visitCompletedStatements = () => {
if (program.body.length > 0) visit(program.body.splice(0));
};
ArtifactParser.parse(source, {
ecmaVersion: "latest",
...options,
program,
onToken: visitCompletedStatements
});
visitCompletedStatements();
}
//#endregion
//#region scripts/lib/package-dist-imports.mjs
const JS_FILE_RE = /\.(?:cjs|js|mjs)$/u;
function normalizePackagePath(value) {
return value.replace(/\\/gu, "/").replace(/^package\//u, "");
}
function stripSpecifierSuffix(value) {
return value.replace(/[?#].*$/u, "");
}
function hasJavaScriptFileExtension(value) {
return /\.(?:cjs|js|mjs)$/u.test(path.posix.basename(stripSpecifierSuffix(value)));
}
function literal(node) {
if (node?.type === "Literal" && typeof node.value === "string") return node.value;
return node?.type === "TemplateLiteral" && node.expressions.length === 0 ? node.quasis[0].value.cooked : void 0;
}
function packageJsonsFor(params, files) {
return params.packageJsons ?? new Map(files.filter((file) => params.readText && path.posix.basename(file) === "package.json").map((file) => [file, params.readText(file)]));
}
function sourceTypeFor(importerPath, packageJsons) {
if (importerPath.endsWith(".cjs")) return "script";
if (importerPath.endsWith(".mjs")) return "module";
let directory = path.posix.dirname(importerPath);
while (true) {
const manifest = packageJsons.get(path.posix.join(directory, "package.json"));
if (manifest !== void 0) {
const type = JSON.parse(manifest).type;
return type === "module" ? "module" : type === "commonjs" ? "script" : void 0;
}
if (directory === "." || directory === "/") return;
directory = path.posix.dirname(directory);
}
}
function resolvesCommonJs(importedPath, fileSet, packageJsons) {
const extensions = [
"",
".js",
".json",
".node"
];
const loadFile = (target) => extensions.some((extension) => fileSet.has(target + extension));
const loadIndex = (directory) => extensions.slice(1).some((extension) => fileSet.has(path.posix.join(directory, "index" + extension)));
if (!importedPath.endsWith("/") && loadFile(importedPath)) return true;
const manifest = packageJsons.get(path.posix.join(importedPath, "package.json"));
const main = manifest === void 0 ? void 0 : JSON.parse(manifest).main;
if (typeof main === "string" && main) {
const target = path.posix.join(importedPath, main);
if (loadFile(target) || loadIndex(target)) return true;
}
return loadIndex(importedPath);
}
function appendImportEdges(source, importerPath, imports, sourceType) {
function visit(node) {
let kind;
let specifier;
if ([
"ImportDeclaration",
"ExportNamedDeclaration",
"ExportAllDeclaration"
].includes(node.type)) specifier = literal(node.source);
else if (node.type === "ImportExpression") specifier = literal(node.source);
else if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "require") {
kind = "require";
specifier = literal(node.arguments[0]);
} else if (node.type === "NewExpression" && node.callee.type === "Identifier" && node.callee.name === "URL" && node.arguments.length >= 2) {
const base = node.arguments[1];
if (base.type === "MemberExpression" && !base.computed && base.property.type === "Identifier" && base.property.name === "url" && base.object.type === "MetaProperty" && base.object.meta.name === "import" && base.object.property.name === "meta") {
kind = "import-meta-url";
specifier = literal(node.arguments[0]);
}
}
if (specifier?.startsWith(".") && (kind !== "import-meta-url" || hasJavaScriptFileExtension(specifier))) {
const importedPath = path.posix.normalize(path.posix.join(path.posix.dirname(importerPath), kind === "require" ? specifier : stripSpecifierSuffix(specifier)));
if (!(kind === "import-meta-url" && importerPath === "dist/managed-handoff-runtime.mjs" && importedPath === "dist/node_modules/koffi/indirect.cjs") && (kind !== "import-meta-url" || importedPath.startsWith("dist/"))) imports.push({
importerPath,
importedPath,
...kind === "require" ? { kind } : {}
});
}
for (const value of Object.values(node)) if (Array.isArray(value)) {
for (const child of value) if (child && typeof child.type === "string") visit(child);
} else if (value && typeof value.type === "string") visit(value);
}
const scan = (type) => visitJavaScriptStatements(source, {
sourceType: type,
allowReturnOutsideFunction: true
}, (statements) => {
for (const statement of statements) visit(statement);
});
if (sourceType) {
scan(sourceType);
return;
}
const start = imports.length;
try {
scan("script");
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
imports.length = start;
scan("module");
}
}
/** Collect missing-file errors for relative imports inside package files. */
function collectPackageDistImportErrors(params) {
const files = [...new Set(params.files.map(normalizePackagePath))];
const fileSet = new Set(files);
const errors = [];
const packageJsons = packageJsonsFor(params, files);
const imports = params.imports ?? collectPackageDistImports({
files,
readText: params.readText,
packageJsons
});
for (const { importerPath, importedPath, kind } of imports) if (!(kind === "require" ? resolvesCommonJs(importedPath, fileSet, packageJsons) : fileSet.has(importedPath))) errors.push(`${importerPath} imports missing ${importedPath}`);
return errors;
}
/** Collect relative dist import edges from package JavaScript files. */
function collectPackageDistImports(params) {
const files = params.files.length === 1 ? [normalizePackagePath(params.files[0])] : [...new Set(params.files.map(normalizePackagePath))].toSorted((left, right) => left.localeCompare(right));
const imports = [];
const packageJsons = packageJsonsFor(params, files);
for (const importerPath of files) {
if (!JS_FILE_RE.test(importerPath) || /(?:^|\/)node_modules\//u.test(importerPath)) continue;
appendImportEdges(params.readText(importerPath), importerPath, imports, sourceTypeFor(importerPath, packageJsons));
}
return imports;
}
//#endregion
//#region scripts/package-source-dependencies.mjs
const PRIVATE_WORKSPACE_VERSION = "0.0.0-private";
function dependenciesRecord(value, label) {
if (value === void 0) return {};
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${label} dependencies must be an object when present`);
return value;
}
function validateBundledPackageDependencyAlignment({ bundledDependencies, bundledPackageLabel, rootDependencies, rootPackageLabel = "root package.json" }) {
const bundled = dependenciesRecord(bundledDependencies, bundledPackageLabel);
const root = dependenciesRecord(rootDependencies, rootPackageLabel);
const aligned = [];
for (const [name, version] of Object.entries(bundled)) {
if (typeof version !== "string") throw new Error(`${bundledPackageLabel} dependency ${name} must declare a string version`);
if (version === PRIVATE_WORKSPACE_VERSION) continue;
const rootVersion = root[name];
if (rootVersion !== void 0 && typeof rootVersion !== "string") throw new Error(`${rootPackageLabel} dependency ${name} must declare a string version`);
if (rootVersion !== version && rootVersion !== `workspace:${version}`) throw new Error(`${rootPackageLabel} must declare ${name}@${version} to bundle ${bundledPackageLabel} without duplicate dependencies`);
aligned.push([name, version]);
}
return aligned;
}
//#endregion
//#region src/infra/package-plugin-composition.ts
/** Shared dependency ownership for release packages and private node distributions. */
function composePackagePlugins(packageJson, plugins) {
const composed = structuredClone(packageJson);
for (const { id, packageJson: plugin } of plugins) for (const section of ["dependencies", "optionalDependencies"]) {
const dependencies = plugin[section] ?? {};
for (const [name, spec] of Object.entries(dependencies)) if (valid(spec) !== spec) throw new Error(`Selected plugin ${id} requires an exact dependency pin: ${name}@${spec}`);
for (const existing of [composed.dependencies, composed.optionalDependencies]) validateBundledPackageDependencyAlignment({
bundledDependencies: dependencies,
bundledPackageLabel: `selected plugin ${id}`,
rootDependencies: {
...dependencies,
...existing
}
});
composed[section] = {
...composed[section],
...dependencies
};
}
for (const name of Object.keys(composed.dependencies ?? {})) delete composed.optionalDependencies?.[name];
for (const { id, packageJson: plugin } of plugins) for (const [name, range] of Object.entries(plugin.peerDependencies ?? {})) {
const version = name === composed.name ? composed.version : composed.dependencies?.[name] ?? composed.optionalDependencies?.[name];
if (!version && !plugin.peerDependenciesMeta?.[name]?.optional || version && !satisfies(version, range)) throw new Error(`Selected plugin ${id} requires peer ${name}@${range} in the distribution`);
}
const exclusions = new Set(plugins.map(({ id }) => `!dist/extensions/${id}/**`));
if (composed.files) composed.files = composed.files.filter((entry) => !exclusions.has(entry));
return composed;
}
//#endregion
//#region src/gateway/worker-environments/node-bootstrap-prebuilt.ts
/** Deployment-owned input; never write or remove this file in the running installation. */
const NODE_BOOTSTRAP_PREBUILT_ARCHIVE = "node-runtime.tgz";
async function copyNodeBootstrapPrebuiltArchive(packageRoot, temporaryRoot) {
if (process.platform === "win32") return;
const tarballPath = path.join(temporaryRoot, NODE_BOOTSTRAP_PREBUILT_ARCHIVE);
const source = await root(packageRoot, {
symlinks: "reject",
hardlinks: "allow",
nonBlockingRead: true
});
const destination = await root(temporaryRoot);
try {
await destination.copyIn(NODE_BOOTSTRAP_PREBUILT_ARCHIVE, {
root: source,
relativePath: NODE_BOOTSTRAP_PREBUILT_ARCHIVE
}, {
maxBytes: MAX_WORKER_BUNDLE_ARCHIVE_BYTES,
overwrite: false,
durable: false,
mode: 384
});
return await readWorkerBundleArchiveManifest(tarballPath, DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS);
} catch {
await fs$1.rm(tarballPath, { force: true });
return;
}
}
//#endregion
//#region src/infra/package-root-imports.ts
const require = createRequire(import.meta.url);
const namespaces = /* @__PURE__ */ new Map([
["node:module", "module"],
["module", "module"],
["node:process", "process"],
["process", "process"],
["node:path", "path"],
["path", "path"],
["node:fs", "fs"],
["fs", "fs"],
["node:fs/promises", "fs-promises"],
["fs/promises", "fs-promises"]
]);
const members = /* @__PURE__ */ new Map([
["module", /* @__PURE__ */ new Map([["createRequire", "factory"]])],
["process", /* @__PURE__ */ new Map([
["getBuiltinModule", "builtin"],
["cwd", "cwd"],
["chdir", "chdir"]
])],
["path", /* @__PURE__ */ new Map([["join", "join"], ["resolve", "resolve"]])],
["fs", /* @__PURE__ */ new Map([["realpathSync", "realpath"], ["promises", "fs-promises"]])],
["fs-promises", /* @__PURE__ */ new Map([["realpath", "realpath-async"]])]
]);
const ambient = /* @__PURE__ */ new Map([
["require", "root"],
["__filename", "location"],
["process", "process"]
]);
const loaders = /* @__PURE__ */ new Set([
"root",
"caller",
"unlocated"
]);
const snapshotsKinds = /* @__PURE__ */ new Set([
"caller-path",
"caller-string",
"caller",
"root"
]);
const callerValues = /* @__PURE__ */ new Set([
"input",
"caller-path",
"caller-string"
]);
const pathValues = /* @__PURE__ */ new Set([
...callerValues,
"literal",
"relative"
]);
const readableValues = /* @__PURE__ */ new Set([
...pathValues,
"module",
"process",
"path",
"fs",
"fs-promises",
"scalar"
]);
const pureCalls = /* @__PURE__ */ new Set([
"factory",
"builtin",
"join",
"resolve",
"realpath",
"realpath-async",
"cwd"
]);
function union(...values) {
const result = [...new Set(values.flat())];
return result.length ? result : ["unknown"];
}
/** Read dependency ownership without resolving or executing the inspected package. */
function collectPackageRootImports(source, onImport) {
const { parse } = require("@babel/parser");
const { default: traverse } = require("@babel/traverse");
const file = parse(source, {
sourceType: "unambiguous",
allowUndeclaredExports: true,
allowReturnOutsideFunction: true,
createImportExpressions: true
});
const imports = [];
const recordImport = (specifier, node, kind = "runtime") => {
imports.push(specifier);
if (onImport) {
if (node.start == null) throw new Error("Parsed package import is missing its source position");
onImport(specifier, node.start, kind);
}
};
const paths = /* @__PURE__ */ new Map();
const calls = [];
const scopes = [file.program];
const declarations = /* @__PURE__ */ new Map();
const assignments = [];
const cwdReferences = [];
const snapshots = /* @__PURE__ */ new Map();
let changedCwd = false;
function symbolAt(node) {
return paths.get(node)?.scope.getBinding(node.name);
}
function literal(node) {
return node?.type === "StringLiteral" ? node.value : void 0;
}
function property(node) {
return node.type === "Identifier" ? node.name : literal(node);
}
function memberName(node) {
return node.computed ? literal(node.property) : property(node.property);
}
function register(path, constant, inputScope) {
for (const name of Object.values(path.getBindingIdentifiers())) {
const symbol = path.scope.getBinding(name.name);
if (symbol) {
const entries = declarations.get(symbol) ?? [];
entries.push({
node: path.node,
name,
constant,
inputScope
});
declarations.set(symbol, entries);
}
}
}
function recordWrite(node, value) {
switch (node.type) {
case "Identifier":
assignments.push([node, value]);
break;
case "AssignmentPattern":
recordWrite(node.left);
break;
case "ObjectPattern":
for (const entry of node.properties) recordWrite(entry.type === "ObjectProperty" ? entry.value : entry.argument);
break;
case "ArrayPattern":
for (const entry of node.elements) if (entry) recordWrite(entry);
break;
case "RestElement": recordWrite(node.argument);
}
}
traverse(file, {
enter(path) {
paths.set(path.node, path);
},
ImportDeclaration(path) {
recordImport(path.node.source.value, path.node, "static");
for (const specifier of path.get("specifiers")) register(specifier, true);
},
ExportNamedDeclaration(path) {
if (path.node.source) recordImport(path.node.source.value, path.node, "static");
},
ExportAllDeclaration(path) {
recordImport(path.node.source.value, path.node, "static");
},
ImportExpression(path) {
const specifier = literal(path.node.source);
if (specifier !== void 0) recordImport(specifier, path.node);
},
"CallExpression|OptionalCallExpression"(path) {
if (path.isCallExpression() || path.isOptionalCallExpression()) calls.push(path.node);
},
Function(path) {
scopes.push(path.node);
for (const parameter of path.get("params")) register(parameter, false, path.node);
},
VariableDeclarator(path) {
register(path, path.parentPath.isVariableDeclaration({ kind: "const" }));
},
AssignmentExpression(path) {
recordWrite(path.node.left, path.node.right);
},
"ForOfStatement|ForInStatement"(path) {
if (path.isForOfStatement() || path.isForInStatement()) {
if (path.node.left.type !== "VariableDeclaration") recordWrite(path.node.left);
}
},
"MemberExpression|OptionalMemberExpression"(path) {
if (path.isMemberExpression() || path.isOptionalMemberExpression()) {
if (memberName(path.node) === "chdir") cwdReferences.push(path.node);
}
}
});
const writes = /* @__PURE__ */ new Map();
for (const [name, value] of assignments) {
const symbol = symbolAt(name);
if (symbol) writes.set(symbol, [...writes.get(symbol) ?? [], value]);
}
function memberOrigins(owners, name) {
return union(owners.map((owner) => owner === "input" ? "input" : name === void 0 ? "unknown" : members.get(owner)?.get(name) ?? "unknown"));
}
function origins(expression, seen = /* @__PURE__ */ new Set(), inputScope, awaited = false) {
if (expression.type === "AwaitExpression") return origins(expression.argument, seen, inputScope, true);
if (expression.type === "StringLiteral" || expression.type === "TemplateLiteral" && expression.expressions.length === 0) {
const value = expression.type === "StringLiteral" ? expression.value : expression.quasis[0]?.value.cooked ?? "";
return [isAbsolute(value) || win32.isAbsolute(value) || URL.canParse(value) ? "literal" : "relative"];
}
if ([
"NumericLiteral",
"BooleanLiteral",
"NullLiteral"
].includes(expression.type)) return ["literal"];
if (expression.type === "ConditionalExpression") return union(origins(expression.consequent, new Set(seen), inputScope), origins(expression.alternate, new Set(seen), inputScope));
if (expression.type === "SequenceExpression") {
const result = expression.expressions.at(-1);
return result ? origins(result, seen, inputScope) : ["unknown"];
}
if (expression.type === "BinaryExpression" || expression.type === "LogicalExpression" || expression.type === "AssignmentExpression") {
if (expression.operator === "=") return origins(expression.right, seen, inputScope);
const values = union(origins(expression.left, new Set(seen), inputScope), origins(expression.right, new Set(seen), inputScope));
if ([
"||",
"&&",
"??",
"||=",
"&&=",
"??="
].includes(expression.operator)) return values;
if (expression.operator === "+" && values.some((value) => callerValues.has(value)) && values.every((value) => pathValues.has(value))) return ["caller-string"];
return ["scalar"];
}
if (expression.type === "Identifier") {
const symbol = symbolAt(expression);
if (!symbol) return [ambient.get(expression.name) ?? "unknown"];
const snapshot = snapshots.get(symbol);
if (snapshot) return snapshot;
if (seen.has(symbol)) return ["unknown"];
seen.add(symbol);
const entries = declarations.get(symbol) ?? [];
const entryParameter = inputScope && entries.some((entry) => entry.inputScope === inputScope);
return union(...entries.map((entry) => declarationOrigins(entry, new Set(seen), inputScope)), ...(entryParameter ? [] : writes.get(symbol) ?? []).map((value) => value ? origins(value, new Set(seen), inputScope) : ["unknown"]));
}
if (expression.type === "MemberExpression" || expression.type === "OptionalMemberExpression") {
const name = memberName(expression);
if (name === "url" && expression.object.type === "MetaProperty" && expression.object.meta.name === "import" && expression.object.property.name === "meta") return ["location"];
return memberOrigins(origins(expression.object, seen, inputScope), name);
}
if (expression.type === "ImportExpression") return [namespaces.get(literal(expression.source) ?? "") ?? "unknown"];
if (expression.type === "CallExpression" || expression.type === "OptionalCallExpression") {
const specifier = literal(expression.arguments[0]);
const namespace = specifier === void 0 ? void 0 : namespaces.get(specifier);
const locations = expression.arguments.map((value) => origins(value, new Set(seen), inputScope));
return union(...origins(expression.callee, new Set(seen), inputScope).map((loader) => {
if (loader === "factory") return (locations[0] ?? ["unknown"]).map((location) => location === "location" ? "root" : callerValues.has(location) ? "caller" : "unlocated");
if (loaders.has(loader) || loader === "builtin") return [namespace ?? "unknown"];
if (loader === "cwd") return [inputScope && !changedCwd ? "caller-path" : "scalar"];
if (loader === "realpath-async" && !awaited) return ["unknown"];
if (loader === "resolve" || (loader === "realpath" || loader === "realpath-async") && expression.arguments.length === 1) {
const values = loader === "resolve" ? locations.flat() : locations[0] ?? ["unknown"];
return [!changedCwd && values.every((value) => [
"input",
"caller-path",
"caller-string",
"relative"
].includes(value)) && (inputScope || values.includes("caller-path")) && (inputScope || !values.includes("caller-string")) ? "caller-path" : "scalar"];
}
if (loader === "join") {
const values = locations.flat();
if (values.every((value) => pathValues.has(value)) && values.some((value) => callerValues.has(value))) return [locations[0]?.every((value) => value === "caller-path") ? "caller-path" : "caller-string"];
return ["scalar"];
}
return ["unknown"];
}));
}
return ["unknown"];
}
function declarationOrigins(declaration, seen, inputScope) {
const { node, name } = declaration;
if (node.type === "ImportSpecifier" || node.type === "ImportNamespaceSpecifier" || node.type === "ImportDefaultSpecifier") {
const owner = paths.get(node)?.parent;
const namespace = owner?.type === "ImportDeclaration" ? namespaces.get(owner.source.value) : void 0;
return namespace ? node.type === "ImportSpecifier" ? memberOrigins([namespace], property(node.imported)) : [namespace] : ["unknown"];
}
if (node.type !== "VariableDeclarator" && !declaration.inputScope) return ["unknown"];
const pattern = node.type === "VariableDeclarator" ? node.id : node;
const initial = node.type === "VariableDeclarator" ? node.init : node.type === "AssignmentPattern" ? node.right : void 0;
const incoming = declaration.inputScope ? union([inputScope === declaration.inputScope ? "input" : "unknown"], initial ? pattern.type === "AssignmentPattern" && pattern.left.type !== "Identifier" ? ["unknown"] : origins(initial, new Set(seen), inputScope) : []) : initial ? origins(initial, new Set(seen), inputScope) : ["unknown"];
if (pattern === name || pattern.type === "AssignmentPattern" && pattern.left === name) return union(incoming, !declaration.inputScope && !declaration.constant ? ["unknown"] : []);
const defaults = [];
let child = name;
let parent = paths.get(child)?.parent;
let directMember;
while (parent && child !== pattern) {
if (parent.type === "AssignmentPattern") defaults.push(parent.left === name ? origins(parent.right, new Set(seen), inputScope) : ["unknown"]);
if (parent.type === "ObjectProperty" && paths.get(parent)?.parent === pattern && (parent.value === name || parent.value.type === "AssignmentPattern" && parent.value.left === name)) directMember = parent.computed ? literal(parent.key) : property(parent.key);
child = parent;
parent = paths.get(child)?.parent;
}
return union(declaration.inputScope ? incoming : directMember === void 0 ? ["unknown"] : memberOrigins(incoming, directMember), ...defaults, !declaration.inputScope && !declaration.constant ? ["unknown"] : []);
}
function pure(node, scope) {
if (node.type === "Identifier") return Boolean(symbolAt(node)) || ambient.has(node.name) || node.name === "undefined";
if ([
"StringLiteral",
"NumericLiteral",
"BooleanLiteral",
"NullLiteral"
].includes(node.type) || node.type === "TemplateLiteral" && node.expressions.length === 0) return true;
if (node.type === "LogicalExpression") return pure(node.left, scope) && pure(node.right, scope);
if (node.type === "ConditionalExpression") return pure(node.test, scope) && pure(node.consequent, scope) && pure(node.alternate, scope);
if (node.type === "AwaitExpression") {
const value = node.argument;
if (value.type === "ImportExpression") return namespaces.has(literal(value.source) ?? "") && pure(value, scope);
if (value.type !== "CallExpression" && value.type !== "OptionalCallExpression") return false;
return origins(value.callee, /* @__PURE__ */ new Set(), scope).every((origin) => origin === "realpath-async") && pure(value, scope);
}
if (node.type === "MemberExpression" || node.type === "OptionalMemberExpression") {
if (origins(node, /* @__PURE__ */ new Set(), scope).includes("location")) return true;
return origins(node.object, /* @__PURE__ */ new Set(), scope).every((value) => readableValues.has(value)) && pure(node.object, scope) && (!node.computed || literal(node.property) !== void 0);
}
if (node.type === "ImportExpression") return !node.options && namespaces.has(literal(node.source) ?? "");
if (node.type !== "CallExpression" && node.type !== "OptionalCallExpression") return false;
const builtinCall = namespaces.has(literal(node.arguments[0]) ?? "") && node.arguments.length === 1;
return origins(node.callee, /* @__PURE__ */ new Set(), scope).every((value) => pureCalls.has(value) && (value === "join" || value === "resolve" || node.arguments.length === (value === "cwd" ? 0 : 1)) || loaders.has(value) && builtinCall) && pure(node.callee, scope) && node.arguments.every((argument) => pure(argument, scope));
}
changedCwd = cwdReferences.some((reference) => origins(reference).includes("chdir")) || [...declarations.values()].some((entries) => entries.some((entry) => declarationOrigins(entry, /* @__PURE__ */ new Set()).includes("chdir")));
for (const scope of scopes.toSorted((a, b) => (a.start ?? 0) - (b.start ?? 0))) {
const body = scope.type === "Program" ? scope : scope.body;
if (body.type !== "Program" && body.type !== "BlockStatement") continue;
if (scope.type !== "Program" && scope.params.some((parameter) => parameter.type !== "Identifier")) continue;
prefix: for (const entry of body.body) {
const statement = entry.type === "ExportNamedDeclaration" || entry.type === "ExportDefaultDeclaration" ? entry.declaration : entry;
if (!statement || statement.type === "ImportDeclaration" || statement.type === "ExportAllDeclaration" || statement.type === "FunctionDeclaration" || statement.type === "EmptyStatement" || statement.type === "ExpressionStatement" && statement.expression.type === "StringLiteral") continue;
if (statement.type !== "VariableDeclaration" || statement.kind !== "const") break;
for (const declaration of statement.declarations) {
if (declaration.id.type !== "Identifier" || !declaration.init || !pure(declaration.init, scope)) break prefix;
const values = origins(declaration.init, /* @__PURE__ */ new Set(), scope);
if (!values.every((value) => snapshotsKinds.has(value) || [
"literal",
"relative",
...namespaces.values(),
...pureCalls
].includes(value))) break prefix;
const symbol = symbolAt(declaration.id);
if (symbol && values.every((value) => snapshotsKinds.has(value))) snapshots.set(symbol, values);
}
}
}
for (const node of calls) {
const specifier = literal(node.arguments[0]);
if (node.arguments.length !== 1 || specifier === void 0) continue;
const values = origins(node.callee);
if (values.includes("root") || node.callee.type === "Identifier" && node.callee.name === "require" && !values.every((value) => value === "caller")) recordImport(specifier, node);
}
return imports;
}
//#endregion
//#region src/infra/runtime-dependency-ownership.ts
const RUNTIME_DEPENDENCY_OWNERSHIP_RELATIVE_PATH = "dist/runtime-dependency-ownership.json";
function parseRuntimeDependencyOwnership(value) {
if (!isRecord(value) || !isRecord(value.chunks)) return null;
for (const chunk of Object.values(value.chunks)) if (!isRecord(chunk) || typeof chunk.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(chunk.sha256) || !Array.isArray(chunk.extensions) || chunk.extensions.length === 0 || !chunk.extensions.every((id) => typeof id === "string" && /^[a-z0-9][a-z0-9-]*$/u.test(id)) || new Set(chunk.extensions).size !== chunk.extensions.length) return null;
return value;
}
function readRuntimeDependencyOwnership(packageRoot, fsImpl = fs) {
const file = join(packageRoot, RUNTIME_DEPENDENCY_OWNERSHIP_RELATIVE_PATH);
if (!fsImpl.existsSync(file)) return null;
const stat = fsImpl.lstatSync(file);
if (!stat.isFile() || stat.size > 1048576) throw new Error("ownership artifact must be a regular file no larger than 1048576 bytes");
const ownership = parseRuntimeDependencyOwnership(JSON.parse(fsImpl.readFileSync(file, "utf8")));
if (!ownership) throw new Error("ownership artifact does not match the supported schema");
return ownership;
}
//#endregion
//#region src/shared/non-packaged-plugin-dirs.ts
/** Bundled plugins available to source builds but excluded from node and npm distributions. */
const NON_PACKAGED_BUNDLED_PLUGIN_DIRS = /* @__PURE__ */ new Set(["qa-channel", "qa-lab"]);
//#endregion
//#region src/gateway/worker-environments/node-bootstrap-runtime-chunks.ts
async function resolveNodeBootstrapRuntimeChunks(packageRoot, files) {
const ownership = readRuntimeDependencyOwnership(packageRoot);
const privateChunks = new Map(files.flatMap((file) => {
if (!file.startsWith("dist/") || !/\.[cm]?js$/u.test(file) || file.startsWith("dist/extensions/")) return [];
const owner = ownership?.chunks[file.slice(5)];
return owner?.extensions.every((id) => NON_PACKAGED_BUNDLED_PLUGIN_DIRS.has(id)) ? [[file, owner]] : [];
}));
const sourceFacts = /* @__PURE__ */ new Map();
if (privateChunks.size === 0) return {
files,
sourceFacts
};
const root$1 = await root(packageRoot, {
hardlinks: "allow",
symlinks: "reject",
nonBlockingRead: true
});
const fileSet = new Set(files);
const imports = /* @__PURE__ */ new Map();
for (const file of files.filter((candidate) => /\.[cm]?js$/u.test(candidate))) {
const { buffer } = await root$1.read(file, {
hardlinks: "allow",
symlinks: "reject",
maxBytes: DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS.maxExpandedBytes
});
const sha256 = createHash("sha256").update(buffer).digest("hex");
const owner = privateChunks.get(file);
if (owner && sha256 !== owner.sha256) throw new Error(`Runtime dependency ownership does not match ${file}; rebuild the Gateway`);
const source = buffer.toString("utf8");
const fileImports = collectPackageDistImports({
files: [file],
readText: () => source
});
sourceFacts.set(file, {
sha256,
imports: fileImports
});
const dependencies = new Set(fileImports.map(({ importedPath }) => importedPath));
const resolveImport = createRequire(path.join(packageRoot, file)).resolve;
for (const specifier of collectPackageRootImports(source)) {
if (!specifier.startsWith(".")) continue;
const specifierPath = specifier.replace(/[?#].*$/u, "");
const direct = path.posix.normalize(path.posix.join(path.posix.dirname(file), specifierPath));
if (fileSet.has(direct) && dependencies.has(direct)) continue;
try {
dependencies.add(path.relative(packageRoot, resolveImport(specifierPath)).split(path.sep).join("/"));
} catch {}
}
imports.set(file, [...dependencies]);
}
const retained = files.filter((file) => !privateChunks.has(file));
for (const file of retained) for (const dependency of imports.get(file) ?? []) if (privateChunks.delete(dependency)) retained.push(dependency);
return {
files: files.filter((file) => !privateChunks.has(file)),
sourceFacts
};
}
//#endregion
//#region src/gateway/worker-environments/node-bootstrap-artifact.ts
const BOOTSTRAP_LAUNCHER_FILES = [
"openclaw.mjs",
"node-version.mjs",
"node-sqlite.mjs",
"node-runtime-update.mjs",
"node-runtime-recovery.mjs",
"cli-root-options.mjs",
"gateway-run-argv.mjs",
"gateway-shutdown-budget.mjs",
"node-host-launcher.mjs",
"node-compile-cache.mjs"
];
const READ_CONCURRENCY = 16;
const IGNORED_PLUGIN_DIRECTORIES = /* @__PURE__ */ new Set([
"node_modules",
"src",
"test",
"tests"
]);
const METADATA_KEYS = [
"name",
"version",
"dependencies",
"optionalDependencies",
"peerDependencies",
"peerDependenciesMeta"
];
function bootstrapPath(value) {
if (!value || value.includes("\\") || value.includes("\0") || value.startsWith("/") || value.split("/").some((part) => !part || part === "." || part === "..")) throw new Error(`Unsafe node distribution path: ${value}`);
return value;
}
async function readPackageManifest(root) {
const value = JSON.parse(await fs$1.readFile(path.join(root, "package.json"), "utf8"));
if (!value.name || valid(value.version) !== value.version) throw new Error("Node distribution requires a named package with an exact version");
return value;
}
function requireRunningBuild(options, text, version) {
const info = JSON.parse(text);
if (!options.runningBuildId || info.buildId !== options.runningBuildId || info.version !== version) throw new Error("Cloud bootstrap requires the running Gateway build; run pnpm build and restart the Gateway before provisioning");
return options.runningBuildId;
}
async function observeBootstrapModes(root) {
const modes = [];
for (const requested of [420, 493]) {
const probe = path.join(root, `.mode-${requested.toString(8)}`);
const handle = await fs$1.open(probe, "wx", requested);
try {
modes.push((await handle.stat()).mode & 511);
} finally {
await handle.close();
await fs$1.rm(probe);
}
}
return modes;
}
async function collectInstalledBundledFiles(root) {
const included = ({ name }) => name !== "node_modules" && !name.startsWith(".");
const { entries, failedDirs, truncated } = await walkDirectory(root, {
maxEntries: DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS.maxEntries,
symlinks: "include",
include: included,
descend: (entry) => {
if (entry.depth >= 64) throw new Error("Node bootstrap dependency exceeds its directory depth limit");
return included(entry);
}
});
if (failedDirs.length > 0) throw failedDirs[0].error;
if (truncated) throw new Error("Node bootstrap distribution exceeds its artifact limits");
return entries.flatMap((entry) => {
if (entry.kind === "directory" || entry.relativePath === "package.json") return [];
if (entry.kind !== "file") throw new Error(`Unsafe bundled node distribution path: ${entry.relativePath}`);
return [entry.relativePath.split(path.sep).join("/")];
});
}
async function resolvePlugins(options, packageRoot) {
const ids = /* @__PURE__ */ new Set();
return await Promise.all(options.plugins.map(async ({ id, root }) => {
if (!/^[a-z0-9][a-z0-9_-]*$/u.test(id) || ids.has(id)) throw new Error(`Invalid or duplicate node bootstrap plugin: ${id}`);
ids.add(id);
const requestedRoot = await fs$1.realpath(root);
const sourceRoot = path.join(packageRoot, "extensions", id);
const bundledRoot = path.join(packageRoot, "dist", "extensions", id);
const builtRoot = requestedRoot === sourceRoot ? bundledRoot : requestedRoot;
const packageJson = await readPackageManifest(builtRoot);
if (requestedRoot === sourceRoot) {
const sourcePackage = await readPackageManifest(sourceRoot);
if (METADATA_KEYS.some((key) => !isDeepStrictEqual(packageJson[key], sourcePackage[key]))) throw new Error(`Built plugin ${id} does not match source metadata; rebuild and restart the Gateway`);
}
if (JSON.parse(await fs$1.readFile(path.join(builtRoot, "openclaw.plugin.json"), "utf8")).id !== id) throw new Error(`Node bootstrap plugin identity does not match ${id}`);
const entries = packageJson.openclaw?.runtimeExtensions ?? packageJson.openclaw?.extensions;
if (!entries?.length) throw new Error(`Node bootstrap plugin ${id} has no runtime entry`);
for (const entry of entries) {
const relative = bootstrapPath(entry.replace(/^\.\//u, "").replace(/\.ts$/u, ".js"));
await fs$1.access(path.join(builtRoot, relative));
}
return {
id,
root: builtRoot,
packageJson,
bundled: builtRoot === bundledRoot
};
}));
}
async function prepareNodeBootstrapArtifact(options, temporaryRoot, usePrebuilt = true) {
try {
var _usingCtx$1 = _usingCtx();
const packageRoot = await fs$1.realpath(options.packageRoot);
const sourcePackage = await readPackageManifest(packageRoot);
if (sourcePackage.name !== "openclaw") throw new Error("Node bootstrap requires the running OpenClaw package root");
const buildInfoPath = path.join(packageRoot, "dist", "build-info.json");
const buildInfo = await fs$1.readFile(buildInfoPath, "utf8").catch((cause) => {
throw new Error("Cloud bootstrap requires a built Gateway; run pnpm build and restart the Gateway before provisioning", { cause });
});
const buildId = requireRunningBuild(options, buildInfo, sourcePackage.version);
const plugins = await resolvePlugins(options, packageRoot);
const packageJson = composePackagePlugins(sourcePackage, plugins);
const modes = await observeBootstrapModes(temporaryRoot);
const mainScope = {
label: packageJson.name,
prefix: "",
files: [],
imports: [],
packageJsons: /* @__PURE__ */ new Map()
};
const scopes = [];
const entries = /* @__PURE__ */ new Map();
const planEntry = (relative, entry) => {
bootstrapPath(relative);
entries.set(relative, entry);
if (entries.size > DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS.maxEntries) throw new Error("Node bootstrap distribution exceeds its artifact limits");
};
let expandedBytes = 0;
let reservedEntries = 0;
const reserveFile = (relative, bytes) => {
bootstrapPath(relative);
expandedBytes += bytes;
reservedEntries += 1;
if (reservedEntries > DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS.maxEntries || expandedBytes > DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS.maxExpandedBytes) throw new Error("Node bootstrap distribution exceeds its artifact limits");
};
const addGeneratedFile = (relative, contents, scope = mainScope) => {
reserveFile(relative, Buffer.byteLength(contents));
planEntry(relative, {
contents: Buffer.from(contents),
scope
});
};
const addFiles = (root, files, prefix = "", scope = mainScope) => {
for (const relative of new Set(files)) {
bootstrapPath(relative);
planEntry(`${prefix}${relative}`, {
source: {
root,
relative
},
scope
});
}
};
const readEntry = async (destination, entry) => {
if ("contents" in entry) return {
contents: entry.contents,
mode: modes[0]
};
const { root, relative } = entry.source;
const source = path.join(root, relative);
if (await fs$1.realpath(source) !== source) throw new Error(`Node distribution cannot contain symbolic links: ${relative}`);
const handle = await fs$1.open(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
try {
const before = await handle.stat();
if (!before.isFile()) throw new Error(`Invalid node distribution file: ${relative}`);
reserveFile(destination, before.size);
const contents = await readFileHandleBounded(handle, before.size);
const current = await fs$1.lstat(source);
if (contents.byteLength !== before.size || current.isSymbolicLink() || current.dev !== before.dev || current.ino !== before.ino || await fs$1.realpath(source) !== source) throw new Error(`Node distribution changed while packaging: ${relative}`);
return {
contents,
mode: modes[(before.mode & 73) !== 0 ? 1 : 0]
};
} finally {
await handle.close();
}
};
const externalPluginPrefixes = plugins.filter((plugin) => !plugin.bundled).map(({ id }) => `dist/extensions/${id}/`);
const publicFiles = (await collectPackageDistInventory(packageRoot, { packageManifest: packageJson })).filter((relative) => !relative.startsWith("dist/worker/") && !relative.startsWith("dist/control-ui/") && !externalPluginPrefixes.some((prefix) => relative.startsWith(prefix)));
const scripts = (sourcePackage.files ?? []).filter((relative) => relative.startsWith("scripts/") && !relative.includes("*") && !relative.endsWith("/"));
const { files, sourceFacts } = await resolveNodeBootstrapRuntimeChunks(packageRoot, [
...BOOTSTRAP_LAUNCHER_FILES,
...publicFiles,
...scripts
].filter((relative) => relative !== LEGACY_PACKAGE_INSTALL_GUARD_RELATIVE_PATH));
if (!files.includes("dist/entry.js") && !files.includes("dist/entry.mjs")) throw new Error("Cloud bootstrap is missing its built CLI entry; run pnpm build and restart the Gateway");
addFiles(packageRoot, files);
packageJson.scripts = Object.fromEntries(Object.entries(packageJson.scripts ?? {}).filter(([name]) => [
"preinstall",
"install",
"postinstall"
].includes(name)));
delete packageJson.devDependencies;
for (const plugin of plugins) {
if (plugin.bundled) continue;
const pluginFiles = [];
const visit = async (directory, relativeRoot = "") => {
for (const child of await fs$1.readdir(directory, { withFileTypes: true })) {
if (child.name.startsWith(".") || IGNORED_PLUGIN_DIRECTORIES.has(child.name)) continue;
const relative = relativeRoot ? `${relativeRoot}/${child.name}` : child.name;
if (relative.split("/").length > 64) throw new Error("Node bootstrap plugin exceeds its directory depth limit");
if (child.isDirectory()) await visit(path.join(directory, child.name), relative);
else if (/\.(?:[cm]?js|json|wasm)$/u.test(child.name)) pluginFiles.push(relative);
}
};
await visit(plugin.root);
addFiles(plugin.root, pluginFiles, `dist/extensions/${plugin.id}/`);
}
const bundledNames = /* @__PURE__ */ new Set([...packageJson.bundleDependencies ?? packageJson.bundledDependencies ?? [], ...Object.entries(packageJson.dependencies ?? {}).filter(([, spec]) => spec.startsWith("workspace:")).map(([name]) => name)]);
for (const name of [...bundledNames].toSorted()) {
bootstrapPath(name);
const root = await fs$1.realpath(path.join(packageRoot, "node_modules", name));
const bundled = await readPackageManifest(root);
if (bundled.name !== name) throw new Error(`Bundled node distribution dependency identity does not match ${name}`);
const dependencies = validateBundledPackageDependencyAlignment({
bundledDependencies: bundled.dependencies,
bundledPackageLabel: `bundled ${name}`,
rootDependencies: packageJson.dependencies
});
for (const [dependency, version] of dependencies) packageJson.dependencies[dependency] = version;
const bundledFiles = sourcePackage.dependencies?.[name]?.startsWith("workspace:") || !root.endsWith(`${path.sep}node_modules${path.sep}${name.split("/").join(path.sep)}`) ? await collectPackageDistInventory(root) : await collectInstalledBundledFiles(root);
if (bundledFiles.length === 0) throw new Error(`Bundled node dependency ${name} needs its compiled distribution; rebuild the Gateway`);
const scope = {
label: name,
prefix: `node_modules/${name}/`,
files: [],
imports: [],
packageJsons: /* @__PURE__ */ new Map(),
...name === "chrome-devtools-mcp" ? { patchedMcp: {
manifest: bundled,
hashes: /* @__PURE__ */ new Map()
} } : {}
};
scopes.push(scope);
addFiles(root, bundledFiles, scope.prefix, scope);
delete bundled.dependencies;
delete bundled.devDependencies;
delete bundled.scripts;
addGeneratedFile(`node_modules/${name}/package.json`, `${JSON.stringify(bundled, null, 2)}\n`, scope);
packageJson.dependencies[name] = bundled.version;
}
packageJson.bundleDependencies = [...bundledNames].toSorted();
delete packageJson.bundledDependencies;
for (const [name, spec] of Object.entries({
...packageJson.optionalDependencies,
...packageJson.dependencies
})) if (valid(spec) !== spec) throw new Error(`Node distribution requires an exact dependency pin: ${name}@${spec}`);
addGeneratedFile("package.json", `${JSON.stringify(packageJson, null, 2)}\n`);
const inventory = [...entries.keys()].filter((entry) => entry.startsWith("dist/")).toSorted();
addGeneratedFile(PACKAGE_DIST_INVENTORY_RELATIVE_PATH, `${JSON.stringify(inventory)}\n`);
addGeneratedFile(PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, "pending\n");
const ordered = [...entries].toSorted(([left], [right]) => Number(path.posix.basename(right) === "package.json") - Number(path.posix.basename(left) === "package.json") || compareWorkerBundlePaths(left, right));
mainScope.files = ordered.map(([relative]) => relative);
for (const [relative, entry] of ordered) if (entry.scope !== mainScope) entry.scope.files.push(relative.slice(entry.scope.prefix.length));
const tarballPath = path.join(temporaryRoot, NODE_BOOTSTRAP_PREBUILT_ARCHIVE);
const prebuiltManifest = usePrebuilt ? await copyNodeBootstrapPrebuiltArchive(packageRoot, temporaryRoot) : void 0;
let entryConsumed = Promise.resolve();
const pack = prebuiltManifest ? void 0 : new tar.Pack({
gzip: true,
noMtime: true,
portable: true,
strict: true,
onWriteEntry(entry) {
entryConsumed = finished(entry, {
readable: true,
writable: false,
cleanup: true
});
entryConsumed.catch(() => void 0);
}
});
const archiveDone = pack ? pipeline(pack, createWriteStream(tarballPath, { flags: "wx" })) : Promise.resolve();
archiveDone.catch(() => void 0);
const manifest = [];
try {
for (let offset = 0; offset < ordered.length; offset += READ_CONCURRENCY) {
const batch = ordered.slice(offset, offset + READ_CONCURRENCY);
const read = await runTasksWithConcurrency({
tasks: batch.map(([relative, entry]) => () => readEntry(relative, entry)),
limit: READ_CONCURRENCY,
errorMode: "stop"
});
if (read.hasError) throw read.firstError;
for (let index = 0; index < batch.length; index += 1) {
const [relative, entry] = batch[index];
const { contents, mode } = read.results[index];
const importerPath = relative.slice(entry.scope.prefix.length);
if (path.posix.basename(relative) === "package.json") {
const text = contents.toString("utf8");
mainScope.packageJsons.set(relative, text);
entry.scope.packageJsons.set(importerPath, text);
}
const identity = {
path: `package/${relative}`,
size: contents.byteLength,
mode: process.platform === "win32" ? 448 : mode,
sha256: createHash("sha256").update(contents).digest("hex")
};
const inspected = sourceFacts.get(relative);
if (inspected && identity.sha256 !== inspected.sha256) throw new Error(`Node distribution changed after import inspection: ${relative}`);
if (entry.scope.patchedMcp) entry.scope.patchedMcp.hashes.set(importerPath, identity.sha256);
else entry.scope.imports.push(...inspected?.imports ?? collectPackageDistImports({
files: [importerPath],
packageJsons: entry.scope.packageJsons,
readText: () => contents.toString("utf8")
}));
manifest.push(identity);
if (!pack) continue;
const input = new tar.ReadEntry(new tar.Header({
path: identity.path,
size: identity.size,
mode,
type: "File"
}));
pack.add(input);
input.end(contents);
await Promise.race([entryConsumed, archiveDone]);
}
}
for (const scope of [...scopes, mainScope]) {
const errors = scope.patchedMcp ? collectPatchedMcpArtifactErrors({
manifest: scope.patchedMcp.manifest,
files: new Set(scope.files),
sha256: (file) => scope.patchedMcp?.hashes.get(file)
}) : collectPackageDistImportErrors(scope);
if (errors.length > 0) throw new Error(`Node distribution ${scope.label} ${scope.patchedMcp ? "has an invalid patched dependency" : "has an incomplete built import closure"}; rebuild and restart the Gateway: ${errors.slice(0, 5).join("; ")}`);
}
if (!isDeepStrictEqual(await observeBootstrapModes(temporaryRoot), modes)) throw new Error("Node bootstrap file creation policy changed while packaging");
if (await fs$1.readFile(buildInfoPath, "utf8") !== buildInfo || !isDeepStrictEqual(await readPackageManifest(packageRoot), sourcePackage)) throw new Error("Gateway build changed while preparing cloud bootstrap; restart the Gateway and retry");
pack?.end();
await archiveDone;
} catch (error) {
pack?.destroy(error instanceof Error ? error : new Error("Node bootstrap archive failed", { cause: error }));
pack?.zip?.destroy();
await archiveDone.catch(() => void 0);
throw error;
}
const tarballBytes = (await fs$1.stat(tarballPath)).size;
if (tarballBytes > 536870912) throw new Error("Node bootstrap archive exceeds the transfer limit");
const archiveManifest = prebuiltManifest ?? await readWorkerBundleArchiveManifest(tarballPath, DEFAULT_WORKER_BUNDLE_ARCHIVE_LIMITS);
manifest.sort((left, right) => compareWorkerBundlePaths(left.path, right.path));
if (hashWorkerBundleManifest(manifest) !== hashWorkerBundleManifest(archiveManifest)) {
if (prebuiltManifest) {
await fs$1.rm(tarballPath);
return await prepareNodeBootstrapArtifact(options, temporaryRoot, false);
}
throw new Error("Node bootstrap archive does not match the verified distribution");
}
const handle = _usingCtx$1.a(await fs$1.open(tarballPath, "r"));
const { digest: tarballSha256 } = await sha256File(handle);
return Object.freeze({
tarballPath,
tarballSha256,
tarballBytes,
openclawVersion: packageJson.version,
buildId,
enabledPluginIds: Object.freeze(plugins.map(({ id }) => id).toSorted())
});
} catch (_) {
_usingCtx$1.e = _;
} finally {
await _usingCtx$1.d();
}
}
/** Owns one immutable deployment artifact for this Gateway process, never the live installation. */
function createNodeBootstrapArtifactProvider(options) {
let prepared;
let temporaryRoot;
let closed = false;
const consumers = /* @__PURE__ */ new Map();
return {
async prepare(signal) {
signal?.throwIfAborted();
if (closed) throw new Error("Node bootstrap artifact provider is closed");
prepared ??= Promise.resolve().then(async () => {
try {
temporaryRoot = await fs$1.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-node-runtime-"));
if (closed) throw new Error("Node bootstrap artifact provider is closed");
const artifact = await prepareNodeBootstrapArtifact(options, temporaryRoot);
if (closed) throw new Error("Node bootstrap artifact provider is closed");
return artifact;
} catch (error) {
if (temporaryRoot) await fs$1.rm(temporaryRoot, {
recursive: true,
force: true
});
temporaryRoot = void 0;
prepared = void 0;
throw error;
}
});
const artifact = await racePromiseWithAbortSignal(prepared, signal);
signal?.throwIfAborted();
if (closed) throw new Error("Node bootstrap artifact provider is closed");
if (signal && !consumers.has(signal)) consumers.set(signal, new Promise((resolve) => {
signal.addEventListener("abort", () => {
consumers.delete(signal);
resolve();
}, { once: true });
}));
return artifact;
},
async close() {
closed = true;
await prepared?.catch(() => void 0);
await Promise.all(consumers.values());
if (temporaryRoot) {
await fs$1.rm(temporaryRoot, {
recursive: true,
force: true
});
temporaryRoot = void 0;
}
}
};
}
//#endregion
export { createNodeBootstrapArtifactProvider };