openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
2,201 lines • 95 kB
JavaScript
import { l as stringifyNonErrorCause, u as toErrorObject } from "./error-coercion-CvJK4lmH.mjs";
import { t as createDeferredCore } from "./deferred-D0La5CRk.mjs";
import { a as resolveGlobalSingleton, r as resolveGlobalMap } from "./global-singleton-0m-JziJW.mjs";
import { a as runInDetachedAsyncContext, c as trackAsyncWork, t as AsyncWorkScope } from "./async-work-scope-BYeX3gh6.mjs";
import { D as withPluginCache, O as PluginInstanceDrainTimeoutError, k as PluginInstanceUnavailableError, o as getPluginCache, r as bindPluginCacheRoot, u as getPluginCacheSource, w as retirePluginCacheInstance, y as releasePluginCacheInstance } from "./plugin-cache-BABafcXD.mjs";
import { i as pluginInstanceInvocation } from "./plugin-instance-invocation-CH4zOUb4.mjs";
import { k as walkDirectorySync, t as FsSafeError } from "./fs-safe-BUXBSN-4.mjs";
import { o as openRootFileSync } from "./boundary-file-read-CCth2Hwg.mjs";
import { r as isPathInside } from "./path-guards-CH7noY0Z.mjs";
import { s as raceWithTimeout } from "./src-D1BHGNTj.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs";
import { n as runWithSpawnBroker, t as getSpawnBroker } from "./context-BGWzAY3i.mjs";
import { t as captureSqliteReadOnlyWorkerScope } from "./sqlite-readonly-worker-context-CtZdPPcK.mjs";
import { n as hashFileDescriptorSync } from "./file-descriptor-B4yBUiAP.mjs";
import { a as safeRealpathSync } from "./path-safety-6KJ2Rbnw.mjs";
import { l as pluginCacheRealpathSync } from "./package-manifest-CEbdS_C4.mjs";
import { d as resolveCanonicalDistRuntimeSource, f as resolvePluginRuntimeArtifactPreference, p as resolvePluginRuntimeArtifactSelection, u as prefersBuiltPluginArtifacts } from "./bundled-dir-DkyLPnyV.mjs";
import { t as shouldRejectHardlinkedPluginFiles } from "./hardlink-policy-C9pW4xqI.mjs";
import { o as loadPluginMetadataSnapshot } from "./plugin-metadata-snapshot-pYUEfhY-.mjs";
import { i as loadPluginRegistrySnapshot } from "./plugin-registry-snapshot-TGiCkYrI.mjs";
import { a as registerCapturedPluginModuleResolver, d as useNodeModuleHooks, i as isPluginSourceModulePath, n as clearPluginModuleRequireCache, o as resolvePluginLoaderTryNative, r as isJavaScriptModulePath, s as supportsBunRuntimeOnResolveTargets } from "./native-module-require-BX9Ubdbu.mjs";
import { c as toSafeImportPath, o as installOpenClawPluginSdkNativeResolver, s as resolvePluginNativeAliasForParent, t as getCachedPluginModuleLoader } from "./plugin-module-loader-cache-CUtYxPiw.mjs";
import { t as createJiti } from "./jiti-factory-B7ZlEz2u.mjs";
import { n as PluginHostObject, t as PluginCallToken } from "./plugin-instance-owned-values-kZIH8zYk.mjs";
import { a as getPluginOriginalValue, c as pluginInstanceState, l as pluginInvocationContext, p as setPluginOriginalValue, r as getPluginInstanceOwner, u as resolvePluginInstanceOwner } from "./plugin-instance-scope-DEzoxqBj.mjs";
import { i as isPluginSdkAliasSpecifier, o as preparePluginLoaderAliases } from "./sdk-alias-A9rXLURS.mjs";
import { n as getPluginRuntimeLoadContextState } from "./load-context-state-Dj3BDflJ.mjs";
import { a as getGatewayContextResolver } from "./gateway-context-binding-VqB7gkMe.mjs";
import { l as withPluginRuntimePluginScope, r as getPluginRegistryForContext, s as withPluginRuntimeGatewayContextResolver } from "./gateway-request-scope-BSmCVVpP.mjs";
import { a as getPluginBunConditions, c as isPluginSourceEntry, n as inspectPluginTypeScriptExecutionFacts, r as resolvePluginPackageMapTarget, t as capturePluginGenerationArtifact } from "./plugin-generation-artifact-CHASLMSu.mjs";
import { l as getActivePluginRegistry, v as requireActivePluginRegistry } from "./runtime-NsVZTERq.mjs";
import { t as getPluginRuntimeGenerationRegistry } from "./generation-state-C1yPL1Zl.mjs";
import { a as mapPluginReturnPromise, o as resolvePluginReturnPromise, r as getBoundLegacyPluginSdkResourceHost } from "./legacy-sdk-resource-host-DC4n8Jcn.mjs";
import { n as getPluginRegistryRuntime } from "./registry-runtime-binding-i7RQa2C4.mjs";
import "./generation-scope-tH9UXyLm.mjs";
import Module, { createRequire, isBuiltin } from "node:module";
import fs from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import path from "node:path";
import { AsyncResource } from "node:async_hooks";
import { types } from "node:util";
import crypto, { randomUUID } from "node:crypto";
import { isMainThread, workerData } from "node:worker_threads";
//#region src/plugins/plugin-module-loader-recovery.ts
function createRecoverySourceMap(resolve, previous) {
return (source) => resolve(previous?.(source) ?? source);
}
function createSourceModuleRecovery(facts, recovery, bindInstance) {
let state = "available";
return {
bind(instance) {
if (state !== "available") throw new Error("Plugin module recovery has already been consumed or released");
instance.onModuleDispose(recovery.disposeAsync);
state = "bound";
bindInstance({
instance,
origin: facts.origin,
source: recovery.resolve(facts.source),
rootDir: recovery.rootDir,
devSourceRoot: facts.devSourceRoot,
standalone: facts.standalone,
pluginSdkResolution: facts.pluginSdkResolution,
recoverySourceMap: createRecoverySourceMap(recovery.resolve, facts.recoverySourceMap),
nativeRecovery: recovery.native
});
instance.sourceDigest = facts.sourceDigest;
},
dispose() {
if (state === "available") {
state = "disposed";
recovery.dispose();
}
}
};
}
/** Transfers a recovery snapshot to a fresh instance and maps its original entry paths. */
function preparePluginModuleLoaderRecovery(params, artifact, bindInstance) {
const facts = {
origin: params.origin,
source: params.source,
devSourceRoot: params.devSourceRoot,
standalone: params.standalone,
pluginSdkResolution: params.pluginSdkResolution,
recoverySourceMap: params.recoverySourceMap
};
params.instance.bindModuleLoaderRecovery(() => createSourceModuleRecovery({
...facts,
sourceDigest: params.instance.sourceDigest
}, artifact.captureRecoverySource(), bindInstance));
return (load, hasSource) => {
const mapSource = params.recoverySourceMap;
params.instance.bindModuleLoader(mapSource ? (source) => load(mapSource(source)) : load, mapSource && hasSource ? (source) => {
let mapped;
try {
mapped = mapSource(source);
} catch {
return false;
}
return hasSource(mapped);
} : hasSource);
};
}
//#endregion
//#region src/plugins/plugin-native-module-loader.ts
/** Native adapters acquire source through the instance's artifact without replacing evaluation. */
function bindNativePluginInstanceModuleLoader(params, cache, artifact, loader, sdkRoots, prepareEntryNativeScopes) {
const bun = Reflect.get(globalThis, "Bun");
const jitiJsx = process.env.JITI_JSX;
const jsxEnabled = jitiJsx === "1" || jitiJsx === "true";
const jsxTranspilers = /* @__PURE__ */ new Map();
const hostSdkTarget = (request, originalParent) => {
const target = request.startsWith("file:") ? fileURLToPath(request) : request.startsWith(".") && originalParent ? path.resolve(path.dirname(originalParent), request) : request;
return path.isAbsolute(target) && sdkRoots.some((root) => isPathInside(root, target)) ? target : void 0;
};
params.instance.onModuleDispose(registerCapturedPluginModuleResolver({
...jsxEnabled && bun ? { load(request) {
if (params.ownsResolution && !params.ownsResolution()) return;
if (!artifact.sourceForCaptured(request)) return;
const sourceLoader = path.extname(request).toLowerCase() === ".jsx" ? "jsx" : "tsx";
let transpiler = jsxTranspilers.get(sourceLoader);
if (!transpiler) {
transpiler = new bun.Transpiler({
loader: sourceLoader,
tsconfig: { compilerOptions: {
jsx: "react",
jsxFactory: "React.createElement",
jsxFragmentFactory: "React.Fragment"
} }
});
jsxTranspilers.set(sourceLoader, transpiler);
}
return {
contents: transpiler.transformSync(fs.readFileSync(request, "utf8")),
loader: "js"
};
} } : {},
prepare(request, parent, kind) {
if (params.ownsResolution && !params.ownsResolution()) return;
const original = artifact.sourceForCaptured(parent);
const sdkTarget = hostSdkTarget(request, original);
if (sdkTarget) return sdkTarget === request ? void 0 : sdkTarget;
if (!original && !artifact.sourceForCaptured(request)) return;
const source = original ? parent : request;
return withPluginCache(cache, () => {
artifact.prepareModule(source);
const facts = artifact.moduleFacts(parent);
if (kind === "import-statement" && facts?.staticImports && !facts.staticImports.has(request)) return;
if (source === parent && isPluginSdkAliasSpecifier(request)) return resolvePluginNativeAliasForParent(request, parent);
let target;
const conditions = [...getPluginBunConditions(kind === "require-call" || kind === "require-resolve")];
if (source === parent && !path.isAbsolute(request) && !request.startsWith("file:") && !isBuiltin(request) && !(request.startsWith("#") && facts?.isNativeImportPattern(request))) {
const captured = artifact.captureModule(parent, request, request.startsWith(".") && kind === "require-resolve" && !facts?.isRequireReference(request) ? [
"node",
"module-sync",
"import"
] : conditions);
if (captured && "target" in captured) target = captured.target.search || captured.target.hash ? captured.target.href : fileURLToPath(captured.target);
else if (captured && "retryNative" in captured) try {
const selected = resolvePluginPackageMapTarget(request, parent, conditions);
if (selected) {
const capturedTarget = artifact.captureResolvedModule(fileURLToPath(selected));
if (capturedTarget) {
const capturedUrl = pathToFileURL(capturedTarget);
capturedUrl.search = selected.search;
capturedUrl.hash = selected.hash;
target = capturedUrl.search || capturedUrl.hash ? capturedUrl.href : capturedTarget;
}
}
} catch {}
}
target ??= source === parent && (path.isAbsolute(request) || request.startsWith("file:")) ? artifact.captureResolvedModule(request.startsWith("file:") ? fileURLToPath(request) : request) : source === request ? request : void 0;
if (target) artifact.prepareModule(target.startsWith("file:") ? fileURLToPath(target) : target);
if (original) artifact.prepareNativeScopes(target?.startsWith("file:") ? fileURLToPath(target) : target ?? source);
return target;
});
},
resolve(request, parent, resolve) {
if (params.ownsResolution && !params.ownsResolution()) return;
const original = artifact.sourceForCaptured(parent);
if (!original || isPluginSdkAliasSpecifier(request) || isBuiltin(request)) return;
const sdkTarget = hostSdkTarget(request, original);
if (sdkTarget) return sdkTarget;
return params.instance.run(() => withPluginCache(cache, () => {
artifact.prepareModule(parent);
const packageMap = artifact.prepareNativeModule(parent, request);
let selected;
try {
selected = resolve();
} catch (error) {
if (packageMap || !(error instanceof Error) || !("code" in error) || error.code !== "MODULE_NOT_FOUND" && error.code !== "ERR_MODULE_NOT_FOUND") throw error;
}
if (selected) {
if (hostSdkTarget(selected)) return selected;
const captured = artifact.captureResolvedModule(selected);
if (captured) {
artifact.prepareModule(captured);
artifact.prepareNativeScopes();
}
return captured;
}
const captured = artifact.captureModule(parent, request, [
"node",
"module-sync",
"require"
]);
return captured && "target" in captured ? fileURLToPath(captured.target) : void 0;
}));
}
}));
const rejectHardlinks = shouldRejectHardlinkedPluginFiles({
origin: params.origin,
rootDir: params.rootDir
});
params.bindModuleLoader((source) => withPluginCache(cache, () => {
const captured = artifact.resolve(source, rejectHardlinks);
artifact.prepareModule(captured);
if (prepareEntryNativeScopes) artifact.prepareNativeScopes(captured);
return loader(toSafeImportPath(captured));
}), artifact.hasSource);
}
//#endregion
//#region src/plugins/plugin-runtime-artifact-resolution.ts
/** Resolves the exact root and entry selected by the plugin runtime loader. */
function clearPluginRuntimeArtifactResolutionMemo() {
getActivePluginRegistry()?.pluginRuntimeArtifacts.clear();
}
/** Applies both loader selection phases in their runtime order. */
function resolvePluginRuntimeArtifact(params) {
const rootDir = resolveCanonicalDistRuntimeSource(pluginCacheRealpathSync(params.rootDir) ?? path.resolve(params.rootDir));
const memoKey = JSON.stringify([
params.pluginId,
rootDir,
params.entryKind
]);
const targetRegistry = params.registry ?? requireActivePluginRegistry();
const cached = targetRegistry.pluginRuntimeArtifacts.get(memoKey);
if (cached) return { ...cached };
const resolved = resolvePluginRuntimeArtifactSelection(params);
targetRegistry.pluginRuntimeArtifacts.set(memoKey, resolved);
return { ...resolved };
}
//#endregion
//#region src/plugins/plugin-runtime-artifact-identity.ts
/** Computes a bounded content identity for plugin-owned runtime artifacts. */
const MAX_RUNTIME_ARTIFACT_DEPTH = 64;
const MAX_RUNTIME_ARTIFACT_ENTRIES = 5e4;
const MAX_RUNTIME_ARTIFACT_FILE_BYTES = 268435456;
const MAX_RUNTIME_ARTIFACT_TOTAL_BYTES = 536870912;
const isRuntimeArtifactEntry = (name) => isPluginSourceEntry(name) && name !== ".hg" && name !== ".svn";
function loadPluginRuntimeArtifactIdentitySources(params) {
const registry = loadPluginRegistrySnapshot(params);
const metadata = loadPluginMetadataSnapshot({
...params,
index: registry
});
return registry.plugins.map((record) => ({
pluginId: record.pluginId,
origin: record.origin,
rootDir: record.rootDir,
source: record.source,
packageBuild: record.packageBuild,
sourcePreferred: metadata.byPluginId.get(record.pluginId)?.sourcePreferred
}));
}
function normalizeRelativePath(filePath) {
return filePath.split(path.sep).join("/");
}
function listRuntimeArtifactFiles(rootDir) {
const scan = walkDirectorySync(rootDir, {
maxDepth: MAX_RUNTIME_ARTIFACT_DEPTH,
maxEntries: MAX_RUNTIME_ARTIFACT_ENTRIES,
symlinks: "include",
descend: (entry) => isRuntimeArtifactEntry(entry.name),
include: (entry) => entry.kind !== "directory" && isRuntimeArtifactEntry(entry.name)
});
if (scan.truncated) throw new Error("plugin runtime artifact exceeds the bounded file scan");
if ((scan.failedDirs?.length ?? 0) > 0) throw new Error("plugin runtime artifact contains an unreadable directory");
return scan.entries.map((entry) => {
if (entry.kind !== "file") throw new Error(`plugin runtime artifact contains unsupported ${entry.kind} entry`);
return normalizeRelativePath(entry.relativePath);
}).toSorted();
}
function sameOpenedFile(before, after) {
return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
}
function hashRuntimeArtifactFile(params) {
const absolutePath = path.join(params.rootDir, params.relativePath);
const opened = openRootFileSync({
absolutePath,
rootPath: params.rootDir,
rootRealPath: params.rootRealPath,
boundaryLabel: "plugin runtime artifact",
maxBytes: MAX_RUNTIME_ARTIFACT_FILE_BYTES,
rejectHardlinks: false
});
if (!opened.ok) throw new Error(`plugin runtime artifact file is not readable: ${params.relativePath}`);
const changedMessage = `plugin runtime artifact file changed while reading: ${params.relativePath}`;
try {
const hashed = hashFileDescriptorSync(opened.fd, opened.stat.size);
const after = fs.fstatSync(opened.fd);
if (hashed.sizeBytes !== opened.stat.size || !sameOpenedFile(opened.stat, after)) throw new Error(changedMessage);
return {
hash: hashed.sha256,
size: opened.stat.size,
mode: opened.stat.mode
};
} catch (error) {
if (error instanceof FsSafeError && error.code === "too-large") throw new Error(changedMessage, { cause: error });
throw error;
} finally {
fs.closeSync(opened.fd);
}
}
/**
* Hashes plugin-owned files only. Dependency stores and VCS metadata are
* separate runtime owners; plugin installs/updates must replace this digest.
*/
function fingerprintPluginRuntimeArtifact(record) {
const runtimeArtifact = record.source ? resolvePluginRuntimeArtifact({
pluginId: record.pluginId,
entryKind: "runtime",
source: record.source,
rootDir: record.rootDir,
origin: record.origin,
preferBuiltPluginArtifacts: prefersBuiltPluginArtifacts(resolvePluginRuntimeArtifactPreference(getPluginRuntimeLoadContextState(getPluginRegistryForContext() ?? void 0)?.preferBuiltPluginArtifacts), record.origin),
sourcePreferred: record.sourcePreferred,
...record.packageBuild ? { packageManifest: { build: record.packageBuild } } : {}
}) : {
rootDir: record.rootDir,
source: void 0
};
const rootDir = path.resolve(runtimeArtifact.rootDir);
const rootRealPath = safeRealpathSync(rootDir);
if (!rootRealPath) throw new Error(`plugin runtime root is unavailable: ${record.pluginId}`);
const source = runtimeArtifact.source ? path.isAbsolute(runtimeArtifact.source) ? runtimeArtifact.source : path.resolve(rootRealPath, runtimeArtifact.source) : null;
const sourceRelativePath = source ? path.relative(rootRealPath, safeRealpathSync(source) ?? source) : null;
if (sourceRelativePath !== null && (sourceRelativePath === ".." || sourceRelativePath.startsWith(`..${path.sep}`) || path.isAbsolute(sourceRelativePath))) throw new Error(`plugin runtime entry escapes its root: ${record.pluginId}`);
const beforeFiles = listRuntimeArtifactFiles(rootRealPath);
if (sourceRelativePath !== null && !beforeFiles.includes(normalizeRelativePath(sourceRelativePath))) throw new Error(`plugin runtime entry is unavailable: ${record.pluginId}`);
const hash = crypto.createHash("sha256");
hash.update("openclaw-plugin-runtime-artifact-v1\0");
hash.update(sourceRelativePath ? normalizeRelativePath(sourceRelativePath) : "<no-source>");
hash.update("\0");
let totalBytes = 0;
for (const relativePath of beforeFiles) {
const file = hashRuntimeArtifactFile({
rootDir: rootRealPath,
rootRealPath,
relativePath
});
totalBytes += file.size;
if (totalBytes > MAX_RUNTIME_ARTIFACT_TOTAL_BYTES) throw new Error("plugin runtime artifact exceeds the bounded content scan");
hash.update(relativePath);
hash.update("\0");
hash.update(String(file.mode));
hash.update("\0");
hash.update(String(file.size));
hash.update("\0");
hash.update(file.hash);
hash.update("\0");
}
const afterFiles = listRuntimeArtifactFiles(rootRealPath);
if (beforeFiles.length !== afterFiles.length || beforeFiles.some((file, i) => file !== afterFiles[i])) throw new Error("plugin runtime artifact changed while reading");
return hash.digest("hex");
}
//#endregion
//#region src/plugins/plugin-shared-module-loader.ts
const state = resolveGlobalSingleton(Symbol.for("openclaw.sharedPluginModuleIdentity"), () => ({
sources: /* @__PURE__ */ new WeakMap(),
warnings: /* @__PURE__ */ new WeakMap()
}));
const log$1 = createSubsystemLogger("plugins");
/** Canonical exports retain file identity across Jiti wrappers and registry replacements. */
function bindSharedPluginModuleLoader(params) {
const { instance, cache, loader } = params;
const root = safeRealpathSync(params.rootDir) ?? path.resolve(params.rootDir);
let currentIdentity;
try {
currentIdentity = fingerprintPluginRuntimeArtifact({
pluginId: instance.pluginId,
origin: "bundled",
rootDir: root
});
} catch (error) {
log$1.warn(`Bundled plugin ${instance.pluginId} identity unavailable: ${formatErrorMessage(error)}`);
}
createSharedModuleBinding(cache, loader, root, currentIdentity)(instance);
}
function createSharedModuleBinding(cache, loader, root, currentIdentity) {
const load = (source) => withPluginCache(cache, () => loader(toSafeImportPath(source)));
const bind = (target) => {
target.bindModuleLoader((source) => {
const value = load(source);
if ((typeof value !== "object" || value === null) && typeof value !== "function") return value;
const exported = Reflect.get(value, "default");
const identity = exported && (typeof exported === "object" || typeof exported === "function") ? exported : value;
const identities = state.sources.get(identity) ?? /* @__PURE__ */ new Map();
state.sources.set(identity, identities);
if (!identities.has(root)) identities.set(root, currentIdentity);
else {
const loadedIdentity = identities.get(root);
if (!loadedIdentity || !currentIdentity || loadedIdentity !== currentIdentity) state.warnings.set(target, !loadedIdentity || !currentIdentity ? "Bundled plugin code remains loaded and its files could not be verified. Restart the Gateway to load edited code." : "Bundled plugin code remains loaded. Restart the Gateway to load edited code.");
}
return value;
});
target.bindModuleLoaderRecovery(captureRecovery);
};
const captureRecovery = () => {
let released = false;
return {
bind(target) {
if (released) throw new Error("Plugin module recovery has already been consumed or released");
released = true;
bind(target);
},
dispose() {
released = true;
}
};
};
return bind;
}
function getSharedPluginCodeReloadWarning(instance) {
return state.warnings.get(instance);
}
//#endregion
//#region src/plugins/plugin-source-build.ts
const require = createRequire(import.meta.url);
const PLUGIN_SOURCE_RESOLVE_PREFIX = "openclaw-plugin-source-resolve:";
/** Compile captured source into a private namespace; native files stay with their capture owner. */
function buildPluginTypeScriptSource(root) {
if (!useNodeModuleHooks()) throw new Error("Plugin source builds require Node module hooks");
const directory = fs.mkdtempSync(path.join(path.dirname(root), ".source-"));
const outputs = /* @__PURE__ */ new Map();
const formats = /* @__PURE__ */ new Map();
const failures = /* @__PURE__ */ new Map();
const helpers = /* @__PURE__ */ new Map();
const sources = /* @__PURE__ */ new Map();
const included = /* @__PURE__ */ new Set();
let disposed = false;
const { jsx, transform } = createJiti(root, {
fsCache: false,
moduleCache: false,
tsconfigPaths: false
}).options;
if (!transform) throw new Error("Jiti source transformer is unavailable");
const include = (source) => {
if (!isPathInside(root, source) || included.has(source)) return;
included.add(source);
if (path.relative(root, source).split(path.sep).includes("node_modules")) return;
if (fs.lstatSync(source).isDirectory()) for (const name of fs.readdirSync(source).toSorted()) include(path.join(source, name));
else if ((/\.[cm]?tsx?$/.test(source) || jsx && source.endsWith(".jsx")) && !/\.d\.[cm]?ts$/.test(source)) {
const emitted = path.join(directory, `module-${outputs.size}.js`);
outputs.set(source, emitted);
sources.set(emitted, { source });
fs.writeFileSync(emitted, "", {
flag: "wx",
mode: 384
});
}
};
const dispose = () => {
disposed = true;
fs.rmSync(directory, {
recursive: true,
force: true
});
};
try {
for (const name of fs.readdirSync(root).toSorted()) include(path.join(root, name));
const compile = (input, destination, format) => {
const { parse, parseExpression } = require("@babel/parser");
const { default: traverse } = require("@babel/traverse");
const { default: generate } = require("@babel/generator");
const { transformSync } = require("esbuild");
const sourceText = fs.readFileSync(input, "utf8");
const mode = sources.get(destination)?.mode;
const transformOptions = {
sourcefile: input,
loader: input.endsWith(".jsx") ? "jsx" : input.endsWith("x") ? "tsx" : "ts",
target: "es2022",
supported: { "import-attributes": true },
platform: "node",
jsx: jsx ? "transform" : "preserve",
tsconfigRaw: { compilerOptions: {
experimentalDecorators: true,
useDefineForClassFields: true
} }
};
const analysis = transformSync(sourceText, {
...transformOptions,
sourcefile: path.relative(root, input)
}).code;
let parsed;
try {
parsed = parse(analysis, {
sourceFilename: input,
sourceType: "unambiguous",
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true,
plugins: input.endsWith("x") ? ["jsx"] : []
});
} catch (error) {
if (error instanceof SyntaxError) throw new SyntaxError(`${path.relative(root, input)}: ${error.message}`, { cause: error });
throw error;
}
let usesCommonJs = false;
let needsHelper = false;
let explicitInterop = false;
let wildcardExports = false;
let loader = "";
let factory = "";
let namespace = "";
const globals = [];
traverse(parsed, {
Program(program) {
loader = program.scope.generateUidIdentifier("pluginRequire").name;
factory = program.scope.generateUidIdentifier("createPluginRequire").name;
namespace = program.scope.generateUidIdentifier("pluginNamespace").name;
},
ReferencedIdentifier(reference) {
if (!reference.isIdentifier()) return;
const name = reference.node.name;
if (reference.scope.getBinding(name)) return;
if (name === "module" || name === "exports") usesCommonJs = true;
if ([
"require",
"__filename",
"__dirname"
].includes(name)) {
globals.push(reference);
needsHelper ||= name === "require";
}
},
ImportDeclaration() {
needsHelper = true;
},
ExportNamedDeclaration(declaration) {
needsHelper ||= Boolean(declaration.node.source);
explicitInterop ||= declaration.node.specifiers.some((specifier) => specifier.exported.type === "StringLiteral" ? specifier.exported.value === "module.exports" : specifier.exported.name === "module.exports");
},
ExportAllDeclaration() {
needsHelper = true;
wildcardExports = true;
},
ImportExpression() {
needsHelper = true;
},
MemberExpression(member) {
needsHelper ||= member.node.object.type === "MetaProperty" && member.node.object.meta.name === "import" && member.node.property.type === "Identifier" && member.node.property.name === "resolve";
}
});
const native = mode === "native";
const esm = native && (format === "module" || format === "module-typescript") || !(native && (format === "commonjs" || format === "commonjs-typescript")) && mode !== "sync" && (/\.mtsx?$/.test(input) || !/\.ctsx?$/.test(input) && parsed.program.sourceType === "module" && !usesCommonJs);
formats.set(destination, esm ? "module" : "commonjs");
const createImportHelper = () => {
const helper = path.join(path.dirname(destination), `.import-meta-${randomUUID()}.mjs`);
fs.writeFileSync(helper, `import { createRequire } from "node:module";
import { fileURLToPath, pathToFileURL } from "node:url";
const modules = createRequire(import.meta.url).cache;
export const bindRequire = (nativeRequire) => {
// Only resolution can retry; evaluation below always runs once through Node.
const select = (specifier, options) => {
try {
return { target: specifier, resolved: nativeRequire.resolve(specifier, options) };
} catch (error) {
if (!["MODULE_NOT_FOUND", "ERR_MODULE_NOT_FOUND", "ERR_PACKAGE_PATH_NOT_EXPORTED", "ERR_PACKAGE_IMPORT_NOT_DEFINED"].includes(error?.code)) throw error;
const url = import.meta.resolve(specifier);
const resolved = url.startsWith("file:") ? fileURLToPath(url) : url;
return { target: resolved, resolved };
}
};
const require = (specifier) => {
const selected = select(specifier);
const cached = modules[pathToFileURL(selected.resolved).href];
return cached ? cached.exports : nativeRequire(selected.target);
};
const resolve = (specifier, options) => select(specifier, options).resolved;
return Object.assign(require, nativeRequire, { resolve: Object.assign(resolve, nativeRequire.resolve) });
};
export const importModule = async (specifier) => {
const resolved = import.meta.resolve(specifier);
const cached = modules[resolved];
const imported = cached
? { "openclaw:async-commonjs": await cached.exports }
: await import(specifier);
if (new URL(resolved).pathname.endsWith(".json")) return imported.default;
const namespace = Object.hasOwn(imported, "openclaw:async-commonjs")
? imported["openclaw:async-commonjs"] : imported;
if (namespace === null || (typeof namespace !== "object" && typeof namespace !== "function")) return namespace;
const fallback = namespace.default;
const delegate = (typeof fallback === "object" || typeof fallback === "function") && !(fallback instanceof Promise);
const values = new Map();
return new Proxy(namespace, {
get(target, key) {
if (values.has(key)) return values.get(key);
let value;
if (key === "__esModule") value = true;
else if (key === "default") {
value = fallback == null ? namespace
: typeof fallback?.default === "function" && namespace.__esModule ? fallback.default : fallback;
} else if (key in target) value = target[key];
else if (delegate) {
value = fallback[key];
if (typeof value === "function") value = value.bind(fallback);
}
values.set(key, value);
return value;
},
});
};
export const resolve = (specifier, options) => {
if (options === undefined) return import.meta.resolve(specifier);
const query = ${JSON.stringify(PLUGIN_SOURCE_RESOLVE_PREFIX)} + encodeURIComponent(JSON.stringify([specifier, options]));
return JSON.parse(decodeURIComponent(import.meta.resolve(query).slice("data:application/json,".length))).value;
};
`, {
flag: "wx",
mode: 384
});
helpers.set(helper, {
source: input,
generated: true,
mode: "async"
});
return helper;
};
if (mode === "sync" || mode === "async") {
const asynchronous = mode === "async";
const result = transform({
source: sourceText,
filename: input,
ts: /\.[cm]?tsx?$/.test(input),
jsx,
async: asynchronous,
interopDefault: true
});
const error = result.error;
if (error) throw new SyntaxError((error instanceof Error ? error.message : stringifyNonErrorCause(error)).replaceAll(input, path.relative(root, input)));
const helper = asynchronous || needsHelper ? createImportHelper() : void 0;
const output = parse(result.code, {
sourceType: "script",
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true
});
const header = parse(`__filename = ${JSON.stringify(input)}; __dirname = ${JSON.stringify(path.dirname(input))};
${helper ? `require = require(${JSON.stringify(helper)}).bindRequire(require("node:module").createRequire(${JSON.stringify(pathToFileURL(input).href)}));
const { importModule: jitiImport, resolve: jitiESMResolve } = require(${JSON.stringify(helper)});
${asynchronous ? "module.require = require;" : ""}` : ""}`);
output.program.body.unshift(...header.program.body);
const code = generate(output).code;
const emitted = asynchronous ? `import Module, { createRequire } from "node:module";
const owner = new Module(${JSON.stringify(input)});
owner.filename = ${JSON.stringify(input)};
owner.paths = Module._nodeModulePaths(${JSON.stringify(path.dirname(input))});
const require = createRequire(import.meta.url);
// Publish partial exports before awaiting dependencies; URL keys preserve import query identity.
require.cache[import.meta.url] = owner;
let value;
try {
await (async function(exports, require, module, __filename, __dirname) {
${code}
})(owner.exports, require, owner);
value = await owner.exports;
owner.loaded = true;
} catch (error) {
delete require.cache[import.meta.url];
throw error;
}
export { value as "openclaw:async-commonjs" };` : code;
if (asynchronous) formats.set(destination, "module");
fs.writeFileSync(destination, emitted, { mode: 384 });
return;
}
let needsRequire = false;
for (const reference of globals) {
const name = reference.node.name;
if (name === "require" && !esm) continue;
const replacement = name === "require" ? parseExpression(loader) : parseExpression(JSON.stringify(name === "__filename" ? input : path.dirname(input)));
needsRequire ||= name === "require";
if (reference.parentPath.isObjectProperty() && reference.key === "value") reference.parentPath.node.shorthand = false;
reference.replaceWith(replacement);
}
if (esm) {
const header = parse(`
${needsRequire ? `import { createRequire as ${factory} } from "node:module";
const ${loader} = ${factory}(${JSON.stringify(pathToFileURL(input).href)});` : ""}
import.meta.url = ${JSON.stringify(pathToFileURL(input).href)};
import.meta.filename = ${JSON.stringify(input)};
import.meta.dirname = ${JSON.stringify(path.dirname(input))};`, { sourceType: "module" });
parsed.program.body.unshift(...header.program.body);
if (wildcardExports && !explicitInterop) parsed.program.body.push(...parse(`import * as ${namespace} from ${JSON.stringify(pathToFileURL(destination).href)};
export { ${namespace} as "module.exports" };`, { sourceType: "module" }).program.body);
}
const emitted = transformSync(generate(parsed).code, {
...transformOptions,
format: esm ? "esm" : "cjs"
});
fs.writeFileSync(destination, emitted.code, { mode: 384 });
};
const hooks = Module.registerHooks({ load(url, context, nextLoad) {
const filename = url.startsWith("file:") ? fileURLToPath(url) : void 0;
if (filename) {
const entry = sources.get(filename);
if (entry && !formats.has(filename) && !failures.has(filename)) try {
if (entry.mode && entry.mode !== "native") entry.mode = context.conditions.includes("require") ? "sync" : "async";
compile(entry.source, filename, entry.nativeFormat ?? context.format);
} catch (error) {
failures.set(filename, error);
}
}
if (filename && failures.has(filename)) throw failures.get(filename);
const format = filename && formats.get(filename);
return nextLoad(url, format ? {
...context,
format
} : context);
} });
return {
directory,
include: (additions) => {
if (disposed) throw new Error("Plugin source view has been disposed");
additions.forEach(include);
},
resolve: (source, mode, nativeFormat) => {
const filename = outputs.get(source);
const entry = filename && sources.get(filename);
if (entry && !formats.has(filename)) {
entry.mode = mode ?? entry.mode ?? "native";
entry.nativeFormat = nativeFormat ?? entry.nativeFormat;
}
return filename ?? source;
},
sourceForOutput: (file) => sources.get(outputs.get(file) ?? file) ?? helpers.get(file),
dispose: () => {
hooks.deregister();
dispose();
}
};
} catch (error) {
dispose();
throw error;
}
}
//#endregion
//#region src/plugins/plugin-instance-module-loader.ts
const retainedNativeEsmModules = resolveGlobalMap(Symbol.for("openclaw.retainedNativeEsmPluginModules"));
function retainedNativeEsmFor(params) {
if (params.nativeRecovery || params.recoverySourceMap || isMainThread || workerData === null || typeof workerData !== "object" || !("sourceCaptureDirectory" in workerData)) return;
const extension = path.extname(params.source).toLowerCase();
if (extension !== ".mjs" && (extension !== ".js" || !isNativeEsmPackage(params.source))) return;
const owner = getPluginInstanceOwner(params.instance);
const workspace = getPluginRuntimeLoadContextState(owner?.registry)?.workspaceDir;
const key = `${fs.realpathSync(params.source)}\0${workspace ?? ""}`;
let retained = retainedNativeEsmModules.get(key);
if (!retained) {
retained = {
builds: /* @__PURE__ */ new Map(),
modules: /* @__PURE__ */ new Map(),
loaded: false,
owners: /* @__PURE__ */ new Set(),
knownOwners: /* @__PURE__ */ new WeakSet()
};
retainedNativeEsmModules.set(key, retained);
}
return retained;
}
function isNativeEsmPackage(source) {
let directory = path.dirname(source);
while (true) {
const manifest = path.join(directory, "package.json");
if (fs.existsSync(manifest)) try {
const parsed = JSON.parse(fs.readFileSync(manifest, "utf8"));
return typeof parsed === "object" && parsed !== null && Object.getOwnPropertyDescriptor(parsed, "type")?.value === "module";
} catch {
return false;
}
const parent = path.dirname(directory);
if (parent === directory) return false;
directory = parent;
}
}
function retainedNativeEsmOwner(retained) {
const requested = pluginInstanceInvocation.getStore()?.instance;
if (requested && retained.owners.has(requested)) return requested;
if (!requested && retained.owners.size === 1) return retained.owners.values().next().value;
throw new Error("Plugin native ESM capture has no live workspace owner for this load");
}
function createSourceOutputLookup(builds) {
const sourceForOutput = (filename) => {
for (const build of builds.values()) {
const source = build.sourceForOutput(filename);
if (source) return source;
}
return { source: filename };
};
return {
sourceForOutput,
moduleSource: (filename) => {
const entry = sourceForOutput(filename);
return entry.generated ? filename : entry.source;
}
};
}
function ownsNativeEsmResolution(retained, instance) {
const requested = pluginInstanceInvocation.getStore()?.instance;
if (!requested) return true;
if (!retained.knownOwners.has(requested)) return false;
if (!retained.owners.has(requested)) throw new Error("Plugin native ESM capture has no live workspace owner for this load");
return requested === instance;
}
function retainedNativeEsmExecution(retained) {
return (run) => retainedNativeEsmOwner(retained).run(run);
}
/** Runtime and setup share code identity policy while keeping separate instance authority. */
function bindPluginInstanceModuleLoader(params) {
const cache = getPluginCache();
if (params.origin === "bundled") {
if (params.expectedSourceDigest !== void 0) throw new Error("Source digest validation is not applicable to core-bundled runtime modules");
let loader;
if (params.createHostModuleLoader) loader = params.createHostModuleLoader();
else {
installOpenClawPluginSdkNativeResolver({
moduleUrl: import.meta.url,
pluginModulePath: params.source,
devSourceRoot: params.devSourceRoot,
pluginSdkResolution: params.pluginSdkResolution
});
loader = getCachedPluginModuleLoader({
modulePath: params.source,
importerUrl: import.meta.url,
devSourceRoot: params.devSourceRoot,
pluginSdkResolution: params.pluginSdkResolution
});
}
bindSharedPluginModuleLoader({
instance: params.instance,
rootDir: params.rootDir,
cache,
loader
});
return;
}
const retained = retainedNativeEsmFor(params);
const sourceBuilds = retained?.builds ?? /* @__PURE__ */ new Map();
const { sourceForOutput, moduleSource } = createSourceOutputLookup(sourceBuilds);
retained?.owners.add(params.instance);
retained?.knownOwners.add(params.instance);
const reusedArtifact = retained?.artifact;
let artifact;
try {
artifact = reusedArtifact ?? capturePluginGenerationArtifact(params.rootDir, params.standalone ? params.source : void 0, retained ? retainedNativeEsmExecution(retained) : (run) => params.instance.run(run), moduleSource, params.nativeRecovery);
if (params.expectedSourceDigest !== void 0 && artifact.sourceDigest !== params.expectedSourceDigest) {
if (!reusedArtifact) artifact.dispose();
throw new Error(`Plugin ${params.instance.pluginId} source changed after installation; inspect it before reloading.`);
}
} catch (error) {
retained?.owners.delete(params.instance);
throw error;
}
if (retained) retained.artifact = artifact;
bindPluginCacheRoot(params.rootDir, artifact.sourceRoot);
params.instance.sourceDigest = artifact.sourceDigest;
params.instance.onModuleDispose(() => {
retained?.owners.delete(params.instance);
if (retained) {
if (retained.loaded || retained.owners.size > 0) return;
retained.artifact = void 0;
retained.modules.clear();
}
for (const build of sourceBuilds.values()) build.dispose();
sourceBuilds.clear();
return artifact.disposeAsync();
});
const bindRecoveredModuleLoader = preparePluginModuleLoaderRecovery(params, artifact, bindPluginInstanceModuleLoader);
const capturedEntry = retained && artifact.resolve(params.source);
const bindModuleLoader = (load, hasSource) => bindRecoveredModuleLoader(retained ? (source) => {
const captured = artifact.resolve(source);
if (retained.modules.has(captured)) return retained.modules.get(captured);
const value = load(source);
retained.modules.set(captured, value);
retained.loaded ||= captured === capturedEntry;
return value;
} : load, hasSource);
const aliases = preparePluginLoaderAliases({
modulePath: params.source,
argv1: process.argv[1],
moduleUrl: import.meta.url,
pluginSdkResolution: params.pluginSdkResolution,
devSourceRoot: params.devSourceRoot
});
if (aliases.packageRoot && !reusedArtifact) artifact.linkHost(aliases.packageRoot);
installOpenClawPluginSdkNativeResolver({
moduleUrl: import.meta.url,
pluginModulePath: params.source,
devSourceRoot: params.devSourceRoot,
allowedParentRoots: [artifact.boundaryRoot],
pluginSdkResolution: params.pluginSdkResolution
});
if (!useNodeModuleHooks()) {
const capturedSource = artifact.resolve(params.source);
artifact.prepareModule(capturedSource);
const bunSourceFacts = process.versions.bun && isPluginSourceModulePath(params.source) ? inspectPluginTypeScriptExecutionFacts(params.source, fs.readFileSync(params.source, "utf8"), createJiti(params.source, {
fsCache: false,
moduleCache: false,
tryNative: false
})) : void 0;
for (const { specifier } of bunSourceFacts?.staticImports ?? []) if (path.isAbsolute(specifier) || specifier.startsWith("file:")) artifact.captureModule(capturedSource, specifier, [
"node",
"module-sync",
"import"
]);
const bunNeedsNativeSource = Boolean(process.versions.bun) && supportsBunRuntimeOnResolveTargets() && (bunSourceFacts?.hasComputedImport === true || bunSourceFacts?.staticImports.some(({ specifier, sideEffect }) => sideEffect && /\.cjs(?:[?#].*)?$/u.test(specifier)) === true);
const tryNative = process.env.JITI_JSX === "1" || process.env.JITI_JSX === "true" ? false : process.versions.bun && artifact.boundaryRoot.includes("\\") || bunNeedsNativeSource ? true : void 0;
const effectiveTryNative = tryNative ?? resolvePluginLoaderTryNative(params.source);
const loader = getCachedPluginModuleLoader({
modulePath: params.source,
importerUrl: import.meta.url,
devSourceRoot: params.devSourceRoot,
pluginSdkResolution: params.pluginSdkResolution,
tryNative,
aliasMap: {
...aliases.getAliasMap(),
...artifact.sourceAliases
}
});
bindNativePluginInstanceModuleLoader({
...params,
bindModuleLoader,
ownsResolution: retained ? () => ownsNativeEsmResolution(retained, params.instance) : void 0
}, cache, artifact, loader, aliases.sdkRoots, !effectiveTryNative);
return;
}
const nativeRequire = createRequire(params.source);
const createPaths = (source, options) => ({
resolver: createJiti(source, {
...options,
fsCache: false,
moduleCache: false,
alias: artifact.sourceAliases
}),
targets: /* @__PURE__ */ new Map()
});
const entryPaths = createPaths(params.source);
const pathResolvers = /* @__PURE__ */ new Map([[artifact.resolve(params.source), entryPaths]]);
const tsconfigPaths = entryPaths.resolver.options.tsconfigPaths;
const demandedModules = /* @__PURE__ */ new Map();
let resolvingPaths = false;
const includeSources = (additions) => {
for (const build of sourceBuilds.values()) build.include(additions);
};
const prepareSource = (filename, mode, nativeFormat) => {
const root = artifact.moduleRoot(filename);
if (!root) return filename;
return params.instance.run(() => {
let build = sourceBuilds.get(root);
if (!build) {
build = buildPluginTypeScriptSource(root);
sourceBuilds.set(root, build);
}
return build.resolve(filename, mode, nativeFormat);
});
};
const hooks = Module.registerHooks({ resolve(specifier, context, nextResolve) {
if (retained && !ownsNativeEsmResolution(retained, params.instance)) return nextResolve(specifier, context);
const parent = context.parentURL;
const parentEntry = parent?.startsWith("file:") ? sourceForOutput(fileURLToPath(parent)) : void 0;
const parentSource = parentEntry?.source;
const parentRoot = parentSource && artifact.moduleRoot(parentSource);
const resolverSource = parentEntry?.generated && parentSource && artifact.sourceForCaptured(parentSource);
if (resolverSource && parentSource && parentRoot && specifier.startsWith("openclaw-plugin-source-resolve:")) return params.instance.run(() => {
const requestJson = decodeURIComponent(specifier.slice(31));
const [request, options] = JSON.parse(requestJson);
const query = typeof options === "string" ? { parentURL: options } : options;
includeSources(artifact.prepareDependency(parentSource, request));
let paths = pathResolvers.get(parentSource);
if (!paths) {
paths = createPaths(resolverSource, entryPaths.resolver.options);
pathResolvers.set(parentSource, paths);
}
const value = paths.resolver.esmResolve(request, {
parentURL: pathToFileURL(parentSource),
...query
});
return {
shortCircuit: true,
url: "data:application/json," + encodeURIComponent(JSON.stringify({ value }))
};
});
const nativeContext = parentSource && parentRoot ? {
...context,
parentURL: pathToFileURL(parentSource).href
} : context;
const sourceMode = !parentRoot ? void 0 : parentEntry?.mode && parentEntry.mode !== "native" ? context.conditions.includes("require") ? "sync" : "async" : "native";
let resolved = parentSource && parentRoot ? params.instance.run(() => withPluginCache(cache, () => {
if (tsconfigPaths && !resolvingPaths && !isBuiltin(specifier) && !isPluginSdkAliasSpecifier(specifier) && !specifier.startsWith(".") && !specifier.startsWith("file:") && !path.isAbsolute(specifier)) {
let paths = pathResolvers.get(parentSource);
if (!paths) {
const original = artifact.sourceForCaptured(parentSource);
if (!original) return nextResolve(specifier, nativeContext);
paths = createPaths(original, entryPaths.resolver.options);
pathResolvers.set(parentSource, paths);
}
const key = JSON.stringify([specifier, context.conditions]);
if (!paths.targets.has(key)) {
resolvingPaths = true;
try {
paths.targets.set(key, paths.resolver.esmResolve(specifier, {
parentURL: pathToFileURL(parentSource),
conditions: [...context.conditions],
try: true
}));
} finally {
resolvingPaths = false;
}
}
const target = paths.targets.get(key);
if (target?.startsWith("file:")) {
const filename = fileURLToPath(target);
const captured = artifact.hasSource(filename) ? artifact.resolve(filename) : filename;
if (artifact.sourceForCaptured(captured)) return {
shortCircuit: true,
url: pathToFileURL(captured).href
};
}
}
const key = JSON.stringify([
parentSource,
specifier,
context.conditions
]);
const demanded = demandedModules.get(key);
if (demanded) {
if ("error" in demanded) throw demanded.error;
return {
shortCircuit: true,
url: demanded.url
};
}
includeSources(artifact.prepareDependency(parentSource, specifier));
let resolutionFailure;
try {
const native = nextResolve(specifier, nativeContext);
if (!(specifier.startsWith("file:") || path.isAbsolute(specifier)) || !native.url.startsWith("file:") || artifact.moduleRoot(sourceForOutput(fileURLToPath(native.url)).source) || aliases.sdkRoots.some((root) => isPathInside(root, fileURLToPath(native.url)))) return native;
resolutionFailure = /* @__PURE__ */ new Error(`Plugin module ${specifier} was not captured`);
} catch (error) {
if (isBuiltin(specifier) || isPluginSdkAliasSpecifier(specifier) || !(error instanceof Error) || !("code" in error) || error.code !== "MODULE_NOT_FOUND" && error.code !== "ERR_MODULE_NOT_FOUND") throw error;
resolutionFailure = error;
}
try {
const captured = artifact.captureModule(parentSource, specifier, context.conditions);
if (!captured) throw resolutionFailure;
includeSources(captured.additions);
if ("retryNative" in captured) {
const native = nextResolve(specifier, nativeContext);
demandedModules.set(key, { url: native.url });
return native;
}
const filename = fileURLToPath(captured.target);
const target = (isPluginSourceModulePath(filename) || filename.endsWith(".jsx")) && artifact.moduleRoot(filename) ? prepareSource(filename, sourceMode, sourceMode === "native" ? nextResolve(context.conditions.includes("require") ? filename : pathToFileURL(filename).href, nativeContext).format : void 0) : filename;
captured.target.pathname = pathToFileURL(target).pathname;
const url = captured.target.href;
demandedModules.set(key, { url });
return {
shortCircuit: true,
url
};
} catch (captureError) {
demandedModules.set(key, { error: captureError });
throw captureError;
}
})) : nextResolve(specifier, nativeContext);
if (resolved.url.startsWith("file:")) {
const resolvedFilename = fileURLToPath(resolved.url);
const entry = sourceForOutput(resolvedFilename);
const filename = entry.generated ? resolvedFilename : entry.source;
const attributes = resolved.importAttributes ?? context.importAttributes;
if (filename.endsWith(".json") && parentRoot && parentEntry?.mode && (resolved.format === void 0 || resolved.format === "json") && attributes && !Object.hasOwn(attributes, "type")) resolved = {
...resolved,
importAttributes: {
...attributes,
type: "json"
}
};
artifact.assertModuleAvailable(filename);
const additions = artifact.prepareModule(filename);
includeSources(additions);
if ((isPluginSourceModulePath(filename) || filename.endsWith(".jsx")) && artifact.moduleRoot(filename)) {
const url = new URL(resolved.url);
url.pathname = pathToFileURL(prepareSource(filename, sourceMode, resolved.format)).pathname;
return {
...resolved,
url: url.href
};
}
}
return resolved;
} });
params.instance.onModuleDispose(() => hooks.deregister());
const results = /* @__PURE__ */ new Map();
bindModuleLoader((source) => withPluginCache(cache, () => {
const captured = artifact.resolve(source);
let result = results.get(captured);
if (!result) {
try {
const target = isPluginSourceModulePath(captured) || captured.endsWith(".jsx") ? prepareSource(captured, "sync") : captured;
result = { value: nativeRequire(target) };
} catch (error) {
result = { error };
}
results.set(captured, result);
}
if ("error" in result) throw result.error;
return result.value;
}), artifact.hasSource);
}
//#endregion
//#region src/plugins/plugin-instance-background.ts
/** Capture host bindings without retaining registration's request or registry generation. */
function createPluginBackgroundRunner(instance, isCleanup) {
const spawnBroker = getSpawnBroker();
const runWithReadOnlyWorkers = captureSqliteReadOnlyWorkerScope();
const resourceHost = withPluginRuntimeGatewayContextResolver(void 0, getBoundLegacyPluginSdkResourceHost, { inheritRequestScope: false });
return (run) => {
const current = pluginInstanceInvocation.getStore();
const cleanup = current?.instance === instance && instance.hasActiveCall && isCleanup(current.token) ? {
instance,
token: current.token
} : void 0;
const registry = instance.owner?.registry;
const runtime = registry && getPluginRegistryRuntime(registry);
const resolver = runtime && getGatewayContextResolver(runtime);
return runInDetachedAsyncContext(() => runWithSpawnBroker(spawnBroker, () => runWithReadOnlyWorkers(() => withPluginRuntimeGatewayContextResolver(resolver, () => {
const invoke = () => cleanup ? pluginInstanceInvocation.run(cleanup, () => instance.run(run)) : instance.runInRegistry(registry, run, { joinDisposal: false });
return !resolver && resourceHost ? resourceHost.run(invoke) : invoke();
}))));
};
}
//#endregion
//#region src/plugins/plugin-instance-argument-views.ts
var CallbackView = class extends PluginHostObject {
#factory;
constructor(value, factory) {
super(value);
this.#factory = factory;
}
static belongsTo(value, factory) {
return #factory in value && value.#factory === factory;
}
};
const arrayCallbacks = /* @__PURE__ */ new Set([
"every",
"filter",
"find",
"findIndex",
"findLast",
"findLastIndex",
"flatMap",
"forEach",
"map",
"reduce",
"reduceRight",
"some",
"sort",
"toSorted"
]);
/** Native signatures distinguish callbacks, stored callables, and function invocation helpers. */
function getPluginMethodArgumentKind(object, key) {
const array = Array.isArray(object);
const intrinsic = typeof object === "function" ? Function.prototype : array ? Array.prototype : types.isMap(object) ? Map.prototype : types.isSet(object) ? Set.prototype : types.isWeakMap(object) ? WeakMap.prototype : types.isWeakSet(object) ? WeakSet.prototype : void 0;
if (!intrinsic || !Object.hasOwn(intrinsic, key)) return;
let prototype = object;
while (prototype && !Object.hasOwn(prototype, key)) prototype = Object.getPrototypeOf(prototype);
const parent = prototype && Object.getPrototypeOf(prototype);
if (!parent || Object.getPrototypeOf(parent) !== null) return;
if (typeof object === "function") return "function";
return key === "forEach" || array && typeof key === "string" && arrayCallbacks.has(key) ? 0 : null;
}
/** Preserves caller data and caches callback views within one instance. */
function createPluginArgumentView(bindings) {
const restoreHandle = (value) => {
if (value === null || typeof value !== "object") return value;
let source = value;
for (let original = bindings.original(source); original; original = bindings.original(source)) source = original;
return source;
};
const callbacks = /* @__PURE__ */ new WeakMap();
const factory = {};
return (args, callbackIndex, field = "") => {
if (callbackIndex === null || args.length === 0) return { args: args.map((value) => typeof value === "function" ? bindings.original(value) ?? value : restoreHandle(value)) };
const callerData = callbackIndex === 0 && (field === "reduce" || field === "reduceRight") ? args.slice(1, 2) : void 0;
return {
args: args.map((value, index) => {
if (typeof value !== "function" || callbackIndex !== void 0 && index !== callbackIndex) return restoreHandle(value);
if (callbackIndex === void 0 && bindings.isWrapped(value)) return bindings.original(value) ?? value;
let callback = callerData ? void 0 : CallbackView.belongsTo(value, factory) ? value : callbacks.get(value);
if (!callback) {
const invoke = (values, run) => bindings.invoke(() => run(values));
callback = new Proxy(value, {
apply: (target, receiver, values) => {
const result = invoke(values, (wrapped) => Reflect.apply(target, receiver, wrapped));
if (callerData) callerData[0] = result;
return result;
},
construct: (target, values, newTarget) => invoke(values, (wrapped) => Reflect.construct(target, wrapped, newTarget === callback ? target : newTarget))
});
bindings.setOriginal(callback, value);
if (!callerData) {
callbacks.set(value, callback);
new CallbackView(callback, factory);
}
}
return callback;
}),
callerData
};
};
}
//#endregion
//#region src/plugins/plugin-instance-bindings.ts
const { values: valueInstances$1 } = pluginInstanceState;
var ExecutableBinding = class extends PluginHostObject {
#factory;
constructor(value, factory) {
super(value);
this.#factory = factory;
}
static belongsTo(value, factory) {
return #factory in value && value.#factory === factory;
}
};
const DATA_FIELDS = /* @__PURE__ */ new Set([
"parameters",
"schema",
"configSchema",
"configJsonSchema",
"inputSchema",
"outputSchema"
]);
function pluginMemberDescriptor(object, key) {
let descriptor;
for (let source = object; source && !descriptor; source = Object.getPrototypeOf(source)) descriptor = Object.getOwnPropertyDescriptor(source, key);
return descriptor;
}
function readPluginMember(object, key, invoke, receiver = object) {
return pluginMemberNeedsAdmission(object, key) ? invoke(() => Reflect.get(object, key, receiver)) : Reflect.get(object, key, receiver);
}
function pluginMemberNeedsAdmission(object, key, getters = true) {
for (let source = object; source; source = Object.getPrototypeOf(source)) {
if (types.isProxy(source)) return true;
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (descriptor) return getters && descriptor.get !== void 0;
}
return false;
}
function hasProxyPrototype(object) {
for (let source = object; source; source = Object.getPrototypeOf(source)) if (types.isProxy(source)) return true;
return false;
}
function isNativePluginData(value) {
return types.isAnyArrayBuffer(value) || types.isArrayBufferView(value) || types.isDate(value) || types.isRegExp(value) || types.isNativeError(value);
}
function bindNativeReceiver(invoke) {
return function(...args) {
return invoke(this, args);
};
}
/** Registration facts and captured stream scopes share the instance's lifetime. */
function createPluginInstanceBindings(bindings) {
const { instance } = bindings;
const factories = /* @__PURE__ */ new WeakSet();
const admitFactory = (factory) => {
factories.add(factory);
};
return {
admitFactory,
create(admit, admitCallback = (run) => admit(() => bindings.invoke(run))) {
return createPluginBindings({
...bindings,
admitFactory,
prepareInvocation: (token, parent) => {
const context = pluginInvocationContext.getStore();
const consumer = bindings.isConsumerToken(token);
const origin = parent ?? pluginInstanceInvocation.getStore();
const resource = bindings.enter(token, () => new AsyncResource("OpenClawPluginStream"));
return {
origin,
reenter: (run) => {
if (consumer && !bindings.hasToken(token)) throw new Error(`Plugin ${instance.pluginId} consumer is closed`);
return resource.runInAsyncScope(() => pluginInstanceInvocation.run(origin, run));
},
assertCurrent: () => {
context?.lookup(instance);
const current = pluginInvocationContext.getStore();
if (current !== context) current?.lookup(instance);
},
run: (run) => resource.runInAsyncScope(run),
close: () => resource.emitDestroy()
};
},
isFactory: (value) => {
for (let source = value; source; source = getPluginOriginalValue(source, instance)) if (factories.has(source)) return true;
return false;
}
}, admit, admitCallback);
}
};
}
/** Builds callable views while the exact instance continues to own admission and leases. */
function createPluginBindings(bindings, admit, admitCallback) {
const wrapped = /* @__PURE__ */ new WeakMap();
const factory = {};
const originalValue = (value) => getPluginOriginalValue(value, bindings.instance);
const derivedReceivers = /* @__PURE__ */ new WeakSet();
const prototypeReceivers = /* @__PURE__ */ new WeakMap();
const iterators = /* @__PURE__ */ new WeakMap();
const wrapArguments = createPluginArgumentView({
original: originalValue,
setOriginal: (value, source) => setPluginOriginalValue(value, source, bindings.instance),
isWrapped: (value) => ExecutableBinding.belongsTo(value, factory),
invoke: admitCallback
});
const passResult = (value) => typeof value === "function" || value !== null && typeof value === "object" && typeof Reflect.get(value, Symbol.asyncIterator) === "function" ? wrap(value) : value;
const bindFactoryResult = (value) => {
if (!Array.isArray(value)) return wrap(value);
return value.map((entry) => wrap(entry));
};
const wrapResult = (result, callerData, executable = false) => {
const completion = resolvePluginReturnPromise(result);
if (completion) {
const pending = mapPluginReturnPromise(completion, (resolved) => executable ? bindFactoryResult(resolved) : passResult(resolved));
if (pending.host) valueInstances$1.setHost(pending.value, bindings.instance);
else valueInstances$1.set(pending.value, bindings.instance);
return pending.value;
}
return callerData?.includes(result) ? result : executable ? bindFactoryResult(result) : passResult(result);
};
/** Callables retain their instance; schemas remain data for host validators. */
const wrap = (value, field = "", callbackIndex) => {
if ((!value || typeof value !== "object") && typeof value !== "function") return value;
if (DATA_FIELDS.has(field) || typeof value === "object" && isNativePluginData(value)) return value;
const object = value;
if (ExecutableBinding.belongsTo(object, factory)) return value;
const cached = wrapped.get(object);
if (cached) return cached;
const methods = /* @__PURE__ */ new Map();
const derivedFields = /* @__PURE__ */ new Set();
const invoke = (run) => {
const current = iterators.get(object);
if (current?.active) return current.invoke(run);
return current && !current.done ? current.reenter(() => admit(run)) : admit(run);
};
const inspectIterable = () => pluginMemberDescriptor(object, Symbol.asyncIterator);
const iterableDescriptor = pluginMemberNeedsAdmission(object, Symbol.asyncIterator, false) ? invoke(inspectIterable) : inspectIterable();
const iterable = typeof iterableDescriptor?.value === "function" || iterableDescriptor?.get !== void 0;
const reflect = (run) => types.isProxy(object) ? invoke(run) : run();
const resolveReceiver = (key, receiver) => {
const receivers = prototypeReceivers.get(object);
if (receivers) {
const original = originalValue(receiver) ?? receiver;
return receivers.get(original) ?? original;
}
if (receiver !== object && receiver !== result) return originalValue(receiver) ?? receiver;
return derivedReceivers.has(object) && (!reflect(() => Object.hasOwn(object, key)) || derivedFields.has(key)) ? result : object;
};
const read = (key, receiver = object) => {
const protocol = key === "next" || key === "return" || key === "throw";
const iteration = iterators.get(object);
if (iteration?.done) {
if (key === Symbol.asyncIterator) return methods.get(key)?.wrapped ?? (() => result);
if (protocol) return (...args) => iteration.call(key, void 0, args);
}
const prepared = methods.get(key);
if (protocol && iteration?.active && prepared) return prepared.wrapped;
let resolvedReceiver = receiver;
let property;
try {
resolvedReceiver = resolveReceiver(key, receiver);
property = readPluginMember(object, key, invoke, resolvedReceiver);
} catch (error) {
if (protocol && iteration?.active) iteration.close();
throw error;
}
if (key === "return" && iteration && typeof property !== "function") {
if (property == null) return (...args) => iteration.call(key, void 0, args);
if (iteration.active) iteration.close();
}
if (typeof property !== "function" || key === "constructor") return wrap(property, String(key));
const cachedMethod = methods.get(key);
if (cachedMethod?.original === property && cachedMethod.receiver === resolvedReceiver) return cachedMethod.wrapped;
if (key === Symbol.asyncIterator || protocol && (iterable || iteration)) {
const bound = key === Symbol.asyncIterator ? (...args) => {
if (iterators.get(object)?.done) return result;
return invoke(() => {
const iterator = Reflect.apply(property, resolvedReceiver, args);
if (!iterator || typeof iterator !== "object" && typeof iterator !== "function") throw new TypeError("Plugin async iterator factory must return an object");
admitIterator(iterator);
return wrap(iterator);
});
} : (...args) => {
try {
const current = iterators.get(object);
return (current && (current.active || current.done) ? current : admitIterator(object)).call(key, property, args);
} catch (error) {
return Promise.resolve().then(() => {
throw error;
});
}
};
methods.set(key, {
original: property,
receiver: resolvedReceiver,
wrapped: bound
});
valueInstances$1.setHost(bound, bindings.instance);
return bound;
}
const classify = () => getPluginMethodArgumentKind(object, key);
const kind = hasProxyPrototype(object) ? invoke(classify) : classify();
if (kind === "function" && !prototypeReceivers.has(object) && (key === "call" || key === "apply" || key === "bind")) {
const bound = key === "bind" ? (...args) => admit(() => {
const source = Reflect.apply(Function.prototype.bind, result, wrapArguments(args).args);
if (bindings.isFactory(result)) bindings.admitFactory(source);
return wrap(source);
}) : wrap(Function.prototype.bind.call(Function.prototype[key], result), key);
methods.set(key, {
original: property,
receiver: resolvedReceiver,
wrapped: bound
});
valueInstances$1.setHost(bound, bindings.instance);
return bound;
}
const bind = () => prototypeReceivers.has(object) ? new Proxy(property, { apply: (target, callReceiver, args) => Reflect.apply(target, resolveReceiver(key, callReceiver), args) }) : Function.prototype.bind.call(property, resolvedReceiver);
const bound = wrap(!originalValue(property) && (pluginMemberNeedsAdmission(property, "length") || pluginMemberNeedsAdmission(property, "name")) ? invoke(bind) : bind(), String(key), kind === "function" ? void 0 : kind);
setPluginOriginalValue(bound, property, bindings.instance);
methods.set(key, {
original: property,
receiver: resolvedReceiver,
wrapped: bound
});
return bound;
};
const handlers = {
get: (target, key, receiver) => {
const fixed = Object.getOwnPropertyDescriptor(target, key);
return fixed?.configurable === false && "value" in fixed && !fixed.writable ? fixed.value : read(key, receiver);
},
has: (_target, key) => pluginMemberNeedsAdmission(object, key, false) ? invoke(() => Reflect.has(object, key)) : Reflect.has(object, key),
getPrototypeOf: () => prototypeReceivers.has(object) ? object : reflect(() => Object.getPrototypeOf(object)),
ownKeys: () => reflect(() => Reflect.ownKeys(object)),
getOwnPropertyDescriptor: (target, key) => {
const original = reflect(() => Object.getOwnPropertyDescriptor(object, key));
if (!original) return;
const configurable = key !== "length" || !Array.isArray(value);
const fixed = Object.getOwnPropertyDescriptor(target, key);
if (configurable && fixed?.configurable === false) return "value" in fixed && fixed.writable ? {
...fixed,
value: read(key)
} : fixed;
if (!configurable) Object.defineProperty(target, key, original);
return "value" in original ? {
...original,
configurable,
value: read(key)
} : {
...original,
configurable,
get: original.get ? bindNativeReceiver((receiver) => read(key, receiver)) : void 0,
set: original.set ? bindNativeReceiver((receiver, [next]) => invoke(() => Reflect.set(object, key, next, resolveReceiver(key, receiver ?? object)))) : void 0
};
},
set: (_target, key, next, receiver) => invoke(() => Reflect.set(object, key, next, pluginMemberDescriptor(object, key)?.set ? resolveReceiver(key, receiver) : receiver)),
preventExtensions: () => false,
defineProperty: (target, key, attributes) => invoke(() => {
const current = handlers.getOwnPropertyDescriptor(target, key);
if (!Reflect.defineProperty(object, key, attributes)) return false;
derivedFields.add(key);
if (current) Object.defineProperty(target, key, current);
return Reflect.defineProperty(target, key, attributes);
}),
deleteProperty: (_target, key) => invoke(() => Reflect.deleteProperty(object, key))
};
let result;
if (typeof value === "function") {
const prototype = reflect(() => Object.getOwnPropertyDescriptor(value, "prototype")?.value);
const receivers = prototype && typeof prototype === "object" ? prototypeReceivers.get(prototype) ?? /* @__PURE__ */ new WeakMap() : void 0;
if (receivers) prototypeReceivers.set(prototype, receivers);
const bind = () => Function.prototype.bind.call(value, void 0);
result = new Proxy(pluginMemberNeedsAdmission(value, "length") || pluginMemberNeedsAdmission(value, "name") ? admit(bind) : bind(), {
...handlers,
apply: (_target, receiver, args) => admit(() => {
const call = wrapArguments(args, callbackIndex, field);
return wrapResult(Reflect.apply(value, receiver, call.args), call.callerData, bindings.isFactory(result));
}),
construct: (_target, args, newTarget) => admit(() => {
const constructed = Reflect.construct(value, wrapArguments(args, callbackIndex, field).args, newTarget === result ? value : newTarget);
receivers?.set(originalValue(constructed) ?? constructed, constructed);
if (newTarget !== result) derivedReceivers.add(constructed);
return wrap(constructed);
})
});
} else result = new Proxy(Array.isArray(value) ? [] : Object.create(reflect(() => Object.getPrototypeOf(object))), handlers);
wrapped.set(object, result);
new ExecutableBinding(result, factory);
setPluginOriginalValue(result, object, bindings.instance);
valueInstances$1.setHost(result, bindings.instance);
return result;
};
const admitIterator = (iterator) => {
const current = iterators.get(iterator);
if (current?.active || current?.done) return current;
const create = () => admit(() => createIteratorAdmission(iterator, current));
return current ? current.reenter(create) : create();
};
const createIteratorAdmission = (iterator, previous) => {
const { token, release } = bindings.lease();
let invocation;
try {
invocation = bindings.prepareInvocation(token, previous?.origin);
} catch (error) {
release();
throw error;
}
let state = "open";
let active = true;
let pending = 0;
let closed = false;
const settle = () => {
if (state === "done" && pending === 0 && !closed) {
closed = true;
invocation.close();
}
if (state !== "open" && pending === 0 && active) {
active = false;
return release();
}
};
const releaseOperation = () => {
pending -= 1;
return settle();
};
const assertActive = () => {
if (!active || !bindings.hasToken(token)) throw new Error(`Plugin ${bindings.instance.pluginId} stream is closed`);
};
const invoke = (run) => {
pending += 1;
try {
try {
assertActive();
invocation.assertCurrent();
} catch (error) {
state = "done";
throw error;
}
return invocation.run(() => {
const result = run();
const completion = resolvePluginReturnPromise(result);
if (completion) return mapPluginReturnPromise(completion, async (value) => {
await releaseOperation();
return value;
}, async (error) => {
await releaseOperation()?.catch(() => {});
throw error;
}).value;
releaseOperation();
return result;
});
} catch (error) {
releaseOperation();
throw error;
}
};
const admission = {
origin: invocation.origin,
reenter: (run) => {
try {
invocation.assertCurrent();
return invocation.reenter(run);
} catch (error) {
state = "done";
settle();
throw error;
}
},
get done() {
return state === "done";
},
get active() {
return active;
},
invoke,
close: () => {
state = "done";
settle();
},
call: (key, method, args) => {
if (state === "done" && key === "return") return Promise.resolve(args[0]).then((value) => ({
done: true,
value
}));
if (state === "done") return Promise.resolve().then(() => admit(() => {
if (key === "throw") throw args[0];
return {
done: true,
value: void 0
};
}));
try {
assertActive();
invocation.assertCurrent();
return invocation.run(async () => {
pending += 1;
try {
if (!method) {
if (key === "return") {
state = "done";
return {
done: true,
value: await args[0]
};
}
throw new TypeError("Plugin iterator method must be callable");
}
const next = await Reflect.apply(method, iterator, args);
if (next === null || typeof next !== "object" && typeof next !== "function") throw new TypeError("Plugin async iterator result must be an object");
assertActive();
state = Reflect.get(next, "done") ? "done" : key === "return" ? "returned" : state;
return next;
} catch (error) {
state = "done";
throw error;
} finally {
const completion = releaseOperation();
if (completion) await completion;
}
});
} catch (error) {
state = "done";
const reject = () => {
throw error;
};
return Promise.resolve(settle()).then(reject, reject);
}
}
};
iterators.set(iterator, admission);
previous?.close();
return admission;
};
return wrap;
}
//#endregion
//#region src/plugins/plugin-instance-disposal.ts
var DisposalFailures = class extends Set {
constructor(instance) {
super();
this.instance = instance;
this.hostErrors = /* @__PURE__ */ new Set();
this.instanceErrors = /* @__PURE__ */ new Set();
}
add(error) {
const current = pluginInstanceInvocation.getStore();
(current?.instance === this.instance && PluginCallToken.isHostCleanup(current.token) ? this.hostErrors : this.instanceErrors).add(error);
return super.add(error);
}
result(errors) {
const hostCleanupErrors = [...this.hostErrors].filter((error) => !this.instanceErrors.has(error));
return {
errors,
...hostCleanupErrors.length ? { hostCleanupErrors } : {}
};
}
};
//#endregion
//#region src/plugins/plugin-instance-settlement.ts
/** Observers never revoke admitted work; cancellation removes only their waiter. */
async function waitForPluginInstanceSettlement(pluginId, waiters, settled, signal) {
signal.throwIfAborted();
if (settled()) return;
await new Promise((resolve, reject) => {
const cleanup = () => {
waiters.delete(wake);
signal.removeEventListener("abort", abort);
};
const wake = () => {
if (settled()) {
cleanup();
resolve();
}
};
const abort = () => {
cleanup();
reject(toErrorObject(signal.reason, `Plugin ${pluginId} work drain aborted`));
};
waiters.add(wake);
signal.addEventListener("abort", abort, { once: true });
});
}
//#endregion
//#region src/plugins/plugin-instance.ts
const { values: valueInstances } = pluginInstanceState;
const SHUTDOWN_TIMEOUT_MS = 5e3;
const log = createSubsystemLogger("plugins/cleanup");
var PluginInstance = class {
constructor(pluginId, owner) {
this.pluginId = pluginId;
this.slots = /* @__PURE__ */ new Map();
this.controller = new AbortController();
this.toolRegistrationComplete = false;
this.controlPlaneInitialized = false;
this.accepting = true;
this.replacementReserved = false;
this.retainedWork = /* @__PURE__ */ new Set();
this.calls = /* @__PURE__ */ new Map();
this.forcedRetirement = false;
this.consumers = /* @__PURE__ */ new Map();
this.cleanups = /* @__PURE__ */ new Map();
this.waiters = /* @__PURE__ */ new Set();
this.bindings = createPluginInstanceBindings({
instance: this,
enter: (token, run) => this.enter(token, run),
invoke: (run) => this.invoke(run),
lease: () => this.lease(),
hasToken: (token) => this.hasToken(token),
isConsumerToken: (token) => this.consumers.has(token)
});
this.admitFactory = this.bindings.admitFactory;
this.wrap = this.bindings.create((run) => this.run(run), (run) => this.runConsumer(run));
if (owner && "record" in owner) {
this.owner = resolvePluginInstanceOwner(owner.record, owner.registry);
if (this.owner.instance) throw new Error(`Plugin ${pluginId} already owns a runtime instance`);
this.owner.instance = this;
pluginInstanceState.records.set(this, this.owner);
} else this.setupCache = owner?.cache;
this.lifecycle = Object.freeze({
signal: this.controller.signal,
onDispose: (cleanup) => this.addCleanup(cleanup, "plugin"),
runInBackgroundContext: createPluginBackgroundRunner(this, (token) => this.calls.get(token)?.cleanup === true)
});
}
addCleanup(cleanup, kind) {
if (this.controller.signal.aborted || (!this.accepting || this.owner?.revoked) && !this.activeCall()) throw new Error(`Plugin ${this.pluginId} is retiring`);
this.cleanups.set(cleanup, kind);
return () => void this.cleanups.delete(cleanup);
}
/** Captured module resources retain physical custody after a forced logical retirement. */
onModuleDispose(cleanup) {
this.addCleanup(cleanup, "module");
}
hasToken(token) {
return this.calls.has(token) || this.consumers.get(token)?.active === true;
}
activeCall(scope = pluginInstanceInvocation.getStore()) {
return scope?.instance === this && this.hasToken(scope.token) ? scope : void 0;
}
get acceptingCalls() {
return this.accepting;
}
get hasActiveCall() {
return this.activeCall() !== void 0;
}
/** A reload has reserved this instance and is draining its admitted work. */
get replacementPending() {
return this.replacementReserved;
}
holdsPendingReplacement(token) {
return this.replacementReserved && this.hasToken(token);
}
run(run) {
const current = this.activeCall();
if (current) return this.enter(current.token, run);
const scoped = pluginInvocationContext.getStore()?.lookup(this);
if (scoped) return scoped.run(run);
if (!this.accepting || this.owner?.revoked) throw new PluginInstanceUnavailableError(this.pluginId);
return this.invoke(run);
}
runInRegistry(registry, run, options) {
const current = this.activeCall();
if (current) return this.enter(current.token, run);
if (!this.accepting || this.owner?.revoked) throw new PluginInstanceUnavailableError(this.pluginId);
return this.invoke(run, this.lease({
registry,
joinDisposal: options?.joinDisposal
}));
}
/** Associates an identity-sensitive public value without replacing it with a view. */
adopt(value) {
return valueInstances.adopt(value, this);
}
createRegistryView(registry, invoke) {
return this.bindings.create((run) => invoke(() => this.runInRegistry(registry, run)));
}
/** Detached host consumption retains its completion independently of its admitting caller. */
runConsumer(consume) {
return this.activeCall() ? this.invoke(consume) : this.run(consume);
}
get hasRetainedConsumers() {
return this.consumers.size > 0;
}
/** Track finite host work without granting invocation authority or joining disposal. */
retainWork() {
if (this.replacementReserved) throw new Error(`Plugin ${this.pluginId} replacement is in progress`);
const token = {};
this.retainedWork.add(token);
return () => {
if (this.retainedWork.delete(token)) this.waiters.forEach((wake) => wake());
};
}
get retainedWorkCount() {
return this.retainedWork.size + [...this.consumers.values()].filter(({ kind }) => kind === "work").length;
}
get ordinaryCallCount() {
return [...this.calls.values()].filter((call) => !call.cleanup).length;
}
/** Observe admitted work without closing callbacks or idle publication custody. */
async waitForRetainedWork(signal, { includeConsumers = true, includeCalls = false } = {}) {
await waitForPluginInstanceSettlement(this.pluginId, this.waiters, () => (!includeCalls || this.ordinaryCallCount === 0) && (includeConsumers ? this.retainedWorkCount : this.retainedWork.size) === 0, signal);
}
/** Reserve replacement atomically before host owners invalidate or stop this instance. */
reserveReplacement() {
if (this.hasActiveCall) throw new Error(`Plugin ${this.pluginId} cannot replace itself from its own active call; retry after the call finishes.`);
if (this.replacementReserved) throw new Error(`Plugin ${this.pluginId} replacement is in progress`);
this.replacementReserved = true;
let released = false;
return () => {
if (!released) {
released = true;
this.replacementReserved = false;
}
};
}
/** Retain executable use or idle donor custody through its physical completion. */
retainConsumer(invoke, registry, kind = "work") {
const current = this.activeCall();
const parent = current && this.consumers.get(current.token);
if (this.replacementReserved && parent?.kind !== "work" || (!this.accepting || this.owner?.revoked) && !parent?.active) throw new Error(`Plugin ${this.pluginId} is retiring`);
const released = createDeferredCore();
const token = {
active: true,
completion: released.promise,
registry,
kind
};
this.consumers.set(token, token);
let closing;
const release = () => {
token.active = false;
if (this.consumers.delete(token)) {
released.resolve();
this.waiters.forEach((wake) => wake());
}
};
const run = (consume) => {
if (!token.active) throw new Error(`Plugin ${this.pluginId} consumer is closed`);
const call = () => this.invoke(consume, {
token,
release: () => void 0
});
return invoke ? invoke(call) : call();
};
return {
run,
wrap: this.bindings.create(run, run),
close: (cleanup) => {
if (!closing && this.consumers.has(token)) {
token.active = false;
const completion = createDeferredCore();
closing = completion.promise.finally(release);
try {
completion.resolve(this.invoke(cleanup, this.lease({
cleanup: true,
registry: token.registry
}), this.disposalFailures));
} catch (error) {
completion.reject(error);
}
}
return closing ?? Promise.reject(/* @__PURE__ */ new Error(`Plugin ${this.pluginId} consumer is closed`));
},
release: () => {
if (!closing) release();
}
};
}
/** Only lifecycle owners may admit teardown after ordinary calls have stopped. */
runCleanup(run) {
const current = this.activeCall();
if (!current) this.controller.signal.throwIfAborted();
return this.invoke(run, current ? {
token: current.token,
release: () => void 0
} : this.lease({ cleanup: true }), this.disposalFailures);
}
invoke(run, { token, release } = this.lease(), cleanupFailures) {
const cleanup = this.calls.get(token)?.cleanup === true;
try {
return this.enter(token, () => {
const value = run();
const completion = resolvePluginReturnPromise(value);
if (completion) {
const settled = mapPluginReturnPromise(completion, async (result) => {
await release();
if (this.forcedRetirement && !cleanup && !this.hasToken(token)) throw new PluginInstanceUnavailableError(this.pluginId);
return result;
}, async (error) => {
cleanupFailures?.add(error);
await release()?.catch(() => {});
throw error;
});
if (settled.host) valueInstances.setHost(settled.value, this);
else valueInstances.set(settled.value, this);
return settled.value;
}
release();
if (this.forcedRetirement && !cleanup && !this.hasToken(token)) throw new PluginInstanceUnavailableError(this.pluginId);
return value;
});
} catch (error) {
cleanupFailures?.add(error);
release();
throw error;
}
}
enter(token, run) {
pluginInvocationContext.getStore()?.lookup(this);
const current = pluginInstanceInvocation.getStore();
const call = current?.instance === this && current.token === token ? current : {
instance: this,
token,
parent: current
};
if (!this.owner) {
const enter = () => pluginInstanceInvocation.run(call, run);
return this.setupCache ? withPluginCache(this.setupCache, enter) : enter();
}
const { record } = this.owner;
const generation = getPluginRuntimeGenerationRegistry();
const registry = this.consumers.get(token)?.registry ?? this.calls.get(token)?.registry ?? (generation?.plugins.includes(record) ? generation : this.owner.registry);
return withPluginRuntimePluginScope({
pluginId: record.id,
pluginSource: record.source,
pluginOrigin: record.origin,
pluginTrustedOfficialInstall: record.trustedOfficialInstall
}, run, registry, call);
}
lease(options = {}) {
const current = this.activeCall();
const registry = options.registry ?? (current && this.calls.get(current.token)?.registry);
const joinDisposal = !options.cleanup && options.joinDisposal !== false;
if (!options.registry && current && this.consumers.has(current.token)) return {
token: current.token,
release: () => void 0
};
const token = new PluginCallToken({}, options.cleanup && options.hostCleanup || current !== void 0 && PluginCallToken.isHostCleanup(current.token));
this.calls.set(token, {
registry,
cleanup: options.cleanup === true || (current && this.calls.get(current.token)?.cleanup) === true
});
return {
token,
release: () => {
this.calls.delete(token);
const timedOut = this.timedOutCalls;
if (timedOut?.remaining.delete(token) && timedOut.remaining.size === 0) {
this.timedOutCalls = void 0;
timedOut.settled.resolve();
}
this.waiters.forEach((wake) => wake());
return joinDisposal && this.calls.size === 0 && !this.controller.signal.aborted ? this.disposal : void 0;
}
};
}
bindModuleLoader(load, hasSource) {
if (this.moduleLoader) throw new Error(`Plugin ${this.pluginId} already owns its module loader`);
this.moduleLoader = load;
this.moduleSourceExists = hasSource;
}
loadModule(source) {
return this.run(() => {
if (!this.moduleLoader) throw new Error(`Plugin ${this.pluginId} has no captured module loader`);
return this.wrap(this.moduleLoader(source));
});
}
hasModuleSource(source) {
return this.moduleSourceExists && this.moduleSourceExists(source);
}
bindModuleLoaderRecovery(capture) {
this.captureModuleRecovery = capture;
}
captureModuleLoaderRecovery() {
if (this.disposing || this.owner?.revoked || this.controller.signal.aborted) throw new PluginInstanceUnavailableError(this.pluginId);
return this.runCleanup(() => {
if (!this.captureModuleRecovery) throw new Error(`Plugin ${this.pluginId} has no recoverable module loader`);
return this.captureModuleRecovery();
});
}
quiesce() {
const accepting = this.accepting;
this.accepting = false;
return accepting;
}
async drain(options) {
this.quiesce();
const ownToken = this.activeCall()?.token;
try {
await this.waitForCalls(ownToken, options?.signal);
if (options?.includeConsumers) await waitForPluginInstanceSettlement(this.pluginId, this.waiters, () => this.consumers.size === 0, options.signal ?? new AbortController().signal);
return { errors: [] };
} catch (error) {
return { errors: [error] };
}
}
async waitForCalls(ownToken, signal) {
const settled = () => [...this.calls.keys()].every((token) => token === ownToken);
if (signal) return waitForPluginInstanceSettlement(this.pluginId, this.waiters, settled, signal);
const deadline = new AbortController();
const timer = setTimeout(() => {
deadline.abort(/* @__PURE__ */ new Error(`Plugin ${this.pluginId} still has active calls after ${SHUTDOWN_TIMEOUT_MS}ms`));
}, SHUTDOWN_TIMEOUT_MS);
try {
await waitForPluginInstanceSettlement(this.pluginId, this.waiters, settled, deadline.signal);
} finally {
clearTimeout(timer);
}
}
get disposing() {
return this.disposal !== void 0;
}
trackTimedOutCalls() {
const { remaining, settled } = this.timedOutCalls ?? {
remaining: /* @__PURE__ */ new Set(),
settled: createDeferredCore()
};
for (const token of this.calls.keys()) remaining.add(token);
if (remaining.size) this.timedOutCalls = {
remaining,
settled
};
else settled.resolve();
return settled.promise;
}
resume() {
this.accepting ||= !this.disposal && !this.controller.signal.aborted && !this.owner?.revoked;
}
dispose(beforeCleanup) {
if (beforeCleanup && this.disposal) return Promise.reject(/* @__PURE__ */ new Error(`Plugin ${this.pluginId} disposal already started`));
if (!this.disposal) {
this.quiesce();
const terminalFailures = this.disposalFailures = new DisposalFailures(this);
const work = new AsyncWorkScope(terminalFailures);
const physical = pluginInvocationContext.exit(() => this.finishDisposal(trackAsyncWork(() => this.runDisposalCleanup(work, terminalFailures, beforeCleanup)), terminalFailures));
const settled = physical.then(() => {
if (terminalFailures.size) throw new AggregateError(terminalFailures, `Plugin ${this.pluginId} cleanup failed`);
});
settled.catch(() => {});
this.disposal = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
const fact = {
activeCallCount: (/* @__PURE__ */ new Set([...this.calls.keys(), ...this.timedOutCalls?.remaining ?? []])).size,
retainedConsumerCount: this.consumers.size
};
this.forcedRetirement = true;
this.trackTimedOutCalls();
for (const [token, call] of this.calls) if (!call.cleanup) this.calls.delete(token);
this.abortDisposal(work);
const error = new PluginInstanceDrainTimeoutError(`Plugin ${this.pluginId} forced retirement after ${SHUTDOWN_TIMEOUT_MS}ms: ${fact.activeCallCount} still-running call(s), ${fact.retainedConsumerCount} retained consumer(s); resource cleanup remains pending.`, settled, {}, fact);
log.warn(error.message);
resolve(terminalFailures.result([error, ...terminalFailures]));
}, SHUTDOWN_TIMEOUT_MS);
physical.then(resolve, reject).finally(() => clearTimeout(timer));
});
this.disposal.catch(() => {});
}
return this.activeCall() ? Promise.resolve({ errors: [] }) : this.disposal;
}
abortDisposal(work) {
if (!this.controller.signal.aborted) pluginInvocationContext.exit(() => work.run(() => this.controller.abort(/* @__PURE__ */ new Error(`Plugin ${this.pluginId} is retiring`))));
}
async runDisposalCleanup(cleanupWork, terminalFailures, beforeCleanup) {
if (this.owner) this.owner.revoked = true;
const failures = [];
let hostFailure;
const runCleanup = (cleanup, hostCleanup = false) => cleanupWork.track(() => this.invoke(cleanup, this.lease({
cleanup: true,
hostCleanup
}), terminalFailures));
try {
await this.waitForCalls();
} catch (error) {
failures.push(new PluginInstanceDrainTimeoutError(formatErrorMessage(error), this.trackTimedOutCalls(), { cause: error }));
}
for (const [token, call] of this.calls) if (!call.cleanup) this.calls.delete(token);
while (this.consumers.size > 0) await Promise.all([...this.consumers.values()].map(({ completion }) => completion));
if (beforeCleanup) try {
await runCleanup(beforeCleanup, true);
} catch (error) {
hostFailure = { error };
}
const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS;
this.abortDisposal(cleanupWork);
const moduleCleanups = [];
for (const [cleanup, kind] of Array.from(this.cleanups).toReversed()) {
if (kind === "module") {
moduleCleanups.push(cleanup);
continue;
}
try {
await raceWithTimeout(runCleanup(cleanup), Math.max(0, deadline - Date.now()), () => {
throw new Error(`Plugin ${this.pluginId} cleanup did not settle`);
});
} catch (error) {
failures.push(error);
}
}
await cleanupWork.drain();
return {
failures,
hostFailure,
moduleCleanups
};
}
async finishDisposal(cleanupCompletion, terminalFailures) {
const { failures, hostFailure, moduleCleanups } = await cleanupCompletion;
await this.timedOutCalls?.settled.promise;
for (const cleanup of moduleCleanups) try {
await this.invoke(cleanup, this.lease({ cleanup: true }));
} catch (error) {
failures.push(error);
terminalFailures.add(error);
}
this.cleanups.clear();
this.calls.clear();
this.waiters.forEach((wake) => wake());
this.moduleLoader = void 0;
this.setupCache = void 0;
this.captureModuleRecovery = void 0;
this.moduleSourceExists &&= false;
this.slots.clear();
for (const failure of terminalFailures) if (!failures.includes(failure)) failures.push(failure);
if (failures.length) log.warn(`Plugin ${this.pluginId} cleanup failed: ${failures.map(formatErrorMessage).join("; ")}`);
if (hostFailure) throw hostFailure.error;
if (failures.length === 0) releasePluginCacheInstance(this);
return terminalFailures.result(failures);
}
};
//#endregion
//#region src/plugins/plugin-setup-module.ts
/** Setup callbacks belong to the inventory that loaded them. */
function getPluginSetupModuleLoader(record, source, rootDir) {
const cache = getPluginCache();
const key = `setup:${record.id}:${source}`;
const cached = cache.setupModules.get(key);
if (!cached && cache.retirement) throw new Error(`Plugin ${record.id} setup inventory has retired`);
const instance = cached ?? new PluginInstance(record.id, { cache });
const discard = () => {
if (instance.controlPlaneInitialized) return;
if (cache.setupModules.get(key) === instance) cache.setupModules.delete(key);
retirePluginCacheInstance(instance, cache).catch(() => {});
};
if (!cached) {
cache.setupModules.set(key, instance);
try {
if (record.origin === "bundled" && isJavaScriptModulePath(source)) {
const distribution = path.dirname(path.dirname(rootDir));
const dependencyRoot = path.basename(path.dirname(rootDir)) === "extensions" && path.basename(distribution) === "dist" ? distribution : rootDir;
getPluginCacheSource(source).disposeModule ??= () => clearPluginModuleRequireCache(source, dependencyRoot);
}
bindPluginInstanceModuleLoader({
instance,
origin: record.origin,
source,
rootDir
});
} catch (error) {
discard();
throw error;
}
}
return Object.assign((entry) => {
try {
return instance.loadModule(entry);
} catch (error) {
discard();
throw error;
}
}, { initialize(run) {
try {
const result = run();
instance.controlPlaneInitialized = true;
return result;
} catch (error) {
discard();
throw error;
}
} });
}
//#endregion
export { fingerprintPluginRuntimeArtifact as a, resolvePluginRuntimeArtifact as c, getSharedPluginCodeReloadWarning as i, PluginInstance as n, loadPluginRuntimeArtifactIdentitySources as o, bindPluginInstanceModuleLoader as r, clearPluginRuntimeArtifactResolutionMemo as s, getPluginSetupModuleLoader as t };