Shell WebSocket API

The Shell WebSocket API provides real-time terminal access with session management, command execution, and output streaming.

Connection

WebSocket Endpoint

ws://localhost:8080/v1/shell/ws

Connection Options

Parameter Type Required Description Example
session_id string No Connect to existing session, creates new if omitted abc123-def456

Examples

Create new session:

const ws = new WebSocket('ws://localhost:8080/v1/shell/ws');

Connect to existing session:

const ws = new WebSocket('ws://localhost:8080/v1/shell/ws?session_id=abc123');

Message Protocol

All messages are JSON-formatted with type and data fields.

Client Messages (Send to Server)

Input Command

Send user input to terminal:

{
  "type": "input",
  "data": "ls -la\n"
}

Resize Terminal

Adjust terminal dimensions:

{
  "type": "resize",
  "data": {
    "cols": 80,
    "rows": 24
  }
}

Heartbeat Response

Respond to server ping:

{
  "type": "pong",
  "timestamp": 1703123456789
}

Server Messages (Receive from Server)

Session ID Notification

Sent when new session is created:

{
  "type": "session_id",
  "data": "c4058a14-148f-4bcc-b608-a092245c6f16"
}

Terminal Output

Real-time command output:

{
  "type": "output",
  "data": "total 8\ndrwxr-xr-x  2 root root 4096 Jan  1 12:00 .\n"
}

Session Restored

When connecting to existing session:

{
  "type": "terminal_restored",
  "data": "Session abc123 restored"
}

Terminal Ready

New session is ready:

{
  "type": "ready",
  "data": "Terminal ready - Session: abc123"
}

Heartbeat Ping

Server keepalive (every 30 seconds):

{
  "type": "ping",
  "data": 1703123456789
}

Error Messages

Various error conditions:

{
  "type": "error",
  "data": "Session not found"
}

Complete Integration Example

Here's a full working example with xterm.js:

<!DOCTYPE html>
<html>
<head>
    <title>AIO Sandbox Terminal</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css" />
    <style>
        body { margin: 0; padding: 20px; background: #1e1e1e; color: white; }
        .terminal-container { width: 100%; height: 500px; background: #000; }
        .controls { margin-bottom: 20px; }
        button { padding: 8px 16px; margin-right: 10px; }
    </style>
</head>
<body>
    <div class="controls">
        <button onclick="terminal.connect()">Connect</button>
        <button onclick="terminal.disconnect()">Disconnect</button>
        <button onclick="terminal.clear()">Clear</button>
    </div>
    <div id="terminal" class="terminal-container"></div>

    <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>

    <script>
        class AIOTerminal {
            constructor() {
                this.terminal = new Terminal({
                    cursorBlink: true,
                    fontSize: 14,
                    fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace'
                });
                
                this.fitAddon = new FitAddon.FitAddon();
                this.terminal.loadAddon(this.fitAddon);
                this.terminal.open(document.getElementById('terminal'));
                this.fitAddon.fit();
                
                this.websocket = null;
                this.sessionId = null;
                
                // Handle user input
                this.terminal.onData(data => this.sendInput(data));
                this.terminal.onResize(({ cols, rows }) => this.sendResize(cols, rows));
            }
            
            connect(sessionId = null) {
                const wsUrl = `ws://localhost:8080/v1/shell/ws${sessionId ? `?session_id=${sessionId}` : ''}`;
                this.websocket = new WebSocket(wsUrl);
                
                this.websocket.onopen = () => {
                    console.log('Connected to AIO Sandbox terminal');
                    this.sendResize(this.terminal.cols, this.terminal.rows);
                };
                
                this.websocket.onmessage = (event) => {
                    const message = JSON.parse(event.data);
                    this.handleMessage(message);
                };
                
                this.websocket.onclose = () => {
                    console.log('Terminal connection closed');
                };
                
                this.websocket.onerror = (error) => {
                    console.error('Terminal connection error:', error);
                };
            }
            
            handleMessage(message) {
                const { type, data } = message;
                
                switch (type) {
                    case 'session_id':
                        this.sessionId = data;
                        console.log('Session ID:', data);
                        break;
                        
                    case 'output':
                    case 'restore_output':
                        this.terminal.write(data);
                        break;
                        
                    case 'ready':
                    case 'terminal_restored':
                        this.terminal.write(`\\x1b[32m${data}\\x1b[0m\\r\\n`);
                        break;
                        
                    case 'ping':
                        this.sendMessage({ type: 'pong', timestamp: data });
                        break;
                        
                    case 'error':
                        this.terminal.write(`\\r\\n\\x1b[31mError: ${data}\\x1b[0m\\r\\n`);
                        break;
                        
                    default:
                        console.log('Unknown message type:', type, data);
                }
            }
            
            sendMessage(message) {
                if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                    this.websocket.send(JSON.stringify(message));
                }
            }
            
            sendInput(data) {
                this.sendMessage({ type: 'input', data });
            }
            
            sendResize(cols, rows) {
                this.sendMessage({ type: 'resize', data: { cols, rows } });
            }
            
            disconnect() {
                if (this.websocket) {
                    this.websocket.close();
                }
            }
            
            clear() {
                this.terminal.clear();
            }
        }
        
        // Initialize terminal
        const terminal = new AIOTerminal();
        
        // Auto-connect on page load
        window.addEventListener('load', () => {
            terminal.connect();
        });
    </script>
</body>
</html>

Session Management

Session Features

  • Persistence: Sessions survive WebSocket disconnections
  • Restoration: Reconnect to existing sessions with command history
  • Timeout: Sessions automatically expire after 1 hour of inactivity
  • Concurrent: Multiple sessions can run simultaneously

Best Practices

  1. Store Session IDs: Save session IDs for reconnection
  2. Handle Disconnections: Implement automatic reconnection logic
  3. Heartbeat Handling: Always respond to ping messages
  4. Error Recovery: Handle connection errors gracefully

Error Handling

Common error scenarios:

Error Cause Solution
Session not found Invalid session_id Create new session
Connection refused Service unavailable Check AIO Sandbox status
Rate limited Too many connections Implement connection pooling

Next Steps

  • File Operations: Learn about file system APIs → File API
  • Integration Examples: See practical implementations → Terminal Examples
  • Browser CDP: Combine with browser automation → Browser API