openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
636 lines (635 loc) • 30.9 kB
JavaScript
import { f as withPluginCache, i as createPluginCache } from "./plugin-cache-DGWspMEc.js";
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { r as defaultRuntime } from "./runtime-CF2WjnNZ.js";
import { l as readConfigFileSnapshotForWrite, n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { t as VERSION } from "./version-v1kuAkGj.js";
import { a as loadInstalledPluginIndex } from "./installed-plugin-index-wrDsjyMD.js";
import { o as parseRegistryNpmSpec } from "./npm-registry-spec-CM_p1_uq.js";
import { r as loadInstalledPluginIndexInstallRecords } from "./installed-plugin-index-record-reader-SXWwf_BU.js";
import { c as normalizeUpdateChannel, d as resolveRegistryUpdateChannel } from "./update-channels-BcfztK6k.js";
import { t as createInstalledPluginOwnershipResolver } from "./installed-plugin-package-ownership-DGTVMT4h.js";
import { n as createMergePatch, t as applyMergePatch } from "./merge-patch-DCMj2JVh.js";
import { r as containsConfigIncludeDirective } from "./io.read-helpers-ZKp-UiGx.js";
import { t as ConfigMutationConflictError } from "./mutation-conflict-Be0wSyDG.js";
import { n as formatInvalidConfigDetails, t as createInvalidConfigError } from "./io.invalid-config-xEPg4zuq.js";
import { n as assertConfigWriteAllowedInCurrentMode } from "./nix-mode-write-guard-uwcnAyQN.js";
import { r as replaceConfigFile } from "./mutate-ZNN4iFCn.js";
import "./config-Cs0XXL3x.js";
import { r as theme } from "./theme-vjDs9tao.js";
import { t as buildNpmResolutionFields } from "./install-source-utils-DPJygB4h.js";
import { a as withPluginInstallRecords, o as withoutPluginInstallRecords, u as configReferencesNpmInstallPath } from "./installed-plugin-index-records-C06Xozeq.js";
import { a as resolvePackageDirInstallTransaction, i as requestDeferredPackageDirInstall } from "./install-package-dir-kJK8m3kQ.js";
import { t as withPluginLifecycleLease } from "./plugin-lifecycle-lease-BtAFH872.js";
import { r as promptYesNo } from "./prompt-BvBir9Yc.js";
import { n as resolvePluginCapabilityConsentCliOptions } from "./plugin-capability-consent-HWTOHYVK.js";
import { B as requestDeferredPluginInstall, U as resolvePluginInstallTransactionRequest, V as resolvePluginInstallOwnerMigrations, W as settlePluginInstallTransactions } from "./install-managed-npm-state-BlNS_UI7.js";
import { i as commitPluginInstallRecordsWithConfig, r as commitPluginInstallRecordsOnly } from "./install-record-commit-C4pioTsf.js";
import { n as refreshPluginRegistryAfterConfigMutation } from "./registry-refresh-CvO7IUdQ.js";
import { i as selectInstallMutationWriteOptions, n as resolveCombinedPluginAndHookConfigMutationPreflight, r as resolveInstallConfigMutationPreflights } from "./install-persistence-iNWG2xIJ.js";
import { i as resolveHookInstallDir, n as installHooksFromNpmSpec } from "./install-7XIb3_ne.js";
import { t as resolveInstallPolicyWarningAcknowledgementCliOptions } from "./install-policy-warning-acknowledgement-DNm6cyVK.js";
import { t as readHookInstalls } from "./installs-Bm8AqVuM.js";
import { t as stageHookInstall } from "./install-record-transaction-BSZea7FD.js";
import { a as readInstalledPackageVersion, n as expectedIntegrityForUpdate, r as isPackageVersionDowngrade } from "./package-update-utils-Bt0FtB_O.js";
import { n as pluginPackageUpdateMayMutateConfig, r as reconcilePluginPackageUpdateConfig, t as capturePluginPackageUpdateSnapshot } from "./plugin-package-update-BpnhBGP4.js";
import { a as pluginInstallRecordMayMigrateConfigId, i as isPluginInstallRecordUpdateSource, n as updateNpmInstalledPlugins, r as isClawHubTrustSkippedOutcome } from "./update-BAjyWYBA.js";
import { t as notifyGatewayPluginMetadataChanged } from "./plugins-update-gateway-signal-C7AdqZ5n.js";
import { isDeepStrictEqual } from "node:util";
//#region src/hooks/update.ts
function createHookPackUpdateIntegrityDriftHandler(params) {
return async (drift) => {
const payload = {
hookId: params.hookId,
spec: drift.spec,
expectedIntegrity: drift.expectedIntegrity,
actualIntegrity: drift.actualIntegrity,
resolution: drift.resolution,
resolvedSpec: drift.resolution.resolvedSpec,
resolvedVersion: drift.resolution.version,
dryRun: params.dryRun
};
if (params.onIntegrityDrift) return await params.onIntegrityDrift(payload);
params.logger.warn?.(`Integrity drift for hook pack "${params.hookId}" (${payload.resolvedSpec ?? payload.spec}): expected ${payload.expectedIntegrity}, got ${payload.actualIntegrity}`);
return false;
};
}
/** Update npm-installed hook packs and return config changes plus per-pack outcomes. */
async function updateNpmInstalledHookPacks(params) {
const logger = params.logger ?? {};
const transactionRequest = resolvePluginInstallTransactionRequest(params);
const persistence = params.dryRun ? void 0 : {
lease: expectDefined(params.lease, "hook update lifecycle lease"),
transactions: expectDefined(transactionRequest?.transactionSink, "hook update transaction sink")
};
const beforePersistentApply = () => {
persistence?.lease.assertOwned();
params.beforePersistentApply?.();
};
if (persistence) beforePersistentApply();
const installs = readHookInstalls(persistence ? { path: persistence.lease.databasePath } : {});
const targets = params.hookIds?.length ? params.hookIds : Object.keys(installs);
const outcomes = [];
let changed = false;
for (const hookId of targets) {
const record = installs[hookId];
if (!record) {
outcomes.push({
hookId,
status: "skipped",
message: `No install record for hook pack "${hookId}".`
});
continue;
}
if (record.source !== "npm") {
outcomes.push({
hookId,
status: "skipped",
message: `Skipping hook pack "${hookId}" (source: ${record.source}).`
});
continue;
}
const effectiveSpec = params.specOverrides?.[hookId] ?? record.spec;
const expectedIntegrity = effectiveSpec === record.spec ? expectedIntegrityForUpdate(record.spec, record.integrity) : void 0;
if (!effectiveSpec) {
outcomes.push({
hookId,
status: "skipped",
message: `Skipping hook pack "${hookId}" (missing npm spec).`
});
continue;
}
let installPath;
try {
installPath = record.installPath ?? resolveHookInstallDir(hookId);
} catch (err) {
outcomes.push({
hookId,
status: "error",
message: `Invalid install path for hook pack "${hookId}": ${String(err)}`
});
continue;
}
const currentVersion = await readInstalledPackageVersion(installPath);
const result = await installHooksFromNpmSpec(requestDeferredPackageDirInstall({
config: params.config,
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
onInstallPolicyWarning: params.onInstallPolicyWarning,
spec: effectiveSpec,
mode: "update",
dryRun: params.dryRun,
beforePersistentApply,
expectedHookPackId: hookId,
expectedIntegrity,
onIntegrityDrift: createHookPackUpdateIntegrityDriftHandler({
hookId,
dryRun: Boolean(params.dryRun),
logger,
onIntegrityDrift: params.onIntegrityDrift
}),
logger
}, transactionRequest?.assertOwned));
if (!result.ok) {
outcomes.push({
hookId,
status: "error",
message: `Failed to ${params.dryRun ? "check" : "update"} hook pack "${hookId}": ${result.error}`
});
continue;
}
const nextVersion = result.version ?? await readInstalledPackageVersion(result.targetDir);
const currentLabel = currentVersion ?? "unknown";
const nextLabel = nextVersion ?? "unknown";
const status = currentVersion && nextVersion && currentVersion === nextVersion ? "unchanged" : "updated";
const downgraded = isPackageVersionDowngrade(currentVersion, nextVersion);
if (!persistence) {
outcomes.push({
hookId,
status,
currentVersion: currentVersion ?? void 0,
nextVersion: nextVersion ?? void 0,
message: status === "unchanged" ? `Hook pack "${hookId}" is up to date (${currentLabel}).` : `${downgraded ? "Would downgrade" : "Would update"} hook pack "${hookId}": ${currentLabel} -> ${nextLabel}.`
});
continue;
}
persistence.transactions.push(await stageHookInstall({
update: {
hookId,
source: "npm",
spec: effectiveSpec,
installPath: result.targetDir,
version: nextVersion,
...buildNpmResolutionFields(result.npmResolution),
hooks: result.hooks
},
payloadTransaction: resolvePackageDirInstallTransaction(result),
lease: persistence.lease,
beforePersistentApply
}));
changed = true;
outcomes.push({
hookId,
status,
currentVersion: currentVersion ?? void 0,
nextVersion: nextVersion ?? void 0,
message: status === "unchanged" ? `Hook pack "${hookId}" already at ${currentLabel}.` : `${downgraded ? "Downgraded" : "Updated"} hook pack "${hookId}": ${currentLabel} -> ${nextLabel}.`
});
}
return {
config: params.config,
changed,
outcomes
};
}
//#endregion
//#region src/cli/plugins-update-outcomes.ts
/** Log update outcomes with severity styling and report whether any errors occurred. */
function logPluginUpdateOutcomes(params) {
let hasErrors = false;
for (const outcome of params.outcomes) {
if (outcome.status === "error") {
hasErrors = true;
params.error(theme.error(outcome.message));
} else if (outcome.status === "skipped") {
if (isClawHubTrustSkippedOutcome(outcome)) hasErrors = true;
params.log(theme.warn(outcome.message));
} else params.log(outcome.message);
if (outcome.channelFallback) params.log(theme.warn(outcome.channelFallback.message));
}
return { hasErrors };
}
//#endregion
//#region src/cli/plugins-install-records.ts
/** Return the installed npm package name for a plugin install record when available. */
function extractInstalledNpmPackageName(install) {
if (install.source !== "npm") return;
const resolvedName = install.resolvedName?.trim();
if (resolvedName) return resolvedName;
return (install.spec ? parseRegistryNpmSpec(install.spec)?.name : void 0) ?? (install.resolvedSpec ? parseRegistryNpmSpec(install.resolvedSpec)?.name : void 0);
}
/** Return the installed npm package name for a hook-pack install record when available. */
function extractInstalledNpmHookPackageName(install) {
const resolvedName = install.resolvedName?.trim();
if (resolvedName) return resolvedName;
return (install.spec ? parseRegistryNpmSpec(install.spec)?.name : void 0) ?? (install.resolvedSpec ? parseRegistryNpmSpec(install.resolvedSpec)?.name : void 0);
}
//#endregion
//#region src/cli/plugins-update-selection.ts
/** Resolve a plugin update target and optional npm spec override from CLI input. */
function resolvePluginUpdateSelection(params) {
if (params.all) {
const rejectedOwners = Object.keys(params.installs).filter((pluginId) => params.rejectedPluginIds?.has(pluginId));
if (rejectedOwners.length > 0) return {
pluginIds: [],
error: params.rejectedPluginIds?.get(rejectedOwners[0])
};
return { pluginIds: Object.keys(params.installs) };
}
if (!params.rawId) return { pluginIds: [] };
if (params.rejectedPluginIds?.has(params.rawId)) return {
pluginIds: [],
error: params.rejectedPluginIds.get(params.rawId)
};
if (Object.hasOwn(params.installs, params.rawId)) return { pluginIds: [params.rawId] };
const installOwner = params.installOwnerByPluginId?.get(params.rawId);
if (installOwner && Object.hasOwn(params.installs, installOwner)) return { pluginIds: [installOwner] };
const parsedSpec = parseRegistryNpmSpec(params.rawId);
if (!parsedSpec) return { pluginIds: [] };
const matches = Object.entries(params.installs).filter(([, install]) => {
return extractInstalledNpmPackageName(install) === parsedSpec.name;
});
if (matches.length !== 1) return { pluginIds: [] };
const [pluginId] = expectDefined(matches[0], "matches capture group 0");
if (!pluginId) return { pluginIds: [] };
if (params.rejectedPluginIds?.has(pluginId)) return {
pluginIds: [],
error: params.rejectedPluginIds.get(pluginId)
};
return {
pluginIds: [pluginId],
specOverrides: { [pluginId]: parsedSpec.raw }
};
}
/** Resolve a hook-pack update target and optional npm spec override from CLI input. */
function resolveHookPackUpdateSelection(params) {
if (params.all) return { hookIds: Object.keys(params.installs) };
if (!params.rawId) return { hookIds: [] };
if (Object.hasOwn(params.installs, params.rawId)) return { hookIds: [params.rawId] };
const parsedSpec = parseRegistryNpmSpec(params.rawId);
if (!parsedSpec) return { hookIds: [] };
const matches = Object.entries(params.installs).filter(([, install]) => {
return extractInstalledNpmHookPackageName(install) === parsedSpec.name;
});
if (matches.length !== 1) return { hookIds: [] };
const [hookId] = expectDefined(matches[0], "matches capture group 0");
if (!hookId) return { hookIds: [] };
return {
hookIds: [hookId],
specOverrides: { [hookId]: parsedSpec.raw }
};
}
//#endregion
//#region src/cli/plugins-update-command.ts
const DEPRECATED_DANGEROUS_FORCE_UNSAFE_UPDATE_WARNING = "--dangerously-force-unsafe-install is deprecated and no longer affects plugin updates because built-in install-time dangerous-code scanning has been removed. Configure security.installPolicy for operator-owned install decisions.";
function mayMutatePluginInstallRecord(record, specOverride) {
if (!isPluginInstallRecordUpdateSource(record)) return false;
if (record?.source === "npm") return Boolean(specOverride ?? record.spec);
if (record?.source === "git") return Boolean(record.spec);
if (record?.source === "clawhub") return Boolean(record.clawhubPackage);
return Boolean(record?.marketplaceSource && record.marketplacePlugin);
}
function pluginConfigReferencesId(config, pluginId) {
const plugins = config.plugins;
return plugins?.allow?.includes(pluginId) || plugins?.deny?.includes(pluginId) || Object.hasOwn(plugins?.entries ?? {}, pluginId) || plugins?.slots?.memory === pluginId || plugins?.slots?.contextEngine === pluginId;
}
function shouldPreserveEmptyPlugins(params) {
const plugins = params.sourceConfig.plugins;
const parsedPlugins = params.parsed && typeof params.parsed === "object" && !Array.isArray(params.parsed) ? params.parsed.plugins : void 0;
return Boolean(plugins && (!Object.hasOwn(plugins, "installs") || Object.keys(plugins).some((key) => key !== "installs") || containsConfigIncludeDirective(parsedPlugins)));
}
function projectUpdaterResultOntoSourceConfig(params) {
const updatePatch = createMergePatch(params.runtimeBase, params.updatedConfig);
return applyMergePatch(params.sourceBase, updatePatch);
}
function assertWriteOptionRecordFresh(params) {
if (!isDeepStrictEqual(params.current ?? {}, params.expected ?? {})) throw new ConfigMutationConflictError(params.message);
}
async function assertRecordsOnlyUpdateConfigFresh(params) {
const prepared = await readConfigFileSnapshotForWrite(params.writeOptions);
const writeOptions = {
...prepared.writeOptions,
...params.writeOptions
};
const currentHash = prepared.snapshot.hash ?? null;
writeOptions.assertConfigPathForWrite?.();
if (writeOptions.expectedConfigPath !== void 0 && writeOptions.expectedConfigPath !== prepared.snapshot.path) throw new ConfigMutationConflictError("config path changed since last load", { retryable: false });
if (params.baseHash !== void 0 && params.baseHash !== currentHash) throw new ConfigMutationConflictError("config changed since last load");
assertWriteOptionRecordFresh({
current: prepared.writeOptions.includeFileTargetsForWrite,
expected: params.writeOptions?.includeFileTargetsForWrite,
message: "included config target changed since last load"
});
assertWriteOptionRecordFresh({
current: prepared.writeOptions.includeFileHashesForWrite,
expected: params.writeOptions?.includeFileHashesForWrite,
message: "included config changed since last load"
});
if (!prepared.snapshot.valid) throw createInvalidConfigError(prepared.snapshot.path, formatInvalidConfigDetails(prepared.snapshot.issues));
}
/** Run plugin/hook-pack updates, persist changed install records, and refresh runtime registry. */
async function runPluginUpdateCommand(params) {
if (params.opts.dryRun) return await runPluginUpdateCommandUnlocked(params);
assertConfigWriteAllowedInCurrentMode();
return await withPluginLifecycleLease({}, async (lease) => await runPluginUpdateCommandUnlocked(params, lease));
}
async function runPluginUpdateCommandUnlocked(params, lease) {
const assertOwned = lease?.assertOwned.bind(lease);
if (!params.opts.dryRun) assertConfigWriteAllowedInCurrentMode();
const sourceSnapshotPromise = readConfigFileSnapshotForWrite().then((prepared) => {
const writeOptions = selectInstallMutationWriteOptions(prepared.writeOptions);
return {
...prepared,
writeOptions: {
...writeOptions,
assertConfigPathForWrite: () => {
assertOwned?.();
writeOptions.assertConfigPathForWrite?.();
}
}
};
}).catch(() => null);
const mutationSnapshot = params.opts.dryRun ? null : await sourceSnapshotPromise;
if (!params.opts.dryRun && !mutationSnapshot) {
defaultRuntime.error("Could not inspect config ownership before updating plugins or hooks.");
return defaultRuntime.exit(1);
}
if (mutationSnapshot && !mutationSnapshot.snapshot.valid) {
defaultRuntime.error("Cannot update plugins or hooks while the config is invalid.");
return defaultRuntime.exit(1);
}
const cfg = mutationSnapshot?.snapshot.runtimeConfig ?? getRuntimeConfig();
const sourceCfg = mutationSnapshot?.snapshot.sourceConfig ?? cfg;
const persistedPluginInstallRecords = await loadInstalledPluginIndexInstallRecords();
const pluginInstallRecords = persistedPluginInstallRecords;
const cfgWithPluginInstallRecords = withPluginInstallRecords(cfg, pluginInstallRecords);
const sourceCfgWithPluginInstallRecords = withPluginInstallRecords(sourceCfg, pluginInstallRecords);
const installedPluginIndex = loadInstalledPluginIndex({
config: cfgWithPluginInstallRecords,
installRecords: pluginInstallRecords
});
const installOwnerByPluginId = /* @__PURE__ */ new Map();
const rejectedPluginIds = /* @__PURE__ */ new Map();
const ownershipResolver = createInstalledPluginOwnershipResolver(installedPluginIndex);
for (const pluginId of /* @__PURE__ */ new Set([...installedPluginIndex.plugins.map((plugin) => plugin.pluginId), ...Object.keys(pluginInstallRecords)])) {
const ownership = ownershipResolver.resolveLifecycle(pluginId);
if (!ownership.ok) {
rejectedPluginIds.set(pluginId, ownership.error);
continue;
}
installOwnerByPluginId.set(pluginId, ownership.value.installOwner);
installOwnerByPluginId.set(ownership.value.installOwner, ownership.value.installOwner);
}
const configuredUpdateChannel = normalizeUpdateChannel(cfg.update?.channel) ?? void 0;
const officialPluginUpdateChannel = resolveRegistryUpdateChannel({
configChannel: configuredUpdateChannel,
currentVersion: VERSION
});
const logger = {
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(msg.includes("╭─") ? msg : theme.warn(msg))
};
if (params.opts.dangerouslyForceUnsafeInstall) defaultRuntime.log(theme.warn(DEPRECATED_DANGEROUS_FORCE_UNSAFE_UPDATE_WARNING));
const pluginSelection = resolvePluginUpdateSelection({
installs: pluginInstallRecords,
installOwnerByPluginId,
rejectedPluginIds,
rawId: params.id,
all: params.opts.all
});
if (pluginSelection.error) {
defaultRuntime.error(pluginSelection.error);
return defaultRuntime.exit(1);
}
const packageUpdateSnapshotResult = capturePluginPackageUpdateSnapshot({
index: installedPluginIndex,
installOwners: pluginSelection.pluginIds
});
if (!packageUpdateSnapshotResult.ok) {
defaultRuntime.error(packageUpdateSnapshotResult.error);
return defaultRuntime.exit(1);
}
const packageUpdateSnapshot = packageUpdateSnapshotResult.value;
const packagePluginIds = Object.fromEntries([...packageUpdateSnapshot.values()].map((ownership) => [ownership.installOwner, [...ownership.pluginIds]]));
const selectedHooks = readHookInstalls();
const hookSelection = resolveHookPackUpdateSelection({
installs: selectedHooks,
rawId: params.id,
all: params.opts.all
});
if (pluginSelection.pluginIds.length === 0 && hookSelection.hookIds.length === 0) {
if (params.opts.all) {
defaultRuntime.log("No tracked plugins or hook packs to update.");
return;
}
defaultRuntime.error(params.id ? `No tracked plugin or hook pack found for "${params.id}". Run "openclaw plugins list" or "openclaw hooks list" to inspect installed packages.` : "Provide a plugin or hook-pack id, or use --all.");
return defaultRuntime.exit(1);
}
const pluginUpdateMayMutate = !params.opts.dryRun && pluginSelection.pluginIds.some((pluginId) => {
return mayMutatePluginInstallRecord(pluginInstallRecords[pluginId], pluginSelection.specOverrides?.[pluginId]);
});
const hookUpdateMayMutate = !params.opts.dryRun && hookSelection.hookIds.some((hookId) => {
const record = selectedHooks[hookId];
return record?.source === "npm" && Boolean(hookSelection.specOverrides?.[hookId] ?? record.spec);
});
if (pluginUpdateMayMutate || hookUpdateMayMutate) {
if (!mutationSnapshot) {
defaultRuntime.error("Could not inspect config ownership before updating plugins or hooks.");
return defaultRuntime.exit(1);
}
const { hookMutation, pluginMutation } = resolveInstallConfigMutationPreflights({
parsed: mutationSnapshot.snapshot.parsed ?? {},
snapshotPath: mutationSnapshot.snapshot.path,
writeOptions: mutationSnapshot.writeOptions
});
const parsedConfig = mutationSnapshot.snapshot.parsed && typeof mutationSnapshot.snapshot.parsed === "object" && !Array.isArray(mutationSnapshot.snapshot.parsed) ? mutationSnapshot.snapshot.parsed : {};
const pluginReferencesMayBeUnresolved = Object.hasOwn(parsedConfig, "$include") || containsConfigIncludeDirective(mutationSnapshot.snapshot.sourceConfig.plugins);
const pluginIdMigrationMayMutate = pluginSelection.pluginIds.some((pluginId) => {
return pluginInstallRecordMayMigrateConfigId({
pluginId,
record: pluginInstallRecords[pluginId],
specOverride: pluginSelection.specOverrides?.[pluginId]
}) && (pluginReferencesMayBeUnresolved || pluginConfigReferencesId(mutationSnapshot.snapshot.sourceConfig, pluginId));
});
const pluginLoadPathMayMutate = pluginSelection.pluginIds.some((pluginId) => configReferencesNpmInstallPath({
config: cfg,
install: pluginInstallRecords[pluginId]
}));
const pluginConfigMayMutate = pluginIdMigrationMayMutate || pluginLoadPathMayMutate || pluginPackageUpdateMayMutateConfig({
config: mutationSnapshot.snapshot.sourceConfig,
index: installedPluginIndex,
snapshot: packageUpdateSnapshot
});
const blockedReasons = /* @__PURE__ */ new Set();
if (pluginConfigMayMutate && pluginMutation.mode === "blocked") blockedReasons.add(pluginMutation.reason);
if (hookUpdateMayMutate && hookMutation.mode === "blocked") blockedReasons.add(hookMutation.reason);
if (pluginConfigMayMutate && hookUpdateMayMutate && pluginMutation.mode === "allowed" && hookMutation.mode === "allowed") {
const combinedMutation = resolveCombinedPluginAndHookConfigMutationPreflight({
parsed: mutationSnapshot.snapshot.parsed ?? {},
snapshotPath: mutationSnapshot.snapshot.path
});
if (combinedMutation.mode === "blocked") blockedReasons.add(combinedMutation.reason);
}
if (blockedReasons.size > 0) {
defaultRuntime.error(Array.from(blockedReasons).join(" "));
return defaultRuntime.exit(1);
}
}
const installPolicyWarningAcknowledgement = resolveInstallPolicyWarningAcknowledgementCliOptions({
acknowledgeInstallPolicyWarning: params.opts.acknowledgeInstallPolicyWarning,
dangerouslyForceUnsafeInstall: params.opts.dangerouslyForceUnsafeInstall,
allowPrompt: !params.opts.dryRun
});
const deferredInstallTransactions = [];
let pluginResult;
try {
pluginResult = pluginSelection.pluginIds.length > 0 ? await updateNpmInstalledPlugins(requestDeferredPluginInstall({
config: cfgWithPluginInstallRecords,
pluginIds: pluginSelection.pluginIds,
packagePluginIds,
specOverrides: pluginSelection.specOverrides,
dryRun: params.opts.dryRun,
updateChannel: params.opts.all ? void 0 : configuredUpdateChannel,
officialPluginUpdateChannel,
syncOfficialPluginInstalls: params.opts.all ? true : void 0,
coreVersion: VERSION,
...installPolicyWarningAcknowledgement,
...resolvePluginCapabilityConsentCliOptions({
acceptCapabilities: params.opts.acceptCapabilities,
action: "update",
allowPrompt: !params.opts.dryRun
}),
logger,
onIntegrityDrift: async (drift) => {
const specLabel = drift.resolvedSpec ?? drift.spec;
defaultRuntime.log(theme.warn(`Integrity drift detected for "${drift.pluginId}" (${specLabel})\nExpected: ${drift.expectedIntegrity}\nActual: ${drift.actualIntegrity}`));
if (drift.dryRun) return true;
return await promptYesNo(`Continue updating "${drift.pluginId}" with this artifact?`);
}
}, deferredInstallTransactions, assertOwned)) : {
config: cfgWithPluginInstallRecords,
changed: false,
outcomes: []
};
} catch (error) {
await settlePluginInstallTransactions(deferredInstallTransactions, "rollback");
throw error;
}
let packageUpdatePersisted = false;
try {
if (pluginSelection.pluginIds.length > 0 && pluginResult.changed && !params.opts.dryRun) {
const nextInstallRecords = pluginResult.config.plugins?.installs ?? {};
const afterIndex = withPluginCache(createPluginCache(), () => loadInstalledPluginIndex({
config: pluginResult.config,
installRecords: nextInstallRecords
}));
const reconciled = reconcilePluginPackageUpdateConfig({
config: pluginResult.config,
beforeIndex: installedPluginIndex,
afterIndex,
snapshot: packageUpdateSnapshot,
installOwnerMigrations: resolvePluginInstallOwnerMigrations(pluginResult)
});
if (!reconciled.ok) {
await settlePluginInstallTransactions(deferredInstallTransactions, "rollback");
defaultRuntime.error(reconciled.error);
return defaultRuntime.exit(1);
}
pluginResult = {
...pluginResult,
config: reconciled.config
};
}
const hookResult = hookSelection.hookIds.length > 0 ? await updateNpmInstalledHookPacks(requestDeferredPluginInstall({
config: pluginResult.config,
lease,
beforePersistentApply: mutationSnapshot?.writeOptions.assertConfigPathForWrite,
hookIds: hookSelection.hookIds,
specOverrides: hookSelection.specOverrides,
dryRun: params.opts.dryRun,
...installPolicyWarningAcknowledgement,
logger,
onIntegrityDrift: async (drift) => {
const specLabel = drift.resolvedSpec ?? drift.spec;
defaultRuntime.log(theme.warn(`Integrity drift detected for hook pack "${drift.hookId}" (${specLabel})\nExpected: ${drift.expectedIntegrity}\nActual: ${drift.actualIntegrity}`));
if (drift.dryRun) return true;
return await promptYesNo(`Continue updating hook pack "${drift.hookId}" with this artifact?`);
}
}, deferredInstallTransactions, assertOwned)) : {
config: pluginResult.config,
changed: false,
outcomes: []
};
if (!params.opts.dryRun && (pluginResult.changed || hookResult.changed)) {
const sourceSnapshot = mutationSnapshot ?? await sourceSnapshotPromise;
if (pluginResult.changed) {
const currentInstallRecords = await loadInstalledPluginIndexInstallRecords();
const currentSnapshot = capturePluginPackageUpdateSnapshot({
index: installedPluginIndex,
installOwners: pluginSelection.pluginIds
});
if (!isDeepStrictEqual(currentInstallRecords, persistedPluginInstallRecords) || !currentSnapshot.ok || !isDeepStrictEqual([...currentSnapshot.value], [...packageUpdateSnapshot])) {
await settlePluginInstallTransactions(deferredInstallTransactions, "rollback");
defaultRuntime.error(currentSnapshot.ok ? "Plugin package ownership changed during update; no config or index changes were committed. Refresh the plugin registry and retry." : currentSnapshot.error);
return defaultRuntime.exit(1);
}
}
const nextPluginInstallRecords = pluginResult.config.plugins?.installs ?? {};
const shouldPersistPluginInstallIndex = pluginResult.changed || Object.keys(pluginInstallRecords).length > 0;
const sourceShapedUpdateConfig = projectUpdaterResultOntoSourceConfig({
runtimeBase: cfgWithPluginInstallRecords,
sourceBase: sourceCfgWithPluginInstallRecords,
updatedConfig: hookResult.config
});
const nextConfig = withoutPluginInstallRecords(sourceShapedUpdateConfig, { preserveEmptyPlugins: shouldPreserveEmptyPlugins({
parsed: sourceSnapshot?.snapshot.parsed,
sourceConfig: sourceSnapshot?.snapshot.sourceConfig ?? {}
}) });
let recordsOnlyPluginUpdate = false;
if (shouldPersistPluginInstallIndex) {
if (isDeepStrictEqual(nextConfig, sourceSnapshot?.snapshot.sourceConfig ?? sourceCfg)) {
await commitPluginInstallRecordsOnly({
previousInstallRecords: persistedPluginInstallRecords,
nextInstallRecords: nextPluginInstallRecords,
nextConfig,
verifyConfigFresh: async () => {
await assertRecordsOnlyUpdateConfigFresh({
baseHash: sourceSnapshot?.snapshot.hash,
writeOptions: sourceSnapshot?.writeOptions
});
}
});
recordsOnlyPluginUpdate = pluginResult.changed;
} else await commitPluginInstallRecordsWithConfig({
previousInstallRecords: persistedPluginInstallRecords,
nextInstallRecords: nextPluginInstallRecords,
nextConfig,
baseHash: sourceSnapshot?.snapshot.hash,
writeOptions: {
...sourceSnapshot?.writeOptions,
afterWrite: {
mode: "restart",
reason: "plugin source changed"
}
}
});
} else await replaceConfigFile({
nextConfig,
baseHash: sourceSnapshot?.snapshot.hash,
writeOptions: sourceSnapshot?.writeOptions
});
packageUpdatePersisted = true;
await settlePluginInstallTransactions(deferredInstallTransactions, "commit").catch(() => logger.warn("Plugin update committed, but cleanup failed. Restart is required."));
if (pluginResult.changed) {
await refreshPluginRegistryAfterConfigMutation({
config: nextConfig,
reason: "source-changed",
installRecords: nextPluginInstallRecords,
invalidateRuntimeCache: false,
logger
});
if (recordsOnlyPluginUpdate) await notifyGatewayPluginMetadataChanged(cfg);
}
defaultRuntime.log("Restart the gateway to load plugins and hooks.");
}
if (logPluginUpdateOutcomes({
outcomes: [...pluginResult.outcomes, ...hookResult.outcomes],
log: defaultRuntime.log,
error: defaultRuntime.error
}).hasErrors) defaultRuntime.exit(1);
} catch (error) {
if (!packageUpdatePersisted) await settlePluginInstallTransactions(deferredInstallTransactions, "rollback");
throw error;
}
}
//#endregion
export { runPluginUpdateCommand as t };