Skip to content

1.9.1: upload files through the sandbox browser's live view - #588

Draft
TonsOfFun wants to merge 3 commits into
feat/claude-code-boot-setupfrom
feat/live-view-file-upload
Draft

TonsOfFun wants to merge 3 commits into
feat/claude-code-boot-setupfrom
feat/live-view-file-upload

Conversation

@TonsOfFun

Copy link
Copy Markdown
Contributor

Release: 1.9.1. This PR changes browser-sidecar/, and @activeagents/browser-sidecar@1.9.0 is already on npm, identical to main's browser-sidecar/. So it bumps activeagent, actionagent and the sidecar to 1.9.1 (CI requires the sidecar's version to equal ActionAgent::VERSION). The 1.9.1 release must publish the sidecar, as a staged npm publish (#585) that a maintainer approves.

Stacked on #584 (base feat/claude-code-boot-setup). Retarget to main once #584 merges.

File upload in the sandbox browser's live view

  • Before this, clicking a page's file input while driving the live view opened a chooser inside the headless Chromium that nobody could see.
  • Sidecar (browser-sidecar/lib/file-upload.mjs, FileChooserRelay): listens for Playwright filechooser events. A chooser opened while a person holds control is offered to the live view as { type: "file_chooser", chooser: { id, multiple, accept, max_files, max_file_bytes } }, and it's included in ready for viewers who join later. A chooser opened while nobody drives is left to Playwright MCP's browser_file_upload, so agents behave as before. The listener is attached only while someone drives, so a headed window's native dialog still opens the rest of the time.
  • Transport: the existing live-view WebSocket, with the same ticket auth and control lock. The client sends file_upload { chooser, files }, then file_chunk (base64, 32 KiB per message, under the 64 KiB cap) or file_cancel. Replies report progress, done, or failed with a code.
  • Storage: files go to a per-chooser directory under the sidecar's workdir (0700 directories, 0600 files, one subdirectory per file), then fileChooser.setFiles(paths). They're kept until the browser stops, because the page reads them from disk lazily.
  • Withdrawn when: the driver cancels, hands back control, or sends nothing for 5 minutes.
  • Playwright MCP modal state: when a person answers or cancels, the relay clears the MCP "File chooser" modal state so later agent tool calls aren't blocked. This reaches into MCP through a private symbol, best-effort, and the real-Chromium smoke test covers it.
  • Dashboard (LiveView.jsx, utils/liveView.mjs): the driver gets an overlay with a real <input type=file> (honouring multiple and accept) plus Cancel, and can drop files on the frame while the chooser is open. Counts and sizes are checked before sending, sending waits when more than 1 MiB is queued, and progress shows as a percentage. Other viewers see "The page is asking for a file."
  • Project page: the Browser tab's copy mentions uploads again (1.9.0: fix a failed boot with Claude Code, and the sandbox browser on the project page #584 removed it).

Limits (sidecar live config; Ruby doesn't set these yet)

Key Default
upload_max_file_bytes 25 MiB
upload_max_files 10 per chooser (0 turns uploads off; a single-file input takes 1)
upload_max_session_bytes 250 MiB per browser session

Security

  • Only the connection holding control can answer, and only the chooser currently offered. Watchers never receive file bytes.
  • File names are reduced to their last segment with control and reserved characters and leading dots removed, capped at 200 bytes. Names and contents are never logged or recorded; the recording gets a file_uploaded marker with the count and bytes.
  • accept is advisory, as it is in browsers.
  • Uploaded files live outside Playwright MCP's uploads directory.

Tests

  • browser-sidecar: 145 pass, 0 skipped, including a real-Chromium run: click through the live view, upload, the page reads the file, and the agent isn't blocked.
  • frontend: 591 pass.
  • actionagent (Ruby 4.0.6): 1688 runs, 0 failures, 7 skips.
  • rubocop: clean.

Open questions

  • Should a person be able to answer a chooser an agent opened? Today it's left to the agent.
  • Dropping files on a page's own drop zone isn't supported; drop works only while a chooser is open.
  • Ruby can't pass the upload_max_* limits to the sidecar yet, in either the local or the Incus backend.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CJAKwnkBaCGr5qjCcmc3kh

TonsOfFun and others added 3 commits October 14, 2026 17:08
A page's file input opened a chooser inside the headless browser, where
nobody could answer it. While someone drives the live view, the sidecar
now offers the chooser to it: the driver picks files (or drops them on
the view), their bytes go over the live view's WebSocket in 32 KiB
chunks, and the sidecar writes them to a private directory and sets
them on the input. Only the connection holding control may answer, and
only the chooser on offer; watchers are told the page is waiting.

Uploads are bounded per file, per chooser and per browser session, and
upload_max_files 0 turns them off. File names are reduced to a safe last
segment, and names and contents are never logged or recorded.

The relay listens only while someone holds control, so an agent's
chooser and a headed window's dialog behave as before, and it clears
the modal state Playwright MCP keeps for a chooser the person answered,
so the agent's next call is not refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJAKwnkBaCGr5qjCcmc3kh
File upload changes the browser sidecar, and @activeagents/browser-sidecar
1.9.0 is already on npm, so the sidecar moves to 1.9.1. CI requires the
sidecar's version to equal ActionAgent::VERSION, and activeagent is
released at the same version, so all three move together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJAKwnkBaCGr5qjCcmc3kh

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant