* fix(browser): close the created tab when opened-tab discovery is aborted
* fix(browser): complete cancelled tab creation cleanup
Close newly created CDP targets on their creating socket when cancellation prevents returning the target ID. Preserve the original error even when close fails.
Capture cleanup policy and timeout before asynchronous work so runtime shutdown cannot fence compensation or mask the open error. Extend the existing cancellation tests through the real profile lifecycle.
* fix(browser): resolve unnamed role refs and initialize AX markers
Keep explicit raw ARIA empty names distinct from omitted serializer names and share one dependency-faithful role lookup. Bind the DOM document in the marker-owning CDP session without adding a round trip.
Related: #130879. The separate snapshot membership and ordering failures remain open.
* chore(browser): shrink ref assertion safety baseline
Record the five type assertions removed by the ref-name repair. Tighten the canonical per-file allowance from seven to two; runtime and browser-test bytes are unchanged.
* fix(browser): reject non-decimal extension install wait strings
normalizeExtensionInstallWaitMs used loose Number() on the external
--wait-ms CLI value, so inputs like 0x1000 or 1e4 coerced into valid
in-range waits instead of being rejected. Migrate the string path to
parseStrictPositiveInteger so only plain decimal integers are accepted;
keep the bound/integer checks as the canonical guard and add a
failing-before/passing-after regression.
* fix(browser): enforce decimal extension wait values
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(browser): preserve refs for quoted control names
Decode the emitted Playwright name grammar once across snapshot consumers and keep CDP and Chrome MCP string producers consistent. Fixes#130571.
* chore(browser): document snapshot JSON string invariant
* docs: place browser snapshot note beside related fixes
* test(browser): use an event listener in snapshot fixture
* fix: prevent dollar-pattern injection in tilde expansion across extensions
String.replace interprets dollar-amp/dollar-1/dollar-backtick in the
replacement string. Eight call sites across extensions and
memory-host-sdk expanded ~ using .replace(/^~/, homeVariable), which
corrupts the path when the home directory contains these sequences.
Switch each to a function replacement (() => homeVariable) so the home
path is treated literally. Same fix as the core home-dir resolver
(PR #122991) and terminal-core/display-string (#111398).
Affected: voice-call utils + doctor, browser config, migrate-claude,
migrate-hermes, memory-lancedb doctor, memory-host-sdk config-utils.
* fix: cover iMessage and 1Password tilde expansion owners
Address review: iMessage CLI path expansion and both 1Password
state-path expansions (OPENCLAW_HOME and OPENCLAW_STATE_DIR) also passed
resolved home as a string replacement. Apply the callback form so a
literal-dollar home path is preserved.
* test(voice-call): add literal-dollar tilde expansion regression
Address review rank-up: mock os.homedir to a literal-dollar path and
assert the resolved path preserves dollar-amp verbatim (fails on the
string replacement form, passes with the callback).
* test(extensions): cover literal-dollar tilde expansion at every changed owner
* test(onepassword): cover tilde OPENCLAW_HOME literal-dollar expansion
* fix(browser): don't steal focus on headed screenshot captures
captureScreenshot unconditionally sent Page.bringToFront before every
capture to avoid a headless-specific stall (#100857), but on a headed
browser that activates the visible tab on every screenshot — the
highest-frequency agent operation — repeatedly stealing focus from a
watching user (#105357).
Detect headed-ness via Browser.getVersion userAgent and only skip
activation when the browser is confirmed headed. Headless browsers, and
any case where detection is unavailable, keep activating so the #100857
stall fix is never reintroduced.
Fixes#105357
* fix(browser): trust managed launch flag for screenshot tab activation
The v1 headed-detection sniffed the browser user-agent to decide whether
to activate the tab before a screenshot. A managed headless Chrome can be
launched with a custom --user-agent via extraArgs, which the sniff would
misclassify as headed and drop the activation that prevents the
background-capture stall (#100857).
For a managed browser the profile already holds the authoritative headless
launch flag, so pass it through and let it win over UA sniffing. Attached/
external sessions (headless unknown) keep the best-effort UA fallback. This
also skips the per-screenshot Browser.getVersion round-trip on the common
managed path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(browser): drop unreliable UA sniff; activate unless confirmed-headed
The re-review flagged a symmetric availability risk in the attached-session
fallback: an attached headless Chrome launched with a custom non-"headless"
--user-agent is misclassified by the UA sniff as headed, suppressing the
activation that prevents the #100857 background-capture stall.
Verified against real Chrome that no runtime signal survives a spoofed UA:
--headless=new with --user-agent=<normal> reports product "Chrome/…", the
spoofed userAgent, and navigator.webdriver === false — indistinguishable from
a headed browser. So the UA sniff cannot be made robust for attached sessions.
Focus-hygiene is only safe where the headless state is authoritative (managed
profiles, via the launch flag). Skip activation ONLY for a confirmed-headed
managed profile; managed headless and every attached/external session keep
activating, so the stall (a hard timeout — strictly worse than an unnecessary
activation) is never risked. This drops the UA sniff and the per-screenshot
Browser.getVersion round-trip entirely. Attached behavior matches current main
(always activate); only managed headed profiles — the primary #105357 case —
change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(browser): preserve focus using actual Chrome launch mode
Co-authored-by: RickLin <83101411+ObliviateRickLin@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* docs(browser): teach the code-mode global call, not the removed tools.call API
The browser-automation skill's Code Mode Loop still taught the guest
`tools.call(browserTool, input)` API that #126262 removed, together with
the `.result.details`/`.result.content` envelope reads. Rewrite the loop
for the current contract: call the `browser(...)` async global directly,
receive the tool's structured details (targetId, url, newElements, stats,
blockedByDialog), and diff url/new-element counts between cells instead of
filtering snapshot text, since rendered page text is no longer
guest-visible in code mode.
* docs(browser): resolve callable from quick index and read via targeted evaluate
* docs(browser): call the search-resolved handle and carry cell state explicitly
Fixes#121919. Preserve screenshot session ownership through the focused agent-scope SDK before media runtime resolves provider credentials.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The browser doctor computed BROWSER_PLUGIN_ROOT by walking one directory
up from the source file. In a source checkout the file lives in
extensions/browser/src/ and chrome-extension assets are at
extensions/browser/chrome-extension, so this worked. In a built package the
compiled artifact lives at the package root (dist/extensions/browser/) and
assets are at dist/extensions/browser/chrome-extension, so the old heuristic
looked for dist/extensions/chrome-extension and failed with ENOENT.
Resolve the package root by searching for package.json, which exists in both
layouts, and fall back to the previous heuristic only when it is absent.
Fixes the doctor report: 'Chrome extension bootstrap status could not be
inspected: ENOENT ... realpath /dist/extensions/chrome-extension'.