pi-agent-browser-native 0.2.62 → 0.2.63

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 CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.2.63 - 2026-06-26
6
+
7
+ ### Changed
8
+
9
+ - Rebaselined upstream capability metadata, command reference, support docs, and real-upstream output-shape metadata for `agent-browser` `0.31.1` / vercel-labs/agent-browser@ed2e10598c9064aecfaeb7cf21b540684db4be2c.
10
+ - Recorded upstream's React renderer bugfix for `react tree`, `react inspect`, and `react suspense`; no wrapper CLI/schema/runtime compatibility change was needed.
11
+ - Isolated the real-upstream `wait --download` contract's browser download directory under the test temp root so upstream's known saveAs limitation no longer spills fixture files into `~/Downloads`.
12
+ - Made Windows browser-dogfood platform smoke fail fast instead of hanging silently by bounding `agent-browser` prewarm commands, killing timed-out process trees, and printing per-suite progress for single-suite runs.
13
+ - Hardened Windows platform-smoke doctor cleanup so disposable probe stop failures fail the doctor instead of leaking Crabbox VMs/leases.
14
+ - Removed a redundant dogfood `domcontentloaded` wait that could race after a successful file-page open on Windows.
15
+
16
+ ### Validation
17
+
18
+ - Ran `npm run docs -- command-reference check`, `npm run verify -- command-reference`, `npm run verify -- real-upstream`, `npm run verify -- dogfood`, `npm run smoke:platform:ubuntu-image`, `npm run verify -- release`, `npm publish --dry-run`, and `git diff --check`.
19
+ - Used subagent and intercom review to confirm 0.31.1 changes are limited to upstream React renderer selection plus version/changelog metadata.
20
+
5
21
  ## 0.2.62 - 2026-06-26
6
22
 
7
23
  ### Changed
@@ -53,11 +53,11 @@ export function reorderWindowsLeadingGlobalArgs(args) {
53
53
  return args;
54
54
  return [args[index], ...leadingGlobals, ...args.slice(index + 1)];
55
55
  }
