openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
106 lines (105 loc) • 5.27 kB
JavaScript
import { k as applyLegacyDoctorMigrations } from "./io.runtime-B9iJRs3w.js";
import { t as findLegacyConfigIssues } from "./legacy-D15RYgFr.js";
import { K as stampConfigWriteMetadata, _ as validateConfigObjectRaw, c as resolveManagedUnsetPathsForWrite, h as validateConfigObjectWithPlugins, s as applyUnsetPathsForWrite } from "./io.types-BUCjdS5v.js";
import { h as resolveConfigSnapshotHash, r as containsConfigIncludeDirective } from "./io.read-helpers-ZKp-UiGx.js";
import { i as transformConfigFile } from "./mutate-ZNN4iFCn.js";
import "./config-Cs0XXL3x.js";
import { t as prepareConfigWriteTopology } from "./io.write-topology-BvvklbCP.js";
import { r as restoreDoctorConfigEnvRefs } from "./config-flow-steps-CmbbCx-s.js";
import { n as findDoctorLegacyConfigIssues } from "./legacy-config-issues-Bz1S8JF8.js";
import { isDeepStrictEqual } from "node:util";
//#region src/commands/doctor/shared/automatic-startup-config-repair.ts
function admitAutomaticConfigRepairSnapshot(snapshot) {
return !snapshot.valid && snapshot.exists && snapshot.raw !== null && (snapshot.includedPaths?.length ?? 0) === 0 && !containsConfigIncludeDirective(snapshot.parsed);
}
function buildAutomaticConfigRepairPlan(snapshot, config, changes) {
return {
config,
changes,
snapshot: {
...snapshot,
sourceConfig: config,
resolved: config,
runtimeConfig: config,
config,
valid: true,
issues: [],
legacyIssues: []
}
};
}
/** Admits only complete, deterministic single-file legacy migrations. */
function planAutomaticConfigRepair(snapshot) {
if (!admitAutomaticConfigRepairSnapshot(snapshot)) return null;
const { next: config, changes } = applyLegacyDoctorMigrations(snapshot.sourceConfig, {
authoredRaw: snapshot.parsed,
resolvedRaw: snapshot.sourceConfig
});
if (!config || isDeepStrictEqual(config, snapshot.sourceConfig) || !validateConfigObjectWithPlugins(config).ok || findDoctorLegacyConfigIssues(config, config).length > 0) return null;
return buildAutomaticConfigRepairPlan(snapshot, config, changes);
}
/**
* State-free repairable preview for callers that run before the shared state database
* may be touched (gateway pre-bootstrap selection, backup discovery). It skips plugin
* doctor contracts and plugin validation, so it can admit a snapshot the full planner
* later refuses — the preflight committer and canonical-write matcher stay authoritative,
* and a refused commit keeps today's fail-closed startup refusal.
*/
function planStartupConfigRepairPreview(snapshot) {
if (!admitAutomaticConfigRepairSnapshot(snapshot)) return null;
const { next: config, changes } = applyLegacyDoctorMigrations(snapshot.sourceConfig, {
authoredRaw: snapshot.parsed,
resolvedRaw: snapshot.sourceConfig
}, { pluginContracts: false });
if (!config || isDeepStrictEqual(config, snapshot.sourceConfig) || !validateConfigObjectRaw(config).ok || findLegacyConfigIssues(config, config).length > 0) return null;
return buildAutomaticConfigRepairPlan(snapshot, config, changes);
}
/**
* Repairable-snapshot trust check for callers that run before startup state admission
* (gateway pre-bootstrap selection, backup discovery). The full planner covers
* plugin-contract migrations but reads the installed-plugin registry from the shared
* state database; when that store is unreachable, fall back to the state-free preview
* so core-key repairs stay reachable and everything else keeps today's fail-closed
* refusal. The preflight committer and canonical-write matcher stay authoritative.
*/
function resolveStartupConfigSnapshot(snapshot) {
if (snapshot.valid) return snapshot;
try {
return planAutomaticConfigRepair(snapshot)?.snapshot;
} catch {
return planStartupConfigRepairPreview(snapshot)?.snapshot;
}
}
/** Matches only the canonical writer result for a previously admitted startup repair. */
function isStartupConfigRepairResult(before, after) {
const plan = planAutomaticConfigRepair(before);
const unsetPaths = resolveManagedUnsetPathsForWrite(void 0);
const expected = plan ? stampConfigWriteMetadata(applyUnsetPathsForWrite(prepareConfigWriteTopology({
snapshot: before,
nextConfig: plan.config,
options: { persistCanonicalAgentRoster: true },
unsetPaths,
env: process.env
}).nextConfig, unsetPaths), void 0, void 0, before.parsed) : null;
return Boolean(expected && after.valid && before.path === after.path && isDeepStrictEqual(expected, after.sourceConfig));
}
/** Commits a planned repair against the exact snapshot admitted by its caller. */
async function commitAutomaticConfigRepair(plan, snapshot) {
await transformConfigFile({
baseHash: resolveConfigSnapshotHash(snapshot) ?? void 0,
transform: (_current, { snapshot: currentSnapshot }, { envSnapshotForRestore }) => ({ nextConfig: restoreDoctorConfigEnvRefs(plan.config, currentSnapshot, envSnapshotForRestore) }),
afterWrite: {
mode: "none",
reason: "automatic migration"
},
writeOptions: {
expectedConfigPath: snapshot.path,
auditOrigin: "doctor",
skipOutputLogs: true,
skipRuntimeSnapshotRefresh: true,
persistCanonicalAgentRoster: true
}
});
}
//#endregion
export { resolveStartupConfigSnapshot as i, isStartupConfigRepairResult as n, planAutomaticConfigRepair as r, commitAutomaticConfigRepair as t };