fix(desktop): make image paste work in remote and cloud workspace terminals

Merged
KI
3mo ago
+914-155

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/attachments naming 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

  • TerminalPane registers a per-terminal imagePasteOverride through the runtime registry (same pattern as link handlers; survives runtime eviction) when the workspace's hostId isn'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 existing filesystem.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 (.gitignore containing *, 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/_2 suffixes 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 .superset dir created — bug reproduced
  • Fix enabled, same gesture: .superset/attachments/image.png pasted as bracketed paste; cksum on the remote side identical to the clipboard bytes
  • .gitignore contains *; git status --porcelain empty; 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 check on all touched files
  • bun test — desktop full suite (2694 pass), packages/shared (773 pass, includes new workspace-attachments determinism 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.writeFile base64 has shipped since March.
  • Worktree dir instead of host temp dir: writeFile is confined to the worktree root by design; the self-ignoring .gitignore removes the git-dirt downside.
  • machineId + host-workspaces list instead of useWorkspaceHostTarget: 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_1 instead of _1.bashrc; a generated name colliding with a real attachment_N file suffixes instead of incrementing N.

Follow-ups

  • Agent-launch and mobile attachments still overwrite across batches (a second launch attaching another image.png clobbers 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 _N to avoid overwrites; .gitignore (*) keeps git clean; drop handler guards webkitGetAsEntry.
  • Runtime/Terminal: add per‑terminal imagePasteOverride in the runtime registry; TerminalPane enables it only when the workspace host differs from this machine; local terminals keep forwarding Ctrl+V.
  • Shared convention: new @superset/shared/workspace-attachments exports WORKSPACE_ATTACHMENTS_DIR and deterministic filename helpers; desktop agent launch adapter, prompt renderer, mobile composer, and buildLaunchSpec updated 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 real attachment_N now 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.