56
- function buildAgentBrowserSpawnCommand(args) {
57
- if (processPlatform !== "win32") {
56
+ export function buildAgentBrowserSpawnCommand(args, platform = processPlatform) {
57
+ if (platform !== "win32") {
58
58
  return { command: "agent-browser", args };
59
59
  }
60
- const commandLine = ["&", "agent-browser", ...reorderWindowsLeadingGlobalArgs(args).map(quoteWindowsPowerShellArg)].join(" ");
60
+ const commandLine = ["&", "agent-browser.cmd", ...reorderWindowsLeadingGlobalArgs(args).map(quoteWindowsPowerShellArg)].join(" ");
61
61
  return { command: "powershell.exe", args: ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", commandLine] };
62
62
  }
63
63
  function terminateSpawnedChild(child, signal) {
@@ -18,13 +18,17 @@ This project intentionally blocks normal `agent-browser` bash usage in most agen
18
18
 
19
19
  <!-- agent-browser-capability-baseline:start upstream-baseline -->
20
20
  <!-- Generated from scripts/agent-browser-capability-baseline.mjs. Run `npm run docs -- command-reference write` to update. Do not edit manually. -->
21
- This reference is baselined to the locally installed `agent-browser 0.31.0` command/help surface, audited against vercel-labs/agent-browser@5acf7f9dc153cd51dcc6c79e1aebb09c83b2261d. Upstream `agent-browser` remains the source of truth for command semantics; this file is the local fallback for Pi agent sessions where direct binary help is blocked or discouraged.
21
+ This reference is baselined to the locally installed `agent-browser 0.31.1` command/help surface, audited against vercel-labs/agent-browser@ed2e10598c9064aecfaeb7cf21b540684db4be2c. Upstream `agent-browser` remains the source of truth for command semantics; this file is the local fallback for Pi agent sessions where direct binary help is blocked or discouraged.
22
22
 
23
23
  The lightweight drift check is `npm run verify -- command-reference`. Run it whenever the installed upstream `agent-browser` version changes or this reference is edited.
24
24
 
25
25
  Use `npm run benchmark:agent-browser` or `npm run verify -- benchmark` before and after agent-facing workflow abstractions to measure task success, tool calls, model-visible output size, stale-ref behavior, artifact success, failure-category coverage, and elapsed-time estimates.
26
26
  <!-- agent-browser-capability-baseline:end upstream-baseline -->
27
27
 
28
+ ### Upstream 0.31.1 rebaseline
29
+
30
+ The 0.31.1 rebaseline is a React bugfix release: `react tree`, `react inspect <id>`, and `react suspense` now pick the `react-dom` renderer with mounted fiber roots instead of hardcoding renderer id `1`. This fixes empty React trees on Next.js 16.3 / Turbopack / RSC pages. No CLI/help/schema surface changed, so the wrapper only updates baseline evidence and keeps the 0.31.0 restore/session handling below.
31
+
28
32
  ### Upstream 0.31.0 rebaseline
29
33
 
30
34
  The 0.31.0 rebaseline adds restore workflow and namespace/session lifecycle surfaces: `--restore [name]`, `--restore-save <policy>`, restore check flags, `--namespace <name>`, `session id`, and `session info`. The wrapper parses those globals, keeps `--namespace` before `--session`, carries namespace context through managed-session probes and state, and keeps `session id` / `session info` sessionless. Use `agent_browser` with `args: ["session", "id", "--scope", "worktree", "--prefix", "my-skill"]` to derive reusable session ids from inside Pi; use `--restore=<key>` when passing an explicit key that could be confused with a command word.
@@ -243,7 +247,7 @@ Use `batch --bail` when later steps should stop after the first failed command.
243
247
 
244
248
  For short constrained flows, use top-level `job` instead of hand-writing `batch` stdin. Supported job steps are `open`, `click`, `fill`, `type`, `select`, `wait`, `assertText`, `assertUrl`, `waitForDownload`, `snapshot`, and `screenshot`. `open` can include `loadState: "domcontentloaded" | "load" | "networkidle"` to insert a `wait --load …` row immediately after navigation before the next click/read step. `click` and `fill` accept either a stable `selector` or the same semantic locator fields as top-level `semanticAction` (`locator`, plus `role`/`name` or `value` as appropriate) and compile locator steps to upstream `find` argv. `type` focuses an optional selector, sends text through upstream keyboard typing, can insert `wait` rows via `delayMs` for human-paced input, and can append a final `press` key such as `Enter`; delayed typing is capped at 200 characters per step, and generated per-character rows are compacted in model-visible batch text while remaining available in `details.batchSteps`. `select` requires `selector` plus `value` or `values`, and compiles to upstream `select <selector> <value...>`. By default the wrapper compiles steps to upstream `batch --bail` so a failed setup/fill/assertion step stops later mutating clicks; set `failFast: false` only when you explicitly need continue-after-error diagnostics. The wrapper records `details.compiledJob.steps[]` plus `details.compiledJob.failFast`. There is still no separate first-class catalog of reusable named browser recipes above `job`, the `qa` preset, and raw `batch`; see [`ARCHITECTURE.md`](ARCHITECTURE.md#no-reusable-recipe-layer-yet) for the closed `RQ-0068` decision and revisit bar.
245
249
 
246
- **Job navigation is explicit.** A `click` step (or other navigation-prone interaction) does not prove the next page loaded. The wrapper does not auto-insert `assertUrl` or `assertText` after clicks inside `job`; add those steps yourself with the exact URL, a `*` / `**` glob-style URL pattern, or on-page text you expect, especially after forms, checkout, tabs, or submit buttons, before screenshots or later steps. Exact and glob-style `assertUrl` values compile to `wait --url` unchanged, including query strings and literal `?`; upstream `agent-browser 0.31.0` matches `*` / `**` patterns against the full active URL. Do not put a whole dynamic checkout into one long job: split around login, sorting/cart mutations, checkout navigation, and final evidence capture so refs and app state can be rechecked between phases.
250
+ **Job navigation is explicit.** A `click` step (or other navigation-prone interaction) does not prove the next page loaded. The wrapper does not auto-insert `assertUrl` or `assertText` after clicks inside `job`; add those steps yourself with the exact URL, a `*` / `**` glob-style URL pattern, or on-page text you expect, especially after forms, checkout, tabs, or submit buttons, before screenshots or later steps. Exact and glob-style `assertUrl` values compile to `wait --url` unchanged, including query strings and literal `?`; upstream `agent-browser 0.31.1` matches `*` / `**` patterns against the full active URL. Do not put a whole dynamic checkout into one long job: split around login, sorting/cart mutations, checkout navigation, and final evidence capture so refs and app state can be rechecked between phases.
247
251
 
248
252
  ```json
249
253
  {
@@ -369,7 +373,7 @@ Top-level `networkSourceLookup` does the same for failed browser requests. When
369
373
 
370
374
  Do not omit the load state value; use `wait --load <state>` with `load`, `domcontentloaded`, or `networkidle`.
371
375
 
372
- For desktop-host readiness, prefer condition waits over fixed sleeps. Use this ladder: `wait --text` / `wait --url` / `wait --fn` / `wait --load <state>` / `wait --download` when a real condition exists; after raw `connect`, run `tab list` → `tab t<N>` → condition wait or `snapshot -i`; after wrapper-owned `electron.launch`, use `electron.probe` / `electron.status` for launch health or target mismatch; use `qa.attached` when expected text or selector plus diagnostics can express the check. Upstream `agent-browser 0.31.0` supports `wait --url` glob forms such as `**/dashboard` against the full active URL. Fixed waits are a last resort: use explicit `--timeout` or top-level `timeoutMs` for legitimately slow waits, and treat a successful fixed-wait payload such as `"waited":"timeout"` as elapsed time only, not proof that the desktop host finished. Verify with an observed condition, fresh snapshot, or screenshot before continuing.
376
+ For desktop-host readiness, prefer condition waits over fixed sleeps. Use this ladder: `wait --text` / `wait --url` / `wait --fn` / `wait --load <state>` / `wait --download` when a real condition exists; after raw `connect`, run `tab list` → `tab t<N>` → condition wait or `snapshot -i`; after wrapper-owned `electron.launch`, use `electron.probe` / `electron.status` for launch health or target mismatch; use `qa.attached` when expected text or selector plus diagnostics can express the check. Upstream `agent-browser 0.31.1` supports `wait --url` glob forms such as `**/dashboard` against the full active URL. Fixed waits are a last resort: use explicit `--timeout` or top-level `timeoutMs` for legitimately slow waits, and treat a successful fixed-wait payload such as `"waited":"timeout"` as elapsed time only, not proof that the desktop host finished. Verify with an observed condition, fresh snapshot, or screenshot before continuing.
373
377
 
374
378
  Use `wait --download [path]` after an earlier action has already started a browser download, such as a dashboard export button that responds asynchronously:
375
379
 
@@ -899,14 +903,14 @@ Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGE
899
903
  <!-- agent-browser-capability-baseline:start capability-token-baseline -->
900
904
  <!-- Generated from scripts/agent-browser-capability-baseline.mjs. Run `npm run docs -- command-reference write` to update. Do not edit manually. -->
901
905
  <details>
902
- <summary>Generated verifier capability baseline for agent-browser 0.31.0</summary>
906
+ <summary>Generated verifier capability baseline for agent-browser 0.31.1</summary>
903
907
 
904
908
  This generated block is review data for maintainers. The human-authored reference sections above remain the readable command guide.
905
909
 
906
910
  #### Source evidence
907
911
  - repository: `vercel-labs/agent-browser`
908
- - upstream HEAD: `5acf7f9dc153cd51dcc6c79e1aebb09c83b2261d`
909
- - upstream package version: `0.31.0`
912
+ - upstream HEAD: `ed2e10598c9064aecfaeb7cf21b540684db4be2c`
913
+ - upstream package version: `0.31.1`
910
914
  - inspected: `agent-browser --version`
911
915
  - inspected: `agent-browser --help`
912
916
  - inspected: `selected agent-browser <command> --help output`
@@ -26,10 +26,10 @@ When upstream ships a new `agent-browser` or the inventory changes:
26
26
 
27
27
  ## Audit result
28
28
 
29
- - Target upstream: `agent-browser 0.31.0` (must match `CAPABILITY_BASELINE.targetVersion` in [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs)).
29
+ - Target upstream: `agent-browser 0.31.1` (must match `CAPABILITY_BASELINE.targetVersion` in [`scripts/agent-browser-capability-baseline.mjs`](../scripts/agent-browser-capability-baseline.mjs)).
30
30
  - Source of truth: `CAPABILITY_BASELINE.inventorySections` in the same file (stable `id` keys: `skills`, `core-commands`, `state-tabs-frames-dialogs`, `network-storage-artifacts-diagnostics`, `batch-auth-setup-ai`, `options-and-env`).
31
- - Status: command-surface supported for the current wrapper contract after the 2026-06-25 0.31.0 audit; 0.31.0 evidence covered the default local gate, command-reference sampling, runtime probes, namespace/session lifecycle review, and restore workflow parser support.
32
- - High-priority support gaps: 2026-05-26 audit found sessionless local commands and command-scoped value flags needed sharper wrapper handling; runtime/tests/docs now cover those paths. The 0.28.0 rebaseline added local `mcp` and `plugin` surfaces plus plugin-backed credential login; wrapper docs/tests mark `mcp` and known `plugin` commands sessionless, with no compatibility shim for older upstream releases. The 0.29.1 rebaseline added upstream `@agent-browser/sandbox` helper-package guidance and stricter `install --with-deps` failure semantics; no new wrapper runtime mode or bundled dependency was required. The 0.30.1 rebaseline fixed upstream `wait --url` glob matching, so constrained `job.assertUrl` delegates glob and exact patterns directly to `wait --url`. The 0.31.0 rebaseline adds restore workflow and namespace/session lifecycle surfaces (`--restore`, restore checks, `--namespace`, `session id`, and `session info`) without adding a wrapper compatibility layer. Prior upstream fixes for click reliability, frame-scoped selectors/waits, form-command fixes, daemon retry improvements, and glibc-pinned release artifacts remain thin passthrough. Remaining upstream-owned caveat: current help still mentions `wait <selector> --state hidden` / `detached` and `find ... uncheck`, but runtime probes show those advertised shapes still fail, so wrapper docs keep `wait --fn` predicates and direct `uncheck` passthrough guidance.
31
+ - Status: command-surface supported for the current wrapper contract after the 2026-06-26 0.31.1 audit; 0.31.1 evidence confirmed no CLI/help/schema drift from 0.31.0 beyond the version string.
32
+ - High-priority support gaps: 2026-05-26 audit found sessionless local commands and command-scoped value flags needed sharper wrapper handling; runtime/tests/docs now cover those paths. The 0.28.0 rebaseline added local `mcp` and `plugin` surfaces plus plugin-backed credential login; wrapper docs/tests mark `mcp` and known `plugin` commands sessionless, with no compatibility shim for older upstream releases. The 0.29.1 rebaseline added upstream `@agent-browser/sandbox` helper-package guidance and stricter `install --with-deps` failure semantics; no new wrapper runtime mode or bundled dependency was required. The 0.30.1 rebaseline fixed upstream `wait --url` glob matching, so constrained `job.assertUrl` delegates glob and exact patterns directly to `wait --url`. The 0.31.0 rebaseline adds restore workflow and namespace/session lifecycle surfaces (`--restore`, restore checks, `--namespace`, `session id`, and `session info`) without adding a wrapper compatibility layer. The 0.31.1 rebaseline fixes upstream React renderer selection for `react tree`, `react inspect`, and `react suspense`; no wrapper runtime change was required. Remaining upstream-owned caveat: current help still mentions `wait <selector> --state hidden` / `detached` and `find ... uncheck`, but runtime probes show those advertised shapes still fail, so wrapper docs keep `wait --fn` predicates and direct `uncheck` passthrough guidance.
33
33
  - Post-`v0.2.29` review state: commits `eb55320` through `86abbfb` add browser guidance/smoke coverage plus `RQ-0086` click-probe reduction, `RQ-0087` same-snapshot form fill batching, `RQ-0088` current-ref fallback on locator misses, `RQ-0089` direct-upstream click mutation investigation, and `RQ-0090` stop-boundary/artifact-path guidance. Verification gates below were rerun on 2026-05-18 after those tasks landed. Constrained `job` (`RQ-0064`), the lightweight `qa` preset (`RQ-0065`), the experimental `sourceLookup` helper (`RQ-0066`), the experimental `networkSourceLookup` helper (`RQ-0067`), optional Exa/Brave-backed `agent_browser_web_search` with Pi-scoped package config (`RQ-0121`), and agent recovery for search/profile configuration failures (`RQ-0122`) are implemented; see [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#job), [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#qa), [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#sourcelookup), [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#networksourcelookup), and [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#optional-companion-web-search). Reusable browser recipes (`RQ-0068`) are intentionally not adopted as a runtime surface; see [`ARCHITECTURE.md`](ARCHITECTURE.md#no-reusable-recipe-layer-yet).
34
34
 
35
35
  ## Open UX/reliability follow-ups from 2026-05-29 agent feedback
@@ -47,23 +47,24 @@ Current summary:
47
47
  | RQ-0129 | Upstream `agent-browser 0.29.1` rebaseline shipped; sandbox helpers are documented upstream package guidance, not a wrapper runtime. | [`docs/support-notes.md`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/docs/support-notes.md) |
48
48
  | RQ-0130 | Upstream `agent-browser 0.30.1` rebaseline shipped; `wait --url` glob support replaces the wrapper `job.assertUrl` predicate workaround. | [`docs/support-notes.md`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/docs/support-notes.md) |
49
49
  | RQ-0131 | Upstream `agent-browser 0.31.0` rebaseline shipped; restore workflow and namespace/session lifecycle globals are parsed, documented, and carried through wrapper-managed probes/state. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0310-rebaseline) |
50
+ | RQ-0132 | Upstream `agent-browser 0.31.1` rebaseline shipped; React renderer selection is upstream-fixed for `react tree`, `react inspect`, and `react suspense`, with no wrapper CLI/schema changes. | [`docs/COMMAND_REFERENCE.md`](COMMAND_REFERENCE.md#upstream-0311-rebaseline) |
50
51
 
51
52
  ## Verification evidence
52
53
 
53
- Re-run the gates below before each release; this table records what the closure audit exercised. Rows marked current for 0.31.0 were rerun on 2026-06-25; older 0.29.1/Pi 0.79.10 rows remain as prior release/platform evidence until the next full release gate refresh.
54
+ Re-run the gates below before each release; this table records what the closure audit exercised. Rows marked current for 0.31.1 were rerun on 2026-06-26; older 0.29.1/Pi 0.79.10 rows remain as prior release/platform evidence until the next full release gate refresh.
54
55
 
55
56
  | Gate | Evidence | Status |
56
57
  | --- | --- | --- |
57
- | Default local gate | `npm run verify` checks generated playbook drift, clean-builds generated `dist/`, runs `tsc --noEmit`, unit/fake tests, generated command-reference blocks, and live command-reference sampling. | **Current for 0.31.0:** pass on 2026-06-25 (`npm run verify`; unit/fake suite plus live command-reference sampling). **Prior release evidence for 0.29.1:** pass on 2026-06-21 inside `npm run verify -- release`; Pi 0.79.10 refresh passed on 2026-06-22 (`npm run verify`). |
58
+ | Default local gate | `npm run verify` checks generated playbook drift, clean-builds generated `dist/`, runs `tsc --noEmit`, unit/fake tests, generated command-reference blocks, and live command-reference sampling. | **Current for 0.31.1:** pass on 2026-06-26 (`npm run verify`; unit/fake suite plus live command-reference sampling). **Prior release evidence for 0.29.1:** pass on 2026-06-21 inside `npm run verify -- release`; Pi 0.79.10 refresh passed on 2026-06-22 (`npm run verify`). |
58
59
  | Pre-PR local gate | `npm run verify -- pre-pr` composes the default gate with package-content verification. Use before larger local handoffs or PR-ready claims when lifecycle/platform/live dogfood cost is not warranted. | Added 2026-06-10; orchestration is locked by `test/project-verify.test.ts` and does not change release mode. |
59
- | Real upstream contract | `npm run verify -- real-upstream` runs the localhost fixture matrix against the real installed `agent-browser` matching the baseline. | **Current for 0.31.0:** pass on 2026-06-25 (`npm run verify -- real-upstream`; localhost fixture matrix and plugin list probe passed against installed `agent-browser 0.31.0`). This pass depends on skipping immediate helper probes after CSS selector clicks that lack upstream href/navigation fields; enabling those probes reproduced the `get text #status` failure in the real-upstream fixture. |
60
- | Packaged Pi smoke | `npm run verify -- package-pi` validates package contents, loads the packaged `agent_browser` tool without requiring optional Brave config, and executes fake-upstream `--version`. | **Current for 0.29.1:** pass on 2026-06-21 as part of `npm run verify -- release` (`verify-package.mjs --smoke-pi`; packaged `agent_browser --version` invocation passed). **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run verify -- package-pi`). |
60
+ | Real upstream contract | `npm run verify -- real-upstream` runs the localhost fixture matrix against the real installed `agent-browser` matching the baseline. | **Current for 0.31.1:** pass on 2026-06-26 (`npm run verify -- real-upstream`; localhost fixture matrix and plugin list probe passed against installed `agent-browser 0.31.1`). This pass depends on skipping immediate helper probes after CSS selector clicks that lack upstream href/navigation fields; enabling those probes reproduced the `get text #status` failure in the real-upstream fixture. |
61
+ | Packaged Pi smoke | `npm run verify -- package-pi` validates package contents, loads the packaged `agent_browser` tool without requiring optional Brave config, and executes fake-upstream `--version`. | **Current for 0.31.1:** pass on 2026-06-26 as part of `npm run verify -- release` (`verify-package.mjs --smoke-pi`; packaged `agent_browser --version` invocation passed). **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run verify -- package-pi`). |
61
62
  | Startup profile | `npm run verify -- startup-profile --samples <n>` clean-builds generated `dist/`, records direct package entrypoint import/factory timing in fresh Node processes, and writes `.artifacts/startup-profile/latest.json`. It must not launch Pi, tmux, mise, npm, browsers, or `agent-browser`; full Pi TUI ready-prompt profiling is intentionally excluded after it proved too invasive for routine verification. Run this opt-in evidence when package layout, the compiled entrypoint, top-level imports, schema registration, or prompt/config startup logic changes. | **Current for compiled entrypoint:** pass on 2026-06-21 (`npm run verify -- startup-profile --samples 3`; direct compiled entrypoint import+factory median 47.3 ms, below the 250 ms budget). Full-Pi startup numbers from the unsafe tmux profiler are not accepted as ongoing release evidence. |
62
- | Deterministic dogfood smoke | `npm run verify -- dogfood` (`scripts/verify-agent-browser-dogfood.ts`) drives the native wrapper against a local file fixture through top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close with the real `agent-browser` on `PATH`. | **Current for 0.29.1:** pass on 2026-06-21 (`npm run verify -- dogfood`; `qa-url`, fresh/current opens, semantic click, job screenshot artifact verification, and close all passed). |
63
+ | Deterministic dogfood smoke | `npm run verify -- dogfood` (`scripts/verify-agent-browser-dogfood.ts`) drives the native wrapper against a local file fixture through top-level `qa`, `semanticAction`, constrained `job`, screenshot artifact verification, and session close with the real `agent-browser` on `PATH`. | **Current for 0.31.1:** pass on 2026-06-26 (`npm run verify -- dogfood`; `qa-url`, fresh/current opens, semantic click, URL follow-up, job screenshot artifact verification, and close all passed). |
63
64
  | Efficiency benchmark | `npm run verify -- benchmark` runs deterministic browser workflow accounting plus focused benchmark tests, including JSONL sampling fixtures and job/qa/sourceLookup/networkSourceLookup/Electron scenario coverage. | **Current:** pass on 2026-06-21 (`npm run verify -- benchmark`; 13/13 deterministic scenarios passed). |
64
- | Crabbox platform smoke | `npm run check:platform-smoke` syntax-checks the harness and cheap invariants. `npm run smoke:platform:ubuntu-image` builds the project-owned Linux image, `npm run smoke:platform:doctor` checks Crabbox 0.26.0+ and local target readiness, and `npm run smoke:platform:all` runs doctor first, then fast target-local `platform-build` (`npm run verify -- platform-target`, pack, clean Pi install) plus `browser-dogfood-smoke` on Crabbox `macos`, `ubuntu`, and `windows-native`; see [`platform-smoke.md`](platform-smoke.md). Target artifacts include Crabbox/provider/work-root metadata, and release review also checks provider-specific `crabbox list` commands for leftover leases/clones. | **Current for 0.29.1:** pass on 2026-06-21 inside `npm run verify -- release`; rebuilt Ubuntu image `pi-agent-browser-native-platform:node24-agent-browser0.29.1`, refreshed the Windows `crabbox-ready` template snapshot to `agent-browser 0.29.1`, doctor passed, then Crabbox platform smoke passed for macOS, Ubuntu, and native Windows. **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run check:platform-smoke`, `npm run smoke:platform:doctor`, `npm run smoke:platform:all`). |
65
- | `verify -- release` / `prepublishOnly` | `npm run verify -- release` chains the default gate with the configured-source lifecycle harness, packaged Pi smoke, and the release-blocking Crabbox platform matrix (`verifySteps` `release` in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs)). `package.json` `prepublishOnly` runs that compose before `npm pack --dry-run` during `npm publish`. It intentionally omits standalone real-upstream, host-only dogfood, and benchmark modes—see [`RELEASE.md`](RELEASE.md#pre-release-checks). | **Current for 0.29.1 / Pi 0.79.10:** pass on 2026-06-22 (`npm run verify -- release`), including default unit/fake gate, generated docs checks, live command-reference sampling, lifecycle harness, packaged Pi smoke, and macOS/Ubuntu/native-Windows Crabbox platform smoke. |
66
- | Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` before reload and `v2` after full relaunch because compiled JS package modules are process-cached), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.2`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | **Current for 0.29.1:** pass on 2026-06-21 as part of `npm run verify -- release`; managed browser session continuity and persisted full output verified before cleanup. **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run verify -- lifecycle`; managed browser session and persisted full output verified before cleanup). |
65
+ | Crabbox platform smoke | `npm run check:platform-smoke` syntax-checks the harness and cheap invariants. `npm run smoke:platform:ubuntu-image` builds the project-owned Linux image, `npm run smoke:platform:doctor` checks Crabbox 0.26.0+ and local target readiness, and `npm run smoke:platform:all` runs doctor first, then fast target-local `platform-build` (`npm run verify -- platform-target`, pack, clean Pi install) plus `browser-dogfood-smoke` on Crabbox `macos`, `ubuntu`, and `windows-native`; see [`platform-smoke.md`](platform-smoke.md). Target artifacts include Crabbox/provider/work-root metadata, and release review also checks provider-specific `crabbox list` commands for leftover leases/clones. | **Current for 0.31.1:** pass on 2026-06-26 inside `npm run verify -- release`; rebuilt Ubuntu image `pi-agent-browser-native-platform:node24-agent-browser0.31.1`, refreshed the Windows `crabbox-ready` template snapshot to `agent-browser 0.31.1`, doctor passed, then Crabbox platform smoke passed for macOS, Ubuntu, and native Windows. **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run check:platform-smoke`, `npm run smoke:platform:doctor`, `npm run smoke:platform:all`). |
66
+ | `verify -- release` / `prepublishOnly` | `npm run verify -- release` chains the default gate with the configured-source lifecycle harness, packaged Pi smoke, and the release-blocking Crabbox platform matrix (`verifySteps` `release` in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs)). `package.json` `prepublishOnly` runs that compose before `npm pack --dry-run` during `npm publish`. It intentionally omits standalone real-upstream, host-only dogfood, and benchmark modes—see [`RELEASE.md`](RELEASE.md#pre-release-checks). | **Current for 0.31.1:** pass on 2026-06-26 (`npm run verify -- release`), including default unit/fake gate, generated docs checks, live command-reference sampling, lifecycle harness, packaged Pi smoke, and macOS/Ubuntu/native-Windows Crabbox platform smoke. |
67
+ | Configured-source lifecycle | `npm run verify -- lifecycle` (`scripts/verify-lifecycle.mjs`) drives `/reload`, closes and relaunches Pi with the same exact `--session-id`, checks the JSONL session header id, session continuity, slash-command sentinel tokens (`v1` before reload and `v2` after full relaunch because compiled JS package modules are process-cached), persisted spill reachability, and real Pi `tool_result` failure-patch semantics for a QA reclassification with a fake upstream on `PATH`. Default Pi model is `zai/glm-5.2`; default per-step wait is **180000 ms** (`DEFAULT_TIMEOUT_MS`); override model with `--model <id>` and waits with `--timeout-ms <ms>`. Passthrough flags in [`scripts/project.mjs`](https://github.com/fitchmultz/pi-agent-browser-native/blob/main/scripts/project.mjs): `--keep-artifacts`, `--model`, `--verbose`, and `--timeout-ms` plus a value (for example `npm run verify -- lifecycle --model openai-codex/gpt-5.5:minimal --keep-artifacts --verbose --timeout-ms 600000`). | **Current for 0.31.1:** pass on 2026-06-26 as part of `npm run verify -- release`; managed browser session continuity and persisted full output verified before cleanup. **Pi 0.79.10 refresh:** pass on 2026-06-22 (`npm run verify -- lifecycle`; managed browser session and persisted full output verified before cleanup). |
67
68
  | Quick isolated Pi smoke | `pi --approve --no-extensions --no-skills -e . --tools agent_browser` from trusted repo root; native `agent_browser` only. | **Current for 0.29.1 / Pi 0.79.9:** pass on 2026-06-21 via tmux with `pi --approve --no-extensions --no-skills -e . --model openai-codex/gpt-5.5:minimal --tools agent_browser`. Covered the public Sauce Demo checkout-overview flow with clean context, native sorting/click/fill flow, screenshot and recording evidence, console/page-error/network diagnostics, and no order placement. A one-line screenshot-plus-recording close-guard smoke on `https://example.com` passed after rebuilding `dist/`, proving close succeeds after both artifact paths are verified. Temp artifacts and tmux sessions were cleaned after evidence capture. |
68
69
 
69
70
  Runtime floor note: package metadata keeps Pi core package peer ranges wildcard per installed Pi package docs, but `pi-agent-browser-doctor` / `npm run doctor` treats `pi --version` below 0.79.10 as a setup failure. This keeps package dependency shape aligned with Pi package loading while still making unsupported host Pi versions a release and first-run blocker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agent-browser-native",
3
- "version": "0.2.62",
3
+ "version": "0.2.63",
4
4
  "description": "pi extension that exposes agent-browser as a native tool for browser automation",
5
5
  "type": "module",
6
6
  "author": "Mitch Fultz (https://github.com/fitchmultz)",
@@ -14,8 +14,8 @@ export const COMMAND_REFERENCE_BASELINE_BLOCK_IDS = Object.freeze(["upstream-bas
14
14
 
15
15
  const sourceEvidence = Object.freeze({
16
16
  repository: "vercel-labs/agent-browser",
17
- upstreamHead: "5acf7f9dc153cd51dcc6c79e1aebb09c83b2261d",
18
- upstreamPackageVersion: "0.31.0",
17
+ upstreamHead: "ed2e10598c9064aecfaeb7cf21b540684db4be2c",
18
+ upstreamPackageVersion: "0.31.1",
19
19
  inspectedSources: Object.freeze([
20
20
  "agent-browser --version",
21
21
  "agent-browser --help",
@@ -774,7 +774,7 @@ const inventorySections = Object.freeze([
774
774
  ]);
775
775
 
776
776
  export const CAPABILITY_BASELINE = Object.freeze({
777
- targetVersion: "0.31.0",
777
+ targetVersion: "0.31.1",
778
778
  sourceEvidence,
779
779
  helpCommands,
780
780
  inventorySections,
@@ -16,14 +16,23 @@ function Write-Section($Name, $Path) {
16
16
  Write-Output "--- $Name END ---"
17
17
  }
18
18
 
19
- function Get-AgentBrowserVersion() {
20
- if (-not (Get-Command agent-browser -ErrorAction SilentlyContinue)) { return "" }
21
- return (& agent-browser --version 2>$null)
19
+ function Get-AgentBrowserCommandPath() {
20
+ foreach ($Name in @("agent-browser.cmd", "agent-browser.exe", "agent-browser")) {
21
+ $Command = Get-Command $Name -ErrorAction SilentlyContinue | Select-Object -First 1
22
+ if ($Command) { return $Command.Source }
23
+ }
24
+ return ""
25
+ }
26
+
27
+ function Get-AgentBrowserVersion($AgentBrowserPath) {
28
+ if (-not $AgentBrowserPath) { return "" }
29
+ return (& $AgentBrowserPath --version 2>$null)
22
30
  }
23
31
 
24
- function Test-AgentBrowser($Version) {
32
+ function Test-AgentBrowser($Version, $AgentBrowserPath) {
25
33
  $Expected = "agent-browser $Version"
26
- $Current = Get-AgentBrowserVersion
34
+ $Current = Get-AgentBrowserVersion $AgentBrowserPath
35
+ Write-Output "PLATFORM_AGENT_BROWSER_PATH=$AgentBrowserPath"
27
36
  Write-Output "PLATFORM_AGENT_BROWSER_VERSION=$Current"
28
37
  $script:AgentBrowserReadyExit = if ($Current -eq $Expected) { 0 } else { 1 }
29
38
  }
@@ -46,6 +55,34 @@ function Test-AgentBrowserBrowserCache() {
46
55
  $script:BrowserCacheExit = 1
47
56
  }
48
57
 
58
+ function Invoke-AgentBrowserWithTimeout($AgentBrowserPath, [string[]]$Arguments, [int]$TimeoutSeconds) {
59
+ $script:LastAgentBrowserCommandExit = 1
60
+ if (-not $script:AgentBrowserCommandCounter) { $script:AgentBrowserCommandCounter = 0 }
61
+ $script:AgentBrowserCommandCounter += 1
62
+ $OutPath = Join-Path $DogfoodDir ("agent-browser-{0}.stdout.txt" -f $script:AgentBrowserCommandCounter)
63
+ $ErrPath = Join-Path $DogfoodDir ("agent-browser-{0}.stderr.txt" -f $script:AgentBrowserCommandCounter)
64
+ $Process = Start-Process -FilePath $AgentBrowserPath -ArgumentList $Arguments -RedirectStandardOutput $OutPath -RedirectStandardError $ErrPath -PassThru -WindowStyle Hidden
65
+
66
+ $TimedOut = -not $Process.WaitForExit($TimeoutSeconds * 1000)
67
+ if ($TimedOut) {
68
+ & taskkill.exe /PID $Process.Id /T /F 2>$null | Out-Null
69
+ Write-Output "PLATFORM_AGENT_BROWSER_COMMAND_TIMEOUT=${TimeoutSeconds}s args=$($Arguments -join ' ')"
70
+ $script:LastAgentBrowserCommandExit = 124
71
+ }
72
+
73
+ $StdoutText = if (Test-Path $OutPath) { Get-Content -Raw $OutPath } else { "" }
74
+ $StderrText = if (Test-Path $ErrPath) { Get-Content -Raw $ErrPath } else { "" }
75
+ if (-not $TimedOut) {
76
+ if ($null -ne $Process.ExitCode) { $script:LastAgentBrowserCommandExit = $Process.ExitCode }
77
+ elseif ($StdoutText -match '"success"\s*:\s*true') { $script:LastAgentBrowserCommandExit = 0 }
78
+ else { $script:LastAgentBrowserCommandExit = 1 }
79
+ }
80
+
81
+ if ($StdoutText) { Write-Output $StdoutText }
82
+ if ($StderrText) { Write-Output $StderrText }
83
+ Write-Output "PLATFORM_AGENT_BROWSER_COMMAND_EXIT=$($script:LastAgentBrowserCommandExit)"
84
+ }
85
+
49
86
  Write-Output "Starting browser-dogfood-smoke in $SourceRoot at $((Get-Date).ToUniversalTime().ToString('o'))"
50
87
  Write-Output "PLATFORM_RUN_ROOT=$RunRoot"
51
88
  Write-Output "PLATFORM_DOGFOOD_ARTIFACT_DIR=$DogfoodArtifactDir"
@@ -57,8 +94,9 @@ Write-Output "PLATFORM_NODE_VERSION=$NodeVersion"
57
94
  $NpmCiExit = $LASTEXITCODE
58
95
  Write-Output "PLATFORM_NPM_CI_EXIT=$NpmCiExit"
59
96
 
97
+ $AgentBrowserPath = Get-AgentBrowserCommandPath
60
98
  $script:AgentBrowserReadyExit = 1
61
- Test-AgentBrowser $AgentBrowserVersion
99
+ Test-AgentBrowser $AgentBrowserVersion $AgentBrowserPath
62
100
  $AgentBrowserExit = $script:AgentBrowserReadyExit
63
101
  Write-Output "PLATFORM_AGENT_BROWSER_READY_EXIT=$AgentBrowserExit"
64
102
  $script:BrowserCacheExit = 1
@@ -72,10 +110,15 @@ if ($BrowserCacheExit -eq 0) {
72
110
  $PrewarmUrl = "file:///" + ($PrewarmPath -replace "\\", "/")
73
111
  for ($Attempt = 1; $Attempt -le 3; $Attempt++) {
74
112
  Write-Output "PLATFORM_AGENT_BROWSER_PREWARM_ATTEMPT=$Attempt"
75
- & agent-browser open --json --session "platform-smoke-prewarm-$Attempt" $PrewarmUrl 2>&1
76
- $BrowserPrewarmExit = $LASTEXITCODE
77
- & agent-browser close --json --session "platform-smoke-prewarm-$Attempt" 2>&1
78
- if ($BrowserPrewarmExit -eq 0) { break }
113
+ $PrewarmSession = "platform-smoke-prewarm-$Attempt"
114
+ Invoke-AgentBrowserWithTimeout $AgentBrowserPath @("open", "--json", "--session", $PrewarmSession, $PrewarmUrl) 45
115
+ $BrowserPrewarmExit = $script:LastAgentBrowserCommandExit
116
+ if ($BrowserPrewarmExit -eq 0) {
117
+ Invoke-AgentBrowserWithTimeout $AgentBrowserPath @("close", "--json", "--session", $PrewarmSession) 15
118
+ $CloseExit = $script:LastAgentBrowserCommandExit
119
+ Write-Output "PLATFORM_AGENT_BROWSER_PREWARM_CLOSE_EXIT=$CloseExit"
120
+ break
121
+ }
79
122
  Start-Sleep -Seconds 2
80
123
  }
81
124
  }
@@ -5,6 +5,7 @@ import { accessSync, constants, mkdirSync, unlinkSync, writeFileSync } from "nod
5
5
  import { resolve } from "node:path";
6
6
 
7
7
  import { CAPABILITY_BASELINE } from "../agent-browser-capability-baseline.mjs";
8
+ import { buildTargetBaseArgs } from "./crabbox-runner.mjs";
8
9
 
9
10
  const DEFAULT_UBUNTU_IMAGE = `pi-agent-browser-native-platform:node24-agent-browser${CAPABILITY_BASELINE.targetVersion}`;
10
11
 
@@ -155,6 +156,69 @@ function checkCrabboxProvider(cbox, args, label, failures) {
155
156
  }
156
157
  }
157
158
 
159
+ function crabbox(cbox, args, timeout = 300_000) {
160
+ try {
161
+ return {
162
+ ok: true,
163
+ stdout: execFileSync(cbox, args, { timeout, stdio: "pipe", env: { ...process.env, CRABBOX_SYNC_GIT_SEED: "false" } }).toString(),
164
+ stderr: "",
165
+ };
166
+ } catch (error) {
167
+ return {
168
+ ok: false,
169
+ stdout: error.stdout?.toString?.() ?? "",
170
+ stderr: error.stderr?.toString?.() ?? error.message,
171
+ };
172
+ }
173
+ }
174
+
175
+ function parseLeaseId(text) {
176
+ return text.match(/\bleased\s+(\S+)/)?.[1]
177
+ ?? text.match(/\blease=(\S+)/)?.[1]
178
+ ?? null;
179
+ }
180
+
181
+ export function disposableWindowsAgentBrowserProbe(cbox, config, expectedVersion) {
182
+ const slug = "piab-doctor-agent-browser";
183
+ const baseArgs = buildTargetBaseArgs("windows-native", config);
184
+ const warm = crabbox(cbox, ["warmup", ...baseArgs, "--slug", slug, "--keep", "--reclaim"], 300_000);
185
+ const leaseId = parseLeaseId(`${warm.stdout}\n${warm.stderr}`) ?? slug;
186
+ if (!warm.ok) return { ok: false, message: `warmup failed: ${(warm.stderr || warm.stdout).slice(-500)}` };
187
+
188
+ let outcome;
189
+ try {
190
+ const probeScript = `$ErrorActionPreference = "Stop"
191
+ $cmd = Get-Command "agent-browser.cmd" -ErrorAction SilentlyContinue | Select-Object -First 1
192
+ if (-not $cmd) { throw "agent-browser.cmd missing" }
193
+ $version = & $cmd.Source --version
194
+ Write-Output "PLATFORM_DOCTOR_AGENT_BROWSER_PATH=$($cmd.Source)"
195
+ Write-Output "PLATFORM_DOCTOR_AGENT_BROWSER_VERSION=$version"
196
+ $roots = @((Join-Path $env:USERPROFILE ".agent-browser\\browsers"), "C:\\WINDOWS\\system32\\config\\systemprofile\\.agent-browser\\browsers")
197
+ $chrome = Get-ChildItem -Path $roots -Recurse -Filter chrome.exe -ErrorAction SilentlyContinue | Select-Object -First 1
198
+ if (-not $chrome) { throw "agent-browser browser cache missing chrome.exe" }
199
+ Write-Output "PLATFORM_DOCTOR_AGENT_BROWSER_CHROME=$($chrome.FullName)"`;
200
+ const probeCommand = `powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${Buffer.from(probeScript, "utf16le").toString("base64")}`;
201
+ const run = crabbox(cbox, ["run", ...baseArgs, "--id", leaseId, "--no-sync", "--shell", probeCommand], 180_000);
202
+ if (!run.ok) outcome = { ok: false, message: `probe failed: ${(run.stderr || run.stdout).slice(-700)}` };
203
+ else {
204
+ const output = run.stdout;
205
+ const versionLine = output.match(/^PLATFORM_DOCTOR_AGENT_BROWSER_VERSION=(.*)$/m)?.[1]?.trim() ?? "";
206
+ const pathLine = output.match(/^PLATFORM_DOCTOR_AGENT_BROWSER_PATH=(.*)$/m)?.[1]?.trim() ?? "";
207
+ const chromeLine = output.match(/^PLATFORM_DOCTOR_AGENT_BROWSER_CHROME=(.*)$/m)?.[1]?.trim() ?? "";
208
+ if (versionLine !== `agent-browser ${expectedVersion}`) outcome = { ok: false, message: `expected agent-browser ${expectedVersion}, got ${versionLine || "missing version"}` };
209
+ else if (!pathLine || !chromeLine) outcome = { ok: false, message: "agent-browser path or browser cache marker missing" };
210
+ else outcome = { ok: true, message: `${versionLine} | ${pathLine} | ${chromeLine}` };
211
+ }
212
+ } finally {
213
+ const stop = crabbox(cbox, ["stop", ...baseArgs, "--id", leaseId], 90_000);
214
+ if (!stop.ok) {
215
+ const prior = outcome?.ok === false ? `; prior result: ${outcome.message}` : "";
216
+ outcome = { ok: false, message: `cleanup failed: ${(stop.stderr || stop.stdout).slice(-500)}${prior}` };
217
+ }
218
+ }
219
+ return outcome;
220
+ }
221
+
158
222
  function checkAgentBrowserVersion(expectedVersion, failures, command = "agent-browser") {
159
223
  const version = shell(`${command} --version`);
160
224
  if (!version) {
@@ -289,6 +353,9 @@ export async function runDoctor(config) {
289
353
  fail(`snapshot ${snapshot} not found on ${vmName}`, failures);
290
354
  }
291
355
  checkCrabboxProvider(cbox, ["--provider", "parallels", "--target", "windows", "--windows-mode", "normal", "--parallels-source", vmName, "--parallels-source-snapshot", snapshot, "--parallels-user", user, "--parallels-work-root", workRoot], "windows parallels", failures);
356
+ const agentBrowserProbe = disposableWindowsAgentBrowserProbe(cbox, { ...config, windowsParallels: { ...config?.windowsParallels, sourceVm: vmName, snapshot, user, workRoot } }, agentBrowserVersion);
357
+ if (agentBrowserProbe.ok) ok(`Windows disposable agent-browser: ${agentBrowserProbe.message}`);
358
+ else fail(`Windows disposable agent-browser probe failed: ${agentBrowserProbe.message}`, failures);
292
359
  }
293
360
  }
294
361
  } else {
@@ -497,8 +497,10 @@ export async function runTargetSuites(config, targetName, suiteNames) {
497
497
  try {
498
498
  let sync = true;
499
499
  for (const suiteName of suiteNames) {
500
+ console.log(` Suite: ${suiteName}`);
500
501
  const result = await runTargetSuite(config, targetName, suiteName, { ...lease, sync }, runId);
501
502
  results.push(result);
503
+ console.log(` ${result.ok ? "PASS" : "FAIL"} ${suiteName} on ${targetName}`);
502
504
  sync = false;
503
505
  if (!result.ok) break;
504
506
  }
@@ -123,7 +123,7 @@ export async function main(argv = process.argv.slice(2)) {
123
123
  }
124
124
 
125
125
  if (args.command === "run") {
126
- const { runTargetSuite, runTargetSuites } = await import("./platform-smoke/targets.mjs");
126
+ const { runTargetSuites } = await import("./platform-smoke/targets.mjs");
127
127
  const targets = args.target ? args.target.split(",").map((name) => name.trim()).filter(Boolean) : config.requiredTargets;
128
128
  const suites = args.suite ? [args.suite] : config.requiredSuites;
129
129
  const supportedTargets = config.supportedTargets ?? config.requiredTargets;
@@ -131,9 +131,7 @@ export async function main(argv = process.argv.slice(2)) {
131
131
  validateNames("suite", suites, config.requiredSuites);
132
132
  const runs = targets.map(async (targetName) => {
133
133
  console.log(`\n=== Target: ${targetName} ===`);
134
- const result = args.suite
135
- ? await runTargetSuite(config, targetName, suites[0])
136
- : await runTargetSuites(config, targetName, suites);
134
+ const result = await runTargetSuites(config, targetName, suites);
137
135
  return { targetName, result };
138
136
  });
139
137
  const results = await Promise.all(runs);