mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-24 15:11:13 +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
226 lines
7.4 KiB
TypeScript
226 lines
7.4 KiB
TypeScript
// Loads documented plugin public surfaces while preserving lazy boundaries.
|
|
import fs from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { openRootFileSync } from "../infra/boundary-file-read.js";
|
|
import { sameFileIdentity } from "../infra/fs-safe-advanced.js";
|
|
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
|
import {
|
|
createPluginModuleLoaderCache,
|
|
getCachedPluginModuleLoader,
|
|
type PluginModuleLoaderCache,
|
|
} from "./plugin-module-loader-cache.js";
|
|
import {
|
|
resolveBundledPluginPublicSurfacePath,
|
|
resolvePluginRootPublicSurfacePath,
|
|
} from "./public-surface-runtime.js";
|
|
import { resolvePluginLoaderTryNative, resolveLoaderPackageRoot } from "./sdk-alias.js";
|
|
|
|
const OPENCLAW_PACKAGE_ROOT =
|
|
resolveLoaderPackageRoot({
|
|
modulePath: fileURLToPath(import.meta.url),
|
|
moduleUrl: import.meta.url,
|
|
}) ?? fileURLToPath(new URL("../..", import.meta.url));
|
|
const publicSurfaceModuleCache = new Map<string, unknown>();
|
|
const sourceArtifactRequire = createRequire(import.meta.url);
|
|
const publicSurfaceLocationCache = new Map<
|
|
string,
|
|
{
|
|
modulePath: string;
|
|
boundaryRoot: string;
|
|
}
|
|
>();
|
|
const moduleLoaders: PluginModuleLoaderCache = createPluginModuleLoaderCache();
|
|
|
|
function isSourceArtifactPath(modulePath: string): boolean {
|
|
switch (path.extname(modulePath).toLowerCase()) {
|
|
case ".ts":
|
|
case ".tsx":
|
|
case ".mts":
|
|
case ".cts":
|
|
case ".mtsx":
|
|
case ".ctsx":
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function canUseSourceArtifactRequire(params: { modulePath: string; tryNative: boolean }): boolean {
|
|
return (
|
|
!params.tryNative &&
|
|
isSourceArtifactPath(params.modulePath) &&
|
|
typeof sourceArtifactRequire.extensions?.[".ts"] === "function"
|
|
);
|
|
}
|
|
|
|
function createResolutionKey(params: { dirName: string; artifactBasename: string }): string {
|
|
const bundledPluginsDir = resolveBundledPluginsDir();
|
|
return `${params.dirName}::${params.artifactBasename}::${bundledPluginsDir ? path.resolve(bundledPluginsDir) : "<default>"}`;
|
|
}
|
|
|
|
function resolvePublicSurfaceLocationUncached(params: {
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
}): { modulePath: string; boundaryRoot: string } | null {
|
|
const bundledPluginsDir = resolveBundledPluginsDir();
|
|
const modulePath = resolveBundledPluginPublicSurfacePath({
|
|
rootDir: OPENCLAW_PACKAGE_ROOT,
|
|
...(bundledPluginsDir ? { bundledPluginsDir, bundledPluginsDirMode: "explicit" as const } : {}),
|
|
dirName: params.dirName,
|
|
artifactBasename: params.artifactBasename,
|
|
});
|
|
if (!modulePath) {
|
|
return null;
|
|
}
|
|
return {
|
|
modulePath,
|
|
boundaryRoot:
|
|
bundledPluginsDir && modulePath.startsWith(path.resolve(bundledPluginsDir) + path.sep)
|
|
? path.resolve(bundledPluginsDir)
|
|
: OPENCLAW_PACKAGE_ROOT,
|
|
};
|
|
}
|
|
|
|
function resolvePublicSurfaceLocation(params: {
|
|
dirName: string;
|
|
artifactBasename: string;
|
|
}): { modulePath: string; boundaryRoot: string } | null {
|
|
const key = createResolutionKey(params);
|
|
const cached = publicSurfaceLocationCache.get(key);
|
|
if (cached) {
|
|
return cached;
|
|
}
|
|
const resolved = resolvePublicSurfaceLocationUncached(params);
|
|
if (resolved) {
|
|
publicSurfaceLocationCache.set(key, resolved);
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
function getModuleLoader(modulePath: string) {
|
|
return getCachedPluginModuleLoader({
|
|
cache: moduleLoaders,
|
|
modulePath,
|
|
importerUrl: import.meta.url,
|
|
preferBuiltDist: true,
|
|
loaderFilename: import.meta.url,
|
|
});
|
|
}
|
|
|
|
function loadPublicSurfaceModule(modulePath: string): unknown {
|
|
const tryNative = resolvePluginLoaderTryNative(modulePath, { preferBuiltDist: true });
|
|
if (canUseSourceArtifactRequire({ modulePath, tryNative })) {
|
|
return sourceArtifactRequire(modulePath);
|
|
}
|
|
return getModuleLoader(modulePath)(modulePath);
|
|
}
|
|
|
|
function loadValidatedPublicSurfaceModule(params: {
|
|
modulePath: string;
|
|
boundaryRoot: string;
|
|
boundaryLabel: string;
|
|
surfaceLabel: string;
|
|
}): object {
|
|
const cached = publicSurfaceModuleCache.get(params.modulePath);
|
|
if (cached) {
|
|
return cached as object;
|
|
}
|
|
|
|
const opened = openRootFileSync({
|
|
absolutePath: params.modulePath,
|
|
rootPath: params.boundaryRoot,
|
|
boundaryLabel: params.boundaryLabel,
|
|
rejectHardlinks: false,
|
|
});
|
|
if (!opened.ok) {
|
|
throw new Error(`Unable to open ${params.surfaceLabel}`, { cause: opened.error });
|
|
}
|
|
const validatedPath = opened.path;
|
|
const validatedStat = opened.stat;
|
|
fs.closeSync(opened.fd);
|
|
|
|
const currentStat = fs.statSync(validatedPath);
|
|
if (!sameFileIdentity(validatedStat, currentStat)) {
|
|
throw new Error(`${params.surfaceLabel} changed after validation`);
|
|
}
|
|
|
|
const sentinel: Record<string, unknown> = {};
|
|
publicSurfaceModuleCache.set(params.modulePath, sentinel);
|
|
publicSurfaceModuleCache.set(validatedPath, sentinel);
|
|
try {
|
|
const loaded = loadPublicSurfaceModule(validatedPath) as object;
|
|
Object.assign(sentinel, loaded);
|
|
return sentinel;
|
|
} catch (error) {
|
|
publicSurfaceModuleCache.delete(params.modulePath);
|
|
publicSurfaceModuleCache.delete(validatedPath);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
artifactBasename: string;
|
|
}): T {
|
|
const location = resolvePublicSurfaceLocation(params);
|
|
if (!location) {
|
|
throw new Error(
|
|
`Unable to resolve bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
|
|
);
|
|
}
|
|
return loadValidatedPublicSurfaceModule({
|
|
modulePath: location.modulePath,
|
|
boundaryRoot: location.boundaryRoot,
|
|
boundaryLabel:
|
|
location.boundaryRoot === OPENCLAW_PACKAGE_ROOT ? "OpenClaw package root" : "plugin root",
|
|
surfaceLabel: `bundled plugin public surface ${params.dirName}/${params.artifactBasename}`,
|
|
}) as T;
|
|
}
|
|
|
|
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic public artifact loaders use caller-supplied module surface types.
|
|
export function loadPluginPublicArtifactModuleSync<T extends object>(params: {
|
|
pluginRoot: string;
|
|
artifactBasename: string;
|
|
}): T {
|
|
const modulePath = resolvePluginRootPublicSurfacePath(params);
|
|
if (!modulePath) {
|
|
throw new Error(
|
|
`Unable to resolve plugin public surface ${params.pluginRoot}/${params.artifactBasename}`,
|
|
);
|
|
}
|
|
return loadValidatedPublicSurfaceModule({
|
|
modulePath,
|
|
boundaryRoot: path.resolve(params.pluginRoot),
|
|
boundaryLabel: "plugin root",
|
|
surfaceLabel: `plugin public surface ${params.artifactBasename}`,
|
|
}) as T;
|
|
}
|
|
|
|
/** Loads the first resolvable bundled public artifact from an ordered candidate list. */
|
|
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Dynamic public artifact loaders use caller-supplied module surface types.
|
|
export function loadBundledPluginPublicArtifactModuleFromCandidatesSync<T extends object>(params: {
|
|
dirName: string;
|
|
artifactCandidates: readonly string[];
|
|
}): T | null {
|
|
for (const artifactBasename of params.artifactCandidates) {
|
|
try {
|
|
return loadBundledPluginPublicArtifactModuleSync<T>({
|
|
dirName: params.dirName,
|
|
artifactBasename,
|
|
});
|
|
} catch (error) {
|
|
if (
|
|
error instanceof Error &&
|
|
error.message.startsWith("Unable to resolve bundled plugin public surface ")
|
|
) {
|
|
continue;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
return null;
|
|
}
|