Skip to content

feat: expose download and waitForDownload actions in browser agent tool#12378

Closed
vabole wants to merge 1 commit into
openclaw:mainfrom
vabole:feat/browser-download-tool
Closed

feat: expose download and waitForDownload actions in browser agent tool#12378
vabole wants to merge 1 commit into
openclaw:mainfrom
vabole:feat/browser-download-tool

Conversation

@vabole

@vabole vabole commented Feb 9, 2026

Copy link
Copy Markdown

Summary

The browser download functionality was fully implemented at the Playwright layer (pw-tools-core.downloads.ts) and CLI layer (register.files-downloads.ts) but was missing from the agent tool — the interface AI agents actually use via the browser tool.

This meant agents had no way to download files from websites, forcing workarounds like XHR/fetch interception with localStorage to capture downloads.

Changes

New actions in browser agent tool

download — Click a ref and save the resulting download:

{ "action": "download", "ref": "e12", "path": "/tmp/report.pdf" }

waitForDownload — Wait for the next download (no click):

{ "action": "waitForDownload", "path": "/tmp/export.csv", "timeoutMs": 30000 }

Both return:

{
  "ok": true,
  "targetId": "...",
  "download": {
    "path": "/tmp/report.pdf",
    "url": "https://example.com/report.pdf",
    "suggestedFilename": "report.pdf"
  }
}

Files changed

File Change
browser-tool.ts Added download and waitForDownload case handlers following the existing upload/dialog pattern
browser-tool.schema.ts Added both actions to BROWSER_TOOL_ACTIONS, added path parameter
browser-tool.test.ts Added unit tests for both actions with proper mocks

Design decisions

  • Follows existing patterns exactly: Both actions mirror the upload/dialog wiring — same proxyRequest path for node targets, same jsonResult wrapping, same parameter extraction style
  • path is required for download (you must specify where to save), optional for waitForDownload (defaults to temp directory)
  • No changes to Playwright layer or CLI commands: Those already worked; this only wires them to the agent-facing tool
  • path added as schema-level optional parameter: Runtime enforcement handles required vs optional per action

Testing

  • ✅ All 14 browser tool tests pass (12 existing + 2 new)
  • ✅ TypeScript build passes
  • ✅ No changes to existing functionality

Agent prompt used

This was implemented by Codex (gpt-5.3-codex, full-auto mode) with the following prompt:

Click to expand prompt
You are working on the OpenClaw project, a personal AI gateway. 

TASK: Expose the existing browser download functionality to the agent tool.

CONTEXT:
- The Playwright download integration already exists in `src/browser/pw-tools-core.downloads.ts` with `waitForDownloadViaPlaywright()` and `downloadViaPlaywright()` 
- The CLI commands already exist in `src/cli/browser-cli-actions-input/register.files-downloads.ts` (waitfordownload, download)
- The agent tool is in `src/agents/tools/browser-tool.ts`
- The agent tool already exposes "upload" and "dialog" actions (cases in a switch statement around line 603-680) but NOT download
- The browser HTTP server that handles routes is likely in `src/browser/` - find the route handlers for `/wait/download` and `/download`

WHAT TO DO:
1. Find how the existing "upload" and "dialog" agent tool actions are wired (in browser-tool.ts) — use the exact same pattern
2. Add a "download" action to the browser agent tool that clicks a ref and saves the download (like `downloadViaPlaywright`)  
3. Add a "waitForDownload" action that just waits for the next download without clicking (like `waitForDownloadViaPlaywright`)
4. Both should return the file path, URL, and suggested filename
5. Add tests in `src/agents/tools/browser-tool.test.ts` following existing test patterns
6. Make sure the TypeScript compiles: run `npm run build` and fix any errors

IMPORTANT:
- Follow existing code patterns exactly (imports, error handling, proxyRequest pattern)
- The "upload" case around line 603 is your template
- Check the schema file too for any type definitions that need updating
- Do NOT modify pw-tools-core.downloads.ts - that already works
- Do NOT modify the CLI commands - those already work

Greptile Overview

Greptile Summary

This PR wires existing Playwright/CLI download support into the agent-facing browser tool by adding two new actions: download (click by ref and save to a specified path) and waitForDownload (wait for the next download, optionally specifying a save path). It updates the browser tool schema to accept the new actions and a path field, and adds unit coverage that mocks the underlying browserDownload / browserWaitForDownload client-actions and asserts the tool passes through ref, path, targetId, timeoutMs, and profile.

The change fits into the existing browser tool pattern by routing through the node browser proxy (proxyRequest) when available (POST /download and /wait/download) and falling back to direct client-actions calls otherwise, returning results via the existing jsonResult wrapper.

Confidence Score: 4/5

  • This PR is close to safe to merge, but the new Vitest mock setup may be order-dependent and flaky in some environments.
  • Core tool wiring appears consistent with existing proxy/direct call patterns and matches existing server endpoints. Main concern is the new vi.mock("../../browser/client-actions.js") factory referencing browserActionMocks declared later in the file, which can break depending on Vitest hoisting/evaluation order, leading to intermittent test/runtime failures.
  • src/agents/tools/browser-tool.test.ts

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

@openclaw-barnacle openclaw-barnacle Bot added the agents Agent runtime and tooling label Feb 9, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +71 to +74
const browserActionMocks = vi.hoisted(() => ({
browserDownload: vi.fn(async () => ({
ok: true,
targetId: "t1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mock ordering breaks tests

browserActionMocks/vi.mock("../../browser/client-actions.js", ...) is declared after other vi.mock(...) calls. With Vitest hoisting semantics, mock factory evaluation can occur before browserActionMocks is initialized, which can make the mock return undefined functions or throw at import time depending on module load order. This file previously avoided that by not referencing late-declared variables in mock factories.

Move the browserActionMocks = vi.hoisted(...) block above any vi.mock that uses it (or inline the hoisted mocks inside the vi.mock factory) so the mocked exports are always defined when the module is evaluated.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/tools/browser-tool.test.ts
Line: 71:74

Comment:
**Mock ordering breaks tests**

`browserActionMocks`/`vi.mock("../../browser/client-actions.js", ...)` is declared *after* other `vi.mock(...)` calls. With Vitest hoisting semantics, mock factory evaluation can occur before `browserActionMocks` is initialized, which can make the mock return `undefined` functions or throw at import time depending on module load order. This file previously avoided that by not referencing late-declared variables in mock factories.

Move the `browserActionMocks = vi.hoisted(...)` block above any `vi.mock` that uses it (or inline the hoisted mocks inside the `vi.mock` factory) so the mocked exports are always defined when the module is evaluated.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a false positive — vi.hoisted() specifically hoists the variable initialization to the top of the file before any vi.mock() factories execute. That's its entire purpose (Vitest docs). The ordering in source code is irrelevant since all vi.hoisted() calls run before module evaluation.

All 14 tests pass consistently, confirming no initialization issue.

@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added stale Marked as stale due to inactivity and removed stale Marked as stale due to inactivity labels Feb 21, 2026
The browser download functionality was fully implemented at the Playwright
layer (pw-tools-core.downloads.ts) and CLI layer (register.files-downloads.ts)
but was missing from the agent tool — the interface AI agents actually use.

Changes:
- Add 'download' action: clicks a ref and saves the resulting download
- Add 'waitForDownload' action: waits for the next download without clicking
- Both return { path, url, suggestedFilename } metadata
- Add 'path' parameter to browser tool schema for save location
- Support proxied (node) and local execution paths
- Add unit tests for both actions

This closes the gap where agents had to use XHR/fetch workarounds to
intercept file downloads from websites.
@openclaw-barnacle

Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant