mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
refactor: collapse plugin loader native fallbacks
This commit is contained in:
@@ -123,7 +123,7 @@ OpenClaw's plugin system has four layers:
|
||||
Core decides whether a discovered plugin is enabled, disabled, blocked, or selected for an exclusive slot such as memory.
|
||||
</Step>
|
||||
<Step title="Runtime loading">
|
||||
Native OpenClaw plugins are loaded in-process via jiti and register capabilities into a central registry. Compatible bundles are normalized into registry records without importing runtime code.
|
||||
Native OpenClaw plugins are loaded in-process and register capabilities into a central registry. Packaged JavaScript loads through native `require`; source TypeScript falls back to Jiti. Compatible bundles are normalized into registry records without importing runtime code.
|
||||
</Step>
|
||||
<Step title="Surface consumption">
|
||||
The rest of OpenClaw reads the registry to expose tools, channels, provider setup, hooks, HTTP routes, CLI commands, and services.
|
||||
|
||||
@@ -5,27 +5,22 @@ import {
|
||||
getCachedPluginJitiLoader,
|
||||
type PluginJitiLoaderCache,
|
||||
} from "../../plugins/jiti-loader-cache.js";
|
||||
import { tryNativeRequireJavaScriptModule } from "../../plugins/native-module-require.js";
|
||||
export { isJavaScriptModulePath } from "../../plugins/native-module-require.js";
|
||||
|
||||
function createModuleLoader() {
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
return (modulePath: string, tryNative?: boolean) => {
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
argvEntry: process.argv[1],
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: import.meta.url,
|
||||
tryNative,
|
||||
});
|
||||
};
|
||||
function loadModule(modulePath: string, tryNative?: boolean) {
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
argvEntry: process.argv[1],
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: import.meta.url,
|
||||
tryNative,
|
||||
});
|
||||
}
|
||||
|
||||
let loadModule = createModuleLoader();
|
||||
|
||||
export function resolveCompiledBundledModulePath(modulePath: string): string {
|
||||
const compiledDistModulePath = modulePath.replace(
|
||||
`${path.sep}dist-runtime${path.sep}`,
|
||||
@@ -84,14 +79,5 @@ export function loadChannelPluginModule(params: {
|
||||
}
|
||||
const safePath = opened.path;
|
||||
fs.closeSync(opened.fd);
|
||||
const shouldTryNative = params.shouldTryNativeRequire?.(safePath);
|
||||
if (shouldTryNative) {
|
||||
const nativeModule = tryNativeRequireJavaScriptModule(safePath, {
|
||||
allowWindows: true,
|
||||
});
|
||||
if (nativeModule.ok) {
|
||||
return nativeModule.moduleExport;
|
||||
}
|
||||
}
|
||||
return loadModule(safePath, shouldTryNative)(safePath);
|
||||
return loadModule(safePath, params.shouldTryNativeRequire?.(safePath))(safePath);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { OpenClawConfig } from "../config/types.js";
|
||||
import { asNullableRecord } from "../shared/record-coerce.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import type { PluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { tryNativeRequireJavaScriptModule } from "./native-module-require.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
|
||||
const CONTRACT_API_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
|
||||
@@ -39,20 +38,12 @@ type PluginManifestRegistryRecord = PluginManifestRegistry["plugins"][number];
|
||||
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
function loadPluginDoctorContractModule(modulePath: string): PluginDoctorContractModule {
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
});
|
||||
}
|
||||
|
||||
function loadPluginDoctorContractModule(modulePath: string): PluginDoctorContractModule {
|
||||
const nativeModule = tryNativeRequireJavaScriptModule(modulePath, { allowWindows: true });
|
||||
if (nativeModule.ok) {
|
||||
return nativeModule.moduleExport as PluginDoctorContractModule;
|
||||
}
|
||||
return getJiti(modulePath)(modulePath) as PluginDoctorContractModule;
|
||||
})(modulePath) as PluginDoctorContractModule;
|
||||
}
|
||||
|
||||
function resolveContractApiPath(rootDir: string): string | null {
|
||||
|
||||
@@ -36,8 +36,6 @@ describe("bundled plugin public surface loader", () => {
|
||||
createJiti,
|
||||
}));
|
||||
vi.doMock("./native-module-require.js", () => ({
|
||||
isJavaScriptModulePath: (modulePath: string) =>
|
||||
modulePath.endsWith(".js") || modulePath.endsWith(".mjs") || modulePath.endsWith(".cjs"),
|
||||
tryNativeRequireJavaScriptModule: () => ({
|
||||
ok: true,
|
||||
moduleExport: { marker: "windows-dist-ok" },
|
||||
@@ -118,8 +116,6 @@ describe("bundled plugin public surface loader", () => {
|
||||
createJiti,
|
||||
}));
|
||||
vi.doMock("./native-module-require.js", () => ({
|
||||
isJavaScriptModulePath: (modulePath: string) =>
|
||||
modulePath.endsWith(".js") || modulePath.endsWith(".mjs") || modulePath.endsWith(".cjs"),
|
||||
tryNativeRequireJavaScriptModule: (modulePath: string) => ({
|
||||
ok: true,
|
||||
moduleExport: { marker: path.basename(path.dirname(modulePath)) },
|
||||
@@ -129,7 +125,7 @@ describe("bundled plugin public surface loader", () => {
|
||||
|
||||
const publicSurfaceLoader = await importFreshModule<
|
||||
typeof import("./public-surface-loader.js")
|
||||
>(import.meta.url, "./public-surface-loader.js?scope=shared-bundled-jiti");
|
||||
>(import.meta.url, "./public-surface-loader.js?scope=bundled-native-public-artifacts");
|
||||
const tempRoot = createTempDir();
|
||||
const bundledPluginsDir = path.join(tempRoot, "dist");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
|
||||
|
||||
@@ -6,13 +6,8 @@ import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { sameFileIdentity } from "../infra/file-identity.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import { getCachedPluginJitiLoader, type PluginJitiLoaderCache } from "./jiti-loader-cache.js";
|
||||
import { tryNativeRequireJavaScriptModule } from "./native-module-require.js";
|
||||
import { resolveBundledPluginPublicSurfacePath } from "./public-surface-runtime.js";
|
||||
import {
|
||||
isBundledPluginExtensionPath,
|
||||
resolvePluginLoaderJitiTryNative,
|
||||
resolveLoaderPackageRoot,
|
||||
} from "./sdk-alias.js";
|
||||
import { resolvePluginLoaderJitiTryNative, resolveLoaderPackageRoot } from "./sdk-alias.js";
|
||||
|
||||
const OPENCLAW_PACKAGE_ROOT =
|
||||
resolveLoaderPackageRoot({
|
||||
@@ -29,7 +24,6 @@ const publicSurfaceLocations = new Map<
|
||||
} | null
|
||||
>();
|
||||
const jitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
const sharedBundledPublicSurfaceJitiLoaders: PluginJitiLoaderCache = new Map();
|
||||
|
||||
function isSourceArtifactPath(modulePath: string): boolean {
|
||||
switch (path.extname(modulePath).toLowerCase()) {
|
||||
@@ -95,57 +89,23 @@ function resolvePublicSurfaceLocation(params: {
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, { preferBuiltDist: true });
|
||||
const sharedLoader = getSharedBundledPublicSurfaceJiti(modulePath, tryNative);
|
||||
if (sharedLoader) {
|
||||
return sharedLoader;
|
||||
}
|
||||
const loader = getCachedPluginJitiLoader({
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: jitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
preferBuiltDist: true,
|
||||
jitiFilename: import.meta.url,
|
||||
});
|
||||
return loader;
|
||||
}
|
||||
|
||||
function loadPublicSurfaceModule(modulePath: string): unknown {
|
||||
const tryNative = resolvePluginLoaderJitiTryNative(modulePath, { preferBuiltDist: true });
|
||||
if (tryNative) {
|
||||
const nativeModule = tryNativeRequireJavaScriptModule(modulePath, { allowWindows: true });
|
||||
if (nativeModule.ok) {
|
||||
return nativeModule.moduleExport;
|
||||
}
|
||||
}
|
||||
if (canUseSourceArtifactRequire({ modulePath, tryNative })) {
|
||||
return sourceArtifactRequire(modulePath);
|
||||
}
|
||||
return getJiti(modulePath)(modulePath);
|
||||
}
|
||||
|
||||
function getSharedBundledPublicSurfaceJiti(modulePath: string, tryNative: boolean) {
|
||||
const bundledPluginsDir = resolveBundledPluginsDir();
|
||||
if (
|
||||
!isBundledPluginExtensionPath({
|
||||
modulePath,
|
||||
openClawPackageRoot: OPENCLAW_PACKAGE_ROOT,
|
||||
...(bundledPluginsDir ? { bundledPluginsDir } : {}),
|
||||
})
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const cacheKey = tryNative ? "bundled:native" : "bundled:source";
|
||||
return getCachedPluginJitiLoader({
|
||||
cache: sharedBundledPublicSurfaceJitiLoaders,
|
||||
modulePath,
|
||||
importerUrl: import.meta.url,
|
||||
jitiFilename: import.meta.url,
|
||||
cacheScopeKey: cacheKey,
|
||||
tryNative,
|
||||
});
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic public artifact loaders use caller-supplied module surface types.
|
||||
export function loadBundledPluginPublicArtifactModuleSync<T extends object>(params: {
|
||||
dirName: string;
|
||||
@@ -157,21 +117,16 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
|
||||
`Unable to resolve bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
|
||||
);
|
||||
}
|
||||
const preparedLocation = location;
|
||||
const cached =
|
||||
loadedPublicSurfaceModules.get(location.modulePath) ??
|
||||
loadedPublicSurfaceModules.get(preparedLocation.modulePath);
|
||||
const cached = loadedPublicSurfaceModules.get(location.modulePath);
|
||||
if (cached) {
|
||||
return cached as T;
|
||||
}
|
||||
|
||||
const opened = openBoundaryFileSync({
|
||||
absolutePath: preparedLocation.modulePath,
|
||||
rootPath: preparedLocation.boundaryRoot,
|
||||
absolutePath: location.modulePath,
|
||||
rootPath: location.boundaryRoot,
|
||||
boundaryLabel:
|
||||
preparedLocation.boundaryRoot === OPENCLAW_PACKAGE_ROOT
|
||||
? "OpenClaw package root"
|
||||
: "plugin root",
|
||||
location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "plugin root",
|
||||
rejectHardlinks: true,
|
||||
});
|
||||
if (!opened.ok) {
|
||||
@@ -193,7 +148,6 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
|
||||
|
||||
const sentinel = {} as T;
|
||||
loadedPublicSurfaceModules.set(location.modulePath, sentinel);
|
||||
loadedPublicSurfaceModules.set(preparedLocation.modulePath, sentinel);
|
||||
loadedPublicSurfaceModules.set(validatedPath, sentinel);
|
||||
try {
|
||||
const loaded = loadPublicSurfaceModule(validatedPath) as T;
|
||||
@@ -201,7 +155,6 @@ export function loadBundledPluginPublicArtifactModuleSync<T extends object>(para
|
||||
return sentinel;
|
||||
} catch (error) {
|
||||
loadedPublicSurfaceModules.delete(location.modulePath);
|
||||
loadedPublicSurfaceModules.delete(preparedLocation.modulePath);
|
||||
loadedPublicSurfaceModules.delete(validatedPath);
|
||||
throw error;
|
||||
}
|
||||
@@ -218,5 +171,4 @@ export function resetBundledPluginPublicArtifactLoaderForTest(): void {
|
||||
loadedPublicSurfaceModules.clear();
|
||||
publicSurfaceLocations.clear();
|
||||
jitiLoaders.clear();
|
||||
sharedBundledPublicSurfaceJitiLoaders.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user