import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, f as normalizeStringifiedOptionalString } from "./string-coerce-mnp54Vah.js";
import { v as uniqueValues } from "./string-normalization-WNUDCpXX.js";
import { d as executeSqliteQuerySync, f as executeSqliteQueryTakeFirstSync, i as openOpenClawStateDatabase, o as runOpenClawStateWriteTransaction, p as getNodeSqliteKysely } from "./openclaw-state-db-x4Za4WWq.js";
import { t as parseByteSize } from "./parse-bytes-00heCtL_.js";
import { c as cronStoreKey } from "./normalize-CIhX90HO.js";
import { u as resolveFailoverReasonFromError } from "./failover-error-BWZh2Zvp.js";
import { i as normalizeCronRunDiagnostics } from "./run-diagnostics-D3ZLtxVb.js";
//#region src/cron/run-log/entry-codec.ts
/** Parses and normalizes persisted cron run-log entry payloads. */
const CRON_FAILOVER_REASONS = new Set([
	"auth",
	"auth_permanent",
	"format",
	"rate_limit",
	"overloaded",
	"billing",
	"server_error",
	"timeout",
	"model_not_found",
	"session_expired",
	"empty_response",
	"no_error_details",
	"unclassified",
	"unknown"
]);
function normalizeCronRunLogErrorReason(value) {
	return typeof value === "string" && CRON_FAILOVER_REASONS.has(value) ? value : void 0;
}
/** Parses a persisted cron run-log entry object and drops invalid or wrong-job rows. */
function parseCronRunLogEntryObject(obj, opts) {
	const jobId = normalizeOptionalString(opts?.jobId);
	if (!obj || typeof obj !== "object") return null;
	const entryObj = obj;
	if (entryObj.action !== "finished") return null;
	if (typeof entryObj.jobId !== "string" || entryObj.jobId.trim().length === 0) return null;
	if (typeof entryObj.ts !== "number" || !Number.isFinite(entryObj.ts)) return null;
	if (jobId && entryObj.jobId !== jobId) return null;
	const usage = entryObj.usage && typeof entryObj.usage === "object" ? entryObj.usage : void 0;
	const normalizedError = typeof entryObj.error === "string" ? entryObj.error : void 0;
	const normalizedProvider = typeof entryObj.provider === "string" && entryObj.provider.trim() ? entryObj.provider : void 0;
	const normalizedErrorReason = normalizeCronRunLogErrorReason(entryObj.errorReason) ?? resolveFailoverReasonFromError(normalizedError, normalizedProvider) ?? void 0;
	const entry = {
		ts: entryObj.ts,
		jobId: entryObj.jobId,
		action: "finished",
		status: entryObj.status,
		error: normalizedError,
		errorReason: normalizedErrorReason,
		summary: entryObj.summary,
		runId: typeof entryObj.runId === "string" && entryObj.runId.trim() ? entryObj.runId : void 0,
		diagnostics: normalizeCronRunDiagnostics(entryObj.diagnostics),
		runAtMs: entryObj.runAtMs,
		durationMs: entryObj.durationMs,
		nextRunAtMs: entryObj.nextRunAtMs,
		model: typeof entryObj.model === "string" && entryObj.model.trim() ? entryObj.model : void 0,
		provider: normalizedProvider,
		usage: usage ? {
			input_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : void 0,
			output_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : void 0,
			total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : void 0,
			cache_read_tokens: typeof usage.cache_read_tokens === "number" ? usage.cache_read_tokens : void 0,
			cache_write_tokens: typeof usage.cache_write_tokens === "number" ? usage.cache_write_tokens : void 0
		} : void 0
	};
	if (typeof entryObj.delivered === "boolean") entry.delivered = entryObj.delivered;
	if (entryObj.deliveryStatus === "delivered" || entryObj.deliveryStatus === "not-delivered" || entryObj.deliveryStatus === "unknown" || entryObj.deliveryStatus === "not-requested") entry.deliveryStatus = entryObj.deliveryStatus;
	if (typeof entryObj.deliveryError === "string") entry.deliveryError = entryObj.deliveryError;
	if (entryObj.failureNotificationDelivery && typeof entryObj.failureNotificationDelivery === "object") {
		const failureNotificationDelivery = entryObj.failureNotificationDelivery;
		if (failureNotificationDelivery.status === "delivered" || failureNotificationDelivery.status === "not-delivered" || failureNotificationDelivery.status === "unknown" || failureNotificationDelivery.status === "not-requested") entry.failureNotificationDelivery = {
			status: failureNotificationDelivery.status,
			...typeof failureNotificationDelivery.delivered === "boolean" ? { delivered: failureNotificationDelivery.delivered } : {},
			...typeof failureNotificationDelivery.error === "string" ? { error: failureNotificationDelivery.error } : {}
		};
	}
	if (entryObj.delivery && typeof entryObj.delivery === "object") entry.delivery = entryObj.delivery;
	if (typeof entryObj.sessionId === "string" && entryObj.sessionId.trim().length > 0) entry.sessionId = entryObj.sessionId;
	if (typeof entryObj.sessionKey === "string" && entryObj.sessionKey.trim().length > 0) entry.sessionKey = entryObj.sessionKey;
	return entry;
}
//#endregion
//#region src/cron/run-log/sqlite-store.ts
function getCronRunLogKysely(db) {
	return getNodeSqliteKysely(db);
}
function normalizeNumber(value) {
	if (typeof value === "bigint") return Number(value);
	return typeof value === "number" ? value : void 0;
}
function booleanToInteger(value) {
	return typeof value === "boolean" ? value ? 1 : 0 : null;
}
function integerToBoolean(value) {
	const normalized = normalizeNumber(value);
	return normalized == null ? void 0 : normalized !== 0;
}
function bindCronRunLogRow(params) {
	const entry = params.entry;
	return {
		store_key: params.storeKey,
		job_id: entry.jobId,
		seq: params.seq,
		ts: entry.ts,
		status: entry.status ?? null,
		error: entry.error ?? null,
		summary: entry.summary ?? null,
		diagnostics_summary: entry.diagnostics?.summary ?? null,
		delivery_status: entry.deliveryStatus ?? null,
		delivery_error: entry.deliveryError ?? null,
		delivered: booleanToInteger(entry.delivered),
		session_id: entry.sessionId ?? null,
		session_key: entry.sessionKey ?? null,
		run_id: entry.runId ?? null,
		run_at_ms: entry.runAtMs ?? null,
		duration_ms: entry.durationMs ?? null,
		next_run_at_ms: entry.nextRunAtMs ?? null,
		model: entry.model ?? null,
		provider: entry.provider ?? null,
		total_tokens: entry.usage?.total_tokens ?? null,
		entry_json: JSON.stringify(entry),
		created_at: Date.now()
	};
}
/** Rehydrates a cron run-log row, preferring indexed SQLite columns over JSON payload values. */
function parseStoredRunLogEntry(row) {
	let rawEntry;
	try {
		rawEntry = JSON.parse(row.entry_json);
	} catch {
		return null;
	}
	const parsed = parseCronRunLogEntryObject(rawEntry, { jobId: row.job_id });
	if (!parsed) return null;
	return {
		...parsed,
		ts: normalizeNumber(row.ts) ?? parsed.ts,
		jobId: row.job_id,
		status: row.status ?? parsed.status,
		error: row.error ?? parsed.error,
		summary: row.summary ?? parsed.summary,
		delivered: integerToBoolean(row.delivered) ?? parsed.delivered,
		deliveryStatus: row.delivery_status ?? parsed.deliveryStatus,
		deliveryError: row.delivery_error ?? parsed.deliveryError,
		sessionId: row.session_id ?? parsed.sessionId,
		sessionKey: row.session_key ?? parsed.sessionKey,
		runId: row.run_id ?? parsed.runId,
		runAtMs: normalizeNumber(row.run_at_ms) ?? parsed.runAtMs,
		durationMs: normalizeNumber(row.duration_ms) ?? parsed.durationMs,
		nextRunAtMs: normalizeNumber(row.next_run_at_ms) ?? parsed.nextRunAtMs,
		model: row.model ?? parsed.model,
		provider: row.provider ?? parsed.provider
	};
}
/** Reads run-log rows for one store, optionally scoped to one job, in chronological order. */
function readCronRunLogRows(db, storeKey, jobId) {
	let query = getCronRunLogKysely(db).selectFrom("cron_run_logs").selectAll().where("store_key", "=", storeKey);
	if (jobId) query = query.where("job_id", "=", jobId);
	return executeSqliteQuerySync(db, query.orderBy("ts", "asc").orderBy("seq", "asc")).rows;
}
function applyRunLogFilters(query, params) {
	let next = query.where("store_key", "=", params.storeKey);
	if (params.jobId) next = next.where("job_id", "=", params.jobId);
	if (params.statuses?.length) next = next.where("status", "in", params.statuses);
	if (params.deliveryStatuses?.length) next = next.where((eb) => eb.or(params.deliveryStatuses.map((status) => status === "not-requested" ? eb.or([eb("delivery_status", "is", null), eb("delivery_status", "=", status)]) : eb("delivery_status", "=", status))));
	const runId = params.runId?.trim();
	if (runId) next = next.where("run_id", "=", runId);
	return next;
}
/** Counts run-log rows after applying the same filters used by paged reads. */
function countCronRunLogRows(params) {
	return normalizeNumber(executeSqliteQueryTakeFirstSync(params.db, applyRunLogFilters(getCronRunLogKysely(params.db).selectFrom("cron_run_logs").select((eb) => eb.fn.countAll().as("count")), params))?.count ?? null) ?? 0;
}
/** Reads a sorted, filtered page of cron run-log rows. */
function readCronRunLogRowsPage(params) {
	let query = applyRunLogFilters(getCronRunLogKysely(params.db).selectFrom("cron_run_logs").selectAll(), params).orderBy("ts", params.sortDir).orderBy("seq", params.sortDir);
	if (params.limit !== void 0 && params.offset !== void 0) query = query.limit(params.limit).offset(params.offset);
	return executeSqliteQuerySync(params.db, query).rows;
}
function nextCronRunLogSeq(db, storeKey, jobId) {
	return (normalizeNumber(executeSqliteQueryTakeFirstSync(db, getCronRunLogKysely(db).selectFrom("cron_run_logs").select((eb) => eb.fn.max("seq").as("seq")).where("store_key", "=", storeKey).where("job_id", "=", jobId))?.seq ?? null) ?? 0) + 1;
}
/** Appends a cron run-log entry with a per-job monotonic sequence number. */
function insertCronRunLogEntry(db, storeKey, entry) {
	const seq = nextCronRunLogSeq(db, storeKey, entry.jobId);
	executeSqliteQuerySync(db, getCronRunLogKysely(db).insertInto("cron_run_logs").values(bindCronRunLogRow({
		storeKey,
		seq,
		entry
	})));
}
/** Prunes old cron run-log rows for one job, retaining the newest keepLines rows. */
function pruneCronRunLogRows(db, storeKey, jobId, keepLines) {
	const keep = Math.max(1, Math.floor(keepLines));
	const keepSeqs = getCronRunLogKysely(db).selectFrom("cron_run_logs").select("seq").where("store_key", "=", storeKey).where("job_id", "=", jobId).orderBy("seq", "desc").limit(keep);
	executeSqliteQuerySync(db, getCronRunLogKysely(db).deleteFrom("cron_run_logs").where("store_key", "=", storeKey).where("job_id", "=", jobId).where("seq", "not in", keepSeqs));
}
//#endregion
//#region src/cron/run-log.ts
/** Public cron run-log API with serialized writes and paged reads. */
const INVALID_CRON_RUN_LOG_JOB_ID_MESSAGE = "invalid cron run log job id";
function assertSafeCronRunLogJobId(jobId) {
	const trimmed = jobId.trim();
	if (!trimmed) throw new Error(INVALID_CRON_RUN_LOG_JOB_ID_MESSAGE);
	if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("\0")) throw new Error(INVALID_CRON_RUN_LOG_JOB_ID_MESSAGE);
	return trimmed;
}
/** Returns whether an error came from cron run-log job id validation. */
function isInvalidCronRunLogJobIdError(err) {
	return err instanceof Error && err.message === INVALID_CRON_RUN_LOG_JOB_ID_MESSAGE;
}
const writesByTarget = /* @__PURE__ */ new Map();
/** Legacy byte cap kept for config parsing compatibility with older file-backed run logs. */
const DEFAULT_CRON_RUN_LOG_MAX_BYTES = 2e6;
/** Default SQLite row retention per cron job when no explicit keepLines value is configured. */
const DEFAULT_CRON_RUN_LOG_KEEP_LINES = 2e3;
/** Resolves configured run-log pruning limits while preserving legacy maxBytes parsing. */
function resolveCronRunLogPruneOptions(cfg) {
	let maxBytes = DEFAULT_CRON_RUN_LOG_MAX_BYTES;
	if (cfg?.maxBytes !== void 0) try {
		const configuredMaxBytes = normalizeStringifiedOptionalString(cfg.maxBytes);
		if (configuredMaxBytes) maxBytes = parseByteSize(configuredMaxBytes, { defaultUnit: "b" });
	} catch {
		maxBytes = DEFAULT_CRON_RUN_LOG_MAX_BYTES;
	}
	let keepLines = DEFAULT_CRON_RUN_LOG_KEEP_LINES;
	if (typeof cfg?.keepLines === "number" && Number.isFinite(cfg.keepLines) && cfg.keepLines > 0) keepLines = Math.floor(cfg.keepLines);
	return {
		maxBytes,
		keepLines
	};
}
function cronRunLogWriteKey(storePath, jobId) {
	return `${cronStoreKey(storePath)}\0${jobId ?? ""}`;
}
async function drainPendingWrite(storePath, jobId) {
	if (jobId) {
		await writesByTarget.get(cronRunLogWriteKey(storePath, jobId))?.catch(() => void 0);
		return;
	}
	const storePrefix = `${cronStoreKey(storePath)}\0`;
	const pending = [...writesByTarget.entries()].filter(([key]) => key.startsWith(storePrefix)).map(([, write]) => write.catch(() => void 0));
	await Promise.all(pending);
}
/** Appends a cron run-log row and serializes writes per store/job before pruning old rows. */
async function appendCronRunLog(params) {
	const storeKey = cronStoreKey(params.storePath);
	const writeKey = cronRunLogWriteKey(params.storePath, params.entry.jobId);
	const next = (writesByTarget.get(writeKey) ?? Promise.resolve()).catch(() => void 0).then(async () => {
		runOpenClawStateWriteTransaction(({ db }) => {
			insertCronRunLogEntry(db, storeKey, params.entry);
			if (params.opts?.keepLines !== false) pruneCronRunLogRows(db, storeKey, params.entry.jobId, params.opts?.keepLines ?? 2e3);
		});
	});
	writesByTarget.set(writeKey, next);
	try {
		await next;
	} finally {
		if (writesByTarget.get(writeKey) === next) writesByTarget.delete(writeKey);
	}
}
/** Reads recent run-log entries synchronously for startup/task reconciliation paths. */
function readCronRunLogEntriesSync(params) {
	const limit = Math.max(1, Math.min(5e3, Math.floor(params.limit ?? 200)));
	const storeKey = cronStoreKey(params.storePath);
	const jobId = params.jobId ? assertSafeCronRunLogJobId(params.jobId) : void 0;
	return readCronRunLogRows(openOpenClawStateDatabase().db, storeKey, jobId).map(parseStoredRunLogEntry).filter((entry) => entry !== null).slice(-limit);
}
function normalizeRunStatusFilter(status) {
	if (status === "ok" || status === "error" || status === "skipped" || status === "all") return status;
	return "all";
}
function normalizeRunStatuses(opts) {
	if (Array.isArray(opts?.statuses) && opts.statuses.length > 0) {
		const filtered = opts.statuses.filter((status) => status === "ok" || status === "error" || status === "skipped");
		if (filtered.length > 0) return uniqueValues(filtered);
	}
	const status = normalizeRunStatusFilter(opts?.status);
	if (status === "all") return null;
	return [status];
}
function normalizeDeliveryStatuses(opts) {
	if (Array.isArray(opts?.deliveryStatuses) && opts.deliveryStatuses.length > 0) {
		const filtered = opts.deliveryStatuses.filter((status) => status === "delivered" || status === "not-delivered" || status === "unknown" || status === "not-requested");
		if (filtered.length > 0) return uniqueValues(filtered);
	}
	if (opts?.deliveryStatus === "delivered" || opts?.deliveryStatus === "not-delivered" || opts?.deliveryStatus === "unknown" || opts?.deliveryStatus === "not-requested") return [opts.deliveryStatus];
	return null;
}
function runIdMatches(entry, runId) {
	const normalized = normalizeOptionalString(runId);
	return !normalized || entry.runId === normalized;
}
function filterRunLogEntries(entries, opts) {
	return entries.filter((entry) => {
		if (!runIdMatches(entry, opts.runId)) return false;
		if (opts.statuses && (!entry.status || !opts.statuses.includes(entry.status))) return false;
		if (opts.deliveryStatuses) {
			const deliveryStatus = entry.deliveryStatus ?? "not-requested";
			if (!opts.deliveryStatuses.includes(deliveryStatus)) return false;
		}
		if (!opts.query) return true;
		return normalizeLowercaseStringOrEmpty(opts.queryTextForEntry(entry)).includes(opts.query);
	});
}
/** Reads a bounded, filterable run-log page for CLI and UI list views. */
async function readCronRunLogEntriesPage(opts) {
	const jobId = opts.jobId ? assertSafeCronRunLogJobId(opts.jobId) : void 0;
	await drainPendingWrite(opts.storePath, jobId);
	const limit = Math.max(1, Math.min(200, Math.floor(opts.limit ?? 50)));
	const statuses = normalizeRunStatuses(opts);
	const deliveryStatuses = normalizeDeliveryStatuses(opts);
	const query = normalizeLowercaseStringOrEmpty(opts.query);
	const sortDir = opts.sortDir === "asc" ? "asc" : "desc";
	const db = openOpenClawStateDatabase().db;
	const storeKey = cronStoreKey(opts.storePath);
	const offset = Math.max(0, Math.floor(opts.offset ?? 0));
	if (!query) {
		const total = countCronRunLogRows({
			db,
			storeKey,
			jobId,
			statuses,
			deliveryStatuses,
			runId: opts.runId
		});
		const boundedOffset = Math.min(total, offset);
		const entries = readCronRunLogRowsPage({
			db,
			storeKey,
			jobId,
			statuses,
			deliveryStatuses,
			runId: opts.runId,
			sortDir,
			offset: boundedOffset,
			limit
		}).map(parseStoredRunLogEntry).filter((entry) => entry !== null);
		if (opts.jobNameById) for (const entry of entries) {
			const jobName = opts.jobNameById[entry.jobId];
			if (jobName) entry.jobName = jobName;
		}
		const nextOffset = boundedOffset + entries.length;
		return {
			entries,
			total,
			offset: boundedOffset,
			limit,
			hasMore: nextOffset < total,
			nextOffset: nextOffset < total ? nextOffset : null
		};
	}
	const filtered = filterRunLogEntries(readCronRunLogRowsPage({
		db,
		storeKey,
		jobId,
		statuses,
		deliveryStatuses,
		runId: opts.runId,
		sortDir
	}).map(parseStoredRunLogEntry).filter((entry) => entry !== null), {
		runId: opts.runId,
		statuses: null,
		deliveryStatuses: null,
		query,
		queryTextForEntry: (entry) => {
			const jobName = opts.jobNameById?.[entry.jobId] ?? "";
			return [
				entry.summary ?? "",
				entry.error ?? "",
				entry.errorReason ?? "",
				entry.diagnostics?.summary ?? "",
				...(entry.diagnostics?.entries ?? []).map((diagnostic) => diagnostic.message),
				entry.jobId,
				jobName,
				entry.delivery?.intended?.channel ?? "",
				entry.delivery?.resolved?.channel ?? "",
				...(entry.delivery?.messageToolSentTo ?? []).map((target) => target.channel)
			].join(" ");
		}
	});
	const sorted = sortDir === "asc" ? filtered.toSorted((a, b) => a.ts - b.ts) : filtered.toSorted((a, b) => b.ts - a.ts);
	const total = sorted.length;
	const boundedOffset = Math.min(total, offset);
	const entries = sorted.slice(boundedOffset, boundedOffset + limit);
	if (opts.jobNameById) for (const entry of entries) {
		const jobName = opts.jobNameById[entry.jobId];
		if (jobName) entry.jobName = jobName;
	}
	const nextOffset = boundedOffset + entries.length;
	return {
		entries,
		total,
		offset: boundedOffset,
		limit,
		hasMore: nextOffset < total,
		nextOffset: nextOffset < total ? nextOffset : null
	};
}
/** Reads a run-log page across all jobs for a specific cron store. */
async function readCronRunLogEntriesPageAll(opts) {
	return readCronRunLogEntriesPage(opts);
}
//#endregion
export { readCronRunLogEntriesSync as a, readCronRunLogEntriesPageAll as i, isInvalidCronRunLogJobIdError as n, resolveCronRunLogPruneOptions as o, readCronRunLogEntriesPage as r, parseCronRunLogEntryObject as s, appendCronRunLog as t };
