openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
135 lines (134 loc) • 5.88 kB
JavaScript
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { t as formatCliCommand } from "./command-format-C7YfyMTd.js";
import { w as tryResolveAmbientOwnerAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as formatDurationCompact } from "./format-duration-CeDWULoS.js";
import { f as resolveCronJobsStorePathFromConfig, s as loadCronJobsStoreWithConfigJobsReadOnly } from "./store-K9I-tbar.js";
import { t as note } from "./note-DqHk3fA1.js";
import { t as CronService } from "./service-CWPJ5f9M.js";
import { s as resolveHeartbeatSchedulerSeed } from "./heartbeat-runner-BE2w39or.js";
import { i as resolveHeartbeatMonitorPlan, n as heartbeatMonitorAddOptions, t as applyHeartbeatMonitorJobs } from "./heartbeat-monitor-CGimh6r5.js";
//#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";
function createDoctorCronService(storePath, cfg) {
const noop = () => {};
const log = {
debug: noop,
info: noop,
warn: noop,
error: noop
};
return new CronService({
storePath,
cronEnabled: false,
cronConfig: cfg.cron,
resolveDefaultAgentId: () => tryResolveAmbientOwnerAgentId(cfg),
log,
enqueueSystemEvent: () => false,
requestHeartbeat: noop,
runIsolatedAgentJob: async () => ({
status: "skipped",
error: "doctor does not execute automations"
})
});
}
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;
return `${change.kind === "create" ? "Create" : "Update"} heartbeat monitor for agent "${change.agentId}" at ${cadence}.`;
}
function noteWarnings(warnings, storePath) {
if (warnings.length === 0) return;
note(`${warnings.join("\n")}\nCron store: ${shortenHomePath(storePath)}`, "Doctor warnings");
}
function cadenceFinding(params) {
return {
checkId: HEARTBEAT_CADENCE_MIGRATION_CHECK_ID,
severity: "warning",
message: describePlannedChange(params.change),
path: params.storePath,
target: params.change.agentId,
requirement: `heartbeat-monitor-${params.change.kind}`,
fixHint: `Run ${formatCliCommand("openclaw doctor --fix")} to materialize heartbeat cadence in cron.`
};
}
/** Reports heartbeat monitor rows that do not yet match cadence config. */
async function collectHeartbeatCadenceMigrationFindings(cfg, env = process.env) {
const storePath = resolveCronJobsStorePathFromConfig(cfg, env);
try {
return (await loadHeartbeatMonitorPlanReadOnly(cfg, storePath, env)).changes.map((change) => cadenceFinding({
storePath,
change
}));
} 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.`
}];
}
}
/** Creates or updates the stable monitor rows used by heartbeat execution. */
async function ensureHeartbeatMonitorJobs(cfg, storePath, env = process.env) {
const cron = createDoctorCronService(storePath, cfg);
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;
}
/** Previews or applies config-to-cron heartbeat cadence materialization. */
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
};
}
const cron = createDoctorCronService(storePath, params.cfg);
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 };