* fix(tabs): preserve refs across --tab peek and cover outer-tab-closed path Follow-up to #1249 so `--tab <id>` is actually useful for agents: - Save and restore the outer tab's `ref_map`, `iframe_sessions`, and `active_frame_id` across a scoped command instead of clearing them. `snapshot` → `--tab N <cmd>` → `click @e1` now keeps the outer tab's refs intact. Scoped commands still see a clean slate so outer refs can't resolve against the scoped tab's DOM. - Close the coverage gap the Vercel review bot flagged on #1249: the previous `e2e_tab_scoped_command_handles_outer_tab_closed` test used `tab_close`, which is in the scoped-dispatch exclusion list, so it never exercised the restore-skip branch it claimed to test. Renamed to `e2e_tab_close_with_tab_id_closes_active_tab` with an honest docstring, and added `e2e_tab_scoped_command_outer_tab_closed_mid_dispatch` that actually hits the branch via `window.opener.close()` on a script-opened intermediate tab. - Add `e2e_tab_scoped_command_isolates_refs_from_outer_tab` pinning that outer refs don't bleed into the scoped tab's DOM resolution. - Rewrite `e2e_tab_scoped_command_clears_state_on_switch` as `e2e_tab_scoped_command_preserves_outer_tab_state`, verifying the restored @e1 still clicks end-to-end. - Update the 52 `--help` entries for `--tab <id>` to describe peek / restore semantics instead of a vague "Target specific tab ID". - Update README, docs site, config schema, and the agent-facing skills reference with working examples (refs survive the peek) and a "when to use \`--tab <id>\` vs \`tab <id>\`" guide so agents pick the right flag for their workflow. * fix(tabs): use t<N> prefix for tab ids, add --label for named tabs Follow-on to the tab work in #1249 and the prior commit, redesigning the tab handle surface before release since nothing ships these features yet. ## Why Incrementing integer tab ids (`1`, `2`, `3`) look indistinguishable from positional indices in command output, LLM-generated scripts, and docs. In the common single-agent case where position and id coincide, readers have no visual cue for which mental model they're using. Positional indices silently shift when unrelated tabs open/close, so misreading a handle as an index is a correctness hazard. ## Changes **Tab ids are now `t1`, `t2`, `t3` (strings).** Bare integer `tabId` values are rejected with a teaching message rather than silently accepted. The `t` prefix matches the `@e1` element-ref convention and makes ids unmistakably non-positional at a glance. **Labels.** Tabs can be created with a user-assigned label (e.g. `docs`, `app`) via `tab new --label <name> [url]`. Labels are interchangeable with `t<N>` ids everywhere a tab ref is accepted. They're never auto-generated, never rewritten on navigation, and must be unique within a session. **Dashboard fix.** `packages/dashboard/src/types.ts` declared `TabInfo.index: number` but the daemon has been sending `tabId` (not `index`) since #892, making `tab.index` `undefined` and breaking the dashboard's close/switch buttons silently. Updated the TS types and usages to consume `tabId` (string) and optional `label`, restoring the dashboard's tab interactions. ## Surface - `cli/src/native/browser.rs`: `TabRef::parse` / `format_tab_id` / `is_valid_label` / `PageInfo.label` / `BrowserManager::resolve_tab_ref` / `BrowserManager::has_label`. `tab_new` gains an optional label argument with duplicate rejection. All JSON responses use the string form and include the label. - `cli/src/native/actions.rs`: scoped-command pre-dispatch and `handle_tab_{switch,close,new}` parse string refs and resolve to stable ids. - `cli/src/{flags,commands,main,output}.rs`: `--tab` / config `tab` are `String`; `tab` subcommand accepts `t<N>` or a label and supports `tab new --label <name> [url]`. All 52 `--help` entries updated. - `agent-browser.schema.json`: `tab` property type is now `string` with a pattern matching `t<N>` or label form. - `packages/dashboard`: `TabInfo.tabId: string` / `label?: string | null`; `closeTabAtom`/`switchTabAtom` take `tabRef: string`; component props updated. - Docs: README, docs site (`commands/` and `configuration/`), and the agent-facing skills reference rewritten with the new examples. ## Tests - Added `TabRef::parse` / `format_tab_id` / `is_valid_label` unit tests pinning the bare-integer rejection, the teaching error, label rules, and round-tripping. - Added `test_tab_switch_by_id` / `_by_label` / `test_tab_new_with_label` / `_with_label_and_url` / `_with_url_then_label` in `commands.rs`; rewrote `test_tab_unknown_subcommand_errors` since labels make `tab select` a legitimate ref. - Added `e2e_tab_new_with_label_can_be_switched_and_peeked`, `e2e_tab_new_with_duplicate_label_errors`, `e2e_tab_scoped_command_rejects_bare_integer`. - Migrated every existing tab e2e test (and one unit test) from integer `tabId` to the string form. `cargo fmt`, `cargo clippy -- -D warnings`, all 30 non-ignored tab unit tests, all 13 tab e2e tests, and `tsc --noEmit` on the dashboard all pass. * refactor(tabs): drop --tab scoped peek flag; keep t<N> ids and labels After fleshing out `--tab <id|label>` in the previous commits (scoped pre/post-dispatch save/restore, ref preservation, outer-tab-closed edge case, full e2e coverage), the machinery-to-value ratio makes the feature hard to justify. Nixing it now while nothing has shipped. ## Why - Every new daemon feature touching per-tab state has to reason about scoped-dispatch interleaving. `ScopedRestore`, pre/post-dispatch hooks, and the exclusion list add ongoing maintenance tax. - Three separate PRs (#892, #1249, and this one pre-nix) were needed to reach "works correctly." That's a smell. - `tab <id|label>` switch + labels already cover the legible multi-tab workflow case. - `--tab` vs `tab <id>` have opposite lifecycle semantics but look identical, teaching every agent two things where one would do. - "Non-disruptive peek" isn't actually race-free: the daemon does swap active tab during execution, so a concurrent client between pre- and post-dispatch sees the scoped tab as active. - Ref-based interaction with scoped tabs never worked ergonomically — refs are per-tab, so `--tab N click @e1` requires `@e1` to already be on tab N, which means a prior switch, which negates the peek. - Adding a feature back is easy; removing shipped API is hard. If per-tab caching (`HashMap<tab_id, RefMap>`) lands later, `--tab` can be reintroduced essentially for free. That's the right time. ## Removed - `--tab <id|label>` global flag (`cli/src/flags.rs`, `cli/src/main.rs`, all 52 `--help` entries in `cli/src/output.rs`). - `tab` property in `agent-browser.schema.json` and the config-options row in `docs/src/app/configuration/page.mdx`. - `ScopedRestore` struct, pre/post-dispatch save/restore in `execute_command` (`cli/src/native/actions.rs`). - `impl Default for RefMap` in `cli/src/native/element.rs` (only added for `mem::take` in the scoped machinery). - `e2e_tab_global_targeting`, `_snapshot`, `_snapshot_non_contiguous`, `e2e_tab_scoped_command_preserves_outer_tab_state`, `_isolates_refs_from_outer_tab`, `_restores_active_tab`, `_outer_tab_closed_mid_dispatch`. 590 lines. - The "When to use `--tab` vs `tab <id|label>`" sections in README, docs site, and skills reference. ## Kept - Stable tab ids (`t1`, `t2`, `t3`) with bare-integer rejection. - User-assigned labels (`tab new --label docs [url]`), with duplicate rejection and interchangeable use everywhere a tab ref is accepted. - `BrowserManager::{active_tab_id, has_tab_id, resolve_tab_ref, has_label}` accessors (still used by the remaining tab handlers). - `TabRef::parse`, `format_tab_id`, `is_valid_label` and their unit tests. - Dashboard TS fix (`TabInfo.tabId` + `label`). - `e2e_tab_close_with_tab_id_closes_active_tab` (renamed docstring to drop the gone exclusion-list reference). - `e2e_tab_new_with_label_can_be_switched_and_closed` (rewrite of the previous `_and_peeked` test — now exercises only switch and close). - `e2e_tab_switch_rejects_bare_integer` (rewrite targeting the `tab_switch` daemon handler rather than the removed scoped path). net: -900 lines across 12 files. `cargo fmt`, `cargo clippy -D warnings`, all 25 non-ignored tab unit tests, all 6 tab e2e tests, and `tsc --noEmit` on the dashboard all pass.
167 lines
4.9 KiB
JSON
167 lines
4.9 KiB
JSON
{
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"title": "Agent Browser Configuration",
|
|
"description": "Configuration file for agent-browser (e.g., agent-browser.json or ~/.agent-browser/config.json)",
|
|
"type": "object",
|
|
"properties": {
|
|
"headed": {
|
|
"type": "boolean",
|
|
"description": "Show browser window instead of running headless."
|
|
},
|
|
"json": {
|
|
"type": "boolean",
|
|
"description": "Output in JSON format."
|
|
},
|
|
"debug": {
|
|
"type": "boolean",
|
|
"description": "Enable debug output."
|
|
},
|
|
"session": {
|
|
"type": "string",
|
|
"description": "Session identifier."
|
|
},
|
|
"sessionName": {
|
|
"type": "string",
|
|
"description": "Auto-save/load state persistence name."
|
|
},
|
|
"executablePath": {
|
|
"type": "string",
|
|
"description": "Path to a custom browser executable."
|
|
},
|
|
"extensions": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Paths to browser extensions. Extensions from user-level and project-level configs are concatenated."
|
|
},
|
|
"profile": {
|
|
"type": "string",
|
|
"description": "Path to the browser profile data directory."
|
|
},
|
|
"state": {
|
|
"type": "string",
|
|
"description": "Path to load/save browser state."
|
|
},
|
|
"proxy": {
|
|
"type": "string",
|
|
"description": "Proxy server URL (e.g., http://localhost:8080)."
|
|
},
|
|
"proxyBypass": {
|
|
"type": "string",
|
|
"description": "Comma-separated domains to bypass the proxy (e.g., localhost,*.internal.com)."
|
|
},
|
|
"args": {
|
|
"type": "string",
|
|
"description": "Additional comma-separated launch arguments for the browser."
|
|
},
|
|
"userAgent": {
|
|
"type": "string",
|
|
"description": "Custom User-Agent string."
|
|
},
|
|
"provider": {
|
|
"type": "string",
|
|
"description": "Provider to use, such as 'ios'."
|
|
},
|
|
"device": {
|
|
"type": "string",
|
|
"description": "Device name or identifier for emulation or providers (e.g., 'iPhone 16 Pro')."
|
|
},
|
|
"ignoreHttpsErrors": {
|
|
"type": "boolean",
|
|
"description": "Ignore HTTPS errors during navigation."
|
|
},
|
|
"allowFileAccess": {
|
|
"type": "boolean",
|
|
"description": "Allow file:// URLs to access local files."
|
|
},
|
|
"cdp": {
|
|
"type": "string",
|
|
"description": "Chrome DevTools Protocol endpoint URL."
|
|
},
|
|
"autoConnect": {
|
|
"type": "boolean",
|
|
"description": "Auto-discover and connect to a running Chrome instance."
|
|
},
|
|
"annotate": {
|
|
"type": "boolean",
|
|
"description": "Annotated screenshot with numbered element labels."
|
|
},
|
|
"colorScheme": {
|
|
"type": "string",
|
|
"enum": ["dark", "light", "no-preference"],
|
|
"description": "Color scheme preference."
|
|
},
|
|
"downloadPath": {
|
|
"type": "string",
|
|
"description": "Default directory for browser downloads."
|
|
},
|
|
"contentBoundaries": {
|
|
"type": "boolean",
|
|
"description": "Wrap page output in boundary markers for LLM safety."
|
|
},
|
|
"maxOutput": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Max characters for page output (truncates beyond limit)."
|
|
},
|
|
"allowedDomains": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Allowed domain patterns (e.g., ['example.com', '*.example.com'])."
|
|
},
|
|
"actionPolicy": {
|
|
"type": "string",
|
|
"description": "Path to action policy JSON file."
|
|
},
|
|
"confirmActions": {
|
|
"type": "string",
|
|
"description": "Comma-separated action categories requiring confirmation."
|
|
},
|
|
"confirmInteractive": {
|
|
"type": "boolean",
|
|
"description": "Enable interactive confirmation prompts (auto-denies if stdin is not a TTY)."
|
|
},
|
|
"engine": {
|
|
"type": "string",
|
|
"enum": ["chrome", "lightpanda"],
|
|
"default": "chrome",
|
|
"description": "Browser engine to use."
|
|
},
|
|
"screenshotDir": {
|
|
"type": "string",
|
|
"description": "Default screenshot output directory."
|
|
},
|
|
"screenshotQuality": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"maximum": 100,
|
|
"description": "JPEG quality for screenshots (0-100)."
|
|
},
|
|
"screenshotFormat": {
|
|
"type": "string",
|
|
"enum": ["png", "jpeg"],
|
|
"description": "Screenshot format."
|
|
},
|
|
"idleTimeout": {
|
|
"type": "string",
|
|
"description": "Auto-shutdown the daemon after inactivity (e.g., '30s', '5m', '1h', or raw milliseconds like '60000')."
|
|
},
|
|
"model": {
|
|
"type": "string",
|
|
"description": "AI model for chat command (e.g., 'openai/gpt-4o')."
|
|
},
|
|
"noAutoDialog": {
|
|
"type": "boolean",
|
|
"description": "Disable automatic dismissal of alert/beforeunload dialogs."
|
|
},
|
|
"headers": {
|
|
"type": "string",
|
|
"description": "Custom HTTP headers supplied as a JSON-formatted string."
|
|
}
|
|
},
|
|
"additionalProperties": true
|
|
}
|