UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

164 lines (163 loc) 7.59 kB
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js"; import "./utils-P__uGsPB.js"; import { t as VERSION } from "./version-v1kuAkGj.js"; import { a as resolveDefaultPluginExtensionsDir, s as resolveDefaultPluginNpmDir } from "./install-paths-ChnxikBv.js"; import { c as listManagedPluginNpmRoots } from "./managed-npm-retention-DB42XOHb.js"; import "./capability-consent-error-details-D4ZVP4Ge.js"; import { a as reconcileRegisteredOpenClawHostLinks, o as relinkOpenClawPeerDependenciesInManagedNpmRoot } from "./plugin-peer-link-60qm_mPq.js"; import { s as UPDATE_POST_CORE_CONVERGENCE_ENV } from "./update-phase-J3w3q1-f.js"; import { n as runActivePluginPayloadSmokeCheck, t as filterRecordsToActive } from "./active-payload-verification-CB3Ex4nX.js"; import { o as pruneStaleLocalBundledPluginInstallRecords, r as maybeRepairStaleManagedNpmBundledPlugins } from "./doctor-plugin-registry-ByqlEVhW.js"; import { t as repairMissingConfiguredPluginInstalls } from "./missing-configured-plugin-install-B3eZm5tD.js"; 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 repairInstalledNpmOpenClawHostLinks(params) { const packageReadFailures = []; try { const npmRoots = await listManagedPluginNpmRoots(resolveDefaultPluginNpmDir(params.env)); const repaired = (await Promise.all(npmRoots.map((npmRoot) => relinkOpenClawPeerDependenciesInManagedNpmRoot({ npmRoot, logger: {}, onPackageReadError: (error, packageDir) => { packageReadFailures.push({ error, packageDir }); } })))).reduce((total, result) => total + result.repaired, 0); const registeredRepair = await reconcileRegisteredOpenClawHostLinks({ installRecords: params.installRecords, extensionsDir: resolveDefaultPluginExtensionsDir(params.env), env: params.env, mode: "repair", 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 npm plugin package(s).`] : []], warnings: [], packageReadFailures }; } catch (err) { const message = `Failed to repair managed npm OpenClaw host peer links: ${err instanceof Error ? err.message : String(err)}`; return { changes: [], warnings: [{ reason: message, message, guidance: [REPAIR_GUIDANCE] }], packageReadFailures }; } } function formatPeerLinkPackageReadWarning(failure) { const message = `Failed to repair managed npm 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) { const env = { ...params.env, OPENCLAW_COMPATIBILITY_HOST_VERSION: params.compatibilityHostVersion ?? VERSION, [UPDATE_POST_CORE_CONVERGENCE_ENV]: "1" }; 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 repair = await repairMissingConfiguredPluginInstalls({ cfg: params.cfg, env, ...prunedBaseline ? { baselineRecords: prunedBaseline.records } : {}, onCapabilityConsent: params.onCapabilityConsent }); const warnings = repair.warnings.map((message) => ({ reason: message, message, guidance: [REPAIR_GUIDANCE] })); const peerLinkRepair = await repairInstalledNpmOpenClawHostLinks({ env, installRecords: repair.records }); warnings.push(...peerLinkRepair.warnings); const notices = (repair.notices ?? []).map((message) => ({ reason: message, message, guidance: [] })); const records = repair.records; const smoke = await runActivePluginPayloadSmokeCheck({ cfg: params.cfg, records, env }); const smokeRecords = filterRecordsToActive({ cfg: params.cfg, records }); 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({ 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 { 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, 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 };