- Remove the per-call `save` option from `tab.screenshot()` to simplify usage.
- Update `tab.screenshot()` to return the saved file path as a promise string.
- Configure screenshot persistence to use daemon path or custom `browser.screenshotDir`.
- Add comprehensive tests verifying temp path return and custom directory saving.
- Added language-specific code formatters for JavaScript, Julia, Python, and Ruby to improve display rendering.
- Integrated display formatting into browser run and eval render tools while preserving verbatim execution.
- Added comprehensive test suites verifying formatting stability, lexical safety, and streaming behavior.
- Added `src/live/` subsystem with WebRTC transport, protocol parser, session controller, and headless Chromium audio injection.
- Added `LiveVisualizer` component with phase states, transcript display, and animated waveform rendering.
- Added `/live` slash command and `LiveCommandController` to toggle live voice sessions.
- Suppressed local TTS via `vocalizer.suspend()` during live mode to prevent audio conflicts.
- Added live-instructions and agent-final-message prompts for agent messaging context.
- Added `protocol.test.ts` covering event parsing, chunking, and context message construction.
The browser tool's open action only passed the requested timeout to acquireTab; acquireBrowser ran under the caller signal alone, so CDP discovery/connect could run through its own fixed 5s/30s waits past the requested deadline. A freshly-created browser also sat in the registry at refCount 0 during worker/surface acquisition: the worker-abort branch released it only on tempHold (never on the fresh refCount-0 case), orphaning the handle, and two different-name opens sharing one refCount-0 browser let a single failure dispose it out from under the survivor.
Compose one open deadline from the caller signal and params.timeout and thread it through both acquireBrowser and acquireTab; caller cancellation stays ToolAbortError, the requested timeout becomes a timeout ToolError. Hold one explicit registry lease across tab acquisition, released exactly once on the mutually-exclusive success/rollback paths, and make the worker-abort browser release mirror the error paths' refCount-0 check.
Fixes#6365
Native combinators (AbortSignal.any, fetch) brand-check internal slots
that a Proxy cannot forward; return signals unwrapped from
bindBrowserRunFacade so tab.signal composes with native cancellation.
- Removed the once wrapper from Puppeteer's request emitter on first fire so it cannot leak into later runs.
- Added regression coverage asserting a fired once handler leaves zero residual request listeners.
Fixes#6004
- Removed run-scoped Puppeteer request handlers and disabled interception on every browser.run exit path.
- Recycled workers when bounded interception cleanup cannot restore the tab, with raw CDP recovery for held requests.
- Added live Chromium coverage for held requests, normal traffic restoration, and thrown setup calls.
Fixes#6004
- Added bare `eN`/`@eN` regex to `parseAriaRefSelector` so agents can copy refs straight from snapshot output.
- Applied ref resolution to `press`, `screenshot`, `drag`, `select`, and `uploadFile` action handlers.
- Fixed `#select` to assign the full option set first then read back, avoiding double-counting when unselecting mid-loop.
- Replaced the losing Bun.sleep with an unrefed timeout cleared in a finally block.
- Added regression coverage that verifies prompt wheel acknowledgements leave no timer behind.
Fixes#5905
- Released tab.scroll after two seconds when a queued wheel event waits on a busy renderer acknowledgement.
- Preserved immediate dispatch failures and added regression coverage for both outcomes.
Fixes#5905
Dialed loopback CMUX_SOCKET_PATH endpoints over TCP and completed the cmux relay HMAC challenge before sending JSON-RPC requests.
Loaded relay credentials from the session environment or the per-port cmux auth file while preserving Unix socket behavior.
Fixes#5788
CmuxTab.#selectorSpec bypassed the puppeteer-backend guards and called
normalized.startsWith(...) directly, so tab.click(await tab.ref("e5")) on a
cmux surface still threw the opaque TypeError instead of the named ToolError.
Apply assertSelectorString at the cmux funnel too.
Fixes#5776
tab.click/type/fill/waitFor*/scrollIntoView route their selector through
parseAriaRefSelector (.trim) and normalizeSelector (.startsWith) before any
validation, so passing the ElementHandle from tab.id(n)/tab.ref(...) (or an
un-awaited Promise of one) crashed with the opaque minified
"A.trim is not a function" instead of an actionable error.
- Added assertSelectorString guard at both selector funnels; throws a ToolError
naming the recovery ((await tab.id(n)).click() or a string selector) and
distinguishing ElementHandle / Promise / primitive.
- Corrected browser.md: handles are called directly, not fed to tab.click.
- Regression tests in both selector suites.
Fixes#5776
disposeBrowserHandle awaited Puppeteer's browser.close() for the headless
kind with no timeout. browser.close() resolves only once Chromium fully
exits, so a wedged process (a Windows failure mode) left releaseTab stuck
in the "Closing tab" phase forever.
Cap the close at 5s and force-kill the Chromium process tree on timeout so
the tool call always releases.
Fixes#5260
- Applied close deadlines to cmux surfaces, orphan targets, and browser handles.
- Surfaced the backend, tab name, and pending cleanup resource on timeout.
- Forced stuck headless browser processes down after Browser.close timed out.
Fixes#5259
- Exported ensureChromiumExecutable so the test can probe launchability.
- CI runner holds the downloaded Chrome but lacks libnspr4 & co., so the
binary fails at dynamic-link time; probe --version and skipIf instead
of failing the release run.
- Implemented cell budget clamping for timeouts to prevent stalled browser operations from exceeding execution limits.
- Added `recover` capabilities for tab workers to clear blocking dialogs and safely terminate hung navigations.
- Introduced `//!world=main` support for `tab.evaluate` via Puppeteer patch to allow execution in main execution contexts.
- Improved failure attribution for tab terminations by tracking dialogs, stalled operations, and specific termination reasons.
- Enhanced `wait()` in browser tools to accept a predicate function in addition to milliseconds.
- Implemented automatic polling with configurable timeout and interval, resolving with the first truthy value.
- Throws a descriptive `ToolError` on timeout instead of waiting for the full execution deadline.
- Added comprehensive unit tests to verify polling behavior, cancellation, and error handling.
- Implement a zero-match watchdog for browser selector operations that aborts after ~2s if no elements are found, preventing actions from unnecessarily consuming the full deadline.
- Lower the default interactive action operation ceiling from 15s to 8s.
- Update `waitFor` and `waitForSelector` to conditionally opt out of the fast-fail mechanism when explicit timeouts or hidden-state expectations are provided.
- Introduced `RunOutput` class to standardize buffering and sequencing of stream text, displays, and screenshots.
- Standardized element interaction via `ActionableHandle` and `fillViaHandle` to improve focus, clear, and typing reliability.
- Updated browser tool method signatures to return consistent actionable handles and integrated diagnostic hints for selector timeouts.
- Consolidated utility functions for safer serialization and cross-boundary value passing into the new output module.
- Implemented a try/catch envelope for browser evaluations to surface detailed page-side JS exceptions and detect unsupported Promise returns.
- Added transparent error reporting for cmux browser surface limitations regarding screenshot clipping and full-page captures.
- Forced tab activation before screenshot capture to prevent stale or sibling-tab image data in shared-endpoint environments.
- setCwd now updates the saved __omp_session__ stack entry so a deferred
cross-runtime setCwd is visible to the runtime's next run (review should-fix)
- JsRuntime installation asserts realm ownership before mutating globals;
a first init during another runtime's live run fails via init-failed
instead of clobbering the active run's globals
- cmux runCmuxCode marks the armed cancel rejection as handled so a sync
setup throw under an already-aborted signal cannot become an unhandled
rejection (review P2)
- credited #4907 in the changelog entry
When the JS eval worker falls back to the in-process inline path, concurrent
JsRuntime instances share one realm. setCwd used to throw on exclusive-owner
conflicts, and the microtask delivery path turned that into a fatal
unhandledRejection that postmortem exited on. Stamp local cwd without
stealing the active realm, report init failures over the worker protocol,
and cover process survival with in-process and child-process regressions.
- Added `markHandled` helper to prevent unhandled promise rejections in fire-and-forget browser tasks.
- Integrated `postmortem.markExpectedCleanupError` to distinguish between expected teardown aborts and actual runtime failures.
- Updated `runCmuxCode` and `WorkerCore` to propagate abort reasons via `ToolAbortError` cause chains.
- Modified `tab-protocol` and supervisor logic to signal expected cleanup states during tab release.
- Added test coverage for `ToolAbortError` wrapping and cause preservation.
Validated path-like renderer inputs before calling path helpers so provider-supplied arrays or objects cannot crash TUI rendering before schema validation reports the bad tool call.
Added renderer regression coverage for read, write, and edit call/result components with array and object path arguments.
Fixes#4525
Codex review of #4502 flagged that a bare `.catch(() => undefined)`
neutralizes the unhandledRejection but leaves the affected `runInTab`
call blocked inside `runCmuxCode` until timeout when the in-flight
code does not make another cmux socket request (e.g. `await
wait(60_000)`). `releaseTab` was signaling the run only by rejecting
an orphaned promise.
Wire the tab-close event all the way into the cmux run body:
- `PendingRun` gains a `closeAc: AbortController` that `releaseTab`
aborts BEFORE calling `pending.reject`. `wait(...)` (via
`waitForBrowserRun` -> `untilAborted`), in-flight cmux socket calls
(via CmuxTab's `#request` -> `untilAborted`), and facade proxies
(via `bindBrowserRunFacade`) all consume the composed signal, so
the run body unwinds within a microtask instead of blocking to its
own timeout.
- `runInTabWithSnapshot`'s cmux branch composes `closeAc.signal` into
the run's signal (`AbortSignal.any([opts.signal, closeAc.signal])`)
and now publishes `runCmuxCode(...)`'s outcome to the shared
`promise` via `.then(resolve, reject)` and returns `await promise`.
Both branches thus await the same promise, so `pending.reject`
always has an attached handler (removing the original crash) AND
the caller sees `Tab "..." was closed` immediately instead of
waiting on the run's timeout.
- Drop the defensive `promise.catch(() => undefined)` — the promise
is now actively consumed on both backends.
The new regression test adds a second case that exercises the
reviewer's exact scenario (`await wait(60_000);`) and asserts:
1. `pending.closeAc.signal.aborted` flips from `false` to `true`
across `releaseTab`, with the tab-close error as its reason.
2. The awaited `runInTab(...)` rejects with `Tab "..." was closed`.
3. No `unhandledRejection` fires.
Verified locally by temporarily removing `closeAc.abort(...)` in
`releaseTab` — the new assertions fail; restoring it makes them pass.
Fixes#4499
The cmux branch of `runInTabWithSnapshot` awaits `runCmuxCode(...)`
directly and never awaits/`.catch`es the `Promise.withResolvers()`
promise it stashes on `tab.pending`. When `releaseTab` walks pending
runs and calls `pending.reject(new ToolError("Tab ... was closed"))`
(a sibling subagent's `browser close --all`, session-scoped reap, etc.),
that orphaned promise had zero handlers and Bun surfaced the rejection
as `unhandledRejection`, which the CLI's top-level handler treats as
fatal — killing every other tab and subagent sharing the process, not
just the affected run.
Attach a no-op `.catch(() => undefined)` to the promise immediately
after creation. Inert for the worker branch (which still awaits the
same promise via `raceWithTimeout`, and attaching a second handler is
safe) and neutralizes the orphan on the cmux branch.
Adds a regression test that drives real `acquireBrowser` /
`acquireTab` / `runInTab` / `releaseTab` against a mocked
`CmuxSocketClient`, races `releaseTab` against an in-flight cmux run,
and asserts no `unhandledRejection` fires.
Fixes#4499
Added renderer metadata for pending and partial result paths that visibly consume spinner frames.
Stopped headerless bash pending previews from scheduling repaint ticks while preserving eval and shell header animation.
Wrapped cmux page, browser, and tab globals with per-run abort checks so stale continuations cannot reuse the long-lived CmuxTab after timeout.
Fixes#3964
Two termination boundaries in the browser tool leaked browser-owned OS resources into the long-lived coding-agent process.
1. Aborted 'open' published an orphan. #open wrapped acquisition in untilAborted, which rejects its outer wrapper on abort but lets the inner launch resolve in the background; acquireBrowser then unconditionally stored the resolved handle in the module-global browsers map. releaseAllTabs walks tabs, not browsers, so the refCount:0 handle stayed alive to process exit.
2. Session dispose had no browser teardown. Browser/tab state lives in module-global maps, and AgentSession.dispose() had no hook to walk them, so headless/spawned Chromium the session opened survived it.
acquireBrowser now short-circuits before launch on a pre-aborted signal and disposes the handle when the launch completes after abort. TabSession records the creating session's id (opts.ownerSessionId, threaded through BrowserTool.#open), preserved across reuse so a subagent re-driving an existing tab does not yank teardown responsibility. AgentSession.dispose() invokes releaseTabsForOwner bounded by withTimeout(3s), mirroring the async-job/MCP disposal pattern.
Regression tests exercise both boundaries via spied CmuxSocketClient (no real puppeteer/socket) and cover: pre-aborted open short-circuit, aborted-mid-launch cleanup, releaseTabsForOwner reaping only owned tabs, and reuse preserving original ownership.
Fixes#3963
- Removed deprecated eval prelude helpers `append`, `tree`, `diff`, `sort`, `uniq`, and `counter` from all supported runtimes.
- Cleaned up runtime implementations, protocol definitions, and UI rendering logic associated with the removed helpers.
- Updated project documentation, prompts, and test suites to reflect the reduced helper API surface.
- Recorded functional changes in the package changelog.
- Implemented `waitForSelector` and `waitForNavigation` methods in the browser tool API.
- Introduced per-operation fail-fast budget management with dynamic timeout clamping.
- Added validation for selector engines to reject unsupported Playwright-only platform features.
- Standardized error handling to provide descriptive, named timeouts for stalled browser operations.
- Implemented `tab.ariaSnapshot()` to capture and represent page structures as ARIA-tree YAML.
- Introduced `tab.ref()` and ref-based selector parsing to enable precise element interaction via unique ARIA identifiers.
- Integrated automated script bundling for cross-environment evaluation of ARIA snapshot logic.
- Updated browser action methods to resolve and target elements using ARIA-ref handles.
- Overhauled stealth spoofing mechanisms for WebGL, screen dimensions, Web workers, and iframe contexts using prototype-aware injection.
- Centralized function string representation patching to improve mimicry of native browser behavior across global objects.
- Patched puppeteer-core to remove detectable evaluation markers and implement lazy, pull-style execution context management.
- Enabled support for capturing LLM request JSON dumps and adjusted launcher flags to improve organic request patterns.
- Avoided value-imports of `puppeteer-core` in main startup files to prevent eager loading of the browser-data dependency graph.
- Used `import type` and literal string checks to defer `puppeteer-core` initialization until browser tools are actually invoked.
- Added a test to verify that `puppeteer-core` and `@puppeteer/browsers` remain off the eager startup import graph.
- Externalized additional heavy dependencies to reduce bundle size and improved minification settings in the build script.
- Bound native Reflect methods to local variables in the browser launch script to prevent detection.
- Updated stealth injection scripts to use the bound Reflect methods instead of global Reflect calls.
- Fixed a type assertion issue in the AgentSession tool proxy.