feat: implement web terminal for tool instances
- Add TerminalSession backend service for docker exec subprocess management
- Add TerminalManager for WebSocket session lifecycle management
- Create WebSocket endpoint at /ws/tool-instances/{id}/terminal
- Add session cookie authentication and instance ownership verification
- Install xterm.js with fit and web-links addons
- Create TerminalComponent with xterm.js integration
- Create TerminalPage with full-screen terminal view
- Add terminal route at /instances/:id/terminal
- Add terminal button to InstanceList for running instances
- Add terminal and arrow-left icons to icon registry
- Add comprehensive terminal CSS styles (dark theme, responsive)
Quality gates: typecheck ✓, lint ✓, build ✓, Python syntax ✓
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
# Tool Terminal Specification
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **WebSocket Terminal**: Provide terminal sessions via WebSocket at `/ws/tool-instances/{instance_id}/terminal`
|
||||
2. **Terminal I/O**: Stream stdin/stdout/stderr bidirectionally in real-time
|
||||
3. **Terminal Resize**: Support dynamic resize with COLS/ROWS updates
|
||||
4. **Session Management**: Multiple independent sessions per instance, cleanup on disconnect
|
||||
5. **Access Control**: Only instance owners can access, reject unauthorized with 403
|
||||
6. **Shell Spawn**: Spawn `/bin/bash` or `/bin/sh` inside container via `docker exec`
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
1. **Latency**: Character input to display < 50ms
|
||||
2. **Concurrent Sessions**: Support 10+ simultaneous terminal sessions
|
||||
3. **Browser Support**: Chrome, Firefox, Safari, Edge
|
||||
4. **Container Lifecycle**: Terminal closes when container stops
|
||||
|
||||
## API Specification
|
||||
|
||||
### WebSocket Endpoint
|
||||
|
||||
**URL:** `wss://{api_host}/ws/tool-instances/{instance_id}/terminal`
|
||||
|
||||
**Protocol:**
|
||||
- Connection requires valid session cookie
|
||||
- Binary frame: terminal output (stdout/stderr)
|
||||
- Text frame: control messages (JSON)
|
||||
|
||||
**Control Messages:**
|
||||
|
||||
Request (Client → Server):
|
||||
```json
|
||||
{
|
||||
"type": "resize",
|
||||
"cols": 80,
|
||||
"rows": 24
|
||||
}
|
||||
```
|
||||
|
||||
Response (Server → Client):
|
||||
```json
|
||||
{
|
||||
"type": "status",
|
||||
"status": "connected"
|
||||
}
|
||||
```
|
||||
|
||||
### REST Endpoint
|
||||
|
||||
**GET /tool-instances/{instance_id}/terminal** (HTML page)
|
||||
- Returns terminal page for the instance
|
||||
- Verifies ownership
|
||||
- Returns 404 if instance not found
|
||||
- Returns 403 if unauthorized
|
||||
|
||||
## Frontend Specification
|
||||
|
||||
### TerminalComponent
|
||||
|
||||
**Props:**
|
||||
```typescript
|
||||
interface TerminalProps {
|
||||
instanceId: string;
|
||||
instanceName: string;
|
||||
onClose?: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- xterm.js terminal with custom theme
|
||||
- WebSocket connection management
|
||||
- Auto-fit to parent container
|
||||
- Connection status indicator
|
||||
- Reconnect on disconnect (3 retries)
|
||||
|
||||
### TerminalPage
|
||||
|
||||
**Route:** `/instances/:instanceId/terminal`
|
||||
- Full-page terminal view
|
||||
- Shows instance name in header
|
||||
- Back button to instance list
|
||||
- Connection status badge
|
||||
|
||||
## Backend Specification
|
||||
|
||||
### TerminalManager
|
||||
|
||||
**Methods:**
|
||||
```python
|
||||
class TerminalManager:
|
||||
async def create_session(
|
||||
self,
|
||||
instance_id: uuid.UUID,
|
||||
user_id: uuid.UUID,
|
||||
websocket: WebSocket
|
||||
) -> TerminalSession
|
||||
|
||||
async def handle_resize(
|
||||
self,
|
||||
session_id: str,
|
||||
cols: int,
|
||||
rows: int
|
||||
) -> None
|
||||
|
||||
async def close_session(self, session_id: str) -> None
|
||||
```
|
||||
|
||||
### TerminalSession
|
||||
|
||||
**Responsibilities:**
|
||||
- Manage docker exec subprocess
|
||||
- Stream I/O between WebSocket and PTY
|
||||
- Handle resize signals
|
||||
- Cleanup on disconnect
|
||||
|
||||
**Docker Command:**
|
||||
```python
|
||||
async def spawn_shell(container_id: str) -> subprocess.Process:
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"docker", "exec", "-i", container_id, "/bin/bash",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
)
|
||||
return proc
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
**Backend:**
|
||||
- FastAPI WebSocket support
|
||||
- asyncio subprocess
|
||||
- docker CLI
|
||||
|
||||
**Frontend:**
|
||||
- `xterm` (v5.x)
|
||||
- `xterm-addon-fit`
|
||||
- `xterm-addon-web-links`
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Install xterm.js dependencies
|
||||
2. Create backend WebSocket endpoint
|
||||
3. Create TerminalManager and TerminalSession
|
||||
4. Create frontend TerminalComponent
|
||||
5. Add terminal route and navigation
|
||||
6. Test with running instances
|
||||
|
||||
## Testing
|
||||
|
||||
- Unit: TerminalSession I/O streaming
|
||||
- Integration: WebSocket connection lifecycle
|
||||
- Manual: Terminal functionality with real containers
|
||||
|
||||
## Quality Gates
|
||||
|
||||
- pytest
|
||||
- mypy
|
||||
- ruff
|
||||
- npm run typecheck
|
||||
- npm run lint
|
||||
- npm run build
|
||||
Reference in New Issue
Block a user