openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
384 lines (383 loc) • 18 kB
JavaScript
import { t as expectDefined } from "./expect-lbe3Hgrh.mjs";
import { y as uniqueStrings } from "./string-normalization-YMwDjufr.mjs";
import { p as shortenHomePath } from "./utils-DUAwk2p5.mjs";
import { n as ok, t as err } from "./result-BQGgYouL.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-oFV7TqIg.mjs";
import { t as createConfigIO } from "./io.factory-SFi7FTm2.mjs";
import { i as loadInstalledPluginIndex } from "./installed-plugin-index-DyE_yB6z.mjs";
import { c as tracePluginLifecyclePhaseAsync, s as tracePluginLifecyclePhase } from "./discovery-TE84EnHS.mjs";
import { a as resolveDefaultPluginExtensionsDir } from "./install-paths-CQIvSOuc.mjs";
import { t as parseClawHubPluginSpec } from "./clawhub-spec-BL1Xg7rK.mjs";
import { r as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader--X-E60FV.mjs";
import { x as withConfigWriteLock } from "./io.warnings-CCkqmvnF.mjs";
import { r as assertConfigWriteAllowedInCurrentMode } from "./config-write-guard-BXUoQ8wS.mjs";
import { t as createInstalledPluginOwnershipResolver } from "./installed-plugin-package-ownership-CTxkgTfv.mjs";
import { t as ManagedPluginLifecycleError } from "./management-lifecycle-error-ySeX1uI2.mjs";
import { u as readConfigFileSnapshotForWrite } from "./io.runtime-FunTy0y1.mjs";
import { o as transformConfigFileWithRetry, r as replaceConfigFile } from "./mutate-cU6JOWHQ.mjs";
import "./config-B-SAEPyK.mjs";
import { i as withPluginLifecycleLease } from "./plugin-lifecycle-lease-DBN8cRG-.mjs";
import { a as withoutPluginInstallRecords, i as withPluginInstallRecords, r as removePluginInstallRecordFromRecords } from "./installed-plugin-index-records-CH0xW_k-.mjs";
import { t as createInstalledPluginIndexScopeLookup } from "./installed-plugin-index-scope-lookup-By4QTjbZ.mjs";
import { r as withClawPackageLifecycleLease } from "./claw-package-lifecycle-lease-pD5SY3Kg.mjs";
import { a as prepareConfigForDisabledPluginSet, i as pluginUninstallTargetExists, n as formatUninstallActionLabels, o as recordPluginPackageUninstallPlan, r as planPluginUninstall, t as applyPluginUninstallDirectoryRemoval } from "./uninstall-DVuFopPP.mjs";
import { i as commitPluginInstallRecordsWithConfig } from "./install-record-commit-DlDVffVb.mjs";
import { n as refreshPluginRegistryAfterConfigMutation } from "./registry-refresh-DdNitlgH.mjs";
import { i as selectInstallMutationWriteOptions } from "./install-config-mutation-CAMgpehu.mjs";
import { o as buildPluginSnapshotReport } from "./status-BWOiehjp.mjs";
import { r as capturePluginRuntimeApplications } from "./lifecycle-BvISpZyj.mjs";
import { t as readPluginMutationSnapshot } from "./management-config-Czsgs-Rf.mjs";
import { i as refreshManagedPluginMetadata, r as loadFreshManagedPluginMetadata } from "./management-service-DZag8sId.mjs";
import { t as withPluginSourceCleanup } from "./source-cleanup-Dm-qIk92.mjs";
import { t as collectClawPluginUninstallWarnings } from "./uninstall-claw-references-Da-x55fW.mjs";
//#region src/plugins/uninstall-selection.ts
/** Resolve user input to the plugin id that should be removed from config/install records. */
function resolvePluginUninstallId(params) {
const rawId = params.rawId.trim();
const pluginConfig = params.config.plugins;
const installs = pluginConfig?.installs ?? {};
const resolveInstalledPlugin = (pluginId) => {
const plugin = params.plugins.find((entry) => entry.id === pluginId);
return plugin ? {
pluginId,
plugin
} : { pluginId };
};
const exactPlugin = params.plugins.find((entry) => entry.id === rawId);
if (exactPlugin) return ok({
pluginId: exactPlugin.id,
plugin: exactPlugin
});
if (Object.hasOwn(installs, rawId) || Object.hasOwn(pluginConfig?.entries ?? {}, rawId) || pluginConfig?.allow?.includes(rawId) || pluginConfig?.deny?.includes(rawId) || pluginConfig?.slots?.memory === rawId || pluginConfig?.slots?.contextEngine === rawId) return ok(resolveInstalledPlugin(rawId));
const matchingPluginIds = new Set(params.plugins.filter((plugin) => plugin.name === rawId).map((plugin) => plugin.id));
for (const [pluginId, install] of Object.entries(installs)) if (install.spec === rawId || install.resolvedSpec === rawId || install.resolvedName === rawId || install.marketplacePlugin === rawId) matchingPluginIds.add(pluginId);
const requestedClawHub = parseClawHubPluginSpec(rawId);
if (requestedClawHub) {
for (const [pluginId, install] of Object.entries(installs)) if ((install.clawhubPackage ?? parseClawHubPluginSpec(install.spec ?? "")?.name ?? parseClawHubPluginSpec(install.resolvedSpec ?? "")?.name) === requestedClawHub.name) matchingPluginIds.add(pluginId);
}
if (matchingPluginIds.size > 1) {
const matches = [...matchingPluginIds].toSorted().join(", ");
return err(`Plugin uninstall target "${rawId}" is ambiguous; matches: ${matches}. Use an exact plugin id.`);
}
const [matchedPluginId] = matchingPluginIds;
return ok(resolveInstalledPlugin(matchedPluginId ?? rawId));
}
//#endregion
//#region src/plugins/management-uninstall.ts
function runUninstallPhase(params, phase, run) {
return params.caller === "cli" ? tracePluginLifecyclePhaseAsync(phase, run, { command: "uninstall" }) : run();
}
async function readUninstallSnapshot(params, phase) {
return await runUninstallPhase(params, phase, async () => {
if (params.caller === "management") return await readPluginMutationSnapshot(params.env ?? process.env);
const { snapshot, writeOptions } = await readConfigFileSnapshotForWrite();
return {
config: snapshot.sourceConfig,
baseHash: snapshot.hash,
writeOptions: selectInstallMutationWriteOptions(writeOptions)
};
});
}
/** Read-only plan; execution always replans under its lease after confirmation. */
async function preparePluginUninstall(params) {
const env = params.env ?? process.env;
const cli = params.caller === "cli";
const snapshot = await readUninstallSnapshot(params, "config read");
const installRecords = await runUninstallPhase(params, "install records load", () => loadInstalledPluginIndexInstallRecords(cli ? {} : { env }));
const config = withPluginInstallRecords(snapshot.config, installRecords);
const metadata = cli ? void 0 : loadFreshManagedPluginMetadata(config, env);
const index = metadata?.index ?? loadInstalledPluginIndex({
config,
installRecords
});
const plugins = metadata ? metadata.index.plugins.map((record) => {
const manifest = metadata.byPluginId.get(record.pluginId);
return {
id: record.pluginId,
name: manifest?.name ?? record.pluginId,
origin: record.origin,
source: manifest?.source,
channelIds: manifest?.channels
};
}) : tracePluginLifecyclePhase("plugin registry snapshot", () => buildPluginSnapshotReport({ config }), { command: "uninstall" }).plugins;
const requestedId = metadata ? metadata.normalizePluginId(params.pluginId.trim()) : params.pluginId;
const selection = cli ? resolvePluginUninstallId({
rawId: requestedId,
config,
plugins
}) : ok({
pluginId: requestedId,
plugin: plugins.find((plugin) => plugin.id === requestedId)
});
if (!selection.ok) return selection;
const { pluginId: requestedPluginId, plugin } = selection.value;
if (!cli) {
if (plugin?.origin === "bundled") return err(`bundled plugin cannot be uninstalled: ${requestedPluginId}; disable it instead`);
if (!plugin && !Object.hasOwn(installRecords, requestedPluginId)) return err(`Plugin not found: ${requestedPluginId}`);
}
const ownership = createInstalledPluginOwnershipResolver(index, env).resolveLifecycle(requestedPluginId);
if (!ownership.ok) return ownership;
const { installOwner: pluginId, pluginIds } = ownership.value;
const policyPluginIds = pluginIds.length ? pluginIds : [pluginId];
let channelIds;
if (cli) {
if (pluginIds.length === 1 && pluginIds[0] === requestedPluginId) channelIds = plugin?.channelIds;
else if (pluginIds.length) channelIds = uniqueStrings(pluginIds.flatMap((id) => plugins.find((entry) => entry.id === id)?.channelIds ?? []));
else if (createInstalledPluginIndexScopeLookup(index).hasChannelContributionOwners([pluginId])) channelIds = [];
} else {
const manifests = pluginIds.flatMap((id) => metadata?.byPluginId.get(id) ?? []);
channelIds = manifests.length ? uniqueStrings(manifests.flatMap((manifest) => manifest.channels)) : ownership.value.kind === "orphan" && createInstalledPluginIndexScopeLookup(index).hasChannelContributionOwners([pluginId]) ? [] : void 0;
}
const runtimeLoadPaths = pluginIds.flatMap((id) => plugins.find((entry) => entry.id === id)?.source ?? []);
const extensionsDir = resolveDefaultPluginExtensionsDir(cli ? void 0 : env);
const planForConfig = (source) => planPluginUninstall(recordPluginPackageUninstallPlan({
config: withPluginInstallRecords(source, installRecords),
pluginId,
...channelIds !== void 0 ? { channelIds } : {},
deleteFiles: !params.keepFiles,
extensionsDir
}, {
runtimePluginIds: policyPluginIds,
runtimeLoadPaths
}));
const plan = planForConfig(snapshot.config);
if (!plan.ok) return err(cli && plugin ? `Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.` : plan.error);
return ok({
snapshot,
installRecords,
pluginId,
requestedPluginId,
pluginIds,
policyPluginIds,
name: plugin?.name || pluginId,
channelIds,
plan,
planForConfig
});
}
/** Shared leased removal; callbacks preserve CLI output at its original mutation boundaries. */
async function uninstallPluginWithPolicy(params) {
const env = params.env ?? process.env;
const cli = params.caller === "cli";
const applyRuntime = params.applyRuntime ? capturePluginRuntimeApplications(params.applyRuntime).applyRuntime : void 0;
return await withPluginLifecycleLease({
...cli ? {} : { env },
signal: params.signal
}, async (lease) => {
const beforePersistentApply = () => {
params.signal?.throwIfAborted();
lease.assertOwned();
params.beforePersistentApply?.();
};
beforePersistentApply();
if (cli) assertConfigWriteAllowedInCurrentMode();
const preparation = await preparePluginUninstall(params);
if (!preparation.ok) return preparation;
const prepared = preparation.value;
params.onPreview?.(prepared);
const uninstall = async () => {
const { pluginId, requestedPluginId, pluginIds, policyPluginIds, installRecords, plan: initialPlan } = prepared;
const snapshot = prepared.snapshot;
const assertConfigPathForWrite = () => {
snapshot.writeOptions.assertConfigPathForWrite?.();
beforePersistentApply();
};
let directoryResult = {
directoryRemoved: false,
warnings: []
};
if (initialPlan.directoryRemoval || applyRuntime) {
const disabledConfig = prepareConfigForDisabledPluginSet(snapshot.config, policyPluginIds, initialPlan.config);
const write = await runUninstallPhase(params, "config disable", () => replaceConfigFile({
sourceConfig: disabledConfig,
baseHash: snapshot.baseHash,
writeOptions: {
...snapshot.writeOptions,
assertConfigPathForWrite,
afterWrite: params.applyRuntime || params.deferRuntime ? {
mode: "none",
reason: "plugin lifecycle applies runtime"
} : { mode: "auto" }
}
}));
await applyRuntime?.({
config: disabledConfig,
write,
pluginIds: policyPluginIds,
reason: "uninstall",
assertInvokerOwned: beforePersistentApply
});
beforePersistentApply();
if (initialPlan.directoryRemoval) {
const removal = initialPlan.directoryRemoval;
directoryResult = await withPluginSourceCleanup(removal.target, {
configPath: write.path,
env,
assertCurrent: beforePersistentApply,
isReferenced: (config) => {
const current = prepared.planForConfig(config);
if (!current.ok) throw new Error(current.error);
return current.actions.loadPath;
}
}, (assertCurrent) => applyPluginUninstallDirectoryRemoval(removal, assertCurrent));
}
for (const warning of directoryResult.warnings) params.onWarning?.(warning);
if (initialPlan.directoryRemoval && pluginUninstallTargetExists(initialPlan.directoryRemoval.target)) {
const message = `Failed to remove plugin directory ${cli ? shortenHomePath(initialPlan.directoryRemoval.target) : initialPlan.directoryRemoval.target}; the plugin remains disabled and tracked so uninstall can be retried.`;
throw cli ? new Error(message) : new ManagedPluginLifecycleError(message, { kind: "unavailable" });
}
}
const nextInstallRecords = removePluginInstallRecordFromRecords(installRecords, pluginId);
const { expectedConfigPath, ownedConfigPathForWrite, auditOrigin } = snapshot.writeOptions;
const io = !cli && params.env ? createConfigIO({
configPath: ownedConfigPathForWrite ?? expectedConfigPath,
env: cloneEnvWithPlatformSemantics(env)
}) : void 0;
let commitReceipt;
const transform = () => transformConfigFileWithRetry({
base: "source",
...io ? { io: {
...io,
readConfigFileSnapshotForWrite: () => createConfigIO({
configPath: io.configPath,
env: cloneEnvWithPlatformSemantics(env)
}).readConfigFileSnapshotForWrite()
} } : {},
writeOptions: {
...expectedConfigPath ? { expectedConfigPath } : {},
...ownedConfigPathForWrite ? { ownedConfigPathForWrite } : {},
...auditOrigin ? { auditOrigin } : {},
assertConfigPathForWrite,
assertCurrent: beforePersistentApply,
...cli || params.applyRuntime ? { allowConfigSizeDrop: true } : {},
...params.applyRuntime || params.deferRuntime ? { afterWrite: {
mode: "none",
reason: "plugin lifecycle applies runtime"
} } : cli ? { afterWrite: {
mode: "restart",
reason: "plugin source changed"
} } : {}
},
transform: (currentConfig) => {
beforePersistentApply();
const plan = prepared.planForConfig(currentConfig);
if (!plan.ok) throw cli ? new Error(plan.error) : new ManagedPluginLifecycleError(plan.error);
return {
nextConfig: withoutPluginInstallRecords(plan.config),
result: plan
};
},
commit: async ({ nextConfig, baseHash, writeOptions, afterWrite }) => {
const receipt = await commitPluginInstallRecordsWithConfig({
previousInstallRecords: installRecords,
nextInstallRecords,
nextConfig,
baseHash,
beforePersistentEffect: beforePersistentApply,
writeOptions: {
...writeOptions,
afterWrite
}
});
commitReceipt = receipt;
return {
config: receipt.configWrite.nextConfig,
persistedHash: receipt.configWrite.persistedHash,
persistedSourceConfig: receipt.configWrite.persistedSourceConfig,
afterWrite: receipt.configWrite.afterWrite
};
}
});
const mutation = await runUninstallPhase(params, "config mutation", () => io ? withConfigWriteLock(io.configPath, transform, env, beforePersistentApply) : transform());
const committed = expectDefined(commitReceipt, "plugin uninstall commit receipt");
const plan = expectDefined(mutation.result, "committed plugin uninstall plan");
const nextConfig = mutation.nextConfig;
params.deferRuntime?.record({
operation: "uninstall",
pluginId,
write: committed
});
const warnings = [
...!cli ? collectClawPluginUninstallWarnings({
pluginId,
installRecord: installRecords[pluginId],
env
}) : [],
...!cli && (requestedPluginId !== pluginId || pluginIds.length > 1) ? [`Uninstalled package "${pluginId}" and all owned plugin entries: ${pluginIds.join(", ")}.`] : [],
...directoryResult.warnings
];
await refreshPluginRegistryAfterConfigMutation({
configPath: committed.configWrite.path,
env,
lease,
reason: "source-changed",
installRecords: nextInstallRecords,
invalidateRuntimeCache: cli ? params.invalidateRuntimeCache : false,
...cli ? { traceCommand: "uninstall" } : {},
logger: { warn: (message) => {
warnings.push(message);
params.onWarning?.(message);
} }
});
if (!cli) refreshManagedPluginMetadata({
config: nextConfig,
env
});
const application = await applyRuntime?.({
config: nextConfig,
write: committed.configWrite,
pluginIds: policyPluginIds,
reason: "uninstall",
assertInvokerOwned: beforePersistentApply
});
const result = {
...application ? { application } : {},
pluginId,
requestedPluginId,
pluginIds,
removed: formatUninstallActionLabels({
...plan.actions,
loadPath: initialPlan.actions.loadPath || plan.actions.loadPath,
directory: directoryResult.directoryRemoved
}),
warnings: [.../* @__PURE__ */ new Set([...warnings, ...application?.warnings ?? []])]
};
params.onComplete?.(result);
return ok(result);
};
const record = prepared.installRecords[prepared.pluginId];
const packageName = record?.source === "clawhub" ? record.clawhubPackage ?? parseClawHubPluginSpec(record.spec ?? "")?.name : void 0;
if (params.clawManaged || !packageName || !cli && !params.applyRuntime) return await uninstall();
return await withClawPackageLifecycleLease({
kind: "plugin",
source: "clawhub",
ref: packageName
}, uninstall, {
...cli ? {} : { env },
required: true
});
});
}
/** Preserve the management API's canonical-id admission and response shape. */
async function uninstallManagedPlugin(params) {
const env = params.env ?? process.env;
return await withPluginLifecycleLease({
env,
signal: params.signal
}, async () => {
const result = await uninstallPluginWithPolicy({
...params,
caller: "management"
});
if (!result.ok) throw new ManagedPluginLifecycleError(result.error);
const { pluginId, removed, warnings, application } = result.value;
return {
pluginId,
removed,
...warnings.length ? { warnings } : {},
...application ? { application } : {}
};
});
}
//#endregion
export { uninstallManagedPlugin as n, uninstallPluginWithPolicy as r, preparePluginUninstall as t };