UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

636 lines (635 loc) 30.9 kB
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 };