openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
124 lines (123 loc) • 5.65 kB
JavaScript
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js";
import { f as resolveHomeDir } from "./utils-CCC-BEJH.js";
import { _ as recoverConfigFromLastKnownGood, g as recoverConfigFromJsonRootSuffix, u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js";
import { n as formatConfigIssueLines } from "./issue-format-CwCoGNbt.js";
import { t as note } from "./note-BRSJp0UF.js";
import { r as noteIncludeConfinementWarning } from "./doctor-config-analysis-BUgmtVDJ.js";
import { t as findDoctorLegacyConfigIssues } from "./legacy-config-issues-BOTisc-X.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/commands/doctor-config-preflight.ts
/** Config preflight for doctor: legacy config/state migration, recovery, and snapshot loading. */
let doctorStateMigrationsPromise = null;
let doctorCronPromise = null;
function loadDoctorStateMigrations() {
doctorStateMigrationsPromise ??= import("./doctor-state-migrations-DN_uDqjB.js");
return doctorStateMigrationsPromise;
}
function loadDoctorCron() {
doctorCronPromise ??= import("./cron-DftHso_S.js");
return doctorCronPromise;
}
async function maybeMigrateLegacyConfig() {
const changes = [];
const home = resolveHomeDir();
if (!home) return changes;
const targetDir = path.join(home, ".openclaw");
const targetPath = path.join(targetDir, "openclaw.json");
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 {}
return changes;
}
function collectDoctorLegacyIssues(snapshot) {
if (!snapshot.exists) return [];
const resolvedRaw = snapshot.sourceConfig ?? snapshot.config ?? {};
return findDoctorLegacyConfigIssues(resolvedRaw, snapshot.parsed ?? resolvedRaw);
}
function addDoctorLegacyIssues(snapshot) {
const legacyIssues = collectDoctorLegacyIssues(snapshot);
if (legacyIssues.length === 0) return snapshot;
return {
...snapshot,
legacyIssues
};
}
/** 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);
}
function noteStateMigrationResult(result) {
if (result.changes.length > 0) note(result.changes.map((entry) => `- ${entry}`).join("\n"), "Doctor changes");
if (result.warnings.length > 0) note(result.warnings.map((entry) => `- ${entry}`).join("\n"), "Doctor warnings");
}
/**
* 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 = {}) {
if (options.migrateState !== false) {
const { autoMigrateLegacyStateDir } = await loadDoctorStateMigrations();
noteStateMigrationResult(await autoMigrateLegacyStateDir({ env: process.env }));
}
if (options.migrateLegacyConfig !== false) {
const legacyConfigChanges = await maybeMigrateLegacyConfig();
if (legacyConfigChanges.length > 0) note(legacyConfigChanges.map((entry) => `- ${entry}`).join("\n"), "Doctor changes");
}
const readOptions = { skipPluginValidation: shouldSkipPluginValidationForDoctorConfigPreflight() };
let snapshot = addDoctorLegacyIssues(await readConfigFileSnapshot(readOptions));
if (options.repairPrefixedConfig === true && snapshot.exists && !snapshot.valid) {
if (await recoverConfigFromJsonRootSuffix(snapshot)) {
note("Removed non-JSON prefix from openclaw.json; original saved as .clobbered.*.", "Config");
snapshot = addDoctorLegacyIssues(await readConfigFileSnapshot(readOptions));
} else if (await recoverConfigFromLastKnownGood({
snapshot,
reason: "doctor-invalid-config"
})) {
note("Restored openclaw.json from last-known-good; original saved as .clobbered.*.", "Config");
snapshot = addDoctorLegacyIssues(await readConfigFileSnapshot(readOptions));
}
}
const invalidConfigNote = options.invalidConfigNote ?? "Config invalid; doctor will run with best-effort config.";
if (invalidConfigNote && snapshot.exists && !snapshot.valid && snapshot.legacyIssues.length === 0) {
note(invalidConfigNote, "Config");
noteIncludeConfinementWarning(snapshot);
}
const warnings = snapshot.warnings ?? [];
if (warnings.length > 0) note(formatConfigIssueLines(warnings, "-").join("\n"), "Config warnings");
const baseConfig = snapshot.sourceConfig ?? snapshot.config ?? {};
if (options.migrateState !== false) {
if (snapshot.valid) {
const { repairLegacyCronStoreWithoutPrompt } = await loadDoctorCron();
noteStateMigrationResult(await repairLegacyCronStoreWithoutPrompt({ cfg: baseConfig }));
}
const { autoMigrateLegacyState, autoMigrateLegacyTaskStateSidecars } = await loadDoctorStateMigrations();
noteStateMigrationResult(snapshot.valid ? await autoMigrateLegacyState({
cfg: baseConfig,
env: process.env,
recoverCorruptTargetStore: options.recoverCorruptTargetStore
}) : await autoMigrateLegacyTaskStateSidecars({ env: process.env }));
}
return {
snapshot,
baseConfig
};
}
//#endregion
export { shouldSkipPluginValidationForDoctorConfigPreflight as n, runDoctorConfigPreflight as t };