openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
2,087 lines • 98.5 kB
JavaScript
import { A as PluginSourceRecoveryUnavailableError, o as getPluginCache } from "./plugin-cache-Bn9I3v9Q.mjs";
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs";
import { t as escapeRegExp } from "./regexp-BZyMFTlj.mjs";
import { o as openRootFileSync$1 } from "./boundary-file-read-CCth2Hwg.mjs";
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs";
import { r as isArtifactPreservingStateRead } from "./openclaw-state-db-readonly-BLZyK3So.mjs";
import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-epCM4Gdh.mjs";
import { a as retainPluginNativeCapturePath, i as retainLoadedPluginSourceCapture, o as retainPluginSourceCaptureInstance, t as createPluginNativeCaptureRoot } from "./plugin-source-capture-directory-Bn77w1M3.mjs";
import { t as PLUGIN_SOURCE_CAPTURE_PREFIX } from "./plugin-source-capture-path-L0n-8p-6.mjs";
import { i as relativePluginPathInsideRootSync, n as isPathInside$1 } from "./path-safety-DRBJanJw.mjs";
import { t as resolveInstalledPluginIndexStateDatabaseOptions } from "./installed-plugin-index-store-path-jarznWL7.mjs";
import { a as retainNativePath, c as startNativeAdmissionPublication, n as nativeAdmissionStateFor, s as snapshotOwners } from "./plugin-native-admission-state-Q-i0SBpy.mjs";
import { t as createJiti } from "./jiti-factory-B7ZlEz2u.mjs";
import { r as isGitRuntimeStagingName, t as collectPluginSafetyInspectedFiles } from "./plugin-safety-inspected-files-BQgMMx0Y.mjs";
import { createRequire, isBuiltin } from "node:module";
import fs from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
import { AsyncLocalStorage } from "node:async_hooks";
import fs$1 from "node:fs/promises";
import { isDeepStrictEqual } from "node:util";
import { createHash, randomUUID } from "node:crypto";
import { copyFileDescriptorSync } from "@openclaw/fs-safe/advanced";
import { FsSafeError } from "@openclaw/fs-safe/errors";
import { moduleResolve } from "import-meta-resolve";
import { parse as parse$1 } from "acorn";
//#region src/plugins/plugin-source-file.ts
const isPluginSourceEntry = (name) => name !== "node_modules" && name !== ".git" && !isGitRuntimeStagingName(name);
function shouldFallbackFromPluginDescriptorCopy(error, platform = process.platform, isBun = Object.hasOwn(process.versions, "bun")) {
return [
"ENOENT",
"ENOTDIR",
"EACCES",
"EPERM"
].some((code) => hasErrnoCode(error, code)) || platform === "darwin" && isBun && hasErrnoCode(error, "EBADF");
}
const scratch = Buffer.allocUnsafe(65536);
const pluginSourceStatIdentity = (stat) => `${stat.dev}:${stat.ino}:${stat.mode}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
const pluginSourceIdentityChangedOnlyByCtime = (previous, current) => previous.slice(0, previous.lastIndexOf(":")) === current.slice(0, current.lastIndexOf(":"));
function withPluginSourceFile(source, boundary, read) {
const opened = openRootFileSync$1({
absolutePath: source,
rootPath: boundary,
boundaryLabel: "plugin build source",
rejectHardlinks: false
});
if (!opened.ok) throw new Error(`Cannot capture plugin source ${source}`, { cause: opened.error });
try {
return read(opened.fd);
} finally {
fs.closeSync(opened.fd);
}
}
function pluginSourceFileIdentity(source, boundary) {
return withPluginSourceFile(source, boundary, (fd) => pluginSourceStatIdentity(fs.fstatSync(fd, { bigint: true })));
}
function isPluginNativeExecutable(source, boundary) {
return withPluginSourceFile(source, boundary, (fd) => {
if (fs.readSync(fd, scratch, 0, 4, 0) !== 4) return false;
const magic = scratch.readUInt32BE(0);
return scratch.readUInt16BE(0) === 19802 || [
2135247942,
4277009102,
4277009103,
3472551422,
3489328638,
3405691582,
3199925962
].includes(magic);
});
}
function copyPluginSourceFile(source, boundary, target) {
withPluginSourceFile(source, boundary, (fd) => {
if (process.platform === "linux" || process.platform === "darwin") {
const descriptor = `${process.platform === "linux" ? "/proc/self/fd" : "/dev/fd"}/${fd}`;
try {
fs.copyFileSync(descriptor, target, fs.constants.COPYFILE_FICLONE);
return;
} catch (error) {
if (!shouldFallbackFromPluginDescriptorCopy(error)) throw error;
}
}
const output = fs.openSync(target, "w", 384);
try {
copyFileDescriptorSync(fd, output, { maxBytes: fs.fstatSync(fd).size });
} catch (error) {
if (error instanceof FsSafeError && error.code === "too-large") throw new Error("Plugin source changed while preparing its reload; retry after the edit finishes.", { cause: error });
throw error;
} finally {
fs.closeSync(output);
}
});
}
function linkPluginSourceFile(source, boundary, target) {
withPluginSourceFile(source, boundary, (fd) => {
const admitted = fs.fstatSync(fd, { bigint: true });
fs.linkSync(source, target);
const linked = fs.statSync(target, { bigint: true });
if (linked.dev !== admitted.dev || linked.ino !== admitted.ino) throw new Error("Native plugin artifact changed during admission");
});
}
function hashPluginSourceFile(source, boundary, receipt, prepared) {
return withPluginSourceFile(source, boundary, (fd) => {
const content = prepared ? void 0 : createHash("sha256");
const sizeBytes = prepared?.sizeBytes ?? fs.fstatSync(fd).size;
receipt?.update(String(sizeBytes)).update("\0");
let position = 0;
for (;;) {
const length = fs.readSync(fd, scratch, 0, Math.min(scratch.length, sizeBytes - position + 1), position);
position += length;
if (length === 0 || position > sizeBytes) break;
const chunk = scratch.subarray(0, length);
content?.update(chunk);
receipt?.update(chunk);
}
if (position !== sizeBytes) throw new Error("Plugin source changed while preparing its reload; retry after the edit finishes.");
return prepared ?? {
contentHash: content.digest("hex"),
sizeBytes
};
});
}
//#endregion
//#region src/plugins/plugin-source-verification.ts
function readPluginSourceDirectory(source) {
const entries = fs.readdirSync(source, { withFileTypes: true }).filter((entry) => isPluginSourceEntry(entry.name)).toSorted((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
const content = entries.map((entry) => [entry.name, entry.isSymbolicLink() ? fs.readlinkSync(path.join(source, entry.name)) : null]);
return {
names: entries.map((entry) => entry.name),
contentHash: createHash("sha256").update(JSON.stringify(content)).digest("hex")
};
}
const pluginSourceInputIdentity = (stat) => stat.isDirectory() ? `${stat.dev}:${stat.ino}:${stat.mode}` : pluginSourceStatIdentity(stat);
function verifyPluginSourceInputs(inputs, sources) {
for (const source of sources) {
const input = inputs.get(source);
const identity = input.native ? pluginSourceFileIdentity(source, input.boundary) : pluginSourceInputIdentity(fs.statSync(source, { bigint: true }));
if (!input.directory && identity !== input.identity && pluginSourceIdentityChangedOnlyByCtime(input.identity, identity) && hashPluginSourceFile(source, input.boundary).contentHash === input.contentHash) input.identity = identity;
if (fs.realpathSync(source) !== source || identity !== input.identity || (input.directory ? readPluginSourceDirectory(source).contentHash : input.native ? input.contentHash : hashPluginSourceFile(source, input.boundary).contentHash) !== input.contentHash) throw new Error("Plugin source changed while preparing its reload; retry after the edit finishes.");
}
}
//#endregion
//#region src/plugins/plugin-generation-file-capture.ts
function createPluginSourceLinkCapture() {
const links = /* @__PURE__ */ new Set();
return {
defer(filename, root, source = filename) {
if (!fs.lstatSync(filename).isSymbolicLink() || isPathInside(root, fs.realpathSync(filename))) return false;
links.add(source);
return true;
},
contains: (filename) => [...links].some((link) => isPathInside(link, filename))
};
}
/** Capture each selected package file once, retaining its source facts and ordered receipt. */
function createPluginGenerationFileCapture({ boundary, destination, directory, outputRoot, capturedPaths, originalSources, hardlinkedSources, sourceCapture, sourceLinks, deferExternalLinks, nativeAdmission, receipt, onPackageMetadata }) {
const { inputs, pendingInputs, additions } = sourceCapture;
const ancestors = /* @__PURE__ */ new Set();
const copy = (source, target) => {
if (capturedPaths.get(path.resolve(source)) === target) return;
const prepared = nativeAdmission.resolvePreparedSource(source);
const input = prepared?.path ?? source;
const inputBoundary = prepared?.boundary ?? boundary;
const real = fs.realpathSync(input);
const retainedNative = nativeAdmission.isRetainedReference(source, real);
if (!isPathInside(inputBoundary, real) && !retainedNative) throw new Error(`Plugin source link leaves its package: ${path.relative(boundary, source)}. Declare shared code as a package dependency.`);
if (!prepared && outputRoot && isPathInside(outputRoot, real)) return;
const stat = fs.statSync(real, { bigint: true });
const captured = capturedPaths.get(real);
const recordContent = (contentHash, sizeBytes = 0, native = false, identity = pluginSourceInputIdentity(stat), admittedBoundary = inputBoundary) => {
if (!captured) {
inputs.set(real, {
identity,
contentHash,
sizeBytes,
directory: stat.isDirectory(),
boundary: admittedBoundary,
...native ? { native: true } : {}
});
pendingInputs.add(real);
}
};
capturedPaths.set(path.resolve(source), target);
originalSources.set(target, path.resolve(source));
capturedPaths.set(target, target);
if (!capturedPaths.has(real)) capturedPaths.set(real, target);
receipt.marker(`${stat.isDirectory() ? "directory" : "file"}\0${path.relative(destination, target)}\0`);
if (stat.isDirectory()) {
if (ancestors.has(real)) throw new Error(`Plugin source contains a directory cycle: ${source}`);
ancestors.add(real);
fs.mkdirSync(target, {
recursive: true,
mode: 448
});
const { names, contentHash } = readPluginSourceDirectory(real);
recordContent(contentHash);
for (const name of names) if (!(deferExternalLinks && !nativeAdmission.isRetainedReference(path.join(source, name)) && sourceLinks.defer(path.join(input, name), inputBoundary, path.join(source, name)))) copy(path.join(source, name), path.join(target, name));
ancestors.delete(real);
} else if (stat.isFile()) {
if (stat.nlink > 1n) hardlinkedSources.add(target);
fs.mkdirSync(path.dirname(target), {
recursive: true,
mode: 448
});
const native = nativeAdmission.materialize(real, inputBoundary, target, stat, source);
if (native) nativeAdmission.reconcileSourceInputs(inputs);
else if (captured) fs.copyFileSync(captured, target, fs.constants.COPYFILE_FICLONE);
else {
copyPluginSourceFile(real, inputBoundary, target);
fs.chmodSync(target, 384 | Number(stat.mode & 64n));
}
receipt.file({
target: native?.path ?? target,
boundary: native?.boundary ?? directory,
sizeBytes: Number(stat.size),
native: native !== void 0,
prepared: native?.content,
onContent: (content) => {
native?.record(content);
recordContent(content.contentHash, content.sizeBytes, native !== void 0, native?.sourceIdentity, native?.sourceBoundary ?? inputBoundary);
}
});
additions.add(target);
if (path.basename(target) === "package.json") onPackageMetadata(source, target);
} else throw new Error(`Plugin build input is not a regular file: ${source}`);
};
return copy;
}
//#endregion
//#region src/plugins/plugin-generation-receipt.ts
/** Reuse the initial raw-byte receipt only when its complete ordered inputs still match. */
function createPluginGenerationReceipt(prepared) {
const events = [];
let finished;
const publishContent = (file, content) => {
if (content.sizeBytes !== file.sizeBytes) throw new Error("Plugin source changed while preparing its reload; retry after the edit finishes.");
if (!file.content) {
file.content = content;
file.onContent(content);
}
};
const readContent = (file, receipt) => {
const content = hashPluginSourceFile(file.target, file.boundary, receipt, file.content);
publishContent(file, content);
};
const inputSignature = () => {
if (events.some((event) => typeof event !== "string" && !event.content)) return;
const signature = createHash("sha256");
for (const event of events) signature.update(JSON.stringify(typeof event === "string" ? ["marker", event] : [
"file",
event.sizeBytes,
event.content.contentHash
]));
return signature.digest("hex");
};
return {
marker(framed) {
if (!finished) events.push(framed);
},
file(params) {
const { prepared: content, ...input } = params;
const file = input;
if (file.native && content) publishContent(file, content);
else if (!file.native || finished) readContent(file);
if (!finished) events.push(file);
},
finish() {
if (finished) return finished;
const signature = inputSignature();
const receipt = signature !== void 0 && prepared?.signature === signature ? void 0 : createHash("sha256");
for (const event of events) if (typeof event === "string") receipt?.update(event);
else if (receipt || !event.content) readContent(event, receipt);
finished = {
signature: signature ?? inputSignature(),
sourceDigest: receipt ? receipt.digest("hex") : prepared.sourceDigest
};
events.length = 0;
return finished;
}
};
}
//#endregion
//#region src/plugins/plugin-package-metadata-capture.ts
function createPluginDependencyResolver() {
const roots = /* @__PURE__ */ new Map();
return (name, importer) => {
const key = `${path.dirname(importer)}\0${name}`;
if (roots.has(key)) return roots.get(key);
for (const nodeModules of createRequire(importer).resolve.paths(`${name}/`) ?? []) {
const candidate = path.join(nodeModules, name);
if (fs.existsSync(path.join(candidate, "package.json"))) {
const resolved = {
root: fs.realpathSync(candidate),
lookupDirectory: path.dirname(nodeModules)
};
roots.set(key, resolved);
return resolved;
}
}
roots.set(key, void 0);
};
}
/** Prepare each importer's package lookup once; Node still selects its export target. */
function createPluginDependencyLookup(importer, manifest, resolve, capture) {
const prepared = /* @__PURE__ */ new Map();
return (specifier) => {
if (!specifier || specifier.startsWith(".") || path.isAbsolute(specifier) || URL.canParse(specifier) || isBuiltin(specifier)) return;
const name = packageName(specifier);
if (name === "openclaw" || name === "@openclaw/plugin-sdk") return;
if (specifier.startsWith("#") || manifest?.exports != null && manifest.name === name) return "package-map";
if (!prepared.has(name)) {
const dependency = resolve(name, importer);
if (dependency) capture(name, dependency);
prepared.set(name, dependency !== void 0);
}
return prepared.get(name);
};
}
function pluginDependencyNames(manifest) {
return /* @__PURE__ */ new Set([
...Object.keys(manifest?.dependencies ?? {}),
...Object.keys(manifest?.optionalDependencies ?? {}),
...Object.keys(manifest?.peerDependencies ?? {})
]);
}
/** Native resolvers need declared package lookups before they can resolve a deferred import. */
function createPluginNativeDependencyScopes(resolve, capture) {
const scopes = /* @__PURE__ */ new Map();
return (source, manifest) => {
const key = path.dirname(source);
let scope = scopes.get(key);
if (!scope) {
const dependencies = [...pluginDependencyNames(manifest)].filter((name) => name !== "openclaw" && name !== "@openclaw/plugin-sdk");
scope = { prepareDependencies: dependencies.length ? () => {
for (const name of dependencies) {
const dependency = resolve(name, source);
if (dependency) capture(name, dependency);
}
} : void 0 };
scopes.set(key, scope);
}
return scope;
};
}
function capturePluginDependencies(params) {
const manifest = params.manifestFile ? JSON.parse(fs.readFileSync(params.manifestFile, "utf8")) : {};
const dependencies = [...[...pluginDependencyNames(manifest)].toSorted().map((name) => ({
name,
importer: path.join(params.root, "package.json")
})), ...[...params.references].flatMap(([importer, names]) => [...names].toSorted().map((name) => ({
name,
importer
})))];
for (const { name, importer } of dependencies) {
if (name === "openclaw" || name === "@openclaw/plugin-sdk") continue;
const dependency = params.resolve(name, importer);
if (!dependency) {
if (!params.manifestFile || params.incidental || name in (manifest.optionalDependencies ?? {}) || name in (manifest.peerDependencies ?? {})) continue;
throw new Error(`Plugin dependency ${name} is missing from ${params.root}; install its dependencies and reload.`);
}
params.capture(name, dependency);
}
return manifest;
}
function resolvePluginModulePackageRoot(filename) {
let directory = path.dirname(filename);
while (path.basename(directory) !== "node_modules") {
if (fs.existsSync(path.join(directory, "package.json"))) return directory;
const parent = path.dirname(directory);
if (parent === directory) break;
directory = parent;
}
return path.dirname(filename);
}
function capturePluginModuleSource(filename, capture) {
const real = fs.realpathSync(filename);
if (!fs.statSync(real).isFile()) return;
capture(resolvePluginModulePackageRoot(real), real);
return real;
}
function capturePluginPackageMetadata(root, destination, copy, isRetainedReference, resolveSource) {
const manifest = path.join(destination, "package.json");
copy(path.join(root, "package.json"), manifest);
let data;
try {
data = asOptionalRecord(JSON.parse(fs.readFileSync(manifest, "utf8")));
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
}
if (data && data.exports == null) {
const main = typeof data.main === "string" && data.main ? data.main : void 0;
const bases = main === void 0 ? ["./index"] : [
`./${main}`,
`./${main}/index`,
"./index"
];
const candidates = [...main === void 0 ? [] : [main], ...bases.flatMap((base) => [
".js",
".json",
".node"
].map((extension) => base + extension))];
for (const candidate of candidates) {
const url = new URL(candidate, pathToFileURL(path.join(root, "package.json")));
if (url.protocol !== "file:") continue;
const filename = fileURLToPath(url);
const prepared = resolveSource?.(filename);
const input = prepared?.path ?? filename;
if (isPathInside(root, filename) && fs.statSync(input, { throwIfNoEntry: false })?.isFile()) {
const real = fs.realpathSync(input);
if (!isPathInside(prepared?.boundary ?? root, real) && !isRetainedReference?.(filename, real)) continue;
copy(filename, path.join(destination, path.relative(root, filename)));
break;
}
}
}
return data;
}
const packageName = (specifier) => specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
const importTargetNames = (value) => {
return [...pluginPackageTargets(value)].filter((target) => target && !target.startsWith(".") && !target.startsWith("#") && !path.isAbsolute(target) && !isBuiltin(target)).map(packageName);
};
/** Capture declared targets; native loading owns conditions and subpath selection. */
function* pluginPackageTargets(value) {
if (typeof value === "string") yield value;
else if (value && typeof value === "object") for (const target of Object.values(value)) yield* pluginPackageTargets(target);
}
/** Package '*' substitutes one subpath everywhere; only its declared fixed prefix is walked. */
function visitPluginPackageTargetFiles(params) {
if (!params.target.startsWith("./")) return;
const marker = randomUUID();
let filename;
try {
filename = fileURLToPath(new URL(params.wildcard ? params.target.replaceAll("*", marker) : params.target, pathToFileURL(params.metadata)));
} catch {
return;
}
if (!isPathInside(params.boundary, filename)) return;
const parts = filename.split(marker);
const matcher = parts.length > 1 ? new RegExp(`^${escapeRegExp(parts[0])}([\\s\\S]*)${parts.slice(1).map(escapeRegExp).join("\\1")}$`, "i") : void 0;
const ancestors = /* @__PURE__ */ new Set();
const visit = (source) => {
if (path.relative(params.boundary, source).split(path.sep).some((name) => name === ".git" || name === "node_modules")) return;
const prepared = params.resolveSource?.(source);
const input = prepared?.path ?? source;
const stat = fs.statSync(input, { throwIfNoEntry: false });
if (!stat) return;
const real = fs.realpathSync(input);
if (!isPathInside(prepared?.boundary ?? params.boundary, real) && !(stat.isFile() && params.isRetainedReference?.(source, real))) return;
if (stat.isDirectory()) {
if (!matcher) return;
if (ancestors.has(real)) throw new Error(`Plugin source contains a directory cycle: ${source}`);
ancestors.add(real);
for (const name of fs.readdirSync(input).filter(isPluginSourceEntry).toSorted()) visit(path.join(source, name));
ancestors.delete(real);
} else if (stat.isFile()) {
if (!matcher || matcher.test(source)) params.visit(source);
}
};
visit(matcher ? path.dirname(parts[0] + "_") : filename);
}
const isPluginPackageFile = (root, file) => isPathInside(root, file) && !path.relative(root, file).split(path.sep).includes("node_modules");
function isCapturedPathInside(root, file) {
return process.platform === "win32" ? isPathInside(root, file) : file === root || file.startsWith(root) && (root.endsWith("/") || file.charCodeAt(root.length) === 47);
}
function isCapturedPackageFile(root, file) {
if (process.platform === "win32") return isPluginPackageFile(root, file);
return isCapturedPathInside(root, file) && !/(?:^|\/)node_modules(?:\/|$)/u.test(file.slice(root.length));
}
/** Retain the matched lookup root; dependency links need their own source-relative mapping. */
function findPluginCapturedPackage(packages, filename, directory) {
const file = process.platform === "win32" ? filename : path.resolve(filename);
if (!isCapturedPathInside(directory, file)) return;
for (const owner of packages.values()) {
if (isCapturedPackageFile(owner.capturedRoot, file)) return {
owner,
root: owner.capturedRoot
};
for (const root of owner.links) if (isCapturedPackageFile(root, file)) return {
owner,
root
};
}
}
/** Captured metadata and declared target preparation share the artifact lifetime. */
function createPluginPackageMetadataCapture(params) {
const metadataScopes = /* @__PURE__ */ new Map();
const pendingScopes = /* @__PURE__ */ new Set();
const prepareNativeScopes = () => {
for (const metadata of pendingScopes) {
pendingScopes.delete(metadata);
const scope = metadataScopes.get(metadata);
if (scope.manifest === void 0) try {
scope.manifest = asOptionalRecord(JSON.parse(fs.readFileSync(metadata, "utf8"))) ?? null;
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
scope.manifest = null;
}
const manifest = scope.manifest;
const owner = params.packageForFile(metadata);
if (!manifest || !owner) continue;
scope.prepareAliases(manifest);
if (owner.state === "body") continue;
const sourceMetadata = params.sourceForCaptured(metadata);
const packageExports = manifest.exports;
const exportMap = packageExports && typeof packageExports === "object" && !Array.isArray(packageExports) && Object.keys(packageExports).some((key) => key.startsWith(".")) ? asOptionalRecord(packageExports) ?? {} : { ".": packageExports };
const declarations = [...Object.entries(asOptionalRecord(manifest.imports) ?? {}), ...Object.entries(exportMap)];
for (const [key, value] of declarations) for (const target of pluginPackageTargets(value)) visitPluginPackageTargetFiles({
metadata: sourceMetadata,
boundary: owner.sourceRoot,
target,
wildcard: key.includes("*"),
isRetainedReference: params.isRetainedReference,
resolveSource: params.resolveSource,
visit(filename) {
owner.captureTarget(path.join(path.dirname(metadata), path.relative(path.dirname(sourceMetadata), filename)));
}
});
}
};
return {
record(metadata, prepareAliases) {
if (metadataScopes.has(metadata)) return;
let aliasesPrepared = false;
metadataScopes.set(metadata, { prepareAliases(manifest) {
if (!aliasesPrepared) {
prepareAliases(manifest);
aliasesPrepared = true;
}
} });
pendingScopes.add(metadata);
},
setManifest(metadata, manifest) {
metadataScopes.get(metadata).manifest = manifest;
},
get pending() {
return pendingScopes.size > 0;
},
prepare(scope) {
if (scope?.prepareDependencies) {
scope.prepareDependencies();
delete scope.prepareDependencies;
}
prepareNativeScopes();
},
createScope({ root, destination, boundary, copy, hasSource }) {
const capturedScopes = /* @__PURE__ */ new Map();
const captureScopeMetadata = (scopeDirectory) => {
if (capturedScopes.has(scopeDirectory)) return capturedScopes.get(scopeDirectory);
let scope;
const source = path.join(scopeDirectory, "package.json");
if (hasSource(source) || fs.existsSync(params.resolveSource?.(source)?.path ?? source)) {
const target = path.join(destination, path.relative(root, source));
copy(source, target);
let parsed;
scope = () => {
if (!parsed) {
const metadata = metadataScopes.get(target);
const data = asOptionalRecord(metadata.manifest) ?? asOptionalRecord(JSON.parse(fs.readFileSync(target, "utf8"))) ?? {};
metadata.manifest = data;
metadata.prepareAliases(data);
parsed = {
source,
manifest: data,
aliases: new Set(importTargetNames(data.imports))
};
}
return parsed;
};
} else if (scopeDirectory !== boundary && isPathInside(boundary, path.dirname(scopeDirectory))) scope = captureScopeMetadata(path.dirname(scopeDirectory));
capturedScopes.set(scopeDirectory, scope);
return scope;
};
return {
captureMetadata: captureScopeMetadata,
resolve: (scopeDirectory) => captureScopeMetadata(scopeDirectory)?.()
};
},
clear() {
metadataScopes.clear();
pendingScopes.clear();
}
};
}
const sourceCaptureDirectory = new AsyncLocalStorage();
/** A compute worker's parent reclaims this scratch directory after confirmed exit. */
function withPluginSourceCaptureDirectory(directory, run, managedRoot) {
return sourceCaptureDirectory.run({
directory,
managedRoot
}, run);
}
/** Admissions and failed-input receipts belong to one source acquisition lifetime. */
function createPluginSourceCapture(execute) {
const override = sourceCaptureDirectory.getStore();
const instance = override === void 0 ? retainPluginSourceCaptureInstance() : void 0;
let created;
let directory;
try {
created = override !== void 0 ? fs.mkdtempSync(path.join(override.directory, PLUGIN_SOURCE_CAPTURE_PREFIX)) : instance.createDirectory();
directory = fs.realpathSync(created);
fs.chmodSync(directory, 448);
} catch (error) {
if (created) fs.rmSync(created, {
recursive: true,
force: true
});
instance?.release();
throw error;
}
const inputs = /* @__PURE__ */ new Map();
const pendingInputs = /* @__PURE__ */ new Set();
const additions = /* @__PURE__ */ new Set();
const captureFailures = /* @__PURE__ */ new Map();
let disposed = false;
const acquire = (capture) => {
if (disposed) throw new Error("Plugin module capture has been disposed");
try {
const value = capture();
verifyPluginSourceInputs(inputs, pendingInputs);
return {
value,
additions: [...additions]
};
} catch (error) {
for (const filename of additions) captureFailures.set(filename, error);
throw error;
} finally {
pendingInputs.clear();
additions.clear();
}
};
const assertModuleAvailable = (filename) => {
if (captureFailures.has(filename)) throw captureFailures.get(filename);
};
const captureAdmitted = (run) => {
const capture = () => acquire(run);
return execute ? execute(capture) : capture();
};
const beginDisposal = () => {
disposed = true;
const filenames = directory + path.sep;
const urls = pathToFileURL(filenames).href;
const cache = createRequire(import.meta.url).cache;
for (const id of Object.keys(cache)) if (id.startsWith(filenames) || id.startsWith(urls)) delete cache[id];
captureFailures.clear();
};
return {
inputs,
pendingInputs,
additions,
capture: captureAdmitted,
assertModuleAvailable,
directory,
outputRoot: override?.managedRoot ?? instance?.managedRoot,
linkHost: (hostRoot) => {
const modules = path.join(directory, "node_modules");
fs.mkdirSync(modules, {
recursive: true,
mode: 448
});
fs.symlinkSync(hostRoot, path.join(modules, "openclaw"), "junction");
},
dispose() {
beginDisposal();
if (!retainLoadedPluginSourceCapture(directory)) fs.rmSync(directory, {
recursive: true,
force: true
});
instance?.release();
},
async disposeAsync() {
beginDisposal();
if (!retainLoadedPluginSourceCapture(directory)) await fs$1.rm(directory, {
recursive: true,
force: true
});
await instance?.releaseAsync();
}
};
}
//#endregion
//#region src/plugins/plugin-native-namespace.ts
const pluginNativeNamespaceDirectory = (fact) => fact.referenceRoot ? fact.sourceDirectory : path.join(fact.capturedRoot, "content");
const pluginNativeNamespaceBoundary = (fact) => fact.referenceRoot ?? fact.capturedRoot;
const pluginNativeNamespaceMemberPath = (fact, relative) => fact.referenceRoot ? fact.members[relative].source : path.join(fact.capturedRoot, "content", relative);
const pluginNativeNamespaceMemberRelativePath = (fact, filename) => fact.referenceRoot ? Object.entries(fact.members).find(([, member]) => member.source === filename)[0] : path.relative(pluginNativeNamespaceDirectory(fact), filename);
function capturePluginNativeDirectoryAliases(selected, packageRoots) {
const directories = /* @__PURE__ */ new Map();
for (const [alias, namespace] of selected) {
const root = Object.keys(packageRoots).filter((candidate) => isPathInside(candidate, alias)).toSorted((left, right) => right.length - left.length)[0];
if (root) directories.set(path.join(packageRoots[root], path.relative(root, alias)), namespace.capturedRoot);
}
return directories;
}
/** The installer owns this peer link; a native directory reference must never retarget it. */
function assertPluginNativeNamespaceHost(fact, hostRoot) {
if (!fact.referenceRoot) return;
for (const directory of createRequire(path.join(fact.sourceDirectory, "native-host.cjs")).resolve.paths("openclaw") ?? []) {
const candidate = path.join(directory, "openclaw");
if (!isPathInside(fact.referenceRoot, candidate) || !fs.existsSync(candidate)) continue;
if (fs.realpathSync(candidate) === hostRoot) return;
break;
}
throw new Error("Retained native directory does not resolve the selected OpenClaw host; repair the installed plugin's OpenClaw peer link before loading it.");
}
function inspectDirectory(directory, boundary, outputRoot, sourceDependencies = true) {
const members = /* @__PURE__ */ new Map();
const directories = /* @__PURE__ */ new Map();
const ancestors = /* @__PURE__ */ new Set();
const resolve = createPluginDependencyResolver();
const visit = (filename, relative, packageBoundary, admittedDependency = false) => {
const source = fs.realpathSync(filename);
if (outputRoot && isPathInside(outputRoot, source)) return;
if (!isPathInside(packageBoundary, source)) throw new Error(`Native plugin companion leaves its package: ${filename}`);
const stat = fs.statSync(source, { bigint: true });
if (!stat.isDirectory() && !stat.isFile()) throw new Error(`Native plugin companion is not a regular file: ${filename}`);
const linkTo = stat.isDirectory() ? directories.get(source) : void 0;
if (sourceDependencies && !admittedDependency && ancestors.has(source)) throw new Error(`Native plugin companions contain a directory cycle: ${filename}`);
members.set(relative, {
source,
identity: pluginSourceStatIdentity(stat),
stat,
boundary: packageBoundary,
...linkTo === void 0 ? {} : { linkTo }
});
if (stat.isDirectory()) {
if (linkTo !== void 0) return;
directories.set(source, relative);
ancestors.add(source);
for (const name of fs.readdirSync(source).toSorted()) if (isPluginSourceEntry(name)) visit(path.join(source, name), path.join(relative, name), packageBoundary);
if (sourceDependencies) {
const root = relative ? source : resolvePluginModulePackageRoot(path.join(source, "native-companion.cjs"));
const manifestFile = path.join(root, "package.json");
if (isPathInside(packageBoundary, root) && fs.existsSync(manifestFile)) capturePluginDependencies({
root,
manifestFile,
incidental: Boolean(relative) && !admittedDependency,
references: /* @__PURE__ */ new Map(),
resolve,
capture(name, dependency) {
visit(dependency.root, path.join(relative, "node_modules", name), dependency.root, true);
}
});
} else {
const modules = path.join(source, "node_modules");
for (const name of fs.existsSync(modules) ? fs.readdirSync(modules).toSorted() : []) {
const names = name.startsWith("@") ? fs.readdirSync(path.join(modules, name)).toSorted().map((entry) => `${name}/${entry}`) : [name];
for (const dependency of names) if (dependency !== "openclaw" && dependency !== "@openclaw/plugin-sdk") visit(path.join(modules, dependency), path.join(relative, "node_modules", dependency), boundary);
}
}
ancestors.delete(source);
}
};
visit(directory, "", boundary);
return members;
}
/** Both names and file identities must match: new, removed, and edited companions invalidate reuse. */
function pluginNativeNamespaceIsCurrent(fact, boundary, outputRoot) {
const source = inspectDirectory(fact.sourceDirectory, boundary, outputRoot);
const captured = inspectDirectory(pluginNativeNamespaceDirectory(fact), pluginNativeNamespaceBoundary(fact), fact.referenceRoot ? outputRoot : void 0, Boolean(fact.referenceRoot));
return source.size === Object.keys(fact.members).length && captured.size === source.size && [...source].every(([relative, member]) => {
const prior = fact.members[relative];
return prior?.source === member.source && prior.sourceIdentity === member.identity && prior.capturedIdentity === captured.get(relative)?.identity;
});
}
/** A retained namespace is complete before exposing any native pathname from it. */
function capturePluginNativeNamespace(params) {
const { sourceDirectory, boundary, capturedRoot, managed, outputRoot, previous } = params;
const from = previous ? pluginNativeNamespaceDirectory(previous) : sourceDirectory;
const fromBoundary = previous ? pluginNativeNamespaceBoundary(previous) : boundary;
const before = inspectDirectory(from, fromBoundary, previous ? void 0 : outputRoot, !previous || Boolean(previous.referenceRoot));
const inspectedEntries = [...before.values()].map((member) => ({
path: member.source,
kind: member.stat.isDirectory() ? "directory" : "file"
}));
for (const root of params.inspectionRoots ?? [boundary]) {
inspectedEntries.push({
path: root,
kind: "directory"
});
for (const name of ["openclaw.plugin.json", "package.json"]) {
const filename = path.join(root, name);
if (fs.statSync(filename, { throwIfNoEntry: false })?.isFile()) inspectedEntries.push({
path: filename,
kind: "file"
});
}
}
const boundaryFiles = previous ? /* @__PURE__ */ new Set() : collectPluginSafetyInspectedFiles(inspectedEntries);
for (const filename of params.inspectedFiles ?? []) if (!previous) boundaryFiles.add(fs.realpathSync(filename));
const directory = path.join(capturedRoot, "content");
const linkedSources = /* @__PURE__ */ new Set();
let referenceRoot;
try {
for (const [relative, member] of before) {
const target = path.join(directory, relative);
if (member.linkTo !== void 0) {
fs.mkdirSync(path.dirname(target), {
recursive: true,
mode: 448
});
fs.symlinkSync(path.relative(path.dirname(target), path.join(directory, member.linkTo)), target, "junction");
} else if (member.stat.isDirectory()) fs.mkdirSync(target, {
recursive: true,
mode: 448
});
else if ((previous || managed && (params.managedRoots ?? [boundary]).some((root) => isPathInside(root, member.source))) && !(previous?.members[relative]?.boundaryChecked ?? boundaryFiles.has(member.source))) {
linkPluginSourceFile(member.source, member.boundary, target);
linkedSources.add(member.source);
} else {
copyPluginSourceFile(member.source, member.boundary, target);
fs.chmodSync(target, 384 | Number(member.stat.mode & 64n));
}
}
} catch (error) {
if (!managed || previous || !params.retainedRoot || [...before.values()].some((member) => !isPathInside(params.retainedRoot, member.source)) || ![
"EXDEV",
"EPERM",
"EACCES",
"ENOTSUP",
"EOPNOTSUPP",
"EMLINK",
"ENOSYS"
].some((code) => hasErrnoCode(error, code))) throw error;
fs.rmSync(directory, {
recursive: true,
force: true
});
linkedSources.clear();
fs.symlinkSync(sourceDirectory, directory, "junction");
referenceRoot = params.retainedRoot;
}
const content = /* @__PURE__ */ new Map();
for (const [relative, member] of before) {
if (!member.stat.isFile()) continue;
if (!previous) {
content.set(relative, { sizeBytes: Number(member.stat.size) });
continue;
}
const filename = path.join(directory, relative);
const stat = pluginSourceStatIdentity(fs.statSync(filename, { bigint: true }));
const digest = hashPluginSourceFile(filename, capturedRoot);
const prior = previous?.members[relative];
if (stat !== pluginSourceStatIdentity(fs.statSync(filename, { bigint: true })) || prior && (digest.contentHash !== prior.contentHash || digest.sizeBytes !== prior.sizeBytes)) throw new Error("Native plugin companion changed during admission");
content.set(relative, digest);
}
const after = inspectDirectory(from, fromBoundary, previous ? void 0 : outputRoot, !previous || Boolean(previous.referenceRoot));
const captured = inspectDirectory(referenceRoot ? sourceDirectory : directory, referenceRoot ?? capturedRoot, referenceRoot ? outputRoot : void 0, Boolean(referenceRoot));
if (before.size !== after.size || captured.size !== before.size || [...before].some(([relative, member]) => {
const current = after.get(relative);
return !current || current.source !== member.source || current.identity !== member.identity && (!(managed || previous) || !pluginSourceIdentityChangedOnlyByCtime(member.identity, current.identity));
})) throw new Error("Native plugin directory changed during admission");
const changed = /* @__PURE__ */ new Map();
const fact = {
sourceDirectory,
capturedRoot,
managed,
...referenceRoot ? { referenceRoot } : {},
members: Object.fromEntries([...after].map(([relative, member]) => {
if (linkedSources.has(member.source) && member.identity === captured.get(relative).identity) changed.set(member.source, member.identity);
const old = previous?.members[relative];
if (old) old.capturedIdentity = member.identity;
return [relative, {
source: old?.source ?? member.source,
sourceIdentity: old?.sourceIdentity ?? member.identity,
capturedIdentity: captured.get(relative).identity,
boundaryChecked: old?.boundaryChecked ?? boundaryFiles.has(member.source),
...content.get(relative)
}];
}))
};
if (previous?.managed) for (const [relative, member] of Object.entries(fact.members)) {
const current = fs.statSync(member.source, {
bigint: true,
throwIfNoEntry: false
});
if (linkedSources.has(after.get(relative).source) && current && current.dev === after.get(relative).stat.dev && current.ino === after.get(relative).stat.ino && pluginSourceStatIdentity(current) === member.capturedIdentity) {
member.sourceIdentity = pluginSourceStatIdentity(current);
previous.members[relative].sourceIdentity = member.sourceIdentity;
changed.set(member.source, member.sourceIdentity);
}
}
return {
fact,
changed
};
}
/** Fill dormant companion digests after the legacy initial receipt has consumed native bytes. */
function finishPluginNativeNamespace(fact) {
for (const [relative, member] of Object.entries(fact.members)) {
if (member.sizeBytes === void 0 || member.contentHash) continue;
const filename = pluginNativeNamespaceMemberPath(fact, relative);
const content = hashPluginSourceFile(filename, pluginNativeNamespaceBoundary(fact));
if (content.sizeBytes !== member.sizeBytes || pluginSourceStatIdentity(fs.statSync(filename, { bigint: true })) !== member.capturedIdentity) throw new Error("Native plugin companion changed before admission completed");
member.contentHash = content.contentHash;
}
}
//#endregion
//#region src/plugins/plugin-native-reference.ts
/** A hardlink needs the owner's complete-directory check before the generation is exposed. */
function linkPluginNativeReference(source, target, fact) {
try {
fs.symlinkSync(fact.capturedPath, target, "file");
return "symlink";
} catch (error) {
if (![
"EPERM",
"EACCES",
"ENOSYS",
"ENOTSUP"
].some((code) => hasErrnoCode(error, code))) throw error;
}
const boundary = path.dirname(fact.capturedPath);
linkPluginSourceFile(fact.capturedPath, boundary, target);
if (fact.contentHash) {
const content = hashPluginSourceFile(fact.capturedPath, boundary);
if (content.contentHash !== fact.contentHash || content.sizeBytes !== fact.sizeBytes) throw new Error("Native plugin artifact changed while linking its generation");
}
const captured = fs.statSync(fact.capturedPath, { bigint: true });
const sourceStat = fs.statSync(source, { bigint: true });
const identity = pluginSourceStatIdentity(sourceStat);
if (identity !== fact.sourceIdentity && (!pluginSourceIdentityChangedOnlyByCtime(fact.sourceIdentity, identity) || (captured.dev !== sourceStat.dev || captured.ino !== sourceStat.ino) && (!fact.contentHash || hashPluginSourceFile(source, path.dirname(source)).contentHash !== fact.contentHash))) throw new Error("Plugin source changed while linking its native artifact");
fact.sourceIdentity = identity;
fact.capturedIdentity = pluginSourceStatIdentity(captured);
return "hardlink";
}
function resolveNativeHost(filename) {
for (const modules of createRequire(filename).resolve.paths("openclaw") ?? []) {
const candidate = path.join(modules, "openclaw");
if (fs.existsSync(candidate)) return fs.realpathSync(candidate);
}
}
/** Hardlinks keep bytes but not realpath parents; only a coherent captured directory can use them. */
function assertPluginNativeReferenceNamespace(target, fact, namespace, boundary, expectedHost) {
const relative = pluginNativeNamespaceMemberRelativePath(namespace, fact.capturedPath);
const directory = path.dirname(relative);
try {
for (const [name, member] of Object.entries(namespace.members)) {
if (!isPathInside(directory, name || ".")) continue;
const filename = fs.realpathSync(path.join(path.dirname(target), path.relative(directory, name || ".")));
if (!isPathInside(boundary, filename) && !isPathInside(pluginNativeNamespaceBoundary(namespace), filename)) throw new Error(`Companion ${name} leaves the captured generation`);
const current = fs.statSync(filename, { bigint: true });
if (member.sizeBytes === void 0) {
if (!current.isDirectory()) throw new Error(`Companion ${name} is not a directory`);
continue;
}
const captured = fs.statSync(pluginNativeNamespaceMemberPath(namespace, name), { bigint: true });
if (current.dev === captured.dev && current.ino === captured.ino) continue;
const content = hashPluginSourceFile(filename, path.dirname(filename));
if (content.contentHash !== member.contentHash || content.sizeBytes !== member.sizeBytes) throw new Error(`Companion ${name} differs from its captured bytes`);
}
if (expectedHost && resolveNativeHost(target) !== expectedHost) throw new Error("The native companion directory resolves a different OpenClaw host");
} catch (cause) {
throw new Error("Native plugin companions cannot be preserved without file symlinks. Enable file symlink support for this filesystem, then reload the plugin.", { cause });
}
}
/** The caller has selected this exact recovery-map entry; symlinks and hardlinks retain its inode. */
function admitPluginNativeRecoveryReference(canonical, fact, prepared = fact) {
if (fs.realpathSync(fact.capturedPath) !== fact.capturedPath) return;
const captured = fs.statSync(fact.capturedPath, { bigint: true });
const source = canonical === fact.capturedPath ? captured : fs.statSync(canonical, { bigint: true });
if (source.dev !== captured.dev || source.ino !== captured.ino) return;
const identity = pluginSourceStatIdentity(source);
if (identity !== prepared.capturedIdentity || prepared.contentHash !== fact.contentHash || prepared.sizeBytes !== fact.sizeBytes) {
const content = hashPluginSourceFile(fact.capturedPath, path.dirname(fact.capturedPath));
if (content.contentHash !== fact.contentHash || content.sizeBytes !== fact.sizeBytes) return;
}
return {
...fact,
sourceIdentity: identity,
capturedIdentity: identity
};
}
//#endregion
//#region src/plugins/plugin-generation-source-lookup.ts
function canonicalSource(rootDir, sourceRoot, source) {
const lexical = path.resolve(source);
const relative = relativePluginPathInsideRootSync(rootDir, lexical);
return relative === void 0 ? lexical : path.join(sourceRoot, relative);
}
function getCapturedSource(sources, rootDir, sourceRoot, source) {
const lexical = path.resolve(source);
return sources.get(lexical) ?? sources.get(canonicalSource(rootDir, sourceRoot, lexical));
}
function createRecoverySourceResolver(rootDir, sourceRoot, sources) {
return (source) => {
const captured = getCapturedSource(sources, rootDir, sourceRoot, source);
if (!captured) throw new Error("Plugin recovery entry is outside its captured source package");
return captured;
};
}
function createRecoverySourceDisposal(recovery, native) {
return {
dispose: () => {
try {
recovery.dispose();
} finally {
native?.dispose();
}
},
disposeAsync: async () => {
await Promise.all([recovery.disposeAsync(), ...native ? [native.disposeAsync()] : []]);
}
};
}
function captureRecoverySource({ rootDir, sourceRoot, capturedRoot, boundaryRoot, capturedPaths, captureNativeRecovery }) {
const recovery = createPluginSourceCapture();
let native;
try {
native = captureNativeRecovery?.();
const hardlinkedTargets = /* @__PURE__ */ new Map();
fs.cpSync(boundaryRoot, recovery.directory, {
recursive: true,
verbatimSymlinks: true,
filter: (from, to) => {
const fact = native?.references.get(from);
if (!fact) return true;
const retained = {
...fact,
sourceIdentity: fact.capturedIdentity
};
if (linkPluginNativeReference(fact.capturedPath, to, retained) === "hardlink") hardlinkedTargets.set(to, retained);
native.references.set(from, retained);
return false;
}
});
for (const [target, fact] of hardlinkedTargets) assertPluginNativeReferenceNamespace(target, fact, native.namespaces.get(fact.namespace), recovery.directory);
const relocate = (filename) => path.join(recovery.directory, path.relative(boundaryRoot, filename));
for (const captured of new Set(capturedPaths.values())) fs.lstatSync(relocate(captured));
const sources = new Map(Array.from(capturedPaths, ([source, captured]) => [source, relocate(captured)]));
return {
rootDir: relocate(capturedRoot),
resolve: createRecoverySourceResolver(rootDir, sourceRoot, sources),
native: native && {
...native,
references: new Map(Array.from(native.references, ([source, fact]) => [relocate(source), fact])),
directories: new Map(Array.from(native.directories, ([source, namespace]) => [relocate(source), namespace]))
},
...createRecoverySourceDisposal(recovery, native)
};
} catch (error) {
createRecoverySourceDisposal(recovery, native).dispose();
if (hasErrnoCode(error, "ENOENT")) throw new PluginSourceRecoveryUnavailableError(error);
throw error;
}
}
/** Resolves captured source identities and gives recovery its own copy of their bytes. */
function createPluginGenerationSourceLookup({ rootDir, sourceRoot, capturedRoot, boundaryRoot, capturedPaths, hardlinkedSources, assertModuleAvailable, captureNativeRecovery }) {
const resolveCaptured = (source) => {
const captured = getCapturedSource(capturedPaths, rootDir, sourceRoot, source);
return captured && isPathInside$1(capturedRoot, captured) ? captured : void 0;
};
return {
hasSource: (source) => resolveCaptured(source) !== void 0,
resolve: (source, rejectHardlinks = false) => {
const captured = resolveCaptured(source);
if (!captured) throw new Error("Plugin entry is outside its captured source package");
if (rejectHardlinks && hardlinkedSources.has(captured)) throw new Error("Plugin source is hardlinked; use a separate file and reload.");
assertModuleAvailable(captured);
return captured;
},
captureRecoverySource: () => captureRecoverySource({
rootDir,
sourceRoot,
capturedRoot,
boundaryRoot,
capturedPaths,
captureNativeRecovery
})
};
}
//#endregion
//#region src/plugins/plugin-source-admission-store.ts
/** Publish native admission facts without replacing the running metadata snapshot. */
async function publishPluginSourceAdmission(params) {
if (isArtifactPreservingStateRead()) return false;
const { env, stateDir, ...publication } = params;
const prepared = structuredClone(publication);
const context = captureOpenClawStateWorkerContext(resolveInstalledPluginIndexStateDatabaseOptions({
env,
stateDir
}));
const { runOpenClawStateWorkerOperation } = await import("./openclaw-state-worker-store-DBFQDJTf.mjs");
return await runOpenClawStateWorkerOperation(context, (scope) => scope.execute({
type: "plugins.metadata.sourceAdmission.publish",
input: prepared
}), { existingOnly: true }) ?? false;
}
//#endregion
//#region src/plugins/plugin-native-admission.ts
function nativeMemberForSource(namespace, source) {
const relative = path.relative(namespace.sourceDirectory, source);
if (namespace.members[relative]?.source === source) return relative;
return Object.entries(namespace.members).find(([, member]) => member.source === source)?.[0];
}
function createNativeRecovery(receipt, references, namespaces, directories, roots) {
const custody = {};
const paths = [...references.values()].map((fact) => fact.capturedPath);
paths.push(...[...namespaces.keys()].map((root) => path.join(root, "content")));
const pins = [...new Set(paths)].map(retainPluginNativeCapturePath);
for (const root of roots) snapshotOwners.get(root).add(custody);
let disposed = false;
const release = () => {
if (disposed) return [];
disposed = true;
for (const pin of pins) pin();
return roots.filter((root) => {
const owners = snapshotOwners.get(root);
owners.delete(custody);
return owners.size === 0;
});
};
return {
receipt: { ...receipt },
references,
namespaces,
directories,
retain(cache) {
if (disposed) throw new Error("Plugin native recovery has been disposed");
const state = nativeAdmissionStateFor(cache);
for (const [key, namespace] of namespaces) {
state.namespaces.set(key, namespace);
retainNativePath(state, path.join(key, "content"));
}
for (const root of roots) {
state.roots.add(root);
snapshotOwners.get(root).add(state);
}
for (const fact of references.values()) retainNativePath(state, fact.capturedPath);
},
dispose() {
for (const root of release()) root.dispose();
},
async disposeAsync() {
await Promise.all(release().map((root) => root.disposeAsync()));
}
};
}
function isNativeArtifact(source, boundary, stat, known) {
return /\.(?:node|so(?:\.\d+)*|dylib|dll|exe|bin)$/i.test(source) || (stat.mode & 73n) !== 0n && path.extname(source) === "" && (known || isPluginNativeExecutable(source, boundary));
}
/** Admission is shared below registration; each generation still owns its module graph. */
function createPluginNativeAdmission(rootDir, directory, entryFile, recovery, outputRoot) {
recovery?.retain(getPluginCache());
const state = nativeAdmissionStateFor();
const key = `${path.resolve(rootDir)}\0${entryFile ? path.resolve(entryFile) : ""}`;
const owner = state.owners.get(path.resolve(rootDir));
const prepared = recovery?.receipt ?? state.receipts.get(key);
const selected = /* @__PURE__ */ new Map();
const priorNamespaces = /* @__PURE__ */ new Set();
const files = /* @__PURE__ */ new Map();
const targets = /* @__PURE__ */ new Map();
const hardlinkedTargets = /* @__PURE__ */ new Set();
const recoveredFiles = /* @__PURE__ */ new Map();
let hostRoot;
let finalReceipt;
const namespaces = () => [...new Set(selected.values())];
for (const [alias, id] of recovery?.directories ?? []) {
const namespace = state.namespaces.get(id);
if (namespace) selected.set(alias, namespace);
}
const namespaceFor = (source) => [...selected].toSorted(([left], [right]) => right.length - left.length).find(([alias]) => isPathInside(alias, source));
const resolvePreparedSource = (source) => {
const file = files.get(path.resolve(source));
if (file) return {
path: file.capturedPath,
boundary: pluginNativeNamespaceBoundary(state.namespaces.get(file.namespace))
};
if (!recovery) for (const namespace of namespaces()) {
const relative = nativeMemberForSource(namespace, source);
if (relative !== void 0) return {
path: pluginNativeNamespaceMemberPath(namespace, relative),
boundary: pluginNativeNamespaceBoundary(namespace)
};
}
const found = namespaceFor(path.resolve(source));
return found ? {
path: path.join(pluginNativeNamespaceDirectory(found[1]), path.relative(found[0], path.resolve(source))),
boundary: pluginNativeNamespaceBoundary(found[1])
} : void 0;
};
const sourceForPrepared = (filename) => {
for (const [alias, namespace] of selected) {
const root = pluginNativeNamespaceDirectory(namespace);
if (isPathInside(root, filename)) {
const relative = path.relative(root, filename);
return !recovery && namespace.members[relative]?.source || path.join(alias, relative);
}
}
};
const createNamespace = (sourceDirectory, boundary, managed, previous, retainedRoot) => {
const root = createPluginNativeCaptureRoot(state.captureStateDir);
state.roots.add(root);
snapshotOwners.set(root, /* @__PURE__ */ new Set([state]));
const { fact, changed } = capturePluginNativeNamespace({
sourceDirectory,
boundary,
managed,
previous,
retainedRoot,
managedRoots: [...state.managedRoots.keys()],
capturedRoot: root.directory,
outputRoot,
inspectionRoots: [.../* @__PURE__ */ new Set([boundary, ...owner ? [owner.rootDir] : []])],
inspectedFiles: [
owner?.source,
owner?.setupSource,
entryFile
].filter((file) => Boolean(file))
});
for (const namespace of state.namespaces.values()) for (const [relative, member] of Object.entries(namespace.members)) {
const identity = changed.get(member.source);
const sourceChanged = identity !== void 0 && pluginSourceIdentityChangedOnlyByCtime(member.sourceIdentity, identity);
const captureChanged = identity !== void 0 && pluginSourceIdentityChangedOnlyByCtime(member.capturedIdentity, identity);
if (!identity || !sourceChanged && !captureChanged) continue;
if (!fs.statSync(pluginNativeNamespaceDirectory(namespace), { throwIfNoEntry: false })) break;
if (namespace !== previous || !captureChanged) {
const capturedHash = hashPluginSourceFile(pluginNativeNamespaceMemberPath(namespace, relative), pluginNativeNamespaceBoundary(namespace)).contentHash;
if (member.contentHash && capturedHash !== member.contentHash || sourceChanged && hashPluginSourceFile(member.source, path.dirname(member.source)).contentHash !== capturedHash) throw new Error("Native plugin companion changed during admission");
member.contentHash ??= capturedHash;
}
if (sourceChanged) member.sourceIdentity = identity;
if (captureChanged) member.capturedIdentity = identity;
}
state.namespaces.set(root.directory, fact);
return fact;
};
const publish = () => {
if (!finalReceipt || files.size === 0) return;
for (const [source, fact] of files) {
state.files.set(source, fact);
retainNativePath(state, fact.capturedPath);
retainNativePath(state, path.join(fact.namespace, "content"));
}
const used = new Set([...files.values()].map((fact) => fact.namespace));
const nativeNamespaces = Object.fromEntries([...used].map((id) => [id, state.namespaces.get(id)]));
const next = structuredClone({
...finalReceipt,
nativeArtifacts: Object.fromEntries(files),
nativeNamespaces
});
const unchanged = isDeepStrictEqual(state.receipts.get(key), next);
state.receipts.set(key, next);
if (!owner || state.artifactPreservingReadOnly) return;
if (unchanged) {
startNativeAdmissionPublication(state, key);
return;
}
const roots = [...state.roots].filter((root) => used.has(root.directory));
const publication = () => publishPluginSourceAdmission({
stateDir: state.publicationStateDir,
pluginId: owner.pluginId,
rootDir: owner.rootDir,
installRecordHash: owner.installRecordHash,
key,
receipt: next
}).then((accepted) => {
if (accepted) {
for (const root of roots) root.commit();
if (state.publications.get(key) === publication) state.publications.delete(key);
}
}).catch((error) => process.emitWarning(`Plugin source receipt was not recorded: ${String(error)}`));
state.publications.set(key, publication);
startNativeAdmissionPublication(state, key);
};
const refreshReference = (fact, input, target) => {
const namespace = state.namespaces.get(fact.namespace);
const relative = pluginNativeNamespaceMemberRelativePath(namespace, fact.capturedPath);
const member = namespace.members[relative];
const linked = {
...fact,
sourceIdentity: pluginSourceStatIdentity(fs.statSync(input, { bigint: true }))
};
if (linkPluginNativeReference(input, target, linked) === "hardlink") hardlinkedTargets.add(target);
else hardlinkedTargets.delete(target);
const oldIdentity = member.capturedIdentity;
member.capturedIdentity = linked.capturedIdentity;
if (member.sourceIdentity === oldIdentity) member.sourceIdentity = linked.capturedIdentity;
fact.sourceIdentity = member.sourceIdentity;
fact.capturedIdentity = linked.capturedIdentity;
return linked.sourceIdentity;
};
const assertReferenceNamespaces = () => {
for (const target of hardlinkedTargets) {
const fact = files.get(targets.get(target));
assertPluginNativeReferenceNamespace(target, fact, state.namespaces.get(fact.namespace), directory, hostRoot);
}
};
const linkHost = (selectedHost) => {
hostRoot = fs.realpathSync(selectedHost);
for (const namespace of namespaces()) {
if (namespace.referenceRoot) {
assertPluginNativeNamespaceHost(namespace, hostRoot);
continue;
}
const link = path.join(namespace.capturedRoot, "node_modules", "openclaw");
const present = fs.lstatSync(link, { throwIfNoEntry: false });
if (present && fs.realpathSync(link) !== hostRoot) {
const replacement = createNamespace(namespace.sourceDirectory, namespace.sourceDirectory, namespace.managed, namespace);
priorNamespaces.add(namespace);
fs.mkdirSync(path.join(replacement.capturedRoot, "node_modules"), {
recursive: true,
mode: 448
});
fs.symlinkSync(hostRoot, path.join(replacement.capturedRoot, "node_modules", "openclaw"), "junction");
for (const [alias, selectedNamespace] of selected) if (selectedNamespace === namespace) selected.set(alias, replacement);
for (const [source, fact] of files) {
if (fact.namespace !== namespace.capturedRoot) continue;
const relative = pluginNativeNamespaceMemberRelativePath(namespace, fact.capturedPath);
const member = replacement.members[relative];
files.set(source, {
...fact,
namespace: replacement.capturedRoot,
capturedPath: path.join(pluginNativeNamespaceDirectory(replacement), relative),
capturedIdentity: member.capturedIdentity,
sourceIdentity: member.sourceIdentity
});
}
for (const [target, source] of targets) {
const fact = files.get(source);
if (fact.namespace !== replacement.capturedRoot) continue;
fs.unlinkSync(target);
refreshReference(fact, fact.capturedPath, target);
}
} else if (!present) {
fs.mkdirSync(path.dirname(link), {
recursive: true,
mode: 448
});
try {
fs.symlinkSync(hostRoot, link, "junction");
} catch (error) {
if (!hasErrnoCode(error, "EEXIST")) throw error;
if (fs.realpathSync(link) !== hostRoot) return linkHost(hostRoot);
}
}
}
};
return {
prepared,
resolvePreparedSource,
sourceForPrepared,
reconcileSourceInputs(inputs) {
for (const namespace of /* @__PURE__ */ new Set([...priorNamespaces, ...namespaces()])) for (const [relative, member] of Object.entries(namespace.members)) {
if (member.sizeBytes === void 0) continue;
const capturedPath = pluginNativeNamespaceMemberPath(namespace, relative);
for (const [source, identity] of [[member.source, member.sourceIdentity], [capturedPath, member.capturedIdentity]]) {
const input = inputs.get(source);
if (!input || input.identity === identity) continue;
if (!pluginSourceIdentityChangedOnlyByCtime(input.identity, identity)) throw new Error("Plugin source changed during native namespace admission");
member.contentHash ??= hashPluginSourceFile(capturedPath, pluginNativeNamespaceBoundary(namespace)).contentHash;
if (input.contentHash !== member.contentHash) throw new Error("Plugin source changed during native namespace admission");
input.identity = identity;
}
}
},
captureRecovery(receipt, packageRoots) {
const references = new Map([...targets].map(([target, source]) => [target, { ...files.get(source) }]));
const used = new Set([...references.values()].map((fact) => fact.namespace));
return createNativeRecovery(receipt, references, new Map([...used].map((id) => [id, structuredClone(state.namespaces.get(id))])), capturePluginNativeDirectoryAliases(selected, packageRoots), [...state.roots].filter((root) => used.has(root.directory)));
},
isRetainedReference(source, real) {
const fact = recovery?.references.get(path.resolve(source));
if (!fact) return false;
const canonical = real ?? fs.realpathSync(source);
const admitted = admitPluginNativeRecoveryReference(canonical, fact, recoveredFiles.get(canonical));
if (!admitted) return false;
recoveredFiles.set(canonical, admitted);
return true;
},
materialize(source, boundary, target, stat, logicalSource = sourceForPrepared(source) ?? source) {
const recovered = recoveredFiles.get(source);
const resolvedSource = sourceForPrepared(source) ?? source;
const known = files.get(logicalSource) ?? state.files.get(logicalSource) ?? recovered;
if (owner?.source === logicalSource || owner?.setupSource === logicalSource || entryFile === logicalSource) return;
if (!isNativeArtifact(source, boundary, stat, Boolean(known))) return;
const found = namespaceFor(resolvedSource);
let namespace = recovered ? state.namespaces.get(recovered.namespace) : namespaces().find((candidate) => Object.values(candidate.members).some((member) => member.source === resolvedSource)) ?? found?.[1];
let alias = found?.[0] ?? path.dirname(resolvedSource);
if (!namespace) {
const tree = [...state.managedRoots].find(([root]) => isPathInside(root, resolvedSource));
const managed = Boolean(tree);
const admittedBoundary = tree?.[0] ?? boundary;
const packageRoot = resolvePluginModulePackageRoot(resolvedSource);
alias = isPathInside(admittedBoundary, packageRoot) ? packageRoot : path.dirname(resolvedSource);
const preferred = known && state.namespaces.get(known.namespace);
namespace = [.../* @__PURE__ */ new Set([...preferred ? [preferred] : [], ...[...state.namespaces.values()].toReversed()])].find((candidate) => {
if (!isPathInside(candidate.sourceDirectory, alias) || candidate.managed !== managed || candidate.referenceRoot !== void 0 && (tree?.[1] !== "retained-npm" || candidate.referenceRoot !== tree[0])) return false;
try {
return pluginNativeNamespaceIsCurrent(candidate, admittedBoundary, outputRoot);
} catch {
return false;
}
});
namespace ??= createNamespace(alias, admittedBoundary, managed, void 0, tree?.[1] === "retained-npm" ? tree[0] : void 0);
alias = namespace.sourceDirectory;
selected.set(alias, namespace);
} else if (recovered) alias = [...selected].find(([, candidate]) => candidate === namespace)?.[0] ?? alias;
const relative = recovered ? pluginNativeNamespaceMemberRelativePath(namespace, recovered.capturedPath) : nativeMemberForSource(namespace, resolvedSource) ?? path.relative(alias, resolvedSource);
const member = namespace.members[relative];
if (!member || member.sizeBytes === void 0) throw new Error("Native plugin artifact is outside its admitted directory");
const fact = {
sourceIdentity: member.sourceIdentity,
contentHash: member.contentHash ?? "",
sizeBytes: member.sizeBytes,
namespace: namespace.capturedRoot,
capturedPath: pluginNativeNamespaceMemberPath(namespace, relative),
capturedIdentity: member.capturedIdentity
};
const inputIdentity = refreshReference(fact, source, target);
files.set(logicalSource, fact);
targets.set(target, logicalSource);
return {
fact,
path: fact.capturedPath,
boundary: pluginNativeNamespaceBoundary(namespace),
sourceIdentity: inputIdentity,
sourceBoundary: path.dirname(source),
content: fact.contentHash ? {
contentHash: fact.contentHash,
sizeBytes: fact.sizeBytes
} : void 0,
record(content) {
Object.assign(fact, content);
Object.assign(member, content);
for (const other of namespaces()) for (const sibling of Object.values(other.members)) {
if (sibling.sizeBytes !== content.sizeBytes || sibling.contentHash) continue;
if (sibling.source === member.source && sibling.sourceIdentity === member.sourceIdentity || sibling.capturedIdentity.split(":").slice(0, 2).join(":") === member.capturedIdentity.split(":").slice(0, 2).join(":")) Object.assign(sibling, content);
}
}
};
},
finish(receipt) {
if (!files.size) return;
for (const namespace of namespaces()) finishPluginNativeNamespace(namespace);
finalReceipt = receipt;
if (hostRoot) linkHost(hostRoot);
assertReferenceNamespaces();
publish();
},
linkHost(selectedHost) {
linkHost(selectedHost);
assertReferenceNamespaces();
publish();
return new Map([...files].map(([source, fact]) => [source, fact.sourceIdentity]));
}
};
}
//#endregion
//#region src/plugins/plugin-source-references.ts
function capturedPluginModuleUrl(filename, specifier, conditions) {
const url = pathToFileURL(filename);
if (!conditions.includes("require") && (specifier.startsWith(".") || specifier.startsWith("file:") || path.isAbsolute(specifier))) {
const requested = new URL(specifier, url);
url.search = requested.search;
url.hash = requested.hash;
}
return url;
}
function staticString(node) {
if ((node?.type === "Literal" || node?.type === "StringLiteral") && typeof node.value === "string") return node.value;
if (node?.type === "TemplateLiteral" && node.expressions?.length === 0) return node.quasis?.[0]?.value.cooked ?? void 0;
}
/** Whether this expression receives a value, directly or through a destructuring pattern. */
function isAssignmentTarget(target) {
let child = target;
for (let parent = target.parentPath; parent?.node; parent = parent.parentPath) {
switch (parent.node.type) {
case "AssignmentExpression":
case "AssignmentPattern":
case "ForInStatement":
case "ForOfStatement": return child.key === "left";
case "UpdateExpression": return true;
case "ObjectProperty":
if (child.key !== "value") return false;
break;
case "ArrayPattern":
case "ObjectPattern":
case "RestElement": break;
default: return false;
}
child = parent;
}
return false;
}
function unwrapReferenceArgument(input) {
let argument = input;
while (argument?.isTSAsExpression() || argument?.isTSTypeAssertion() || argument?.isTSNonNullExpression() || argument?.isTSSatisfiesExpression() || argument?.isTSInstantiationExpression()) argument = argument.get("expression");
return argument;
}
/** Inspect authored TypeScript syntax before Jiti rewrites module operations. */
function inspectPluginTypeScriptExecutionFacts(source, sourceText, resolver) {
let hasComputedImport = false;
const staticImports = /* @__PURE__ */ new Map();
const recordStaticImport = (specifier, sideEffect) => {
staticImports.set(specifier, (staticImports.get(specifier) ?? false) || sideEffect);
};
resolver.transform({
source: sourceText,
filename: source,
ts: true,
async: true,
babel: { plugins: [{ pre(file) {
file.path.traverse({
ImportDeclaration(declaration) {
const specifier = staticString(declaration.get("source").node);
if (specifier !== void 0) recordStaticImport(specifier, declaration.node?.specifiers?.length === 0);
},
ExportNamedDeclaration(declaration) {
const specifier = staticString(declaration.get("source").node);
if (specifier !== void 0) recordStaticImport(specifier, false);
},
ExportAllDeclaration(declaration) {
const specifier = staticString(declaration.get("source").node);
if (specifier !== void 0) recordStaticImport(specifier, false);
},
ImportExpression(expression) {
if (staticString(expression.get("source").node) === void 0) hasComputedImport = true;
},
CallExpression(call) {
const args = call.get("arguments");
if (call.get("callee").node?.type === "Import" && staticString(unwrapReferenceArgument(args[0])?.node) === void 0) hasComputedImport = true;
}
});
} }] }
});
return {
hasComputedImport,
staticImports: [...staticImports].map(([specifier, sideEffect]) => ({
specifier,
sideEffect
}))
};
}
/** Read the native factory binding before Jiti rewrites modules and import.meta. */
function isCurrentFileRequire(call) {
const callee = call.get("callee");
const reference = callee.isMemberExpression() && !callee.node.computed && callee.get("property").isIdentifier({ name: "resolve" }) ? callee.get("object") : callee;
let init = reference;
if (reference.isIdentifier() && reference.node.name) {
const binding = reference.scope.getBinding(reference.node.name);
if (!binding?.constant || !binding.path.isVariableDeclarator()) return false;
init = binding.path.get("init");
}
if (!init.isCallExpression()) return false;
const args = init.get("arguments");
const anchor = args.length === 1 ? args[0] : void 0;
if (!anchor || !(anchor.isMemberExpression() && !anchor.node.computed && anchor.get("object").node?.type === "MetaProperty" && anchor.matchesPattern("import.meta.url") || anchor.node?.type === "Identifier" && anchor.node.name === "__filename" && !anchor.scope.getBinding("__filename"))) return false;
const factory = init.get("callee");
return ["module", "node:module"].some((source) => factory.referencesImport(source, "createRequire") || factory.isMemberExpression() && !factory.node.computed && factory.get("property").isIdentifier({ name: "createRequire" }) && factory.get("object").referencesImport(source, "default"));
}
const TRANSFORMED_REFERENCE_NAMES = /* @__PURE__ */ new Set([
"createRequire",
"URL",
"readFile",
"readFileSync",
"createReadStream",
"join",
"resolve",
"require",
"jitiImport",
"jitiESMResolve",
"__dirname"
]);
function* sourceChildren(node) {
for (const value of Object.values(node)) for (const child of Array.isArray(value) ? value : [value]) if (child && typeof child === "object" && "type" in child) yield child;
}
function parseNativePluginJavaScript(source, sourceText) {
if (!/\.[cm]?js$/.test(source)) return;
let tree;
try {
tree = parse$1(sourceText, {
ecmaVersion: "latest",
sourceType: "module",
allowAwaitOutsideFunction: true
});
} catch (error) {
if (error instanceof SyntaxError) return;
throw error;
}
const needsTransform = (node, exportedDeclaration = false) => {
if (node.type === "MetaProperty") return true;
if (node.type === "VariableDeclaration" && (node.kind === "using" || node.kind === "await using")) return true;
if ((node.type === "ForInStatement" || node.type === "ForOfStatement") && node.left.type !== "VariableDeclaration") return true;
const exported = node.type === "ExportSpecifier" || node.type === "ExportAllDeclaration" ? node.exported : void 0;
if ((exported?.type === "Identifier" ? exported.name : staticString(exported)) === "__esModule" || exportedDeclaration && node.type === "Identifier" && node.name === "__esModule") return true;
if (node.type === "Identifier" && (node.name === "require" || node.name === "__dirname")) return true;
if (node.type === "ImportExpression" && (node.source.type !== "Literal" || typeof node.source.value !== "string" || node.options != null)) return true;
if (node.type === "ImportDeclaration") {
if (node.source.value === "module" || node.source.value === "node:module") return true;
for (const specifier of node.specifiers) {
const imported = specifier.type === "ImportSpecifier" ? specifier.imported.type === "Identifier" ? specifier.imported.name : staticString(specifier.imported) : void 0;
if (TRANSFORMED_REFERENCE_NAMES.has(specifier.local.name) || imported !== void 0 && TRANSFORMED_REFERENCE_NAMES.has(imported)) return true;
}
}
for (const child of sourceChildren(node)) if (needsTransform(child, exportedDeclaration || node.type === "ExportNamedDeclaration" && child === node.declaration)) return true;
return false;
};
return needsTransform(tree) ? void 0 : tree;
}
function parseTransformedPluginSource(source, code) {
try {
return parse$1(code, {
ecmaVersion: "latest",
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true
});
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
throw new SyntaxError(`${source}: could not parse transformed source: ${error.message}`, { cause: error });
}
}
/** Visit literal module and explicit asset inputs without evaluating plugin code. */
function visitPluginSourceReferences(source, sourceText, resolver, visitReference) {
const visitDirectoryAsset = (name, parts) => {
if ((name === "join" || name === "resolve") && parts.length > 0 && parts.every((part) => part !== void 0) && (name === "join" || !parts.some((part) => path.isAbsolute(part)))) visitReference(path.join(".", ...parts), "asset");
};
const tree = parseNativePluginJavaScript(source, sourceText) ?? parseTransformedPluginSource(source, resolver.transform({
source: sourceText,
filename: source,
ts: /\.[cm]?tsx?$/.test(source),
async: true,
babel: { plugins: [{ pre(file) {
file.path.traverse({
MemberExpression(member) {
if (member.get("object").node?.type === "MetaProperty" && isAssignmentTarget(member)) member.replaceWith({
type: "Identifier",
name: "importMetaTarget"
});
},
CallExpression(call) {
const args = call.get("arguments");
if (unwrapReferenceArgument(args[0])?.matchesPattern("import.meta.dirname")) {
const callee = call.get("callee");
const member = callee.isMemberExpression() ? callee.get("property") : callee;
const name = ["join", "resolve"].find((method) => ["path", "node:path"].some((moduleName) => callee.referencesImport(moduleName, method))) ?? (member.isIdentifier() ? member.node.name : void 0) ?? "";
visitDirectoryAsset(name, args.slice(1).map((arg) => staticString(unwrapReferenceArgument(arg)?.node)));
}
const specifier = staticString(unwrapReferenceArgument(args.length === 1 ? args[0] : void 0)?.node);
if (specifier !== void 0 && isCurrentFileRequire(call)) visitReference(specifier, "require");
}
});
} }] }
}));
const staticImports = /* @__PURE__ */ new Set();
for (const statement of tree.body) if ((statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") && statement.source) {
const reference = staticString(statement.source);
if (reference !== void 0) staticImports.add(reference);
}
for (const reference of staticImports) visitReference(reference, "import");
const visit = (node) => {
if (node.type === "ImportExpression") {
const reference = staticString(node.source);
if (reference !== void 0) visitReference(reference, "import");
}
if (node.type === "CallExpression" || node.type === "NewExpression") {
const { callee: call, arguments: args } = node;
const callee = call.type === "SequenceExpression" ? call.expressions.at(-1) : call;
const member = callee.type === "MemberExpression" ? callee.property : callee;
const name = member.type === "Identifier" ? member.name : "";
const module = callee.type === "Identifier" ? [
"require",
"jitiImport",
"jitiESMResolve"
].includes(name) : callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "require" && name === "resolve";
const asset = node.type === "NewExpression" && name === "URL" || [
"readFile",
"readFileSync",
"createReadStream"
].includes(name);
const reference = staticString(args[0]);
if ((module || asset) && reference !== void 0) visitReference(reference, module ? name === "require" || name === "resolve" ? "require" : "import" : "asset");
if (callee.type === "MemberExpression" && args[0]?.type === "Identifier" && args[0].name === "__dirname") visitDirectoryAsset(name, args.slice(1).map(staticString));
}
for (const child of sourceChildren(node)) visit(child);
};
visit(tree);
}
//#endregion
//#region src/plugins/plugin-generation-artifact.ts
/** Capture selective entries and whole dependencies without replacing earlier file bytes. */
function capturePluginGenerationArtifact(rootDir, entryFile, execute, moduleSource, nativeRecovery) {
const sourceCapture = createPluginSourceCapture(execute);
const directory = sourceCapture.directory;
const packages = /* @__PURE__ */ new Map();
const capturedPaths = /* @__PURE__ */ new Map();
const originalSources = /* @__PURE__ */ new Map();
const hardlinkedSources = /* @__PURE__ */ new Set();
const nativeAdmission = createPluginNativeAdmission(rootDir, directory, entryFile, nativeRecovery, sourceCapture.outputRoot);
const metadataCapture = createPluginPackageMetadataCapture({
sourceForCaptured: (filename) => originalSources.get(filename),
packageForFile: (filename) => packageForFile(filename),
isRetainedReference: nativeAdmission.isRetainedReference,
resolveSource: nativeAdmission.resolvePreparedSource
});
const sourceAliases = {};
const receipt = createPluginGenerationReceipt(nativeAdmission.prepared);
const { inputs, pendingInputs, additions, capture: acquireSources, assertModuleAvailable } = sourceCapture;
const captureAdmitted = (run) => {
const acquired = acquireSources(run);
nativeAdmission.finish(receipt.finish());
return acquired;
};
const moduleCaptures = /* @__PURE__ */ new Map();
const resolveDependency = createPluginDependencyResolver();
const copyPackage = (root, entry, metadataOnly = false, executableEntry = false) => {
const boundary = root;
const outputRoot = sourceCapture.outputRoot && isPathInside(boundary, sourceCapture.outputRoot) ? sourceCapture.outputRoot : void 0;
const existing = packages.get(root);
if (existing) {
if (!metadataOnly) existing.materialize(executableEntry ? entry : void 0);
return existing.destination;
}
const packageId = `package-${packages.size}`;
const moduleRoot = path.join(directory, packageId, "node_modules");
const parentName = path.basename(path.dirname(boundary));
const sourceModuleRoot = parentName.startsWith("@") ? path.dirname(path.dirname(boundary)) : path.dirname(boundary);
const destination = path.join(moduleRoot, parentName.startsWith("@") ? parentName : "", path.basename(boundary));
const capturedBoundary = destination;
sourceAliases[root] = destination;
receipt.marker(`${packageId}\0`);
const owner = {
destination,
capturedRoot: capturedBoundary,
sourceRoot: boundary,
links: /* @__PURE__ */ new Set(),
state: "metadata",
captureTarget(filename) {
const source = path.join(boundary, path.relative(capturedBoundary, filename));
if (!capturedPaths.has(source) && fs.statSync(source, { throwIfNoEntry: false })?.isFile() && (isPathInside(boundary, fs.realpathSync(source)) || nativeAdmission.isRetainedReference(source))) {
copy(source, filename);
scopes.captureMetadata(path.dirname(source));
}
},
materialize(selectedEntry) {
if (typeof owner.state === "object") throw owner.state.error;
if (owner.state === "body" && !selectedEntry) return;
owner.state = selectedEntry && owner.state !== "body" ? "entry" : "body";
try {
if (selectedEntry) captureFile(path.resolve(selectedEntry));
else copy(root, destination);
captureDependencies();
} catch (error) {
owner.state = { error };
throw error;
}
}
};
packages.set(root, owner);
const sourceLinks = createPluginSourceLinkCapture();
const copy = createPluginGenerationFileCapture({
boundary,
destination,
directory,
outputRoot,
capturedPaths,
originalSources,
hardlinkedSources,
sourceCapture,
sourceLinks,
deferExternalLinks: Boolean(execute),
nativeAdmission,
receipt,
onPackageMetadata(source, target) {
metadataCapture.record(target, (manifest) => {
for (const alias of importTargetNames(manifest.imports)) {
if (alias === "openclaw" || alias === "@openclaw/plugin-sdk") continue;
const dependency = resolveDependency(alias, source);
if (dependency) linkDependency(alias, dependency, true);
}
});
}
});
const linkDependency = (name, dependency, captureMetadataOnly = false) => {
const captured = copyPackage(dependency.root, void 0, captureMetadataOnly);
const lookupDirectory = isPluginPackageFile(boundary, dependency.lookupDirectory) ? path.join(capturedBoundary, path.relative(boundary, dependency.lookupDirectory)) : path.join(dependency.lookupDirectory, "node_modules") === sourceModuleRoot ? path.dirname(moduleRoot) : capturedBoundary;
const link = path.join(lookupDirectory, "node_modules", name);
packages.get(dependency.root).links.add(link);
if (!fs.existsSync(link)) {
fs.mkdirSync(path.dirname(link), {
recursive: true,
mode: 448
});
fs.symlinkSync(path.relative(path.dirname(link), captured), link, "junction");
additions.add(link);
}
};
const scopes = metadataCapture.createScope({
root,
destination,
boundary,
copy,
hasSource: (source) => capturedPaths.has(source)
});
const references = /* @__PURE__ */ new Map();
const getNativeScope = createPluginNativeDependencyScopes(resolveDependency, (name, dependency) => linkDependency(name, dependency, true));
const scannedDirectories = /* @__PURE__ */ new Set();
const captureFile = (source, options) => {
const existingSource = capturedPaths.get(path.resolve(source));
if (existingSource && (!/\.[cm]?[jt]sx?$/.test(source) || moduleCaptures.has(existingSource))) return;
const target = existingSource ?? path.join(destination, path.relative(root, source));
if (!existingSource) {
const prepared = nativeAdmission.resolvePreparedSource(source);
const real = fs.realpathSync(prepared?.path ?? source);
if (!isPathInside(prepared?.boundary ?? boundary, real) && !nativeAdmission.isRetainedReference(source, real)) throw new Error("Standalone plugin input leaves its source directory");
if (!prepared && outputRoot && isPathInside(outputRoot, real)) return;
if (fs.statSync(real).isDirectory()) {
if (scannedDirectories.has(real)) throw new Error("Standalone plugin input contains a directory cycle");
scannedDirectories.add(real);
for (const name of fs.readdirSync(real).toSorted()) if (isPluginSourceEntry(name)) captureFile(path.join(source, name), options);
scannedDirectories.delete(real);
return;
}
copy(source, target);
}
if (!/\.[cm]?[jt]sx?$/.test(source)) return;
const scope = scopes.resolve(path.dirname(source));
const prepareDependency = createPluginDependencyLookup(source, scope?.manifest, resolveDependency, linkDependency);
const resolver = createJiti(source, {
...options,
fsCache: false,
moduleCache: false,
tryNative: false
});
const captureReference = (reference, kind, conditions) => {
const module = kind !== "asset";
const importUrl = kind === "import" && reference.startsWith(".") ? new URL(reference, pathToFileURL(source)) : void 0;
const value = importUrl ? `./${path.relative(path.dirname(source), fileURLToPath(importUrl))}` : module && reference.startsWith("file:") ? fileURLToPath(reference) : reference;
const resolve = (specifier) => {
const resolved = resolver.esmResolve(specifier, {
try: true,
conditions: conditions ? [...conditions] : kind === "require" ? ["node", "require"] : ["node", "import"]
});
if (!resolved?.startsWith("file:")) return resolved;
const url = new URL(resolved);
const filename = fileURLToPath(url);
const captured = moduleSource?.(filename) ?? filename;
url.pathname = pathToFileURL(originalSources.get(captured) ?? nativeAdmission.sourceForPrepared(captured) ?? captured).pathname;
return url.href;
};
const addDependency = (name, importer = source) => {
const imports = references.get(importer) ?? /* @__PURE__ */ new Set();
imports.add(name);
references.set(importer, imports);
};
if (module && !value.startsWith(".") && !path.isAbsolute(value)) {
if (isBuiltin(value)) return;
const name = packageName(value);
const resolved = resolve(value);
const input = resolved?.startsWith("file:") ? fileURLToPath(resolved) : resolved;
if (resolver.options.tsconfigPaths && name !== "openclaw" && name !== "@openclaw/plugin-sdk" && resolved?.startsWith("file:") && input) {
if (isPluginPackageFile(boundary, input) && (capturedPaths.has(path.resolve(input)) || isPluginPackageFile(boundary, fs.realpathSync(input)))) {
captureFile(input, resolver.options);
return input;
}
if (!isPathInside(resolveDependency(name, source)?.root ?? boundary, input)) return conditions && execute ? captureExecutableFile(input) : null;
}
const self = scope?.manifest.exports != null && scope.manifest.name === name;
if (!value.startsWith("#") && !self) {
if (conditions && !resolved) return;
addDependency(name);
return resolved?.startsWith("file:") ? input : void 0;
}
if (!input || isBuiltin(input)) return;
let external = false;
if (!self && scope) for (const alias of scope.aliases) {
const dependency = resolveDependency(alias, scope.source);
if (dependency && isPathInside(dependency.root, input)) {
addDependency(alias, scope.source);
external = true;
}
}
if (!external) captureFile(input, resolver.options);
return input;
}
const requested = path.resolve(path.dirname(source), value);
const lexicalBoundary = entry && !executableEntry ? path.resolve(rootDir) : root;
const local = module && path.isAbsolute(value) && isPathInside(lexicalBoundary, requested) ? path.join(boundary, path.relative(lexicalBoundary, requested)) : requested;
if (module && !isPathInside(boundary, local)) {
if (!conditions || !execute) return null;
const selected = resolve(local);
if (!selected?.startsWith("file:")) return;
return captureExecutableFile(fileURLToPath(selected));
}
if (!value || !module && path.isAbsolute(value) || !isPathInside(boundary, local) || local === boundary) return;
if (module && path.isAbsolute(value) && path.relative(boundary, local).split(path.sep).includes("node_modules")) return;
const fromCopy = owner.state === "body" && !sourceLinks.contains(local);
const moduleRequest = nativeAdmission.resolvePreparedSource(local)?.path ?? (fromCopy ? path.join(destination, path.relative(root, local)) : local);
const resolved = module ? resolve(moduleRequest) : void 0;
if (module && !resolved) return;
const input = resolved?.startsWith("file:") ? fileURLToPath(resolved) : resolved ?? local;
const capturedInput = capturedPaths.has(path.resolve(input));
const preparedInput = nativeAdmission.resolvePreparedSource(input);
if (capturedInput || fs.existsSync(preparedInput?.path ?? input)) {
if (module && execute && !capturedInput && !preparedInput && !isPathInside(boundary, fs.realpathSync(input))) return conditions ? captureExecutableFile(input) : null;
captureFile(input, resolver.options);
if (module && path.isAbsolute(value)) capturedPaths.set(requested, capturedPaths.get(path.resolve(input)));
return input;
}
};
const observed = /* @__PURE__ */ new Map();
const captureObservedReference = (reference, kind, conditions) => {
const key = `${kind}\0${reference}`;
if (!observed.has(key) || conditions && execute && observed.get(key) === null) observed.set(key, captureReference(reference, kind, conditions));
return observed.get(key) ?? void 0;
};
const captureModule = (specifier, conditions) => {
const inputFilename = specifier.startsWith("file:") ? fileURLToPath(specifier) : path.isAbsolute(specifier) ? specifier : void 0;
const known = inputFilename && capturedPaths.get(path.resolve(inputFilename));
if (execute && known) return { target: capturedPluginModuleUrl(known, specifier, conditions) };
const name = packageName(specifier);
const self = scope?.manifest.exports != null && scope.manifest.name === name;
const bare = !specifier.startsWith(".") && !path.isAbsolute(specifier) && !specifier.startsWith("file:");
if (resolver.options.tsconfigPaths && bare && !self && !specifier.startsWith("#")) {
const mapped = captureObservedReference(specifier, conditions.includes("require") ? "require" : "import", conditions);
if (mapped && capturedPaths.has(path.resolve(mapped))) {
captureDependencies();
return { target: pathToFileURL(capturedPaths.get(path.resolve(mapped))) };
}
}
const dependencyPrepared = prepareDependency(specifier);
if (typeof dependencyPrepared === "boolean") return dependencyPrepared ? { retryNative: true } : void 0;
if (dependencyPrepared === "package-map") {
let selected;
try {
selected = moduleResolve(specifier, pathToFileURL(target), new Set(conditions));
} catch (error) {
if (!(error instanceof Error) || !("code" in error) || error.code !== "ERR_MODULE_NOT_FOUND") throw error;
if (!("url" in error) || typeof error.url !== "string") return;
selected = new URL(error.url);
}
if (selected.protocol !== "file:") return;
const filename = fileURLToPath(selected);
if (isPluginPackageFile(capturedBoundary, filename)) {
const original = path.join(boundary, path.relative(capturedBoundary, filename));
if (!capturedPaths.has(original) && !fs.existsSync(original)) return;
captureFile(original, resolver.options);
} else packageForFile(filename)?.materialize();
captureDependencies();
return { retryNative: true };
}
const observedSource = captureObservedReference(specifier, conditions.includes("require") ? "require" : "import", conditions);
captureDependencies();
const captured = observedSource ? capturedPaths.get(path.resolve(observedSource)) : void 0;
if (!captured) return;
return { target: capturedPluginModuleUrl(captured, specifier, conditions) };
};
const nativeScope = getNativeScope(source, scope?.manifest);
moduleCaptures.set(target, {
prepareDependency,
nativeScope,
capture: captureModule
});
if (entry && !executableEntry) visitPluginSourceReferences(source, fs.readFileSync(target, "utf8"), resolver, captureObservedReference);
};
const captureDependencies = () => {
const manifestPath = path.join(root, "package.json");
if (!entry && !capturedPaths.has(manifestPath)) return;
const manifest = capturePluginDependencies({
root,
manifestFile: entry ? void 0 : path.join(destination, "package.json"),
references,
resolve: resolveDependency,
capture: linkDependency
});
if (!entry) metadataCapture.setManifest(path.join(destination, "package.json"), manifest);
};
if (metadataOnly) {
const manifest = capturePluginPackageMetadata(root, destination, copy, nativeAdmission.isRetainedReference, nativeAdmission.resolvePreparedSource);
metadataCapture.setManifest(path.join(destination, "package.json"), manifest ?? null);
} else owner.materialize(entry);
return destination;
};
const captureExecutableFile = (filename) => execute?.(() => capturePluginModuleSource(filename, (root, source) => copyPackage(root, source, false, true)));
const packageForFile = (filename) => findPluginCapturedPackage(packages, filename, directory)?.owner;
try {
const sourceRoot = fs.realpathSync(rootDir);
const entry = entryFile ? fs.realpathSync(entryFile) : void 0;
const root = copyPackage(sourceRoot, entry);
sourceAliases[path.resolve(rootDir)] = root;
if (entry && entryFile) {
const alias = path.join(sourceRoot, path.relative(path.resolve(rootDir), path.resolve(entryFile)));
capturedPaths.set(alias, capturedPaths.get(entry));
}
const assertSourceCurrent = () => {
if (fs.realpathSync(rootDir) !== sourceRoot || entryFile && fs.realpathSync(entryFile) !== entry) throw new Error("Plugin source root changed after capture");
nativeAdmission.reconcileSourceInputs(inputs);
verifyPluginSourceInputs(inputs, inputs.keys());
};
const initialReceipt = receipt.finish();
assertSourceCurrent();
nativeAdmission.finish(initialReceipt);
pendingInputs.clear();
additions.clear();
const captures = [
moduleCaptures,
hardlinkedSources,
metadataCapture,
packages
];
const clearCaptures = () => captures.forEach((capture) => capture.clear());
return {
sourceRoot,
rootDir: root,
sourceAliases,
linkHost: (hostRoot) => {
sourceCapture.linkHost(hostRoot);
for (const [source, identity] of nativeAdmission.linkHost(hostRoot)) {
const input = inputs.get(source);
if (input?.native) input.identity = identity;
}
nativeAdmission.reconcileSourceInputs(inputs);
},
sourceForCaptured: (file) => originalSources.get(path.resolve(file)),
boundaryRoot: directory,
sourceDigest: initialReceipt.sourceDigest,
...createPluginGenerationSourceLookup({
rootDir,
sourceRoot,
capturedRoot: root,
boundaryRoot: directory,
capturedPaths,
hardlinkedSources,
assertModuleAvailable,
captureNativeRecovery: () => nativeAdmission.captureRecovery(initialReceipt, sourceAliases)
}),
assertSourceCurrent,
moduleRoot: (filename) => originalSources.has(filename) ? packageForFile(filename)?.capturedRoot : void 0,
assertModuleAvailable,
prepareModule: (filename) => {
const owner = packageForFile(filename);
const source = originalSources.get(filename);
const needsEntry = execute && source && /\.[cm]?[jt]sx?$/.test(source) && !moduleCaptures.has(filename);
if (!owner || (owner.state === "entry" || owner.state === "body") && !needsEntry) return [];
return captureAdmitted(() => {
owner.materialize(owner.state === "entry" ? source : void 0);
if (needsEntry && owner.state !== "entry") owner.materialize(source);
}).additions;
},
prepareDependency: (importer, specifier) => captureAdmitted(() => moduleCaptures.get(importer)?.prepareDependency(specifier)).additions,
prepareNativeScopes: (importer) => {
const scope = importer ? moduleCaptures.get(importer)?.nativeScope : void 0;
return scope?.prepareDependencies || metadataCapture.pending ? captureAdmitted(() => metadataCapture.prepare(scope)) : void 0;
},
prepareNativeModule: (importer, specifier) => captureAdmitted(() => {
const packageMap = moduleCaptures.get(importer)?.prepareDependency(specifier) === "package-map";
metadataCapture.prepare();
return packageMap;
}).value,
captureModule: (importer, specifier, conditions) => {
const result = captureAdmitted(() => moduleCaptures.get(importer)?.capture(specifier, conditions));
return result.value ? {
...result.value,
additions: result.additions
} : void 0;
},
captureResolvedModule: (filename) => {
const known = capturedPaths.get(path.resolve(filename));
if (known) {
assertModuleAvailable(known);
return known;
}
return captureAdmitted(() => {
const captured = findPluginCapturedPackage(packages, filename, directory);
const original = captured ? path.join(captured.owner.sourceRoot, path.relative(captured.root, filename)) : filename;
const source = captureExecutableFile(original);
const target = source ? capturedPaths.get(source) : void 0;
if (target) capturedPaths.set(path.resolve(filename), target);
return target;
}).value;
},
dispose: () => {
sourceCapture.dispose();
clearCaptures();
},
disposeAsync: () => sourceCapture.disposeAsync().then(clearCaptures)
};
} catch (error) {
sourceCapture.dispose();
throw error;
}
}
//#endregion
export { isPluginSourceEntry as a, withPluginSourceCaptureDirectory as i, inspectPluginTypeScriptExecutionFacts as n, visitPluginSourceReferences as r, capturePluginGenerationArtifact as t };