pi-agent-browser-native 0.2.60 → 0.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +11 -11
- package/dist/extensions/agent-browser/index.js +51 -30
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -3
- package/dist/extensions/agent-browser/lib/argv-grammar.js +29 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +10 -1
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +7 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +1 -23
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +5 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +2 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +25 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +4 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +25 -17
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +14 -11
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +3 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +5 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +43 -27
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +66 -54
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +24 -15
- package/dist/extensions/agent-browser/lib/playbook.js +4 -4
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +8 -7
- package/dist/extensions/agent-browser/lib/results/presentation.js +2 -1
- package/dist/extensions/agent-browser/lib/runtime.js +62 -10
- package/dist/extensions/agent-browser/lib/session-page-state.js +14 -7
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/COMMAND_REFERENCE.md +66 -24
- package/docs/SUPPORT_MATRIX.md +10 -8
- package/docs/TOOL_CONTRACT.md +24 -24
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +36 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.62 - 2026-06-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Rebaselined upstream capability metadata, command reference, support docs, playbook guidance, and real-upstream output-shape metadata for `agent-browser` `0.31.0` / vercel-labs/agent-browser@5acf7f9.
|
|
10
|
+
- Added upstream `--namespace`, `--restore`, restore-check flags, and `session id` / `session info` support to wrapper parsing, session policy, launch-scoped flag handling, and docs.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Made wrapper-managed browser state namespace-aware across tab/ref tracking, allowed-domain policy, trace/profiler ownership, branch restore, cleanup, nextActions, and fresh-launch recovery.
|
|
15
|
+
- Reduced post-click diagnostic fragility for upstream `agent-browser 0.31.0`: CSS selector clicks without upstream href/navigation fields now skip immediate helper probes, while ref/href clicks keep navigation summaries and overlay diagnostics.
|
|
16
|
+
- Preserved namespace context for managed-session failure recovery and missing-binary nextActions.
|
|
17
|
+
|
|
18
|
+
### Validation
|
|
19
|
+
|
|
20
|
+
- Ran `npm run verify`, `npm run docs -- command-reference check`, `npm run typecheck`, focused runtime/diagnostic/passthrough tests, `npm run verify -- real-upstream`, `npm run verify -- dogfood`, `npm run verify -- lifecycle`, `npm run smoke:platform:doctor`, `npm run smoke:platform:all`, `npm publish --dry-run`, and `git diff --check`.
|
|
21
|
+
- Ran the required reviewer subagent loop until it returned `no findings`.
|
|
22
|
+
|
|
23
|
+
## 0.2.61 - 2026-06-24
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Rebaselined upstream capability metadata, command reference, support docs, playbook guidance, and real-upstream output-shape metadata for `agent-browser` `0.30.1` / vercel-labs/agent-browser@7379f7dbea76ad8dbf47f177349c4c3ce9263dcb.
|
|
28
|
+
- Removed the constrained `job.assertUrl` glob-to-`wait --fn` workaround now that upstream `wait --url` matches glob patterns such as `**/dashboard` against the full active URL.
|
|
29
|
+
|
|
30
|
+
### Validation
|
|
31
|
+
|
|
32
|
+
- Ran `npm run verify`, `npm run docs`, focused `npx tsx --test test/agent-browser.extension-input-modes.test.ts`, `npm run verify -- command-reference`, and `git diff --check`.
|
|
33
|
+
- Probed upstream `agent-browser 0.30.1` directly: `wait --url "**/dashboard"` succeeds after `pushstate /dashboard`; `find ... uncheck` and `wait <selector> --state hidden|detached` still fail, so only the URL-glob workaround was removed.
|
|
34
|
+
|
|
5
35
|
## 0.2.60 - 2026-06-24
|
|
6
36
|
|
|
7
37
|
### Changed
|
package/README.md
CHANGED
|
@@ -79,13 +79,13 @@ The result is optimized for agent work:
|
|
|
79
79
|
| Stateful cookies/storage/auth output bloats or leaks context | Presentation layer redacts `details.data` for cookies and credential-like storage values while keeping low-risk local QA values such as `theme: dark` readable; recursively scrubs other structured upstream JSON (network, diff, trace/profiler, stream, dashboard, chat, auth, dialog, frame, state, and similar) using sensitive key names plus string heuristics; masks sensitive argv flags and positionals; scrubs secrets from failed batch step errors; and exposes a compact redacted `batch` matrix on top-level `details.data` | `extensions/agent-browser/lib/results/presentation.ts`, `extensions/agent-browser/lib/results/presentation/diagnostics.ts`, `extensions/agent-browser/lib/runtime.ts`, `test/agent-browser.presentation-diagnostics.test.ts` |
|
|
80
80
|
| Stale `@eN` refs fail mysteriously | Records per-session `details.refSnapshot`, rejects mismatched URLs / unknown refs / unsafe `batch` stdin ordering before spawn, adds recovery guidance to rerun `snapshot -i` or use stable `find` locators | `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/session-page-state.ts`, `test/agent-browser.session-page-state.test.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.extension-semantic-recovery.test.ts` |
|
|
81
81
|
| Agents need stable success/failure buckets | Exposes bounded `resultCategory`, `successCategory`, and `failureCategory` on tool `details` for branching without parsing prose; a `tool_result` hook also aligns real Pi `isError` semantics, naming `Pi tool isError: true` in prose output while preserving parseable caller-requested `--json` output | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/results/categories.ts`, `extensions/agent-browser/lib/results/shared.ts` (re-export barrel), `extensions/agent-browser/index.ts`, `extensions/agent-browser/lib/pi-tool-rendering.ts`, `test/agent-browser.results.test.ts`, `test/agent-browser.extension-validation.test.ts`, `test/agent-browser.pi-pipeline.test.ts` |
|
|
82
|
-
| Clicks can report success without the page receiving the event | Top-level non-Electron direct `click` calls on
|
|
83
|
-
| Dashboard scroll commands can look successful while nothing moves | Samples viewport and prominent scroll-container positions around top-level `scroll` calls; unchanged positions produce `details.scrollNoop`, visible recovery guidance, and exact `nextActions` for snapshot/screenshot verification | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `test/agent-browser.extension-validation.test.ts` |
|
|
82
|
+
| Clicks can report success without the page receiving the event | Top-level non-Electron direct `click` calls on `xpath=` targets or role-gated current `@e…` refs (`button`, `checkbox`, `menuitem`, `radio`, `switch`, `tab`) install a bounded target-specific DOM-event probe; eligible `@e…` refs use the latest snapshot role/name metadata, and duplicate-name refs use snapshot-order `duplicateIndex` rather than requiring a unique name. If upstream reports success but no trusted event reaches the resolved target, the wrapper fails the tool, exposes `details.clickDispatch`, and suggests explicit retry/inspect next actions (no in-page replay), including a nested-scroll `scrollintoview` action when the probe sees the target outside a scroll container or viewport. Unresolved locator clicks such as raw `find … click` are left upstream-owned to avoid false failures for frame-scoped targets. Other click results still expose `details.pageChangeSummary`, and unchanged-URL clicks can surface evidence-backed `details.overlayBlockers` candidates. | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), `extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts`, `extensions/agent-browser/lib/results/presentation/navigation.ts`, `test/agent-browser.presentation.test.ts`, `test/agent-browser.extension-click-dispatch.test.ts` |
|
|
83
|
+
| Dashboard scroll commands can look successful while nothing moves | Samples viewport and prominent scroll-container positions around large top-level `scroll` calls on an existing or fresh managed session; unchanged positions produce `details.scrollNoop`, visible recovery guidance, and exact `nextActions` for snapshot/screenshot verification | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `test/agent-browser.extension-validation.test.ts` |
|
|
84
84
|
| Dropdown/combobox clicks can focus or hit native option box-model errors | Adds first-class `select <selector> <value...>` paths through raw `args`, `semanticAction`, and `job`; for custom combobox clicks, detects focused controls with explicit `aria-expanded` state but no visible options and returns `details.comboboxFocus` plus exact recovery `nextActions` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#core-page-and-element-commands), `extensions/agent-browser/lib/input-modes/semantic-action.ts`, `test/agent-browser.extension-input-modes.test.ts`, `test/agent-browser.extension-validation.test.ts` |
|
|
85
85
|
| Recording workflows fail late when `ffmpeg` is missing | After successful `record start` / `record restart`, warns when `ffmpeg` is not on `PATH` so agents can install or fix PATH before `record stop` | [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#details), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#diff-debug-and-streaming), `test/agent-browser.extension-validation.test.ts` |
|
|
86
86
|
| Direct binary help may be blocked in agent sessions | Publishes a repo-readable command reference and verifies it against the target upstream version | `npm run verify` |
|
|
87
87
|
| Desktop Electron apps need discovery, CDP attach, and safe teardown | Top-level `electron` runs host `list` / isolated `launch` (temp profile, OS-chosen debug port) / `status` / `probe` / `cleanup`, merges `launchId` plus managed `sessionName`, supports `handoff` `snapshot` / `tabs` / `connect`, and surfaces mismatch and post-command health guidance; wrapper cleanup applies only to launches it created | `extensions/agent-browser/lib/electron/discovery.ts`, `launch.ts`, `cleanup.ts`, [`docs/TOOL_CONTRACT.md`](docs/TOOL_CONTRACT.md#electron), [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#electron-desktop-apps) |
|
|
88
|
-
| Agents need bundled `skills` text and local setup/status commands without touching the live session | Treats `skills list`, `skills get …`, `skills path …`, local auth profile management (`auth save/list/show/delete/remove`), `profiles`, `dashboard`, `device list`, `doctor`, `install`, `upgrade`, `session list`, `plugin add/list/show/run`, `mcp --help`, and targeted/all saved-state maintenance (`state clear --all`, `state clear -a`, named clear, or `state clean --older-than <days>`) as sessionless reads/actions: no implicit managed `--session` under default `sessionMode: "auto"` (same session-ownership goal as plain-text `--help` / `--version`), while bare `mcp` server calls are blocked and provider/browser-backed workflows stay thin passthroughs that require upstream setup and credentials | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#built-in-skills), `extensions/agent-browser/lib/command-policy.ts`, `extensions/agent-browser/lib/runtime.ts` |
|
|
88
|
+
| Agents need bundled `skills` text and local setup/status commands without touching the live session | Treats `skills list`, `skills get …`, `skills path …`, local auth profile management (`auth save/list/show/delete/remove`), `profiles`, `dashboard`, `device list`, `doctor`, `install`, `upgrade`, `session list`, `session id`, `session info`, `plugin add/list/show/run`, `mcp --help`, and targeted/all saved-state maintenance (`state clear --all`, `state clear -a`, named clear, or `state clean --older-than <days>`) as sessionless reads/actions: no implicit managed `--session` under default `sessionMode: "auto"` (same session-ownership goal as plain-text `--help` / `--version`), while bare `mcp` server calls are blocked and provider/browser-backed workflows stay thin passthroughs that require upstream setup and credentials | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#built-in-skills), `extensions/agent-browser/lib/command-policy.ts`, `extensions/agent-browser/lib/runtime.ts` |
|
|
89
89
|
|
|
90
90
|
## Fastest way to try it
|
|
91
91
|
|
|
@@ -338,16 +338,16 @@ Typical pitfalls:
|
|
|
338
338
|
- Do not reuse `@e…` refs across navigation. The wrapper records the latest snapshot refs per session and fails mutation-prone stale/recycled refs before upstream can silently hit a different current-page element; use the session-aware `refresh-interactive-refs` next action.
|
|
339
339
|
- If upstream classifies the failure as `stale-ref` and `details.compiledSemanticAction` is present for a compiled `find` action, `details.nextActions` may list `retry-semantic-action-after-stale-ref` after `refresh-interactive-refs`, carrying the same compiled `find` argv so you can retry the locator-stable target once it is safe to do so. `select` calls that used stale `@refs` only get refresh guidance; use a fresh snapshot or stable selector before retrying (contract in [`docs/TOOL_CONTRACT.md#semanticaction`](docs/TOOL_CONTRACT.md#semanticaction)).
|
|
340
340
|
- If the failure is `selector-not-found`, the wrapper may take one fresh snapshot and add `Current snapshot ref fallback` when that snapshot has exact visible role/name matches for the failed `find` / `semanticAction` target. Non-fill targets can include direct `try-current-visible-ref*` next actions, and semantic click misses can still add bounded `Agent-browser candidate fallbacks` such as `button`/`link` role retries for `text` clicks. `semanticAction` does not expose `uncheck` while upstream `find ... uncheck` is not runtime-supported; use raw `args: ["uncheck", <selector-or-ref>]` after a stable selector or fresh snapshot ref. For semantic `fill` misses on desktop or host-controlled rich inputs, prefer `details.richInputRecovery`: refresh refs, choose the current editable `@ref`, focus or click it, then use `keyboard inserttext` or `keyboard type` with the intended text. Direct contenteditable fills are verified with `get text` when snapshot metadata proves the target is contenteditable; if replacement did not happen, `details.fillVerification` warns before any submit step. Those recovery nextActions do not copy the fill text and do not press `Enter` or submit; only submit when the user flow explicitly calls for it (same contract link).
|
|
341
|
-
- A successful upstream `click` is not proof that the web app handled the event or changed state. For top-level non-Electron direct clicks on
|
|
342
|
-
- A successful `snapshot -i` can surface `Possible overlay blockers` immediately when refs already contain strong dialog/alertdialog evidence plus close/dismiss controls. If a **top-level**
|
|
343
|
-
- If `get text <selector>` reads a non-ref CSS selector with multiple matches or a hidden first match while visible matches exist, including successful `batch` steps, the wrapper may add `Selector text visibility warning`, `details.selectorTextVisibility` (plus `selectorTextVisibilityAll` for multiple batched warnings), and `inspect-visible-text-candidates` next actions; the warning names the matching `details.nextActions` id. Prefer a visible `@ref`, a scoped selector, or a targeted `eval --stdin` over hidden tab content.
|
|
341
|
+
- A successful upstream `click` is not proof that the web app handled the event or changed state. For top-level non-Electron direct clicks on `xpath=` targets and eligible current `@e…` refs, the wrapper may fail the tool with `details.clickDispatch` and a `Click dispatch diagnostic` line when upstream reported success but no trusted DOM event reached the resolved target. Raw `find … click` locator calls are not probed because the wrapper has no concrete element before upstream resolves the locator, and document-level probes can falsely fail frame-scoped clicks. `@e…` ref click probes are limited to current snapshot refs with accessible role `button`, `checkbox`, `menuitem`, `radio`, `switch`, or `tab`, using duplicate-name snapshot order when needed. Use the suggested `inspect-click-dispatch-miss` / `retry-click-after-dispatch-miss` next actions instead of assuming the click mutated the page; when `details.clickDispatch.scrollContainer` is present, use `scroll-target-into-view-after-dispatch-miss` first. When the task depends on a mutation, follow `inspect-after-mutation` / `pageChangeSummary` evidence with a wait, URL/text check, or fresh snapshot before trusting the result; if the target still did not change, retry with a current visible ref or stable selector and report the workflow issue instead of silently continuing. For static local fixtures where the user only needs to exercise app code, an explicit `eval --stdin` programmatic click such as `document.querySelector("#demo").click()` can be a diagnostic workaround, but treat it as an untrusted scripted activation rather than proof a real user click works, and never use it to bypass user instructions. Respect explicit user stop boundaries yourself: if the user says to stop before order/post/purchase/submit, gather evidence on that page and do not click the final action. The wrapper does not parse broad prompt text into business-intent action blocks; `details.promptGuard` is reserved for concrete artifact-before-close checks.
|
|
342
|
+
- A successful `snapshot -i` can surface `Possible overlay blockers` immediately when refs already contain strong dialog/alertdialog evidence plus close/dismiss controls. If a **top-level** `@e…`/`ref=` click succeeds (unified command `click`, not a `batch` step), upstream reports `data.clicked`, and `details.navigationSummary.url` stays on the same tab URL under the same normalization as ref preflight (fragment-insensitive), the wrapper may take one extra `snapshot -i` and add `Possible overlay blockers` with `details.overlayBlockers` (`candidates`, `summary`, optional `snapshot` refresh for refs) plus session-aware `inspect-overlay-state` / bounded `try-overlay-blocker-candidate-*` next actions when that snapshot shows strong modal context (`dialog` / `alertdialog`) and close/dismiss-like controls. Page-wide words like privacy, sign in, or banner alone do not trigger this diagnostic. The unchanged-URL check compares the prior pinned tab target with `details.navigationSummary.url`; CSS selector clicks do not run this overlay probe. Also skipped when tab correction or about-blank recovery already ran on that result.
|
|
343
|
+
- If `get text <selector>` reads a non-ref, non-simple-id CSS selector with multiple matches or a hidden first match while visible matches exist, including successful `batch` steps, the wrapper may add `Selector text visibility warning`, `details.selectorTextVisibility` (plus `selectorTextVisibilityAll` for multiple batched warnings), and `inspect-visible-text-candidates` next actions; the warning names the matching `details.nextActions` id. Prefer a visible `@ref`, a scoped selector, or a targeted `eval --stdin` over hidden tab content.
|
|
344
344
|
- In wrapper-tracked attached Electron sessions, broad selectors such as `body`, `html`, `main`, or `[role=application]` may read the whole app shell. The wrapper may add `Broad Electron get text selector warning`, `details.electronGetTextScopeWarning`, and `snapshot-for-electron-text-scope`; ordinary browser pages, including `file://` fixtures, do not qualify without Electron launch provenance. Prefer `snapshot -i`, a current `@ref`, or a narrower panel selector.
|
|
345
345
|
|
|
346
346
|
### Constrained browser jobs
|
|
347
347
|
|
|
348
348
|
For short repeatable workflows, pass a top-level `job` instead of hand-writing `batch` stdin. Keep dynamic app jobs short around navigation, click, and rerender boundaries; avoid packing a whole checkout into one job. The wrapper only supports constrained steps (`open`, `click`, `fill`, `type`, `select`, `wait`, `assertText`, `assertUrl`, `waitForDownload`, `snapshot`, and `screenshot`), compiles them to existing upstream `batch` commands, and echoes the compiled commands as `details.compiledJob` for auditability. `open` steps can include `loadState` (`domcontentloaded`, `load`, or `networkidle`) to insert a readiness wait before the next step. `click` and `fill` steps can use either CSS `selector` or semantic locator fields (`locator`, `role`/`value`, optional `name`) so a job can express flows like role/name search without brittle selectors. `type` can use `selector`, `text`, optional `delayMs` for per-character pacing, and optional `press` for a final key such as `Enter`; paced type compiles to existing `focus`, `keyboard type`, `wait`, and `press` batch rows, is capped at 200 characters per delayed step, and compacts model-visible batch text while full rows remain in `details.batchSteps`. The same compile path backs top-level `qa`, so long `qa` runs surface the same timeout evidence shape. If a long `job`, `qa`, or `batch` hits the wrapper watchdog, `details.timeoutPartialProgress` may recover per-step status (`completed`, `failed`, `pending`, or `unknown`), current page title/URL, declared artifact paths that already exist on disk, and either a `retry-timeout-step` next action for the first incomplete read-only or idempotent step or `inspect-current-page-after-timeout` when the first incomplete step may be mutating and needs state inspection before a shorter follow-up flow (see [`docs/TOOL_CONTRACT.md#details`](docs/TOOL_CONTRACT.md#details)). There is no separate catalog of reusable named browser recipes above `job`, `qa`, and raw `batch`; see [`docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet`](docs/ARCHITECTURE.md#no-reusable-recipe-layer-yet) for the closed `RQ-0068` decision and when to revisit it.
|
|
349
349
|
|
|
350
|
-
**Navigation inside `job` is explicit.** A successful `click` does not prove the next page loaded; add `assertUrl` and/or `assertText` after navigation-prone clicks (forms, checkout, tabs, submit buttons) before screenshots or steps that assume the new page. `assertUrl` accepts exact URLs and `*` / `**` glob-style patterns
|
|
350
|
+
**Navigation inside `job` is explicit.** A successful `click` does not prove the next page loaded; add `assertUrl` and/or `assertText` after navigation-prone clicks (forms, checkout, tabs, submit buttons) before screenshots or steps that assume the new page. `assertUrl` accepts exact URLs and `*` / `**` glob-style patterns and now compiles directly to upstream `wait --url` for both forms.
|
|
351
351
|
|
|
352
352
|
```json
|
|
353
353
|
{
|
|
@@ -436,7 +436,7 @@ For local app debugging, `sourceLookup` can gather candidate component/file loca
|
|
|
436
436
|
|
|
437
437
|
This is an experiment, not a guarantee. React hints require a session opened with `--enable react-devtools`, and many builds do not expose useful sourcemap/source metadata; `status: "no-candidates"` is common when nothing matched, and `status: "unsupported"` only when no candidates were found **and** a compiled `react` batch step failed (if DOM or workspace search still produced candidates, you get `candidates-found` instead). For wrapper-tracked packaged Electron apps, a no-candidate result includes `details.sourceLookup.workspaceRoot`, optional `details.sourceLookup.electronContext`, limitations explaining that the scan is limited to the Pi cwd and does not unpack app bundles/`app.asar`, plus Electron snapshot/probe/tab next actions when a launch is known.
|
|
438
438
|
|
|
439
|
-
`networkSourceLookup` is the matching failed-request experiment. It runs `network request <id>` when `requestId` is present and/or `network requests --filter …` when `filter` or `url` is present (`url` supplies the filter pattern when `filter` is omitted); add `session` when the generated batch should target an explicit upstream session. It merges failed-request rows from the batch JSON with initiator-style hints and a bounded workspace literal scan (`maxWorkspaceFiles` defaults to 2000, cap 5000), surfaces everything under `details.networkSourceLookup`, and avoids automatic blame or edits. Compact `network requests` results with safe request IDs also add `details.nextActions` for request details, bounded `networkSourceLookup` on actionable failures, path filtering, diagnostic-buffer clearing before a repro, or HAR capture so agents can branch without guessing request-id syntax. For noisy aggregate buffers, wrapper-side `network requests --current-page` / `--current-origin` keeps only rows matching the active page origin, while `--current-url` keeps exact active-document URL rows and reports counts in `details.networkRequestsPageFilter`. When the wrapper has seen `network route` in the same session, pending fetch/XHR rows or CORS-looking errors that match the route surface `details.networkRouteDiagnostics` plus executable follow-ups to inspect the request or start HAR capture; same-origin/CORS-correct fixture retry guidance stays in prose. Network diagnostics are read-only for wrapper page state: request URLs in `network request` or generated `networkSourceLookup` batches do not replace the session’s active page target or invalidate page-scoped refs from the app page.
|
|
439
|
+
`networkSourceLookup` is the matching failed-request experiment. It runs `network request <id>` when `requestId` is present and/or `network requests --filter …` when `filter` or `url` is present (`url` supplies the filter pattern when `filter` is omitted); add `namespace` / `session` when the generated batch should target an explicit upstream namespace/session. It merges failed-request rows from the batch JSON with initiator-style hints and a bounded workspace literal scan (`maxWorkspaceFiles` defaults to 2000, cap 5000), surfaces everything under `details.networkSourceLookup`, and avoids automatic blame or edits. Compact `network requests` results with safe request IDs also add `details.nextActions` for request details, bounded `networkSourceLookup` on actionable failures, path filtering, diagnostic-buffer clearing before a repro, or HAR capture so agents can branch without guessing request-id syntax. For noisy aggregate buffers, wrapper-side `network requests --current-page` / `--current-origin` keeps only rows matching the active page origin, while `--current-url` keeps exact active-document URL rows and reports counts in `details.networkRequestsPageFilter`. When the wrapper has seen `network route` in the same session, pending fetch/XHR rows or CORS-looking errors that match the route surface `details.networkRouteDiagnostics` plus executable follow-ups to inspect the request or start HAR capture; same-origin/CORS-correct fixture retry guidance stays in prose. Network diagnostics are read-only for wrapper page state: request URLs in `network request` or generated `networkSourceLookup` batches do not replace the session’s active page target or invalidate page-scoped refs from the app page.
|
|
440
440
|
|
|
441
441
|
```json
|
|
442
442
|
{ "networkSourceLookup": { "requestId": "req-1", "url": "/api/fail" } }
|
|
@@ -481,9 +481,9 @@ Use these rules:
|
|
|
481
481
|
- Use public/temp profiles for tests and examples.
|
|
482
482
|
- Do not assume `--profile Default` is correct. Ask the agent to run `profiles` to list Chrome profile directory names, then `doctor` if profile/user-data-dir resolution still fails.
|
|
483
483
|
- For non-Chrome Chromium browsers such as Brave, Edge, Arc, or Vivaldi, use `--executable-path <path>` when upstream can launch that executable. If you need that browser's existing login state, use the browser's real profile/user-data directory path when upstream accepts it, or attach with `--auto-connect` / `connect` to a debug-enabled running browser when appropriate.
|
|
484
|
-
- Use `sessionMode: "fresh"` when switching from public browsing to `--profile`, `--executable-path`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, `--enable`, `-p` / `--provider`, or iOS `--device`.
|
|
484
|
+
- Use `sessionMode: "fresh"` when switching from public browsing to `--profile`, `--executable-path`, `--restore`, `--restore-save`, restore check flags, `--namespace`, `--session-name`, `--cdp`, `--state`, `--auto-connect`, `--init-script`, `--enable`, `-p` / `--provider`, or iOS `--device`.
|
|
485
485
|
- Use `--session` when you want to manage a live upstream session name yourself.
|
|
486
|
-
- Do not treat `--session` as persisted auth or tab restore after `close`, `quit`, or `exit`; use `--
|
|
486
|
+
- Do not treat `--session` alone as persisted auth or tab restore after `close`, `quit`, or `exit`; use `--session <id> --restore`, `--profile`, or `--state` for persistence.
|
|
487
487
|
- Prefer page actions and storage checks over cookie dumps. `cookies get` can expose real profile cookies.
|
|
488
488
|
- Prefer `auth save --password-stdin` over putting passwords in `args`; the wrapper only accepts caller `stdin` for `batch`, `eval --stdin`, and `auth save --password-stdin` (top-level `job` and `qa` compile to `batch` and supply their own stdin).
|
|
489
489
|
- Use `state save <path>` / `state load <path>` for portable test state. `state save` is reported as a file artifact with verification metadata; if an upstream-successful artifact command reports a non-pending file path that the wrapper cannot find on disk, the tool fails with `failureCategory: "artifact-missing"` instead of treating the path as durable. `state load` may mention a path but is not treated as a newly saved artifact.
|
|
@@ -703,7 +703,7 @@ These calls return plain text and stay stateless: the extension does not inject
|
|
|
703
703
|
|
|
704
704
|
<!-- agent-browser-playbook:start wrapper-tab-recovery -->
|
|
705
705
|
<!-- Generated from extensions/agent-browser/lib/playbook.ts. Run `npm run docs -- playbook write` to update. -->
|
|
706
|
-
- After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.
|
|
706
|
+
- After launch-scoped open/goto/navigate calls that can restore existing tabs (for example --profile, --restore, --session-name, or --state), agent_browser best-effort re-selects the tab whose URL matches the returned page when restored tabs steal focus during launch.
|
|
707
707
|
- After the wrapper observes tab-drift risk for a session (for example profile restore correction, overlapping stale opens, or resumed session state), later active-tab commands best-effort pin that tab inside the same upstream invocation. Routine same-session commands are not preflighted with tab list just because a target tab is known.
|
|
708
708
|
- For sessions with observed tab-drift risk, after a successful command on a known target tab, agent_browser also best-effort restores that intended tab if a restored/background tab steals focus after the command completes. Routine same-session commands skip this post-command tab-list probe.
|
|
709
709
|
- If a known session target unexpectedly reports about:blank, agent_browser best-effort re-selects the prior intended target when it still exists; if recovery fails, it records the observed about:blank target and reports exact recovery guidance instead of treating the prior page as active.
|
|
@@ -10,14 +10,14 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { Text } from "@earendil-works/pi-tui";
|
|
11
11
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
12
12
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
13
|
-
import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, extractExplicitSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
|
|
13
|
+
import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, extractExplicitNamespace, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, extractExplicitSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
|
|
14
14
|
import { isRecord } from "./lib/parsing.js";
|
|
15
15
|
import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
|
|
16
16
|
import { isCloseCommand } from "./lib/command-taxonomy.js";
|
|
17
17
|
import { cleanupSecureTempArtifacts, } from "./lib/temp.js";
|
|
18
18
|
import { AGENT_BROWSER_PARAMS, } from "./lib/input-modes.js";
|
|
19
19
|
import { parseAllowedDomainsPolicyFromArgs } from "./lib/navigation-policy.js";
|
|
20
|
-
import { closeManagedSession, runAgentBrowserTool } from "./lib/orchestration/browser-run.js";
|
|
20
|
+
import { closeManagedSession, getSessionContextKey, runAgentBrowserTool } from "./lib/orchestration/browser-run.js";
|
|
21
21
|
import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
|
|
22
22
|
import { parseBatchStdinJsonArray } from "./lib/orchestration/batch-stdin.js";
|
|
23
23
|
import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
|
|
@@ -95,6 +95,8 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
95
95
|
const args = getToolResultArgs(details);
|
|
96
96
|
const command = typeof details.command === "string" ? details.command : extractCommandTokens(args)[0];
|
|
97
97
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
98
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
99
|
+
const sessionKey = getSessionContextKey(sessionName, namespace);
|
|
98
100
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
99
101
|
const outcome = getManagedSessionOutcome(details);
|
|
100
102
|
const outcomeSucceeded = outcome?.succeeded === true;
|
|
@@ -104,17 +106,18 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
104
106
|
if (outcomeSucceeded && outcomeStatus === "closed") {
|
|
105
107
|
const closedSessionName = outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName;
|
|
106
108
|
if (closedSessionName)
|
|
107
|
-
restoredPolicies.delete(closedSessionName);
|
|
109
|
+
restoredPolicies.delete(getSessionContextKey(closedSessionName, namespace) ?? closedSessionName);
|
|
108
110
|
}
|
|
109
111
|
if (outcomeSucceeded && outcomeStatus === "replaced") {
|
|
110
112
|
const replacedSessionName = typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined;
|
|
113
|
+
const replacedSessionNamespace = typeof outcome.replacedSessionNamespace === "string" ? outcome.replacedSessionNamespace : namespace;
|
|
111
114
|
if (replacedSessionName)
|
|
112
|
-
restoredPolicies.delete(replacedSessionName);
|
|
115
|
+
restoredPolicies.delete(getSessionContextKey(replacedSessionName, replacedSessionNamespace) ?? replacedSessionName);
|
|
113
116
|
}
|
|
114
117
|
if (succeeded && isCloseCommand(command)) {
|
|
115
118
|
const closedSessionName = explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName;
|
|
116
119
|
if (closedSessionName)
|
|
117
|
-
restoredPolicies.delete(closedSessionName);
|
|
120
|
+
restoredPolicies.delete(getSessionContextKey(closedSessionName, namespace) ?? closedSessionName);
|
|
118
121
|
}
|
|
119
122
|
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
120
123
|
const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
|
|
@@ -126,22 +129,27 @@ function restoreAllowedDomainsBySessionFromBranch(branch) {
|
|
|
126
129
|
const outcomeKeepsSessionCurrent = outcome?.activeAfter === true
|
|
127
130
|
&& (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")
|
|
128
131
|
&& outcomeCurrentSessionName === sessionName;
|
|
129
|
-
const policy = (succeeded || outcomeKeepsSessionCurrent) &&
|
|
130
|
-
if (policy &&
|
|
131
|
-
restoredPolicies.set(
|
|
132
|
+
const policy = (succeeded || outcomeKeepsSessionCurrent) && sessionKey && !isCloseCommand(command) ? parseAllowedDomainsPolicyFromArgs(args) : undefined;
|
|
133
|
+
if (policy && sessionKey)
|
|
134
|
+
restoredPolicies.set(sessionKey, policy);
|
|
132
135
|
}
|
|
133
136
|
return restoredPolicies;
|
|
134
137
|
}
|
|
135
138
|
function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
|
|
136
139
|
if (!sessionName)
|
|
137
140
|
return;
|
|
138
|
-
const
|
|
141
|
+
const key = getSessionContextKey(sessionName, options.namespace) ?? sessionName;
|
|
142
|
+
const existing = sessions.get(key);
|
|
139
143
|
const branchOwned = existing && !existing.branchOwned ? false : options.branchOwned === true;
|
|
140
|
-
sessions.set(
|
|
144
|
+
sessions.set(key, { branchOwned, cwd, namespace: options.namespace, sessionName });
|
|
141
145
|
}
|
|
142
|
-
function untrackOwnedManagedSession(sessions, sessionName) {
|
|
143
|
-
if (sessionName)
|
|
146
|
+
function untrackOwnedManagedSession(sessions, sessionName, namespace) {
|
|
147
|
+
if (!sessionName)
|
|
148
|
+
return;
|
|
149
|
+
if (sessionName.includes("\u0000"))
|
|
144
150
|
sessions.delete(sessionName);
|
|
151
|
+
else
|
|
152
|
+
sessions.delete(getSessionContextKey(sessionName, namespace) ?? sessionName);
|
|
145
153
|
}
|
|
146
154
|
function untrackOwnedManagedSessionFromBranchClose(sessions, sessionName, activeBranchRank, closeBranchRank) {
|
|
147
155
|
if (!sessionName || closeBranchRank === undefined)
|
|
@@ -163,7 +171,8 @@ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
|
|
|
163
171
|
const currentSessionName = typeof outcome.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
164
172
|
const attemptedSessionName = typeof outcome.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
|
|
165
173
|
if (outcome.activeAfter === true && (status === "created" || status === "replaced" || status === "unchanged")) {
|
|
166
|
-
|
|
174
|
+
const namespace = isRecord(details) && typeof details.namespace === "string" ? details.namespace : undefined;
|
|
175
|
+
trackOwnedManagedSession(sessions, currentSessionName, cwd, { namespace });
|
|
167
176
|
}
|
|
168
177
|
if (succeeded && status === "closed") {
|
|
169
178
|
untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName);
|
|
@@ -320,6 +329,7 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
320
329
|
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
321
330
|
const command = typeof details.command === "string" ? details.command : extractCommandTokens(args)[0];
|
|
322
331
|
const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
|
|
332
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
323
333
|
const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
|
|
324
334
|
const usedImplicitSession = details.usedImplicitSession === true;
|
|
325
335
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
@@ -329,19 +339,20 @@ function collectBranchManagedResourceEvents(branch) {
|
|
|
329
339
|
const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
|
|
330
340
|
const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
|
|
331
341
|
if (outcomeSucceeded && outcome.activeAfter === true && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")) {
|
|
332
|
-
setBranchRankForString(events.managedSessionActiveRanks, outcomeCurrentSessionName, eventRank);
|
|
342
|
+
setBranchRankForString(events.managedSessionActiveRanks, getSessionContextKey(outcomeCurrentSessionName, namespace), eventRank);
|
|
333
343
|
}
|
|
334
344
|
if (outcomeSucceeded && outcomeStatus === "closed") {
|
|
335
|
-
setBranchRankForString(events.managedSessionCloseRanks, outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, eventRank);
|
|
345
|
+
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, namespace), eventRank);
|
|
336
346
|
}
|
|
337
347
|
if (outcomeSucceeded && outcomeStatus === "replaced") {
|
|
338
|
-
|
|
348
|
+
const replacedSessionNamespace = typeof outcome.replacedSessionNamespace === "string" ? outcome.replacedSessionNamespace : namespace;
|
|
349
|
+
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined, replacedSessionNamespace), eventRank);
|
|
339
350
|
}
|
|
340
351
|
if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh")) {
|
|
341
|
-
events.managedSessionActiveRanks.set(sessionName, eventRank);
|
|
352
|
+
events.managedSessionActiveRanks.set(getSessionContextKey(sessionName, namespace) ?? sessionName, eventRank);
|
|
342
353
|
}
|
|
343
354
|
if (succeeded && isCloseCommand(command)) {
|
|
344
|
-
setBranchRankForString(events.managedSessionCloseRanks, explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, eventRank);
|
|
355
|
+
setBranchRankForString(events.managedSessionCloseRanks, getSessionContextKey(explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, namespace), eventRank);
|
|
345
356
|
}
|
|
346
357
|
const electron = isRecord(details.electron) ? details.electron : undefined;
|
|
347
358
|
const launch = electron && isElectronLaunchRecord(electron.launch) ? electron.launch : undefined;
|
|
@@ -384,13 +395,14 @@ function syncElectronCleanupManagedSessions(sessions, cleanupResults) {
|
|
|
384
395
|
untrackOwnedManagedSession(sessions, sessionName);
|
|
385
396
|
}
|
|
386
397
|
}
|
|
387
|
-
async function closeOwnedManagedSessionsExcept(sessions, keepSessionName, timeoutMs) {
|
|
388
|
-
|
|
389
|
-
|
|
398
|
+
async function closeOwnedManagedSessionsExcept(sessions, keepSessionName, timeoutMs, keepNamespace) {
|
|
399
|
+
const keepKey = getSessionContextKey(keepSessionName, keepNamespace);
|
|
400
|
+
for (const [key, owner] of [...sessions]) {
|
|
401
|
+
if (key === keepKey)
|
|
390
402
|
continue;
|
|
391
|
-
const error = await closeManagedSession({ cwd: owner.cwd, sessionName, timeoutMs });
|
|
403
|
+
const error = await closeManagedSession({ cwd: owner.cwd, namespace: owner.namespace, sessionName: owner.sessionName, timeoutMs });
|
|
392
404
|
if (!error)
|
|
393
|
-
sessions.delete(
|
|
405
|
+
sessions.delete(key);
|
|
394
406
|
}
|
|
395
407
|
}
|
|
396
408
|
async function closeOwnedManagedSessions(sessions, timeoutMs) {
|
|
@@ -410,7 +422,7 @@ function shouldSerializeBrowserCommand(options) {
|
|
|
410
422
|
return true;
|
|
411
423
|
if (options.explicitSessionName === options.managedSessionName)
|
|
412
424
|
return true;
|
|
413
|
-
if (options.ownedManagedSessions.has(options.explicitSessionName))
|
|
425
|
+
if (options.ownedManagedSessions.has(getSessionContextKey(options.explicitSessionName, options.explicitNamespace) ?? options.explicitSessionName))
|
|
414
426
|
return true;
|
|
415
427
|
return getActiveElectronRecords(options.ownedElectronLaunchRecords).some((record) => record.sessionName === options.explicitSessionName);
|
|
416
428
|
}
|
|
@@ -470,6 +482,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
470
482
|
let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
|
|
471
483
|
let managedSessionName = managedSessionBaseName;
|
|
472
484
|
let managedSessionCwd = process.cwd();
|
|
485
|
+
let managedSessionNamespace;
|
|
473
486
|
let freshSessionOrdinal = 0;
|
|
474
487
|
let sessionPageState = new SessionPageState();
|
|
475
488
|
let traceOwners = new Map();
|
|
@@ -483,10 +496,11 @@ export default function agentBrowserExtension(pi) {
|
|
|
483
496
|
const ownedManagedSessions = new Map();
|
|
484
497
|
const managedSessionExecutionQueue = new AsyncExecutionQueue();
|
|
485
498
|
let branchStateGeneration = 0;
|
|
486
|
-
const clearSessionScopedBrowserState = (sessionName) => {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
499
|
+
const clearSessionScopedBrowserState = (sessionName, namespace) => {
|
|
500
|
+
const key = getSessionContextKey(sessionName, namespace) ?? sessionName;
|
|
501
|
+
allowedDomainsBySession.delete(key);
|
|
502
|
+
networkRoutesBySession.delete(key);
|
|
503
|
+
sessionPageState.clearSession(key);
|
|
490
504
|
};
|
|
491
505
|
const restoreBranchBackedState = (ctx, options) => {
|
|
492
506
|
branchStateGeneration += 1;
|
|
@@ -516,6 +530,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
516
530
|
? previousManagedSessionName
|
|
517
531
|
: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, nextFreshSessionOrdinal)
|
|
518
532
|
: restoredState.sessionName;
|
|
533
|
+
managedSessionNamespace = shouldReservePostCloseSession ? undefined : restoredState.namespace;
|
|
519
534
|
managedSessionCwd = ctx.cwd;
|
|
520
535
|
freshSessionOrdinal = nextFreshSessionOrdinal;
|
|
521
536
|
sessionPageState = SessionPageState.fromBranch(branch);
|
|
@@ -536,7 +551,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
536
551
|
removeInactiveOwnedElectronLaunchRecords(ownedElectronLaunchRecords, branchOwnedElectronLaunchIds, electronLaunchRecords, branchResourceEvents.electronLaunchActiveRanks, branchResourceEvents.electronLaunchCleanupRanks);
|
|
537
552
|
}
|
|
538
553
|
if (restoredState.active) {
|
|
539
|
-
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, { branchOwned: true });
|
|
554
|
+
trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, { branchOwned: true, namespace: restoredState.namespace });
|
|
540
555
|
}
|
|
541
556
|
mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
|
|
542
557
|
branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
|
|
@@ -595,10 +610,11 @@ export default function agentBrowserExtension(pi) {
|
|
|
595
610
|
await closeOwnedManagedSessions(ownedManagedSessions, implicitSessionCloseTimeoutMs);
|
|
596
611
|
}
|
|
597
612
|
else {
|
|
598
|
-
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs);
|
|
613
|
+
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, managedSessionActive ? managedSessionNamespace : undefined);
|
|
599
614
|
}
|
|
600
615
|
});
|
|
601
616
|
managedSessionActive = false;
|
|
617
|
+
managedSessionNamespace = undefined;
|
|
602
618
|
sessionPageState.reset();
|
|
603
619
|
traceOwners = new Map();
|
|
604
620
|
artifactManifest = undefined;
|
|
@@ -721,6 +737,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
721
737
|
clearSessionScopedBrowserState(closedSessionName);
|
|
722
738
|
if (closedSessionName === managedSessionName) {
|
|
723
739
|
managedSessionActive = false;
|
|
740
|
+
managedSessionNamespace = undefined;
|
|
724
741
|
freshSessionOrdinal += 1;
|
|
725
742
|
managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
|
|
726
743
|
}
|
|
@@ -735,7 +752,9 @@ export default function agentBrowserExtension(pi) {
|
|
|
735
752
|
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: electronHostResult });
|
|
736
753
|
}
|
|
737
754
|
const explicitSessionName = extractExplicitSessionName(toolArgs);
|
|
755
|
+
const explicitNamespace = extractExplicitNamespace(toolArgs);
|
|
738
756
|
const serializeBrowserCommand = shouldSerializeBrowserCommand({
|
|
757
|
+
explicitNamespace,
|
|
739
758
|
explicitSessionName,
|
|
740
759
|
managedSessionName,
|
|
741
760
|
ownedElectronLaunchRecords,
|
|
@@ -756,6 +775,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
756
775
|
managedSessionBaseName,
|
|
757
776
|
managedSessionCwd,
|
|
758
777
|
managedSessionName,
|
|
778
|
+
managedSessionNamespace,
|
|
759
779
|
networkRoutesBySession,
|
|
760
780
|
sessionPageState,
|
|
761
781
|
traceOwners,
|
|
@@ -784,6 +804,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
784
804
|
managedSessionActive = browserRunState.managedSessionActive;
|
|
785
805
|
managedSessionCwd = browserRunState.managedSessionCwd;
|
|
786
806
|
managedSessionName = browserRunState.managedSessionName;
|
|
807
|
+
managedSessionNamespace = browserRunState.managedSessionNamespace;
|
|
787
808
|
for (const closedSessionName of browserRunState.closedManagedSessionNames) {
|
|
788
809
|
untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
|
|
789
810
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Responsibilities: Own command-token extraction, command/subcommand identification, and descriptor construction.
|
|
4
4
|
* Scope: Pure argv parsing; runtime planning and session policy consume descriptors instead of re-parsing tokens.
|
|
5
5
|
*/
|
|
6
|
-
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "./argv-grammar.js";
|
|
6
|
+
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, optionalGlobalValueFlagConsumesNext } from "./argv-grammar.js";
|
|
7
7
|
import { isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
8
8
|
function isBooleanLiteral(token) {
|
|
9
9
|
const normalized = token?.trim().toLowerCase();
|
|
@@ -12,12 +12,15 @@ function isBooleanLiteral(token) {
|
|
|
12
12
|
export function findCommandStartIndex(args) {
|
|
13
13
|
for (let index = 0; index < args.length; index += 1) {
|
|
14
14
|
const token = args[index];
|
|
15
|
-
if (token.startsWith("--session=")) {
|
|
15
|
+
if (token.startsWith("--session=") || token.startsWith("--namespace=") || token.startsWith("--restore=")) {
|
|
16
16
|
continue;
|
|
17
17
|
}
|
|
18
18
|
if (token.startsWith("-")) {
|
|
19
19
|
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
20
|
-
if (
|
|
20
|
+
if (optionalGlobalValueFlagConsumesNext(normalizedToken, args[index + 1])) {
|
|
21
|
+
index += 1;
|
|
22
|
+
}
|
|
23
|
+
else if (VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
|
|
21
24
|
index += 1;
|
|
22
25
|
}
|
|
23
26
|
else if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(normalizedToken) &&
|
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
* Responsibilities: Own global/command value-flag sets and boolean/value-flag validation used during argv parsing.
|
|
4
4
|
* Scope: Pure token grammar; command semantics and subprocess execution live elsewhere.
|
|
5
5
|
*/
|
|
6
|
+
import { isKnownCommandToken } from "./command-taxonomy.js";
|
|
6
7
|
export const GLOBAL_VALUE_FLAGS = [
|
|
7
8
|
"--session",
|
|
9
|
+
"--namespace",
|
|
8
10
|
"--cdp",
|
|
9
11
|
"--config",
|
|
10
12
|
"--profile",
|
|
11
13
|
"--session-name",
|
|
14
|
+
"--restore-save",
|
|
15
|
+
"--restore-check-url",
|
|
16
|
+
"--restore-check-text",
|
|
17
|
+
"--restore-check-fn",
|
|
12
18
|
"--proxy",
|
|
13
19
|
"--proxy-bypass",
|
|
14
20
|
"--headers",
|
|
@@ -52,11 +58,13 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
52
58
|
"--name",
|
|
53
59
|
"--older-than",
|
|
54
60
|
"--output",
|
|
61
|
+
"--prefix",
|
|
55
62
|
"--path",
|
|
56
63
|
"--port",
|
|
57
64
|
"--resource-type",
|
|
58
65
|
"--resource-types",
|
|
59
66
|
"--sameSite",
|
|
67
|
+
"--scope",
|
|
60
68
|
"--selector",
|
|
61
69
|
"-s",
|
|
62
70
|
"--status",
|
|
@@ -69,6 +77,7 @@ export const COMMAND_VALUE_FLAGS = [
|
|
|
69
77
|
"--password",
|
|
70
78
|
"--wait-until",
|
|
71
79
|
];
|
|
80
|
+
export const OPTIONAL_GLOBAL_VALUE_FLAGS = new Set(["--restore"]);
|
|
72
81
|
export const VALUE_FLAGS = new Set([...GLOBAL_VALUE_FLAGS, ...COMMAND_VALUE_FLAGS]);
|
|
73
82
|
export const PREVALIDATED_VALUE_FLAGS = new Set(GLOBAL_VALUE_FLAGS);
|
|
74
83
|
export const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE = new Set(["--args"]);
|
|
@@ -116,6 +125,25 @@ export function hasOnlyOptionFlags(tokens, allowedBooleanFlags, allowedValueFlag
|
|
|
116
125
|
}
|
|
117
126
|
return true;
|
|
118
127
|
}
|
|
128
|
+
export function optionalGlobalValueFlagConsumesNext(flag, nextToken) {
|
|
129
|
+
if (!OPTIONAL_GLOBAL_VALUE_FLAGS.has(flag) || nextToken === undefined || nextToken.startsWith("-"))
|
|
130
|
+
return false;
|
|
131
|
+
return !isKnownCommandToken(nextToken);
|
|
132
|
+
}
|
|
119
133
|
export function stripSessionlessShapeGlobalFlags(commandTokens) {
|
|
120
|
-
|
|
134
|
+
const stripped = [];
|
|
135
|
+
for (let index = 0; index < commandTokens.length; index += 1) {
|
|
136
|
+
const token = commandTokens[index];
|
|
137
|
+
const flagName = getFlagName(token);
|
|
138
|
+
if (token === "--json")
|
|
139
|
+
continue;
|
|
140
|
+
if ((flagName === "--session" || flagName === "--namespace") && !token.includes("=")) {
|
|
141
|
+
index += 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (token.startsWith("--session=") || token.startsWith("--namespace="))
|
|
145
|
+
continue;
|
|
146
|
+
stripped.push(token);
|
|
147
|
+
}
|
|
148
|
+
return stripped;
|
|
121
149
|
}
|
|
@@ -16,6 +16,7 @@ const DOCTOR_BOOLEAN_FLAGS = new Set(["--fix", "--json", "--offline", "--quick"]
|
|
|
16
16
|
const INSTALL_BOOLEAN_FLAGS = new Set(["--with-deps", "-d"]);
|
|
17
17
|
const STATE_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "clear", "clean", "rename"]);
|
|
18
18
|
const STATE_CLEAN_VALUE_FLAGS = new Set(["--older-than"]);
|
|
19
|
+
const SESSION_ID_VALUE_FLAGS = new Set(["--scope", "--prefix"]);
|
|
19
20
|
function isSessionlessAuthCommand(commandTokens) {
|
|
20
21
|
const [, subcommand, target, ...rest] = commandTokens;
|
|
21
22
|
if (!SESSIONLESS_AUTH_SUBCOMMANDS.has(subcommand ?? ""))
|
|
@@ -64,6 +65,14 @@ function isSessionlessPluginCommand(commandTokens) {
|
|
|
64
65
|
return true;
|
|
65
66
|
return PLUGIN_SESSIONLESS_SUBCOMMANDS.has(subcommand);
|
|
66
67
|
}
|
|
68
|
+
function isSessionlessSessionCommand(commandTokens) {
|
|
69
|
+
const [, subcommand, ...rest] = commandTokens;
|
|
70
|
+
if (subcommand === "list" || subcommand === "info")
|
|
71
|
+
return rest.length === 0;
|
|
72
|
+
if (subcommand === "id")
|
|
73
|
+
return hasOnlyOptionFlags(rest, JSON_BOOLEAN_FLAGS, SESSION_ID_VALUE_FLAGS);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
67
76
|
function isSessionlessCommand(commandTokens) {
|
|
68
77
|
const normalizedTokens = stripSessionlessShapeGlobalFlags(commandTokens);
|
|
69
78
|
const [command, subcommand] = normalizedTokens;
|
|
@@ -86,7 +95,7 @@ function isSessionlessCommand(commandTokens) {
|
|
|
86
95
|
if (command === "profiles" || command === "upgrade")
|
|
87
96
|
return normalizedTokens.length === 1;
|
|
88
97
|
if (command === "session")
|
|
89
|
-
return normalizedTokens
|
|
98
|
+
return isSessionlessSessionCommand(normalizedTokens);
|
|
90
99
|
if (command === "state")
|
|
91
100
|
return isSessionlessStateCommand(normalizedTokens);
|
|
92
101
|
return false;
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
* session/ref guards, result recommendations, and presentation summaries without coupling unrelated behaviors.
|
|
5
5
|
* Scope: Static command capability taxonomy only; command-shape parsing, spawning, and formatting live elsewhere.
|
|
6
6
|
*/
|
|
7
|
+
const ADDITIONAL_COMMAND_TOKENS = [
|
|
8
|
+
"auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "read", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
|
|
9
|
+
];
|
|
7
10
|
const COMMAND_CAPABILITIES = [
|
|
8
11
|
{
|
|
9
12
|
command: "back",
|
|
@@ -258,6 +261,10 @@ for (const entry of COMMAND_CAPABILITIES) {
|
|
|
258
261
|
COMMAND_CAPABILITY_BY_NAME.set(alias, entry);
|
|
259
262
|
}
|
|
260
263
|
}
|
|
264
|
+
const KNOWN_COMMAND_TOKENS = new Set([...COMMAND_CAPABILITY_BY_NAME.keys(), ...ADDITIONAL_COMMAND_TOKENS]);
|
|
265
|
+
export function isKnownCommandToken(token) {
|
|
266
|
+
return KNOWN_COMMAND_TOKENS.has(token);
|
|
267
|
+
}
|
|
261
268
|
function getCommandCapability(command) {
|
|
262
269
|
return command === undefined ? undefined : COMMAND_CAPABILITY_BY_NAME.get(command);
|
|
263
270
|
}
|
|
@@ -68,28 +68,6 @@ const JOB_STEP_ALLOWED_FIELDS = {
|
|
|
68
68
|
wait: new Set(["action", "milliseconds"]),
|
|
69
69
|
waitForDownload: new Set(["action", "path"]),
|
|
70
70
|
};
|
|
71
|
-
function globUrlPatternToRegexSource(pattern) {
|
|
72
|
-
let source = "^";
|
|
73
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
74
|
-
const char = pattern.charAt(index);
|
|
75
|
-
if (char === "*") {
|
|
76
|
-
let runLength = 1;
|
|
77
|
-
while (pattern[index + runLength] === "*")
|
|
78
|
-
runLength += 1;
|
|
79
|
-
source += runLength === 1 ? "[^/]*" : ".*";
|
|
80
|
-
index += runLength - 1;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
source += char.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return `${source}$`;
|
|
87
|
-
}
|
|
88
|
-
function compileJobAssertUrlArgs(url) {
|
|
89
|
-
if (!url.includes("*"))
|
|
90
|
-
return ["wait", "--url", url];
|
|
91
|
-
return ["wait", "--fn", `new RegExp(${JSON.stringify(globUrlPatternToRegexSource(url))}).test(location.href)`];
|
|
92
|
-
}
|
|
93
71
|
function compileJobTypeSteps(step) {
|
|
94
72
|
const text = getRequiredJobString(step, "text", "type");
|
|
95
73
|
if (text.error)
|
|
@@ -182,7 +160,7 @@ function compileAssertUrlJobStep(step) {
|
|
|
182
160
|
const result = getRequiredJobString(step, "url", "assertUrl");
|
|
183
161
|
if (result.error)
|
|
184
162
|
return { error: result.error };
|
|
185
|
-
return { args:
|
|
163
|
+
return { args: ["wait", "--url", result.value] };
|
|
186
164
|
}
|
|
187
165
|
function compilePathArtifactJobStep(step, action) {
|
|
188
166
|
const result = getRequiredJobString(step, "path", action);
|
|
@@ -259,12 +259,15 @@ export function compileAgentBrowserNetworkSourceLookup(input) {
|
|
|
259
259
|
return { error: "networkSourceLookup must be an object." };
|
|
260
260
|
const filter = input.filter;
|
|
261
261
|
const requestId = input.requestId;
|
|
262
|
+
const namespace = input.namespace;
|
|
262
263
|
const session = input.session;
|
|
263
264
|
const url = input.url;
|
|
264
265
|
if (filter !== undefined && (typeof filter !== "string" || filter.trim().length === 0))
|
|
265
266
|
return { error: "networkSourceLookup.filter must be a non-empty string when provided." };
|
|
266
267
|
if (requestId !== undefined && (typeof requestId !== "string" || requestId.trim().length === 0))
|
|
267
268
|
return { error: "networkSourceLookup.requestId must be a non-empty string when provided." };
|
|
269
|
+
if (namespace !== undefined && (typeof namespace !== "string" || namespace.trim().length === 0))
|
|
270
|
+
return { error: "networkSourceLookup.namespace must be a non-empty string when provided." };
|
|
268
271
|
if (session !== undefined && (typeof session !== "string" || session.trim().length === 0))
|
|
269
272
|
return { error: "networkSourceLookup.session must be a non-empty string when provided." };
|
|
270
273
|
if (url !== undefined && (typeof url !== "string" || url.trim().length === 0))
|
|
@@ -282,8 +285,8 @@ export function compileAgentBrowserNetworkSourceLookup(input) {
|
|
|
282
285
|
if (effectiveFilter) {
|
|
283
286
|
steps.push({ action: "network", args: ["network", "requests", "--filter", effectiveFilter] });
|
|
284
287
|
}
|
|
285
|
-
const args = typeof
|
|
286
|
-
return { compiled: { args, query: { filter, maxWorkspaceFiles: maxWorkspaceFiles.value, requestId, session, url }, stdin: JSON.stringify(steps.map((step) => step.args)), steps } };
|
|
288
|
+
const args = [...(typeof namespace === "string" ? ["--namespace", namespace] : []), ...(typeof session === "string" ? ["--session", session] : []), "batch"];
|
|
289
|
+
return { compiled: { args, query: { filter, maxWorkspaceFiles: maxWorkspaceFiles.value, namespace, requestId, session, url }, stdin: JSON.stringify(steps.map((step) => step.args)), steps } };
|
|
287
290
|
}
|
|
288
291
|
function getResultPayload(item) {
|
|
289
292
|
return isRecord(item.result) && "data" in item.result ? item.result.data : item.result;
|