From dabddb21655c45cbdac0f2b8f31f08b0db1c35fd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 1 May 2026 22:03:18 +0100 Subject: [PATCH] refactor: collapse plugin loader native fallbacks --- docs/plugins/architecture.md | 2 +- src/channels/plugins/module-loader.ts | 38 +++++--------- src/plugins/doctor-contract-registry.ts | 13 +---- src/plugins/public-surface-loader.test.ts | 6 +-- src/plugins/public-surface-loader.ts | 60 +++-------------------- 5 files changed, 22 insertions(+), 97 deletions(-) diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md index d17a16242f6..ff5a4f70126 100644 --- a/docs/plugins/architecture.md +++ b/docs/plugins/architecture.md @@ -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. - 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. The rest of OpenClaw reads the registry to expose tools, channels, provider setup, hooks, HTTP routes, CLI commands, and services. diff --git a/src/channels/plugins/module-loader.ts b/src/channels/plugins/module-loader.ts index ec3dfa51c37..4cd509318f4 100644 --- a/src/channels/plugins/module-loader.ts +++ b/src/channels/plugins/module-loader.ts @@ -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); } diff --git a/src/plugins/doctor-contract-registry.ts b/src/plugins/doctor-contract-registry.ts index a2e645c3562..58f70eeaa32 100644 --- a/src/plugins/doctor-contract-registry.ts +++ b/src/plugins/doctor-contract-registry.ts @@ -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 { diff --git a/src/plugins/public-surface-loader.test.ts b/src/plugins/public-surface-loader.test.ts index 454a0f80fd8..78ad568e9b2 100644 --- a/src/plugins/public-surface-loader.test.ts +++ b/src/plugins/public-surface-loader.test.ts @@ -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; diff --git a/src/plugins/public-surface-loader.ts b/src/plugins/public-surface-loader.ts index 96e2b838367..798fa922893 100644 --- a/src/plugins/public-surface-loader.ts +++ b/src/plugins/public-surface-loader.ts @@ -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(params: { dirName: string; @@ -157,21 +117,16 @@ export function loadBundledPluginPublicArtifactModuleSync(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(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(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(); }