* fix(browser): drain download saves and use monotonic cursor for act response
- Add drainPendingDownloadSaves() to wait for in-flight saveAs before sampling
- Use monotonic downloadSeq cursor instead of bounded-list length
- Propagate downloads info in POST /act response for click/batch/evaluate
Fixes#93250
* fix(lint): add braces around single-line if returns
Fixes eslint(curly) failures in drainPendingDownloadSaves and
pickNewDownloads. PR #93307 required CI gate.
Ref: ClawSweeper P1 review finding
* fix(browser): scope act download metadata to action
* fix(browser): broadcast downloads to all active captures to prevent misattribution
When concurrent /act calls overlap on the same page, using
state.actionDownloadCaptures.at(-1) assigned downloads to the wrong
action's capture. Push managed save promises to all active captures
so the triggering action always receives its download metadata.
Also adds regression tests: broadcast-to-all-captures, sequential
capture isolation, and strengthen the dispose test to assert no
re-capture after disposal.
* fix(browser): prevent unhandled rejection when download capture action throws before drain
Move managedSave.catch() before the captures-branch check so the
rejection handler always runs, preventing an unhandled promise
rejection when the action throws before drain() is called. Simplify
the handler by removing the now-dead return + catch at the bottom.
* fix(browser): type downloads in BrowserActResponse, fix lint unused var
- Add optional downloads field to BrowserActResponse type contract so
typed callers of browserAct() can consume the new payload without casts.
- Remove unused afterDispose variable in pw-session tests (lint fix).
* fix(test): correct post-dispose download assertion
capture.promises is not cleared by dispose(), so re-draining a disposed
capture still returns pre-dispose results. This is benign — callers
should drain before disposing. Update the test to assert the actual
behavior (still shows old results, new download not captured).
* test(browser): cover /act download metadata response
* refactor(browser): report action-owned downloads safely
* fix(browser): close action download ownership races
* test(browser): type action download capture mock
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): preserve managed Chrome cookies
* fix(browser): guard graceful close process ownership
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): resolve act targetId aliases before mismatch check
The /act top-level and batch targetId guards compared the caller-supplied
targetId against the resolved canonical tab.targetId with raw string
equality. Any supported alias form (tabId, label, suggestedTargetId, or a
unique id prefix) resolves to a different canonical id, so act requests that
followed the documented 'prefer suggestedTargetId/tabId/label' guidance were
rejected with 403 ACT_TARGET_ID_MISMATCH even though they named the correct
tab. snapshot/open/close/tabs lack this guard and kept working, matching the
reported symptom matrix.
Resolve the action targetId through the same tab alias resolution the route
used and reject only ids that resolve to a different tab.
* fix(browser): canonicalize act targetId aliases before Playwright dispatch
The /act gate accepted tabId/label/suggested/prefix aliases of the request
tab but left them on action.targetId. The managed executor reads
action.targetId ?? targetId for an exact page lookup (executeSingleAction ->
getPageForTargetId), so an alias missed the lookup and broke the action at
runtime whenever more than one page was open (single-page masked it via the
pages.length===1 fallback). Canonicalize the action targetId (top-level and
nested batch sub-actions) to the resolved tab id before dispatch; reject ids
that resolve to a different tab. Replace the mock-masked contract assertions
with executor-action assertions plus direct canonicalizer unit tests.
* test(browser): brace act-targetId guard clauses for curly lint
* docs(changelog): note browser target alias fix
* docs(changelog): note browser target alias fix
* fix(browser): reject ambiguous batch target aliases
* test(browser): type targetless act fixture
* docs(changelog): move browser alias fix to unreleased
* chore: drop nonessential browser changelog entry
---------
Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The /set/media control route accepts dark|light|no-preference|none, but the
CLI rejected no-preference, so prefers-color-scheme: no-preference could not
be emulated from the CLI. Align the CLI argument, validation, and error text
with the route contract and update the browser-control docs media line.
When keys is empty, keys[keys.length - 1] returns undefined, and the
previous ?? fallback would silently create a property with an empty
string key on the target object. Add an early return so empty keys are
a no-op instead of silently polluting the object.
* chore(release): close out 2026.6.10 on main
* chore(release): align native app metadata for 2026.6.10
* chore(release): sync Android 2026.6.10 notes
* docs(changelog): preserve 2026.6.9 history
* docs(changelog): preserve 2026.6.9 history
Summary:
- The branch adds Vitest coverage for browser action-input CLI request bodies across element, navigation/resize, fill/evaluate, and upload paths, plus blank-ref validation.
- PR surface: Tests +278. Total +278 across 4 files.
- Reproducibility: yes. for a source-level coverage gap: current main exposes the browser action-input command ... isting tests still lack broad success-path request-body assertions. This is not a runtime bug reproduction.
Automerge notes:
- PR branch already contained follow-up commit before automerge: test(browser): cover click-coords action body
Validation:
- ClawSweeper review passed for head c070a8d51b.
- Required merge gates passed before the squash merge.
Prepared head SHA: c070a8d51b
Review: https://github.com/openclaw/openclaw/pull/92574#issuecomment-4697124920
Co-authored-by: Stellar鱼 <2182712990@qq.com>
Co-authored-by: yu-xin-c <2182712990@qq.com>
* fix(browser): use openTab return value to prevent wsUrl race in ensureTabAvailable
When ensureTabAvailable opens a new tab on empty list, the return value
from openTab was discarded. A subsequent listTabs() call may return tabs
without webSocketDebuggerUrl populated yet, causing the wsUrl filter to
eliminate the newly opened tab and throw BrowserTabNotFoundError.
Fix: capture openTab's return value and merge it into candidates if the
wsUrl filter excluded it. openTab's internal discovery loop already
resolves wsUrl, so the returned tab is always valid.
* fix(browser): harden tab selection discovery
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Remove the unused requireRef import and void anchor from Browser navigation command registration while keeping navigate/resize registration covered by regression tests. Fixes#83878.