openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
233 lines (232 loc) • 9.93 kB
JavaScript
import { r as theme } from "./theme-vjDs9tao.js";
import { i as isPathInside } from "./path-BlG8lhgR.js";
import { a as tracePluginLifecyclePhaseAsync } from "./discovery-BDSfxwT6.js";
import { g as shortenHomePath, p as resolveUserPath } from "./utils-CCC-BEJH.js";
import { n as defaultRuntime } from "./runtime-B4lgFmsS.js";
import "./path-guards-CBe_wA_B.js";
import { t as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-CPHayOZI.js";
import { t as buildNpmResolutionFields } from "./install-source-utils-Zb798u54.js";
import { n as recordPluginInstallInRecords, o as withoutPluginInstallRecords } from "./installed-plugin-index-records-DXbAzXDd.js";
import { n as enablePluginInConfig } from "./enable-DXODwa0_.js";
import { i as replaceConfigFile } from "./config-C9RxTsn1.js";
import { n as applyPluginUninstallDirectoryRemoval, o as planPluginUninstall } from "./uninstall-e8GjmqkO.js";
import { s as buildPluginSnapshotReport } from "./status-TB29O9XH.js";
import { i as enableInternalHookEntries, o as logHookPackRestartHint, s as logSlotWarnings, t as applySlotSelectionForPlugin } from "./plugins-command-helpers-BMTwD7oF.js";
import { t as recordHookInstall } from "./installs-DC4E2xLJ.js";
import { r as commitPluginInstallRecordsWithConfig } from "./plugins-install-record-commit--Zbbdxlm.js";
import { t as refreshPluginRegistryAfterConfigMutation } from "./plugins-registry-refresh-D83-QnpV.js";
//#region src/cli/npm-resolution.ts
/** Choose the install-record spec for an npm package, optionally pinning to the resolved version. */
function resolvePinnedNpmSpec(params) {
const recordSpec = params.pin && params.resolvedSpec ? params.resolvedSpec : params.rawSpec;
if (!params.pin) return { recordSpec };
if (!params.resolvedSpec) return {
recordSpec,
pinWarning: "Could not resolve exact npm version for --pin; storing original npm spec."
};
return {
recordSpec,
pinNotice: `Pinned npm install record to ${params.resolvedSpec}.`
};
}
/** Build the npm section of a plugin install record. */
function buildNpmInstallRecordFields(params) {
return {
source: "npm",
spec: params.spec,
installPath: params.installPath,
version: params.version,
...buildNpmResolutionFields(params.resolution)
};
}
/** Resolve and log npm pinning decisions before constructing the persisted install record. */
function resolvePinnedNpmInstallRecord(params) {
const pinInfo = resolvePinnedNpmSpec({
rawSpec: params.rawSpec,
pin: params.pin,
resolvedSpec: params.resolution?.resolvedSpec
});
logPinnedNpmSpecMessages(pinInfo, params.log, params.warn);
return buildNpmInstallRecordFields({
spec: pinInfo.recordSpec,
installPath: params.installPath,
version: params.version,
resolution: params.resolution
});
}
/** CLI adapter for npm install-record pinning with styled warning output. */
function resolvePinnedNpmInstallRecordForCli(rawSpec, pin, installPath, version, resolution, log, warnFormat) {
return resolvePinnedNpmInstallRecord({
rawSpec,
pin,
installPath,
version,
resolution,
log,
warn: (message) => log(warnFormat(message))
});
}
/** Emit any user-facing notice or warning from npm pin resolution. */
function logPinnedNpmSpecMessages(pinInfo, log, logWarn) {
if (pinInfo.pinWarning) logWarn(pinInfo.pinWarning);
if (pinInfo.pinNotice) log(pinInfo.pinNotice);
}
//#endregion
//#region src/cli/plugins-install-persist.ts
function addInstalledPluginToAllowlist(cfg, pluginId) {
const allow = cfg.plugins?.allow;
if (!Array.isArray(allow) || allow.length === 0 || allow.includes(pluginId)) return cfg;
return {
...cfg,
plugins: {
...cfg.plugins,
allow: [...allow, pluginId].toSorted()
}
};
}
function removeInstalledPluginFromDenylist(cfg, pluginId) {
const deny = cfg.plugins?.deny;
if (!Array.isArray(deny) || !deny.includes(pluginId)) return cfg;
const nextDeny = deny.filter((id) => id !== pluginId);
const plugins = {
...cfg.plugins,
...nextDeny.length > 0 ? { deny: nextDeny } : {}
};
if (nextDeny.length === 0) delete plugins.deny;
return {
...cfg,
plugins
};
}
function sourceMatchesInstalledPath(params) {
const activeSource = resolveUserPath(params.activeSource, params.env);
const installedSource = resolveUserPath(params.installedSource, params.env);
return activeSource === installedSource || isPathInside(installedSource, activeSource);
}
function logShadowedNpmInstallWarning(params) {
if (params.install.source !== "npm") return;
const installedSource = params.install.installPath ?? params.install.sourcePath;
if (!installedSource) return;
const active = buildPluginSnapshotReport({
config: params.config,
effectiveOnly: true,
onlyPluginIds: [params.pluginId]
}).plugins.find((plugin) => plugin.id === params.pluginId);
if (!active || active.origin !== "config" || sourceMatchesInstalledPath({
activeSource: active.source,
installedSource
})) return;
params.runtime.log(theme.warn([
`Warning: installed plugin "${params.pluginId}" is not the active source because a config-selected plugin with the same id is currently selected:`,
` active config source: ${shortenHomePath(active.source)}`,
` installed npm source: ${shortenHomePath(installedSource)}`,
"Run `openclaw plugins doctor` for repair options."
].join("\n")));
}
function resolveComparableInstallPath(install) {
return install.installPath ?? install.sourcePath;
}
function shouldPreserveReplacedInstallPath(params) {
const removalTarget = resolveUserPath(params.removalTarget);
const nextInstallPath = resolveUserPath(params.nextInstallPath);
return isPathInside(removalTarget, nextInstallPath) || isPathInside(nextInstallPath, removalTarget);
}
function resolveReplacedManagedInstallRemoval(params) {
if (!params.previousInstall) return null;
const previousInstallPath = resolveComparableInstallPath(params.previousInstall);
const nextInstallPath = resolveComparableInstallPath(params.nextInstall);
if (!previousInstallPath || !nextInstallPath) return null;
if (shouldPreserveReplacedInstallPath({
removalTarget: previousInstallPath,
nextInstallPath
})) return null;
const plan = planPluginUninstall({
config: { plugins: { installs: { [params.pluginId]: params.previousInstall } } },
pluginId: params.pluginId,
deleteFiles: true
});
if (!plan.ok || !plan.directoryRemoval) return null;
if (shouldPreserveReplacedInstallPath({
removalTarget: plan.directoryRemoval.target,
nextInstallPath
})) return null;
return plan.directoryRemoval;
}
async function persistPluginInstall(params) {
const runtime = params.runtime ?? defaultRuntime;
const installConfig = params.enable === false ? params.snapshot.config : removeInstalledPluginFromDenylist(addInstalledPluginToAllowlist(params.snapshot.config, params.pluginId), params.pluginId);
let next = params.enable === false ? installConfig : enablePluginInConfig(installConfig, params.pluginId, { updateChannelConfig: false }).config;
const installRecords = await tracePluginLifecyclePhaseAsync("install records load", () => loadInstalledPluginIndexInstallRecords(), { command: "install" });
const replacedInstallRemoval = resolveReplacedManagedInstallRemoval({
pluginId: params.pluginId,
previousInstall: installRecords[params.pluginId],
nextInstall: params.install
});
const nextInstallRecords = recordPluginInstallInRecords(installRecords, {
pluginId: params.pluginId,
...params.install
});
const slotResult = params.enable === false ? {
config: next,
warnings: []
} : await tracePluginLifecyclePhaseAsync("slot selection", async () => applySlotSelectionForPlugin(next, params.pluginId), {
command: "install",
pluginId: params.pluginId
});
next = withoutPluginInstallRecords(slotResult.config);
await tracePluginLifecyclePhaseAsync("config mutation", () => commitPluginInstallRecordsWithConfig({
previousInstallRecords: installRecords,
nextInstallRecords,
nextConfig: next,
baseHash: params.snapshot.baseHash,
writeOptions: { afterWrite: {
mode: "restart",
reason: "plugin source changed"
} }
}), { command: "install" });
if (replacedInstallRemoval) {
const removalResult = await tracePluginLifecyclePhaseAsync("replaced install cleanup", () => applyPluginUninstallDirectoryRemoval(replacedInstallRemoval), {
command: "install",
pluginId: params.pluginId
});
for (const warning of removalResult.warnings) runtime.log(theme.warn(warning));
if (removalResult.directoryRemoved) runtime.log(theme.muted(`Removed previous plugin install directory: ${shortenHomePath(replacedInstallRemoval.target)}`));
}
await refreshPluginRegistryAfterConfigMutation({
config: next,
reason: "source-changed",
installRecords: nextInstallRecords,
invalidateRuntimeCache: params.invalidateRuntimeCache,
traceCommand: "install",
logger: { warn: (message) => runtime.log(theme.warn(message)) }
});
logSlotWarnings(slotResult.warnings, runtime);
if (params.warningMessage) runtime.log(theme.warn(params.warningMessage));
runtime.log(params.successMessage ?? `Installed plugin: ${params.pluginId}`);
logShadowedNpmInstallWarning({
config: next,
pluginId: params.pluginId,
install: params.install,
runtime
});
runtime.log("Restart the gateway to load plugins.");
return next;
}
async function persistHookPackInstall(params) {
const runtime = params.runtime ?? defaultRuntime;
let next = enableInternalHookEntries(params.snapshot.config, params.hooks);
next = recordHookInstall(next, {
hookId: params.hookPackId,
hooks: params.hooks,
...params.install
});
await replaceConfigFile({
nextConfig: next,
baseHash: params.snapshot.baseHash
});
runtime.log(params.successMessage ?? `Installed hook pack: ${params.hookPackId}`);
logHookPackRestartHint(runtime);
return next;
}
//#endregion
export { resolvePinnedNpmInstallRecordForCli as i, persistPluginInstall as n, buildNpmInstallRecordFields as r, persistHookPackInstall as t };