import { DeliveredResult } from '@byollm/protocol'; /** Why a wait ended without a result. */ declare class NoRunnerAvailableError extends Error { readonly jobId: string; readonly reason: string; readonly name = "NoRunnerAvailableError"; constructor(jobId: string, reason: string); } /** The wait exceeded its timeout while a runner was still plausibly working. */ declare class ResultTimeoutError extends Error { readonly jobId: string; readonly timeoutMs: number; readonly name = "ResultTimeoutError"; constructor(jobId: string, timeoutMs: number); } interface WaitOptions { /** Give up after this long. Default 5 minutes. */ readonly timeoutMs?: number; /** * Called instead of throwing when no runner can take the job. Return a * substitute and the wait resolves with it; return nothing and * {@link NoRunnerAvailableError} is thrown. * * **`void` is in the union so that "return nothing" can be written the way * anybody would write it.** The sentence above has always promised that * mode, and the type refused it: `() => { showConnectModal(); }` is * `() => void`, which TypeScript will not assign to a signature returning * `string | undefined`, and `async () => { await ask(); }` failed the same * way. The async one is the worse miss, because prompting somebody to * connect and waiting for them IS asynchronous — the README advertises that * exact use and the first person to follow it would have been told their * correct code was wrong. Found by installing the published package into an * empty project and typing the README out. * * **A string is enough.** It is the app's own fallback answer — a hosted * model's text, a cached reply — not wire data, and requiring a whole * `DeliveredResult` for it was ceremony that invited invented shapes. The * README's own example got it wrong, which is how this was found. * * **Whatever comes back is labelled `fallback: true` by the wait, not by * the caller** — {@link MUSTS.FALLBACK_LABELED}. Work that did not come * from the user's own compute must not be reportable as though it did, and * that stays true whether an app returns a bare string or a full record it * assembled itself. The stamp is applied after this function returns, so * there is no shape an app can hand back that hides what it is. */ readonly onNoRunner?: (reason: string) => string | DeliveredResult | undefined | void | Promise; /** Abort the wait. */ readonly signal?: AbortSignal; } /** * How an app learns a job finished. * * byollm_003 Rev 1 is explicit that this is a *channel* — webhook, Realtime * subscription, or poll — and never an implied in-request `await`. The * polling implementation below is the portable default; the Supabase adapter * substitutes Realtime for the same interface. */ interface ResultDelivery { waitFor(jobId: string, options?: WaitOptions): Promise; } interface PollingDeliveryDeps { /** Current state of the job, or null if unknown. */ readonly read: (jobId: string) => Promise; /** * Whether a runner could still take this job — **when there is anybody to * ask.** * * Optional since alpha.66, and its absence is the answer rather than a * missing dependency. On the cloud lane nothing writes runners into this * site's store: devices pair with the relay, so the question has no local * answer and `runnerAvailability` refuses to invent one. * * The refusal was correct and it landed in a loop that asked every 500ms. * `job.result()` threw on its first poll for every cloud-lane site — a * refusal aimed at outsiders that our own delivery tripped over. * * Not fixed by catching the throw here. That is a swallowed error in * costume, and a catch wide enough to hold it would also eat a store that * had genuinely gone away. The instrument is simply not handed over on a * lane where it cannot see, and this loop does not ask a question nobody * can answer. */ readonly availability?: (jobId: string) => Promise<{ available: boolean; reason?: string; blocked: boolean; }>; readonly sleep?: (ms: number) => Promise; /** * Injectable clock. It must advance in step with {@link sleep}: a test that * stubs one and not the other gets a loop whose grace window never elapses. */ readonly now?: () => number; /** * How long a sustained no-runner signal must persist before it is believed. * Defaults to {@link NO_RUNNER_GRACE_MS}. */ readonly graceMs?: number; } /** * The portable delivery channel: poll the store until the job is terminal. * * Correct everywhere and adequate for most apps. An adapter with a push * channel should replace it — see the Supabase adapter's Realtime delivery. */ declare class PollingDelivery implements ResultDelivery { #private; constructor(deps: PollingDeliveryDeps); waitFor(jobId: string, options?: WaitOptions): Promise; } export { NoRunnerAvailableError as N, type PollingDeliveryDeps as P, type ResultDelivery as R, type WaitOptions as W, PollingDelivery as a, ResultTimeoutError as b };