Files
openclaw/src/cron/stagger.ts
2026-02-21 20:18:11 -08:00

48 lines
1.5 KiB
TypeScript

import type { CronSchedule } from "./types.js";
export const DEFAULT_TOP_OF_HOUR_STAGGER_MS = 5 * 60 * 1000;
function parseCronFields(expr: string) {
return expr.trim().split(/\s+/).filter(Boolean);
}
export function isRecurringTopOfHourCronExpr(expr: string) {
const fields = parseCronFields(expr);
if (fields.length === 5) {
const [minuteField, hourField] = fields;
return minuteField === "0" && hourField.includes("*");
}
if (fields.length === 6) {
const [secondField, minuteField, hourField] = fields;
return secondField === "0" && minuteField === "0" && hourField.includes("*");
}
return false;
}
export function normalizeCronStaggerMs(raw: unknown): number | undefined {
const numeric =
typeof raw === "number"
? raw
: typeof raw === "string" && raw.trim()
? Number(raw)
: Number.NaN;
if (!Number.isFinite(numeric)) {
return undefined;
}
return Math.max(0, Math.floor(numeric));
}
export function resolveDefaultCronStaggerMs(expr: string): number | undefined {
return isRecurringTopOfHourCronExpr(expr) ? DEFAULT_TOP_OF_HOUR_STAGGER_MS : undefined;
}
export function resolveCronStaggerMs(schedule: Extract<CronSchedule, { kind: "cron" }>): number {
const explicit = normalizeCronStaggerMs(schedule.staggerMs);
if (explicit !== undefined) {
return explicit;
}
const expr = (schedule as { expr?: unknown }).expr;
const cronExpr = typeof expr === "string" ? expr : "";
return resolveDefaultCronStaggerMs(cronExpr) ?? 0;
}