fix(desktop): make image paste work in remote and cloud workspace terminals
Summary
- Pasting an image (or dropping files) into a terminal whose PTY runs on another machine — a relay-reached host or a cloud sandbox — now uploads the bytes into the workspace and pastes a worktree-relative path, instead of forwarding a Ctrl+V the remote TUI can't do anything with.
- Extracts the
.superset/attachmentsnaming convention into@superset/shared/workspace-attachments, replacing three hand-rolled copies (agent-launch adapter, its prompt renderer, mobile composer) that had to stay byte-identical but were kept in sync only by a comment.
Why / Context
Image paste forwards \x16 and lets the TUI (Claude Code, Codex) read the OS clipboard — of the machine the PTY runs on. A remote host or sandbox never holds the user's local screenshot, so the paste silently did nothing (Codex shows "Failed to paste image"). Users hit this on every remote/cloud workspace.
How It Works
TerminalPaneregisters a per-terminalimagePasteOverridethrough the runtime registry (same pattern as link handlers; survives runtime eviction) when the workspace'shostIdisn't this machine's — a cloud sandbox has no host row at all, so "list ready and no row" also counts as remote.- The override reads the clipboard
Files, writes them via the existingfilesystem.writeFile(base64) into<worktree>/.superset/attachments/— the same directory agent-launch attachments and mobile composer attachments use — and pastes the shell-escaped worktree-relative paths. - The directory self-ignores (
.gitignorecontaining*, create-only) so attachments never dirty git status; this also covers launch/mobile attachments, which previously showed as untracked. - Uploads are create-only and probe
_1/_2suffixes against the real directory, so a repeat paste never overwrites a file an earlier prompt referenced (the batch-scoped dedupe in launch/mobile can't see prior batches). - Drag-drop of plain files onto a remote terminal reroutes through the same upload (dropped local paths point at nothing on the remote machine); folder drops and all local-workspace behavior are unchanged.
Manual QA (done — verified over CDP against the real app)
Stood up a genuinely remote target in dev: a second standalone host-service with a distinct machine identity, registered against the dev API and local relay, and a session workspace created on it through the actual new-workspace UI (Blaxel keys in dev are placeholders, so a real sandbox wasn't reachable; kind: "sandbox" takes the same code path as kind: "remote" in this change).
- Baseline with the fix disabled: real Cmd+V (image on OS clipboard) pastes nothing, no
.supersetdir created — bug reproduced - Fix enabled, same gesture:
.superset/attachments/image.pngpasted as bracketed paste;cksumon the remote side identical to the clipboard bytes -
.gitignorecontains*;git status --porcelainempty; Changes panel shows "No changes" - Second paste of the same image lands as
image_1.png(no overwrite), live over the relay - Local workspace terminal: paste still forwards Ctrl+V, nothing uploaded, no dir created
Testing
bun run typecheck(desktop, packages/shared)bunx biome checkon all touched filesbun test— desktop full suite (2694 pass), packages/shared (773 pass, includes newworkspace-attachmentsdeterminism tests), mobile (33 pass)- New unit tests: paste-override routing in the fallback, upload helper (relative paths, gitignore create-only, collision probing, sanitization), shared naming (two independent passes over the same list produce identical names — the writer/renderer contract)
- Mutation-checked the new tests fail when the logic is broken
Design Decisions
- Renderer-only instead of a new host endpoint: deployed sandboxes bake host-service into their image and never update, so a server-side change wouldn't reach the affected users.
filesystem.writeFilebase64 has shipped since March. - Worktree dir instead of host temp dir:
writeFileis confined to the worktree root by design; the self-ignoring.gitignoreremoves the git-dirt downside. machineId+ host-workspaces list instead ofuseWorkspaceHostTarget: that hook mints sandbox access credentials in an effect per consumer; long-lived terminal panes would multiply the broker loop.- Directory-probing dedupe instead of the convention's batch-scoped set: pastes accumulate across a session; overwriting would silently swap an image an earlier prompt referenced.
Known Limitations
- Not exercised against a real Blaxel sandbox (no real API key in dev); the detection and transport are identical for
kind: "sandbox". - Naming edge changes from the refactor (writer and prompt renderer move in lockstep): dotfiles dedupe as
.bashrc_1instead of_1.bashrc; a generated name colliding with a realattachment_Nfile suffixes instead of incrementing N.
Follow-ups
- Agent-launch and mobile attachments still overwrite across batches (a second launch attaching another
image.pngclobbers the first one's file). Adopting the paste path's directory probing there is a deliberate behavior change per surface — worth its own ticket.
Summary by cubic
Enables image/file paste in remote and cloud workspace terminals by uploading clipboard files into the workspace and pasting worktree‑relative paths. Previously we forwarded Ctrl+V, which remote TUIs could not use.
- Remote terminals: intercept image/file paste and plain-file drag/drop, write via filesystem.writeFile to
WORKSPACE_ATTACHMENTS_DIR(.superset/attachments/), then paste shell‑escaped relative paths; create‑only writes probe_Nto avoid overwrites;.gitignore(*) keeps git clean; drop handler guardswebkitGetAsEntry. - Runtime/Terminal: add per‑terminal
imagePasteOverridein the runtime registry;TerminalPaneenables it only when the workspace host differs from this machine; local terminals keep forwarding Ctrl+V. - Shared convention: new
@superset/shared/workspace-attachmentsexportsWORKSPACE_ATTACHMENTS_DIRand deterministic filename helpers; desktop agent launch adapter, prompt renderer, mobile composer, andbuildLaunchSpecupdated so writers and renderers produce identical paths. - Naming and safety: reject
"."/".."; case‑insensitive same‑batch dedupe; dotfiles dedupe as.bashrc_1; generated names colliding with a realattachment_Nnow suffix (e.g.,attachment_2_1); refuse files >50MB and batches >200MB before reading; errors surface as toasts. - Tests/docs: add override routing and upload coverage (including fast‑fail that does no reads or directory creation); extract
fileToBase64; document sandbox clipboard mismatch; renderer‑only, no host‑service changes.
Summary by CodeRabbit
- New Features
- Paste images and files into terminals in remote workspaces; uploads are inserted as workspace-relative paths.
- File drops and clipboard uploads now provide consistent attachment handling across desktop and mobile.
- Attachment filenames are sanitized and collision-resistant.
- Uploads are limited to 50 MB per file and 200 MB per batch.
- Bug Fixes
- Improved handling of unsupported clipboard content and upload failures with fallback behavior and error notifications.
- Documentation
- Documented clipboard limitations and attachment handling for remote hosts.