fix: clash agents had no microphone — expose input sinks as real capture sources#77
Conversation
…ure sources
Even with new headless, both agents stayed silent. Root cause: the runner gave
each browser PULSE_SOURCE=Sink_A_In.monitor, but Chromium/WebRTC does NOT
enumerate PipeWire `.monitor` sources as microphones. In the live container,
getUserMedia({audio:true}) returned NotFoundError with inputs:[], so the agents'
voice web app (Agora ConvoAI) never started its call → no greeting → the
broadcaster published pure silence.
("Agents heard the moderator" was a red herring: Sink_*_In had the moderator
audio, but that was the runner's own parec dump — the browser never captured it.)
Fix: pipewire-setup.sh remaps each input-sink monitor into a real capture source
(module-remap-source → Mic_A / Mic_B), and clash-runner.ts points the browsers
at Mic_A/Mic_B instead of the raw monitors.
Verified in the live runner container:
- Sink_A_In.monitor → getUserMedia NotFoundError, inputs:[]
- Mic_A (remap) → inputs:["Default","Mic_A"], track state:live
- tone into Sink_A_In → captured on Mic_A at RMS 0.38 (audio flows through)
Adds a Docker audio-pipeline regression check (Mic_A/Mic_B exist and carry
audio) and updates CLASH_DESIGN.md. Runner image must be rebuilt + redeployed.
🤖 Built with SMT <smt@agora.build>
🤖 Codex Code ReviewNo findings. I reviewed the PR diff for the audio setup, runner wiring, browser env, and regression test. The change looks logically consistent: Residual note: I did not run the audio integration test in this read-only environment. |
🤖 Claude Code ReviewI reviewed the full diff plus the surrounding context in SummaryThis is a clean, well-scoped fix. The root-cause diagnosis is correct — Chromium/WebRTC does not enumerate a PulseAudio/PipeWire Correctness of the audio graph holds up: both signal paths that must reach an agent's mic write into Test coverage is genuinely strong here — the new "Mic_A carries audio played into Sink_A_In" assertion (line 115) exercises the remap end-to-end with an RMS check rather than just asserting the source exists, which is what actually proves the fix works (channel-map/upmix correctness included). Setup robustness is consistent with the existing pattern: under No security concerns: these are container-internal shell scripts with no user-controlled input (no injection surface), no credential handling, and Minor observations (non-blocking)
Overall: correct, well-tested, safe to merge. |
Problem
After the new-headless fix (#76), both clash agents were still silent —
debug_agent_*_out.raw=0.000000.Root cause (found in the live runner container)
The runner gives each agent browser
PULSE_SOURCE=Sink_A_In.monitor, but Chromium/WebRTC does not enumerate PipeWire.monitorsources as microphones. A secure-context probe against the real site showed:So the agent's voice web app (Agora ConvoAI) can't get a mic → never starts its call → no greeting → the broadcaster publishes pure silence.
Fix
pipewire-setup.shremaps each input-sink monitor into a real capture source (module-remap-source→Mic_A/Mic_B), andclash-runner.tspoints the browsers atMic_A/Mic_Binstead of the raw monitors.Validation (live container)
Sink_A_In.monitorinputs: []Mic_A(remap)inputs: ["Default","Mic_A"]state: livePlus audio flow-through: a tone played into
Sink_A_Inwas captured onMic_Aat RMS 0.38.Coverage
Mic_A/Mic_Bexist and carry audio played intoSink_*_In.CLASH_DESIGN.md(mic-exposure section).Deploy
Runner-side — image rebuilds on merge (CI), then
vox-upgrade.shon the runner host. No server/schema changes.Generated with SMT smt@agora.io