/** * Unified target resolver for browser actions. * * Resolution pipeline: * * 1. Input classification: all-digit → numeric ref path, otherwise → CSS path. * The CSS path passes the raw string to `querySelectorAll` and lets the * browser parser decide what's valid. No frontend regex whitelist — the * goal is that any selector accepted by `browser find --css` is accepted * by the same selector on `get/click/type/select`. * 2. Ref path: cascading match levels (see below), using data-opencli-ref * plus the fingerprint map populated by snapshot + find. * 3. CSS path: querySelectorAll + match-count policy (see ResolveOptions) * 4. Structured errors: * - numeric: not_found / stale_ref * - CSS: invalid_selector / selector_not_found / selector_ambiguous * / selector_nth_out_of_range * * All JS is generated as strings for page.evaluate() — runs in the browser. * * ── Cascading stale-ref (browser-use style) ────────────────────────── * Strict equality on the fingerprint rejected too many live pages — SPA * re-renders swap text / role while keeping id + testId. The resolver * now walks three tiers before giving up: * * 1. EXACT — tag + strong id (id or testId) agree, ≤1 soft mismatch * 2. STABLE — tag + strong id agree, soft signals drifted (aria-label, * role, text) — agent gets a warning but the action * proceeds so dynamic pages don't stall * 3. REIDENTIFIED — original ref either missing from the DOM or fully * mismatched, but the fingerprint uniquely identifies * a single other live element via id / testId / * aria-label. Re-tag that element with the old ref and * surface match_level so the caller knows we swapped. * * Only when all three fail do we emit `stale_ref`. Every success envelope * carries `match_level` so downstream CLIs can surface the weakest tier * a caller actually traversed. */ export interface ResolveOptions { /** * When CSS matches multiple elements, pick the element at this 0-based * index instead of raising `selector_ambiguous`. Raises * `selector_nth_out_of_range` if `nth >= matches.length`. */ nth?: number; /** * When CSS matches multiple elements, pick the first match instead of * raising `selector_ambiguous`. Used by read commands (get text / value / * attributes) to deliver a best-effort answer + matches_n in the envelope. * Ignored when `nth` is also set (nth wins). */ firstOnMulti?: boolean; } /** Tier the resolver traversed to land the target. Callers may surface this to agents. */ export type TargetMatchLevel = 'exact' | 'stable' | 'reidentified'; /** * Generate JS that resolves a target to a single DOM element. * * Returns a JS expression that evaluates to: * { ok: true, matches_n, match_level } — success (el stored in `__resolved`) * { ok: false, code, message, hint, candidates, matches_n? } — structured error * * `match_level` is always set on success: * - CSS path → 'exact' * - numeric ref path → whichever tier matched ('exact' / 'stable' / 'reidentified') * * The resolved element is stored in `window.__resolved` for downstream helpers. */ export declare function resolveTargetJs(ref: string, opts?: ResolveOptions): string; /** * Generate JS that scrolls + measures `__resolved` without clicking. * * Generic click prefers CDP `Input.dispatchMouseEvent`, which fires the full * pointer/mouse chain that Radix/MUI/shadcn dropdowns rely on. Keep measurement * separate so the CDP-primary path does not call DOM `el.click()` first. */ export declare function boundingRectResolvedJs(opts?: { skipScroll?: boolean; }): string; /** * Generate JS for click that uses the unified resolver. * Assumes resolveTargetJs has been called and __resolved is set. * * This is the JS fallback path. BasePage.click uses boundingRectResolvedJs for * the CDP-primary path and only reaches this when native click is unavailable * or the target has no usable rect. */ export declare function clickResolvedJs(opts?: { skipScroll?: boolean; }): string; /** * Generate JS for type that uses the unified resolver. */ export declare function typeResolvedJs(text: string): string; export type FillResolvedResult = { ok: true; actual: string; expected: string; length: number; mode: 'input' | 'textarea' | 'contenteditable'; } | { ok: false; actual: string; expected: string; length: number; mode: 'input' | 'textarea' | 'contenteditable'; } | { ok: false; reason: string; tag?: string; type?: string; role?: string; }; /** * Prepare the resolved element for native CDP Input.insertText. * * This preserves `browser type`'s existing "replace current text" semantics: * focus the editable target, select its current contents, then let CDP insert * real browser text input so rich editors can update their internal state. */ export declare function prepareNativeTypeResolvedJs(opts?: { skipScroll?: boolean; skipFocus?: boolean; }): string; /** * Verify the exact value/text currently held by the resolved editable target. * Assumes resolveTargetJs and prepareNativeTypeResolvedJs have already set * `window.__resolved` to the normalized editable host. */ export declare function verifyFilledResolvedJs(expected: string): string; /** * Generate JS for scrollTo that uses the unified resolver. * Assumes resolveTargetJs has been called and __resolved is set. */ export declare function scrollResolvedJs(opts?: { skipScroll?: boolean; }): string; /** * Generate JS to get text content of resolved element. */ export declare function getTextResolvedJs(): string; /** * Generate JS to get value of resolved input/textarea element. */ export declare function getValueResolvedJs(): string; /** * Generate JS to get all attributes of resolved element. */ export declare function getAttributesResolvedJs(): string; /** * Generate JS to select an option on a resolved