- Box init + chownInBox exec explicitly as root: under podman keep-id the
container's default user is the mapped uid, which cannot useradd/chown.
- Init renames an existing uid-1000 passwd entry (podman keep-id injects one
named after the HOST user) instead of adding a duplicate that would win the
getpwuid lookup and make whoami report the wrong name.
- Terminal connections tag their shell with a per-connection marker and reap
the process group on WebSocket close: closing the attach socket never
killed the exec'd shell, so every disconnect leaked an attached tmux client
(session stuck at the smallest zombie's size; enough dead clients starve
tmux rendering for live ones).
- Job image: nvm 0.39.5 at /usr/local/nvm (user-writable) with
/etc/profile.d/nvm.sh, matching what user dotfiles expect; docs for the box
user + ownership model.
The workspace terminal died everywhere: bun's node:http client never
emits the client 'upgrade' event (it surfaces the 101 as a plain
response), so docker-modem's exec.start({hijack:true}) waited forever —
a connected WebSocket with no bytes flowing (prod), or an instant close
when the socket was unreachable (dev). The pre-Phase-1 bridge avoided
dockerode attach for exactly this reason; the real-TTY-resize rewrite
reintroduced it.
- runtime/exec-stream.ts: speak the exec-start HTTP upgrade over a raw
net socket (docker and podman both answer 101 UPGRADED). Works under
bun and node; honors the modem's socketPath/host:port. The stream is
returned paused so the first screen paint can't race the caller's
data handler. Exec create/resize stay on dockerode (plain POSTs).
- user-container.ts: acquireUserBox re-ensures the container on every
acquire instead of trusting a cached "initialized" flag, so a box
removed behind the worker's back (manual rm, another instance's
reaper) is recreated instead of bricking terminals and agent turns
until restart.
- terminal.ts: bridge failures are logged and close the WebSocket with
a clamped human-readable reason instead of a silent catch;
xterm-session.tsx displays that reason instead of a mute
"Session ended". Also roomier terminal line spacing (1.2 -> 1.35).
The backend aiProviderProfiles authType union added 'anthropic_oauth_json'
but the Expo provider form's local AuthType type was stale, breaking
typecheck. Add the literal, a picker option, help text, and multiline
handling to match the Next app.
Claude-Session: https://claude.ai/code/session_01ScyZ1NhfN84LAnHpwhfEQk
Wire copy-on-select in XtermSession (shared by the workspace terminal
and the /machine terminal): a header checkbox toggles the behavior and
persists to localStorage under spoon.terminal.copyOnSelect. On mount the
stored value takes precedence over the copyOnSelect prop default. When
enabled, onSelectionChange copies the current selection via
navigator.clipboard.writeText — guarded (empty selections skipped;
clipboard-unavailable/denied errors swallowed). The handler reads the
toggle through a ref so live flips apply without reconnecting.
WebLinksAddon remains loaded so printed URLs are clickable.
Manual verification: in /machine and a workspace terminal, enabling
copy-on-select and selecting text places it on the clipboard; a printed
URL is clickable and opens in a new tab.