14c1bc381e
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.6 KiB
5.6 KiB
V3b — Voice Chat: Claude Code on PRIMARY + "ready gate" before speaking (spawned 2026-10-01, main session 771b2744)
Bounded background agent, personal_projects #214. Budget: --max-turns 60. Same tool call twice with identical arguments → STOP with a partial wrap-up.
STATE: V3 built ~/voice-chat on the LAPTOP (ssh administrator@192.168.1.89, key auth; Omarchy/Hyprland, Python 3.14, PipeWire, tmux) assuming Claude Code runs on the laptop. Read ~/voice-chat/README.md and code on the laptop first, and the build plan at /home/administrator/Desktop/claude/agent-builder/voice_chat_build_plan.md (primary-hosted topology). Servers: STT http://192.168.1.90:8300, TTS http://192.168.1.90:8004 (Chatterbox, jarvis.wav).
OWNER FACTS + DECISIONS (2026-10-01 22:52):
- The owner runs Claude Code ON PRIMARY (192.168.1.88, host Daily-Driver-PC, user administrator), in a terminal on the laptop that is SSH'd into primary. Audio in/out happens on the LAPTOP.
- D1 Input path: laptop push-to-talk (existing) → STT → text is typed into the Claude Code tmux pane ON PRIMARY via
ssh administrator@192.168.1.88 tmux send-keys -t <target> -l <text>(no Enter unless auto_submit). Target discovery runs on primary (pane whose current command is claude). If the owner's Claude Code on primary is NOT in tmux today, document the one-time change (run claude insidetmux new -As claude) — do not change it yourself. Laptop→primary SSH: verify key auth from the laptop to primary works (ssh -o BatchMode=yes administrator@192.168.1.88 truefrom the laptop). If it doesn't, STAGE the exact key setup steps for the owner (no changes to authorized_keys by you). - D2 Output path: the Claude Code Stop hook runs ON PRIMARY (~/voice-chat-hook/ on primary, stdlib Python, venv not
needed). It extracts the speakable summary (reuse V3's logic) and delivers it to the LAPTOP; the laptop fetches TTS
and plays. Delivery = a tiny queue: primary appends a JSON line (text, session id, ts) to the laptop via
ssh administrator@192.168.1.89 'voice-chat enqueue'(stdin) — non-blocking, never fails the hook (exit 0 always, short timeout), and falls back silently if the laptop is off. Primary→laptop SSH key auth exists (the main session uses it). No listening network ports. - D3 READY GATE (new owner requirement): the owner may be watching TV with headphones OFF. Replies must NOT play
until the owner signals ready. Modes in config:
gate = "ready_key"(default) |"immediate". In ready_key mode: when a reply is queued, the laptop shows a desktop notification "Claude has a reply (N waiting)" plus an optional short soft chime (configchime=falsedefault) and waits. The owner presses a ready key (stage a Hyprland bind, e.g. SUPER+CTRL+K, callingvoice-chat ready) → the queue plays in order. Additional commands:voice-chat skip(drop queued),voice-chat replay(repeat last),voice-chat gate immediate|ready_key. Queued items expire after a configurable time (default 30 min) but stay listed invoice-chat status. Push-to-talk itself also counts as ready (if the owner starts talking, queued replies play first? NO — decided: PTT does not auto-play; only the ready key plays). - D4 Keep everything user-level, no sudo, no daemons unless needed (a user systemd unit for the queue player on the
laptop is acceptable if required; prefer none:
voice-chat readyplays synchronously). - STAGE, do NOT activate: the primary Claude Code hook (~/.claude/settings.json ON PRIMARY — produce the snippet + an installer like V3's install_hook.py that the owner runs; do NOT run it on the real file; NOTE the main session's own Claude Code on primary uses that settings file, so activation must be the owner's choice), and the new Hyprland binds on the laptop (append-ready snippet). The laptop-side V3 hook (laptop ~/.claude) is no longer the primary path — keep it but mark it optional in the README. SELF-TESTS (no audio played aloud, no human voice):
- Input: Chatterbox-synthesised sentence → laptop STT → injected into a throwaway tmux session ON PRIMARY running
cat(create it withtmux new -d -s voicechat-selftest caton primary; kill it after) → assert text arrived. - Output + gate: feed a sample Stop-hook JSON (documented schema; V3 already fetched it) to the primary hook → assert
it returns < 0.2 s with exit 0 → assert the laptop queue has 1 item and nothing played → run
voice-chat readywith a file sink (VOICE_CHAT_OUT) → assert a valid WAV of plausible length → queue empty. Also testimmediatemode,skip, expiry (with a short test expiry), and laptop-offline behaviour (point delivery at an unreachable host → hook still exits 0 fast). - Unit tests for queue/expiry/gate on the laptop; run V3's existing tests too.
HARD RULES: no sudo (
⏸ OWNER-COMMAND-REQUESTblock if truly needed); do not touch the running Claude Code session, ~/.claude/settings.json on either host, Hyprland config, authorized_keys, or any server container; only write in ~/voice-chat (laptop), ~/voice-chat-hook (primary), and the README copy; no audio playback; no git. DELIVERABLES: updated laptop README + primary ~/voice-chat-hook/README.md; copy both to /opt/appdata/docker/docker-compose/voice-tts/ (LAPTOP_CLIENT_README.md, PRIMARY_HOOK_README.md). FINAL message = wrap-up JSON only: status, project "voice-chat", files_built[], staged[] (path, what, where, host), self_tests[] (name, result, numbers), owner_steps[] (exact, per host, in order), ssh_auth {laptop_to_primary, primary_to_laptop}, design_conflicts[], actions_taken[], actions_failed[], unverified_claims[], next_step, notes.