Client to Server Events
Events sent from CLI to hub.
message
Send a message to a session.
Payload:
Example:
socket.emit('message', {
sid: 'abc123',
message: {
type: 'text',
text: 'Hello from CLI'
},
localId: 'local-456'
})
Behavior:
- Validates namespace access
- Stores message in database
- Assigns sequence number
- Broadcasts to session room via
update event
- Publishes to SSE subscribers
- Extracts todos from TodoWrite tool calls
session-alive
Keep session active and update state.
Payload:
Example:
socket.emit('session-alive', {
sid: 'abc123',
time: Date.now(),
thinking: false,
mode: 'remote',
permissionMode: 'default'
})
Behavior:
- Updates session’s
activeAt timestamp
- Updates
thinking state
- Prevents session timeout
- Send every 5 seconds
session-end
Mark session as ended.
Payload:
Example:
socket.emit('session-end', {
sid: 'abc123',
time: Date.now()
})
Behavior:
- Marks session as inactive
- Notifies web clients via SSE
- Stops timeout tracking
Update session metadata with optimistic locking.
Payload:
Callback Response:
type Response =
| { result: 'success', version: number, metadata: any }
| { result: 'version-mismatch', version: number, metadata: any }
| { result: 'error', reason?: string }
Example:
socket.emit('update-metadata', {
sid: 'abc123',
expectedVersion: 5,
metadata: {
path: '/home/user/project',
host: 'laptop',
name: 'My Project'
}
}, (response) => {
if (response.result === 'success') {
console.log('Metadata updated to version', response.version)
} else if (response.result === 'version-mismatch') {
console.log('Version mismatch, server at', response.version)
}
})
Behavior:
- Validates expected version
- Increments version on success
- Broadcasts update to session room
- Returns new version or conflict
update-state
Update session agent state with optimistic locking.
Payload:
Callback Response:
type Response =
| { result: 'success', version: number, agentState: any }
| { result: 'version-mismatch', version: number, agentState: any }
| { result: 'error', reason?: string }
Example:
socket.emit('update-state', {
sid: 'abc123',
expectedVersion: 3,
agentState: {
controlledByUser: true,
requests: {
'req_456': {
tool: 'edit',
arguments: { path: 'src/main.ts' },
createdAt: Date.now()
}
}
}
}, (response) => {
if (response.result === 'success') {
console.log('State updated to version', response.version)
}
})
machine-alive
Keep machine online.
Payload:
Example:
socket.emit('machine-alive', {
machineId: 'machine_xyz',
time: Date.now()
})
Behavior:
- Updates machine’s
lastSeen timestamp
- Send every 10 seconds
Update machine metadata.
Payload:
Callback: Same as update-metadata
machine-update-state
Update machine runner state.
Payload:
Callback: Same as update-state
rpc-register
Register an RPC method handler.
Payload:
Example:
socket.emit('rpc-register', {
method: 'uploadFile'
})
// Handle RPC requests
socket.on('rpc-request', async (data, callback) => {
if (data.method === 'uploadFile') {
const params = JSON.parse(data.params)
const result = await handleUploadFile(params)
callback(JSON.stringify(result))
}
})
rpc-unregister
Unregister an RPC method handler.
Payload:
Example:
socket.emit('rpc-unregister', {
method: 'uploadFile'
})
terminal:ready
Notify that terminal is ready.
Payload:
terminal:output
Send terminal output.
Payload:
terminal:exit
Notify terminal exit.
Payload:
terminal:error
Notify terminal error.
Payload:
ping
Test connection latency.
Callback: Immediate acknowledgment
Example:
const start = Date.now()
socket.emit('ping', () => {
const latency = Date.now() - start
console.log('Latency:', latency, 'ms')
})
usage-report
Report usage statistics (internal).
Payload: Arbitrary usage data
Server to Client Events
Events sent from hub to CLI.
update
Broadcast session/machine/message updates.
Payload:
Update body (varies by type)
Update Types:
new-message
{
t: 'new-message',
sid: 'abc123',
message: {
id: 'msg_xyz',
seq: 42,
createdAt: 1709856000000,
localId: 'local-123',
content: {...}
}
}
update-session
{
t: 'update-session',
sid: 'abc123',
metadata: {
version: 6,
value: {...}
},
agentState: {
version: 4,
value: {...}
}
}
update-machine
{
t: 'update-machine',
machineId: 'machine_xyz',
metadata: {
version: 2,
value: {...}
},
runnerState: {
version: 1,
value: {...}
}
}
Example:
socket.on('update', (update) => {
if (update.body.t === 'new-message') {
console.log('New message:', update.body.message)
} else if (update.body.t === 'update-session') {
console.log('Session updated:', update.body.sid)
}
})
rpc-request
Receive RPC method call from hub.
Payload:
Callback: Return JSON-encoded result
Example:
socket.on('rpc-request', async (data, callback) => {
const { method, params } = data
const parsedParams = JSON.parse(params)
try {
let result
if (method === 'uploadFile') {
result = await handleUploadFile(parsedParams)
} else if (method === 'checkPathExists') {
result = await handleCheckPath(parsedParams)
} else {
result = { success: false, error: 'Unknown method' }
}
callback(JSON.stringify(result))
} catch (error) {
callback(JSON.stringify({
success: false,
error: error.message
}))
}
})
terminal:open
Open a terminal for a session.
Payload:
terminal:write
Write input to terminal.
Payload:
terminal:resize
Resize terminal dimensions.
Payload:
terminal:close
Close terminal.
Payload:
error
Socket error notification.
Payload:
Error code: namespace-missing, access-denied, or not-found
Error scope: session or machine
Example:
socket.on('error', (data) => {
console.error('Socket error:', data.message)
if (data.code === 'access-denied') {
console.error('Access denied to', data.scope, data.id)
}
})