Skip to content

v0.75.0 — the session mirror shows what your commands print

Choose a tag to compare

@ralyodio ralyodio released this 29 Aug 19:30
09692c4

The session mirror shows what your commands print

Open app.moshcode.sh/sessions/<id>, type a command, and watch the page show you a command line, a horizontal rule, a blank stretch, and shell exited (code 0). Everything between — the part you opened the page for — was never there.

The mirror was only ever wired into two launchers. /agents claude and the workflow tools run under a pseudo-terminal so a copy of their output can be streamed to the page (v0.68.0); everything else spawned with stdio: "inherit", which hands the child the terminal's own file descriptors. Those bytes go to the tty and nowhere else — they never pass through the moshcode process, so neither the teeOutput hook nor the pty capture could see them.

Which meant a shell command was invisible. That is most of what a pit does: !cmd, /shell, and every shell-valued /alias/prs, /issues, /merge, whatever you bound — all land in the same launcher. It reads as "stderr is missing," because the lines that do survive are the ones moshcode prints itself, and it prints them to stdout. Both streams were gone.

Measured rather than reasoned about, by pointing a real mirrored pit at a stand-in for the app and recording every byte it posted: on v0.74.0 a command's stdout and stderr both reach the terminal and neither reaches the session page — 919 bytes of the pit's own chrome around an empty middle.

The capture now lives in one place and defaults to the live mirror. captureSpec (src/pty.mjs) holds the whole dance — temp transcript, the flavour-specific script(1) argv, the follower, the banner strip, the cleanup — and reads the active mirror as its sink rather than waiting to be handed one. That inversion is the actual repair: every launcher taught by hand is one more that can be written without being, which is exactly how this happened.

Captured now: shell commands, /install, /upgrade, /plugin, /skill, /mcp, moshscript's shell() (so /run), /billing, and /payments connect. The blocking ones — those last three are spawnSync — hold the event loop, so their output arrives in one batch when the command finishes rather than live. Batched beats absent.

Two things are left uncaptured deliberately. The sudo prompt in escalate.mjs, because a password prompt is not something to route through a web page; and the tmux hand-offs, which give the terminal away wholesale.

Nothing changes for a pit that is not mirroring, a piped or scripted run, or a box with no script(1) we can drive: the spawn spec comes back exactly as passed and the old inherit path runs. MOSHCODE_MIRROR_PTY=0 still forces capture off.

Scrollback stopped losing the tail of a burst

The other half of the same symptom, from the app end. POST /api/sessions/:id/output stored chunk.slice(0, 20000) while publishing the whole chunk to everyone watching — so output past the row cap was on your screen live and gone after a reload, with nothing to say bytes were missing. A build that dumps a wall of text inside one 150ms flush is exactly that case. The row cap stays; the overflow becomes further rows in seq order, so a replay reassembles what was actually posted.

Upgrading

moshcode upgrade, or npm i -g moshcode@0.75.0.