UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

217 lines (216 loc) • 9.42 kB
import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import "./utils-DgZn2dLu.mjs"; import "./capability-consent-error-details-DqbomXex.mjs"; import { f as resolvePostCoreConvergenceEnv } from "./update-phase-BClSJ17n.mjs"; import { a as resolveDefaultPluginExtensionsDir, s as resolveDefaultPluginNpmDir } from "./install-paths-R7__5hE9.mjs"; import { o as reconcileRegisteredOpenClawHostLinks, s as relinkOpenClawPeerDependenciesInManagedNpmRoot } from "./plugin-peer-link-B8_Vs9lb.mjs"; import { l as listManagedPluginNpmRoots } from "./managed-npm-retention-BPamOGs_.mjs"; import { i as withPluginLifecycleLease } from "./plugin-lifecycle-lease-DHQMWweY.mjs"; import { n as recoverInstalledPluginConfigIds } from "./installed-plugin-id-recovery-BZgBncuI.mjs"; import { n as runActivePluginPayloadSmokeCheck, t as filterRecordsToActive } from "./active-payload-verification-Bu09VRDF.mjs"; import { r as maybeRepairStaleManagedNpmBundledPlugins, s as pruneStaleLocalBundledPluginInstallRecords } from "./doctor-plugin-registry-CpSPo9JM.mjs"; import { t as repairMissingConfiguredPluginInstalls } from "./missing-configured-plugin-install-By3ezo3t.mjs"; import path from "node:path"; //#region src/commands/doctor/shared/post-core-plugin-convergence.ts const REPAIR_GUIDANCE = "Run `openclaw update repair` to retry plugin repair."; const inspectGuidance = (pluginId) => `Run \`openclaw plugins inspect ${pluginId} --runtime --json\` for details.`; function smokeFailureGuidance(failure) { if (failure.reason !== "unreadable-package-json") return [REPAIR_GUIDANCE, inspectGuidance(failure.pluginId)]; return [`Fix file access for ${failure.installPath ? path.join(failure.installPath, "package.json") : "the plugin package.json"} so it is readable by the user running OpenClaw. For EACCES or EPERM, correct its ownership or permissions; otherwise resolve the reported filesystem I/O error, then retry.`, inspectGuidance(failure.pluginId)]; } async function repairInstalledOpenClawHostLinks(params) { const packageReadFailures = []; let effectFailure; const beforePersistentEffect = params.beforePersistentEffect ? () => { if (effectFailure) throw effectFailure.error; try { params.beforePersistentEffect?.(); } catch (error) { effectFailure ??= { error }; throw effectFailure.error; } } : void 0; try { const npmRoots = await listManagedPluginNpmRoots(resolveDefaultPluginNpmDir(params.env)); const results = await Promise.allSettled(npmRoots.map((npmRoot) => relinkOpenClawPeerDependenciesInManagedNpmRoot({ npmRoot, beforePersistentApply: beforePersistentEffect, logger: {}, onPackageReadError: (error, packageDir) => { packageReadFailures.push({ error, packageDir }); } }))); if (effectFailure) throw effectFailure.error; let repaired = 0; for (const result of results) { if (result.status === "rejected") throw result.reason; repaired += result.value.repaired; } const registeredRepair = await reconcileRegisteredOpenClawHostLinks({ installRecords: params.installRecords, extensionsDir: resolveDefaultPluginExtensionsDir(params.env), env: params.env, mode: "repair", beforePersistentApply: beforePersistentEffect, onPackageReadError: (error, packageDir) => { packageReadFailures.push({ error, packageDir }); } }); return { changes: [...repaired > 0 ? [`Repaired OpenClaw host peer link(s) for ${repaired} managed npm plugin package(s).`] : [], ...registeredRepair.repaired > 0 ? [`Repaired OpenClaw host peer link(s) for ${registeredRepair.repaired} registered plugin package(s).`] : []], warnings: [], packageReadFailures }; } catch (err) { if (effectFailure) throw effectFailure.error; beforePersistentEffect?.(); return { changes: [], warnings: [formatPeerLinkPackageReadWarning({ error: err })], packageReadFailures }; } } function formatPeerLinkPackageReadWarning(failure) { const message = `Failed to repair installed OpenClaw host peer links: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`; return { reason: message, message, guidance: [REPAIR_GUIDANCE] }; } /** * Mandatory post-core convergence pass. Runs AFTER the core package files * are swapped and the in-update doctor pass has already returned, but BEFORE * the gateway is restarted. Transient repair fetch failures stay nonblocking; * consent that prevents activation and payload smoke failures are errors. * Gateway startup quarantines known payload failures before any module import, * then boots with those plugins marked configured-unavailable. */ async function runPostCorePluginConvergence(params) { return await withPluginLifecycleLease({ env: params.env, assertCurrent: params.beforePersistentEffect }, (lease) => runPostCorePluginConvergenceWithLease({ ...params, beforePersistentEffect: () => lease.assertOwned() })); } async function runPostCorePluginConvergenceWithLease(params) { const env = resolvePostCoreConvergenceEnv(params.env, params.compatibilityHostVersion); params.beforePersistentEffect?.(); const staleManagedNpmBundledPluginRepair = maybeRepairStaleManagedNpmBundledPlugins({ config: params.cfg, env, prompter: { shouldRepair: true }, ...params.baselineInstallRecords ? { installRecords: params.baselineInstallRecords } : {} }); const convergenceBaseline = staleManagedNpmBundledPluginRepair?.installRecords ?? params.baselineInstallRecords; const prunedBaseline = convergenceBaseline ? pruneStaleLocalBundledPluginInstallRecords({ installRecords: convergenceBaseline, env }) : null; const warnings = []; const repair = await repairMissingConfiguredPluginInstalls({ cfg: params.cfg, timeoutMs: params.timeoutMs, workTimeoutMs: params.workTimeoutMs, env, ...prunedBaseline ? { baselineRecords: prunedBaseline.records } : {}, onCapabilityConsent: params.onCapabilityConsent, onWarning: ({ message, pluginId }) => { warnings.push({ ...pluginId ? { kind: "repair", pluginId } : {}, reason: message, message, guidance: [REPAIR_GUIDANCE] }); }, beforePersistentEffect: params.beforePersistentEffect }); params.beforePersistentEffect?.(); const peerLinkRepair = await repairInstalledOpenClawHostLinks({ env, installRecords: repair.records, beforePersistentEffect: params.beforePersistentEffect }); params.beforePersistentEffect?.(); warnings.push(...peerLinkRepair.warnings); const notices = (repair.notices ?? []).map((message) => ({ reason: message, message, guidance: [] })); const records = repair.records; const recovered = params.configPersistence === "caller" ? await recoverInstalledPluginConfigIds(params.cfg, env) : { config: params.cfg, changes: [], notices: [], recovery: /* @__PURE__ */ new Map() }; params.beforePersistentEffect?.(); notices.push(...recovered.notices.map((message) => ({ reason: message, message, guidance: [REPAIR_GUIDANCE] }))); const smoke = await runActivePluginPayloadSmokeCheck({ cfg: recovered.config, records, env }); const smokeRecords = filterRecordsToActive({ cfg: recovered.config, records, env }); const resolveInstallRecordPaths = (installRecords) => new Set(Object.values(installRecords).flatMap((record) => { const installPath = record.installPath?.trim(); return installPath ? [path.resolve(resolveUserPath(installPath, env))] : []; })); const knownInstallPaths = resolveInstallRecordPaths(records); const activeInstallPaths = resolveInstallRecordPaths(smokeRecords); const smokeFailureInstallPaths = new Set(smoke.failures.flatMap((failure) => failure.installPath ? [path.resolve(failure.installPath)] : [])); for (const failure of peerLinkRepair.packageReadFailures.toSorted((left, right) => left.packageDir.localeCompare(right.packageDir))) { const packageDir = path.resolve(failure.packageDir); const hasTypedFailure = smokeFailureInstallPaths.has(packageDir); const belongsToInactivePlugin = knownInstallPaths.has(packageDir) && !activeInstallPaths.has(packageDir); if (!hasTypedFailure && !belongsToInactivePlugin) warnings.push(formatPeerLinkPackageReadWarning(failure)); } for (const failure of smoke.failures) warnings.push({ kind: "load", pluginId: failure.pluginId, reason: `${failure.reason}: ${failure.detail}`, message: `Plugin "${failure.pluginId}" failed post-core payload smoke check (${failure.reason}): ${failure.detail}`, guidance: smokeFailureGuidance(failure) }); return { config: recovered.config, configChanges: recovered.changes, installedPluginIdRecovery: recovered.recovery, changes: [ ...staleManagedNpmBundledPluginRepair?.removedPluginIds.map((pluginId) => `Removed stale managed install record for bundled plugin "${pluginId}".`) ?? [], ...prunedBaseline?.stale.map((record) => `Removed stale local bundled plugin install record "${record.pluginId}".`) ?? [], ...repair.changes, ...peerLinkRepair.changes ], notices, warnings, outcomes: repair.outcomes, ...repair.repairedPluginIds?.length ? { repairedPluginIds: repair.repairedPluginIds } : {}, errored: repair.outcomes?.some((outcome) => outcome.status === "error" && outcome.code === "PLUGIN_CAPABILITY_CONSENT_REQUIRED") === true || smoke.failures.length > 0, smokeFailures: smoke.failures, installRecords: records }; } //#endregion export { runPostCorePluginConvergence as t };