In-browser terminal attached to worker tmux session #7

Closed
opened 2026-05-14 10:49:52 +02:00 by lz · 0 comments
Owner

Summary

Add a Terminal button to each session card that opens a browser-based terminal attached to the worker container's claude tmux session — without requiring the user to leave the UI or run docker exec manually.

Motivation

Each nexus-worker container runs tmux as PID 1 with a session named claude. Currently the only way to inspect live agent output, send input, or interrupt a stuck run is to docker exec -it <container> tmux attach from the host. This is fine for power users but breaks the self-contained UX that agent-nexus otherwise provides, and it's completely unavailable to anyone accessing the UI remotely (e.g. from the Claude mobile app flow).

Since agent-nexus already has:

  • the Docker socket (via the socket proxy) with exec on the allowlist
  • full knowledge of which containers are managed (nexus-managed=true)
  • a Fastify backend that could trivially host a WebSocket endpoint

...the infrastructure to build this is essentially already in place.

Proposed implementation

Backend (nexus/):

  • Add a WebSocket route, e.g. GET /api/sessions/:id/terminal
  • On connection: call docker exec against the worker container via the socket proxy, passing -it to allocate a PTY, with the command tmux attach -t claude
  • Bridge the WebSocket to the exec PTY stream bidirectionally
  • Handle terminal resize by forwarding SIGWINCH via the Docker exec resize API (POST /exec/:id/resize?h=&w=)
  • Detach cleanly on WebSocket close without killing the tmux session (send tmux detach-client or close the exec stream)

Frontend (nexus/web/):

  • Add a terminal icon/button to the session card
  • On click: open a modal or slide-over panel containing an xterm.js instance (already a natural fit given the project's node-pty/xterm.js lineage)
  • Wire xterm.js onData → WebSocket send, and WebSocket onmessagexterm.write()
  • Use xterm's FitAddon to track terminal dimensions and send resize events to the backend

Relevant constraints

  • The docker-socket-proxy allowlist already permits exec endpoints (EXEC_CREATE, EXEC_START, EXEC_INSPECT in the Tecnativa proxy config) — no proxy config change should be needed, but worth verifying EXEC_RESIZE is also allowed
  • The exec must run as the same user that owns the tmux socket inside the container, otherwise tmux attach will fail with a socket permission error
  • The tmux session name is currently hardcoded as claude (per the worker entrypoint) — the backend can rely on this
  • Care should be taken not to send exit or kill-session accidentally on WebSocket disconnect; prefer detach-client -s claude before closing the exec stream

Out of scope for this issue

  • Read-only log streaming (separate concern, possibly simpler)
  • Multiple simultaneous terminal panels per session
  • Any changes to the worker image itself
### Summary Add a **Terminal** button to each session card that opens a browser-based terminal attached to the worker container's `claude` tmux session — without requiring the user to leave the UI or run `docker exec` manually. ### Motivation Each `nexus-worker` container runs tmux as PID 1 with a session named `claude`. Currently the only way to inspect live agent output, send input, or interrupt a stuck run is to `docker exec -it <container> tmux attach` from the host. This is fine for power users but breaks the self-contained UX that agent-nexus otherwise provides, and it's completely unavailable to anyone accessing the UI remotely (e.g. from the Claude mobile app flow). Since agent-nexus already has: - the Docker socket (via the socket proxy) with `exec` on the allowlist - full knowledge of which containers are managed (`nexus-managed=true`) - a Fastify backend that could trivially host a WebSocket endpoint ...the infrastructure to build this is essentially already in place. ### Proposed implementation **Backend** (`nexus/`): - Add a WebSocket route, e.g. `GET /api/sessions/:id/terminal` - On connection: call `docker exec` against the worker container via the socket proxy, passing `-it` to allocate a PTY, with the command `tmux attach -t claude` - Bridge the WebSocket to the exec PTY stream bidirectionally - Handle terminal resize by forwarding `SIGWINCH` via the Docker exec resize API (`POST /exec/:id/resize?h=&w=`) - Detach cleanly on WebSocket close without killing the tmux session (send `tmux detach-client` or close the exec stream) **Frontend** (`nexus/web/`): - Add a terminal icon/button to the session card - On click: open a modal or slide-over panel containing an **xterm.js** instance (already a natural fit given the project's node-pty/xterm.js lineage) - Wire xterm.js `onData` → WebSocket send, and WebSocket `onmessage` → `xterm.write()` - Use xterm's `FitAddon` to track terminal dimensions and send resize events to the backend ### Relevant constraints - The docker-socket-proxy allowlist already permits `exec` endpoints (`EXEC_CREATE`, `EXEC_START`, `EXEC_INSPECT` in the Tecnativa proxy config) — no proxy config change should be needed, but worth verifying `EXEC_RESIZE` is also allowed - The exec must run as the same user that owns the tmux socket inside the container, otherwise `tmux attach` will fail with a socket permission error - The tmux session name is currently hardcoded as `claude` (per the worker entrypoint) — the backend can rely on this - Care should be taken not to send `exit` or `kill-session` accidentally on WebSocket disconnect; prefer `detach-client -s claude` before closing the exec stream ### Out of scope for this issue - Read-only log streaming (separate concern, possibly simpler) - Multiple simultaneous terminal panels per session - Any changes to the worker image itself
lz closed this issue 2026-05-24 11:54:16 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lz/agent-nexus#7
No description provided.