openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
526 lines (525 loc) • 19.2 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js";
import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js";
import { i as resolveRootPathSync, r as resolveRootPath } from "./root-path-BgCKz8X4.js";
import { i as openRootFileSync, n as matchRootFileOpenFailure, r as openRootFile } from "./root-file-jRMCpJW4.js";
import "./boundary-file-read-CBe_wA_B.js";
import { r as getPackageManifestMetadata } from "./manifest-BcY4wyAU.js";
import "./boundary-path-CBe_wA_B.js";
import fs from "node:fs";
import path from "node:path";
//#region src/plugins/package-compat.ts
/** Resolves the plugin API compatibility range declared by package metadata. */
function resolvePackagePluginApiRange(packageMetadata) {
if (packageMetadata === void 0 || packageMetadata === null) return { ok: true };
if (!isRecord(packageMetadata)) return { ok: true };
if (!("compat" in packageMetadata)) return { ok: true };
const compat = packageMetadata.compat;
if (compat === void 0 || compat === null) return { ok: true };
if (!isRecord(compat)) return {
ok: false,
error: "package.json openclaw.compat must be an object"
};
if (!("pluginApi" in compat)) return { ok: true };
const pluginApi = compat.pluginApi;
if (typeof pluginApi !== "string") return {
ok: false,
error: "package.json openclaw.compat.pluginApi must be a string"
};
const range = pluginApi.trim();
if (!range) return {
ok: false,
error: "package.json openclaw.compat.pluginApi must not be empty"
};
return {
ok: true,
range
};
}
//#endregion
//#region src/plugins/package-entrypoints.ts
/** True when a package entrypoint needs built JavaScript candidates. */
function isTypeScriptPackageEntry(entryPath) {
return [
".ts",
".mts",
".cts"
].includes(path.extname(entryPath).toLowerCase());
}
/** Lists built runtime entry candidates for a TypeScript package entrypoint. */
function listBuiltRuntimeEntryCandidates(entryPath) {
if (!isTypeScriptPackageEntry(entryPath)) return [];
const normalized = entryPath.replace(/\\/g, "/");
const withoutExtension = normalized.replace(/\.[^.]+$/u, "");
const normalizedRelative = normalized.replace(/^\.\//u, "");
const distWithoutExtension = normalizedRelative.startsWith("src/") ? `./dist/${normalizedRelative.slice(4).replace(/\.[^.]+$/u, "")}` : `./dist/${withoutExtension.replace(/^\.\//u, "")}`;
const withJavaScriptExtensions = (basePath) => [
`${basePath}.js`,
`${basePath}.mjs`,
`${basePath}.cjs`
];
return uniqueStrings([...withJavaScriptExtensions(distWithoutExtension), ...withJavaScriptExtensions(withoutExtension)]).filter((candidate) => candidate !== normalized);
}
//#endregion
//#region src/plugins/package-entry-resolution.ts
function runtimeExtensionsLengthMismatchMessage(params) {
return `package.json openclaw.runtimeExtensions length (${params.runtimeExtensionsLength}) must match openclaw.extensions length (${params.extensionsLength})`;
}
function readPackageManifestStringList(params) {
if (!Array.isArray(params.value)) return {
ok: true,
entries: []
};
const entries = [];
for (const [index, entry] of params.value.entries()) {
const normalized = normalizeOptionalString(entry);
if (!normalized) return {
ok: false,
error: `package.json ${params.fieldName}[${index}] must be a non-empty string`
};
entries.push(normalized);
}
return {
ok: true,
entries
};
}
function resolvePackageRuntimeExtensionEntries(params) {
const runtimeExtensionsResult = readPackageManifestStringList({
fieldName: "openclaw.runtimeExtensions",
value: getPackageManifestMetadata(params.manifest ?? void 0)?.runtimeExtensions
});
if (!runtimeExtensionsResult.ok) return runtimeExtensionsResult;
const runtimeExtensions = runtimeExtensionsResult.entries;
if (runtimeExtensions.length === 0) return {
ok: true,
runtimeExtensions: []
};
if (runtimeExtensions.length !== params.extensions.length) return {
ok: false,
error: runtimeExtensionsLengthMismatchMessage({
runtimeExtensionsLength: runtimeExtensions.length,
extensionsLength: params.extensions.length
})
};
return {
ok: true,
runtimeExtensions
};
}
function missingCompiledRuntimeEntryMessage(params) {
return `${params.label} requires compiled runtime output for TypeScript entry ${params.entry}: expected ${params.candidates.join(", ")}. This is a plugin packaging issue, not a local config problem; update or reinstall the plugin after the publisher ships compiled JavaScript, or disable/uninstall the plugin until then. TypeScript source fallback is only supported for source checkouts and local development paths.`;
}
async function validatePackageExtensionEntry(params) {
const absolutePath = path.resolve(params.packageDir, params.entry);
try {
if (!(await resolveRootPath({
absolutePath,
rootPath: params.packageDir,
boundaryLabel: "plugin package directory"
})).exists) return params.requireExisting ? {
ok: false,
error: `${params.label} not found: ${params.entry}`
} : {
ok: true,
exists: false
};
} catch {
return {
ok: false,
error: `${params.label} escapes plugin directory: ${params.entry}`
};
}
const opened = await openRootFile({
absolutePath,
rootPath: params.packageDir,
boundaryLabel: "plugin package directory"
});
if (!opened.ok) return matchRootFileOpenFailure(opened, {
path: () => ({
ok: false,
error: `${params.label} not found: ${params.entry}`
}),
io: () => ({
ok: false,
error: `${params.label} unreadable: ${params.entry}`
}),
validation: () => ({
ok: false,
error: `${params.label} failed plugin directory boundary checks: ${params.entry}`
}),
fallback: () => ({
ok: false,
error: `${params.label} failed plugin directory boundary checks: ${params.entry}`
})
});
fs.closeSync(opened.fd);
return {
ok: true,
exists: true
};
}
/** Validates package extension/setup entries before installing a plugin package. */
async function validatePackageExtensionEntriesForInstall(params) {
const runtimeResolution = resolvePackageRuntimeExtensionEntries({
manifest: params.manifest,
extensions: params.extensions
});
if (!runtimeResolution.ok) return runtimeResolution;
for (const [index, entry] of params.extensions.entries()) {
const sourceEntry = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry,
label: "extension entry",
requireExisting: false
});
if (!sourceEntry.ok) return sourceEntry;
const runtimeEntry = runtimeResolution.runtimeExtensions[index];
if (runtimeEntry) {
const runtimeResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: runtimeEntry,
label: "runtime extension entry",
requireExisting: true
});
if (!runtimeResult.ok) return runtimeResult;
continue;
}
let foundBuiltEntry = false;
const builtEntryCandidates = listBuiltRuntimeEntryCandidates(entry);
for (const builtEntry of builtEntryCandidates) {
const builtResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: builtEntry,
label: "inferred runtime extension entry",
requireExisting: false
});
if (!builtResult.ok) return builtResult;
if (builtResult.exists) {
foundBuiltEntry = true;
break;
}
}
if (foundBuiltEntry) continue;
if (sourceEntry.exists && isTypeScriptPackageEntry(entry) && params.allowSourceTypeScriptEntries) continue;
if (sourceEntry.exists && isTypeScriptPackageEntry(entry)) return {
ok: false,
error: missingCompiledRuntimeEntryMessage({
label: "package install",
entry,
candidates: builtEntryCandidates
})
};
if (sourceEntry.exists) continue;
if (builtEntryCandidates.length > 0) return {
ok: false,
error: missingCompiledRuntimeEntryMessage({
label: "package install",
entry,
candidates: builtEntryCandidates
})
};
return {
ok: false,
error: `extension entry not found: ${entry}`
};
}
const packageManifest = getPackageManifestMetadata(params.manifest);
const setupEntry = normalizeOptionalString(packageManifest?.setupEntry);
const runtimeSetupEntry = normalizeOptionalString(packageManifest?.runtimeSetupEntry);
if (runtimeSetupEntry && !setupEntry) return {
ok: false,
error: "package.json openclaw.runtimeSetupEntry requires openclaw.setupEntry"
};
if (setupEntry) {
const sourceEntry = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: setupEntry,
label: "setup entry",
requireExisting: false
});
if (!sourceEntry.ok) return sourceEntry;
if (runtimeSetupEntry) {
const runtimeResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: runtimeSetupEntry,
label: "runtime setup entry",
requireExisting: true
});
if (!runtimeResult.ok) return runtimeResult;
return { ok: true };
}
let foundBuiltSetupEntry = false;
const builtSetupCandidates = listBuiltRuntimeEntryCandidates(setupEntry);
for (const builtEntry of builtSetupCandidates) {
const builtResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: builtEntry,
label: "inferred runtime setup entry",
requireExisting: false
});
if (!builtResult.ok) return builtResult;
if (builtResult.exists) {
foundBuiltSetupEntry = true;
break;
}
}
if (foundBuiltSetupEntry) return { ok: true };
if (sourceEntry.exists && isTypeScriptPackageEntry(setupEntry) && params.allowSourceTypeScriptEntries) return { ok: true };
if (sourceEntry.exists && isTypeScriptPackageEntry(setupEntry)) return {
ok: false,
error: missingCompiledRuntimeEntryMessage({
label: "package install",
entry: setupEntry,
candidates: builtSetupCandidates
})
};
if (sourceEntry.exists) return { ok: true };
if (builtSetupCandidates.length > 0) return {
ok: false,
error: missingCompiledRuntimeEntryMessage({
label: "package install",
entry: setupEntry,
candidates: builtSetupCandidates
})
};
return {
ok: false,
error: `setup entry not found: ${setupEntry}`
};
}
return { ok: true };
}
function resolvePackageEntrySource(params) {
const source = path.resolve(params.packageDir, params.entryPath);
const rejectHardlinks = params.rejectHardlinks ?? true;
const candidates = [source];
const openCandidate = (absolutePath) => {
const opened = openRootFileSync({
absolutePath,
rootPath: params.packageDir,
...params.packageRootRealPath !== void 0 ? { rootRealPath: params.packageRootRealPath } : {},
boundaryLabel: "plugin package directory",
rejectHardlinks
});
if (!opened.ok) return matchRootFileOpenFailure(opened, {
path: () => null,
io: () => {
params.diagnostics.push({
level: "warn",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: `extension entry unreadable (I/O error): ${params.entryPath}`,
source: params.sourceLabel
});
return null;
},
fallback: () => {
params.diagnostics.push({
level: "error",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: `extension entry escapes package directory: ${params.entryPath}`,
source: params.sourceLabel
});
return null;
}
});
const safeSource = opened.path;
fs.closeSync(opened.fd);
return safeSource;
};
if (!rejectHardlinks) {
const builtCandidate = source.replace(/\.[^.]+$/u, ".js");
if (builtCandidate !== source) candidates.push(builtCandidate);
}
for (const candidate of new Set(candidates)) {
if (!fs.existsSync(candidate)) continue;
return openCandidate(candidate);
}
return openCandidate(source);
}
function shouldInferBuiltRuntimeEntry(origin) {
return origin === "config" || origin === "global";
}
function shouldRequireBuiltRuntimeEntry(origin) {
return origin === "global";
}
function resolveSafePackageEntry(params) {
const absolutePath = path.resolve(params.packageDir, params.entryPath);
if (fs.existsSync(absolutePath)) {
const existingSource = resolvePackageEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: params.entryPath,
pluginIdHint: params.pluginIdHint,
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
if (!existingSource) return null;
return {
relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/"),
existingSource
};
}
try {
resolveRootPathSync({
absolutePath,
rootPath: params.packageDir,
...params.packageRootRealPath !== void 0 ? { rootCanonicalPath: params.packageRootRealPath } : {},
boundaryLabel: "plugin package directory"
});
} catch {
params.diagnostics.push({
level: "error",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: `extension entry escapes package directory: ${params.entryPath}`,
source: params.sourceLabel
});
return null;
}
return { relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/") };
}
function resolveOptionalExistingPackageEntrySource(params) {
const source = path.resolve(params.packageDir, params.entryPath);
if (!fs.existsSync(source)) return { status: "missing" };
const resolved = resolvePackageEntrySource(params);
return resolved ? {
status: "resolved",
source: resolved
} : { status: "invalid" };
}
function resolvePackageRuntimeEntrySource(params) {
const safeEntry = resolveSafePackageEntry({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: params.entryPath,
pluginIdHint: params.pluginIdHint,
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
if (!safeEntry) return null;
if (params.runtimeEntryPath) {
const runtimeSource = resolvePackageEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: params.runtimeEntryPath,
pluginIdHint: params.pluginIdHint,
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
if (runtimeSource) return runtimeSource;
params.diagnostics.push({
level: "error",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: `${params.runtimeEntryLabel ?? "runtime entry"} not found: ${params.runtimeEntryPath}`,
source: params.sourceLabel
});
return null;
}
if (shouldInferBuiltRuntimeEntry(params.origin)) {
const builtEntryCandidates = listBuiltRuntimeEntryCandidates(safeEntry.relativePath);
for (const candidate of builtEntryCandidates) {
const runtimeSource = resolveOptionalExistingPackageEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: candidate,
pluginIdHint: params.pluginIdHint,
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
if (runtimeSource.status === "resolved") return runtimeSource.source;
if (runtimeSource.status === "invalid") return null;
}
if ((params.requireBuiltRuntimeEntry ?? shouldRequireBuiltRuntimeEntry(params.origin)) && isTypeScriptPackageEntry(safeEntry.relativePath)) {
params.diagnostics.push({
level: "warn",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: missingCompiledRuntimeEntryMessage({
label: "installed plugin package",
entry: safeEntry.relativePath,
candidates: builtEntryCandidates
}),
source: params.sourceLabel
});
return null;
}
}
if (safeEntry.existingSource) return safeEntry.existingSource;
if (params.rejectHardlinks === false) {
const trustedFallbackSource = resolvePackageEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: params.entryPath,
pluginIdHint: params.pluginIdHint,
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
if (trustedFallbackSource) return trustedFallbackSource;
}
params.diagnostics.push({
level: "error",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: `${params.sourceEntryLabel ?? "extension entry"} not found: ${safeEntry.relativePath}`,
source: params.sourceLabel
});
return null;
}
/** Resolves the runtime setup source for a plugin package manifest. */
function resolvePackageSetupSource(params) {
const packageManifest = getPackageManifestMetadata(params.manifest ?? void 0);
const setupEntryPath = normalizeOptionalString(packageManifest?.setupEntry);
if (!setupEntryPath) return null;
return resolvePackageRuntimeEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath: setupEntryPath,
sourceEntryLabel: "setup entry",
runtimeEntryPath: normalizeOptionalString(packageManifest?.runtimeSetupEntry),
runtimeEntryLabel: "runtime setup entry",
pluginIdHint: packageManifest?.plugin?.id ?? packageManifest?.channel?.id,
origin: params.origin,
...params.requireBuiltRuntimeEntry !== void 0 ? { requireBuiltRuntimeEntry: params.requireBuiltRuntimeEntry } : {},
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
}
/** Resolves runtime extension sources for a plugin package manifest. */
function resolvePackageRuntimeExtensionSources(params) {
const runtimeResolution = resolvePackageRuntimeExtensionEntries({
manifest: params.manifest,
extensions: params.extensions
});
if (!runtimeResolution.ok) {
params.diagnostics.push({
level: "error",
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message: runtimeResolution.error,
source: params.sourceLabel
});
return [];
}
return params.extensions.flatMap((entryPath, index) => {
const source = resolvePackageRuntimeEntrySource({
packageDir: params.packageDir,
...params.packageRootRealPath !== void 0 ? { packageRootRealPath: params.packageRootRealPath } : {},
entryPath,
sourceEntryLabel: "extension entry",
runtimeEntryPath: runtimeResolution.runtimeExtensions[index],
runtimeEntryLabel: "runtime extension entry",
pluginIdHint: params.pluginIdHint,
origin: params.origin,
...params.requireBuiltRuntimeEntry !== void 0 ? { requireBuiltRuntimeEntry: params.requireBuiltRuntimeEntry } : {},
sourceLabel: params.sourceLabel,
diagnostics: params.diagnostics,
rejectHardlinks: params.rejectHardlinks
});
return source ? [source] : [];
});
}
//#endregion
export { resolvePackagePluginApiRange as i, resolvePackageSetupSource as n, validatePackageExtensionEntriesForInstall as r, resolvePackageRuntimeExtensionSources as t };