feat: voice mic, pixel life layer, enhanced transcript-debug UX

VoiceMicButton component, PixelLife aquatic layer, improved UserMessageBubble
with voice display, AgentBadge terminal switcher, ChatContainer voice integration,
FloatingTranscriptDebug ocean life enhancements, and terminal registry support.
Remove traefik config.
This commit is contained in:
2026-02-20 12:12:53 -06:00
parent b7f03a777b
commit 220d595568
19 changed files with 2221 additions and 358 deletions

View File

@@ -33,6 +33,47 @@ const sessions = new Map<string, TerminalSession>()
// Map WebSocket to sessionId
const wsToSession = new Map<any, string>()
// ── Global terminal registry ──
// Tracks metadata about transcript-debug terminals so all clients can see/connect to them
interface TerminalRegistryEntry {
ephemeralSessionId: string // PTY session ID on this server
transcriptSessionId: string // Claude transcript session being resumed (or '__new__')
agent: string // ejecutor | nucleo000 | claude
label: string // First user message or short description
command: string // Full command that was run
createdAt: string // ISO timestamp
}
const terminalRegistry = new Map<string, TerminalRegistryEntry>() // keyed by ephemeralSessionId
function getRegistrySnapshot() {
return Array.from(terminalRegistry.values()).map(entry => {
const ptySession = sessions.get(entry.ephemeralSessionId)
return {
...entry,
alive: !!ptySession,
clients: ptySession?.clients.size ?? 0,
bufferSize: ptySession?.outputBuffer.length ?? 0
}
})
}
function broadcastRegistryChange() {
const message = JSON.stringify({
type: 'terminal-registry-change',
registry: getRegistrySnapshot(),
timestamp: Date.now()
})
let clientCount = 0
for (const [, session] of sessions) {
for (const ws of session.clients) {
try { ws.send(message); clientCount++ } catch { /* skip */ }
}
}
console.log(`[Terminal] Registry broadcast → ${clientCount} clients (${terminalRegistry.size} entries)`)
}
function getOrCreateSession(sessionId: string = DEFAULT_SESSION_ID): TerminalSession {
let session = sessions.get(sessionId)
@@ -83,6 +124,12 @@ function getOrCreateSession(sessionId: string = DEFAULT_SESSION_ID): TerminalSes
}
sessions.delete(sessionId)
// Auto-remove from terminal registry
if (terminalRegistry.has(sessionId)) {
terminalRegistry.delete(sessionId)
broadcastRegistryChange()
}
// Mark agent as not running if this is an agent session
if (sessionId.startsWith('agent-')) {
const agentId = sessionId.replace('agent-', '')
@@ -122,6 +169,13 @@ export function killSession(sessionId: string): boolean {
}
sessions.delete(sessionId)
// Auto-remove from terminal registry
if (terminalRegistry.has(sessionId)) {
terminalRegistry.delete(sessionId)
broadcastRegistryChange()
}
return true
}
@@ -301,6 +355,67 @@ export function startTerminalServer() {
}
}
// ── Terminal Registry endpoints ──
// List all registered terminals (global, for all clients)
if (url.pathname === '/terminal-registry' && req.method === 'GET') {
return Response.json({ registry: getRegistrySnapshot() }, { headers: corsHeaders })
}
// Register a new terminal
if (url.pathname === '/register-terminal' && req.method === 'POST') {
try {
const body = await req.json() as TerminalRegistryEntry
if (!body.ephemeralSessionId) {
return Response.json({ error: 'ephemeralSessionId required' }, { status: 400, headers: corsHeaders })
}
terminalRegistry.set(body.ephemeralSessionId, {
ephemeralSessionId: body.ephemeralSessionId,
transcriptSessionId: body.transcriptSessionId || '',
agent: body.agent || '',
label: body.label || '',
command: body.command || '',
createdAt: body.createdAt || new Date().toISOString()
})
console.log(`[Terminal] Registered terminal: ${body.ephemeralSessionId}${body.transcriptSessionId} (${body.agent})`)
broadcastRegistryChange()
return Response.json({ success: true }, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 400, headers: corsHeaders })
}
}
// Update a registered terminal (e.g. re-key transcriptSessionId, update label)
if (url.pathname === '/update-terminal' && req.method === 'POST') {
try {
const body = await req.json() as Partial<TerminalRegistryEntry> & { ephemeralSessionId: string }
const entry = terminalRegistry.get(body.ephemeralSessionId)
if (!entry) {
return Response.json({ error: 'Not found' }, { status: 404, headers: corsHeaders })
}
if (body.transcriptSessionId !== undefined) entry.transcriptSessionId = body.transcriptSessionId
if (body.label !== undefined) entry.label = body.label
if (body.agent !== undefined) entry.agent = body.agent
if (body.command !== undefined) entry.command = body.command
broadcastRegistryChange()
return Response.json({ success: true }, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 400, headers: corsHeaders })
}
}
// Unregister a terminal (does NOT kill the PTY — use /kill-session for that)
if (url.pathname === '/unregister-terminal' && req.method === 'POST') {
try {
const body = await req.json() as { ephemeralSessionId: string }
const deleted = terminalRegistry.delete(body.ephemeralSessionId)
if (deleted) broadcastRegistryChange()
return Response.json({ success: true, deleted }, { headers: corsHeaders })
} catch (e: any) {
return Response.json({ error: e.message }, { status: 400, headers: corsHeaders })
}
}
// Check if this is a WebSocket upgrade request
const upgradeHeader = req.headers.get('upgrade')
console.log(`[Terminal] Request: ${req.method} ${url.pathname}, Upgrade: ${upgradeHeader}`)