Second voice UX iteration. Targets Marius's live-test pain points from today.
- **Voice-mode system prompt** (personality/VOICE_MODE.md, plumbed via
claude_session.build_system_prompt(voice_mode=True)) — when the voice
adapter starts a session, append voice-tailored instructions: short replies,
no markdown, no abbreviations, time without seconds, distances rounded
to "mii"/"milioane", no curly quotes / em-dash / ellipsis. Marius asked
for a "in-the-car friend" persona for voice.
- **Isolated voice session key** (router.py) — voice mode uses
`voice:<channel_id>` so it doesn't share context with the text adapter
on the same Discord channel. Fresh start, voice prompt applied
automatically without `/clear` ceremony. `/clear` drops both keys.
- **Metric units + Romanian thousands** (src/voice/normalize.py) —
`384.000 km` was being read as "trei sute optzeci și patru virgulă zero
zero zero km" because the dot was treated as decimal separator and `km`
wasn't expanded. New `normalize_thousands` collapses Romanian thousands
separators (`X.000`/`X.000.000`) before number expansion, and
`expand_units` handles km/kg/cm/mm/ml/ha/mp with correct Romanian
pluralization ("un kilometru", "două kilograme", "douăzeci de
centimetri", "o sută de kilometri" with "de" particle).
- **`/voice setvoice <M1-F5>` slash command** (discord_voice.py) — Discord
native autocomplete; swaps the live TTSQueue voice_id AND persists
voice.default_voice to config.json. No restart needed.
- **Verbal voice change** (src/voice/voice_commands.py — new module +
29 tests) — say "schimbă vocea pe M5" / "vorbește cu vocea F3" / "voce
em cinci" from inside the voice channel. Detector requires both a
trigger word (voce/vorbește/schimbă/treci pe) and a recognizable voice
ID (direct "M5", word form "em cinci", or fallback substring match for
Whisper-mangled forms like "unul cinci"=M5 and "Mâcinci"=M5). On
detection: live-swap, persist to config, mirror to chat with
`🎤 ... / 🔊 Voce → M5`, speak short ack in the NEW voice, skip
Claude. "pământinci" still can't be recovered (no recoverable digit
substring); user gets passthrough to Claude in that case.
- **Whisper initial_prompt** now lists the voice-command vocabulary so
STT biases toward producing clean "M5" / "F3" tokens instead of
inventing "pământ" / "unul" phonetic neighbors.
- **Fast barge-in** (pipeline.py EchoVoiceSink) — previously `ttsq.clear()`
only fired in `on_segment_done` (after 800ms silence + 2-3s STT ≈ 3s lag).
Now also fires from the sink as soon as VAD detects ≥2 consecutive
windows (~200ms) of sustained speech on Marius's user while Echo has
pending TTS frames. Single-window glitches don't cut Echo off; sustained
speech does. (Acoustic echo bleed-through still requires headphones —
no AEC in the bot.)
- Tests: 130 voice + router tests pass; updated test_router.py to expect
`/clear` to drop both text and voice session keys.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
personality/AGENTS.md — added ## Voice mode section after ## Platform
Formatting (logical fit: voice este încă un platform-specific register,
alături de Discord/WhatsApp formatting). 7 reguli aplicabile când
adapter_name == "discord-voice":
- 1-3 propoziții max
- fără markdown / fără bullet / fără linkuri
- numere/valute conversaționale ("treizeci de lei" nu "30 RON" —
normalize.py face conversia tehnică)
- lung/structurat → "L-am scris în chat." + text mirror
- ton ca la o cafea cu Marius, nu raport corporate
personality/TOOLS.md — added ### Discord Voice section după ### Whisper:
- ce e (bot ascultă/transcrie/răspunde rostit)
- "în voce" = /voice join, presence Listening, auto-leave 5min
- latency expectations ~5s perceived, filler peste 3s
- streaming TTS per clauză (zero gap)
- limitări (1-3 propoziții, STT pe cuvinte rare/acronime)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Instrucțiunea era prea restrictivă (doar formulare/documente "acționabile").
Acum include orice URL relevant: articole, linkuri de citit, resurse.
Același comportament adăugat și în HEARTBEAT pentru TL;DR din KB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Grep across personality/ for clawd|openclaw|clawdbot found no hits after
Lane A's earlier sweep. Single remaining operational reference to the
now-decommissioned night-execute cron has been softened to a generic
'add to approved-tasks.md' note.
- heartbeat saves unread whitelisted emails via email_process --save --json
- fix: add --add-dir so Claude CLI subprocess can access memory/ symlink
- email_check/process: use BODY.PEEK[] to avoid marking emails as read
- email_process: simplify credential loading via credential_store only
- config: heartbeat interval 30→120min, quiet hours end 08→07
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Git commands without arguments (git push, git status, git diff, etc.) were not matched by the existing wildcard patterns. Added bare variants and git stash support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SOUL.md: don't retract excessively when information is correct.
TOOLS.md: verify tools before claiming unavailable, offer alternatives.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace all ~/clawd and ~/.clawdbot paths with ~/echo-core equivalents
in tools (git_commit, ralph_prd_generator, backup_config, lead-gen)
- Update personality files: TOOLS.md repo/paths, AGENTS.md security audit cmd
- Migrate HANDOFF.md architectural decisions to docs/architecture.md
- Tighten credentials/ dir to 700, add to .gitignore
- Add .claude/ and *.pid to .gitignore
- Various adapter, router, and session improvements from prior work
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>