feat(wake): client-capture wake word for remote desktop - #75298
feat(wake): client-capture wake word for remote desktop#75298appletechie wants to merge 3 commits into
Conversation
Remote headless backends have no PortAudio mic, so "hey hermes" fails even when openWakeWord is installed. Let the desktop stream 16 kHz int16 PCM via wake.feed while detection stays server-side. - wake_word.capture: auto|local|client (+ GUI client_capture prefer) - WakeWordDetector external_audio queue + feed_audio API - wake.feed RPC; wake.start/status report capture + frame_length - Desktop getUserMedia feeder; stop on wake.detected, re-arm after voice - Docs + unit tests (26 pass in tests/tools/test_wake_word.py)
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real remote-backend gap: current main still opens the wake listener through backend sounddevice.InputStream in tools/wake_word.py:953-983.
Problems
tui_gateway/server.py:12814deriveswake.status.capturefrom raw config, not the active listener. Withcapture: auto, GUI start selects client capture (:12616-12618), but status reports local. Afterwake.detectedstops the feeder (apps/desktop/src/app/contrib/wiring.tsx:688-690),resumeWakeAfterVoice()only reattaches it forstatus.capture === 'client'(apps/desktop/src/store/wake-word.ts:342-353). The resumed listener has no client PCM.tui_gateway/server.py:12700now passesexternal_audio, but current gateway test stubs attests/test_tui_gateway_server.py:1459and:1656do not accept that keyword. Update those fixtures and add gateway RPC coverage.- Desktop
/wake onstill omitsclient_captureatapps/desktop/src/app/session/hooks/use-prompt-actions/slash.ts:677-684. apps/desktop/src/lib/wake-client-capture.ts:113-127drops any frame generated while a prior RPC awaits; with 80 ms frames (:11), remote latency can create detector gaps.
Suggested changes
- Report the effective active capture mode/frame size, cover wake → voice → re-arm, pass client preference through Desktop slash handling, and use bounded queued PCM delivery.
Automated hermes-sweeper review.
| hint = f"Wake-word input device could not be resolved: {input_device['error']}" | ||
| if silent and not hint: | ||
| hint = silent_audio_hint(input_device) | ||
| capture = reqs.get("capture") or str(cfg.get("capture") or "auto") |
There was a problem hiding this comment.
For capture: auto, GUI wake.start selects client mode via prefer_client, but this fresh status probe has no such preference and reports local. After detection stops the feeder, the Desktop uses status.capture to decide whether to reattach it, so the resumed external listener receives no PCM. Return the active detector's effective mode (and frame metadata), or thread the caller capture preference through wake.status.
| _on_detect, | ||
| owner=transport, | ||
| config=cfg, | ||
| external_audio=external_audio, |
There was a problem hiding this comment.
Please update the existing gateway test doubles as part of this signature change: tests/test_tui_gateway_server.py:1459 and :1656 only accept callback, owner, and config, so this keyword makes those relevant tests fail before their assertions run.
| let inflight = false | ||
|
|
||
| const pushFrame = async (frame: Float32Array) => { | ||
| if (stopped || inflight) { |
There was a problem hiding this comment.
This drops every 80 ms audio frame while a previous wake.feed RPC is awaiting its response. That is especially risky for the remote/high-latency case this feature targets; use a bounded ordered queue or explicit backpressure that preserves contiguous PCM instead of discarding it.
- wake.status reports effective capture from the armed detector (client vs local), plus frame_length/sample_rate; GUI status probes prefer client - Gateway test doubles accept external_audio on start_listening - Desktop PCM feeder uses a bounded ordered queue instead of dropping frames while a wake.feed RPC is in flight - /wake on and status/re-arm paths pass client_capture so remote reattach works
|
Addressed in 0d39c11:
|
|
@teknium1 Anything else i need to do to get this rolling? |
|
Superseded by #79491, which carries your commits with authorship intact and adds one behavior fix on top: with |
What does this PR do?
Remote Hermes backends (Docker / headless VM / machine in another room) often have no microphone. Today wake word opens PortAudio on the server, so the desktop ear fails with “Failed to open the wake-word microphone.”
This PR keeps detection on the backend (openWakeWord / sherpa / porcupine unchanged) and adds client capture: the desktop streams 16 kHz mono int16 PCM over the existing authenticated WebSocket via a new
wake.feedRPC. That enables remote agent + Mac mic hands-free.Related Issue
N/A (user-reported remote-desktop wake failure on headless hosts)
Type of Change
Changes Made
wake_word.capture:auto|local|clientWakeWordDetector,feed_audio(),resolve_capture_mode()wake.feedRPC;wake.start/wake.statusreportcapture,frame_length,sample_rategetUserMediafeeder (wake-client-capture.ts); GUI passesclient_capture: truewake.detectedso voice can take the mic; re-arm after voiceHow to Test
pytest tests/tools/test_wake_word.py -q(26 passed locally)/dev/snd):capture: local/ auto without client prefer)/wake onstill uses local PortAudio (noclient_capture)Checklist
Code
feat(wake): …)pytest tests/tools/test_wake_word.py -qand tests passDocumentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — config default is inhermes_cli/config_defaults.py(wake_word.capture)For New Skills