packages/cua-driver/docs/browser-semantic-state-plan.md
The implementation adds semantic_v2 without changing the dom_refs_v1
compatibility default. Deterministic core coverage, the public reference and
how-to pages, the installed skill, and a real standalone-browser harness row
are in place. The final acceptance gate is the exact-SHA Windows and Linux X11
workflow plus the source-installed macOS replay described below.
Make get_browser_state return the page content and controls that matter in the
current browser view, even when a large application retains hundreds of hidden,
offscreen, or stale nodes. After exact browser attachment, an agent should read
and operate webpage content through browser tools. Native AX and PX remain for
browser chrome, permission prompts, downloads, file pickers, and explicit
fallbacks.
The implementation must preserve these existing guarantees:
pid and window_id remain the binding anchor;get_browser_state remains read-only;The collector in cua-driver-core/src/browser/engine.rs calls
DOM.getDocument, walks the pierced DOM in document order, marks nodes as
interactive from broad tag and attribute rules, and truncates the result to 300
refs after collection.
This causes four problems:
Increasing the cap would increase output size while preserving the ordering fault.
get_browser_state produces deterministic state and capabilities. It does not
run a model, choose an action, mutate the page, or repair a failed workflow.
Callers inspect state, select a current ref, invoke a typed action, and inspect
state again.
Readable page content and actionable elements are different data sets:
browser_click or browser_type.The collector classifies and ranks nodes before applying output budgets. The default order is:
Document order is retained within each tier.
Browser layout visibility describes whether page content is rendered in or near the tab viewport. It does not depend on whether the native browser window is foreground, covered by another window, or outside the visible desktop. This distinction preserves full-background browser actions.
The driver may cache protocol connections and data used within one snapshot. It must not cache natural-language action plans or replay old selectors. Workflow repetition and self-repair belong in the calling agent. Cua Driver continues to require a current capability for each mutation.
Collect the following browser protocol state concurrently where dependencies allow:
| Source | Purpose |
|---|---|
| Frame tree | Frame topology, loader identity, and parent relationships |
| Accessibility tree | Roles, names, values, states, readable text, and ignored-node pruning |
| Pierced DOM | Backend node IDs, tags, attributes, author shadow roots, and same-process frame documents |
| Layout snapshot | Computed visibility, bounds, viewport relationship, and paint order |
| Layout metrics | Viewport dimensions, scroll offsets, and device scale |
Build one DOM index for each unique CDP session during a snapshot. Same-process frames share their session index. Out-of-process frames use the existing capability-tested child-session route.
Add an internal SemanticNode model with at least:
frame_ref
backend_node_id?
role
name?
value?
states
parent
children
document_order
visibility
action_kinds
visibility is a closed enum:
in_viewport
near_viewport
offscreen
css_hidden
no_layout
page_occluded
unknown
Treat page_occluded conservatively. If paint-order evidence is incomplete,
use unknown and keep the node. Never infer CSS or page occlusion from native
window occlusion.
Use the accessibility tree for the semantic outline because it already contains roles, names, values, state, and readable text. Remove ignored and redundant structural nodes while preserving useful ancestors. Collapse repeated static text only when the parent already carries the same accessible name.
Some custom controls do not appear as actionable AX nodes. Add a bounded DOM supplement for elements with clear interaction evidence:
The supplement must reject aria-hidden, hidden, display:none,
visibility:hidden, zero opacity, and inherited pointer-cursor noise.
Preserve the current frame security model:
FrameRef and document identity;Avoid repeated full DOM reads per frame. Index each unique protocol session once and slice frame subtrees from that index.
Large pages can fail an unbounded DOM.getDocument call. Use a bounded fallback:
DOM.describeNode calls;Transient transport failures remain errors. They must not be treated as a capability gap or partial success.
Add a versioned snapshot request without breaking existing clients:
{
"session": "research-1",
"target_id": "bt-...",
"tab_id": "tab-...",
"snapshot_format": "semantic_v2"
}
Keep the existing response available as dom_refs_v1 during migration. The
skill and harnesses move to semantic_v2 before it becomes the default. Remove
the old format only through the normal deprecation process.
The new response contains:
{
"status": "ok",
"mode": "snapshot",
"snapshot": {
"id": "p42",
"format": "semantic_v2",
"complete": false,
"scope": "viewport",
"omitted": {
"css_hidden": 410,
"offscreen": 82,
"unprovable_frame": 0
},
"continuation": "opaque-capability-or-null"
},
"page": {
"url": "https://fixture.invalid/inbox",
"title": "Inbox",
"focused_ref": "p42:8"
},
"outline": "...compact semantic tree...",
"refs": []
}
The outline is compact text for model consumption. refs remains structured
JSON for deterministic action routing. The response must not expose raw CDP
target IDs, backend node IDs, object IDs, or selectors.
Each ref reports enough public information to choose a typed action:
{
"ref": "p42:8",
"role": "button",
"name": "Reply",
"states": { "disabled": false },
"actions": ["click"],
"frame": "main",
"visibility": "in_viewport"
}
Internal storage keeps the existing backend node and frame evidence. Mutation
tools reject a ref when the requested action is absent from actions.
Support three read-only scope mechanisms in this order:
scope_ref: inspect the subtree rooted at a current semantic or actionable
ref;query: return role, accessible-name, and visible-text matches with ancestor
context;continuation: inspect the next ranked segment from the same live snapshot
generation.All scope tokens are opaque, session-bound, tab-bound, and generation-bound. Do not expose XPath or CSS as the primary agent contract. A later expert-only selector field may be considered after the capability path is accepted.
Replace the single truncated boolean with:
Budgets are configuration constants in the first implementation. Public numeric overrides wait until performance and abuse bounds are known.
cua-driver-core/src/browser/engine.rscua-driver-core/src/browser/store.rsRefEntry;cua-driver-core/src/browser/tools.rssnapshot_format, scope_ref, query, and continuation to snapshot
mode;target_id field in the current snapshot response;Skills/cua-driver/BROWSER.md to use semantic_v2;get_window_state for browser chrome and native fallbacks;Extend the repo-local web harness with a large application fixture containing:
Use fixture data only. Do not use customer domains, account names, email text, profile paths, or browser history in source, logs, screenshots, or artifacts.
Add tests for:
Run the same source-built Rust rows on each accepted browser and platform:
| Scenario | Foreground posture | Background posture | Required evidence |
|---|---|---|---|
| Read visible detail text | yes | yes | semantic outline contains fixture text |
| Click visible action | yes | yes | fixture journal changes once |
| Type into visible editor | yes | yes | exact delivered text and no leaked input |
| Hidden-node pressure | yes | yes | visible controls survive the budget |
| Scoped duplicate-name action | yes | yes | only the scoped region changes |
| Modal page occlusion | yes | yes | covered control omitted or marked occluded |
| Continuation | yes | yes | offscreen control becomes addressable |
| Rerender staleness | yes | yes | old ref refuses, new ref succeeds |
| Frame and shadow action | yes | yes | exact contained frame mutates |
Background rows run with the browser natively occluded and with focus, cursor, and leaked-input sentinels active. The page viewport stays unchanged so browser layout visibility remains comparable between postures.
The semantic collector should produce equivalent page results across operating systems because it runs above the native platform layer. Platform-specific differences are limited to attachment, endpoint proof, consent, and native sentinels.
Gate: the fixture reproduces the defect deterministically on a source build.
Gate: visible detail text and editor state are available without native AX.
Gate: hidden-node pressure no longer displaces visible state.
scope_ref, semantic query, and opaque continuation;Gate: duplicate names and offscreen content are addressable without raw selectors or an unbounded response.
Gate: large multi-frame fixtures finish within the accepted budgets and report partial coverage truthfully when a budget is exhausted.
Gate: all supported rows pass or produce their documented structured refusal, and the old snapshot format remains compatible.
semantic_v2 the skill default;Gate: one release cycle completes with no unresolved compatibility or privacy regression.
The project is complete when:
browser_act or browser_extract tools;