/** * LLM decisions and how they become executed Steps. A Decision is mapped to a typed Step * (`decisionToStep`, a pure translation plus locate-enrichment) and then executed through the SAME * `BuiltinStepHandler` the replay pipeline uses — one execution path for discover, replay, and * step-heal (invariant #2), instead of a second driver-dispatch switch drifting alongside it. */ import type { Driver } from "../ports.js"; import type { Assertion, PageElement, Step, WaitUntil } from "../types.js"; export interface Decision { action: "click" | "doubleClick" | "hover" | "type" | "select" | "pressKey" | "scroll" | "goto" | "waitFor" /** Explore-only (#102): record an observed UX problem without touching the page. `text` carries * the problem, `severity` how bad it is. Discover's prompt never teaches it; if a model emits it * anyway, `decisionToStep` rejects it like `done`. */ | "note" | "done"; text?: string; /** Disambiguate identically-named elements (#127): the element's role, and the 0-based * position among same role+name matches — the `(nth=K)` marker the listing shows. */ role?: string; nth?: number; value?: string; key?: string; direction?: "down" | "up"; url?: string; until?: WaitUntil; reason?: string; assertions?: Assertion[]; /** note: how bad the observed problem is (default "info"). */ severity?: "info" | "warn" | "error"; } export type PolicyVerdict = { ok: true; } | { ok: false; reason: string; }; /** What the loop can show a policy about the page (#77): the elements it just snapshotted, and — * when an observation is already in hand (vet) — the current URL. */ export interface PolicyContext { elements: readonly PageElement[]; url?: string; } /** * A deterministic gate the discover loop consults before executing each proposed action (invariant #2: * inject behavior, don't branch in the loop). Kept app-agnostic (invariant #1) — the engine offers the * seam; a consumer supplies the rules (block destructive controls, cap wandering, stop on a goal). */ export interface ActionPolicy { /** Vet an action before it runs. Rejected → recorded as a failure so the LLM re-decides; it never * executes. A throwing vet is treated as a rejection, not a crash. A stateful policy may count * calls across the run (e.g. a consecutive-scroll cap). */ vet(decision: Decision, ctx?: PolicyContext): PolicyVerdict; /** End discovery because the GOAL is reached — the freeze is trusted (non-truncated); step bounds * belong to `maxSteps`. Checked each iteration with the fresh page elements, and once more at the * step cap. `steps` includes the seed `baseUrl` goto. */ stop?(steps: readonly Step[], ctx?: PolicyContext): boolean; } /** First JSON object in a model reply, tolerating fences, prose, and trailing extra objects. */ export declare function parseDecision(text: string): Decision; /** * Map a non-`done` decision to a typed Step. Target-bearing actions are enriched with resilient * locators (role + structural index) at freeze time, so replay survives a UI rename without the LLM. * Pure translation — execution happens through the shared step handler in `applyDecision`. */ export declare function decisionToStep(driver: Driver, decision: Decision): Promise; /** Execute a non-`done` decision and return the Step it produced. Throws if it fails. */ export declare function applyDecision(driver: Driver, decision: Decision): Promise; /** A short `action "text"|url` label for failure/policy messages. */ export declare function describeAction(decision: Decision): string; /** * Why a target-bearing decision is ambiguous on this page, or undefined if it isn't (#127): the * name matches several elements within one role and the decision carries no `nth`. Checked by the * loop BEFORE the driver runs — the principle lives once in core, so every consumer's driver gets * it — and the message tells the model how to re-decide. Cross-role multi-matches are not * ambiguous here: an a11y wrapper pair (link over its own StaticText) is the common benign case, * and a real cross-role duplicate is addressable by sending `role`. */ export declare function describeAmbiguity(decision: Decision, elements: readonly PageElement[]): string | undefined;