openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
105 lines (104 loc) • 4.21 kB
JavaScript
import { o as getPluginCacheRoot, s as getPluginCacheSource } from "./plugin-cache-DGWspMEc.js";
import { n as describeRootFileOpenFailure, s as openRootFileSync } from "./boundary-file-read-uaJcf6X6.js";
import { t as hasErrnoCode } from "./errno-CkbDOfLk.js";
import { o as recordPluginModuleRoot, t as getCachedPluginModuleLoader } from "./plugin-module-loader-cache-lf3kAaBw.js";
import { f as isJavaScriptModulePath, u as PLUGIN_SOURCE_MODULE_EXTENSIONS } from "./sdk-alias-BMTmaiMP.js";
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";
//#region src/channels/plugins/module-loader.ts
/**
* Channel plugin module loader.
*
* Loads JavaScript or source plugin modules through native require or cached TS loaders.
*/
const nodeRequire = createRequire(import.meta.url);
function loadModuleWithJiti(modulePath, rootDir) {
return getCachedPluginModuleLoader({
modulePath,
rootDir,
importerUrl: import.meta.url,
loaderFilename: import.meta.url,
tryNative: false,
cacheScopeKey: "channel-plugin-module-loader"
})(modulePath);
}
function loadModule(modulePath, rootDir) {
const extension = path.extname(modulePath).toLowerCase();
const isSource = PLUGIN_SOURCE_MODULE_EXTENSIONS.includes(extension);
if (!isJavaScriptModulePath(modulePath) && !(isSource && typeof nodeRequire.extensions?.[extension] === "function")) {
if (isSource) return loadModuleWithJiti(modulePath, rootDir);
throw new Error(`channel plugin module must be built JavaScript: ${modulePath}`);
}
try {
return nodeRequire(modulePath);
} catch (error) {
if (isSource) return loadModuleWithJiti(modulePath, rootDir);
throw new Error(`failed to load channel plugin module with native require: ${modulePath}`, { cause: error });
}
}
function resolveSourceModuleCandidates(rootDir, specifier) {
const normalizedSpecifier = specifier.replace(/\\/g, "/");
const resolvedPath = path.resolve(rootDir, normalizedSpecifier);
if (path.extname(resolvedPath)) return [];
return PLUGIN_SOURCE_MODULE_EXTENSIONS.map((extension) => `${resolvedPath}${extension}`);
}
/**
* Resolves a plugin-relative module specifier to an existing candidate path.
*/
function resolveExistingPluginModulePath(rootDir, specifier) {
const artifacts = getPluginCacheRoot(rootDir).artifacts;
const key = `channel-specifier:${specifier}`;
const cached = artifacts.get(key);
if (cached) return cached.modulePath;
const modulePath = resolvePluginModulePath(rootDir, specifier);
artifacts.set(key, {
modulePath,
boundaryRoot: rootDir
});
return modulePath;
}
function resolvePluginModulePath(rootDir, specifier) {
const resolvedPath = path.resolve(rootDir, specifier.replace(/\\/g, "/"));
try {
return nodeRequire.resolve(resolvedPath);
} catch (error) {
if (!hasErrnoCode(error, "MODULE_NOT_FOUND")) throw error;
}
for (const candidate of resolveSourceModuleCandidates(rootDir, specifier)) if (fs.existsSync(candidate)) return candidate;
return resolvedPath;
}
/**
* Loads a channel plugin module after enforcing plugin-root file boundaries.
*
* `rootDir` is always the plugin's own directory, so the containment failure is
* reported against that one root; no caller boundary override exists.
*/
function loadChannelPluginModule(params) {
const source = getPluginCacheSource(params.modulePath);
const key = `channel-plugin-module:${path.resolve(params.rootDir)}`;
const cached = source.variants.get(key)?.exports;
if (cached) return cached.value;
const boundaryLabel = "plugin root";
const opened = openRootFileSync({
absolutePath: params.modulePath,
rootPath: params.rootDir,
boundaryLabel,
rejectHardlinks: false,
skipLexicalRootCheck: true
});
if (!opened.ok) throw new Error(describeRootFileOpenFailure({
failure: opened,
subject: "plugin module path",
boundaryLabel,
filePath: params.modulePath
}), { cause: opened.error });
const safePath = opened.path;
fs.closeSync(opened.fd);
recordPluginModuleRoot(safePath, params.rootDir);
const value = loadModule(safePath, params.rootDir);
source.variants.set(key, { exports: { value } });
return value;
}
//#endregion
export { resolveExistingPluginModulePath as n, loadChannelPluginModule as t };