# Browser Agent Runtime OpenCLI browser should become reliable for unknown SaaS workflows without becoming a Playwright clone. The target is an agent-native runtime: - compact observation that gives an agent the right refs in one call, - refs that survive common React re-render/stale-node cases, - real browser input events for user-like interactions, - a small command surface that reduces calls only when the abstraction is proven. This design was triggered by a customer report: Mercury expense category dropdowns worked with `agent-browser + Claude Code`, but OpenCLI often failed to select the category. ## Source Findings We compared OpenCLI against `vercel-labs/agent-browser` at source level, not only from documentation. ### What agent-browser Actually Does The reliable dropdown behavior is not from a full Playwright actionability pipeline or a magic custom-select command. - `cli/src/native/interaction.rs`: `click` resolves the target center and sends CDP `Input.dispatchMouseEvent` as `mouseMoved`, `mousePressed`, `mouseReleased`. It does not run Playwright's full visible/stable/enabled/ receives-events pipeline. - `cli/src/native/interaction.rs`: `select_option` only handles native `` remains a separate operation that sets selected options and dispatches `change`, matching both OpenCLI's current behavior and agent-browser's `select_option` behavior. ### AX Observation And Refs Add an AX-backed observation path alongside the current DOM snapshot. Ref identity should include enough data for re-resolution: ```ts type BrowserRef = { ref: string; backendNodeId?: number; role: string; name: string; nth?: number; frame?: { frameId?: string; sessionId?: string; url?: string; }; bbox?: { x: number; y: number; width: number; height: number }; }; ``` Resolution order: 1. cached `backendNodeId`, 2. AX re-query by role/name/nth in the same frame, 3. existing CSS/DOM resolver fallback when the target is not a ref. The first rollout should be additive: - keep current `browser state` output stable, - Phase 0: add an internal or opt-in AX prototype; do not change the default `browser state` text output, - Phase 1 decision point: either promote AX to default observation or keep it opt-in, - decision criteria: fixture pass rate, stale-ref recovery rate, manual SaaS results, adapter compatibility, and snapshot token size versus the current DOM snapshot. ### Frame Routing Refs from iframes need frame context so commands can act without manual frame switching in common cases. The route should mirror agent-browser's model: - same-origin iframe: parent session plus `frameId` params where CDP supports them, - cross-origin iframe: dedicated attached target session when available, - if unsupported: typed `frame_unreachable` with the iframe ref/name/url. Do not add a global "switch frame" burden to the normal agent path unless the target cannot be routed automatically. ### Actionability Helper Do not start with full Playwright actionability. Implement a small helper that prevents known silent failures: 1. resolve target/ref, 2. scroll into view when CDP supports it, 3. measure a non-zero bounding box, 4. dispatch native input. Boundary by phase: - MVP: scroll into view plus non-zero bounding box only. - Phase 1: add visible/enabled/not-editable checks if the target action needs them and compatibility remains clean. - Phase 2: add stability and receives-events checks only if fixtures or manual SaaS cases show real failures. Each added wait/check has compatibility and latency cost. Typed statuses: ```ts type ActionStatus = | 'ready' | 'not_found' | 'stale_ref_recovered' | 'stale_ref_unresolved' | 'zero_rect' | 'not_visible' | 'disabled' | 'not_editable' | 'frame_unreachable' | 'native_backend_unavailable'; ``` ### Semantic Locators Add semantic locator support after AX refs are in place: ```bash opencli browser click --role button --name "Submit" opencli browser fill --label "Email" "me@example.com" opencli browser get text --testid invoice-total ``` For write operations, ambiguous locators must fail with candidates. They should not silently choose the first match. ### Command Surface Keep the surface smaller than agent-browser and Playwright. Near-term primitives: ```bash opencli browser click opencli browser dblclick opencli browser hover opencli browser focus opencli browser check opencli browser uncheck opencli browser upload opencli browser drag opencli browser wait download [pattern] ``` Keep `browser select` native ``, - checkbox/radio label retarget, - file upload, - same-origin iframe form, - cross-origin iframe where CDP target attach is available. Manual SaaS matrix: | Site / app | Scenario | Controls | |---|---|---| | Mercury | expense category | custom select, portal | | Brex | expense category / memo | custom select, text input | | Ramp | reimbursement category | custom select | | Stripe Dashboard | filters | combobox, menu | | Linear | issue fields | combobox | | Notion | property select | custom select, portal | | Airtable | field select | custom select, grid | | Workday | form dropdown | custom select, iframe risk | | Concur | expense form | custom select, upload | | GitHub | labels | combobox, portal | Manual matrix is not a CI gate at first. It is the calibration set for deciding whether OpenCLI is approaching agent-browser reliability. Execution process: - Phase 0 completion: @opencli-质量官 runs Mercury, Brex, and Linear when access is available. - On each form page, collect `opencli browser state --compare-sources` so the AX default decision has DOM-vs-AX metrics: refs, frame sections, approximate tokens, elapsed time, and per-source errors. - Pass means the relevant category/field can be selected and the form state can be saved or committed. - Failures do not block the already-shipped MVP unless they expose a regression, but they become Phase 1 backlog with exact command sequence and failure reason. ## Test Strategy Phase 0: - unit tests for CDP-primary click fallback behavior, - local fixture that records pointer/mouse/click event order, - component fixtures for Radix/shadcn/MUI-style controls, - stale-ref fixture that re-renders an option after snapshot and verifies AX re-resolution, - direct CDP and Browser Bridge paths where practical. Regression gates: ```bash npm run build npm run typecheck npm test -- --run src/browser src/cli.test.ts npm run check:typed-error-lint npm run check:silent-column-drop ``` Before changing default action behavior, also run the adapter suite on main and on the branch and compare failures. ## Documentation Update after Phase 0: - `skills/opencli-browser/SKILL.md`: recommend snapshot/click/snapshot/click for custom dropdowns until `choose` exists. - Browser command help: explain that `select` is native `