pi-agent-browser-native 0.6.12 → 0.6.14
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 +27 -0
- package/README.md +24 -20
- package/dist/extensions/agent-browser/index.js +24 -10
- package/dist/extensions/agent-browser/lib/argv-grammar.js +1 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +4 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +32 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +14 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +15 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +3 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +20 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +14 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +5 -2
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +4 -2
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +100 -9
- package/dist/extensions/agent-browser/lib/playbook.js +5 -4
- package/dist/extensions/agent-browser/lib/process.js +18 -5
- package/dist/extensions/agent-browser/lib/recording-reservations.js +8 -1
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +15 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +18 -9
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +3 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation.js +7 -3
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +14 -2
- package/dist/extensions/agent-browser/lib/results/snapshot.js +12 -5
- package/dist/extensions/agent-browser/lib/runtime.js +3 -1
- package/dist/extensions/agent-browser/lib/session-page-state.js +7 -3
- package/dist/extensions/agent-browser/lib/webmcp-observation.js +28 -0
- package/dist/scripts/agent-browser-target.mjs +1 -1
- package/docs/ARCHITECTURE.md +16 -5
- package/docs/COMMAND_REFERENCE.md +71 -15
- package/docs/ELECTRON.md +1 -1
- package/docs/RELEASE.md +7 -6
- package/docs/SUPPORT_MATRIX.md +15 -9
- package/docs/TOOL_CONTRACT.md +19 -5
- package/docs/platform-smoke.md +3 -3
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +34 -3
- package/scripts/agent-browser-target.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.6.14 - 2026-09-17
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Recommend `agent-browser` 0.38.1 while retaining the stable 0.35.0 minimum and separate upstream installation.
|
|
10
|
+
- Support native delta snapshots and persistent refs without treating partial revisions as empty pages; keep native delta baselines upstream-owned.
|
|
11
|
+
- Preserve conditional screenshot options and skipped-image results without inventing files or attaching old images.
|
|
12
|
+
- Parse session pointer modes and recording cursor/contact-sheet options, verify contact-sheet images, and surface native WebMCP catalog updates. Document stateful auth login and native recording timing fixes.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Give automatic managed-session cleanup the normal 35-second command budget, rather than aborting during native Chrome's five-second shutdown grace. Explicit cleanup-timeout overrides remain supported. Report cleanup deadlines with the phase and elapsed time instead of incorrectly reporting caller cancellation.
|
|
17
|
+
|
|
18
|
+
## 0.6.13 - 2026-09-14
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Suppress Chrome's extra startup window using stock `--no-startup-window`, preserving headless defaults and composing caller launch arguments. URL-less `open` now uses native lazy URL reads so active pages keep their browser, profile, and URL across direct, batch, and script calls. External engines and attachments remain unchanged.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Give ordinary browser calls a native session and restore key per root Pi session. Parent/descendant groups share one browser without a global queue or child-exit teardown. Scope named Chrome profile bootstrap to automatic roots; preserve explicit browsers, script, and Electron isolation.
|
|
27
|
+
|
|
28
|
+
### Validation
|
|
29
|
+
|
|
30
|
+
- Start managed-lifecycle release checks explicitly in fresh mode; retain root-session ownership without leaving test browsers open.
|
|
31
|
+
|
|
5
32
|
## 0.6.12 - 2026-09-13
|
|
6
33
|
|
|
7
34
|
### Fixed
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ The result is optimized for agent work:
|
|
|
85
85
|
| Dashboard scroll commands can look successful while nothing moves | Handles standard `scroll <dir> [px]` against the document first (including pages whose smooth-scroll CSS defeats upstream wheel timing), falls back upstream when the document cannot move, and samples viewport/containers around the fallback; unchanged positions fail as `upstream-error` with `details.scrollNoop`, visible recovery guidance, and exact snapshot/screenshot checks. Unsupported `scrollintoview text=...` fails before dispatch, including inside effective batch rows, and shows exact native `find text ... hover` and snapshot/ref recovery payloads; help remains native pass-through. | [`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` |
|
|
86
86
|
| Dropdown/combobox clicks can focus or hit native option box-model errors | Adds first-class `select <selector> <value...>` paths through raw `args`, `job`, and `semanticAction`; semantic role/name or label select resolves exactly one current visible combobox/listbox ref before action. Custom combobox clicks still detect focused controls with explicit `aria-expanded` state but no visible options and return `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` |
|
|
87
87
|
| Recording workflows fail late when `ffmpeg` is missing or report stale lifecycle state | After successful `record start` / `record restart`, reports `successCategory: "artifact-pending"`, returns an exact `stop-pending-recording` action, warns when older natives report pending output without `ffmpeg`, and conservatively invalidates prior page-scoped `@e…` refs on every executed start attempt and URL-bearing restart to protect older supported natives. This is not evidence of a page change: 0.37 records the active page unless a URL is supplied; FPS-only calls keep the intended tab; an unbounded transcript-backed namespace/session index reserves active destinations across aliases, serializes artifact lifecycle and explicit wait/output writes, persists cross-branch close tombstones, retires every successful close path (including every matching namespace owner for `close --all`), rejects missing/stale restart output, coalesces terminal batch state, keeps only the newest pending path per identity, rejects recording starts after a nested close, folds Unicode path aliases, and retains exact cleanup actions with visible guidance on any later same-session failure | [`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`, `test/agent-browser.presentation-artifacts-batch.test.ts` |
|
|
88
|
-
| Upstream CLI drift can silently invalidate wrapper behavior | Publishes a repo-readable command reference, verifies it against the recommended 0.
|
|
88
|
+
| Upstream CLI drift can silently invalidate wrapper behavior | Publishes a repo-readable command reference, verifies it against the recommended 0.38.1 target, and probes browser-backed calls once per cwd/PATH so stable versions below the 0.35.0 floor fail before browser launch with installed/expected version evidence | `npm run verify` |
|
|
89
89
|
| Pages can expose structured workflows through experimental WebMCP | Shows native positive WebMCP availability in navigation summaries and passes through `webmcp list`, `invoke`, detached `result` / `cancel`, params/frame/timeout options, and the bundled `webmcp-gen` skill; treats `--no-webmcp` as launch-scoped, keeps pending or unsuccessfully settled targets unverified with an actionable `get url` follow-up, invalidates stale refs after page tools run, and budgets effective raw or stdin batch timeouts | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#webmcp-page-tools), `test/agent-browser.extension-ref-guards.test.ts`, `test/agent-browser.wait-timeouts.test.ts`, `test/agent-browser.real-upstream-contract.test.ts` |
|
|
90
90
|
| 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) |
|
|
91
91
|
| Agents need bundled `skills` text and local setup/status commands without touching the live session | Treats `skills list/get/path`, local auth/profile/setup commands, `session list`, and local state lifecycle commands as sessionless reads/actions when upstream does not need a live page. Session/state rows and targets remain visible, and supported upstream state/config/path operations pass through unchanged. Browser-backed workflows still receive an implicit session only when the caller did not choose one. | [`docs/COMMAND_REFERENCE.md`](docs/COMMAND_REFERENCE.md#built-in-skills), `extensions/agent-browser/lib/command-policy.ts`, `extensions/agent-browser/lib/runtime.ts` |
|
|
@@ -108,7 +108,9 @@ Optional external tools unlock the full command surface:
|
|
|
108
108
|
| `agent-browser` | All browser automation through this extension | See upstream install docs |
|
|
109
109
|
| `ffmpeg` | Recording WebM (libvpx) or MP4 (libx264); install before `record start` / `record restart` | `brew install ffmpeg` or `brew install ffmpeg-full` |
|
|
110
110
|
|
|
111
|
-
Keep both binaries on `PATH`. This package recommends `agent-browser 0.
|
|
111
|
+
Keep both binaries on `PATH`. This package recommends `agent-browser 0.38.1` and accepts stable versions at or above the 0.35.0 floor; browser-backed calls fail fast below that floor while local inspection/setup commands remain available for diagnosis. Native 0.37 validates `ffmpeg`, the output extension and FPS before recording; older supported natives may defer failures until stop. Use `record start <path.webm|path.mp4> [url] [--fps <n>]` (1–60 fps, default 30), then verify the completed file after `record stop`. Native `doctor` checks the recording dependency and encoders.
|
|
112
|
+
|
|
113
|
+
On 0.38.1, use `snapshot --delta` for native full/unchanged/changed revisions and `snapshot --delta --full` to reset its baseline. Surviving DOM nodes keep their refs, but navigation/replaced nodes still require fresh refs. The wrapper preserves native revisions and refreshes complete refs internally for partial results. `screenshot --if-changed` (or `--threshold 0.01`) skips unchanged images without creating an artifact. `--input-mode instant|smooth|human` controls native session pointer movement; `click ... --human`, `drag ... --human`, and timed `mouse move` remain ordinary `args`. Recordings accept `--cursor` and `--contact-sheet`; the latter produces a timestamped PNG verified after stop. `auth login <name> --no-navigate` fills a prepared login page after upstream checks its origin. See the [command reference](docs/COMMAND_REFERENCE.md#upstream-0381-rebaseline) for examples and limits.
|
|
112
114
|
|
|
113
115
|
### Android / Termux
|
|
114
116
|
|
|
@@ -188,7 +190,7 @@ npm run doctor
|
|
|
188
190
|
The doctor checks:
|
|
189
191
|
|
|
190
192
|
- upstream `agent-browser` exists on `PATH`
|
|
191
|
-
- the installed upstream is a stable version at or above the supported 0.35.0 floor; 0.
|
|
193
|
+
- the installed upstream is a stable version at or above the supported 0.35.0 floor; 0.38.1 remains the recommended baseline
|
|
192
194
|
- `pi --version` meets the minimum Pi runtime floor for this release; older Pi versions are setup failures
|
|
193
195
|
- Pi settings do not point at multiple active `pi-agent-browser-native` sources
|
|
194
196
|
|
|
@@ -317,6 +319,10 @@ Open a page and inspect it (first-call recipe: open → snapshot -i → interact
|
|
|
317
319
|
|
|
318
320
|
Chromium launch switches belong in the value of `--args`, for example `{ "args": ["--args", "--no-sandbox", "open", "https://example.com"], "sessionMode": "fresh" }` when that switch is needed. A bare `--no-sandbox` in the command slot is an unknown command; in an `open` / `goto` / `navigate` option position, upstream ignores it. The wrapper diagnoses those forms without rejecting literal text, select values or paths in other commands. For batches, put `--args` in top-level `args` before `batch`, not inside a row.
|
|
319
321
|
|
|
322
|
+
Local Chrome startup uses the stock binary's `--args --no-startup-window` capability to suppress Chrome's extra startup window. Headless remains the default. Caller launch arguments keep native CLI → environment → config precedence and follow this default; browsers started before this change with custom arguments may restart once when their native launch configuration changes. Ordinary follow-ups do not resend bootstrap settings. CDP, providers, Electron, and Lightpanda remain native-owned; no tabs are deleted or profiles edited by this behavior.
|
|
323
|
+
|
|
324
|
+
`open` without a URL uses native `get url`: it launches when needed and otherwise preserves the current page. This also applies to effective batch rows and script calls. `details.args` preserves the request, while `effectiveArgs`, command, URL data, and lifecycle describe the actual native operation. Use `open about:blank` only when you intend to navigate there. Help/version requests remain unchanged.
|
|
325
|
+
|
|
320
326
|
Watch a browser window during a demo, QA run, or user-completed login by adding upstream's global `--headed` flag on the first launch. Use `sessionMode: "fresh"` if a managed session may already exist, because headed/headless state is launch-scoped. A successful first/fresh local wrapper-managed headed launch, including a launch inside `batch`, returns `details.browserWindow = { mode: "headed", ownership: "wrapper-managed", sessionName, visibility: "unverified" }` and one visible handoff sentence; CDP, auto-connect, provider, and Electron attachments do not. This proves that the wrapper requested and upstream launched headed mode, not that the OS window is visible on the user's display; remote, container, or virtual-display setups can still hide it. After the user finishes in the window, continue with `sessionMode: "auto"`.
|
|
321
327
|
|
|
322
328
|
```json
|
|
@@ -608,33 +614,31 @@ After a successful unnamed fresh launch, later default `sessionMode: "auto"` cal
|
|
|
608
614
|
|
|
609
615
|
### Shared browser defaults
|
|
610
616
|
|
|
611
|
-
|
|
617
|
+
Ordinary browser calls use one native named browser per **root Pi session**. A parent and its subagents share that browser; unrelated roots get different names and can browse concurrently. `pi-subagents` supplies `PI_SUBAGENT_ROOT_SESSION_ID` through its existing launch records, including detached runs, delegated forks, grandchildren, and revived children. Ordinary Pi new/fork/clone sessions get their own root identity; resuming the same root or changing its cwd keeps its browser name.
|
|
618
|
+
|
|
619
|
+
Coordinate multi-call navigation within the group using existing subagent/intercom tools. Native commands are serialized by each browser daemon; wrapper helper queues are process-local. There is no global focus queue. Parent or child exit does not close the group browser. Native idle policy applies; explicitly close only your group's browser when the group is finished.
|
|
620
|
+
|
|
621
|
+
To bootstrap new root browsers from an authenticated normal Chrome profile, set the existing **Pi package** config in `~/.pi/config/pi-agent-browser-native/config.json`:
|
|
612
622
|
|
|
613
623
|
```json
|
|
614
624
|
{
|
|
615
|
-
"
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
625
|
+
"browser": {
|
|
626
|
+
"defaultProfile": { "name": "Default", "policy": "always" },
|
|
627
|
+
"executablePath": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
628
|
+
}
|
|
619
629
|
}
|
|
620
630
|
```
|
|
621
631
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
Pi quit does not close a caller-owned shared browser or impose its implicit 15-minute idle timeout. Native idle policy applies, including its default headed-browser exemption; explicit `--idle-timeout` applies to every helper in that call too. Keep native idle settings consistent between calls because changing them can restart the daemon. `AGENT_BROWSER_SOCKET_DIR` is honored for caller-owned sessions; the wrapper-specific socket override still takes priority.
|
|
632
|
+
For inactive automatic roots, global/override `always` profile **names** and the configured executable bootstrap native launch through a scoped subprocess environment shared with helpers. Active roots retain native launch settings instead of resending defaults that could replace an explicitly profiled browser; their existing restore key is preserved, including follow-ups naming the same root browser. Native Chrome profile names use independent temporary copies, not the source profile's lock. Profile paths and other profile policies remain advisory; project guidance does not change a root's launch settings when a child changes cwd. Explicit unrelated sessions, native launch/config/environment choices, isolated `script`, and Electron launches do not receive the root bootstrap defaults. Do not set a global native profile merely to enable this feature: it can change the launch configuration of existing unrelated browsers.
|
|
625
633
|
|
|
626
|
-
|
|
634
|
+
Each automatic root has its own stable native restore key. Native save/restore preserves cookies, localStorage, and sessionStorage across normal close/restart; it does not merge auth between roots or save new IndexedDB-only credentials, service workers, or page-memory grants. Named profile copies can bootstrap existing IndexedDB, but are discarded on close and never write back to the communal profile. Verify the actual application after restart, not just a successful restore receipt. If an application needs unsupported state, use a supported persistent **per-root** profile and collect required sign-ins together; never point concurrent roots at one writable profile directory. Sites can expire sessions or require a fresh human challenge.
|
|
627
635
|
|
|
628
|
-
|
|
636
|
+
Explicit native `session` defaults (config or `AGENT_BROWSER_SESSION`) still override automatic root selection, including `sessionMode: "fresh"`. They intentionally share whichever browser the caller selected. Per-call session/namespace flags retain precedence. Ordinary launch options such as `--profile` or `--executable-path` still use the root-group name. Repeat explicit options when deliberately relaunching a persistent per-root profile after close. Explicit `fresh` and attachment choices retain their existing managed-session lifecycle; later auto calls follow that selected managed session. Use `session info` with the reported `sessionName` for read-only native daemon/browser evidence. Unknown native fields remain unknown.
|
|
629
637
|
|
|
630
638
|
Policy-required URL reads keep the two-call read → confirm/deny flow. Returned actions name the actual native namespace/session, including `default`; routing survives transcript resume and branch changes. Only explicit-read provenance plus native `capabilities.readRequiresConfirmation: true` permits matching confirm/deny without page helpers. This capability includes native confirmation-ID checking. Legacy prompts retain correct routing but normal page checks; DOM or content-shaped prompts never receive the exemption.
|
|
631
639
|
|
|
632
640
|
Full live identity, browser-independent native reads/confirmations and detailed recording receipts require the companion upstream fixes; the current recommended release does not yet supply all of them. Older supported versions remain accepted with unavailable evidence marked unknown. The extension does not upgrade or restart your installed browser to obtain these fields.
|
|
633
641
|
|
|
634
|
-
Coordinate tabs/navigation between agents sharing a browser; the wrapper's per-session queue is local to one Pi process. Persistent profile storage preserves browser data, not a promise of permanent website login: sites can expire or revoke sessions and require a new human challenge. Standing permission avoids repeated permission requests, but cannot change those site policies.
|
|
635
|
-
|
|
636
|
-
Before enabling global native profile defaults, update and fully restart every participating Pi runtime. Older loaded wrappers can still let disposable `script` launches read HOME config; editing this source or running `/reload` does not replace their cached code. Coordinate the browser cutover too: moving an existing daemon from the old wrapper's explicit 15-minute idle timeout to native idle policy changes its launch fingerprint and can restart it. Do not change that policy while another agent is using the browser.
|
|
637
|
-
|
|
638
642
|
### Profile selection
|
|
639
643
|
|
|
640
644
|
The wrapper does not clone profiles or hide what upstream Chrome/Chromium profile or executable you chose. Passing `--profile` or `--executable-path` is an explicit upstream `agent-browser` choice. Visible page content from real profiles is model-visible and may persist in transcripts or saved artifacts; redaction protects credential-like cookie/storage/auth values, not ordinary page text you asked the browser to read. Redactions use `[REDACTED]` (URL-encoded in parsed URLs); ordinary technical phrases such as `bearer token` stay intact. URL redaction includes `code`, `authorization_session_id`, and auth-context `state` / `nonce` across visible text, details, spills, and `outputPath` exports. URLs needing no redaction keep their original spelling.
|
|
@@ -756,7 +760,7 @@ The opt-in real-upstream suite is separate because it drives a real browser inst
|
|
|
756
760
|
npm run verify -- real-upstream
|
|
757
761
|
```
|
|
758
762
|
|
|
759
|
-
That mode sets `PI_AGENT_BROWSER_REAL_UPSTREAM=1` and runs `test/agent-browser.real-upstream-contract.test.ts` against the real `agent-browser` on `PATH` (the stable version must meet the 0.35.0 floor; current command-reference validation targets the recommended 0.
|
|
763
|
+
That mode sets `PI_AGENT_BROWSER_REAL_UPSTREAM=1` and runs `test/agent-browser.real-upstream-contract.test.ts` against the real `agent-browser` on `PATH` (the stable version must meet the 0.35.0 floor; current command-reference validation targets the recommended 0.38.1 capability baseline). It covers inspection, skills, experimental WebMCP list/invoke/result/cancel plus `--no-webmcp`, and a broad core interaction and navigation matrix on localhost fixtures (including off-viewport click, frame-scoped selector/wait/click behavior, form command fixes, `batch` stdin, and `pushstate`), plus `vitals`, network route/requests/HAR, diff snapshot/screenshot/url, trace/profiler, console/errors/highlight, stream enable/status/disable, `cookies set --curl`, a `react tree` missing-renderer path, and `wait --download` with the on-disk caveat documented in release notes. The harness uses a throwaway temp `HOME` and dedicated socket/screenshot directories so the run does not touch your normal browser profile paths. Browser-opening or credential-dependent families such as `inspect`, `dashboard`, `chat`, provider clouds, and OS clipboard flows stay in fake-upstream or manual validation unless a safe deterministic fixture is added. For prerequisites, isolation details, and troubleshooting, see [`docs/RELEASE.md`](docs/RELEASE.md#real-upstream-contract-validation).
|
|
760
764
|
|
|
761
765
|
A deterministic host-only live-browser wrapper smoke is available without an LLM choosing tool calls:
|
|
762
766
|
|
|
@@ -789,12 +793,12 @@ npm run verify -- release
|
|
|
789
793
|
5. Screenshots, downloads, recordings, traces, profiles, and spill files are normalized as Pi-visible artifacts where possible.
|
|
790
794
|
6. Generated playbook text in docs and tool metadata stays aligned with `extensions/agent-browser/lib/playbook.ts`.
|
|
791
795
|
|
|
792
|
-
The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it. The recommended baseline is 0.
|
|
796
|
+
The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it. The recommended baseline is 0.38.1 and the stable runtime floor is 0.35.0; newer stable versions are accepted without version-specific compatibility shims.
|
|
793
797
|
|
|
794
798
|
## Current limits
|
|
795
799
|
|
|
796
800
|
- Published pre-1.0 package.
|
|
797
|
-
- Recommends upstream `agent-browser` 0.
|
|
801
|
+
- Recommends upstream `agent-browser` 0.38.1 and accepts stable runtimes at or above 0.35.0.
|
|
798
802
|
- Does not bundle `agent-browser`; users install it separately.
|
|
799
803
|
- Does not provide a human browser UI inside Pi; the primary UX is agent-invoked tool calls. `--headed` asks upstream to show a browser window, but the wrapper cannot yet prove that the window is visible on the user's desktop.
|
|
800
804
|
- Localhost means the browser host's loopback, not necessarily the shell/Pi host. If `http://localhost:<port>` or `http://127.0.0.1:<port>` fails with errors such as `ERR_EMPTY_RESPONSE`, use an environment-specific host-reachable HTTP(S) address. A `file://` fixture is supported when upstream browser launch settings allow it; use HTTP(S) only when the browser environment cannot reach the local file.
|
|
@@ -22,7 +22,7 @@ import { cleanupSecureTempArtifacts } from "./lib/temp.js";
|
|
|
22
22
|
import { AGENT_BROWSER_PARAMS } from "./lib/input-modes/params.js";
|
|
23
23
|
import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, createAgentBrowserScriptSessionName, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
|
|
24
24
|
import { closeManagedSession, getSessionContextKey, runAgentBrowserTool } from "./lib/orchestration/browser-run/index.js";
|
|
25
|
-
import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
|
|
25
|
+
import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination, getRecordContactSheetDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
|
|
26
26
|
import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
|
|
27
27
|
import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
|
|
28
28
|
import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
|
|
@@ -75,6 +75,8 @@ function getArtifactPreflightValidationError(options) {
|
|
|
75
75
|
for (const reservation of options.activeRecordingReservations ?? []) {
|
|
76
76
|
try {
|
|
77
77
|
activeRecordingDestinations.add(canonicalizeExplicitArtifactDestination(reservation.cwd, reservation.absolutePath));
|
|
78
|
+
if (reservation.contactSheetPath)
|
|
79
|
+
activeRecordingDestinations.add(canonicalizeExplicitArtifactDestination(reservation.cwd, reservation.contactSheetPath));
|
|
78
80
|
}
|
|
79
81
|
catch (canonicalizationError) {
|
|
80
82
|
if (!cleanupOnly)
|
|
@@ -106,8 +108,9 @@ function getArtifactPreflightValidationError(options) {
|
|
|
106
108
|
if (isCloseCommand(commandStep[0]))
|
|
107
109
|
sawBatchClose = true;
|
|
108
110
|
}
|
|
109
|
-
const artifactDestination
|
|
110
|
-
|
|
111
|
+
for (const artifactDestination of [getExplicitArtifactDestination(commandStep), getRecordContactSheetDestination(commandStep)]) {
|
|
112
|
+
if (!artifactDestination)
|
|
113
|
+
continue;
|
|
111
114
|
let canonicalDestination;
|
|
112
115
|
try {
|
|
113
116
|
canonicalDestination = canonicalizeExplicitArtifactDestination(options.cwd, artifactDestination);
|
|
@@ -1126,7 +1129,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1126
1129
|
}
|
|
1127
1130
|
for (const [key, reservation] of activeRecordingReservations) {
|
|
1128
1131
|
const restored = restoredRecordingState.active.get(key);
|
|
1129
|
-
if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd || restored.recordingId !== reservation.recordingId || restored.startedAtMs !== reservation.startedAtMs)
|
|
1132
|
+
if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd || restored.recordingId !== reservation.recordingId || restored.startedAtMs !== reservation.startedAtMs || restored.contactSheetPath !== reservation.contactSheetPath)
|
|
1130
1133
|
recordingReservationsDirty = true;
|
|
1131
1134
|
restoredRecordingState.active.set(key, reservation);
|
|
1132
1135
|
}
|
|
@@ -1370,7 +1373,12 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1370
1373
|
}
|
|
1371
1374
|
if (resolvedInput.kind !== "script")
|
|
1372
1375
|
await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
|
|
1373
|
-
|
|
1376
|
+
const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
|
|
1377
|
+
const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
|
|
1378
|
+
const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1379
|
+
const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
|
|
1380
|
+
? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
|
|
1381
|
+
return withNativeSessionDefaults(resolvedInput, ctx.cwd, signal, async (resolvedInput, withLaunchDefaults) => {
|
|
1374
1382
|
const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
|
|
1375
1383
|
if (readConfirmation)
|
|
1376
1384
|
resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
|
|
@@ -1614,7 +1622,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1614
1622
|
const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
|
|
1615
1623
|
? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
|
|
1616
1624
|
: undefined;
|
|
1617
|
-
const runBrowserCommand = async () => {
|
|
1625
|
+
const runBrowserCommand = async (daemonInactive) => {
|
|
1618
1626
|
flushRecordingReservations();
|
|
1619
1627
|
const branchRestoreGenerationAtStart = branchRestoreGeneration;
|
|
1620
1628
|
const generationAtStart = branchStateGeneration;
|
|
@@ -1664,6 +1672,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1664
1672
|
?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
|
|
1665
1673
|
const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
|
|
1666
1674
|
let result = await runAgentBrowserTool({
|
|
1675
|
+
daemonInactive,
|
|
1667
1676
|
ctx,
|
|
1668
1677
|
cwd: ctx.cwd,
|
|
1669
1678
|
electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
|
|
@@ -1771,6 +1780,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1771
1780
|
return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
|
|
1772
1781
|
};
|
|
1773
1782
|
const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
|
|
1783
|
+
const runWithLaunchDefaults = () => withLaunchDefaults ? withLaunchDefaults(runBrowserCommand) : runBrowserCommand();
|
|
1774
1784
|
const runWithinSessionQueue = () => {
|
|
1775
1785
|
if (closesAllSessions)
|
|
1776
1786
|
return managedSessionExecutionQueue.run(() => {
|
|
@@ -1783,13 +1793,13 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1783
1793
|
sessionMode: params.sessionMode ?? "auto",
|
|
1784
1794
|
stdin: resolvedInput.toolStdin,
|
|
1785
1795
|
});
|
|
1786
|
-
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace,
|
|
1796
|
+
return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
|
|
1787
1797
|
});
|
|
1788
1798
|
if (serializeBrowserCommand)
|
|
1789
|
-
return managedSessionExecutionQueue.run(
|
|
1799
|
+
return managedSessionExecutionQueue.run(runWithLaunchDefaults);
|
|
1790
1800
|
return callerOwnedSessionQueueKey
|
|
1791
|
-
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace,
|
|
1792
|
-
:
|
|
1801
|
+
? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults)
|
|
1802
|
+
: runWithLaunchDefaults();
|
|
1793
1803
|
};
|
|
1794
1804
|
if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
|
|
1795
1805
|
return runWithinSessionQueue();
|
|
@@ -1814,6 +1824,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
|
|
|
1814
1824
|
validationError: artifactValidationError,
|
|
1815
1825
|
}));
|
|
1816
1826
|
});
|
|
1827
|
+
}, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
|
|
1828
|
+
id: rootSessionId,
|
|
1829
|
+
profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
|
|
1830
|
+
executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
|
|
1817
1831
|
});
|
|
1818
1832
|
},
|
|
1819
1833
|
};
|
|
@@ -324,6 +324,10 @@ export function getRecordCommandOperands(tokens) {
|
|
|
324
324
|
// Native validates the range; bare/non-numeric --fps keeps its old literal meaning.
|
|
325
325
|
if (tokens[index] === "--fps" && /^\+?\d+$/.test(tokens[index + 1] ?? ""))
|
|
326
326
|
index += 1;
|
|
327
|
+
else if (tokens[index] === "--cursor" || tokens[index] === "--contact-sheet")
|
|
328
|
+
continue;
|
|
329
|
+
else if (tokens[index] === "--contact-sheet-threshold" && tokens[index + 1] !== undefined)
|
|
330
|
+
index += 1;
|
|
327
331
|
else
|
|
328
332
|
operands.push(tokens[index]);
|
|
329
333
|
}
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
import { projectUpstreamGlobalFlags } from "../argv-grammar.js";
|
|
2
|
+
/** Bare open's native launch action drops prior launch options. URL reads lazily launch without navigating. */
|
|
3
|
+
export function normalizeUrlLessOpen(args, stdin, batchStep = false) {
|
|
4
|
+
const { tokens, indices } = batchStep ? { tokens: args, indices: args.map((_, index) => index) } : projectUpstreamGlobalFlags(args);
|
|
5
|
+
if (tokens[0] === "open" && !tokens.slice(1).some(token => !token.startsWith("--"))) {
|
|
6
|
+
const index = indices[0];
|
|
7
|
+
return { args: [...args.slice(0, index), "get", "url", ...args.slice(index + 1)], stdin };
|
|
8
|
+
}
|
|
9
|
+
if (tokens[0] !== "batch")
|
|
10
|
+
return { args, stdin };
|
|
11
|
+
const rawSteps = tokens.slice(1).flatMap((token, offset) => {
|
|
12
|
+
const step = token === "--bail" ? undefined : parseBatchCommandArgument(token).step;
|
|
13
|
+
return step ? [{ index: indices[offset + 1], step }] : [];
|
|
14
|
+
});
|
|
15
|
+
if (tokens.slice(1).some(token => token !== "--bail")) {
|
|
16
|
+
let normalized = args;
|
|
17
|
+
for (const { index, step } of rawSteps) {
|
|
18
|
+
const row = normalizeUrlLessOpen(step, undefined, true).args;
|
|
19
|
+
if (row === step)
|
|
20
|
+
continue;
|
|
21
|
+
if (normalized === args)
|
|
22
|
+
normalized = [...args];
|
|
23
|
+
normalized[index] = row.map(token => `'${token.replaceAll("'", "'\\''")}'`).join(" ");
|
|
24
|
+
}
|
|
25
|
+
return { args: normalized, stdin };
|
|
26
|
+
}
|
|
27
|
+
const steps = parseUserBatchStdin(stdin).steps;
|
|
28
|
+
if (!steps?.length)
|
|
29
|
+
return { args, stdin };
|
|
30
|
+
const normalized = steps.map(step => normalizeUrlLessOpen(step, undefined, true).args);
|
|
31
|
+
return { args, stdin: normalized.some((step, index) => step !== steps[index]) ? JSON.stringify(normalized) : stdin };
|
|
32
|
+
}
|
|
1
33
|
const BATCH_STDIN_EXAMPLE = ' Example: { "args": ["batch"], "stdin": "[[\\"get\\",\\"title\\"],[\\"get\\",\\"url\\"]]" }';
|
|
2
34
|
// Mirror upstream commands::shell_words_split so policy inspection sees the same argv.
|
|
3
35
|
export function parseBatchCommandArgument(command) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstatSync, readlinkSync, realpathSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { foldAgentBrowserFilesystemIdentity } from "../../argv-grammar.js";
|
|
4
4
|
import { parseWaitCommandTokens } from "../../argv-descriptor.js";
|
|
5
5
|
import { getRecordCommandOperands } from "../../command-taxonomy.js";
|
|
@@ -10,14 +10,18 @@ function isSingleScreenshotPathToken(token) {
|
|
|
10
10
|
return false;
|
|
11
11
|
return explicitlyRelative || token.includes("/") || SCREENSHOT_IMAGE_EXTENSIONS.some((extension) => token.endsWith(extension));
|
|
12
12
|
}
|
|
13
|
-
function getScreenshotPositionalIndices(commandTokens) {
|
|
13
|
+
export function getScreenshotPositionalIndices(commandTokens) {
|
|
14
14
|
if (commandTokens[0] !== "screenshot")
|
|
15
15
|
return [];
|
|
16
16
|
const positionalIndices = [];
|
|
17
17
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
18
18
|
const token = commandTokens[index];
|
|
19
|
-
if (token === "--full" || token === "-f")
|
|
19
|
+
if (token === "--full" || token === "-f" || token === "--if-changed")
|
|
20
20
|
continue;
|
|
21
|
+
if (token === "--threshold") {
|
|
22
|
+
index += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
21
25
|
positionalIndices.push(index);
|
|
22
26
|
}
|
|
23
27
|
return positionalIndices;
|
|
@@ -91,6 +95,13 @@ function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
|
91
95
|
export function canonicalizeExplicitArtifactDestination(cwd, destination, platform = process.platform) {
|
|
92
96
|
return canonicalizeArtifactPath(resolve(cwd, destination), platform, new Set());
|
|
93
97
|
}
|
|
98
|
+
export function getRecordContactSheetDestination(commandTokens) {
|
|
99
|
+
const path = getRecordCommandOperands(commandTokens).path;
|
|
100
|
+
if (!path || !commandTokens.some((token) => token === "--contact-sheet" || token === "--contact-sheet-threshold"))
|
|
101
|
+
return undefined;
|
|
102
|
+
const extension = extname(path);
|
|
103
|
+
return extension ? `${path.slice(0, -extension.length)}.contact-sheet.png` : undefined;
|
|
104
|
+
}
|
|
94
105
|
export function getExplicitArtifactDestination(commandTokens) {
|
|
95
106
|
const command = commandTokens[0];
|
|
96
107
|
const subcommand = commandTokens[1];
|
|
@@ -11,7 +11,7 @@ function normalizeAccessibleName(name) {
|
|
|
11
11
|
function getClickDispatchProbeTarget(commandTokens, refSnapshot) {
|
|
12
12
|
if (commandTokens[0] !== "click" || commandTokens.includes("--new-tab"))
|
|
13
13
|
return undefined;
|
|
14
|
-
const selector = commandTokens
|
|
14
|
+
const selector = commandTokens.slice(1).find((token) => token !== "--human");
|
|
15
15
|
if (!selector || selector.startsWith("-"))
|
|
16
16
|
return undefined;
|
|
17
17
|
const refId = parseRefId(selector);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { runAgentBrowserProcess, withAttachedBrowserSessionContext } from "../../process.js";
|
|
1
|
+
import { runAgentBrowserProcess, withAttachedBrowserSessionContext, withChromeStartupArgs } from "../../process.js";
|
|
2
2
|
import { isRecord } from "../../parsing.js";
|
|
3
|
+
import { collectNativeWebMcp, getNativeWebMcpCatalog } from "../../webmcp-observation.js";
|
|
4
|
+
import { redactSensitiveValue } from "../../runtime.js";
|
|
5
|
+
import { formatWebMcpCatalogUpdate } from "../../results/presentation/common.js";
|
|
3
6
|
import { withOwnedManagedSessionContext } from "../../managed-session-restore.js";
|
|
4
7
|
import { cleanupClickDispatchProbe } from "./click-dispatch.js";
|
|
5
8
|
import { applyBrowserRunStatePatch, getSessionContextKey } from "./session-state.js";
|
|
@@ -9,8 +12,21 @@ import { processBrowserOutput } from "./process-output.js";
|
|
|
9
12
|
export { closeManagedSession } from "./managed-session-daemon-policy.js";
|
|
10
13
|
export { getSessionContextKey } from "./session-state.js";
|
|
11
14
|
export async function runAgentBrowserTool(options) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
15
|
+
const observed = await collectNativeWebMcp(() => withChromeStartupArgs(options.input.persistentChromeArgs, () => withAttachedBrowserSessionContext(options.preserveAttachedBrowserSession === true, () => runAgentBrowserToolInContext(options))));
|
|
16
|
+
const result = observed.result;
|
|
17
|
+
let details = isRecord(result.details) ? result.details : undefined;
|
|
18
|
+
if (observed.catalog) {
|
|
19
|
+
const catalog = redactSensitiveValue(observed.catalog);
|
|
20
|
+
details = { ...details, webMcpCatalog: catalog };
|
|
21
|
+
result.details = details;
|
|
22
|
+
if (!options.input.toolArgs.includes("--json") && JSON.stringify(getNativeWebMcpCatalog(details.data)) !== JSON.stringify(catalog)) {
|
|
23
|
+
const notice = formatWebMcpCatalogUpdate(catalog);
|
|
24
|
+
if (result.content[0]?.type === "text")
|
|
25
|
+
result.content[0] = { ...result.content[0], text: `${result.content[0].text}\n\n${notice}` };
|
|
26
|
+
else
|
|
27
|
+
result.content.push({ type: "text", text: notice });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
14
30
|
const sessionKey = getSessionContextKey(typeof details?.sessionName === "string" ? details.sessionName : undefined, typeof details?.namespace === "string" ? details.namespace : undefined);
|
|
15
31
|
const page = options.state.sessionPageState.get(sessionKey);
|
|
16
32
|
return page.tabReopenPending === undefined ? result : {
|
|
@@ -29,7 +45,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
29
45
|
return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
30
46
|
try {
|
|
31
47
|
const artifactRunStartedAtMs = Date.now();
|
|
32
|
-
const processResult = await runAgentBrowserProcess({
|
|
48
|
+
const processResult = await withChromeStartupArgs(prepared.chromeStartupArgs, () => runAgentBrowserProcess({
|
|
33
49
|
args: prepared.processArgs,
|
|
34
50
|
browserIndependentReadConfirmation: prepared.readConfirmation !== undefined,
|
|
35
51
|
cwd: options.cwd,
|
|
@@ -43,7 +59,7 @@ async function runAgentBrowserToolInContext(options) {
|
|
|
43
59
|
signal: options.signal,
|
|
44
60
|
stdin: prepared.processStdin,
|
|
45
61
|
timeoutMs: prepared.processTimeoutMs,
|
|
46
|
-
});
|
|
62
|
+
}));
|
|
47
63
|
const missingBinaryResult = await buildMissingBinaryFailureResult({
|
|
48
64
|
compatibilityWorkaround: prepared.compatibilityWorkaround,
|
|
49
65
|
electronLaunch: prepared.electronLaunch,
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js
CHANGED
|
@@ -142,7 +142,13 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
142
142
|
}
|
|
143
143
|
export async function closeManagedSession(options) {
|
|
144
144
|
const controller = new AbortController();
|
|
145
|
-
|
|
145
|
+
let phase = "policy coordination";
|
|
146
|
+
let phaseStartedAt = Date.now();
|
|
147
|
+
let timeoutError;
|
|
148
|
+
const timer = setTimeout(() => {
|
|
149
|
+
timeoutError = `Managed-session cleanup timed out after ${options.timeoutMs} ms during ${phase} (${Date.now() - phaseStartedAt} ms in this phase).`;
|
|
150
|
+
controller.abort();
|
|
151
|
+
}, options.timeoutMs);
|
|
146
152
|
let stdoutSpillPath;
|
|
147
153
|
const closeArgs = [...(options.namespace !== undefined ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
|
|
148
154
|
const policyLock = options.policyLock ?? await acquireManagedSessionPolicyLock({
|
|
@@ -153,9 +159,11 @@ export async function closeManagedSession(options) {
|
|
|
153
159
|
});
|
|
154
160
|
if (!policyLock) {
|
|
155
161
|
clearTimeout(timer);
|
|
156
|
-
return "Managed-session policy coordination is unavailable or busy; cleanup did not run. Retry after the current operation finishes or repair the private policy-lock directory.";
|
|
162
|
+
return timeoutError ?? "Managed-session policy coordination is unavailable or busy; cleanup did not run. Retry after the current operation finishes or repair the private policy-lock directory.";
|
|
157
163
|
}
|
|
158
164
|
try {
|
|
165
|
+
phase = "daemon inspection";
|
|
166
|
+
phaseStartedAt = Date.now();
|
|
159
167
|
const daemon = await inspectManagedSessionDaemon({
|
|
160
168
|
cwd: options.cwd,
|
|
161
169
|
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
@@ -170,6 +178,8 @@ export async function closeManagedSession(options) {
|
|
|
170
178
|
const daemonRestoreKey = options.restoreState.getDaemonRestoreKey(options.sessionName, options.namespace);
|
|
171
179
|
const ownedRestoreKey = !options.restoreState.isDisabled(options.sessionName, options.namespace)
|
|
172
180
|
&& isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
|
|
181
|
+
phase = "native close";
|
|
182
|
+
phaseStartedAt = Date.now();
|
|
173
183
|
const processResult = await runAgentBrowserProcess({
|
|
174
184
|
args: closeArgs,
|
|
175
185
|
cwd: options.cwd,
|
|
@@ -179,6 +189,7 @@ export async function closeManagedSession(options) {
|
|
|
179
189
|
preserveAttachedBrowserSession: options.preserveAttachedBrowserSession,
|
|
180
190
|
signal: controller.signal,
|
|
181
191
|
});
|
|
192
|
+
clearTimeout(timer);
|
|
182
193
|
stdoutSpillPath = processResult.stdoutSpillPath;
|
|
183
194
|
if (!processResult.aborted && !processResult.spawnError && processResult.exitCode === 0) {
|
|
184
195
|
const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
|
|
@@ -191,7 +202,7 @@ export async function closeManagedSession(options) {
|
|
|
191
202
|
statePath: typeof data?.statePath === "string" ? data.statePath : undefined,
|
|
192
203
|
});
|
|
193
204
|
}
|
|
194
|
-
return getAgentBrowserErrorText({
|
|
205
|
+
return timeoutError ?? getAgentBrowserErrorText({
|
|
195
206
|
aborted: processResult.aborted,
|
|
196
207
|
command: "close",
|
|
197
208
|
effectiveArgs: redactInvocationArgs(closeArgs),
|
|
@@ -204,7 +215,7 @@ export async function closeManagedSession(options) {
|
|
|
204
215
|
});
|
|
205
216
|
}
|
|
206
217
|
catch (error) {
|
|
207
|
-
return error instanceof Error ? error.message : String(error);
|
|
218
|
+
return timeoutError ?? (error instanceof Error ? error.message : String(error));
|
|
208
219
|
}
|
|
209
220
|
finally {
|
|
210
221
|
clearTimeout(timer);
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js
CHANGED
|
@@ -13,6 +13,9 @@ function parseSnapshotFilterRequest(commandTokens) {
|
|
|
13
13
|
let search;
|
|
14
14
|
for (let index = 0; index < commandTokens.length; index += 1) {
|
|
15
15
|
const token = commandTokens[index];
|
|
16
|
+
// Wrapper filters need a full native tree, not a revision-relative patch.
|
|
17
|
+
if (token === "--delta" || token === "--full")
|
|
18
|
+
continue;
|
|
16
19
|
if (token === "--viewport")
|
|
17
20
|
continue;
|
|
18
21
|
if (token === "--diff")
|
|
@@ -2,7 +2,9 @@ import { copyFile, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { extractExplicitSessionName, getBooleanFlagValue, isUpstreamEnvFlagEnabled, projectUpstreamGlobalFlags, resolveAgentBrowserNamespace } from "../../argv-grammar.js";
|
|
4
4
|
import { isCloseCommand } from "../../command-taxonomy.js";
|
|
5
|
-
import { isBrowserIndependentRead } from "../../command-policy.js";
|
|
5
|
+
import { isBrowserIndependentRead, needsManagedSession } from "../../command-policy.js";
|
|
6
|
+
import { parseArgvDescriptor } from "../../argv-descriptor.js";
|
|
7
|
+
import { prepareAgentBrowserSpawnArgs, withChromeStartupArgs } from "../../process.js";
|
|
6
8
|
import { cleanupElectronLaunchResources } from "../../electron/cleanup.js";
|
|
7
9
|
import { launchElectronApp } from "../../electron/launch.js";
|
|
8
10
|
import { pathExists } from "../../fs-utils.js";
|
|
@@ -22,7 +24,7 @@ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSe
|
|
|
22
24
|
import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
23
25
|
import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
|
|
24
26
|
import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.js";
|
|
25
|
-
import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
|
|
27
|
+
import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
|
|
26
28
|
import { buildManagedSessionOutcome, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, findElectronLaunchRecordForSession, extractStringResultField, ensureSessionTabTarget, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
|
|
27
29
|
import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
|
|
28
30
|
import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
@@ -170,6 +172,8 @@ export async function prepareAgentBrowserArgs(args, stdin, cwd) {
|
|
|
170
172
|
}
|
|
171
173
|
async function repairScreenshotData(options) {
|
|
172
174
|
const { cwd, data, request } = options;
|
|
175
|
+
if (data.changed === false)
|
|
176
|
+
return { data };
|
|
173
177
|
const reportedPath = typeof data.path === "string" ? data.path : undefined;
|
|
174
178
|
const reportedAbsolutePath = reportedPath ? resolve(cwd, reportedPath) : undefined;
|
|
175
179
|
let status = await pathExists(request.absolutePath) ? "saved" : "missing";
|
|
@@ -551,7 +555,16 @@ export async function prepareBrowserRun(options) {
|
|
|
551
555
|
};
|
|
552
556
|
}
|
|
553
557
|
}
|
|
554
|
-
|
|
558
|
+
let chromeStartupArgs;
|
|
559
|
+
if (options.input.chromeStartupArgs !== undefined && !options.preserveAttachedBrowserSession && !browserIndependent
|
|
560
|
+
&& !executionPlan.validationError && !isCloseCommand(executionPlan.commandInfo.command)
|
|
561
|
+
&& needsManagedSession(parseArgvDescriptor(preparedArgs.args), runtimeToolStdin)) {
|
|
562
|
+
const inactive = ownedManagedSession ? managedSessionDaemonInactive : options.daemonInactive
|
|
563
|
+
?? (executionPlan.sessionName !== undefined && (await inspectManagedSessionDaemon({ cwd, signal, sessionName: executionPlan.sessionName, namespace: executionPlan.namespace })).status === "inactive");
|
|
564
|
+
if (inactive || options.input.configuredChromeLaunch)
|
|
565
|
+
chromeStartupArgs = options.input.chromeStartupArgs;
|
|
566
|
+
}
|
|
567
|
+
return await withChromeStartupArgs(chromeStartupArgs, () => withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
555
568
|
const managedSessionRestoreDisabled = () => state.managedSessionRestoreState.isDisabled(executionPlan.sessionName, executionPlan.namespace);
|
|
556
569
|
const sessionStateKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
557
570
|
const priorSessionPageState = sessionPageState.get(sessionStateKey);
|
|
@@ -737,7 +750,7 @@ export async function prepareBrowserRun(options) {
|
|
|
737
750
|
requirement: livePageRequirement,
|
|
738
751
|
stdin: runtimeToolStdin,
|
|
739
752
|
});
|
|
740
|
-
const redactedEffectiveArgs = redactInvocationArgs(executionPlan.effectiveArgs);
|
|
753
|
+
const redactedEffectiveArgs = redactInvocationArgs(prepareAgentBrowserSpawnArgs(executionPlan.effectiveArgs, undefined, options.preserveAttachedBrowserSession, chromeStartupArgs));
|
|
741
754
|
const redactedRecoveryHint = redactRecoveryHint(executionPlan.recoveryHint);
|
|
742
755
|
const compatibilityWorkaround = executionPlan.compatibilityWorkaround;
|
|
743
756
|
const statePatch = executionPlan.managedSessionName === freshSessionName
|
|
@@ -1022,7 +1035,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1022
1035
|
catch { }
|
|
1023
1036
|
}
|
|
1024
1037
|
const processTimeoutMs = options.params.timeoutMs ?? getDialogAwareProcessTimeoutMs(commandTokens, promptRefSnapshot, processStdin) ?? getCommandAwareProcessTimeoutMs(commandTokens, processStdin, readTimeoutPageUrl);
|
|
1025
|
-
const redactedProcessArgs = redactInvocationArgs(processArgs);
|
|
1038
|
+
const redactedProcessArgs = redactInvocationArgs(prepareAgentBrowserSpawnArgs(processArgs, ownedManagedSession?.compatibilityUserAgent, options.preserveAttachedBrowserSession, chromeStartupArgs));
|
|
1026
1039
|
const scrollAmount = Number(commandTokens.find((token) => /^\d+(?:\.\d+)?$/.test(token)));
|
|
1027
1040
|
const shouldProbeScrollNoop = executionPlan.commandInfo.command === "scroll" && executionPlan.startupScopedFlags.length === 0 && (state.managedSessionActive || sessionMode === "fresh") && (!Number.isFinite(scrollAmount) || scrollAmount >= 500);
|
|
1028
1041
|
const scrollPositionBefore = shouldProbeScrollNoop
|
|
@@ -1043,6 +1056,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1043
1056
|
return {
|
|
1044
1057
|
kind: "ready",
|
|
1045
1058
|
prepared: {
|
|
1059
|
+
chromeStartupArgs,
|
|
1046
1060
|
commandTokens,
|
|
1047
1061
|
headedLaunch,
|
|
1048
1062
|
providerLaunch,
|
|
@@ -1091,7 +1105,7 @@ export async function prepareBrowserRun(options) {
|
|
|
1091
1105
|
userRequestedJson,
|
|
1092
1106
|
},
|
|
1093
1107
|
};
|
|
1094
|
-
});
|
|
1108
|
+
}));
|
|
1095
1109
|
}
|
|
1096
1110
|
finally {
|
|
1097
1111
|
if (!managedSessionPolicyLockTransferred)
|