openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
666 lines (665 loc) • 38 kB
JavaScript
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { n as isTruthyEnvValue } from "./env-M3R40TOb.js";
import { h as recoverConfigFromLastKnownGood, m as recoverConfigFromJsonRootSuffix } from "./io.runtime-B9iJRs3w.js";
import { f as resolveHomeDir } from "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { d as resolveCanonicalConfigPath, w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { o as resolveCompatibilityHostVersion } from "./version-v1kuAkGj.js";
import { s as resolveOpenClawStateSqlitePath } from "./openclaw-state-db-schema-version-c1ZL6JGz.js";
import { m as assertOpenClawStateWriteAllowedAtPath } from "./openclaw-state-db-BRTnL-D8.js";
import { _ as resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-wrDsjyMD.js";
import { l as normalizePluginsConfig, u as resolveEffectiveEnableState } from "./config-state-BkU1frVq.js";
import { n as cloneEnvWithPlatformSemantics } from "./config-env-vars-DUfQlcAk.js";
import { d as hashRuntimeConfigValue } from "./runtime-snapshot-BaQikjTR.js";
import { l as logConfigWarningsOnce } from "./io.types-BUCjdS5v.js";
import "./env-vars-CyiONVe6.js";
import { l as parseConfigJson5 } from "./io.read-helpers-ZKp-UiGx.js";
import { n as formatConfigIssueLines } from "./issue-format-CQvxWNW8.js";
import "./io-bdCzpGWJ.js";
import { a as formatPluginVerificationDiagnostic, c as setActiveDegradedPlugins, t as buildDegradedPluginsFromVerificationFailures } from "./runtime-degraded-state-B_-lbcPZ.js";
import { t as note } from "./note-DqHk3fA1.js";
import { r as noteIncludeConfinementWarning } from "./doctor-config-analysis-BuQuAQxk.js";
import { t as migrateLegacyConfig } from "./legacy-config-migrate-ibYHB0mZ.js";
import { r as planAutomaticConfigRepair, t as commitAutomaticConfigRepair } from "./automatic-startup-config-repair-BVYl6aS8.js";
import { n as maybeRepairPluginOpenClawHostLinks } from "./doctor-plugin-host-links-D0NqP-RD.js";
import { t as inspectShippedPluginInstallConfigRecords } from "./plugin-install-config-migration-Cruvbiy0.js";
import { n as createDoctorPluginMetadataSnapshotScope } from "./plugin-metadata-snapshot-scope-BQf8TDa9.js";
import { n as persistRefreshedPluginIndex, r as readDoctorConfigPreflightSnapshot, t as needsRefreshedPluginIndexPersistence } from "./doctor-config-preflight-plugin-index-BxWAbSDh.js";
import { a as recordStartupMigrationWarnings, t as formatStartupMigrationFailure } from "./state-migrations.messages-oWDQJF4s.js";
import { a as throwStartupMigrationRefusal, i as throwStartupMigrationIdentityChanged, n as refuseStartupMigrationsForLiveGatewayOwner, r as throwStartupMigrationGuardRejected } from "./doctor-startup-migration-refusal-Cc83Kqtn.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/commands/doctor/shared/legacy-config-state-migration-input.ts
function resolveStateMigrationConfigInput(params) {
const pluginDoctorConfig = params.snapshot.sourceConfig ?? params.snapshot.config ?? params.snapshot.parsed;
if (params.snapshot.valid) return params.snapshot.legacyIssues.length > 0 && pluginDoctorConfig !== void 0 ? {
cfg: params.baseConfig,
pluginDoctorConfig
} : { cfg: params.baseConfig };
const migrationSource = pluginDoctorConfig ?? params.snapshot.parsed;
if (params.snapshot.legacyIssues.length === 0 || migrationSource === void 0) return null;
const migrated = migrateLegacyConfig(migrationSource);
if (!migrated.config || migrated.partiallyValid) return { pluginDoctorConfig: pluginDoctorConfig ?? migrationSource };
return {
cfg: migrated.config,
...pluginDoctorConfig ? { pluginDoctorConfig } : {}
};
}
//#endregion
//#region src/commands/doctor-config-preflight-checkpoint.ts
function resolveMigrationCheckpointIdentity(params) {
if (!params.snapshot.valid || !params.pluginMigrationFingerprint) return null;
const stateMigrationInput = resolveStateMigrationConfigInput({
snapshot: params.snapshot,
baseConfig: params.baseConfig
});
const effectiveConfig = stateMigrationInput?.cfg ?? params.baseConfig;
const pluginDoctorConfig = stateMigrationInput?.pluginDoctorConfig ?? effectiveConfig;
return {
effectiveConfigFingerprint: hashRuntimeConfigValue(effectiveConfig),
pluginDoctorConfigFingerprint: hashRuntimeConfigValue(pluginDoctorConfig),
pluginMigrationFingerprint: params.pluginMigrationFingerprint
};
}
function migrationCheckpointIdentitiesMatch(left, right) {
return left !== null && right !== null && left.effectiveConfigFingerprint === right.effectiveConfigFingerprint && left.pluginDoctorConfigFingerprint === right.pluginDoctorConfigFingerprint && left.pluginMigrationFingerprint === right.pluginMigrationFingerprint;
}
//#endregion
//#region src/commands/doctor-config-preflight-legacy-config.ts
async function maybeMigrateLegacyConfig() {
const changes = [];
const home = resolveHomeDir();
if (!home) return changes;
const targetPath = resolveCanonicalConfigPath();
const targetDir = path.dirname(targetPath);
try {
await fs.access(targetPath);
return changes;
} catch {}
const legacyCandidates = [path.join(home, ".clawdbot", "clawdbot.json")];
let legacyPath = null;
for (const candidate of legacyCandidates) try {
await fs.access(candidate);
legacyPath = candidate;
break;
} catch {}
if (!legacyPath) return changes;
await fs.mkdir(targetDir, { recursive: true });
try {
await fs.copyFile(legacyPath, targetPath, fs.constants.COPYFILE_EXCL);
changes.push(`Migrated legacy config: ${legacyPath} -> ${targetPath}`);
} catch (error) {
if ((error && typeof error === "object" && "code" in error ? error.code : void 0) !== "EEXIST") throw new Error(`Failed to migrate legacy config ${legacyPath} -> ${targetPath}: ${formatErrorMessage(error)}`, { cause: error });
}
return changes;
}
//#endregion
//#region src/commands/doctor-config-preflight-measure.ts
const startupPreflightTraceStartedAt = performance.now();
async function measureGatewayStartupPreflightStep(name, run) {
if (!isTruthyEnvValue(process.env.OPENCLAW_GATEWAY_STARTUP_TRACE)) return await run();
const startedAt = performance.now();
try {
return await run();
} finally {
const durationMs = performance.now() - startedAt;
const totalMs = performance.now() - startupPreflightTraceStartedAt;
const { formatConsoleDiagnosticLine } = await import("./json-console-line-kiRP3hor.js");
const message = `[gateway] startup trace: cli.bootstrap.${name} ${durationMs.toFixed(1)}ms total=${totalMs.toFixed(1)}ms`;
process.stderr.write(`${formatConsoleDiagnosticLine({
level: "info",
message
})}\n`);
}
}
async function measureDoctorConfigPreflightStep(name, run, measure) {
const tracedRun = () => measureGatewayStartupPreflightStep(name, run);
return measure ? await measure(`doctor.config-preflight.${name}`, tracedRun) : await tracedRun();
}
//#endregion
//#region src/commands/doctor-config-preflight-plugin-verification.ts
async function planStartupPluginVerification(params) {
const { planStartupPluginConvergence } = await measureDoctorConfigPreflightStep("plugin-plan-import", () => import("./startup-plugin-convergence-plan-QTMS_8gb.js"), params.measure);
return await measureDoctorConfigPreflightStep("plugin-plan", () => planStartupPluginConvergence({
config: params.cfg,
env: params.env
}), params.measure);
}
function isStartupPluginVerificationFailureActive(params) {
return resolveEffectiveEnableState({
id: params.failure.pluginId,
origin: "global",
config: normalizePluginsConfig(params.cfg.plugins),
rootConfig: params.cfg
}).enabled;
}
function buildStartupPluginQuarantine(params) {
return buildDegradedPluginsFromVerificationFailures(params.failures.filter((failure) => Boolean(failure.installPath) && isStartupPluginVerificationFailureActive({
cfg: params.cfg,
failure
})));
}
function formatStartupPluginSmokeFailure(failure) {
return `Plugin "${failure.pluginId}": ${formatPluginVerificationDiagnostic({
kind: "plugin-verification",
reason: failure.reason,
detail: failure.detail,
...failure.installPath ? { installPath: failure.installPath } : {}
})}. Run \`openclaw update repair\` to retry plugin repair.`;
}
async function runStartupUpgradeConvergence(params) {
if (!(await planStartupPluginVerification(params)).required) return {
blockingDiagnostic: null,
quarantinedPlugins: []
};
const { runPostCorePluginConvergence } = await measureDoctorConfigPreflightStep("plugin-convergence-import", () => import("./post-core-plugin-convergence-wOcrIKaN.js"), params.measure);
const convergence = await measureDoctorConfigPreflightStep("plugin-convergence", () => runPostCorePluginConvergence({
cfg: params.cfg,
env: params.env,
compatibilityHostVersion: resolveCompatibilityHostVersion(params.env)
}), params.measure);
if (convergence.changes.length > 0) note(convergence.changes.map((entry) => `- ${entry}`).join("\n"), "Doctor changes");
const notices = convergence.notices ?? [];
if (notices.length > 0) note(notices.map((notice) => `- ${notice.message} ${notice.guidance.join(" ")}`.trim()).join("\n"), "Doctor notices");
const warnings = convergence.warnings.map((warning) => `${warning.message} ${warning.guidance.join(" ")}`.trim());
if (warnings.length > 0) note(warnings.map((warning) => `- ${warning}`).join("\n"), "Doctor warnings");
const quarantinedPlugins = buildStartupPluginQuarantine({
cfg: params.cfg,
failures: convergence.smokeFailures
});
const nonBlockingWarningKeys = new Set(convergence.smokeFailures.filter((failure) => Boolean(failure.installPath) || !isStartupPluginVerificationFailureActive({
cfg: params.cfg,
failure
})).map((failure) => JSON.stringify([failure.pluginId, `${failure.reason}: ${failure.detail}`])));
const blockingMessages = convergence.warnings.filter((warning) => !warning.pluginId || !nonBlockingWarningKeys.has(JSON.stringify([warning.pluginId, warning.reason]))).map((warning) => `${warning.message} ${warning.guidance.join(" ")}`.trim());
return {
blockingDiagnostic: blockingMessages.length > 0 ? {
kind: "plugin-verification",
messages: blockingMessages
} : null,
quarantinedPlugins
};
}
async function refreshStartupPluginQuarantine(params) {
const plan = await planStartupPluginVerification(params);
if (!plan.required) return {
blockingDiagnostic: null,
quarantinedPlugins: []
};
const { runActivePluginPayloadSmokeCheck } = await measureDoctorConfigPreflightStep("plugin-payload-verification-import", () => import("./active-payload-verification-n014Obex.js"), params.measure);
const smoke = await measureDoctorConfigPreflightStep("plugin-payload-verification", () => runActivePluginPayloadSmokeCheck({
cfg: params.cfg,
records: plan.installRecords,
env: params.env
}), params.measure);
const result = mapStartupPluginQuarantineRefresh({
cfg: params.cfg,
failures: smoke.failures
});
if (result.quarantinedPlugins.length > 0) note(result.quarantinedPlugins.map((plugin) => `- ${formatStartupPluginSmokeFailure({
pluginId: plugin.pluginId,
reason: plugin.diagnostic.reason,
detail: plugin.diagnostic.detail,
...plugin.diagnostic.installPath ? { installPath: plugin.diagnostic.installPath } : {}
})}`).join("\n"), "Doctor warnings");
return result;
}
function mapStartupPluginQuarantineRefresh(params) {
const quarantinedPlugins = buildStartupPluginQuarantine(params);
const blockingFailures = params.failures.filter((failure) => !failure.installPath && isStartupPluginVerificationFailureActive({
cfg: params.cfg,
failure
}));
return {
blockingDiagnostic: blockingFailures.length > 0 ? {
kind: "plugin-verification",
messages: blockingFailures.map(formatStartupPluginSmokeFailure)
} : null,
quarantinedPlugins
};
}
function formatStartupPluginVerificationFailure(diagnostic) {
return [
"OpenClaw plugin verification failed; refusing to report the gateway ready.",
...diagnostic.messages.map((message) => `- ${message}`),
"Resolve the plugin verification errors above, then restart the Gateway."
].join("\n");
}
//#endregion
//#region src/commands/doctor-config-preflight-startup.ts
/** Completes startup verification and returns the accepted config and metadata generation. */
async function completeStartupMigrationPreflight(params) {
let snapshotRead = params.snapshotRead;
const snapshot = snapshotRead.snapshot;
const baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
if ((params.shouldRecordStateCheckpoint || params.shouldRecordStartupCheckpoint) && params.startupMigrationHeartbeatError) throw params.startupMigrationHeartbeatError instanceof Error ? params.startupMigrationHeartbeatError : /* @__PURE__ */ new Error("OpenClaw startup migration lease heartbeat failed.");
if (params.shouldRecordStateCheckpoint && params.stateMigrationsAllowed && params.freshConfigGuardAllowed && params.startupMigrationWarnings.length === 0 && snapshot.valid) {
if (!params.migrationCheckpoint) throw new Error("OpenClaw state migration checkpoint module was not loaded.");
params.migrationCheckpoint.recordSuccessfulStateMigrations({
env: params.startupMigrationEnv,
identity: params.migrationCheckpointIdentity,
lease: params.startupMigrationLease
});
}
if (params.gatewayStartupCheckpointRequired) {
if (params.shouldRecordStartupCheckpoint && !snapshot.valid) throwStartupMigrationRefusal(formatStartupMigrationFailure(["OpenClaw config is invalid; run \"openclaw doctor --fix\" before startup."]));
setActiveDegradedPlugins([]);
if (snapshot.valid) {
const pluginConvergence = params.shouldRecordStartupCheckpoint ? await runStartupUpgradeConvergence({
cfg: baseConfig,
env: process.env,
...params.measure ? { measure: params.measure } : {}
}) : await refreshStartupPluginQuarantine({
cfg: baseConfig,
env: process.env,
...params.measure ? { measure: params.measure } : {}
});
setActiveDegradedPlugins(pluginConvergence.quarantinedPlugins);
if (pluginConvergence.blockingDiagnostic) throwStartupMigrationRefusal(formatStartupPluginVerificationFailure(pluginConvergence.blockingDiagnostic));
if (params.shouldRecordStartupCheckpoint) {
const convergedSnapshotRead = await params.readConfigSnapshotForPreflight(false);
const convergedBaseConfig = convergedSnapshotRead.snapshot.sourceConfig ?? convergedSnapshotRead.snapshot.config ?? {};
const convergedIdentity = resolveMigrationCheckpointIdentity({
snapshot: convergedSnapshotRead.snapshot,
baseConfig: convergedBaseConfig,
pluginMigrationFingerprint: convergedSnapshotRead.pluginMigrationFingerprint
});
if (!migrationCheckpointIdentitiesMatch(params.migrationCheckpointIdentity, convergedIdentity)) throwStartupMigrationIdentityChanged();
snapshotRead = convergedSnapshotRead;
}
}
recordStartupMigrationWarnings(params.startupMigrationWarnings);
}
if (params.shouldRecordStartupCheckpoint && params.startupMigrationWarnings.length === 0) {
if (!params.migrationCheckpoint) throw new Error("OpenClaw startup migration checkpoint module was not loaded.");
params.migrationCheckpoint.recordSuccessfulStartupMigrations({
env: params.startupMigrationEnv,
identity: params.migrationCheckpointIdentity,
lease: params.startupMigrationLease
});
}
return snapshotRead;
}
function noteStateMigrationResult(result) {
for (const key of [
"changes",
"notices",
"warnings"
]) if (result[key]?.length) note(result[key].map((entry) => `- ${entry}`).join("\n"), `Doctor ${key}`);
}
//#endregion
//#region src/commands/doctor-config-preflight.cron.ts
/** Restores retired cron migration inputs that canonical config migration intentionally strips. */
function withLegacyConfig(config, legacyConfig) {
const legacyCron = legacyConfig?.cron;
if (!legacyCron || !Object.hasOwn(legacyCron, "store") && !Object.hasOwn(legacyCron, "webhook")) return config;
return {
...config,
cron: {
...config.cron,
...Object.hasOwn(legacyCron, "store") ? { store: legacyCron.store } : {},
...Object.hasOwn(legacyCron, "webhook") ? { webhook: legacyCron.webhook } : {}
}
};
}
/** Isolates the trusted partition selector from a partially valid legacy config. */
function retainStoreConfig(config) {
const cron = config?.cron;
if (typeof cron?.store !== "string" || !cron.store.trim()) return;
return { cron: {
store: cron.store,
...Object.hasOwn(cron, "webhook") ? { webhook: cron.webhook } : {}
} };
}
//#endregion
//#region src/commands/doctor-config-preflight.ts
/** Config preflight for doctor: legacy config/state migration, recovery, and snapshot loading. */
const loadStateDirMigrations = createLazyRuntimeModule(() => import("./state-migrations.state-dir-Df6o1cH0.js"));
const loadLegacyCronRepair = createLazyRuntimeModule(() => import("./legacy-repair-_zbLO8QN.js"));
const configLog = createSubsystemLogger("config");
/** Returns true during updater-managed config rewrites where plugin validation may be stale. */
function shouldSkipPluginValidationForDoctorConfigPreflight(env = process.env) {
return isTruthyEnvValue(env.OPENCLAW_UPDATE_IN_PROGRESS);
}
/**
* Runs early doctor config checks before the main config repair flow.
*
* It may migrate legacy state/config paths, recover corrupt target config when requested, and
* returns the best-effort config snapshot used by later doctor checks.
*/
async function runDoctorConfigPreflight(options = {}) {
const stateMigrationsRequested = options.migrateState !== false;
const gatewayStartupCheckpointRequired = options.requireStartupMigrationCheckpoint === true;
const migrationLog = gatewayStartupCheckpointRequired ? {
info() {},
warn() {}
} : void 0;
if (gatewayStartupCheckpointRequired) await refuseStartupMigrationsForLiveGatewayOwner(process.env);
if (stateMigrationsRequested) await assertOpenClawStateWriteAllowedAtPath({
databasePath: resolveOpenClawStateSqlitePath(process.env),
env: process.env
});
const measurePreflightStep = (name, run) => measureDoctorConfigPreflightStep(name, run, options.measure);
let migrationCheckpoint = gatewayStartupCheckpointRequired || options.requireStateMigrationCheckpoint === true ? await measurePreflightStep("startup-checkpoint-import", () => import("./startup-migration-checkpoint-JEz9ELDC.js")) : void 0;
let startupMigrationEnv = process.env;
let shouldRecordStateCheckpoint = false;
let shouldRecordStartupCheckpoint = false;
let shouldPersistRefreshedPluginIndex = false;
let migrationCheckpointIdentity = null;
let skipPristineStartupStateMigrations = options.skipPristineStartupStateMigrations === true;
let skipPristineCoreStateMigrations = skipPristineStartupStateMigrations || options.skipPristineCoreStateMigrations === true;
let startupMigrationLease;
let startupMigrationHeartbeat;
let startupMigrationHeartbeatError;
const startupMigrationWarnings = [];
const cronCodexRuntimePolicyTargets = [];
let doctorMediaPersistenceAttempted = false;
let legacyConfigMigrationComplete = false;
let configSnapshotRead;
const { run: runWithPluginMetadataSnapshot } = createDoctorPluginMetadataSnapshotScope({
getBaseSnapshot: () => configSnapshotRead?.pluginMetadataSnapshot,
env: process.env
});
const refreshMigrationCheckpoint = (checkpoint, snapshotRead) => {
const { snapshot, pluginMigrationFingerprint } = snapshotRead;
migrationCheckpointIdentity = resolveMigrationCheckpointIdentity({
snapshot,
baseConfig: snapshot.sourceConfig ?? snapshot.config ?? {},
pluginMigrationFingerprint
});
shouldRecordStateCheckpoint = stateMigrationsRequested;
shouldRecordStartupCheckpoint = gatewayStartupCheckpointRequired;
if (shouldRecordStateCheckpoint || shouldRecordStartupCheckpoint) {
const checkpointStatus = checkpoint.readMigrationCheckpointStatus({
env: startupMigrationEnv,
identity: migrationCheckpointIdentity
});
shouldRecordStateCheckpoint &&= checkpointStatus === "stale";
shouldRecordStartupCheckpoint &&= checkpointStatus !== "startup-current";
}
shouldPersistRefreshedPluginIndex = needsRefreshedPluginIndexPersistence(snapshotRead);
};
const ensureStartupMigrationLease = async () => {
if (startupMigrationLease || !migrationCheckpoint) return;
if (gatewayStartupCheckpointRequired) await refuseStartupMigrationsForLiveGatewayOwner(startupMigrationEnv);
startupMigrationLease = await migrationCheckpoint.acquireStartupMigrationLeaseWithWait({ env: startupMigrationEnv });
configSnapshotRead = await readConfigSnapshotForPreflight(false);
refreshMigrationCheckpoint(migrationCheckpoint, configSnapshotRead);
if (!shouldRecordStateCheckpoint && !shouldRecordStartupCheckpoint && !shouldPersistRefreshedPluginIndex) {
startupMigrationLease.release();
startupMigrationLease = void 0;
return;
}
startupMigrationHeartbeat = setInterval(() => {
try {
startupMigrationLease?.heartbeat();
} catch (error) {
startupMigrationHeartbeatError = error;
}
}, 6e4);
startupMigrationHeartbeat.unref?.();
};
const noteStartupStateMigrationResult = (result) => {
startupMigrationWarnings.push(...result.warnings);
noteStateMigrationResult({
...result,
warnings: gatewayStartupCheckpointRequired ? [] : result.warnings
});
};
const migrateLegacyConfigIfNeeded = async () => {
if (legacyConfigMigrationComplete) return;
legacyConfigMigrationComplete = true;
if (options.migrateLegacyConfig === false) return;
const legacyConfigChanges = await measurePreflightStep("legacy-config-migration", maybeMigrateLegacyConfig);
if (legacyConfigChanges.length > 0) note(legacyConfigChanges.map((entry) => `- ${entry}`).join("\n"), "Doctor changes");
};
const readConfigSnapshotForPreflight = async (allowCurrentPluginMetadata = true) => await measurePreflightStep("config-snapshot", () => readDoctorConfigPreflightSnapshot({
allowCurrentPluginMetadata,
includePluginMetadata: Boolean(migrationCheckpoint) || options.preparePluginMetadataSnapshot === true,
measure: options.measure,
observe: options.observe,
preparePluginMetadataSnapshot: options.preparePluginMetadataSnapshot === true,
skipPluginValidation: shouldSkipPluginValidationForDoctorConfigPreflight()
}));
try {
if (migrationCheckpoint && !skipPristineStartupStateMigrations) {
const { planPristineStartupStateMigrations } = await measurePreflightStep("pristine-state-plan-import", () => import("./pristine-startup-state-DBk_ZbwQ.js"));
const pristineStatePlan = await measurePreflightStep("pristine-state-plan", () => planPristineStartupStateMigrations(process.env));
skipPristineStartupStateMigrations = pristineStatePlan.skipAllStateMigrations;
skipPristineCoreStateMigrations ||= pristineStatePlan.skipCoreStateMigrations;
}
if (skipPristineStartupStateMigrations && !gatewayStartupCheckpointRequired) migrationCheckpoint = void 0;
const stateMigrationsAllowed = !stateMigrationsRequested || options.beforeStateMigrations === void 0 || await measurePreflightStep("state-migration-guard", () => options.beforeStateMigrations?.());
if (gatewayStartupCheckpointRequired && !stateMigrationsAllowed) throwStartupMigrationGuardRejected();
if (migrationCheckpoint) {
await migrateLegacyConfigIfNeeded();
configSnapshotRead = await readConfigSnapshotForPreflight();
startupMigrationEnv = cloneEnvWithPlatformSemantics(process.env);
refreshMigrationCheckpoint(migrationCheckpoint, configSnapshotRead);
if (shouldRecordStateCheckpoint || shouldRecordStartupCheckpoint || shouldPersistRefreshedPluginIndex) await ensureStartupMigrationLease();
}
const stateDirMigrations = stateMigrationsRequested && (!migrationCheckpoint || shouldRecordStateCheckpoint) && !skipPristineStartupStateMigrations ? await measurePreflightStep("state-migrations-import", loadStateDirMigrations) : void 0;
if (stateDirMigrations && stateMigrationsAllowed) {
const { autoMigrateLegacyStateDir } = stateDirMigrations;
noteStartupStateMigrationResult(await measurePreflightStep("state-dir-migrations", () => autoMigrateLegacyStateDir({
env: process.env,
log: migrationLog
})));
}
await migrateLegacyConfigIfNeeded();
if (!configSnapshotRead || stateDirMigrations) configSnapshotRead = await readConfigSnapshotForPreflight(!stateDirMigrations);
let snapshot = configSnapshotRead.snapshot;
let activeConfigRepair = null;
if (options.repairPrefixedConfig === true && snapshot.exists && !snapshot.valid) {
const pendingPluginInstallConfig = inspectShippedPluginInstallConfigRecords(snapshot.sourceConfig).status !== "missing";
activeConfigRepair = typeof snapshot.raw === "string" && parseConfigJson5(snapshot.raw).ok ? runWithPluginMetadataSnapshot({ config: snapshot.sourceConfig ?? snapshot.config ?? {} }, () => planAutomaticConfigRepair(snapshot)) : null;
let configRepaired = false;
if (!activeConfigRepair && await recoverConfigFromJsonRootSuffix(snapshot)) {
note("Removed non-JSON prefix from openclaw.json; original saved as .clobbered.*.", "Config");
configRepaired = true;
} else if (!activeConfigRepair && !pendingPluginInstallConfig && await recoverConfigFromLastKnownGood({
snapshot,
reason: "doctor-invalid-config"
})) {
note("Restored openclaw.json from last-known-good; original saved as .clobbered.*.", "Config");
configRepaired = true;
}
if (configRepaired) {
configSnapshotRead = await readConfigSnapshotForPreflight(false);
snapshot = configSnapshotRead.snapshot;
}
if (!snapshot.valid && typeof snapshot.raw === "string" && !parseConfigJson5(snapshot.raw).ok) throw new Error(`Config at ${snapshot.path} is not parseable and cannot be repaired automatically. The file remains unchanged. Inspect the exact parse error with ${formatCliCommand("openclaw config validate")}, then hand-edit the file; or move it aside and run ${formatCliCommand("openclaw onboard")} to generate a fresh config.`);
}
const invalidConfigNote = options.invalidConfigNote ?? "Config invalid; doctor will run with best-effort config.";
if (invalidConfigNote && snapshot.exists && !snapshot.valid && !activeConfigRepair && snapshot.legacyIssues.length === 0) {
note(invalidConfigNote, "Config");
noteIncludeConfinementWarning(snapshot);
}
const warnings = snapshot.warnings ?? [];
if (warnings.length > 0) {
if (process.stdout.isTTY) note(formatConfigIssueLines(warnings, "-").join("\n"), "Config warnings");
else logConfigWarningsOnce({
configPath: snapshot.path,
warnings,
logger: configLog
});
}
let baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
const automaticConfigRepair = activeConfigRepair ?? (gatewayStartupCheckpointRequired && !snapshot.valid && !shouldSkipPluginValidationForDoctorConfigPreflight() ? runWithPluginMetadataSnapshot({ config: baseConfig }, () => planAutomaticConfigRepair(snapshot)) : null);
const stateMigrationInput = resolveStateMigrationConfigInput({
snapshot,
baseConfig
});
if (migrationCheckpoint) migrationCheckpointIdentity = resolveMigrationCheckpointIdentity({
snapshot,
baseConfig,
pluginMigrationFingerprint: configSnapshotRead.pluginMigrationFingerprint
});
shouldPersistRefreshedPluginIndex = migrationCheckpoint !== void 0 && needsRefreshedPluginIndexPersistence(configSnapshotRead);
if (shouldPersistRefreshedPluginIndex) await ensureStartupMigrationLease();
const freshConfigGuardAllowed = !(stateDirMigrations !== void 0 || shouldRecordStateCheckpoint || shouldRecordStartupCheckpoint || shouldPersistRefreshedPluginIndex) || !stateMigrationsAllowed || options.beforeStateMigrations === void 0 || await measurePreflightStep("fresh-config-guard", () => options.beforeStateMigrations?.(snapshot));
if (gatewayStartupCheckpointRequired && !freshConfigGuardAllowed) throwStartupMigrationGuardRejected();
if (stateDirMigrations && stateMigrationsAllowed && freshConfigGuardAllowed) {
if (options.doctorOnlyStateMigrations === true) {
const { detectLegacyExecApprovals, migrateLegacyExecApprovals } = await import("./state-migrations.exec-approvals-N28k6cmC.js");
const stateDir = resolveStateDir(process.env);
noteStartupStateMigrationResult(await measurePreflightStep("exec-approvals-migration", () => migrateLegacyExecApprovals({
detected: detectLegacyExecApprovals({
stateDir,
doctorOnlyStateMigrations: true
}),
stateDir,
env: process.env
})));
}
if (gatewayStartupCheckpointRequired && (snapshot.valid || automaticConfigRepair)) {
if (!startupMigrationLease) throw new Error("Startup plugin host-link repair requires the startup migration lease.");
await measurePreflightStep("plugin-host-link-repair", () => maybeRepairPluginOpenClawHostLinks({
env: startupMigrationEnv,
prompter: { shouldRepair: true }
}));
}
const { autoMigrateLegacyTaskStateSidecars } = stateDirMigrations;
if (stateMigrationInput) {
const pluginDoctorOnlyConfig = stateMigrationInput.pluginDoctorConfig ?? stateMigrationInput.cfg;
if (skipPristineCoreStateMigrations && pluginDoctorOnlyConfig && !retainStoreConfig(pluginDoctorOnlyConfig)) {
const { autoMigrateLegacyPluginDoctorState } = await import("./state-migrations.plugin-doctor-abwlXT7T.js");
noteStartupStateMigrationResult(await measurePreflightStep("plugin-doctor-migrations", () => runWithPluginMetadataSnapshot({ config: pluginDoctorOnlyConfig }, () => autoMigrateLegacyPluginDoctorState({
config: pluginDoctorOnlyConfig,
env: process.env,
log: migrationLog,
...options.doctorOnlyStateMigrations === true ? { doctorOnlyStateMigrations: true } : {}
}))));
} else if (stateMigrationInput.cfg) {
const { autoMigrateLegacyState } = await import("./state-migrations.doctor-DtjzVXti.js");
const migrationConfig = stateMigrationInput.cfg;
const pluginDoctorConfig = stateMigrationInput.pluginDoctorConfig;
const { collectCronCodexRuntimePolicyTargetsReadOnly, repairLegacyCronStoreWithoutPrompt } = await measurePreflightStep("cron-repair-import", loadLegacyCronRepair);
noteStartupStateMigrationResult(await measurePreflightStep("cron-repair", () => repairLegacyCronStoreWithoutPrompt({
cfg: withLegacyConfig(migrationConfig, pluginDoctorConfig),
migrateCodexModelRefs: false
})));
if (options.repairPrefixedConfig === true) {
const cronCodexPlan = await measurePreflightStep("cron-policy-scan", () => collectCronCodexRuntimePolicyTargetsReadOnly({ cfg: migrationConfig }));
cronCodexRuntimePolicyTargets.push(...cronCodexPlan.targets);
noteStartupStateMigrationResult({
changes: [],
warnings: cronCodexPlan.warnings
});
}
const legacyStateResult = await measurePreflightStep("legacy-state-migrations", () => runWithPluginMetadataSnapshot({ config: pluginDoctorConfig ?? migrationConfig }, () => autoMigrateLegacyState({
cfg: migrationConfig,
...pluginDoctorConfig ? { pluginDoctorConfig } : {},
env: process.env,
log: migrationLog,
recoverCorruptTargetStore: options.recoverCorruptTargetStore,
doctorOnlyStateMigrations: options.doctorOnlyStateMigrations,
...gatewayStartupCheckpointRequired ? { allowLegacyDeviceIdentityImport: true } : {}
})));
doctorMediaPersistenceAttempted = options.doctorOnlyStateMigrations === true;
noteStartupStateMigrationResult(legacyStateResult);
} else if (stateMigrationInput.pluginDoctorConfig) {
const pluginDoctorConfig = stateMigrationInput.pluginDoctorConfig;
const cronMigrationConfig = retainStoreConfig(pluginDoctorConfig);
if (cronMigrationConfig) {
const { repairLegacyCronStoreWithoutPrompt } = await measurePreflightStep("cron-repair-import", loadLegacyCronRepair);
noteStartupStateMigrationResult(await measurePreflightStep("cron-repair", () => repairLegacyCronStoreWithoutPrompt({
cfg: cronMigrationConfig,
migrateCodexModelRefs: false
})));
const { migrateLegacyConfigMachineState } = await import("./state-migrations.config-machine-state-D4Kwqyum.js");
noteStartupStateMigrationResult(migrateLegacyConfigMachineState({
config: pluginDoctorConfig,
env: process.env
}));
}
const { autoMigrateLegacyPluginDoctorState } = await import("./state-migrations.plugin-doctor-abwlXT7T.js");
noteStartupStateMigrationResult(await measurePreflightStep("plugin-doctor-migrations", () => runWithPluginMetadataSnapshot({ config: pluginDoctorConfig }, () => autoMigrateLegacyPluginDoctorState({
config: pluginDoctorConfig,
env: process.env,
log: migrationLog,
...options.doctorOnlyStateMigrations === true ? { doctorOnlyStateMigrations: true } : {}
}))));
noteStartupStateMigrationResult(await measurePreflightStep("task-sidecar-migrations", () => autoMigrateLegacyTaskStateSidecars({
env: process.env,
log: migrationLog
})));
}
} else noteStartupStateMigrationResult(await measurePreflightStep("task-sidecar-migrations", () => autoMigrateLegacyTaskStateSidecars({
env: process.env,
log: migrationLog
})));
}
if (stateDirMigrations && stateMigrationsAllowed && freshConfigGuardAllowed && options.doctorOnlyStateMigrations === true && !doctorMediaPersistenceAttempted) {
const { migrateLegacyMediaPersistence } = await import("./state-migrations.media-persistence-Ccydkmqd.js");
noteStartupStateMigrationResult(await measurePreflightStep("media-persistence-migration", () => migrateLegacyMediaPersistence({ env: process.env })));
}
if (automaticConfigRepair && stateMigrationsAllowed && freshConfigGuardAllowed) {
if (gatewayStartupCheckpointRequired && !startupMigrationLease) throw new Error("Automatic startup config repair requires the startup migration lease.");
if (!(options.beforeStateMigrations === void 0 || await measurePreflightStep("startup-config-repair-guard", () => options.beforeStateMigrations?.()))) throwStartupMigrationGuardRejected();
startupMigrationLease?.heartbeat();
await measurePreflightStep("automatic-config-repair", () => runWithPluginMetadataSnapshot({ config: automaticConfigRepair.config }, () => commitAutomaticConfigRepair(automaticConfigRepair, snapshot)));
note(`Migrated legacy config keys${activeConfigRepair ? " in the active openclaw.json" : " at startup"}:\n${automaticConfigRepair.changes.map((entry) => `- ${entry}`).join("\n")}`, "Doctor changes");
configSnapshotRead = await readConfigSnapshotForPreflight(false);
snapshot = configSnapshotRead.snapshot;
baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
if (options.beforeStateMigrations && !await measurePreflightStep("repaired-config-guard", () => options.beforeStateMigrations?.(snapshot))) throwStartupMigrationGuardRejected();
if (migrationCheckpoint) refreshMigrationCheckpoint(migrationCheckpoint, configSnapshotRead);
}
if (migrationCheckpoint && configSnapshotRead.pluginMetadataSnapshot && configSnapshotRead.pluginMetadataSnapshot.policyHash !== resolveInstalledPluginIndexPolicyHash(baseConfig, startupMigrationEnv)) {
configSnapshotRead = await readConfigSnapshotForPreflight(false);
snapshot = configSnapshotRead.snapshot;
baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
refreshMigrationCheckpoint(migrationCheckpoint, configSnapshotRead);
}
if (shouldPersistRefreshedPluginIndex && stateMigrationsAllowed && freshConfigGuardAllowed && snapshot.valid) {
const persistedSnapshotRead = await persistRefreshedPluginIndex({
env: startupMigrationEnv,
lease: startupMigrationLease,
measure: measurePreflightStep,
readPersistedSnapshot: () => readConfigSnapshotForPreflight(false),
snapshotRead: configSnapshotRead
});
const persistedBaseConfig = persistedSnapshotRead.snapshot.sourceConfig ?? persistedSnapshotRead.snapshot.config ?? {};
const persistedIdentity = resolveMigrationCheckpointIdentity({
snapshot: persistedSnapshotRead.snapshot,
baseConfig: persistedBaseConfig,
pluginMigrationFingerprint: persistedSnapshotRead.pluginMigrationFingerprint
});
if (!migrationCheckpointIdentity || !persistedIdentity || migrationCheckpointIdentity.effectiveConfigFingerprint !== persistedIdentity.effectiveConfigFingerprint || migrationCheckpointIdentity.pluginDoctorConfigFingerprint !== persistedIdentity.pluginDoctorConfigFingerprint) throw new Error("OpenClaw config identity changed while persisting the refreshed plugin registry; refusing to write the migration checkpoint. Run \"openclaw doctor --fix\" and retry.");
configSnapshotRead = persistedSnapshotRead;
migrationCheckpointIdentity = persistedIdentity;
}
configSnapshotRead = await completeStartupMigrationPreflight({
freshConfigGuardAllowed,
gatewayStartupCheckpointRequired,
migrationCheckpoint,
migrationCheckpointIdentity,
measure: options.measure,
readConfigSnapshotForPreflight,
shouldRecordStartupCheckpoint,
shouldRecordStateCheckpoint,
snapshotRead: configSnapshotRead,
startupMigrationEnv,
startupMigrationHeartbeatError,
startupMigrationLease,
startupMigrationWarnings,
stateMigrationsAllowed
});
snapshot = configSnapshotRead.snapshot;
baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
return {
snapshot,
baseConfig,
...configSnapshotRead.pluginMetadataSnapshot ? { pluginMetadataSnapshot: configSnapshotRead.pluginMetadataSnapshot } : {},
...cronCodexRuntimePolicyTargets.length > 0 ? { cronCodexRuntimePolicyTargets } : {}
};
} finally {
if (startupMigrationHeartbeat) clearInterval(startupMigrationHeartbeat);
startupMigrationLease?.release();
}
}
//#endregion
export { shouldSkipPluginValidationForDoctorConfigPreflight as n, runDoctorConfigPreflight as t };