4be70440e87ce1480b26644d93c275d78d776751
Squashed branch: voice/dave-recv → master. Closes Pas 12 (DAVE E2E) and lands voice-mode UX polish + verbal voice control on top of the Pas 1-10 scaffolding already on master. ## DAVE E2E receive-side decrypt (e4f3177) Vendored fork: discord-ext-voice-recv 0.5.3a+echo.dave1. Patches the receive pipeline to handle Discord's mandatory DAVE encryption on voice gateway v=8. - `_maybe_dave_decrypt`: uses davey.can_passthrough(user_id) as primary gate, falls through to dave.decrypt for DAVE-epoch peers, drops on decrypt failure without killing the reader thread. - VAD fix: silero-vad v5+ requires exactly 512 samples; our 100ms window (1600 samples) was silently raising ValueError → STT never fired. Now slice into 512-sample chunks. - Whisper: bumped beam_size 1→5 and added RO initial_prompt. - Tests: 11 DAVE unit tests + 2 callback integration tests + contract test with fork-version guard. ## Voice UX polish (d1bc77e) - Killed the 3s "mă gândesc" filler (always collided with Claude p50 4-7s). - Barge-in via `ttsq.clear()` at top of `on_segment_done`. - DTX silence-flush poller (200ms tick) — Discord stops sending RTP packets when silent, so the inline silence-check in sink.write() never fired for trailing audio; background thread handles it. - `EchoStreamingAudioSource.read()` non-blocking — old `get_frame(timeout=0.1)` wrecked Discord's 20ms cadence and the client interpreted bursts as stuttering (Marius heard "4 de minute" instead of full sentence). - RO time expansion: 23:09 → "douăzeci și trei și nouă minute". - Supertonic Unicode sanitize centralized in tools/tts.py. - Whisper local_files_only=True — no HF metadata GET on each startup. - Diagnostic logging through sink → VAD → Claude stream → TTS chain. ## Voice mode iteration (e589e48) - `personality/VOICE_MODE.md` — voice-tailored system prompt (short, no markdown, no abbreviations, time without seconds, distances in "mii"/"milioane"); plumbed via build_system_prompt(voice_mode=True). - Isolated voice session key `voice:<channel_id>` — voice doesn't share context with text adapter on the same channel; auto-applied without /clear ceremony. /clear drops both keys. - Metric units + Romanian thousands (normalize.py): "384.000 km" → "trei sute optzeci și patru de mii de kilometri" with feminine-correct pluralization and "de" particle for ≥20. - `/voice setvoice <M1-F5>` slash command with native autocomplete; swaps live + persists voice.default_voice to config.json. - Verbal voice change (src/voice/voice_commands.py + 29 tests) — "schimbă vocea pe M5", "voce em cinci", with permissive substring fallback for Whisper-mangled forms like "Mâcinci"=M5 and "unul cinci"=M5. Whisper initial_prompt now lists voice vocabulary to bias STT toward clean outputs. - Fast barge-in: VAD ≥2 consecutive windows (~200ms) on Marius's user while Echo has pending TTS frames → cut him off mid-sentence so user doesn't wait the full silence + STT cycle. Acoustic echo bleed-through still requires headphones (no AEC). ## Test suite 130 voice + router tests pass (test_voice_recv_dave, test_voice_session_cleanup, test_voice_adapter_contract, test_voice_normalize, test_voice_commands, test_router). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Echo Core
AI-powered personal assistant bot with Discord, Telegram, and WhatsApp bridges. Uses Claude Code CLI for conversation, with persistent sessions, cron scheduling, semantic memory search, and heartbeat monitoring.
Quick Start
# Interactive setup wizard (recommended for first install)
bash setup.sh
The wizard handles prerequisites, virtual environment, bridge tokens, config, and systemd services in 10 guided steps.
Manual Setup
# 1. Create venv and install dependencies
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# 2. Store Discord token in keyring
./cli.py secrets set discord_token
# 3. Edit config.json (bot name, owner ID, channels)
# 4. Start
systemctl --user start echo-core
Architecture
┌─────────────┐
│ Claude CLI │
└──────┬──────┘
│
┌──────┴──────┐
│ Router │
└──────┬──────┘
┌────────────┼────────────┐
│ │ │
┌─────┴─────┐ ┌───┴───┐ ┌──────┴──────┐
│ Discord │ │Telegram│ │ WhatsApp │
│ (d.py) │ │(ptb) │ │(Baileys+py) │
└────────────┘ └────────┘ └─────────────┘
- Discord: slash commands via discord.py
- Telegram: commands + inline keyboards via python-telegram-bot
- WhatsApp: Node.js Baileys bridge + Python polling adapter
- All three run concurrently in the same asyncio event loop
Key Components
| Component | Description |
|---|---|
src/main.py |
Entry point — starts all adapters + scheduler + heartbeat |
src/router.py |
Routes messages to Claude or handles commands |
src/claude_session.py |
Claude Code CLI wrapper with --resume sessions |
src/credential_store.py |
Keyring-based secrets manager |
src/scheduler.py |
APScheduler cron jobs |
src/heartbeat.py |
Periodic health checks |
src/memory_search.py |
Ollama embeddings + SQLite semantic search |
cli.py |
CLI tool — status, doctor, logs, secrets, cron, etc. |
setup.sh |
Interactive 10-step setup wizard |
bridge/whatsapp/ |
Node.js WhatsApp bridge (Baileys + Express) |
CLI Usage
The setup wizard installs eco as a global command (~/.local/bin/eco):
eco status # Bot online/offline, uptime
eco doctor # Full diagnostic check
eco restart # Restart the service
eco restart --bridge # Restart bot + WhatsApp bridge
eco stop # Stop the service
eco logs # Tail echo-core.log (last 20 lines)
eco logs 50 # Last 50 lines
eco secrets list # Show stored credentials
eco secrets set <name> # Store a secret in keyring
eco secrets test # Check required secrets
eco sessions list # Active Claude sessions
eco sessions clear # Clear all sessions
eco channel list # Registered Discord channels
eco cron list # Show scheduled jobs
eco cron run <name> # Force-run a cron job
eco memory search "<query>" # Semantic search in memory
eco memory reindex # Rebuild search index
eco heartbeat # Run health checks
eco whatsapp status # WhatsApp bridge connection
eco whatsapp qr # QR code pairing instructions
eco send <alias> <message> # Send message via router
Configuration
config.json — runtime configuration:
{
"bot": {
"name": "Echo",
"default_model": "opus",
"owner": "DISCORD_USER_ID",
"admins": ["TELEGRAM_USER_ID"]
},
"channels": { },
"telegram_channels": { },
"whatsapp": {
"enabled": true,
"bridge_url": "http://127.0.0.1:8098",
"owner": "PHONE_NUMBER"
},
"whatsapp_channels": { }
}
Secrets (Discord/Telegram tokens) are stored in the system keyring, not in config files.
Services
Echo Core runs as systemd user services:
systemctl --user start echo-core # Start bot
systemctl --user start echo-whatsapp-bridge # Start WA bridge
systemctl --user status echo-core # Check status
journalctl --user -u echo-core -f # Follow logs
Requirements
- Python 3.12+
- Claude Code CLI
- Node.js 22+ (only for WhatsApp bridge)
Tests
source .venv/bin/activate
pytest tests/
440 tests, zero failures.
Description
Languages
Python
73%
HTML
23.3%
Shell
2.6%
JavaScript
0.6%
CSS
0.5%