pi-agent-browser-native 0.6.9 → 0.6.10
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 +16 -0
- package/README.md +13 -13
- package/dist/extensions/agent-browser/index.js +8 -21
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -7
- package/dist/extensions/agent-browser/lib/argv-grammar.js +6 -0
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +4 -8
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +15 -2
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +2 -2
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +3 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +6 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +11 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +4 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +13 -17
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +5 -11
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +2 -2
- package/dist/extensions/agent-browser/lib/playbook.js +4 -4
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +17 -29
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +5 -5
- package/dist/extensions/agent-browser/lib/session-page-state.js +1 -1
- package/dist/scripts/agent-browser-target.mjs +1 -1
- package/docs/ARCHITECTURE.md +4 -3
- package/docs/COMMAND_REFERENCE.md +34 -18
- package/docs/RELEASE.md +6 -4
- package/docs/SUPPORT_MATRIX.md +18 -14
- package/docs/TOOL_CONTRACT.md +11 -9
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +10 -3
- package/scripts/agent-browser-target.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.10 - 2026-09-08
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Follow native artifact operands and raw-batch precedence consistently in preflight, recording reservations, result metadata and timeout recovery. Preserve literal batch operands and replay retries as a single native row (#168).
|
|
8
|
+
- Handle recording `--fps` options without losing the requested path or intended pinned page. Keep conservative start/ref protection for older supported natives without claiming a page replacement; FPS-only restarts keep refs (#169).
|
|
9
|
+
- Show positive native WebMCP availability in navigation summaries and distinguish current recording dependency checks from older deferred failures (#169).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Recommend `agent-browser` 0.37.0 while keeping the stable 0.35.0 minimum, no upper cap and native-owned recording/tab setup.
|
|
14
|
+
|
|
15
|
+
### Known limitations
|
|
16
|
+
|
|
17
|
+
- Native 0.37 short/cold recordings on Ubuntu can still fail or produce a shorter clip. This release does not change the upstream recording engine.
|
|
18
|
+
|
|
3
19
|
## 0.6.9 - 2026-09-08
|
|
4
20
|
|
|
5
21
|
### Fixed
|
package/README.md
CHANGED
|
@@ -82,13 +82,13 @@ The result is optimized for agent work:
|
|
|
82
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 require a unique role/name in both the latest snapshot and the live candidates. Duplicate-name refs pass through without a probe: their old ordinal cannot prove which element received the click. 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`; `observed: false` explicitly marks dispatch-only mutation summaries and adds a visible `Action dispatched; application change unverified` warning. 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
83
|
| 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` |
|
|
84
84
|
| 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` |
|
|
85
|
-
| 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
|
|
86
|
-
| Upstream CLI drift can silently invalidate wrapper behavior | Publishes a repo-readable command reference, verifies it against the recommended 0.
|
|
87
|
-
| Pages can expose structured workflows through experimental WebMCP |
|
|
85
|
+
| 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` |
|
|
86
|
+
| Upstream CLI drift can silently invalidate wrapper behavior | Publishes a repo-readable command reference, verifies it against the recommended 0.37.0 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` |
|
|
87
|
+
| 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` |
|
|
88
88
|
| 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) |
|
|
89
89
|
| 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` |
|
|
90
90
|
|
|
91
|
-
Artifact results show known requested paths separately from reported/resolved locations and infer image MIME types from file headers, not suffixes. Parent-directory failures return path-specific `validation-error` guidance before browser dispatch. Use absolute artifact paths in raw batch strings because the daemon's working directory may differ from Pi's. Dispatched `record start` and URL-bearing `record restart` attempts also return one fresh-snapshot warning on success or failure, including in JSON output; unreached rows do not
|
|
91
|
+
Artifact results show known requested paths separately from reported/resolved locations and infer image MIME types from file headers, not suffixes. Parent-directory failures return path-specific `validation-error` guidance before browser dispatch. Use absolute artifact paths in raw batch strings because the daemon's working directory may differ from Pi's. Dispatched `record start` and URL-bearing `record restart` attempts also return one fresh-snapshot warning on success or failure, including in JSON output. It describes conservative ref invalidation, not an observed page change; unreached rows do not emit it.
|
|
92
92
|
|
|
93
93
|
## Fastest way to try it
|
|
94
94
|
|
|
@@ -104,13 +104,13 @@ Optional external tools unlock the full command surface:
|
|
|
104
104
|
| Dependency | Required for | macOS install example |
|
|
105
105
|
| --- | --- | --- |
|
|
106
106
|
| `agent-browser` | All browser automation through this extension | See upstream install docs |
|
|
107
|
-
| `ffmpeg` |
|
|
107
|
+
| `ffmpeg` | Recording WebM (libvpx) or MP4 (libx264); install before `record start` / `record restart` | `brew install ffmpeg` or `brew install ffmpeg-full` |
|
|
108
108
|
|
|
109
|
-
Keep both binaries on `PATH`. This package recommends `agent-browser 0.
|
|
109
|
+
Keep both binaries on `PATH`. This package recommends `agent-browser 0.37.0` 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.
|
|
110
110
|
|
|
111
111
|
### Android / Termux
|
|
112
112
|
|
|
113
|
-
Android support currently uses Termux's system Chromium rather than Chrome for Testing. Upstream issue [vercel-labs/agent-browser#1587](https://github.com/vercel-labs/agent-browser/issues/1587) tracks native Android packaging; until upstream ships an Android launcher, install the packaged Linux-musl arm64 binary without lifecycle scripts and point the global command at it:
|
|
113
|
+
Android support currently uses Termux's system Chromium rather than Chrome for Testing. Upstream issue [vercel-labs/agent-browser#1587](https://github.com/vercel-labs/agent-browser/issues/1587) tracks native Android packaging; until upstream ships an Android launcher, install the packaged Linux-musl arm64 binary without lifecycle scripts and point the global command at it. The last locally validated Android setup used 0.36.0:
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
116
|
pkg install tur-repo x11-repo
|
|
@@ -186,7 +186,7 @@ npm run doctor
|
|
|
186
186
|
The doctor checks:
|
|
187
187
|
|
|
188
188
|
- upstream `agent-browser` exists on `PATH`
|
|
189
|
-
- the installed upstream is a stable version at or above the supported 0.35.0 floor; 0.
|
|
189
|
+
- the installed upstream is a stable version at or above the supported 0.35.0 floor; 0.37.0 remains the recommended baseline
|
|
190
190
|
- `pi --version` meets the minimum Pi runtime floor for this release; older Pi versions are setup failures
|
|
191
191
|
- Pi settings do not point at multiple active `pi-agent-browser-native` sources
|
|
192
192
|
|
|
@@ -387,7 +387,7 @@ Evaluate page JavaScript through stdin. Put the script in the top-level `stdin`
|
|
|
387
387
|
|
|
388
388
|
Use `outputPath` when `eval`, `get`, `snapshot`, or another extraction should be saved as a durable workspace file. Keep it distinct from screenshot, download, recording, and other browser artifact destinations; preflight rejects known same-call aliases before browser activity, and the result writer preserves the browser artifact if an alias becomes apparent only afterward. The wrapper writes `details.data` when present, otherwise the model-facing text content. When presentation compacted a large direct result, a result row, or the whole `batch`, it instead reads the full command-redacted pre-compaction payload only from the corresponding live wrapper-managed spill recorded in `details.artifactManifest`; if any required spill is unavailable or untrusted, the call fails without writing compact metadata to the requested path. `details.outputFile` reports the saved path and byte count. Explicit upstream `--json` content stays parseable; in that case the save notice lives only in `details.outputFile`.
|
|
389
389
|
|
|
390
|
-
Extract several known refs or selectors in one `batch` call instead of many serial getter calls:
|
|
390
|
+
Extract several known refs or selectors in one `batch` call instead of many serial getter calls. When a prior snapshot and session are available and the same-page freshness checks apply, ref-consuming calls add one extra `snapshot -i` preflight per top-level call or batch. Batching shares that probe across rows; it does not remove it:
|
|
391
391
|
|
|
392
392
|
```json
|
|
393
393
|
{ "args": ["batch"], "stdin": "[[\"get\",\"text\",\"@e64\"],[\"get\",\"text\",\"@e65\"]]" }
|
|
@@ -457,7 +457,7 @@ Typical pitfalls:
|
|
|
457
457
|
|
|
458
458
|
### Constrained browser jobs
|
|
459
459
|
|
|
460
|
-
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 URL plus a title after the URL is verified, 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, `inspect-current-page-after-timeout` when the target is already verified, or a fail-fast `verify-page-target-after-timeout` batch that runs `get url` before `snapshot -i` when the target is unknown (see [`docs/TOOL_CONTRACT.md#details`](docs/TOOL_CONTRACT.md#details)). There is no separate catalog of reusable named browser recipes above one-shot ad hoc `script`, `job`, `qa`, and raw `batch`; `script` has no names, registry, or persistent workflow state; 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.
|
|
460
|
+
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 URL plus a title after the URL is verified, 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, `inspect-current-page-after-timeout` when the target is already verified, or a fail-fast `verify-page-target-after-timeout` batch that runs `get url` before `snapshot -i` when the target is unknown (see [`docs/TOOL_CONTRACT.md#details`](docs/TOOL_CONTRACT.md#details)). Timeout recovery follows the effective batch source and native artifact operand positions, including literal dash-leading paths. Executable retries preserve the original row in a one-row batch. There is no separate catalog of reusable named browser recipes above one-shot ad hoc `script`, `job`, `qa`, and raw `batch`; `script` has no names, registry, or persistent workflow state; 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.
|
|
461
461
|
|
|
462
462
|
`assertText` accepts only `text`, not selector or locator fields. Clicks can stale later `@refs`; split the job and take a fresh snapshot before using them.
|
|
463
463
|
|
|
@@ -713,7 +713,7 @@ The opt-in real-upstream suite is separate because it drives a real browser inst
|
|
|
713
713
|
npm run verify -- real-upstream
|
|
714
714
|
```
|
|
715
715
|
|
|
716
|
-
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.
|
|
716
|
+
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.37.0 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).
|
|
717
717
|
|
|
718
718
|
A deterministic host-only live-browser wrapper smoke is available without an LLM choosing tool calls:
|
|
719
719
|
|
|
@@ -746,12 +746,12 @@ npm run verify -- release
|
|
|
746
746
|
5. Screenshots, downloads, recordings, traces, profiles, and spill files are normalized as Pi-visible artifacts where possible.
|
|
747
747
|
6. Generated playbook text in docs and tool metadata stays aligned with `extensions/agent-browser/lib/playbook.ts`.
|
|
748
748
|
|
|
749
|
-
The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it. The recommended baseline is 0.
|
|
749
|
+
The upstream browser engine remains [`agent-browser`](https://agent-browser.dev/). This package does not bundle it. The recommended baseline is 0.37.0 and the stable runtime floor is 0.35.0; newer stable versions are accepted without version-specific compatibility shims.
|
|
750
750
|
|
|
751
751
|
## Current limits
|
|
752
752
|
|
|
753
753
|
- Published pre-1.0 package.
|
|
754
|
-
- Recommends upstream `agent-browser` 0.
|
|
754
|
+
- Recommends upstream `agent-browser` 0.37.0 and accepts stable runtimes at or above 0.35.0.
|
|
755
755
|
- Does not bundle `agent-browser`; users install it separately.
|
|
756
756
|
- 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.
|
|
757
757
|
- 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.
|
|
@@ -6,7 +6,7 @@ import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./
|
|
|
6
6
|
import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
|
|
7
7
|
import { SessionPageState } from "./lib/session-page-state.js";
|
|
8
8
|
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, isPlainTextInspectionArgs, } from "./lib/runtime.js";
|
|
9
|
-
import { extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
|
|
9
|
+
import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
|
|
10
10
|
import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
|
|
11
11
|
import { needsManagedSession } from "./lib/command-policy.js";
|
|
12
12
|
import { ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
|
|
@@ -67,7 +67,7 @@ function getArtifactPreflightValidationError(options) {
|
|
|
67
67
|
return error;
|
|
68
68
|
const activeRecordingDestinations = new Set();
|
|
69
69
|
const cleanupOnly = steps.length > 0 && steps.every((step) => {
|
|
70
|
-
const [command, subcommand] =
|
|
70
|
+
const [command, subcommand] = step;
|
|
71
71
|
return isCloseCommand(command) || (command === "record" && subcommand === "stop");
|
|
72
72
|
});
|
|
73
73
|
for (const reservation of options.activeRecordingReservations ?? []) {
|
|
@@ -93,10 +93,9 @@ function getArtifactPreflightValidationError(options) {
|
|
|
93
93
|
}
|
|
94
94
|
const artifactDestinations = new Map();
|
|
95
95
|
let sawBatchClose = false;
|
|
96
|
-
for (const [index,
|
|
97
|
-
const commandStep = extractUpstreamCommandTokens(step);
|
|
96
|
+
for (const [index, commandStep] of steps.entries()) {
|
|
98
97
|
if (batch) {
|
|
99
|
-
const stepValidationError = validateToolArgs(
|
|
98
|
+
const stepValidationError = validateToolArgs(commandStep, { batchStep: true });
|
|
100
99
|
if (stepValidationError)
|
|
101
100
|
return `Unsupported batch step ${index + 1}: ${stepValidationError}`;
|
|
102
101
|
if (sawBatchClose && commandStep[0] === "record" && (commandStep[1] === "start" || commandStep[1] === "restart")) {
|
|
@@ -127,7 +126,7 @@ function getArtifactPreflightValidationError(options) {
|
|
|
127
126
|
}
|
|
128
127
|
artifactDestinations.set(canonicalDestination, index);
|
|
129
128
|
}
|
|
130
|
-
if (batch && commandStep[0] === "screenshot" &&
|
|
129
|
+
if (batch && commandStep[0] === "screenshot" && commandStep.includes("--annotate")) {
|
|
131
130
|
return [
|
|
132
131
|
`Unsupported batch screenshot annotation in step ${index + 1}: put --annotate in top-level args, not inside the batch step.`,
|
|
133
132
|
`Use: { "args": ["--annotate", "batch"], "stdin": "[[\\"screenshot\\",\\"/path/to/image.png\\"]]" }`,
|
|
@@ -138,7 +137,7 @@ function getArtifactPreflightValidationError(options) {
|
|
|
138
137
|
}
|
|
139
138
|
function commandClosesAllSessions(args, stdin) {
|
|
140
139
|
const parsed = getArtifactCommandSteps(args, stdin);
|
|
141
|
-
return !parsed.error && parsed.steps.some(
|
|
140
|
+
return !parsed.error && parsed.steps.some(isCloseAllCommand);
|
|
142
141
|
}
|
|
143
142
|
function commandTouchesArtifactLifecycle(args, stdin, outputPath) {
|
|
144
143
|
if (outputPath)
|
|
@@ -146,10 +145,7 @@ function commandTouchesArtifactLifecycle(args, stdin, outputPath) {
|
|
|
146
145
|
const parsed = getArtifactCommandSteps(args, stdin);
|
|
147
146
|
if (parsed.error)
|
|
148
147
|
return true;
|
|
149
|
-
return parsed.steps.some((step) =>
|
|
150
|
-
const commandStep = extractUpstreamCommandTokens(step);
|
|
151
|
-
return getExplicitArtifactDestination(commandStep) !== undefined || commandStep[0] === "record" || commandStep[0] === "screenshot" || isCloseCommand(commandStep[0]);
|
|
152
|
-
});
|
|
148
|
+
return parsed.steps.some((step) => getExplicitArtifactDestination(step) !== undefined || step[0] === "record" || step[0] === "screenshot" || isCloseCommand(step[0]));
|
|
153
149
|
}
|
|
154
150
|
function isResultFileArtifact(artifact) {
|
|
155
151
|
return isRecord(artifact)
|
|
@@ -299,12 +295,6 @@ function detailsReportCloseAllApplied(details, succeeded) {
|
|
|
299
295
|
|| (succeeded && isCloseAllCommand(extractUpstreamCommandTokens(args)))
|
|
300
296
|
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
301
297
|
}
|
|
302
|
-
function deleteIdentityKeysInNamespace(entries, namespace) {
|
|
303
|
-
for (const key of entries.keys()) {
|
|
304
|
-
if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
305
|
-
entries.delete(key);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
298
|
function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironment()) {
|
|
309
299
|
const autoConnectEnv = env.AGENT_BROWSER_AUTO_CONNECT;
|
|
310
300
|
return extractUpstreamCommandTokens(args)[0] === "connect"
|
|
@@ -663,9 +653,6 @@ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessi
|
|
|
663
653
|
}
|
|
664
654
|
}
|
|
665
655
|
}
|
|
666
|
-
async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs, attachedSessionKeys, onClosed) {
|
|
667
|
-
await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs, attachedSessionKeys, undefined, onClosed);
|
|
668
|
-
}
|
|
669
656
|
function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
|
|
670
657
|
const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
|
|
671
658
|
const offBranchRecords = new Map();
|
|
@@ -1308,7 +1295,7 @@ export default function agentBrowserExtension(pi) {
|
|
|
1308
1295
|
for (const identity of getCleanupResultsClosedManagedSessionIdentities(electronCleanupResults))
|
|
1309
1296
|
retireRecordingSession(identity.sessionName, identity.namespace);
|
|
1310
1297
|
if (quitting) {
|
|
1311
|
-
await
|
|
1298
|
+
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, undefined, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
|
|
1312
1299
|
}
|
|
1313
1300
|
else {
|
|
1314
1301
|
await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, managedSessionActive ? managedSessionNamespace : undefined, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
|
|
@@ -48,14 +48,13 @@ export function parseWaitCommandTokens(commandTokens) {
|
|
|
48
48
|
if (match)
|
|
49
49
|
return { subcommand: match.token };
|
|
50
50
|
}
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
const candidate = commandTokens[downloadPathIndex];
|
|
51
|
+
const downloadIndex = considered.findIndex((entry) => entry.token === "--download" || entry.token === "-d");
|
|
52
|
+
if (downloadIndex >= 0) {
|
|
53
|
+
const candidate = considered[downloadIndex + 1];
|
|
55
54
|
return {
|
|
56
|
-
downloadPath: candidate && !candidate.startsWith("--") ? candidate : undefined,
|
|
57
|
-
downloadPathIndex: candidate && !candidate.startsWith("--") ?
|
|
58
|
-
subcommand:
|
|
55
|
+
downloadPath: candidate && !candidate.token.startsWith("--") ? candidate.token : undefined,
|
|
56
|
+
downloadPathIndex: candidate && !candidate.token.startsWith("--") ? candidate.index : undefined,
|
|
57
|
+
subcommand: considered[downloadIndex].token,
|
|
59
58
|
};
|
|
60
59
|
}
|
|
61
60
|
return { subcommand: considered[0]?.token };
|
|
@@ -175,6 +175,12 @@ export function isAgentBrowserSessionIdentityKeyInNamespace(identityKey, namespa
|
|
|
175
175
|
const prefix = getAgentBrowserSessionIdentityKey("", namespace);
|
|
176
176
|
return prefix ? identityKey.startsWith(prefix) : !identityKey.includes("\0");
|
|
177
177
|
}
|
|
178
|
+
export function deleteIdentityKeysInNamespace(entries, namespace) {
|
|
179
|
+
for (const key of entries.keys()) {
|
|
180
|
+
if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
181
|
+
entries.delete(key);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
178
184
|
/** Mirror upstream global parsing: full argv, no `--` sentinel, and only global value payloads are skipped. */
|
|
179
185
|
export function scanUpstreamGlobalFlagOccurrences(args, targetFlag) {
|
|
180
186
|
const occurrences = [];
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
2
|
import { isCloseAllCommand, isCloseCommand } from "./command-taxonomy.js";
|
|
3
3
|
import { isRecord } from "./parsing.js";
|
|
4
|
-
function getRowBrowserLaunched(row) {
|
|
5
|
-
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
6
|
-
const lifecycle = isRecord(row.lifecycle) ? row.lifecycle : isRecord(result?.lifecycle) ? result.lifecycle : undefined;
|
|
7
|
-
const effectiveLaunch = isRecord(lifecycle?.effectiveLaunch) ? lifecycle.effectiveLaunch : undefined;
|
|
8
|
-
return typeof effectiveLaunch?.browserLaunched === "boolean" ? effectiveLaunch.browserLaunched : undefined;
|
|
9
|
-
}
|
|
10
4
|
export function batchHasSuccessfulCloseAll(data, fallbackCommands = []) {
|
|
11
5
|
if (!Array.isArray(data))
|
|
12
6
|
return false;
|
|
@@ -34,7 +28,10 @@ export function getSuccessfulBatchCloseLifecycle(rows, fallbackCommands = []) {
|
|
|
34
28
|
const rowCommand = Array.isArray(row.command) && row.command.every((token) => typeof token === "string")
|
|
35
29
|
? row.command
|
|
36
30
|
: fallbackCommands[index];
|
|
37
|
-
const
|
|
31
|
+
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
32
|
+
const lifecycle = isRecord(row.lifecycle) ? row.lifecycle : isRecord(result?.lifecycle) ? result.lifecycle : undefined;
|
|
33
|
+
const effectiveLaunch = isRecord(lifecycle?.effectiveLaunch) ? lifecycle.effectiveLaunch : undefined;
|
|
34
|
+
const browserLaunched = typeof effectiveLaunch?.browserLaunched === "boolean" ? effectiveLaunch.browserLaunched : undefined;
|
|
38
35
|
if (!rowCommand) {
|
|
39
36
|
if (sawClose && browserLaunched !== false) {
|
|
40
37
|
endsClosed = false;
|
|
@@ -49,7 +46,6 @@ export function getSuccessfulBatchCloseLifecycle(rows, fallbackCommands = []) {
|
|
|
49
46
|
endsClosed = true;
|
|
50
47
|
browserActiveAfterClose = false;
|
|
51
48
|
recordingClosedAfterBatch = true;
|
|
52
|
-
const result = isRecord(row.result) ? row.result : isRecord(row.data) ? row.data : undefined;
|
|
53
49
|
statePath = typeof result?.statePath === "string" ? result.statePath : undefined;
|
|
54
50
|
}
|
|
55
51
|
else if (sawClose && command === "record") {
|
|
@@ -316,13 +316,26 @@ export function isSessionTabPinningExcludedCommand(command) {
|
|
|
316
316
|
export function isSessionTabPostCommandCorrectionExcludedCommand(command) {
|
|
317
317
|
return hasCommandCapability(command, "excludedFromPostCommandCorrection");
|
|
318
318
|
}
|
|
319
|
-
|
|
319
|
+
export function getRecordCommandOperands(tokens) {
|
|
320
|
+
if (tokens[0] !== "record" || !["start", "restart"].includes(tokens[1] ?? ""))
|
|
321
|
+
return {};
|
|
322
|
+
const operands = [];
|
|
323
|
+
for (let index = 2; index < tokens.length && operands.length < 2; index += 1) {
|
|
324
|
+
// Native validates the range; bare/non-numeric --fps keeps its old literal meaning.
|
|
325
|
+
if (tokens[index] === "--fps" && /^\+?\d+$/.test(tokens[index + 1] ?? ""))
|
|
326
|
+
index += 1;
|
|
327
|
+
else
|
|
328
|
+
operands.push(tokens[index]);
|
|
329
|
+
}
|
|
330
|
+
return operands.length > 0 ? { path: operands[0], url: operands[1] } : { path: tokens[2], url: tokens[3] };
|
|
331
|
+
}
|
|
332
|
+
/** Starts conservatively invalidate refs because older supported natives replace the page, even on failure. Restarts invalidate only when they have a URL. */
|
|
320
333
|
export function isRecordPageTransitionCommand(tokens) {
|
|
321
334
|
if (tokens[0] !== "record")
|
|
322
335
|
return false;
|
|
323
336
|
if (tokens[1] === "start")
|
|
324
337
|
return true;
|
|
325
|
-
return tokens[1] === "restart" && tokens.
|
|
338
|
+
return tokens[1] === "restart" && getRecordCommandOperands(tokens).url !== undefined;
|
|
326
339
|
}
|
|
327
340
|
export function isWebMcpPageMutationCommand(tokens) {
|
|
328
341
|
return isWebMcpPageMutation(tokens[0], tokens[1]);
|
|
@@ -104,7 +104,7 @@ export function agentBrowserExplicitConfigIsPresent(parentEnv = getAgentBrowserP
|
|
|
104
104
|
return hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV);
|
|
105
105
|
}
|
|
106
106
|
/** Caller-selected upstream config disables the wrapper's automatic restore injection without blocking that config. */
|
|
107
|
-
export function agentBrowserConfigBlocksManagedRestore(
|
|
107
|
+
export function agentBrowserConfigBlocksManagedRestore(parentEnv = getAgentBrowserProcessEnvironment(), args = [], platform = process.platform) {
|
|
108
108
|
return !resolveManagedSessionRestoreHome(parentEnv, platform) || agentBrowserExplicitConfigIsPresent(parentEnv, args);
|
|
109
109
|
}
|
|
110
110
|
function omitWrapperInjectedUserAgent(args, enabled) {
|
|
@@ -151,7 +151,7 @@ function isManagedSessionRestoreIncompatible(options, namespace = extractExplici
|
|
|
151
151
|
const args = omitWrapperInjectedUserAgent(options.args, options.wrapperInjectedUserAgent);
|
|
152
152
|
if (options.cwd && !hasManagedSessionRestoreProjectIdentity(options.cwd))
|
|
153
153
|
return true;
|
|
154
|
-
if (options.cwd && agentBrowserConfigBlocksManagedRestore(
|
|
154
|
+
if (options.cwd && agentBrowserConfigBlocksManagedRestore(effectiveEnv, args))
|
|
155
155
|
return true;
|
|
156
156
|
return !ensureManagedSessionRestoreStorageIsSecure(effectiveEnv, process.platform, namespace);
|
|
157
157
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { chmodSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, isAbsolute, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { directoryContainsSymlink, ensureManagedSessionRestoreStorageIsSecure, ensureOwnerOnlyDirectory, getManagedRestoreSessionsDirectory, isManagedSessionRestoreKey, resolveManagedSessionRestoreCheckoutRoot, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
|
|
5
5
|
const OWNED_RESTORE_SNAPSHOT_FAMILIES_TO_KEEP = 2;
|
|
6
6
|
const OWNED_RESTORE_SNAPSHOT_MAX_RECORDS = 256;
|
|
7
7
|
const OWNED_RESTORE_SNAPSHOT_RECORD_MAX_BYTES = 16 * 1_024;
|
|
@@ -286,10 +286,8 @@ function scanOwnedSnapshots(options) {
|
|
|
286
286
|
export function pruneOwnedManagedSessionRestoreSnapshots(options) {
|
|
287
287
|
const parentEnv = options.parentEnv ?? process.env;
|
|
288
288
|
const platform = options.platform ?? process.platform;
|
|
289
|
-
const restoreKey = options.restoreKey
|
|
290
|
-
|
|
291
|
-
: isManagedSessionRestoreKey(options.restoreKey) ? options.restoreKey : undefined;
|
|
292
|
-
if (!restoreKey)
|
|
289
|
+
const restoreKey = options.restoreKey;
|
|
290
|
+
if (!isManagedSessionRestoreKey(restoreKey))
|
|
293
291
|
return 0;
|
|
294
292
|
const home = resolveManagedSessionRestoreHome(parentEnv, platform);
|
|
295
293
|
if (!home)
|
|
@@ -2,8 +2,7 @@ import { lstatSync, readlinkSync, realpathSync, statSync } from "node:fs";
|
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { foldAgentBrowserFilesystemIdentity } from "../../argv-grammar.js";
|
|
4
4
|
import { parseWaitCommandTokens } from "../../argv-descriptor.js";
|
|
5
|
-
|
|
6
|
-
const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
|
|
5
|
+
import { getRecordCommandOperands } from "../../command-taxonomy.js";
|
|
7
6
|
const SCREENSHOT_IMAGE_EXTENSIONS = [".jpeg", ".jpg", ".png", ".webp"];
|
|
8
7
|
function isSingleScreenshotPathToken(token) {
|
|
9
8
|
const explicitlyRelative = token.startsWith("./") || token.startsWith("../");
|
|
@@ -17,11 +16,7 @@ function getScreenshotPositionalIndices(commandTokens) {
|
|
|
17
16
|
const positionalIndices = [];
|
|
18
17
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
19
18
|
const token = commandTokens[index];
|
|
20
|
-
if (
|
|
21
|
-
index += 1;
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
if (SCREENSHOT_BOOLEAN_FLAGS.has(token))
|
|
19
|
+
if (token === "--full" || token === "-f")
|
|
25
20
|
continue;
|
|
26
21
|
positionalIndices.push(index);
|
|
27
22
|
}
|
|
@@ -54,9 +49,6 @@ function getDiffScreenshotOutputPath(commandTokens) {
|
|
|
54
49
|
}
|
|
55
50
|
return outputPath;
|
|
56
51
|
}
|
|
57
|
-
function foldArtifactPath(path, platform) {
|
|
58
|
-
return foldAgentBrowserFilesystemIdentity(path, platform);
|
|
59
|
-
}
|
|
60
52
|
function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
61
53
|
let cursor = absolutePath;
|
|
62
54
|
const suffix = [];
|
|
@@ -71,7 +63,7 @@ function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
|
71
63
|
catch {
|
|
72
64
|
// The destination does not exist yet; canonical ancestry still catches aliases.
|
|
73
65
|
}
|
|
74
|
-
return
|
|
66
|
+
return foldAgentBrowserFilesystemIdentity(canonicalPath, platform);
|
|
75
67
|
}
|
|
76
68
|
catch {
|
|
77
69
|
let symlinkTarget;
|
|
@@ -90,7 +82,7 @@ function canonicalizeArtifactPath(absolutePath, platform, seenSymlinks) {
|
|
|
90
82
|
}
|
|
91
83
|
const parent = dirname(cursor);
|
|
92
84
|
if (parent === cursor)
|
|
93
|
-
return
|
|
85
|
+
return foldAgentBrowserFilesystemIdentity(absolutePath, platform);
|
|
94
86
|
suffix.unshift(basename(cursor));
|
|
95
87
|
cursor = parent;
|
|
96
88
|
}
|
|
@@ -120,7 +112,7 @@ export function getExplicitArtifactDestination(commandTokens) {
|
|
|
120
112
|
return commandTokens[3];
|
|
121
113
|
if ((command === "trace" || command === "profiler") && subcommand === "stop")
|
|
122
114
|
return commandTokens[2];
|
|
123
|
-
if (command === "record"
|
|
124
|
-
return commandTokens
|
|
115
|
+
if (command === "record")
|
|
116
|
+
return getRecordCommandOperands(commandTokens).path;
|
|
125
117
|
return undefined;
|
|
126
118
|
}
|
|
@@ -7,11 +7,11 @@ import { formatSessionArtifactRetentionSummary } from "../../results/artifact-ma
|
|
|
7
7
|
import { buildInspectOverlayStateAction, buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
8
8
|
import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget } from "../../results/selector-recovery.js";
|
|
9
9
|
import { extractRefSnapshotFromData, isAboutBlankUrl, normalizeComparableUrl } from "../../session-page-state.js";
|
|
10
|
-
import {
|
|
10
|
+
import { redactInvocationArgs, redactSensitiveText } from "../../runtime.js";
|
|
11
11
|
import { isRecord } from "../../parsing.js";
|
|
12
12
|
import { extractBatchResultCommand, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, runSessionCommandData, } from "./session-state.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
13
|
+
import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
|
|
14
|
+
import { getExplicitArtifactDestination } from "./artifact-paths.js";
|
|
15
15
|
const ELECTRON_FILL_VERIFICATION_TIMEOUT_MS = 2_000;
|
|
16
16
|
export function sleepMs(ms) {
|
|
17
17
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -227,7 +227,7 @@ export async function collectRecordingDependencyWarning(options) {
|
|
|
227
227
|
return undefined;
|
|
228
228
|
if (await executableExistsOnPath("ffmpeg"))
|
|
229
229
|
return undefined;
|
|
230
|
-
return { command: recordCommand, dependency: "ffmpeg", message: `${recordCommand}
|
|
230
|
+
return { command: recordCommand, dependency: "ffmpeg", message: `${recordCommand} reported a pending recording, but ffmpeg is not on PATH. Its output is unverified; install ffmpeg before starting a new recording.`, reason: "ffmpeg-missing-for-recording", recommendations: ["Install ffmpeg before recording; on macOS with Homebrew, brew install ffmpeg or brew install ffmpeg-full.", "Stop this recording, check the result, and start a new recording after ensuring Pi can find ffmpeg on PATH."] };
|
|
231
231
|
}
|
|
232
232
|
export function formatRecordingDependencyWarningText(warning) {
|
|
233
233
|
if (!warning)
|
|
@@ -711,12 +711,10 @@ export async function collectElectronHandoff(options) {
|
|
|
711
711
|
}
|
|
712
712
|
return { handoff: "snapshot", refSnapshot, snapshot, ...(snapshotRetryCount > 0 ? { snapshotRetryCount } : {}), tabs };
|
|
713
713
|
}
|
|
714
|
-
function getTimeoutProgressSteps(compiledJob,
|
|
714
|
+
function getTimeoutProgressSteps(compiledJob, commandTokens, stdin) {
|
|
715
715
|
if (compiledJob)
|
|
716
716
|
return compiledJob.steps.map((step, index) => ({ args: step.args, generatedFrom: step.generatedFrom, index: index + 1 }));
|
|
717
|
-
|
|
718
|
-
return [];
|
|
719
|
-
return parseValidBatchStepEntries(stdin).map(({ index, step }) => ({ args: step, index: index + 1 }));
|
|
717
|
+
return getUpstreamEffectiveBatchSteps(commandTokens, stdin).map((args, index) => ({ args, index: index + 1 }));
|
|
720
718
|
}
|
|
721
719
|
function getLastPositionalToken(args, startIndex = 1) {
|
|
722
720
|
for (let index = args.length - 1; index >= startIndex; index -= 1) {
|
|
@@ -726,20 +724,8 @@ function getLastPositionalToken(args, startIndex = 1) {
|
|
|
726
724
|
}
|
|
727
725
|
return undefined;
|
|
728
726
|
}
|
|
729
|
-
function getTimeoutStepArtifactPath(
|
|
730
|
-
|
|
731
|
-
const [command] = commandArgs;
|
|
732
|
-
if (command === "screenshot") {
|
|
733
|
-
const index = getScreenshotPathTokenIndex(commandArgs);
|
|
734
|
-
return index === undefined ? undefined : commandArgs[index];
|
|
735
|
-
}
|
|
736
|
-
if (command === "pdf")
|
|
737
|
-
return getLastPositionalToken(commandArgs);
|
|
738
|
-
if (command === "download")
|
|
739
|
-
return getLastPositionalToken(commandArgs, 2);
|
|
740
|
-
if (command === "wait")
|
|
741
|
-
return parseWaitCommandTokens(commandArgs).downloadPath;
|
|
742
|
-
return undefined;
|
|
727
|
+
function getTimeoutStepArtifactPath(commandTokens) {
|
|
728
|
+
return ["screenshot", "pdf", "download", "wait"].includes(commandTokens[0]) ? getExplicitArtifactDestination(commandTokens) : undefined;
|
|
743
729
|
}
|
|
744
730
|
async function statTimeoutArtifactPath(absolutePath) {
|
|
745
731
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -795,7 +781,7 @@ const TIMEOUT_RETRYABLE_COMMANDS = new Set([
|
|
|
795
781
|
]);
|
|
796
782
|
function getTimeoutStepRetry(step) {
|
|
797
783
|
const command = step.args[0];
|
|
798
|
-
return command && TIMEOUT_RETRYABLE_COMMANDS.has(command) ? { args: step.args } : undefined;
|
|
784
|
+
return command && TIMEOUT_RETRYABLE_COMMANDS.has(command) ? { args: ["batch"], stdin: JSON.stringify([step.args]) } : undefined;
|
|
799
785
|
}
|
|
800
786
|
function normalizeUrlForTimeoutComparison(url) {
|
|
801
787
|
if (!url)
|
|
@@ -873,7 +859,7 @@ function buildTimeoutProgressSteps(options) {
|
|
|
873
859
|
};
|
|
874
860
|
}
|
|
875
861
|
export async function collectTimeoutPartialProgress(options) {
|
|
876
|
-
const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.
|
|
862
|
+
const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.commandTokens, options.stdin);
|
|
877
863
|
const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
|
|
878
864
|
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
879
865
|
const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
@@ -930,7 +916,7 @@ export function formatTimeoutPartialProgressText(progress, pageTargetUnknown = f
|
|
|
930
916
|
lines.push(`- ... ${progress.steps.length - shownSteps.length} more step${progress.steps.length - shownSteps.length === 1 ? "" : "s"} omitted`);
|
|
931
917
|
}
|
|
932
918
|
if (progress.retryStep?.retry?.args) {
|
|
933
|
-
const payload = JSON.stringify({
|
|
919
|
+
const payload = JSON.stringify({ ...progress.retryStep.retry, stdin: JSON.stringify([redactInvocationArgs(progress.retryStep.args)]) });
|
|
934
920
|
lines.push(pageTargetUnknown
|
|
935
921
|
? `Retry candidate for step ${progress.retryStep.index}: ${payload}. Verify the current URL before running it.`
|
|
936
922
|
: `Retry failed step: ${payload}`);
|
|
@@ -205,7 +205,7 @@ export async function prepareFinalResultRecoveryState(options) {
|
|
|
205
205
|
return { categoryDetails, currentRefSnapshot, currentRefSnapshotInvalidation, noActivePageSnapshotFailure, richInputRecoveryDiagnostic, visibleRefFallbackDiagnostic, visibleRefFallbackSessionName };
|
|
206
206
|
}
|
|
207
207
|
function buildTimeoutPartialProgressNextActions(options) {
|
|
208
|
-
const
|
|
208
|
+
const retry = options.timeoutPartialProgress?.retryStep?.retry;
|
|
209
209
|
const stepIndex = options.timeoutPartialProgress?.retryStep?.index;
|
|
210
210
|
const freshSessionAbandoned = options.sessionMode === "fresh" && options.timeoutPartialProgress?.liveUrlRecovered !== true;
|
|
211
211
|
if (options.currentSessionTabTargetUnknown && !freshSessionAbandoned && options.executionPlan.sessionName) {
|
|
@@ -220,12 +220,12 @@ function buildTimeoutPartialProgressNextActions(options) {
|
|
|
220
220
|
tool: "agent_browser",
|
|
221
221
|
}];
|
|
222
222
|
}
|
|
223
|
-
if (
|
|
223
|
+
if (retry) {
|
|
224
224
|
return [{
|
|
225
225
|
id: "retry-timeout-step",
|
|
226
226
|
params: freshSessionAbandoned
|
|
227
|
-
? {
|
|
228
|
-
: { args: withOptionalSessionArgs(options.executionPlan.sessionName,
|
|
227
|
+
? { ...retry, sessionMode: "fresh" }
|
|
228
|
+
: { ...retry, args: withOptionalSessionArgs(options.executionPlan.sessionName, retry.args) },
|
|
229
229
|
reason: freshSessionAbandoned
|
|
230
230
|
? `Retry the first incomplete timed-out step${stepIndex === undefined ? "" : ` ${stepIndex}`} in a fresh browser session because the timed-out fresh session was not proven live.`
|
|
231
231
|
: `Retry the first incomplete timed-out step${stepIndex === undefined ? "" : ` ${stepIndex}`} against the current browser session.`,
|