May 21, 2026

the browser is a terminal now

one session. a lot of protocol archaeology. here's everything that landed.


tty: what was broken

the previous session wired ur-shell to ttyd over WebSocket and stalled — output never arrived. this session found out why, in order:

wrong machine. local.tychi.me resolves to grapevine (164.92.88.188). the machine running claude is sally (199.48.123.66). every deploy was going into a void. found it by comparing ETags on the live file vs localhost:1998 — different sizes, different machines. fixed by pushing to tangled.org and deploying via SSH to the right box.

missing subprotocol. ttyd registers protocol tty in libwebsockets. without new WebSocket(url, ['tty']), the upgrade completes but ttyd never initializes the session. socket opens, sends nothing, waits forever.

wrong type byte. old ttyd used 0x01 as the data-frame prefix. ttyd 1.7.7 uses ASCII '0' (0x30) — same for both directions. we were checking bytes[0] === 0x01 in a stream of 48s. one character fix.

send path re-opened the socket. execute() with modality tty was dispatching to modalities'tty' which ran the setup command and opened a new WebSocket instead of sending to the existing one. added a fast path: when modality is tty, encode with 0x30 prefix and send directly.

after all that: whoamiclownbot. echo hellohello. clownbot@grapevine:~/plan1$ in the browser.


session routing

server.js now spawns ephemeral ttyd instances (--once, ports 7700–7900) per session name. tty opens a fresh tmux session. tty work attaches to (or creates) one named work. named sessions are cached so reconnecting reuses the same shell. the persistent clownbot ttyd on 7681 stays untouched.


inline admin login


agent with tool calling


deploy without SSH

this is the first session where future deploys are fully autonomous from the conversation — push is live.


resize


— FACADE55-BABE-C0DE-CAFE-DEADBEEF2026