#!/usr/bin/env python3 """Full mic-free chain with the BRIDGE brain backend (not claude -p). Runs the real wake + STT + TTS models, but builds the brain from sim-transport configuration. Proves wake -> STT -> bridge-brain -> TTS works end to end without a mic, without claude -p, and without a live Claude session — the exact shape of the eventual Syl integration, minus the ssh transport. Loads models, so run it under a memory cap, e.g.: systemd-run --user --scope -p MemoryMax=1500M -p MemorySwapMax=0 \ nice -n19 ionice -c3 .venv/bin/python test_pipeline_bridge.py """ from __future__ import annotations import sys import tempfile from pathlib import Path import brain_bridge import pipeline from sim_persona import SimPersona PROJECT = Path(__file__).resolve().parent results: list[tuple[bool, str]] = [] def check(ok: bool, detail: str) -> None: results.append((ok, detail)) print(f" [{'PASS' if ok else 'FAIL'}] {detail}") def main() -> int: d = Path(tempfile.mkdtemp(prefix="voice-pipe-bridge-")) inbox = d / "manual-inbox.jsonl" reply = d / "voice-reply.txt" sim = SimPersona(inbox, reply, poll_s=0.05) sim.start() # Swap the module-level brain for the config-driven factory. run_pipeline() # calls brain() by name in this module's namespace, so the full chain exercises # the same transport selection as a deployed bridge without touching repo config. pipeline.brain = brain_bridge.build_brain( { "brain_transport": "sim", "brain_persona": "syl", "brain_inbox_path": str(inbox), "brain_reply_path": str(reply), "brain_timeout_s": 20, "brain_poll_s": 0.05, }, system_prompt=pipeline.SYL_VOICE_SYSTEM_PROMPT, ) clip = str(PROJECT / "test_audio" / "clip_jarvis.wav") if not Path(clip).exists(): # Synthesize the prompt clip with Piper if the builder's clip is gone. pipeline.speak("hey jarvis, what is two plus two?", clip) try: # Pin the wake word so the test is independent of config.json's active one. r = pipeline.run_pipeline( clip, out_wav_path=str(d / "reply.wav"), wake_word="hey_jarvis" ) finally: sim.stop() check(r["wake_fired"], f"wake fired on the clip (score {r['wake_score']})") check(bool(r["transcript"]), f"STT produced text: {r['transcript']!r}") check( r["reply"] == "Two plus two is four.", f"bridge brain answered correctly: {r['reply']!r}", ) check( bool(r["output_wav"]) and Path(r["output_wav"]).exists(), f"TTS wrote a reply wav: {r['output_wav']}", ) print( "\ntimings (s): " + ", ".join(f"{k}={v:.2f}" for k, v in r["timings_s"].items()) ) n_pass = sum(1 for ok, _ in results if ok) print(f"=== {n_pass}/{len(results)} checks passed ===") return 0 if n_pass == len(results) else 1 if __name__ == "__main__": sys.exit(main())