openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
164 lines (163 loc) • 7.59 kB
JavaScript
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 };