Three fixes for multi-session terminal bugs:
1. Race-condition double creation: The auto-create effect fired twice because
loadSessions returned 0 while an earlier createSession was still in flight.
Added hasAutoCreated guard ref to ensure only one auto-create happens.
2. Page reload spawns new sessions: After server restart, list_terminal_sessions
filtered out DB-only sessions (no in-memory counterpart), so the frontend
thought no sessions existed and auto-created new ones. Reverted the filter
so DB rows are always returned. The WebSocket handler now restores the
in-memory session from the DB row on demand when connecting.
3. No input after connection: The backend _read_loop would break on any send
error, causing asyncio.wait to cancel the _write_loop. Made _read_loop
retry up to 3 times before giving up, preventing transient send errors
from killing input handling.
Quality gates: pytest (15/15 passed), tsc clean
- Add TerminalSessionModel DB table with instance_id FK, name, status,
created_at, last_activity_at, closed_at columns
- Add Alembic migration for terminal_sessions table
- Refactor TerminalManager to use composite key (instance_id, session_id)
supporting up to 5 concurrent sessions per instance
- Add create_session, get_session, get_sessions_for_instance, close_session
- Preserve get_or_create_session for backward compatibility (default session)
- Fix attach_websocket to only close sockets within same session
- Add name (auto-generated 'Session N') and status tracking to TerminalSession
- Add 7 unit tests for multi-session logic
Quality gates: pytest (7 new passed, 174 total passed, 51 pre-existing failures)
- Add startup_command field to ToolType model and API
- Execute startup command before interactive shell in terminal sessions
- Add tmux and ranger to OpenCode container spec
- Update Tool Workshop UI with startup_command input for terminal types
- Add backend tests for startup_command CRUD operations
- Sync specs: tool-terminal, tool-types-definition, opencode-web-server
- New spec: tool-terminal-startup-command
Quality gates: Frontend typecheck/lint passed. Backend tests blocked by environment (Python/Docker not available).
OpenSpec: terminal-startup-and-container-tools
TerminalManager was trying to create an asyncio task at module import time,
but no event loop exists yet during import. This caused RuntimeError on startup.
Changes:
- _start_idle_check() now checks if event loop is running before creating task
- If no loop exists, silently skips (will be started lazily)
- Added lazy start call in get_or_create_session() when websocket connects
- Terminal sessions now persist across WebSocket disconnections
- Added circular output buffer (10KB) for replay on reconnect
- Added idle timeout cleanup (30 minutes)
- Added reset functionality via WebSocket message and HTTP endpoint
- Concurrent connections close old WebSocket when new one connects
- Frontend: Added reset button with confirmation dialog
- Frontend: Handle resetting status and reconnection
Refs: persistent-terminal-sessions