a919ff8611
- Add proposal, design, specs, and tasks for persistent terminal sessions - Support reconnection, output buffer replay, reset, and idle timeout Refs: persistent-terminal-sessions
74 lines
3.8 KiB
Markdown
74 lines
3.8 KiB
Markdown
## Context
|
|
|
|
Currently, terminal sessions are ephemeral. Each WebSocket connection to `/ws/tool-instances/{instance_id}/terminal` spawns a new `docker exec` process via `TerminalManager.create_session()`. When the WebSocket disconnects, the session is cleaned up and the `docker exec` process is killed. This means users lose their shell state, running processes, and command history every time they disconnect.
|
|
|
|
The current architecture:
|
|
- `TerminalManager` tracks sessions by `session_id` (UUID) in a dictionary
|
|
- Each session creates a new PTY and `docker exec` process
|
|
- WebSocket I/O loops are tied to the session lifecycle
|
|
- No concept of reconnection or session persistence
|
|
|
|
## Goals / Non-Goals
|
|
|
|
**Goals:**
|
|
- Terminal sessions persist across WebSocket disconnections/reconnections
|
|
- Users reconnect to the same shell process, maintaining state and history
|
|
- Add explicit "Reset Terminal" functionality to kill and restart the session
|
|
- Graceful handling of idle timeouts to clean up abandoned sessions
|
|
- Buffer recent output for replay on reconnect
|
|
|
|
**Non-Goals:**
|
|
- Multi-user shared terminal sessions (one session per instance, but only one active WebSocket at a time)
|
|
- Session persistence across container restarts or instance stops
|
|
- Full terminal scrollback history persistence (only recent buffer)
|
|
- Automatic session restoration after instance restart
|
|
|
|
## Decisions
|
|
|
|
**1. Session tracking by instance_id**
|
|
- Rationale: One persistent session per tool instance is the simplest model
|
|
- Alternative: Track by user_id + instance_id — rejected because it's overkill; users don't need multiple terminals to the same container
|
|
- Trade-off: Only one user can have an active terminal at a time per instance
|
|
|
|
**2. Detach WebSocket from session on disconnect**
|
|
- Rationale: Keep the `docker exec` process alive, just remove the WebSocket reference
|
|
- Implementation: Session stores a list of WebSocket connections (initially just one)
|
|
- On disconnect: remove WebSocket from session, don't kill process
|
|
- On reconnect: attach new WebSocket to existing session
|
|
|
|
**3. Circular buffer for output replay**
|
|
- Rationale: Users should see what happened while disconnected
|
|
- Size: 10KB buffer (configurable) — enough for ~100 lines of typical output
|
|
- Implementation: Buffer stores raw bytes, replayed on WebSocket attach
|
|
|
|
**4. Reset terminal via WebSocket message**
|
|
- Rationale: Users need a way to kill a stuck or corrupted session
|
|
- Implementation: JSON control message `{"type": "reset"}` kills process and starts fresh
|
|
- Alternative: HTTP endpoint — rejected because it's more complex and less intuitive
|
|
|
|
**5. Idle timeout cleanup**
|
|
- Rationale: Prevent resource leaks from abandoned sessions
|
|
- Timeout: 30 minutes of no WebSocket connections
|
|
- Implementation: Background task checks last_activity timestamp
|
|
|
|
## Risks / Trade-offs
|
|
|
|
- **[Risk] Zombie sessions**: Users disconnect and never reconnect, leaving `docker exec` processes running
|
|
- Mitigation: Idle timeout of 30 minutes cleans up abandoned sessions
|
|
- **[Risk] Session corruption**: If the shell process crashes, the session is dead but still tracked
|
|
- Mitigation: Health check on `docker exec` process; auto-reset on next connect if dead
|
|
- **[Risk] Concurrent connections**: Multiple tabs trying to connect to the same instance
|
|
- Mitigation: Only allow one active WebSocket per session; new connection closes old one with a message
|
|
|
|
## Migration Plan
|
|
|
|
1. Deploy updated backend services (TerminalManager, TerminalSession, terminal endpoint)
|
|
2. Deploy frontend changes (reset button, reconnection handling)
|
|
3. No database migration needed
|
|
4. Rollback: Revert to previous code; existing sessions will be killed on disconnect as before
|
|
|
|
## Open Questions
|
|
|
|
- Should we show a "session resumed" indicator in the UI?
|
|
- Should we persist the last N commands for command history?
|