mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-05 00:51:37 +00:00
* refactor(plugins): trim activation and contract exports * test(plugins): restore fixture cleanup * refactor(plugins): trim install and loader exports * test(plugins): fully reset loader caches * refactor(plugins): trim metadata and catalog exports * test(plugins): preserve catalog trust coverage * refactor(plugins): trim provider and plugin exports * refactor(plugins): trim runtime and tool exports * test(plugins): update dead-export consumers * test(plugins): remove empty dead-export suites * refactor(plugins): align exports with split registry * refactor(plugins): trim drifted loader exports * style(plugins): format test fixtures * refactor(scripts): use supported plugin APIs * refactor(plugins): finish dead export cleanup * chore(deadcode): refresh export baseline * test(cli): mock production memory state * chore(deadcode): sync latest export baseline * fix(tests): keep plugin fixtures inside core * chore(deadcode): refresh rebased export baseline * chore(deadcode): sync current ratchets * fix(plugins): retain reserved slot invariant * fix(plugins): preserve dead-export invariants * test(plugins): use neutral catalog query fixture * test(plugins): satisfy catalog lint * test(plugins): preserve integrity drift coverage * fix(ci): register skill experience live proof
127 lines
3.8 KiB
TypeScript
127 lines
3.8 KiB
TypeScript
// Runtime plugin boundary helpers enforce package and source boundaries for runtime loading.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { expectDefined } from "@openclaw/normalization-core";
|
|
import { getRuntimeConfig } from "../../config/config.js";
|
|
import { loadPluginManifestRegistry } from "../manifest-registry.js";
|
|
import {
|
|
isJavaScriptModulePath,
|
|
tryNativeRequireJavaScriptModule,
|
|
} from "../native-module-require.js";
|
|
import {
|
|
getCachedPluginSourceModuleLoader,
|
|
type PluginModuleLoaderCache,
|
|
} from "../plugin-module-loader-cache.js";
|
|
import type { PluginOrigin } from "../plugin-origin.types.js";
|
|
|
|
type PluginRuntimeRecord = {
|
|
origin?: PluginOrigin;
|
|
rootDir?: string;
|
|
source: string;
|
|
};
|
|
|
|
function readPluginBoundaryConfigSafely() {
|
|
try {
|
|
return getRuntimeConfig();
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
export function resolvePluginRuntimeRecordByEntryBaseNames(
|
|
entryBaseNames: string[],
|
|
onMissing?: () => never,
|
|
): PluginRuntimeRecord | null {
|
|
const manifestRegistry = loadPluginManifestRegistry({
|
|
config: readPluginBoundaryConfigSafely(),
|
|
});
|
|
const matches = manifestRegistry.plugins.filter((plugin) => {
|
|
if (!plugin?.source) {
|
|
return false;
|
|
}
|
|
const record = {
|
|
rootDir: plugin.rootDir,
|
|
source: plugin.source,
|
|
};
|
|
return entryBaseNames.every(
|
|
(entryBaseName) => resolvePluginRuntimeModulePath(record, entryBaseName) !== null,
|
|
);
|
|
});
|
|
if (matches.length === 0) {
|
|
if (onMissing) {
|
|
onMissing();
|
|
}
|
|
return null;
|
|
}
|
|
if (matches.length > 1) {
|
|
const pluginIds = matches.map((plugin) => plugin.id).join(", ");
|
|
throw new Error(
|
|
`plugin runtime boundary is ambiguous for entries [${entryBaseNames.join(", ")}]: ${pluginIds}`,
|
|
);
|
|
}
|
|
const record = expectDefined(matches[0], "matches capture group 0");
|
|
return {
|
|
...(record.origin ? { origin: record.origin } : {}),
|
|
rootDir: record.rootDir,
|
|
source: record.source,
|
|
};
|
|
}
|
|
|
|
export function resolvePluginRuntimeModulePath(
|
|
record: Pick<PluginRuntimeRecord, "rootDir" | "source">,
|
|
entryBaseName: string,
|
|
onMissing?: () => never,
|
|
): string | null {
|
|
const candidates = [
|
|
path.join(path.dirname(record.source), `${entryBaseName}.js`),
|
|
path.join(path.dirname(record.source), `${entryBaseName}.ts`),
|
|
...(record.rootDir
|
|
? [
|
|
path.join(record.rootDir, `${entryBaseName}.js`),
|
|
path.join(record.rootDir, `${entryBaseName}.ts`),
|
|
]
|
|
: []),
|
|
];
|
|
for (const candidate of candidates) {
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
if (onMissing) {
|
|
onMissing();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getPluginBoundarySourceLoader(modulePath: string, loaders: PluginModuleLoaderCache) {
|
|
return getCachedPluginSourceModuleLoader({
|
|
cache: loaders,
|
|
modulePath,
|
|
importerUrl: import.meta.url,
|
|
loaderFilename: import.meta.url,
|
|
});
|
|
}
|
|
|
|
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic plugin boundary loaders use caller-supplied module types.
|
|
export function loadPluginBoundaryModule<TModule>(
|
|
modulePath: string,
|
|
loaders: PluginModuleLoaderCache,
|
|
options: { origin?: PluginOrigin } = {},
|
|
): TModule {
|
|
if (isJavaScriptModulePath(modulePath)) {
|
|
const native = tryNativeRequireJavaScriptModule(modulePath, {
|
|
allowWindows: true,
|
|
fallbackOnNativeError: options.origin !== "bundled",
|
|
});
|
|
if (native.ok) {
|
|
return native.moduleExport as TModule;
|
|
}
|
|
if (options.origin === "bundled") {
|
|
throw new Error(`bundled plugin runtime module must load natively: ${modulePath}`);
|
|
}
|
|
} else if (options.origin === "bundled") {
|
|
throw new Error(`bundled plugin runtime module must be built JavaScript: ${modulePath}`);
|
|
}
|
|
|
|
return getPluginBoundarySourceLoader(modulePath, loaders)(modulePath) as TModule;
|
|
}
|