import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-Bje8XVt9.js";
import { n as resolveGlobalSingleton } from "./global-singleton-COlWgaGc.js";
import { o as parseAgentSessionKey } from "./session-key-utils-B9qLc2A8.js";
import { c as getAgentRunContext } from "./agent-events-Buswj6xX.js";
import { u as resolveStorePath } from "./paths-Bg_QeV2r.js";
import { t as loadSessionStore } from "./store-load-8x3UDRSN.js";
import "./sessions-D8ZHV39C.js";
import { C as markTaskTerminalById, M as setTaskCleanupAfterById, T as maybeDeliverTaskTerminalUpdate, h as listTaskRecords, i as ensureTaskRegistryReady, j as resolveTaskForLookupToken, r as deleteTaskRecordById, u as getTaskById, x as markTaskLostById } from "./task-registry-CGJEzFkf.js";
import { n as summarizeTaskRecords } from "./task-registry.summary-CETuqQCm.js";
import "./runtime-internal-DEhlXwxq.js";
import { l as tryRecoverTaskBeforeMarkLost } from "./detached-task-runtime-BOUW8jcB.js";
import { n as readAcpSessionEntry } from "./session-meta-BVDxOAkE.js";
import { t as deriveSessionChatType } from "./session-chat-type-D0XhOC4-.js";
import { n as loadCronStoreSync, r as resolveCronStorePath } from "./store-DKoc1lYY.js";
import { n as listTaskAuditFindings, r as summarizeTaskAuditFindings, t as configureTaskAuditTaskProvider } from "./task-registry.audit-B2KIDt0G.js";
import { a as resolveCronRunLogPath, i as readCronRunLogEntriesSync } from "./run-log-CmV4xJL4.js";
//#region src/cron/active-jobs.ts
const CRON_ACTIVE_JOB_STATE_KEY = Symbol.for("openclaw.cron.activeJobs");
function getCronActiveJobState() {
	return resolveGlobalSingleton(CRON_ACTIVE_JOB_STATE_KEY, () => ({ activeJobIds: /* @__PURE__ */ new Set() }));
}
function markCronJobActive(jobId) {
	if (!jobId) return;
	getCronActiveJobState().activeJobIds.add(jobId);
}
function clearCronJobActive(jobId) {
	if (!jobId) return;
	getCronActiveJobState().activeJobIds.delete(jobId);
}
function isCronJobActive(jobId) {
	if (!jobId) return false;
	return getCronActiveJobState().activeJobIds.has(jobId);
}
//#endregion
//#region src/tasks/task-registry.maintenance.ts
const TASK_RECONCILE_GRACE_MS = 5 * 6e4;
const TASK_RETENTION_MS = 10080 * 6e4;
const TASK_SWEEP_INTERVAL_MS = 6e4;
/**
* Number of tasks to process before yielding to the event loop.
* Keeps the main thread responsive during large sweeps.
*/
const SWEEP_YIELD_BATCH_SIZE = 25;
let sweeper = null;
let deferredSweep = null;
let sweepInProgress = false;
let configuredCronStorePath;
let configuredCronRuntimeAuthoritative = false;
const defaultTaskRegistryMaintenanceRuntime = {
	readAcpSessionEntry,
	loadSessionStore,
	resolveStorePath,
	isCronJobActive,
	getAgentRunContext,
	parseAgentSessionKey,
	deleteTaskRecordById,
	ensureTaskRegistryReady,
	getTaskById,
	listTaskRecords,
	markTaskLostById,
	markTaskTerminalById,
	maybeDeliverTaskTerminalUpdate,
	resolveTaskForLookupToken,
	setTaskCleanupAfterById,
	isCronRuntimeAuthoritative: () => configuredCronRuntimeAuthoritative,
	resolveCronStorePath: () => configuredCronStorePath ?? resolveCronStorePath(),
	loadCronStoreSync,
	resolveCronRunLogPath,
	readCronRunLogEntriesSync
};
let taskRegistryMaintenanceRuntime = defaultTaskRegistryMaintenanceRuntime;
function createCronRecoveryContext() {
	return {
		storePath: taskRegistryMaintenanceRuntime.resolveCronStorePath(),
		runLogsByJobId: /* @__PURE__ */ new Map()
	};
}
function findSessionEntryByKey(store, sessionKey) {
	const direct = store[sessionKey];
	if (direct) return direct;
	const normalized = normalizeLowercaseStringOrEmpty(sessionKey);
	for (const [key, entry] of Object.entries(store)) if (normalizeLowercaseStringOrEmpty(key) === normalized) return entry;
}
function isActiveTask(task) {
	return task.status === "queued" || task.status === "running";
}
function isTerminalTask(task) {
	return !isActiveTask(task);
}
function hasLostGraceExpired(task, now) {
	return now - (task.lastEventAt ?? task.startedAt ?? task.createdAt) >= TASK_RECONCILE_GRACE_MS;
}
function parseCronExecutionId(task) {
	const runId = task.runId?.trim();
	if (!runId?.startsWith("cron:")) return;
	const separator = runId.lastIndexOf(":");
	if (separator <= 5) return;
	const startedAt = Number(runId.slice(separator + 1));
	if (!Number.isFinite(startedAt)) return;
	const jobId = runId.slice(5, separator).trim();
	if (!jobId || task.sourceId?.trim() && task.sourceId.trim() !== jobId) return;
	return {
		jobId,
		startedAt
	};
}
function isTimeoutCronError(error) {
	return error === "cron: job execution timed out";
}
function mapCronTerminalStatus(status, error) {
	if (status === "ok" || status === "skipped") return "succeeded";
	return isTimeoutCronError(error) ? "timed_out" : "failed";
}
function getCronRunLogEntries(context, jobId) {
	const cached = context.runLogsByJobId.get(jobId);
	if (cached) return cached;
	let entries = [];
	try {
		const logPath = taskRegistryMaintenanceRuntime.resolveCronRunLogPath({
			storePath: context.storePath,
			jobId
		});
		entries = taskRegistryMaintenanceRuntime.readCronRunLogEntriesSync(logPath, {
			jobId,
			limit: 5e3
		});
	} catch {
		entries = [];
	}
	context.runLogsByJobId.set(jobId, entries);
	return entries;
}
function getCronStore(context) {
	if (context.store !== void 0) return context.store;
	try {
		context.store = taskRegistryMaintenanceRuntime.loadCronStoreSync(context.storePath);
	} catch {
		context.store = null;
	}
	return context.store;
}
function resolveCronRunLogRecovery(execution, context) {
	const entry = getCronRunLogEntries(context, execution.jobId).findLast((candidate) => candidate.jobId === execution.jobId && candidate.action === "finished" && candidate.runAtMs === execution.startedAt && (candidate.status === "ok" || candidate.status === "skipped" || candidate.status === "error"));
	if (!entry) return;
	const durationMs = typeof entry.durationMs === "number" && Number.isFinite(entry.durationMs) ? Math.max(0, entry.durationMs) : void 0;
	const endedAt = durationMs === void 0 ? entry.ts : execution.startedAt + durationMs;
	return {
		status: mapCronTerminalStatus(entry.status, entry.error),
		endedAt,
		lastEventAt: endedAt,
		...entry.error !== void 0 ? { error: entry.error } : {},
		...entry.summary !== void 0 ? { terminalSummary: entry.summary } : {}
	};
}
function resolveCronJobStateRecovery(execution, context) {
	const job = getCronStore(context)?.jobs.find((entry) => entry.id === execution.jobId);
	if (!job || job.state.lastRunAtMs !== execution.startedAt) return;
	const status = job.state.lastRunStatus ?? job.state.lastStatus;
	if (status !== "ok" && status !== "skipped" && status !== "error") return;
	const durationMs = typeof job.state.lastDurationMs === "number" && Number.isFinite(job.state.lastDurationMs) ? Math.max(0, job.state.lastDurationMs) : 0;
	const endedAt = execution.startedAt + durationMs;
	return {
		status: mapCronTerminalStatus(status, job.state.lastError),
		endedAt,
		lastEventAt: endedAt,
		...job.state.lastError !== void 0 ? { error: job.state.lastError } : {}
	};
}
function resolveDurableCronTaskRecovery(task, context) {
	if (task.runtime !== "cron" || !isActiveTask(task)) return;
	const execution = parseCronExecutionId(task);
	if (!execution) return;
	return resolveCronRunLogRecovery(execution, context) ?? resolveCronJobStateRecovery(execution, context);
}
function hasActiveCliRun(task) {
	const candidateRunIds = [task.sourceId, task.runId];
	for (const candidate of candidateRunIds) {
		const runId = candidate?.trim();
		if (runId && taskRegistryMaintenanceRuntime.getAgentRunContext(runId)) return true;
	}
	return false;
}
function hasBackingSession(task) {
	if (task.runtime === "cron") {
		if (!taskRegistryMaintenanceRuntime.isCronRuntimeAuthoritative()) return true;
		const jobId = task.sourceId?.trim();
		return jobId ? taskRegistryMaintenanceRuntime.isCronJobActive(jobId) : false;
	}
	if (task.runtime === "cli" && hasActiveCliRun(task)) return true;
	const childSessionKey = task.childSessionKey?.trim();
	if (!childSessionKey) return true;
	if (task.runtime === "acp") {
		const acpEntry = taskRegistryMaintenanceRuntime.readAcpSessionEntry({ sessionKey: childSessionKey });
		if (!acpEntry || acpEntry.storeReadFailed) return true;
		return Boolean(acpEntry.entry);
	}
	if (task.runtime === "subagent" || task.runtime === "cli") {
		if (task.runtime === "cli") {
			const chatType = deriveSessionChatType(childSessionKey);
			if (chatType === "channel" || chatType === "group" || chatType === "direct") return false;
		}
		const agentId = taskRegistryMaintenanceRuntime.parseAgentSessionKey(childSessionKey)?.agentId;
		const storePath = taskRegistryMaintenanceRuntime.resolveStorePath(void 0, { agentId });
		const store = taskRegistryMaintenanceRuntime.loadSessionStore(storePath);
		return Boolean(findSessionEntryByKey(store, childSessionKey));
	}
	return true;
}
function shouldMarkLost(task, now) {
	if (!isActiveTask(task)) return false;
	if (!hasLostGraceExpired(task, now)) return false;
	return !hasBackingSession(task);
}
function shouldPruneTerminalTask(task, now) {
	if (!isTerminalTask(task)) return false;
	if (typeof task.cleanupAfter === "number") return now >= task.cleanupAfter;
	return now - (task.endedAt ?? task.lastEventAt ?? task.createdAt) >= TASK_RETENTION_MS;
}
function shouldStampCleanupAfter(task) {
	return isTerminalTask(task) && typeof task.cleanupAfter !== "number";
}
function resolveCleanupAfter(task) {
	return (task.endedAt ?? task.lastEventAt ?? task.createdAt) + TASK_RETENTION_MS;
}
function markTaskLost(task, now) {
	const cleanupAfter = task.cleanupAfter ?? projectTaskLost(task, now).cleanupAfter;
	const updated = taskRegistryMaintenanceRuntime.markTaskLostById({
		taskId: task.taskId,
		endedAt: task.endedAt ?? now,
		lastEventAt: now,
		error: task.error ?? "backing session missing",
		cleanupAfter
	}) ?? task;
	taskRegistryMaintenanceRuntime.maybeDeliverTaskTerminalUpdate(updated.taskId);
	return updated;
}
function markTaskRecovered(task, recovery) {
	const updated = taskRegistryMaintenanceRuntime.markTaskTerminalById({
		taskId: task.taskId,
		status: recovery.status,
		endedAt: recovery.endedAt,
		lastEventAt: recovery.lastEventAt,
		...recovery.error !== void 0 ? { error: recovery.error } : {},
		...recovery.terminalSummary !== void 0 ? { terminalSummary: recovery.terminalSummary } : {}
	}) ?? projectTaskRecovered(task, recovery);
	taskRegistryMaintenanceRuntime.maybeDeliverTaskTerminalUpdate(updated.taskId);
	return updated;
}
function projectTaskRecovered(task, recovery) {
	const projected = {
		...task,
		status: recovery.status,
		endedAt: recovery.endedAt,
		lastEventAt: recovery.lastEventAt,
		...recovery.error !== void 0 ? { error: recovery.error } : {},
		...recovery.terminalSummary !== void 0 ? { terminalSummary: recovery.terminalSummary } : {}
	};
	return {
		...projected,
		...typeof projected.cleanupAfter === "number" ? {} : { cleanupAfter: resolveCleanupAfter(projected) }
	};
}
function projectTaskLost(task, now) {
	const projected = {
		...task,
		status: "lost",
		endedAt: task.endedAt ?? now,
		lastEventAt: now,
		error: task.error ?? "backing session missing"
	};
	return {
		...projected,
		...typeof projected.cleanupAfter === "number" ? {} : { cleanupAfter: resolveCleanupAfter(projected) }
	};
}
function reconcileTaskRecordForOperatorInspection(task, context = createCronRecoveryContext()) {
	const cronRecovery = resolveDurableCronTaskRecovery(task, context);
	if (cronRecovery) return projectTaskRecovered(task, cronRecovery);
	const now = Date.now();
	if (!shouldMarkLost(task, now)) return task;
	return projectTaskLost(task, now);
}
function reconcileInspectableTasks() {
	taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
	const cronRecoveryContext = createCronRecoveryContext();
	return taskRegistryMaintenanceRuntime.listTaskRecords().map((task) => reconcileTaskRecordForOperatorInspection(task, cronRecoveryContext));
}
configureTaskAuditTaskProvider(reconcileInspectableTasks);
function getInspectableTaskRegistrySummary() {
	return summarizeTaskRecords(reconcileInspectableTasks());
}
function getInspectableTaskAuditSummary() {
	return summarizeTaskAuditFindings(listTaskAuditFindings({ tasks: reconcileInspectableTasks() }));
}
function reconcileTaskLookupToken(token) {
	taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
	const task = taskRegistryMaintenanceRuntime.resolveTaskForLookupToken(token);
	return task ? reconcileTaskRecordForOperatorInspection(task) : void 0;
}
function previewTaskRegistryMaintenance() {
	taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
	const now = Date.now();
	let reconciled = 0;
	let recovered = 0;
	let cleanupStamped = 0;
	let pruned = 0;
	const cronRecoveryContext = createCronRecoveryContext();
	for (const task of taskRegistryMaintenanceRuntime.listTaskRecords()) {
		if (resolveDurableCronTaskRecovery(task, cronRecoveryContext)) {
			recovered += 1;
			continue;
		}
		if (shouldMarkLost(task, now)) {
			reconciled += 1;
			continue;
		}
		if (shouldPruneTerminalTask(task, now)) {
			pruned += 1;
			continue;
		}
		if (shouldStampCleanupAfter(task)) cleanupStamped += 1;
	}
	return {
		reconciled,
		recovered,
		cleanupStamped,
		pruned
	};
}
/**
* Yield control back to the event loop so that pending I/O callbacks,
* timers, and incoming requests can be processed between batches of
* synchronous task-registry maintenance work.
*/
function yieldToEventLoop() {
	return new Promise((resolve) => setImmediate(resolve));
}
function startScheduledSweep() {
	if (sweepInProgress) return;
	sweepInProgress = true;
	const clearSweepInProgress = () => {
		sweepInProgress = false;
	};
	sweepTaskRegistry().then(clearSweepInProgress, clearSweepInProgress);
}
async function runTaskRegistryMaintenance() {
	taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
	const now = Date.now();
	let reconciled = 0;
	let recovered = 0;
	let cleanupStamped = 0;
	let pruned = 0;
	const tasks = taskRegistryMaintenanceRuntime.listTaskRecords();
	const cronRecoveryContext = createCronRecoveryContext();
	let processed = 0;
	for (const task of tasks) {
		const current = taskRegistryMaintenanceRuntime.getTaskById(task.taskId);
		if (!current) continue;
		const cronRecovery = resolveDurableCronTaskRecovery(current, cronRecoveryContext);
		if (cronRecovery) {
			if (markTaskRecovered(current, cronRecovery).status !== current.status) recovered += 1;
			processed += 1;
			if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
			continue;
		}
		if (shouldMarkLost(current, now)) {
			const recovery = await tryRecoverTaskBeforeMarkLost({
				taskId: current.taskId,
				runtime: current.runtime,
				task: current,
				now
			});
			const freshAfterHook = taskRegistryMaintenanceRuntime.getTaskById(current.taskId);
			if (!freshAfterHook || !shouldMarkLost(freshAfterHook, now)) {
				processed += 1;
				if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
				continue;
			}
			if (recovery.recovered) {
				recovered += 1;
				processed += 1;
				if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
				continue;
			}
			if (markTaskLost(freshAfterHook, now).status === "lost") reconciled += 1;
			processed += 1;
			if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
			continue;
		}
		if (shouldPruneTerminalTask(current, now) && taskRegistryMaintenanceRuntime.deleteTaskRecordById(current.taskId)) {
			pruned += 1;
			processed += 1;
			if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
			continue;
		}
		if (shouldStampCleanupAfter(current) && taskRegistryMaintenanceRuntime.setTaskCleanupAfterById({
			taskId: current.taskId,
			cleanupAfter: resolveCleanupAfter(current)
		})) cleanupStamped += 1;
		processed += 1;
		if (processed % SWEEP_YIELD_BATCH_SIZE === 0) await yieldToEventLoop();
	}
	return {
		reconciled,
		recovered,
		cleanupStamped,
		pruned
	};
}
async function sweepTaskRegistry() {
	return runTaskRegistryMaintenance();
}
function startTaskRegistryMaintenance() {
	taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
	deferredSweep = setTimeout(() => {
		deferredSweep = null;
		startScheduledSweep();
	}, 5e3);
	deferredSweep.unref?.();
	if (sweeper) return;
	sweeper = setInterval(startScheduledSweep, TASK_SWEEP_INTERVAL_MS);
	sweeper.unref?.();
}
function stopTaskRegistryMaintenance() {
	if (deferredSweep) {
		clearTimeout(deferredSweep);
		deferredSweep = null;
	}
	if (sweeper) {
		clearInterval(sweeper);
		sweeper = null;
	}
	sweepInProgress = false;
}
const stopTaskRegistryMaintenanceForTests = stopTaskRegistryMaintenance;
function setTaskRegistryMaintenanceRuntimeForTests(runtime) {
	taskRegistryMaintenanceRuntime = runtime;
}
function resetTaskRegistryMaintenanceRuntimeForTests() {
	taskRegistryMaintenanceRuntime = defaultTaskRegistryMaintenanceRuntime;
	configuredCronStorePath = void 0;
	configuredCronRuntimeAuthoritative = false;
}
function configureTaskRegistryMaintenance(options) {
	configuredCronStorePath = options.cronStorePath?.trim() || void 0;
	if (options.cronRuntimeAuthoritative !== void 0) configuredCronRuntimeAuthoritative = options.cronRuntimeAuthoritative;
}
function getReconciledTaskById(taskId) {
	const task = getTaskById(taskId);
	return task ? reconcileTaskRecordForOperatorInspection(task) : void 0;
}
//#endregion
export { markCronJobActive as _, previewTaskRegistryMaintenance as a, reconcileTaskRecordForOperatorInspection as c, setTaskRegistryMaintenanceRuntimeForTests as d, startTaskRegistryMaintenance as f, clearCronJobActive as g, sweepTaskRegistry as h, getReconciledTaskById as i, resetTaskRegistryMaintenanceRuntimeForTests as l, stopTaskRegistryMaintenanceForTests as m, getInspectableTaskAuditSummary as n, reconcileInspectableTasks as o, stopTaskRegistryMaintenance as p, getInspectableTaskRegistrySummary as r, reconcileTaskLookupToken as s, configureTaskRegistryMaintenance as t, runTaskRegistryMaintenance as u };
