/** * Capture Agent — Wait Contract (AUT-240) * * Single source of truth for the engine's adaptive waiting behaviour. Zero user * configuration: every budget/deadline below is an internal constant. Versioned * (`WAIT_CONTRACT_VERSION`) so runs can be debugged and future migrations * reasoned about; the global engine-versioning infrastructure is a separate * effort. * * The model has three layers (see the AUT-240 issue): * - Layer A — actionability & semantic postconditions. * - Layer B — `waitForVisuallyStable` (screenshot stabilization). * - Layer C — dynamic budgets: a wait extends while the page is making progress * (network / DOM) and is cut once it is genuinely stuck, up to a hard * per-media global deadline. This is the real cure for the engine's #1 pain — * a slow-but-progressing page no longer trips a fixed timeout. */ import type { MediaMode, ProgressSnapshot } from './execution-types.js'; /** Bump when the wait semantics change in a way worth tracing on a run. */ export declare const WAIT_CONTRACT_VERSION = 1; /** * Hard ceiling on adaptive waiting, per media mode. Applied as a FLOOR via * `max()` with the compiled opcode timeout (see `resolveGlobalWaitDeadlineMs`) * so it never shortens an intentionally-long opcode (`SLEEP`, `END_CLIP`). */ export declare const GLOBAL_WAIT_CAP_MS: Record; /** No observed progress for this long ⇒ the wait is "stuck" and gets cut. */ export declare const STUCK_WINDOW_MS = 4000; /** Watchdog poll cadence while a wait is in flight. */ export declare const PROGRESS_POLL_INTERVAL_MS = 250; /** * A timed-out wait cannot be cancelled through the adapter API. Before recovery * or the next opcode starts, give the orphaned operation this long to settle so * it does not mutate the page concurrently. Mirrors the runner's drain. */ export declare const ORPHAN_DRAIN_MS = 2000; /** DOM-quiet settle window for visual stabilization (Layer B). */ export declare const DOM_QUIET_WINDOW_MS = 250; /** Bounded pixel-convergence fallback (Layer B). */ export declare const PIXEL_FALLBACK_MAX_PASSES = 3; export declare const PIXEL_FALLBACK_DIFF_THRESHOLD = 0.01; /** * Resolve the per-opcode global wait deadline (absolute ms timestamp). * `compiledTimeoutMs` is the opcode's compiled `timeoutMs` — treated as a FLOOR, * never a ceiling, so the deadline can only ever be extended past the compiled * value, never shortened below it. */ export declare function resolveGlobalWaitDeadlineMs(startedAtMs: number, compiledTimeoutMs: number, mediaMode: MediaMode): number; /** * Did the page make observable, CORROBORATED progress between two snapshots? * Progress is one of: * - a navigation in flight (`navigating`); * - a first-party request COMPLETING — a net decrease in `inflightRequests` * (something finished or failed, i.e. a response the page can act on); * - a `readyState` transition; * - a `domNodeCount` change (the page rendered/mutated something). * * A null current reading is treated conservatively as "no progress"; the first * real reading (null `prev`) counts as progress to seed the watchdog window. * * Deliberately does NOT key on monotonic counters that a steady self-poll bumps * forever (`networkEventCount`, `lastNetworkActivityAtMs`) nor on a request * merely STARTING (an INCREASE in `inflightRequests`): an app polling its own * origin every tick would otherwise keep this `true` indefinitely, `lastProgressAt` * would keep resetting, and the stuck-cut would never fire — burning the global * deadline and skipping recovery (AUT-240 D2). Requiring a completion or a * DOM/readyState change means real loads (requests resolve, the DOM fills, * readyState advances) still register, while idle chatter does not. * * The network fields are also first-party-scoped at the source (see * `getProgressSnapshot` / `isFirstPartyForProgress`): background third-party * telemetry (analytics beacons, ad pixels, polling to other origins) is * excluded so it cannot masquerade as the app's own work. */ export declare function hasProgress(prev: ProgressSnapshot | null | undefined, cur: ProgressSnapshot | null | undefined): boolean; export type ProgressBudgetCut = 'stuck' | 'deadline'; export interface ProgressBudgetOptions { /** `Date.now()` when the surrounding opcode started (for elapsed/min-budget). */ startedAtMs: number; /** Absolute hard deadline — the wait never runs past this. */ globalDeadlineMs: number; /** * Minimum patience (the compiled `timeoutMs` floor) before the stuck-cut may * fire. A slow page that produces no early signal still gets at least this * long before being judged stuck. */ minBudgetMs: number; /** No-progress window before cutting "stuck". Defaults to `STUCK_WINDOW_MS`. */ stuckWindowMs?: number; /** Watchdog cadence. Defaults to `PROGRESS_POLL_INTERVAL_MS`. */ pollIntervalMs?: number; /** * Progress probe. When omitted, the stuck-cut is disabled and the operation * simply runs with its compiled floor budget (legacy fixed-timeout behaviour) * — graceful degradation for adapters with no progress signal. */ getProgress?: () => Promise; } export interface ProgressBudgetResult { /** Present when the wrapped operation resolved before any cut. */ result?: T; /** Present when the watchdog cut the wait early. */ cut?: ProgressBudgetCut; /** Total time spent (ms). */ waitedMs: number; } /** * Run an adaptive wait that extends while the page is making progress and is cut * once it is genuinely stuck or the global deadline is reached. * * `run(budgetMs)` is given a generous budget (up to the global deadline) and is * expected to resolve as soon as its condition is met (or to time out at the * budget). A concurrent watchdog races it: while `run` is still pending, the * watchdog cuts the wait if no progress is observed for `stuckWindowMs` (after * `minBudgetMs` has elapsed) or once the global deadline passes. If `run` * resolves first, its result is returned and the watchdog is moot. */ export declare function runWithProgressBudget(run: (budgetMs: number) => Promise, options: ProgressBudgetOptions): Promise>;