feat(browser): expose agent download actions#101369
Conversation
Co-authored-by: changcy <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ccb35d08b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function resolveBrowserDownloadRequestTimeoutMs(timeoutMs: unknown): number { | ||
| const waitTimeoutMs = | ||
| normalizePositiveTimeoutMs(timeoutMs) ?? DEFAULT_BROWSER_DOWNLOAD_TIMEOUT_MS; | ||
| // Keep the HTTP client alive after the Playwright waiter expires so its | ||
| // timeout/error response reaches the caller instead of a transport abort. | ||
| return addTimerTimeoutGraceMs(waitTimeoutMs, BROWSER_DOWNLOAD_REQUEST_TIMEOUT_SLACK_MS) ?? 1; |
There was a problem hiding this comment.
Keep download transports open until saves finish
When a download starts near the end of the requested wait, or when the file itself takes more than 5 seconds to finish, this timeout aborts the client even though the browser route is still inside download.saveAs(...). Playwright emits the download event when the transfer starts, and saveAs waits for completion, so the server can legitimately take longer than waitTimeoutMs + 5s; the new agent action will report a failed browser request for large/slow exports that were successfully detected and are still being saved.
Useful? React with 👍 / 👎.
| profile, | ||
| }); | ||
| params.onTabActivity?.(readStringValue((result as { targetId?: unknown }).targetId) ?? targetId); | ||
| return jsonResult(result); |
There was a problem hiding this comment.
Wrap page-controlled download metadata
For downloads from a web page, download.url and download.suggestedFilename are page/header-controlled, but jsonResult(result) puts those strings directly into the model-visible tool output. Other browser-originated surfaces in this module wrap/neutralize their text before returning it; this new action should do the same so a malicious filename or URL cannot inject instructions into the next agent turn.
Useful? React with 👍 / 👎.
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: feat(browser): expose agent download actions This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
|
Land-ready proof for prepared head
Known proof gaps: none. |
|
Merged via squash.
|
* feat(browser): expose agent download actions Co-authored-by: changcy <[email protected]> * chore: keep release notes in PR context --------- Co-authored-by: changcy <[email protected]>
* feat(browser): expose agent download actions Co-authored-by: changcy <[email protected]> * chore: keep release notes in PR context --------- Co-authored-by: changcy <[email protected]>
* feat(browser): expose agent download actions Co-authored-by: changcy <[email protected]> * chore: keep release notes in PR context --------- Co-authored-by: changcy <[email protected]>
What Problem This Solves
Resolves a problem where agents could not deliberately save a download to a known guarded path or wait for a download that begins after an asynchronous export completes. The lower-level managed-browser routes and CLI existed, but the model-facing
browsertool omitted both operations.Related: #38519, #12378, #93307, #74411
Why This Change Was Made
This takes @GRD-Chang's explicit-action design from #74411 and ports it to the current Browser plugin action boundary. It exposes
downloadandwaitfordownload, keeps one typed host/node dispatch path, carries the browser wait through the HTTP/node/Gateway timeout layers, and keeps all writes behind the existing managed-profile and guarded-download-root controls.This complements #93307 rather than replacing it:
/actstill reports downloads caused during an immediate action, while these operations support a caller-selected path and delayed exports that need an explicit waiter. Existing-session profiles remain unsupported. The routes now also enforce the current-tab navigation policy before capturing bytes.This maintainer replacement preserves the contributor's authorship because the stale fork branch behind #74411 could not accept the current-main rewrite through the verified branch-update path without serializing the repository-wide base gap.
User Impact
Agents can click a snapshot ref and save its download to a chosen relative path, or arm
waitfordownloadbefore a delayed export completes. Both return the completed download URL, suggested filename, and guarded local path for later tool use.Evidence
1a97a19e434bbe940120ad865109c3f02ea276ec: full hosted CI passed on attempt 2 after one unrelatedsubagent-fanout-synthesisQA retry. Run: https://github.com/openclaw/openclaw/actions/runs/28842562919tbx_01kwxavv0ks9wszpejv07qj0dt: 7 focused files, 154/154 tests; fullcheck:changed; targeted oxfmt. Focused run: https://github.com/openclaw/openclaw/actions/runs/28840624922Page.waitForEventapplies the supplied timeout andDownload.saveAswaits through the artifact save/remote stream.local-containerleasecbx_09eff0c13634(swift-crab-f524), exit 0 in 162.651s. A rebuilt package started a fresh Gateway and Chromium 149, and a realopenai/gpt-5.4agent completed both a nameddownloadand a 45-second delayedwaitfordownload. The validator independently checked unpredictable file bytes, rejected traversal, and confirmed both model-facing actions in agent state.ref/pathvalidation, traversal and symlink rejection, existing-session rejection, current-tab navigation guard, host routing, node proxy path rewriting, and inner/proxy/Gateway timeout layering.Contributor credit is preserved in the signed maintainer history with
Co-authored-by: changcy <[email protected]>.