A finished (non-timeout) agent-browser failure at the backend level — the CLI
exiting 101 against a stale session daemon, or empty/non-JSON output from a dead
one — returned normally as {"success": False}, so the cached local session record
was never marked suspect and every later browser command re-ran against the same
dead daemon until the process was recycled by hand (#115184).
- _interpret_browser_command_output carries `returncode` on its three
protocol-level failure dicts (parsed page-level errors never carry one)
- _is_recoverable_local_backend_failure classifies those on plain local Chromium
sessions only (cloud/CDP/real-profile/Lightpanda own their recovery)
- _recycle_local_session is the local half of _handle_browser_command_timeout,
extracted so the timeout path and the new path share the alive→suspect /
dead→tree-kill+evict split
- _run_browser_command retries once on the replacement session; `close` is
exempt (a dead daemon is already closed and cleanup must not spawn a session
just to close it)
- argv construction + spawn moved into _dispatch_browser_command so the retry
loop stays a loop over one call
Based on #115206 by @liuhao1024 (returncode on the failure dict, the
recoverability predicate, the one-retry loop); reshaped so the recycle helper is
shared with the timeout path instead of calling the timeout handler.
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
The base64 guard covered only `eval` scripts. cmd.exe re-parses every
argument the .cmd shim forwards, so multi-line text sent through `fill`
(browser_type) was truncated at its first line and %VAR% expanded the same
way (#113838). Any non-eval command whose argv carries a newline or % now
runs as `agent-browser batch --json` with the command as a JSON array on
stdin (served from a temp file like stdout/stderr), and the single batch
entry is unwrapped to the usual {success, data, error} shape. The shim test
now drives _run_browser_command with _spawn_and_collect captured, so the
call-site wiring is guarded, not just the helper.
Follow-up to the cherry-picked one-line ``_GET_IMAGES_JS`` (#113844, @KoNit-K),
closing the class the issue asked to audit. Supersedes the earlier #82278 (@Clubheader),
which reached the same shim-truncation diagnosis via ``eval --stdin``; base64 needs no
stdin plumbing and also survives cmd.exe ``%VAR%`` expansion:
- ``browser_tool_session._shim_safe_eval_args``: when the resolved argv[0] is a
``.cmd``/``.bat`` shim (``npx.cmd``, npm's ``agent-browser.cmd`` on Windows)
the ``eval`` script is sent as ``--base64 <b64>`` (``agent-browser eval -b``,
present since the 0.26 floor). cmd.exe re-parses the child command line —
a newline ends the argument and ``%VAR%`` expands even inside quotes — so
this is the only lossless transport for model-authored ``browser_console``
expressions and the vault ``eval`` fallback, not just the bundled constant.
Every other spawn target (native binary, POSIX shim) keeps the raw argv.
- Tests: two invariants in ``tests/tools/test_browser_eval_shim_args.py``
(shim → base64 round trip with native/POSIX/non-eval controls; every
``*_JS`` constant across ``tools/browser_*`` is single-line). The
contributor's get_images regression test is dropped as subsumed by the
module-wide constant scan.
Host-specific (Windows): code-path proof. Live on this host: real
``agent-browser --json eval -b <b64>`` of the collapsed script returns the
image list (data: URIs filtered); ``eval "JSON.stringify("`` — the first line
the shim delivers — reproduces the reporter's exact
``SyntaxError: Unexpected end of input``.
Co-authored-by: Clubheader <Clubheader@users.noreply.github.com>
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.