UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

168 lines (167 loc) • 6.61 kB
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { t as createSubsystemLogger } from "./subsystem-I-uqdx4f.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-DRG_I6tz.mjs"; import { r as getRuntimeConfig } from "./io.runtime-BsUXSsD4.mjs"; import "./config-DLycWFvB.mjs"; import { c as readAgentDeletionJournal } from "./agent-deletion-journal-CDljXcnp.mjs"; import { S as runWithGatewayIndependentRootWorkAdmission, l as isGatewayRestartDrainError } from "./gateway-work-admission-rc4ZgKq_.mjs"; import { o as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-Cnk9g9d0.mjs"; import { a as resolveAllAgentSessionStoreTargetsSync, s as resolveConfiguredAgentDatabaseTargets } from "./targets-BbcRkdUC.mjs"; import { n as createRetainedAgentDatabaseMatcher } from "./agent-deletion-discovery-BRLBtW0y.mjs"; import { t as resolveCronJobsStorePath } from "./paths-C7ju4So0.mjs"; import { a as runSessionRegistryMaintenanceForStore } from "./sessions-DQtCC-5j.mjs"; import { r as loadCronJobsStore } from "./store-ChPMJI5O.mjs"; import { t as maintainCronRunHistory } from "./run-history-ctEZG5QO.mjs"; //#region src/cron/maintenance-scheduler.ts const CRON_SWEEP_INTERVAL_MS = 6e4; function createCronMaintenanceScheduler(run, onError) { let scope; let scheduledSweep; function startScheduledSweep(signal) { if (scheduledSweep) return scheduledSweep; let admitted = false; scheduledSweep = runWithGatewayIndependentRootWorkAdmission(async () => { admitted = true; await run(); }, "cron:maintenance", signal).catch((error) => { if (admitted || !signal.aborted && !isGatewayRestartDrainError(error)) onError(error); }).finally(() => { scheduledSweep = void 0; }); return scheduledSweep; } return { start: (scheduler) => { if (scope && !scope.signal.aborted) return; scope = scheduler.scope(); const { signal } = scope; scope.schedule({ id: "cron-maintenance", delayMs: 5e3, everyMs: CRON_SWEEP_INTERVAL_MS, run: () => startScheduledSweep(signal) }); }, stop: async () => { await Promise.all([scope?.stop(), scheduledSweep]); } }; } //#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"); /** Gateway lifecycle owns retention even when scheduled execution is disabled. */ const { start: startCronMaintenance, stop: stopCronMaintenance } = 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 })); //#endregion export { startCronMaintenance, stopCronMaintenance };