SPB Git

Repository Public

loop-stats.ts 2.3 KB 33 lines typescript

1// Heartbeats of the background loops (read by the monitoring dashboard and the "worker failing" alert). Its own small module2// so that the agent engines (which have their own timers, outside worker.ts `loop()`) can report here too — v8 hardening.3export interface LoopStat { name: string; everyMs: number; runs: number; lastStartAt: number | null; lastOkAt: number | null; lastErrorAt: number | null; lastError: string | null; /** failed passes in a row */ failures: number }4export const loopStats = new Map<string, LoopStat>();56/** Register a loop and get the three marks of one pass. */7export function heartbeat(name: string, everyMs: number): { start(): void; ok(): void; fail(reason: string): void; stat: LoopStat } {8  const st: LoopStat = { name, everyMs, runs: 0, lastStartAt: null, lastOkAt: null, lastErrorAt: null, lastError: null, failures: 0 };9  loopStats.set(name, st);10  return { stat: st, start: () => { st.lastStartAt = Date.now(); st.runs += 1; }, ok: () => { st.lastOkAt = Date.now(); st.failures = 0; },11    fail: (reason) => { st.lastErrorAt = Date.now(); st.lastError = reason.slice(0, 200); st.failures += 1; } };12}1314/** One pass of an agent engine: its outcome per agent. The pass FAILED when it threw, or when there are agents and every one15 *  of them ended in an error (or is backing off after one) — an engine that runs but can place nothing is not healthy. */16export function enginePassFailure(out: Record<string, string>): string | null {17  const all = Object.values(out);18  if (!all.length) return null;19  const bad = all.filter((x) => x.startsWith('error:') || x.startsWith('backing off'));20  return bad.length === all.length ? (all.find((x) => x.startsWith('error:')) ?? bad[0]!).slice(0, 200) : null;21}2223/** Run `pass` on a timer with a heartbeat (never two passes at once). Returns the stop function. */24export function engineLoop(name: string, everyMs: number, pass: () => Promise<Record<string, string>>): () => void {25  const hb = heartbeat(name, everyMs);26  let busy = false;27  const tick = (): void => { if (busy) return; busy = true; hb.start();28    pass().then((out) => { const why = enginePassFailure(out); if (why) hb.fail(why); else hb.ok(); }, (e: Error) => hb.fail(e.message ?? String(e))).finally(() => { busy = false; }); };29  const t = setInterval(tick, everyMs);30  t.unref();31  return () => { clearInterval(t); loopStats.delete(name); };32}33
Files main
Menu

Theme