feat(sophia): deliver Ctrl-D EOF to foreground commands
The previous pipe layout shared one stdin pipe between term.Terminal
(prompt) and the foreground command. That conflated two lifetimes:
closing the pipe to deliver EOF to a wasm guest on Ctrl-D would also
take down the prompt, and not closing it left REPLs unable to exit
their inner read loop.
Split it into two pipes. The prompt pipe is long-lived — term.Terminal
reads it for the entire SSH session. The command pipe is rotated each
sh.Run: a fresh os.Pipe before the run, handed to the runner via
interp.StdIO, closed and dropped after.
The SSH-input pump now routes bytes by mode (gated on the existing
commandActive atomic): prompt pipe while idle, command pipe while a
command is foreground. While in command mode, it scans for VEOF
(Ctrl-D, 0x04) — the same byte a kernel PTY recognises — and on hit,
flushes anything before it, then closes the command's stdin so the
guest's next fd_read returns 0. A small mutex guards the rotating
write handle so the pump can race-safely close it from its goroutine
while the main loop swaps in the next pipe.
Assisted-by: Claude Opus 4.7 via Claude Code
Signed-off-by: Xe Iaso <me@xeiaso.net>