export type Job = { id: number; type: string; schedule: string; action: any; status: string; nextRun: string; createdAt?: string | undefined; }; export type SchedulerOptions = { /** * - Path to JSON persistence file. */ file?: string | null | undefined; /** * - Tick interval in ms. */ interval?: number | undefined; /** * - Handler errors callback. */ onError?: ((err: any, job: Job) => void) | null | undefined; }; /** * Time-triggered agent turns. The only way the agent acts without being messaged. * * Interface: * add(job) → jobId * remove(jobId) → void * list() → [jobs] * start(handler) → begin tick loop (handler receives due jobs) * stop() → stop tick loop */ /** * @typedef {object} Job * @property {number} id * @property {string} type * @property {string} schedule * @property {*} action * @property {string} status * @property {string} nextRun * @property {string} [createdAt] */ /** * @typedef {object} SchedulerOptions * @property {string|null} [file] - Path to JSON persistence file. * @property {number} [interval=60000] - Tick interval in ms. * @property {((err: any, job: Job) => void)|null} [onError] - Handler errors callback. */ export class Scheduler { /** * @param {SchedulerOptions} [options={}] * @when you need to fire agent turns on a schedule — cron expressions or relative intervals — driven by a periodic tick * @fails an errored job routes to the onError handler and never crashes the tick loop; a malformed cron/interval is rejected when the job is added. * @example * const sched = new Scheduler({ interval: 60000 }); * sched.add({ id: 'poll', cron: '0 * * * *', run }); */ constructor(options?: SchedulerOptions); _file: string | null; _interval: number; onError: ((err: any, job: Job) => void) | null; /** @type {Job[]} */ _jobs: Job[]; /** @type {NodeJS.Timeout|null} */ _timer: NodeJS.Timeout | null; /** @type {Set} */ _running: Set; _nextId: number; _save(): void; /** @param {{ type?: string, schedule: string, action: * }} job */ add(job: { type?: string; schedule: string; action: any; }): number; /** @param {number} jobId */ remove(jobId: number): void; list(): { id: number; type: string; schedule: string; action: any; status: string; nextRun: string; createdAt?: string | undefined; }[]; /** * Begin the tick loop. Calls `handler(job)` for each due job every tick interval. * * - `handler` is called with the full job object: `{ id, type, schedule, action, status, nextRun }`. * - Jobs that are still running (handler hasn't resolved) are skipped on subsequent ticks * via the internal `_running` Set — this prevents overlapping executions of the same job. * - Within a single tick, due jobs are executed sequentially (awaited one at a time). * - If a handler throws, the error is passed to `onError(err, job)` if configured. * The tick loop continues to the next job — handler errors never crash the scheduler. * * @param {(job: object) => Promise} handler - Async function called for each due job. */ start(handler: (job: object) => Promise): void; stop(): void; /** * @param {string} schedule - Relative ('5s','30m','2h','1d') or cron expression. * @returns {Date} The next run time. * @throws {Error} `[Scheduler] Cannot parse schedule` — when format is not recognized. * @private */ private _parseSchedule; }