0026: Terminals live in leashd
On this page
Status: accepted
Context
Each site gets terminals in the app: your shell in the site's folder, with the PATH and environment its processes run with. A terminal has to outlive the window (closing it keeps Short Leash in the menu bar), keep its output for when you come back, and show its output in the app as it arrives. The app holds no state of its own (everything comes from leashd), and nothing Short Leash runs may listen on the network or run as root.
Decision
- leashd owns the terminals.
leash_core::terminalsopens a pseudo-terminal withportable-pty(MIT; ConPTY on Windows) and starts your login shell in it ($SHELL -l, or PowerShell on Windows), as you, in the site's folder, with the environment the engine builds for the site's processes plusTERM=xterm-256colorandCOLORTERM=truecolor. For zsh, Short Leash's own startup files (ZDOTDIR) load yours and then put the site's tools first on PATH again. - The local API is the only way in. Create, input (base64), resize, rename, seen, restart and close are routes on leashd's unix socket (named pipe on Windows); output is a
terminalOutputevent on the same server-sent event stream as everything else. No terminal listens on a port. - Output is batched and numbered. A thread reads each PTY; a task gathers output for about a frame (16 ms) and sends it as one chunk whose
seqis one more than the last. The last 1 MiB is kept for replay;GET …/outputreturns it with theseqof its last chunk, so the app can skip chunks it already has. - Attention is read from the output. A bell (BEL outside OSC strings) or an OSC 9 or OSC 777 notification sets
attentionuntil the app says it was seen. A bell within half a second of typing (a shell's failed completion) doesn't count. - Activity is the name of the PTY's foreground process group leader (
tcgetpgrp), looked up withpsonce per new process, when it isn't the shell. Windows reports none. - Terminals outlive a restart as their output. Details and the last 256 KiB of each terminal's output are saved under
<home>/terminals/(folder0700, files0600). Amended by 0029: the details are kept in Short Leash's database, the newest 200 terminals; the output stays interminals/<id>.out. On start they're listed as ended withstoppedByRestart; restart starts a new shell in the same terminal. When leashd stops, every shell is hung up (SIGHUP, then SIGKILL for what's left).
Alternatives
- PTYs in the Tauri shell would put the shells next to the view, but they would end with the app, need state in the shell (which has no domain logic), and leave the CLI and other windows without them.
- A WebSocket per terminal would stream bytes with less overhead, but it means another listener; the existing event stream already carries live rows at this rate.
Consequences
- Terminals keep running with the window closed and survive the app's restart; a leashd restart ends them but keeps their output.
- Output reaches the app base64-encoded inside JSON, about a third larger than raw bytes, at most once per frame per terminal.
- Saved output can hold whatever a program printed, so it's readable only by you, like Short Leash's other private files.
- The shell runs your own startup files, so a slow
~/.zshrcslows a new terminal, as it would in any terminal app.