UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

179 lines (178 loc) • 7.24 kB
import { o as racePromiseWithAbortSignal } from "./src-D1BHGNTj.mjs"; import "./abort-signal-QqAmLWTl.mjs"; import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { t as createSubsystemLogger } from "./subsystem-4NzR43uz.mjs"; import "./openclaw-agent-db-contract-OYt2DCoK.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-BvvgY6-t.mjs"; import { n as prepareAgentDatabaseDeletionSnapshotRead } from "./agent-deletion-journal.read-BRl4UyPn.mjs"; import { T as runWithGatewayIndependentRootWorkAdmission, f as isGatewayRestartDrainError } from "./gateway-work-admission-DAV5kkgV.mjs"; import { o as resolveSqliteTargetFromSessionStorePath } from "./session-sqlite-target-Dd3VSz82.mjs"; import { c as resolveConfiguredAgentDatabaseTargets, o as resolveAllAgentSessionStoreTargetsSync } from "./targets-kx08xomQ.mjs"; import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs"; import "./config-VnHbY_QV.mjs"; import { t as getAgentDatabaseStartupAdmission } from "./agent-database-startup-BYk3phjn.mjs"; import { r as createRetainedAgentDatabaseMatcherFromSnapshot } from "./agent-deletion-discovery-DxqezB7f.mjs"; import { t as resolveCronJobsStorePath } from "./paths-Dqy-ZJ8r.mjs"; import { a as runSessionRegistryMaintenanceForStore } from "./sessions-CRUJeu5H.mjs"; import { r as loadCronJobsStore } from "./store-l4f-3zyW.mjs"; import { t as maintainCronRunHistory } from "./run-history-CXnjOwaN.mjs"; //#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 env = process.env; return await prepareAgentDatabaseDeletionSnapshotRead({ env }).withCurrentSnapshot(async (snapshot, assertSnapshotCurrent) => { const stores = []; const assertCurrent = () => { params.assertCurrent?.(); assertSnapshotCurrent(); }; assertCurrent(); const registeredDatabases = (snapshot?.registeredAgentDatabases ?? []).filter((entry) => entry.schemaVersion === 25); const isRetained = createRetainedAgentDatabaseMatcherFromSnapshot(env, () => resolveConfiguredAgentDatabaseTargets(cfg, { env, registeredDatabases }), snapshot); const deletedAgents = new Map(snapshot?.deletedAgents.map(({ agentId, status }) => [agentId, status])); for (const target of resolveAllAgentSessionStoreTargetsSync(cfg, { env, registeredDatabases })) { assertCurrent(); const deletion = deletedAgents.get(target.agentId) ?? "absent"; const databasePath = deletion !== "absent" ? target.storePath : resolveSqliteTargetFromSessionStorePath(target.storePath, { agentId: target.agentId, env, registeredDatabases }).path; const retained = deletion === "absent" ? isRetained(databasePath, target.agentId) : void 0; if (deletion !== "absent" || typeof retained === "object") { stores.push({ ...target, skippedReason: deletion === "pending" ? "agent-deletion-pending" : "agent-deletion-complete" }); continue; } 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 }); assertCurrent(); stores.push({ agentId: target.agentId, storePath: target.storePath, 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"); let scope; let scheduledSweep; async function sweep(signal) { let admitted = false; try { const preparation = getAgentDatabaseStartupAdmission()?.pendingPreparation; if (preparation) { log.info("Cron maintenance deferred until agent database startup preparation completes"); await racePromiseWithAbortSignal(preparation, signal); } await runWithGatewayIndependentRootWorkAdmission(async () => { admitted = true; 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 }); }, "cron:maintenance", signal); } catch (error) { if (admitted || !signal.aborted && !isGatewayRestartDrainError(error)) log.warn("Cron maintenance failed", { error }); } } /** Gateway lifecycle owns retention even when scheduled execution is disabled. */ function startCronMaintenance(scheduler) { if (scope && !scope.signal.aborted) return; scope = scheduler.scope(); const { signal } = scope; scope.schedule({ id: "cron-maintenance", delayMs: 5e3, everyMs: 6e4, run: () => scheduledSweep ??= sweep(signal).finally(() => { scheduledSweep = void 0; }) }); } async function stopCronMaintenance() { await Promise.all([scope?.stop(), scheduledSweep]); } //#endregion export { startCronMaintenance, stopCronMaintenance };