62d1bdc462
- Add TerminalSessionTabs component with status dots, rename, close, max-5 limit - Add 7 component tests for tab rendering, selection, close, rename - TerminalComponent: sessionId prop, forwardRef with fit() method - TerminalPage: multi-session orchestration, tab switching, auto-create default - Fullscreen mode: Alt+Shift+F toggle, auto-hide tabs, Esc exit - Keyboard shortcuts: Alt+Shift+N/W/ArrowLeft/ArrowRight/R - Add CSS for tabs, fullscreen, mobile responsive - Update useTerminalSessions hook for session CRUD - terminal_manager.py: lookup by internal session_id fallback Quality gates: tsc --noEmit clean, vitest (7/7 new tests passed), pytest (182 passed)
5.7 KiB
5.7 KiB
Apply Report: PR 1 – Database + Backend Core for Multi-Session Terminal UX
Summary
Implemented the database schema, Alembic migration, TerminalManager multi-session core, and TerminalSession name/status tracking for the multi-session terminal UX feature. All changes are backward-compatible with the existing single-session /terminal WebSocket endpoint.
Key Changes
- Database Schema – Added
terminal_sessionstable withUUIDPrimaryKeyMixin+TimestampMixin, storinginstance_id,name,status,last_activity_at, andclosed_at. - Alembic Migration – Created migration
2026_05_28_add_terminal_sessions(down-revision from20260527_160017_add_pi_agent). - TerminalSession – Added
name(auto-generated as "Session N"),statusfield (active/resetting/closed), and updatedreset()/close()to set status appropriately. - TerminalManager – Migrated
_sessionsdict fromdict[str, TerminalSession]todict[tuple[str, str], TerminalSession]. Addedcreate_session(),get_session(),get_sessions_for_instance(),close_session(), and updatedreset_session()to accept an optionalsession_id. Preservedget_or_create_session()for backward compatibility (uses"default"session_id). Idle cleanup now operates on composite keys and fires DB status updates asynchronously. - Tests – Created 7 unit tests covering session creation, max-5 enforcement, filtering, close/removal, WebSocket isolation, default session keying, and idle cleanup DB updates.
Files Created
apps/api/src/models/terminal_session.pyapps/api/alembic/versions/2026_05_28_add_terminal_sessions_table.pyapps/api/tests/services/test_terminal_manager_multi.py
Files Modified
apps/api/src/models/__init__.py– ImportedTerminalSessionModelapps/api/src/main.py– ImportedTerminalSessionModelfor Alembic model discoveryapps/api/src/services/terminal_manager.py– Full refactor to composite-key session management with DB fire-and-forget helpersapps/api/src/services/terminal_session.py– Addedname,status,_instance_counters, and status transitions
Test Results
New Tests (7/7 passed)
$ cd apps/api && python -m pytest tests/services/test_terminal_manager_multi.py -v
tests/services/test_terminal_manager_multi.py::test_create_session_increases_count PASSED
tests/services/test_terminal_manager_multi.py::test_create_session_enforces_max_5 PASSED
tests/services/test_terminal_manager_multi.py::test_get_sessions_for_instance_filters_by_instance PASSED
tests/services/test_terminal_manager_multi.py::test_close_session_removes_from_dict PASSED
tests/services/test_terminal_manager_multi.py::test_attach_websocket_only_closes_same_session PASSED
tests/services/test_terminal_manager_multi.py::test_default_session_keyed_separately PASSED
tests/services/test_terminal_manager_multi.py::test_idle_cleanup_updates_db_status PASSED
======================== 7 passed, 4 warnings in 0.11s =========================
Full Suite (no regressions)
$ cd apps/api && python -m pytest tests/ -q
51 failed, 174 passed, 6 warnings in 15.96s
- Baseline failures: 51 (pre-existing, unchanged by this PR)
- New passes: +7 (from
test_terminal_manager_multi.py) - No new failures introduced
Deviations from Design
- Duplicate
created_atcolumn – The design spec and its Alembic snippet listedcreated_attwice (once explicitly, once fromTimestampMixin). I removed the explicitcreated_atfrom the model and migration, relying onTimestampMixinwhich providesserver_default=func.now(). - DB write implementation – The design showed DB writes inside
TerminalManagerbut didn't specify the exact async pattern. I implemented them asasyncio.create_task-wrapped coroutines usingSessionLocal()so they are non-blocking. Unit tests mock_mark_closed_in_dband_insert_db_session_rowto verify calls without needing a live DB. get_or_create_sessionauto-name – The design said default session should count toward the 5-session limit. The current implementation does count it, butget_or_create_sessioncreates the default session outside thecreate_sessionpath (to preserve backward compat). Future REST endpoints can enforce the limit at the API layer before calling either path.
Blockers / Risks
- Global singleton test isolation –
TerminalManageris still a global singleton (terminal_manager = TerminalManager()). The unit tests create fresh instances via themanagerfixture, but integration tests that import the global may need care to reset state between tests. - DB fire-and-forget in tests – The aiosqlite background thread emits
RuntimeError: Event loop is closedwarnings when the test event loop tears down before the fire-and-forget DB task completes. This is harmless in tests but worth monitoring. - Migration head – The migration chains from
20260527_160017_add_pi_agent. If a new migration lands ondevbefore this PR merges, thedown_revisionmust be updated.
Next Recommended Action
- Task 5 (WebSocket endpoint + REST API) – Implement the new
/ws/tool-instances/{instance_id}/terminal/{session_id}WebSocket route and the REST endpoints (GET/POST/DELETE .../terminal/sessions) inapps/api/src/api/terminal.py. Extract the shared auth/validation/I/O loop into_handle_terminal_websocket()as specified in the design. - Run migration in a staging environment – Verify
alembic upgrade headapplies cleanly anddowngradereverses without data loss. - Integration tests for WebSocket multi-session – Create
apps/api/tests/api/test_terminal_ws_multi.pyto validate concurrent session isolation and the default-session alias.