* fix(tabs): initialize tab_id on missing PageInfo sites
PR #892 added a required `tab_id: u32` field to `PageInfo` but missed two
initializer sites, which broke the build on the PR branch. CI never caught
this because the external-contributor workflow status was `action_required`
and never ran.
- `cli/src/native/browser.rs:395` — the `direct_page` branch of
`connect_cdp_inner` used by the cloud providers (Browserbase, Browserless,
Browser Use, Kernel, AgentCore). Use `assign_tab_id()` to get a fresh id.
- `cli/src/native/browser.rs:1580` — a unit test initializer. Use `tab_id: 1`
since the test doesn't exercise id assignment.
* feat(tabs): restore active tab and clear per-tab state for scoped --tab
Follow-up on PR #892's `--tab <id>` flag.
The original implementation called `tab_switch_by_id` directly from the
pre-dispatch block in `execute_command` but didn't touch the daemon's
per-tab state, and never restored the previously-active tab. Two concrete
issues this fixes:
1. `state.ref_map`, `state.iframe_sessions`, and `state.active_frame_id`
were left intact across the pre-dispatch switch, so `--tab N click @e1`
would try to resolve `@e1` against the scoped tab's DOM using a
backend-node id from the outer tab. In practice the click handler's
role+name fallback hid this as "element not found" errors, but on pages
where both tabs have similarly-labelled elements it could click the
wrong one.
2. The PR description promised scoped routing would "restore the previous
active tab", but the implementation permanently switched. `--tab 3
snapshot` would leave tab 3 as the active tab even after the command
returned, surprising subsequent non-scoped commands.
This change:
- Saves the current tab's stable `tab_id` (not its array index, which
would shift if the scoped command closed other tabs) before switching.
- Clears per-tab daemon state before the switch so refs/iframes/frame
context can't leak between tabs.
- After the action runs, restores the original active tab (also via
stable id) unless that tab was closed during the scoped command, in
which case we leave the scoped tab active.
- Adds `BrowserManager::active_tab_id()` and `has_tab_id()` accessors
to support the above without exposing the internal `pages` vector.
* test(tabs): regression tests for scoped --tab state clearing and restoration
Three new `#[ignore]` e2e tests pinning the fixed behavior:
- `e2e_tab_scoped_command_clears_state_on_switch` — populates `ref_map` on
tab 1, runs a `tabId: 2`-scoped command, asserts `ref_map`,
`iframe_sessions`, and `active_frame_id` are all cleared.
- `e2e_tab_scoped_command_restores_active_tab` — sets up two tabs, runs
a scoped command against the non-active one, asserts a subsequent
unscoped command reflects the originally-active tab.
- `e2e_tab_scoped_command_handles_outer_tab_closed` — runs a scoped
`tab_close` that kills the outer tab itself, asserts no error and the
scoped tab becomes active.
Also updates two misleading comments in the PR's existing
`e2e_tab_global_targeting*` tests to reflect restoration semantics; the
assertions themselves were already consistent with restoration.
* docs(tabs): document stable tab IDs and --tab scoped-command flag
Per AGENTS.md, changes that users or agents would need to know about must
land in every doc surface. Fills the gaps PR #892 left:
- `README.md` — new `--tab <id>` row in the Options table, rewrite the
tab command examples to use `<id>` instead of `<n>`, add a paragraph
explaining stable tab IDs and `--tab` peek semantics.
- `docs/src/app/commands/page.mdx` — same command-example rewrite plus a
new "Stable tab IDs and `--tab`" subsection.
- `docs/src/app/configuration/page.mdx` — add `tab` row to the config
options table so JSON config users can discover it.
- `agent-browser.schema.json` — add `tab` property with description,
matching the config schema.
- `skills/agent-browser/references/commands.md` — same command-example
rewrite plus a short paragraph for agents on when to use `--tab`.