fix(tabs): correct --tab scoped commands and un-break provider direct-page path (#1249)

* 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`.
This commit is contained in:
Chris Tate
2026-04-16 12:34:14 -05:00
committed by GitHub
parent 67dc631977
commit c201623710
8 changed files with 284 additions and 18 deletions
+15 -3
View File
@@ -290,13 +290,24 @@ agent-browser network har stop [output.har] # Stop and save HAR (temp path if
### Tabs & Windows
```bash
agent-browser tab # List tabs
agent-browser tab # List tabs (shows stable `tabId` for each)
agent-browser tab new [url] # New tab (optionally with URL)
agent-browser tab <n> # Switch to tab n
agent-browser tab close [n] # Close tab
agent-browser tab <id> # Switch to tab by id
agent-browser tab close [id] # Close tab by id (defaults to active tab)
agent-browser window new # New window
```
Tab IDs are stable and never reused within a session, so agents can keep
referring to the same tab across commands even if other tabs are opened or
closed in between. To run a single command against a specific tab without
changing the active tab, use the global `--tab <id>` flag:
```bash
agent-browser tab new https://docs.example.com # opens and activates tab 2
agent-browser --tab 1 snapshot # peek at tab 1 (tab 2 stays active)
agent-browser click "#submit" # runs on tab 2 as expected
```
### Frames
```bash
@@ -610,6 +621,7 @@ This is useful for multimodal AI models that can reason about visual layout, unl
|--------|-------------|
| `--session <name>` | Use isolated session (or `AGENT_BROWSER_SESSION` env) |
| `--session-name <name>` | Auto-save/restore session state (or `AGENT_BROWSER_SESSION_NAME` env) |
| `--tab <id>` | Target a specific tab by stable `tabId` for this command only; the active tab is restored afterward |
| `--profile <name\|path>` | Chrome profile name or persistent directory path (or `AGENT_BROWSER_PROFILE` env) |
| `--state <path>` | Load storage state from JSON file (or `AGENT_BROWSER_STATE` env) |
| `--headers <json>` | Set HTTP headers scoped to the URL's origin |