(remote): send a prompt to an unattached remote session (#219) - #397
Conversation
A live remote session that is not open in a terminal can now be given a prompt from its row. The text is written as one NDJSON line to the session's messaging socket through a single ssh, on stdin only, so it never reaches a command line. The socket path comes from the descriptor on the main side; the renderer sends only alias, session id and text. Refs #219
|
Reviewing |
|
Adversarial review at
Checked and holding: the prompt travels on stdin only; the remote command holds fixed text, an integer pid and the validated, single-quoted path; the alias cannot start with |
The remote command now runs ncat only if it has --send-only and nc only if its usage line lists -N and -U, else exits 127, so busybox and netcat-traditional get the install hint instead of a usage error. The 30 s dedupe key is reserved before the spawn, kept on a timeout (the line may already be on the socket) and released on a definite failure. Also: bound the text length before encoding, scope the dialog's key handler to the dialog, and attribute dialogs.js to its real path in the coverage test. Refs #219
|
Reviewing |
The remote command is now one single-quoted argument of sh -c, so a login shell such as fish never parses it. The nc usage pattern is the intended literal and is pinned through a real grep. The dialog overlay is focusable and clicks inside return focus to the textarea so Escape keeps working. Refs #219
|
Re-review at |
# Conflicts: # CHANGELOG.md
First part of #219: send a prompt to a live remote session that is not attached in a terminal, over the CLI's own messaging socket. The issue stays open for the follow-ups listed below.
What changed
remote-send.js(new): builds the NDJSON line{"type":"user","message":{"role":"user","content":...},"msgV":1,"session_id":...}(one trailing\n, embedded newlines stay escaped,session_idincluded so a descriptor whose pid was reused never reaches another session), builds the remote command, and maps its result. The remote command holds only fixed text, the integer pid and the single-quoted socket path: pid check,[ -S path ], thenncat --send-only -Uornc -N -U. Exit 7 = the pid is no longer aclaudeprocess, 8 = socket gone, 127 = noncat/nc.remote-attach.js:defaultRunRemoteCommandtakes aninputoption. stdin becomes a pipe,-nis dropped, the text is written and stdin closed. Same spawn site as before, soremote-ssh-spawn-sites.test.jsis untouched. A timeout reportstimedOut: true, which the adapter turns into a failure ("no confirmation that the line was written"), never a success.remote-send-prompt {alias, sessionId, text}(main.js,preload.js), modelled onremote-stop-session. The descriptor, and with it the socket path, is looked up main-side; the renderer never supplies a path. A session attached in a terminal is refused.public/dialogs.js. The dialog says "Sent", never "delivered", and keeps the text when a send fails.^/[A-Za-z0-9._/-]+\.sock$/ has../ exceeds 107 bytes, a line over 1 MiB of UTF-8, the same text to the same session within 30 s (injected clock; armed only by a successful send).docs/remote-hosts.md,.ai/contexts/session-cache.md; CHANGELOG entry.The prompt text never reaches a command line, local or remote. ssh is spawned with an argv array, no shell.
.keyfiles and the 8192-byte descriptor cap are untouched.Tests
test/remote-send.test.js: the line, path validation, the command, refusals without spawn, byte cap (boundary inclusive, multibyte), exit-code messages, timeout, 30 s dedupe, IPC contract, preload/main wiring. A realshruns the delivery segment with a fakenc/ncaton PATH and checks the exact stdin and argv; where/proc/<pid>/cmdlineexists it also runs the full command against a liveclaude-named process and a real unix socket (skipped on Windows).test/remote-run-input.test.js:-nabsent with input, stdin piped, exact write then close, timeout, stdin error.test/dom-sidebar-remote-send.test.js,test/dom-send-prompt-dialog.test.js: button on remote rows only, click does not open the row, CSS gating, dialog behaviour.remote-ssh-spawn-sites.test.jsandremote-stop.test.jsunchanged and green.session_iddropped;-nkept with input; stdin ignored / not closed;timedOutflag dropped; path regex loosened;..allowed; 107 to 108 bytes; length cap removed;buildSendCommandwithout validation; Windows pipe not special-cased; each of exit 7, 8, 127, timeout and non-zero mapped to success; cap measured on characters / exclusive / excluding the newline; dedupe window 29 s / never armed / armed on failure / ignoring session / ignoring host / real clock;nc -Nand--send-onlydropped; ncat never preferred;execdropped; payload-supplied socket path honoured; attached check removed; text type unchecked; preload leaking an extra field; pid guard removed; button on local rows; click bubbling; CSS showing it on attached rows; dialog saying "Delivered", clearing text on failure, sending empty or trimmed text, rendering the error as HTML.Not verified (needs a real host)
statusUpdatedAtmoves)./procwas not run on the machine this was written on.Out of scope
Inbox /
notify_when_idle/ replies; Windows hosts; trigger files targeting remote ids; the tier ladder (#218); launch and enrolment (#222); the attention state (#394).Refs #219