Marius Mutu d1bc77e87d feat(voice): polish voice loop UX — filler kill, barge-in, DTX flush, time/RO TTS
End-to-end voice UX iteration after DAVE E2E shipped. Each change addresses a
real symptom Marius hit in live testing today:

- Kill the 3s filler ("mă gândesc"): Claude p50 is 4-7s so the filler always
  fired BEFORE the response and collided with it. Removed all filler infra
  from pipeline.py + tts_stream.py (FILLER_DELAY_S, _filler_task, push_filler,
  load_thinking_wav, thinking.wav cache).

- Barge-in: ttsq.clear() at the top of on_segment_done drops stale frames so
  a new utterance cuts off Echo's previous response cleanly.

- DTX silence flush: Discord stops sending RTP packets when the user goes
  silent (DTX), so the inline silence-check in sink.write() never fired for
  the trailing audio of an utterance — STT was missed entirely. Added a
  background poller thread that checks the silence-flush condition every
  200ms independent of incoming packets.

- Discord audio cadence fix: EchoStreamingAudioSource.read() blocked 100ms
  per call when pcm_queue was empty, wrecking Discord's 20ms frame pacing →
  client interpreted the stream as stutter and discarded leading frames
  (Marius heard "4 de minute în București" instead of the full sentence).
  Switched to get_frame_nowait() — instant return, silence frame on empty.

- RO time expansion: "23:09" was being read as "douăzeci și trei:nouă"
  with literal colon. Added expand_time() with feminine-correct minute
  formatting (un minut / două minute / douăzeci de minute / una de minute).

- Supertonic Unicode sanitize centralized in tools/tts.py: Romanian curly
  quotes (`„`, `"`, `"`, `—`, `…`) crash Supertonic with HTTP 500. Map them
  to ASCII at the synthesize() entry so BOTH voice mode and /audio command
  are covered without duplication. normalize.py re-exports for compat.

- Whisper offline: WhisperModel(..., local_files_only=True) — no more
  huggingface.co metadata GET on every startup. Model is already cached.

- Diagnostic logging across the chain: sink first-packet, VAD first-speech,
  voice stream block (Claude → callback), push_text (text → clauses queued),
  TTS pushed (clauses → frames). Lets future "spoke but Echo silent" bugs
  pinpoint exactly where the chain breaks.

- Captured Supertonic curly-quote lesson in tasks/lessons.md.

All 76 voice tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:33:24 +00:00
2026-05-27 06:12:13 +00:00
2026-05-20 22:28:39 +00:00
2026-05-05 07:48:55 +00:00
2026-02-19 14:09:12 +00:00

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
No description provided
Readme 8.7 MiB
Languages
Python 73%
HTML 23.3%
Shell 2.6%
JavaScript 0.6%
CSS 0.5%