openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
289 lines (288 loc) • 11.7 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs";
import { a as openRootFile, i as matchRootFileOpenFailure } from "./boundary-file-read-CCth2Hwg.mjs";
import { i as resolveRootPath } from "./boundary-path-CAv_plX7.mjs";
import { a as checkPluginCacheEntry, r as getPackageManifestMetadata, s as pluginCacheExistsSync } from "./package-manifest-CEbdS_C4.mjs";
import { n as isTypeScriptPackageEntry, r as listBuiltRuntimeEntryCandidates } from "./package-entrypoints-DCjwjOnD.mjs";
import "./manifest-BXjFCp0F.mjs";
import fs from "node:fs";
import path from "node:path";
//#region src/plugins/package-entry-resolution.ts
function reportPackageEntryDiagnostic(params, level, message) {
params.diagnostics.push({
level,
...params.pluginIdHint ? { pluginId: params.pluginIdHint } : {},
message,
source: params.sourceLabel
});
return null;
}
function resolvePackageRuntimeExtensionEntries(params) {
const declared = getPackageManifestMetadata(params.manifest ?? void 0)?.runtimeExtensions;
const runtimeExtensions = [];
if (!Array.isArray(declared)) return {
ok: true,
runtimeExtensions
};
for (const [index, entry] of declared.entries()) {
const normalized = normalizeOptionalString(entry);
if (!normalized) return {
ok: false,
error: `package.json openclaw.runtimeExtensions[${index}] must be a non-empty string`
};
runtimeExtensions.push(normalized);
}
if (runtimeExtensions.length > 0 && runtimeExtensions.length !== params.extensions.length) return {
ok: false,
error: `package.json openclaw.runtimeExtensions length (${runtimeExtensions.length}) must match openclaw.extensions length (${params.extensions.length})`
};
return {
ok: true,
runtimeExtensions
};
}
function missingCompiledRuntimeEntryMessage(params) {
const label = params.context === "install" ? "package install" : "installed plugin package";
const recovery = params.context === "install" ? "retry installation after the publisher ships compiled JavaScript" : "update or reinstall the plugin after the publisher ships compiled JavaScript, or disable/uninstall the plugin until then";
return `${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; ${recovery}. 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
};
}
async function validatePackageEntryForInstall(params) {
const sourceEntry = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: params.entry,
label: `${params.entryKind} entry`,
requireExisting: false
});
if (!sourceEntry.ok) return sourceEntry;
if (params.runtimeEntry) {
const runtimeResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: params.runtimeEntry,
label: `runtime ${params.entryKind} entry`,
requireExisting: true
});
return runtimeResult.ok ? { ok: true } : runtimeResult;
}
const builtEntryCandidates = listBuiltRuntimeEntryCandidates(params.entry);
for (const builtEntry of builtEntryCandidates) {
const builtResult = await validatePackageExtensionEntry({
packageDir: params.packageDir,
entry: builtEntry,
label: `inferred runtime ${params.entryKind} entry`,
requireExisting: false
});
if (!builtResult.ok) return builtResult;
if (builtResult.exists) return { ok: true };
}
if (sourceEntry.exists && (!isTypeScriptPackageEntry(params.entry) || params.allowSourceTypeScriptEntries)) return { ok: true };
if (builtEntryCandidates.length > 0) return {
ok: false,
error: missingCompiledRuntimeEntryMessage({
context: "install",
entry: params.entry,
candidates: builtEntryCandidates
})
};
return {
ok: false,
error: `${params.entryKind} entry not found: ${params.entry}`
};
}
/** Validates package extension/setup entries before installing a plugin package. */
async function validatePackageExtensionEntriesForInstall(params) {
const runtimeResolution = resolvePackageRuntimeExtensionEntries(params);
if (!runtimeResolution.ok) return runtimeResolution;
for (const [index, entry] of params.extensions.entries()) {
const result = await validatePackageEntryForInstall({
packageDir: params.packageDir,
entry,
runtimeEntry: runtimeResolution.runtimeExtensions[index],
entryKind: "extension",
allowSourceTypeScriptEntries: params.allowSourceTypeScriptEntries
});
if (!result.ok) return result;
}
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) return await validatePackageEntryForInstall({
packageDir: params.packageDir,
entry: setupEntry,
runtimeEntry: runtimeSetupEntry,
entryKind: "setup",
allowSourceTypeScriptEntries: params.allowSourceTypeScriptEntries
});
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 = checkPluginCacheEntry({
rootDir: params.packageDir,
relativePath: path.relative(params.packageDir, absolutePath),
rootRealPath: params.packageRootRealPath,
rejectHardlinks
});
if (!opened.ok) return matchRootFileOpenFailure(opened, {
path: () => null,
io: () => reportPackageEntryDiagnostic(params, "warn", `extension entry unreadable (I/O error): ${params.entryPath}`),
fallback: () => reportPackageEntryDiagnostic(params, "error", `extension entry escapes package directory: ${params.entryPath}`)
});
return opened.exists ? opened.path : null;
};
if (!rejectHardlinks) {
const builtCandidate = source.replace(/\.[^.]+$/u, ".js");
if (builtCandidate !== source) candidates.push(builtCandidate);
}
for (const candidate of candidates) {
if (!pluginCacheExistsSync(candidate)) continue;
return openCandidate(candidate);
}
return openCandidate(source);
}
function resolveSafePackageEntry(params) {
const absolutePath = path.resolve(params.packageDir, params.entryPath);
if (pluginCacheExistsSync(absolutePath)) {
const existingSource = resolvePackageEntrySource(params);
if (!existingSource) return null;
return {
relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/"),
existingSource
};
}
if (!checkPluginCacheEntry({
rootDir: params.packageDir,
relativePath: params.entryPath,
rootRealPath: params.packageRootRealPath,
rejectHardlinks: params.rejectHardlinks ?? true
}).ok) return reportPackageEntryDiagnostic(params, "error", `extension entry escapes package directory: ${params.entryPath}`);
return { relativePath: path.relative(params.packageDir, absolutePath).replace(/\\/g, "/") };
}
function resolvePackageRuntimeEntrySource(params) {
const safeEntry = resolveSafePackageEntry(params);
if (!safeEntry) return null;
if (params.runtimeEntryPath) {
const runtimeSource = resolvePackageEntrySource({
...params,
entryPath: params.runtimeEntryPath
});
if (runtimeSource) return runtimeSource;
return reportPackageEntryDiagnostic(params, "error", `${params.runtimeEntryLabel ?? "runtime entry"} not found: ${params.runtimeEntryPath}`);
}
if (params.origin === "config" || params.origin === "global") {
const builtEntryCandidates = listBuiltRuntimeEntryCandidates(safeEntry.relativePath);
for (const candidate of builtEntryCandidates) {
if (!pluginCacheExistsSync(path.resolve(params.packageDir, candidate))) continue;
return resolvePackageEntrySource({
...params,
entryPath: candidate
});
}
if ((params.requireBuiltRuntimeEntry ?? params.origin === "global") && isTypeScriptPackageEntry(safeEntry.relativePath)) return reportPackageEntryDiagnostic(params, "warn", missingCompiledRuntimeEntryMessage({
context: "installed",
entry: safeEntry.relativePath,
candidates: builtEntryCandidates
}));
}
if (safeEntry.existingSource) return safeEntry.existingSource;
if (params.rejectHardlinks === false) {
const trustedFallbackSource = resolvePackageEntrySource(params);
if (trustedFallbackSource) return trustedFallbackSource;
}
return reportPackageEntryDiagnostic(params, "error", `${params.sourceEntryLabel ?? "extension entry"} not found: ${safeEntry.relativePath}`);
}
/** 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({
...params,
entryPath: setupEntryPath,
sourceEntryLabel: "setup entry",
runtimeEntryPath: normalizeOptionalString(packageManifest?.runtimeSetupEntry),
runtimeEntryLabel: "runtime setup entry",
pluginIdHint: params.pluginIdHint ?? normalizeOptionalString(packageManifest?.plugin?.id) ?? normalizeOptionalString(packageManifest?.channel?.id)
});
}
/** Resolves runtime extension sources for a plugin package manifest. */
function resolvePackageRuntimeExtensionSources(params) {
return resolvePackageRuntimeExtensions(params).map((entry) => entry.source);
}
/** Keeps declarations paired with their runtime sources when earlier entries cannot resolve. */
function resolvePackageRuntimeExtensions(params) {
const runtimeResolution = resolvePackageRuntimeExtensionEntries(params);
if (!runtimeResolution.ok) {
reportPackageEntryDiagnostic(params, "error", runtimeResolution.error);
return [];
}
return params.extensions.flatMap((entryPath, index) => {
const source = resolvePackageRuntimeEntrySource({
...params,
entryPath,
sourceEntryLabel: "extension entry",
runtimeEntryPath: runtimeResolution.runtimeExtensions[index],
runtimeEntryLabel: "runtime extension entry"
});
return source ? [{
entryPath,
source
}] : [];
});
}
//#endregion
export { validatePackageExtensionEntriesForInstall as i, resolvePackageRuntimeExtensions as n, resolvePackageSetupSource as r, resolvePackageRuntimeExtensionSources as t };