openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
182 lines (181 loc) • 6.98 kB
JavaScript
import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs";
import { t as formatCliCommand } from "./command-format-Cte1SWM4.mjs";
import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs";
import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-epCM4Gdh.mjs";
import { r as getRuntimeConfig } from "./io.runtime-FunTy0y1.mjs";
import "./config-B-SAEPyK.mjs";
import { o as readAgentDeletionJournal } from "./agent-deletion-journal-DWltVExw.mjs";
import { S as runWithGatewayIndependentRootWorkAdmission, l as isGatewayRestartDrainError } from "./gateway-work-admission-G8XukyHo.mjs";
import { o as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-BGognaPy.mjs";
import { a as resolveAllAgentSessionStoreTargetsSync, s as resolveConfiguredAgentDatabaseTargets } from "./targets-B3vrlQbX.mjs";
import { n as createRetainedAgentDatabaseMatcher } from "./agent-deletion-discovery-8wxjvjaN.mjs";
import { t as resolveCronJobsStorePath } from "./paths-D0lej1Bn.mjs";
import { u as runSessionRegistryMaintenanceForStore } from "./sessions-nJdQddS3.mjs";
import { r as loadCronJobsStore } from "./store-abzXKIps.mjs";
import { t as maintainCronRunHistory } from "./run-history-EvkINrxl.mjs";
//#region src/cron/maintenance-scheduler.ts
const CRON_SWEEP_INTERVAL_MS = 6e4;
function createCronMaintenanceScheduler(run, onError) {
let sweepJob;
let scheduledSweep = null;
function startScheduledSweep(schedulerSignal) {
if (scheduledSweep) return scheduledSweep.completion;
const admission = new AbortController();
const signal = AbortSignal.any([admission.signal, schedulerSignal]);
let admitted = false;
const completion = runWithGatewayIndependentRootWorkAdmission(async () => {
admitted = true;
await run();
}, "cron:maintenance", signal).catch((error) => {
if (admitted || !signal.aborted && !isGatewayRestartDrainError(error)) onError(error);
}).finally(() => {
scheduledSweep = null;
});
scheduledSweep = {
completion,
cancelAdmission: () => admission.abort()
};
return completion;
}
return {
start(scheduler) {
if (sweepJob) return;
sweepJob = scheduler.schedule({
id: "cron-maintenance",
atMs: scheduler.now() + 5e3,
everyMs: CRON_SWEEP_INTERVAL_MS,
run: () => startScheduledSweep(scheduler.signal)
});
},
async stop() {
sweepJob?.cancel();
sweepJob = void 0;
const pending = scheduledSweep;
pending?.cancelAdmission();
await pending?.completion;
}
};
}
//#endregion
//#region src/cron/session-registry-maintenance.ts
const log$1 = createSubsystemLogger("cron/maintenance");
const SESSION_REGISTRY_RETENTION_MS = 6048e5;
function resolveExplicitCronSessionSegment(sessionKey) {
return /^(?:agent:[^:]+:)?cron:([^:]+)$/u.exec(sessionKey?.trim() ?? "")?.[1]?.toLowerCase();
}
async function readRunningCronJobIds() {
try {
const cronStorePath = resolveCronJobsStorePath();
const runningJobs = (await loadCronJobsStore(cronStorePath)).jobs.filter((job) => typeof job.state?.runningAtMs === "number");
const ids = /* @__PURE__ */ new Set();
for (const job of runningJobs) {
ids.add(job.id.toLowerCase());
if (job.sessionTarget === "main") continue;
const explicitSessionSegment = resolveExplicitCronSessionSegment(job.sessionKey);
if (explicitSessionSegment) ids.add(explicitSessionSegment);
}
return {
ok: true,
ids,
count: runningJobs.length
};
} catch (err) {
return {
ok: false,
reason: formatErrorMessage(err)
};
}
}
async function runSessionRegistryMaintenance(params) {
params.assertCurrent?.();
const cfg = getRuntimeConfig();
const runningCronJobs = await readRunningCronJobIds();
params.assertCurrent?.();
if (!runningCronJobs.ok) return {
retentionMs: SESSION_REGISTRY_RETENTION_MS,
runningCronJobs: 0,
pruned: 0,
skippedStores: 0,
stores: [],
skippedReason: `cron store unreadable: ${runningCronJobs.reason}`
};
const stores = [];
const env = process.env;
for (const target of resolveAllAgentSessionStoreTargetsSync(cfg)) {
params.assertCurrent?.();
const deletion = readAgentDeletionJournal(target.agentId, { env }, "runtime");
const databasePath = deletion ? target.storePath : resolveSqliteTargetFromSessionStorePath(target.storePath, {
agentId: target.agentId,
env
}).path;
const retained = deletion ? void 0 : createRetainedAgentDatabaseMatcher(env, () => resolveConfiguredAgentDatabaseTargets(cfg, { env }))(databasePath, target.agentId);
if (deletion?.cleanupCompleted || typeof retained === "object") {
stores.push({
...target,
skippedReason: "agent-deletion-complete"
});
continue;
}
if (deletion) throw new Error(`OpenClaw agent database is unavailable while agent ${target.agentId} is deleted.`);
if (retained) {
const reason = retained === "held" ? "deletion journal reconstruction" : "deletion journal unavailable";
const warning = `Held agent ${target.agentId} database ${databasePath} (${reason}); skipped session retention. Run "${formatCliCommand("openclaw doctor --fix", env)}" for explicit restoration guidance.`;
log$1.warn(warning);
stores.push({
...target,
skippedReason: "agent-store-held",
warning
});
continue;
}
const result = await runSessionRegistryMaintenanceForStore({
...target,
apply: params.apply,
retentionMs: SESSION_REGISTRY_RETENTION_MS,
runningCronJobIds: runningCronJobs.ids,
assertCurrent: params.assertCurrent
});
params.assertCurrent?.();
stores.push({
agentId: target.agentId,
storePath: target.storePath,
beforeCount: result.beforeCount,
afterCount: result.afterCount,
pruned: result.pruned,
preservedRunning: result.preservedRunning
});
}
return {
retentionMs: SESSION_REGISTRY_RETENTION_MS,
runningCronJobs: runningCronJobs.count,
pruned: stores.reduce((total, store) => total + ("pruned" in store ? store.pruned : 0), 0),
skippedStores: stores.filter((store) => "skippedReason" in store).length,
stores
};
}
//#endregion
//#region src/cron/maintenance.ts
const log = createSubsystemLogger("cron/maintenance");
const scheduler = createCronMaintenanceScheduler(async () => {
const context = captureOpenClawStateWorkerContext();
const assertCurrent = () => {
context.admission.assertCurrent();
};
await maintainCronRunHistory(context, assertCurrent);
assertCurrent();
const result = await runSessionRegistryMaintenance({
apply: true,
assertCurrent
});
assertCurrent();
if (result.skippedReason) log.warn("Session registry maintenance skipped", { reason: result.skippedReason });
}, (error) => log.warn("Cron maintenance failed", { error }));
/** Gateway lifecycle owns retention even when scheduled execution is disabled. */
function startCronMaintenance(gatewayScheduler) {
scheduler.start(gatewayScheduler);
}
async function stopCronMaintenance() {
await scheduler.stop();
}
//#endregion
export { startCronMaintenance, stopCronMaintenance };