- Update TTSManager to accept an optional abort signal, allowing for graceful cancellation of TTS playback. - Implement abort handling in the generateAndWaitForPlayback method to clean up pending playbacks and reject promises when playback is aborted. - Modify Session class to pass the abort signal during TTS generation, improving control over playback interruptions. These changes improve the robustness of the voice assistant's TTS functionality and enhance user experience by allowing for better management of audio playback.
7.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Voice-controlled terminal assistant using OpenAI's Realtime API (via Whisper STT, GPT-4, and TTS). Single-service architecture combining Express server, React UI, and tmux-based terminal management.
Monorepo Structure
This is an npm workspace monorepo:
- Root: Workspace configuration and shared TypeScript config
- packages/voice-assistant: Main application (only active package currently)
Development Commands
All commands run from the repository root:
# Development (starts Express server with integrated Vite middleware)
npm run dev
# Build everything
npm run build
# Type checking
npm run typecheck
# Production
npm run start
Voice Assistant Package Commands
From packages/voice-assistant/:
# Development
npm run dev # Watch mode: Express server + Vite HMR
npm run dev:server # Express server only (port 3000)
# Building
npm run build # Build both UI and server
npm run build:ui # Vite build � dist/ui
npm run build:server # TypeScript � dist/server
# Production
npm start # Run built server
# Type checking
npm run typecheck # Check both UI and server
Architecture
Overall Structure
Single service with three main subsystems:
-
Server Layer (
src/server/):- Express HTTP server (port 3000)
- WebSocket server for bidirectional communication
- Vite middleware in development, static files in production
-
Agent Layer (
src/server/agent/):orchestrator.ts: Main conversation managerllm-openai.ts: GPT-4 streaming with tool executionstt-openai.ts: Whisper transcriptiontts-openai.ts: OpenAI TTS synthesissystem-prompt.ts: Agent instructions (voice-first UX focused)
-
Daemon Layer (
src/server/daemon/):terminal-manager.ts: High-level terminal operationstmux.ts: Low-level tmux integrationtool-definitions.ts: OpenAI function schemas for LLM
-
UI Layer (
src/ui/):- React 18 + Vite
- WebSocket client for real-time communication
- Audio capture and playback handling
Key Architecture Decisions
Terminal Model:
- Uses tmux session "voice-dev" with windows-as-terminals
- Each terminal = one tmux window with single pane
- Terminal ID = tmux window ID (format:
@123) - Terminal names must be unique
Voice-First Design:
- All responses designed for TTS output
- STT errors handled intelligently (see
agent-prompt.md) - No visual feedback assumptions
Streaming Architecture:
- Audio: Browser � WebSocket � Whisper STT � LLM � TTS � WebSocket � Browser
- Text chunks streamed for real-time response
- Tool calls logged as activity events
Data Flow
- Voice Input: Browser captures audio � sends chunks via WebSocket � STT transcribes � orchestrator processes
- LLM Processing: Orchestrator streams GPT-4 responses � executes tool calls � accumulates response
- Voice Output: Response � TTS synthesis � base64 audio � WebSocket � browser playback
TypeScript Configuration
Three separate configs:
tsconfig.base.json: Root workspace defaults (ES2020, ESNext modules)packages/voice-assistant/tsconfig.json: UI build config (React JSX, bundler mode, noEmit)packages/voice-assistant/tsconfig.server.json: Server build config (NodeNext modules, outputs to dist/server)
Module System:
- Server: ES modules with
.jsextensions in imports (NodeNext resolution) - UI: Vite bundler mode (no file extensions needed)
Environment Variables
Required in packages/voice-assistant/.env:
OPENAI_API_KEY=sk-... # Required for GPT-4, Whisper, and TTS
# Optional TTS configuration
TTS_VOICE=alloy # alloy|echo|fable|onyx|nova|shimmer
TTS_MODEL=tts-1 # tts-1 (fast) | tts-1-hd (quality)
# Server config
PORT=3000
NODE_ENV=development
Terminal Control (MCP-like Tools)
The LLM has access to terminal operations via function calling:
list_terminals(): Get all terminals with statuscreate_terminal(name, workingDirectory, initialCommand?): Create new terminalsend_text(terminalId, text, pressEnter, captureOutput?): Execute commandscapture_terminal(terminalId, lines?, wait?): Read terminal outputkill_terminal(terminalId): Remove terminal
See src/server/daemon/tool-definitions.ts for complete schemas.
WebSocket Protocol
Message types (see src/server/types.ts):
Client � Server:
user_message: Text inputaudio_chunk: Audio data (base64, includes format and isLast flag)recording_state: Recording started/stoppedping: Keepalive
Server � Client:
activity_log: Conversation events (transcript, tool calls, errors)assistant_chunk: Streaming response textaudio_output: Complete TTS audio (base64 MP3)transcription_result: STT outputstatus: Connection statepong: Keepalive response
Agent System Prompt
Located in agent-prompt.md (32KB) - loaded by system-prompt.ts.
Critical aspects:
- Voice-first interaction guidelines
- STT error handling strategies
- Terminal operation best practices
- Response brevity requirements (user is on phone)
Testing
Currently manual testing:
- Start dev server:
npm run dev - Open http://localhost:3000
- Test WebSocket connection (status indicator)
- Test voice recording/playback
- Test terminal operations via
/api/test-terminal
Test scripts:
test-llm.sh: Direct LLM streaming testtest-orchestrator.js: Conversation flow test
File Organization
packages/voice-assistant/
�� src/
�� server/
�� agent/ # LLM, STT, TTS, orchestration
�� daemon/ # Terminal management + tool schemas
�� index.ts # Express + WebSocket server
�� websocket-server.ts
�� types.ts # Shared server types
�� ui/
�� components/ # React components
�� hooks/ # WebSocket, audio hooks
�� lib/ # Audio capture/playback utilities
�� App.tsx
�� main.tsx
�� dist/ # Build output (gitignored)
�� agent-prompt.md # System prompt source
�� package.json
�� tsconfig.json # UI TypeScript config
�� tsconfig.server.json # Server TypeScript config
�� vite.config.ts
Common Tasks
Adding a new terminal tool:
- Add function schema to
daemon/tool-definitions.ts - Implement handler in
agent/llm-openai.tsexecuteTool function - If needed, add low-level tmux operation to
daemon/tmux.ts
Modifying agent behavior:
- Edit
agent-prompt.mdfor instructions - Restart server (prompt loaded on startup)
Changing audio formats:
- STT: Modify
agent/stt-openai.ts(currently accepts webm/ogg/mp3) - TTS: Change TTS_MODEL env var or
tts-openai.tsconfig
WebSocket message types:
- Add type to
types.tsWebSocketMessage union - Add handler in
websocket-server.tsor client hooks
Known Patterns
- No barrel files: Direct imports only (no index.ts exports)
- Function declarations: Always use
functionnot arrow functions for exports - Error handling: Let errors bubble to Express/WebSocket error handlers
- Tool execution: Synchronous within LLM streaming loop (tools modify conversation state)
- Conversation cleanup: Auto-cleanup runs every 10 minutes (60 min TTL)
- never run dev server, user will test manually