Unify file upload: server-driven clipboard + tmux injection - #660
Merged
Conversation
niiyeboahforce-pushed
the
agt_16399979a72d/dispatch-dev
branch
from
June 9, 2026 17:19
3b00537 to
267824aCompareniiyeboahforce-pushed
the
agt_16399979a72d/dispatch-dev
branch
from
June 10, 2026 19:27
c5c1b98 to
8097c6fCompareniiyeboah
commented
Jun 10, 2026
CollaboratorAuthor
Update: addressed architecture + infra review (both approved ✅)Ran author-requested architecture-review and infra-review passes on this branch. Both returned Correctness / robustness
Maintainability
Infra hardening
VerificationServer + web type checks clean · 92 server unit tests pass · web production build succeeds. |
Drop files onto the agent terminal → upload to the media store, insert `[File #N] <path>` (per-agent index, increments across drops). Drag-and-drop is always path-based (works on headless VMs). Image paste (Cmd/Ctrl+V) is hybrid: when the host can place the image on a clipboard the agent CLI can read (macOS pasteboard, or Linux+Xvfb), it injects natively (host clipboard + Ctrl+V → inline [Image #N]); otherwise it falls back to the path-based upload. Capability is reported by GET /api/v1/system/defaults (`clipboardImagePaste`); the agent env exports DISPLAY=$DISPATCH_COPY_DISPLAY so the CLI can read the Xvfb clipboard. Shiny status-gradient drop overlay + shared-ActivityBars uploading pill (role=status). Hardened the /api/v1/clipboard/image endpoint (stdin EPIPE guard). Incorporates frontend-ux / infra / architecture review fixes. Tests: command builder (DISPLAY), system defaults (capability), e2e (drag→media, paste native when capable, paste fallback when not). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Architecture review: - Extract hostClipboardImageCapable() so /system/defaults and /clipboard/image share one predicate (no drift; Linux branch now gated on it instead of an open-ended else). - Fix misleading /clipboard/image header + 150ms comments: the paste hybrid DOES call this route; drop the "remove this route" guidance. - pasteImage: single owner for the uploadingFiles flag per phase (clear before delegating to the path-based fallback). - Source clipboardImagePaste via a shared useSystemDefaults React Query hook; migrate create-agent-dialog off its duplicate raw fetch. - Soften the [File #N] counter comment (per-mount, cosmetic label). Infra review: - Replace the 150ms heuristic with a deterministic readiness poll (xclip owns the selection + advertises the target) and reject on timeout so the client can fall back — closes the silent, previously non-recoverable paste-failure race. - Track and SIGTERM the previous resident xclip before spawning a new one; document the orphan-on-restart behavior. - Rate-limit (30/min) and size-cap (8MB) /clipboard/image. - macOS temp file now written into a private mkdtemp dir (no predictable /tmp clobber); cleaned via rm. - Document the shared-Xvfb / cross-agent clipboard limitation on the DISPLAY export. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clear lastXclipPid in the proc 'exit' handler so a self-exited xclip's (possibly recycled) PID is never SIGTERM'd on the next paste. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…injection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Files callback Remove the dual uploadAndInsertFiles/pasteImage callbacks and their supporting refs (fileSeqRef, clipboardCapableRef, useSystemDefaults) from use-terminal.ts. Replace with a single uploadFiles callback that delegates delivery entirely to the server via the inject flag. The client no longer types paths into the terminal, tracks file numbering, or manages clipboard routing — the server handles all of it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clipboard-write logic moved to shared/lib/clipboard-write.ts (used by the media inject flow). The client no longer routes to a separate clipboard endpoint — all uploads go through the unified media endpoint. E2E tests updated to match: the two clipboard-endpoint tests are replaced by a single "pastes images via the media endpoint" test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use pasteText() (bracketed paste, no Enter) instead of sendCommand() for [File #N] injection. This matches the clipboard path behavior — both now paste without submitting, letting the user add context or review before sending. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each upload E2E test now checks the media list API to confirm the DB entry exists with correct name, source, and size — not just that the request was sent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract UploadingOverlay into its own component that logs on mount. The E2E test listens for that console message instead of injecting an 800ms route delay. Also wait for the upload response (not just the request) so the DB write has committed before asserting on media entries. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
selfcontainedforce-pushed
the
agt_16399979a72d/dispatch-dev
branch
from
June 12, 2026 14:32
2372a35 to
7a82956CompareThe server-side inject flow calls hostClipboardImageCapable() directly — the client no longer needs the capability flag. Drop it from the API response, client type, and unit test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
3 tasks
selfcontained added a commit
that referenced
this pull request
Jun 13, 2026
PR #660 unified file upload with server-driven clipboard + tmux injection, but the in-app Media docs didn't mention drag-and-drop or clipboard paste onto the terminal. Added an "Uploading files to agents" section covering both methods and updated the sidebar Share file paragraph to distinguish non-injected uploads. Added a file-upload ambient tip for feature discovery. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
selfcontained added a commit
that referenced
this pull request
Aug 11, 2026
- Fall back to a generic reason ("This action is currently unavailable.")
in the caption slot when a disabled shortcut has no caption of its own —
the tooltip alone isn't reachable on touch devices, so a caption-less
disabled pin previously went inert with zero visible explanation.
- Swap in a Ban icon for a disabled shortcut, ahead of the destructive
AlertTriangle swap, so it reads distinctly from the other blocked states
(agent-not-running, no-stable-id) that share the same dimmed styling.
- Rename ShortcutPinItem's local `disabled` prop to `agentUnavailable` to
stop it colliding, in reading, with the new `pin.disabled` field.
Addresses review #660 items #1415, #1416, #1417.selfcontained added a commit
that referenced
this pull request
Aug 11, 2026
* Add a disabled state to shortcut pins
Retiring a shortcut pin's action currently means deleting the pin — losing
the context of what was offered. Add disabled: boolean (shortcut pins only,
alongside icon/variant/confirm) so an agent can grey out a shortcut instead:
the button renders non-interactive (aria-disabled, dimmed, cursor-not-allowed,
no click delivery), and the existing caption field doubles as the reason
shown under it (e.g. "already building — agt_...").
- pin-merge.ts: disabled joins the shortcut-only fields stripped on re-type
- pin-run.ts / mcp-handlers.ts: server-side guard refuses to run a disabled
pin even if the client bypasses the greyed-out button
- pins-panel.tsx: disabled pins get their own tooltip reason, independent of
the existing "agent not running" / "no stable ID" unavailable states
- dispatch_pin tool description + schema document the new field
Motivated by an idea from the shortcut-pins dogfooding thread: an idea's
launch pin has to be deleted once its builder is already running.
* Address frontend-ux-review feedback on disabled pins
- Fall back to a generic reason ("This action is currently unavailable.")
in the caption slot when a disabled shortcut has no caption of its own —
the tooltip alone isn't reachable on touch devices, so a caption-less
disabled pin previously went inert with zero visible explanation.
- Swap in a Ban icon for a disabled shortcut, ahead of the destructive
AlertTriangle swap, so it reads distinctly from the other blocked states
(agent-not-running, no-stable-id) that share the same dimmed styling.
- Rename ShortcutPinItem's local `disabled` prop to `agentUnavailable` to
stop it colliding, in reading, with the new `pin.disabled` field.
Addresses review #660 items #1415, #1416, #1417.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /api/v1/agents/:id/mediagains aninject=trueform field. When set, the server decides the delivery mechanism (native clipboard for images when capable, or[File #N] <path>via tmuxsend-keys) and executes terminal injection itself — the client no longer manages delivery logic.uploadAndInsertFilesfor drag-drop,pasteImagefor clipboard paste) with a singleuploadFilescallback. RemovesfileSeqRef,clipboardCapableRef,pasteImageRef, and theuseSystemDefaultsdependency from the terminal hook. Net -88 lines on the client.POST /api/v1/clipboard/image(184 lines) — its logic now lives inshared/lib/clipboard-write.ts, called by the media inject flow.extensionForMime()— WebP gets.webp, GIF gets.gif(previously all non-PNG/JPEG got.jpg).code != nullfix for signal-killed xclip, settled-race guard in clipboard write, PID-reuse TOCTOU fix in xclip reaper.Test plan
pnpm run check— type check passes (server + web + scripts)pnpm run finalize:web— production build succeedspnpm run test— unit tests passpnpm run test:e2e— 171 E2E tests pass (including updated drag-drop/paste tests)[File #N] <path>appears in agent prompt🤖 Generated with Claude Code