agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
75 lines • 5 kB
TypeScript
/**
* 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.
*/
/** What a wait reads and arms. The platform's by default; a test passes a fake. */
export interface SleepClock {
/** Monotonic milliseconds, fractions welcome. */
now(): number;
/** Arm a one-shot timer; returns its handle. */
setTimer(fire: () => void, ms: number): unknown;
/** Disarm a handle `setTimer` returned. */
clearTimer(handle: unknown): void;
}
/** Builds an aborted wait's rejection from the signal that aborted it. */
export type AbortErrorOf = (signal: AbortSignal) => unknown;
/** Wait at least `ms` of monotonic time. See the module notes for zero and abort. */
export type Sleep = (ms: number, signal?: AbortSignal | null, abortError?: AbortErrorOf) => Promise<void>;
/** A wait over `clock`. The library uses `sleep`; this exists so a test can fire timers early. */
export declare function makeSleep(clock: SleepClock): Sleep;
/** The library's wait. Every promised minimum in `src/` goes through it. */
export declare const sleep: Sleep;
//# sourceMappingURL=sleep.d.ts.map