mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 11:55:47 -06:00
8e1c238c1c
* fix(cron): harden scheduling timestamp boundaries Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * test(prompts): refresh cron tool snapshots Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * test(cron): keep config revision fixture Date-valid Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * refactor(cron): consolidate scheduling lifecycle Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * refactor(cron): keep task history dependencies acyclic Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * fix(cron): canonicalize timestamp auto-disable Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * test(cron): verify startup overflow notifications Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe * chore(ci): repair main baseline gates Amp-Thread-ID: https://ampcode.com/threads/T-019fe949-92e4-76bd-8cfa-aea44fcfaebe --------- Co-authored-by: Amp <amp@ampcode.com>
53 lines
1.8 KiB
TypeScript
53 lines
1.8 KiB
TypeScript
import { asDateTimestampMs } from "@openclaw/normalization-core/number-coercion";
|
|
import { parseDurationMs } from "../cli/parse-duration.js";
|
|
import type { CronPacing } from "./types.js";
|
|
|
|
/** Parsed positive pacing bounds used for validation and next-run clamping. */
|
|
type CronPacingBounds = {
|
|
minMs?: number;
|
|
maxMs?: number;
|
|
};
|
|
|
|
function parsePositivePacingDuration(value: string, field: "min" | "max"): number {
|
|
try {
|
|
const durationMs = parseDurationMs(value);
|
|
if (durationMs > 0) {
|
|
return durationMs;
|
|
}
|
|
} catch {
|
|
// Normalize parser details into the cron configuration contract below.
|
|
}
|
|
throw new Error(`cron pacing ${field} must be a positive duration`);
|
|
}
|
|
|
|
/** Validates pacing strings and returns their millisecond bounds. */
|
|
export function parseCronPacingBounds(pacing: CronPacing): CronPacingBounds {
|
|
if (pacing.min === undefined && pacing.max === undefined) {
|
|
throw new Error("cron pacing requires at least one of min or max");
|
|
}
|
|
const minMs =
|
|
pacing.min === undefined ? undefined : parsePositivePacingDuration(pacing.min, "min");
|
|
const maxMs =
|
|
pacing.max === undefined ? undefined : parsePositivePacingDuration(pacing.max, "max");
|
|
if (minMs !== undefined && maxMs !== undefined && minMs > maxMs) {
|
|
throw new Error("cron pacing min must not exceed max");
|
|
}
|
|
return { minMs, maxMs };
|
|
}
|
|
|
|
/** Clamps one successful run's proposal against its job-local pacing bounds. */
|
|
export function resolvePacedNextRunAtMs(params: {
|
|
nowMs: number;
|
|
delayMs: number;
|
|
pacing: CronPacing;
|
|
}): number | undefined {
|
|
const { minMs, maxMs } = parseCronPacingBounds(params.pacing);
|
|
const proposedAtMs = params.nowMs + params.delayMs;
|
|
return asDateTimestampMs(
|
|
Math.min(
|
|
params.nowMs + (maxMs ?? Number.POSITIVE_INFINITY),
|
|
Math.max(params.nowMs + (minMs ?? 0), proposedAtMs),
|
|
),
|
|
);
|
|
}
|