Repository navigation
Conversation
The dashboard Chat tab is an xterm mirror of a TUI running inside the gateway (often a remote Docker container). Ctrl+V / `/paste` route to the server-side clipboard, which has no access to the browser's clipboard, so pasting a screenshot always fails with "No image found in clipboard" (issue NousResearch#24860). Handle it fully client-side: intercept the DOM `paste` event on the terminal host, and when the clipboard holds an image, upload the bytes the browser already has to the gateway's managed-files root via the existing /api/files/upload-stream endpoint, then drive the TUI's existing `/image <path>` command over the PTY. No new RPC, no server changes. Text paste is untouched — the handler only fires when an image is present and otherwise lets the event through. Adds web/src/lib/chatImagePaste.ts (clipboard parsing + upload helper) with unit tests for the clipboard-extraction logic.
✅ Validated live (end-to-end)Tested against a running dashboard + gateway (Hermes v0.18.0, Unraid/Docker, loopback mode) by building this branch's web bundle, pointing Confirmed the full chain fires:
Text paste is unaffected (handler no-ops when the clipboard holds no image). Note: exercised via a synthetic |
|
Superseded by #61929 — thanks @bird! Kept your client composition ( Closing in favor of #61929. |
|
Superseded by #61929. |
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
Dashboard Chat is an xterm mirror of a TUI inside the gateway, so server-side clipboard.paste never sees the browser clipboard. Upload pasted/dropped images to the profile's images/ dir (same place clipboard.paste / image.attach use), then drive /image over the PTY. Uses a dedicated /api/chat/image-upload endpoint (magic-byte check, 25MB cap, profile scope) instead of relative managed-files uploads that 400 on local dashboards without a locked root. Ctrl/Cmd+Shift+V also tries clipboard.read() for images before falling back to text, since preventDefault on that chord suppresses the DOM paste event. Salvages NousResearch#57912 (client composition + /image PTY drive) and folds in NousResearch#48563's upload endpoint + drop path. Co-authored-by: bird <6666242+bird@users.noreply.github.com> Co-authored-by: tt-a1i <53142663+tt-a1i@users.noreply.github.com>
What does this PR do?
Makes image paste work in the dashboard Chat tab.
The Chat tab is an xterm mirror of a TUI running inside the gateway (often a
remote Docker container).
Ctrl+V//pasteroute to the server-sideclipboard.pasteRPC, which shells out toxclip/wl-pasteinside thecontainer. That container has no X11/Wayland display and no access to the
browser's clipboard, so pasting a screenshot always fails with "No image found
in clipboard".
This handles it fully client-side, reusing endpoints that already ship — no
new RPC, no server changes, no new deps:
pasteevent on the terminal host element.preventDefault()and upload the bytes thebrowser already has via the existing
POST /api/files/upload-stream(
api.uploadFile). It lands atuploads/paste/paste_<timestamp>.<ext>underthe gateway's
HERMES_HOME./image <path>slash command over the PTYWebSocket, using the same burst-then-Return timing as
handleCopyLast.Text paste is untouched — the handler only fires when an image is present and
otherwise lets the event fall through to the existing text path. This works
identically in loopback and gated/OAuth-cookie auth modes because
api.uploadFile→fetchJSONalready attaches the session token / sendscredentials: 'include'.Related Issue
Fixes #24860 (the image-paste half; text paste is tracked separately in #29975).
Type of Change
Changes Made
web/src/lib/chatImagePaste.ts(new) —firstImageFromClipboard()(DataTransfer parsing, incl. Safari/Firefox
files[]fallback) anduploadPastedImage()(size cap, ext sniff, upload → absolute container path).Kept out of the component so the clipboard logic is unit-testable.
web/src/lib/chatImagePaste.test.ts(new) — 5 vitest cases for theclipboard-extraction logic.
web/src/pages/ChatPage.tsx— register/unregister the hostpastelistenerinside the terminal effect; surface upload/connection errors inline in the
terminal.
How to Test
hermes dashboard --host 0.0.0.0 --tui --no-openCtrl+V./image— send your messageand the agent receives the image. Pasting plain text still behaves as before.
Automated checks (run in
web/):npm run typecheck→ passes (exit 0)npx vitest run src/lib/chatImagePaste.test.ts→ 5/5 passnpx eslinton the changed files → 0 errorsChecklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — N/A (frontend-only change; ranwebtypecheck + vitest instead, see above)Documentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/A (no config keys)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AScreenshots / Logs
Scope note for reviewers: the automated checks above cover typecheck, the
pure clipboard-parsing unit tests, and lint. I did not run a live
end-to-end paste against a running dashboard + gateway, so the runtime wiring
(DOM
paste→ upload →/imageover the PTY) is validated by constructionrather than an interactive session. The upload endpoint,
api.uploadFile, andthe
/imageTUI command are all pre-existing and exercised elsewhere; this PRonly composes them. A quick manual paste smoke-test before merge is worth doing.