openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
140 lines (139 loc) • 5.95 kB
JavaScript
import { p as shortenHomePath } from "./utils-CitNzLyF.mjs";
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs";
import { t as GatewayScheduler } from "./gateway-scheduler-B6bteUrw.mjs";
import { C as tryResolveAmbientOwnerAgentId } from "./agent-scope-config-BEk6-ThS.mjs";
import { t as formatDurationCompact } from "./format-duration-CyyGVUjR.mjs";
import { t as note } from "./note-Bi0VsI6R.mjs";
import { n as resolveCronJobsStorePathFromConfig } from "./paths-Dqy-ZJ8r.mjs";
import "./store-l4f-3zyW.mjs";
import { n as loadCronJobsStoreWithConfigJobsReadOnly } from "./read-only-HKCt5rQZ.mjs";
import { t as CronService } from "./service-DnmPiLd1.mjs";
import { a as resolveHeartbeatSchedulerSeed, i as resolveHeartbeatMonitorPlan, n as heartbeatMonitorAddOptions, t as applyHeartbeatMonitorJobs } from "./heartbeat-monitor-Dred-8sc.mjs";
//#region src/commands/doctor-heartbeat-cadence-migration.ts
/** Doctor-owned materialization of heartbeat cadence config into cron monitor rows. */
const HEARTBEAT_CADENCE_MIGRATION_CHECK_ID = "core/doctor/heartbeat-cadence-migration";
async function withDoctorCronService(storePath, cfg, run) {
const scheduler = new GatewayScheduler();
const noop = () => {};
const log = {
debug: noop,
info: noop,
warn: noop,
error: noop
};
const cron = new CronService({
scheduler,
storePath,
cronEnabled: false,
cronConfig: cfg.cron,
resolveDefaultAgentId: () => tryResolveAmbientOwnerAgentId(cfg),
log,
enqueueSystemEvent: () => false,
requestHeartbeat: noop,
runIsolatedAgentJob: async () => ({
status: "skipped",
error: "doctor does not execute automations"
})
});
try {
return await run(cron);
} finally {
cron.stop();
await scheduler.stop();
}
}
async function loadHeartbeatMonitorPlanReadOnly(cfg, storePath, env) {
const loaded = await loadCronJobsStoreWithConfigJobsReadOnly(storePath, env);
const schedulerSeed = resolveHeartbeatSchedulerSeed(void 0, {
env,
readOnly: true
});
return resolveHeartbeatMonitorPlan(cfg, loaded.store.jobs, { schedulerSeed });
}
function describePlannedChange(change) {
if (change.kind === "remove") return `Remove stale heartbeat monitor for agent "${change.agentId}".`;
const schedule = change.input.schedule;
const cadence = schedule.kind === "every" ? formatDurationCompact(schedule.everyMs) : schedule.kind;
const action = change.kind === "create" ? "Create" : "Update";
const state = change.input.enabled ? `at ${cadence}` : "as disabled";
return `${action} heartbeat monitor for agent "${change.agentId}" ${state}.`;
}
function noteWarnings(warnings, storePath) {
if (warnings.length === 0) return;
note(`${warnings.join("\n")}\nCron store: ${shortenHomePath(storePath)}`, "Doctor warnings");
}
async function collectHeartbeatCadenceMigrationFindings(cfg, env = process.env) {
const storePath = resolveCronJobsStorePathFromConfig(cfg, env);
try {
return (await loadHeartbeatMonitorPlanReadOnly(cfg, storePath, env)).changes.map((change) => ({
checkId: HEARTBEAT_CADENCE_MIGRATION_CHECK_ID,
severity: "warning",
message: describePlannedChange(change),
path: storePath,
target: change.agentId,
requirement: `heartbeat-monitor-${change.kind}`,
fixHint: `Run ${formatCliCommand("openclaw doctor --fix")} to materialize heartbeat cadence in cron.`
}));
} catch (error) {
return [{
checkId: HEARTBEAT_CADENCE_MIGRATION_CHECK_ID,
severity: "error",
message: `Heartbeat cadence could not be inspected: ${formatErrorMessage(error)}`,
path: storePath,
requirement: "heartbeat-monitor-inspection",
fixHint: `Run ${formatCliCommand("openclaw doctor --fix")} after resolving the cron store error.`
}];
}
}
async function ensureHeartbeatMonitorJobs(cfg, storePath, env = process.env) {
return await withDoctorCronService(storePath, cfg, async (cron) => {
const jobs = await cron.list({ includeDisabled: true });
const schedulerSeed = resolveHeartbeatSchedulerSeed(void 0, { env });
const { specs } = resolveHeartbeatMonitorPlan(cfg, jobs, { schedulerSeed });
const monitors = /* @__PURE__ */ new Map();
for (const spec of specs) {
const result = await cron.add(spec.input, heartbeatMonitorAddOptions(spec.agentId));
const job = "job" in result ? result.job : result;
monitors.set(spec.agentId, job);
}
return monitors;
});
}
async function maybeMigrateHeartbeatCadenceToCron(params) {
const env = params.env ?? process.env;
const storePath = resolveCronJobsStorePathFromConfig(params.cfg, env);
const changes = [];
const warnings = [];
if (!params.shouldRepair) {
try {
const plan = await loadHeartbeatMonitorPlanReadOnly(params.cfg, storePath, env);
if (plan.changes.length > 0) note(plan.changes.map(describePlannedChange).join("\n"), "Heartbeat cadence migration preview");
} catch (error) {
warnings.push(`Could not inspect heartbeat monitor jobs: ${formatErrorMessage(error)}`);
}
noteWarnings(warnings, storePath);
return {
changes,
warnings
};
}
await withDoctorCronService(storePath, params.cfg, async (cron) => {
const schedulerSeed = resolveHeartbeatSchedulerSeed(void 0, { env });
const result = await applyHeartbeatMonitorJobs({
cron,
cfg: params.cfg,
schedulerSeed
});
changes.push(...result.applied.map(describePlannedChange));
for (const failure of result.failures) warnings.push(failure.change ? `Heartbeat monitor for agent "${failure.change.agentId}" was not migrated: ${formatErrorMessage(failure.error)}` : `Could not inspect heartbeat monitor jobs: ${formatErrorMessage(failure.error)}`);
});
if (changes.length > 0) note(changes.join("\n"), "Doctor changes");
noteWarnings(warnings, storePath);
return {
changes,
warnings
};
}
//#endregion
export { ensureHeartbeatMonitorJobs as n, maybeMigrateHeartbeatCadenceToCron as r, collectHeartbeatCadenceMigrationFindings as t };