refactor: untangle whatsapp runtime boundary

This commit is contained in:
Peter Steinberger
2026-03-19 03:12:16 +00:00
parent 510f4276b5
commit 30a94dfd3b
33 changed files with 848 additions and 474 deletions

View File

@@ -8,7 +8,6 @@ import { isChannelConfigured } from "../config/plugin-auto-enable.js";
import type { PluginInstallRecord } from "../config/types.plugins.js";
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
import { resolveOpenClawPackageRootSync } from "../infra/openclaw-root.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveUserPath } from "../utils.js";
import { inspectBundleMcpRuntimeSupport } from "./bundle-mcp.js";
@@ -31,6 +30,17 @@ import { setActivePluginRegistry } from "./runtime.js";
import type { CreatePluginRuntimeOptions } from "./runtime/index.js";
import type { PluginRuntime } from "./runtime/types.js";
import { validateJsonSchemaValue } from "./schema-validator.js";
import {
buildPluginLoaderJitiOptions,
listPluginSdkAliasCandidates,
listPluginSdkExportedSubpaths,
resolveLoaderPackageRoot,
resolvePluginSdkAliasCandidateOrder,
resolvePluginSdkAliasFile,
resolvePluginSdkScopedAliasMap,
shouldPreferNativeJiti,
type LoaderModuleResolveParams,
} from "./sdk-alias.js";
import type {
OpenClawPluginDefinition,
OpenClawPluginModule,
@@ -90,130 +100,13 @@ export function clearPluginLoaderCache(): void {
const defaultLogger = () => createSubsystemLogger("plugins");
type PluginSdkAliasCandidateKind = "dist" | "src";
type LoaderModuleResolveParams = {
modulePath?: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
};
function resolveLoaderModulePath(params: LoaderModuleResolveParams = {}): string {
return params.modulePath ?? fileURLToPath(params.moduleUrl ?? import.meta.url);
}
function resolveLoaderPackageRoot(
params: LoaderModuleResolveParams & { modulePath: string },
): string | null {
const cwd = params.cwd ?? path.dirname(params.modulePath);
const fromModulePath = resolveOpenClawPackageRootSync({ cwd });
if (fromModulePath) {
return fromModulePath;
}
const argv1 = params.argv1 ?? process.argv[1];
const moduleUrl = params.moduleUrl ?? (params.modulePath ? undefined : import.meta.url);
return resolveOpenClawPackageRootSync({
cwd,
...(argv1 ? { argv1 } : {}),
...(moduleUrl ? { moduleUrl } : {}),
});
}
function resolvePluginSdkAliasCandidateOrder(params: {
modulePath: string;
isProduction: boolean;
}): PluginSdkAliasCandidateKind[] {
const normalizedModulePath = params.modulePath.replace(/\\/g, "/");
const isDistRuntime = normalizedModulePath.includes("/dist/");
return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"];
}
function listPluginSdkAliasCandidates(params: {
srcFile: string;
distFile: string;
modulePath: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
}) {
const orderedKinds = resolvePluginSdkAliasCandidateOrder({
modulePath: params.modulePath,
isProduction: process.env.NODE_ENV === "production",
});
const packageRoot = resolveLoaderPackageRoot(params);
if (packageRoot) {
const candidateMap = {
src: path.join(packageRoot, "src", "plugin-sdk", params.srcFile),
dist: path.join(packageRoot, "dist", "plugin-sdk", params.distFile),
} as const;
return orderedKinds.map((kind) => candidateMap[kind]);
}
let cursor = path.dirname(params.modulePath);
const candidates: string[] = [];
for (let i = 0; i < 6; i += 1) {
const candidateMap = {
src: path.join(cursor, "src", "plugin-sdk", params.srcFile),
dist: path.join(cursor, "dist", "plugin-sdk", params.distFile),
} as const;
for (const kind of orderedKinds) {
candidates.push(candidateMap[kind]);
}
const parent = path.dirname(cursor);
if (parent === cursor) {
break;
}
cursor = parent;
}
return candidates;
}
const resolvePluginSdkAliasFile = (params: {
srcFile: string;
distFile: string;
modulePath?: string;
argv1?: string;
cwd?: string;
moduleUrl?: string;
}): string | null => {
try {
const modulePath = resolveLoaderModulePath(params);
for (const candidate of listPluginSdkAliasCandidates({
srcFile: params.srcFile,
distFile: params.distFile,
modulePath,
argv1: params.argv1,
cwd: params.cwd,
moduleUrl: params.moduleUrl,
})) {
if (fs.existsSync(candidate)) {
return candidate;
}
}
} catch {
// ignore
}
return null;
};
const resolvePluginSdkAlias = (): string | null =>
resolvePluginSdkAliasFile({ srcFile: "root-alias.cjs", distFile: "root-alias.cjs" });
function buildPluginLoaderJitiOptions(aliasMap: Record<string, string>) {
return {
interopDefault: true,
// Prefer Node's native sync ESM loader for built dist/*.js modules so
// bundled plugins and plugin-sdk subpaths stay on the canonical module graph.
tryNative: true,
extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"],
...(Object.keys(aliasMap).length > 0
? {
alias: aliasMap,
}
: {}),
};
}
function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}): string | null {
try {
const modulePath = resolveLoaderModulePath(params);
@@ -243,63 +136,6 @@ function resolvePluginRuntimeModulePath(params: LoaderModuleResolveParams = {}):
return null;
}
const cachedPluginSdkExportedSubpaths = new Map<string, string[]>();
function listPluginSdkExportedSubpaths(params: { modulePath?: string } = {}): string[] {
const modulePath = params.modulePath ?? fileURLToPath(import.meta.url);
const packageRoot = resolveOpenClawPackageRootSync({
cwd: path.dirname(modulePath),
});
if (!packageRoot) {
return [];
}
const cached = cachedPluginSdkExportedSubpaths.get(packageRoot);
if (cached) {
return cached;
}
try {
const pkgRaw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8");
const pkg = JSON.parse(pkgRaw) as {
exports?: Record<string, unknown>;
};
const subpaths = Object.keys(pkg.exports ?? {})
.filter((key) => key.startsWith("./plugin-sdk/"))
.map((key) => key.slice("./plugin-sdk/".length))
.filter((subpath) => Boolean(subpath) && !subpath.includes("/"))
.toSorted();
cachedPluginSdkExportedSubpaths.set(packageRoot, subpaths);
return subpaths;
} catch {
return [];
}
}
const resolvePluginSdkScopedAliasMap = (): Record<string, string> => {
const aliasMap: Record<string, string> = {};
for (const subpath of listPluginSdkExportedSubpaths()) {
const resolved = resolvePluginSdkAliasFile({
srcFile: `${subpath}.ts`,
distFile: `${subpath}.js`,
});
if (resolved) {
aliasMap[`openclaw/plugin-sdk/${subpath}`] = resolved;
}
}
return aliasMap;
};
function shouldPreferNativeJiti(modulePath: string): boolean {
switch (path.extname(modulePath).toLowerCase()) {
case ".js":
case ".mjs":
case ".cjs":
case ".json":
return true;
default:
return false;
}
}
export const __testing = {
buildPluginLoaderJitiOptions,
listPluginSdkAliasCandidates,