UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

112 lines • 6.2 kB
"use strict"; /** * sleep — the ONE wait in the library, and it keeps its promised minimum. * * Pattern: One factory over a clock and a timer (`makeSleep`), one instance * over the platform's (`sleep`). No state between calls. * Role: Leaf. Seven modules each kept a private `setTimeout` promise — the * retry back-offs (`withRetry`, `withCredentialRetry`, * `retryingFetch`, `typesafe`), the device-flow poll, the mock * provider's thinking time and the sign-in door's minimum answer * time. Every one promised "at least `ms`", and none kept it. * (`retryingFetch` has since moved onto footprintjs's `attempt()` * runner, which waits on its own timer.) * Emits: N/A. * * ── WHY A BARE `setTimeout` IS NOT A MINIMUM ───────────────────────────── * Node fires a timer when the event loop's clock says `ms` have passed since * the timer was armed, and that clock counts WHOLE milliseconds: the stamp * taken when the timer is armed is floored, so a timer armed late inside a * millisecond fires up to that fraction early once the clock refreshes. On * Linux the loop clock is the coarse monotonic clock, up to a tick behind, * and the shortfall can pass a full millisecond — so adding a millisecond to * the delay does not fix it, and reading a better clock around the old timer * only measures it. Node also truncates a fractional delay * (`setTimeout(f, 4.7)` arms 4) and turns a delay past 2^31 − 1 ms into 1 ms. * * ── THE LAW ────────────────────────────────────────────────────────────── * A wait from here never resolves before `ms` of MONOTONIC time have passed. * The deadline is read from `performance.now()` when the wait starts; when the * timer fires the clock is read again, and a timer that fired early is * re-armed for what is left — rounded up, and split below 2^31 ms — until the * deadline has passed. The timer decides when to LOOK; the clock decides * whether the wait is over. * * ── WHAT IT DOES NOT PROMISE ───────────────────────────────────────────── * A maximum. A loaded host wakes a timer late, and nothing here shortens a * wait to make up for it. A timeout (abort after `ms`) and a grace timer are * not waits with a minimum and do not belong here — they stay beside the work * they bound. * * ── ZERO AND ABORT ─────────────────────────────────────────────────────── * `ms <= 0` (or not a number) is no wait: it resolves at once and does not * consult the signal, because there is nothing to cancel. That is what the * retry back-offs and the mock provider always did for a zero wait, and the * two copies that did consult it (`typesafe`, the device-flow poll) check the * signal themselves right beside the wait — so no caller's result changed when * they moved here. A positive wait rejects at once on an already-aborted * signal and clears its timer on a later abort. The rejection is the caller's * to choose (`abortError`) — each caller kept the error it threw before; the * default is `signal.reason ?? new Error('Aborted')`. * * ── TESTING WITH FAKE TIMERS ───────────────────────────────────────────── * The clock and the timer are read at CALL time, so a fake-timer install that * fakes both (`vi.useFakeTimers()` in this repo's Vitest fakes `performance` * with `setTimeout`) drives a wait exactly. A fake `setTimeout` beside a real * `performance.now()` leaves a wait waiting for real time: fake both, or * neither. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.sleep = exports.makeSleep = void 0; /** The longest delay a platform timer holds; Node turns a longer one into 1 ms. */ const MAX_TIMER_MS = 2 ** 31 - 1; const reasonOf = (signal) => signal.reason ?? new Error('Aborted'); /** The platform's clock and timer, looked up at call time (fake timers stay visible). */ const PLATFORM_CLOCK = { now: () => performance.now(), setTimer: (fire, ms) => setTimeout(fire, ms), clearTimer: (handle) => clearTimeout(handle), }; /** A wait over `clock`. The library uses `sleep`; this exists so a test can fire timers early. */ function makeSleep(clock) { return (ms, signal, abortError = reasonOf) => { if (!(ms > 0)) return Promise.resolve(); return new Promise((resolve, reject) => { if (signal?.aborted) { reject(abortError(signal)); return; } const until = clock.now() + ms; let timer; let unwatch = () => undefined; const check = () => { const left = until - clock.now(); if (left > 0) { timer = clock.setTimer(check, timerMsFor(left)); return; } unwatch(); resolve(); }; timer = clock.setTimer(check, timerMsFor(ms)); if (signal) { const watched = signal; const onAbort = () => { clock.clearTimer(timer); reject(abortError(watched)); }; watched.addEventListener('abort', onAbort, { once: true }); unwatch = () => watched.removeEventListener('abort', onAbort); } }); }; } exports.makeSleep = makeSleep; /** A timer for what is left: rounded UP (Node truncates a fraction), never past what a timer holds. */ function timerMsFor(left) { return Math.min(Math.ceil(left), MAX_TIMER_MS); } /** The library's wait. Every promised minimum in `src/` goes through it. */ exports.sleep = makeSleep(PLATFORM_CLOCK); //# sourceMappingURL=sleep.js.map