Skip to content

Add Desktop microphone picker and STT diagnostics - #46916

Open
rod-nxtlevel wants to merge 1 commit into
NousResearch:mainfrom
nxtlevelsaas:codex/desktop-voice-input
Open

Add Desktop microphone picker and STT diagnostics#46916
rod-nxtlevel wants to merge 1 commit into
NousResearch:mainfrom
nxtlevelsaas:codex/desktop-voice-input

Conversation

@rod-nxtlevel

Copy link
Copy Markdown
Contributor

Summary

Adds a proper Hermes Desktop voice input settings surface so users can choose which local microphone the Desktop app uses for dictation and voice conversations, and can understand where speech-to-text is being processed.

This PR is intentionally independent of PR #46458. It is based on current upstream main and does not include the profile-scoped settings routing changes from that PR.

What changed

  • Adds a new Voice Input panel under Desktop settings for the voice config section.
  • Lists available audio input devices with navigator.mediaDevices.enumerateDevices().
  • Persists the selected microphone device locally in Desktop renderer storage.
  • Includes a System default microphone option for safe fallback behavior.
  • Uses the selected microphone when starting getUserMedia() for voice recording.
  • Falls back to the system default microphone if a saved device is missing or unplugged.
  • Shows a native notification when Desktop falls back from a saved microphone to the system default.
  • Adds GET /api/audio/status for non-mutating STT readiness diagnostics.
  • Surfaces whether transcription is local or remote based on the active Desktop backend connection.
  • Extends the Desktop transcription request timeout to better tolerate local Whisper/model startup.
  • Handles mixed-version remote backends: if the connected backend does not yet expose /api/audio/status, the UI shows a friendly unsupported-diagnostics message instead of a raw IPC/404 error.
  • Adds tests for the STT status helper.

Root cause / motivation

Hermes Desktop voice capture used the browser/Electron default input device with no user-visible picker. On systems with multiple microphones, users could not tell which microphone was active and could not choose the intended device.

The investigation also found that Desktop voice capture is local to the Desktop app, but transcription is performed by the connected Hermes backend:

  • Desktop records microphone audio locally through browser/Electron media APIs.
  • Desktop sends the recorded audio payload to the connected backend at /api/audio/transcribe.
  • In local mode, that backend runs on the user's machine.
  • In remote mode, the raw audio payload goes to the configured remote Hermes backend for transcription.
  • The model API receives the resulting transcript text, not the raw audio file.

The new settings text makes this explicit so users can distinguish microphone selection from STT backend location.

Dependency findings

Desktop microphone capture itself does not require faster_whisper, sounddevice, or numpy; capture is handled by Electron/browser APIs.

Those Python packages are relevant to backend-side local STT support. The new status helper reports their availability without triggering lazy installs. It also detects command-based local STT, such as an installed whisper binary, as a valid local transcription backend.

During local testing on macOS, backend status reported:

  • faster_whisper_available: false
  • sounddevice_available: false
  • numpy_available: false
  • local_command_available: true
  • available: true

So the Desktop app did not need those packages to capture audio, and this PR does not install them automatically.

Backward compatibility

A newer Desktop app may connect to an older remote Hermes backend that does not yet have /api/audio/status. In that case the settings panel now treats the specific 404 /api/audio/status / No such API endpoint response as unsupported diagnostics and displays:

This backend does not expose speech-to-text diagnostics yet. Voice transcription may still work; update the backend to see readiness details.

Other status failures still surface as real errors.

Validation

  • npm ci --workspace apps/desktop
  • npm run typecheck in apps/desktop
  • Targeted ESLint on changed Desktop TypeScript/TSX files
  • scripts/run_tests.sh tests/tools/test_transcription.py
  • python -m py_compile tools/transcription_tools.py hermes_cli/web_server.py
  • Direct smoke check of tools.transcription_tools.transcription_status()
  • Local macOS Desktop pack/build from the installed checkout
  • codesign --verify --deep --strict on the built app bundle
  • Manual Desktop test with a selected microphone and a remote backend

Note: the local macOS test build skipped notarization because Apple notarization environment variables were not configured. Codesign verification passed.

@alt-glitch alt-glitch added type/feature New feature or request comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/gateway Gateway runner, session dispatch, delivery tool/tts Text-to-speech and transcription P3 Low — cosmetic, nice to have labels Jun 15, 2026
@rod-nxtlevel rod-nxtlevel changed the title [codex] Add Desktop microphone picker and STT diagnostics Add Desktop microphone picker and STT diagnostics Jun 16, 2026
@rod-nxtlevel
rod-nxtlevel marked this pull request as ready for review June 16, 2026 14:34
@alt-glitch alt-glitch added comp/desktop Electron desktop app (apps/desktop/*) comp/cli CLI entry point, hermes_cli/, setup wizard and removed comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 26, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling a real Desktop voice-input gap: current main still records from the default device at apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts:187-189 and has no /api/audio/status route.

Problems

  • apps/desktop/src/hermes.ts:735 sets a 120-second transcription timeout, but remote-profile and global-remote requests are intercepted before the generic IPC handler reads it (apps/desktop/electron/main.ts:7801-7826, 7903-7923). Those paths call requestJsonForProfile(), which fixes the timeout at DEFAULT_FETCH_TIMEOUT_MS (apps/desktop/electron/main.ts:6172-6177), currently 15 seconds (electron/hardening.ts:6).
  • tools/transcription_tools.py:223 reports every non-hard-coded provider unavailable. Main supports user-declared command providers at tools/transcription_tools.py:1697-1711 and plugin providers at 1713-1737; valid configurations on either path would receive a false-negative diagnostic.

Suggested changes

  • Propagate request.timeoutMs through remote request helpers and cover remote transcription timeout routing.
  • Resolve diagnostics through the existing command-provider/plugin availability mechanisms, with tests for each extension path.
  • Add route and renderer compatibility tests for /api/audio/status and the old-backend 404 fallback.

Automated hermes-sweeper review.

return window.hermesDesktop.api<AudioTranscriptionResponse>({
path: '/api/audio/transcribe',
method: 'POST',
timeoutMs: 120_000,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reaches the normal IPC handler, but remote-profile/global-remote requests are intercepted first and routed through requestJsonForProfile(), which currently hard-codes the 15-second default. Please thread this timeout through the remote routing helpers too, or remote STT still times out after 15 seconds.

if name == "elevenlabs":
return bool(get_env_value("ELEVENLABS_API_KEY"))

return False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This marks all custom command and plugin-backed STT providers unavailable. transcribe_audio() already dispatches configured command providers and registered plugins after the built-in branches, so the diagnostics should consult those existing availability paths rather than returning false for every unknown name.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@0xLeathery

Copy link
Copy Markdown
Contributor

Review note: complementary failure mode in use-mic-recorder

Thanks for the device-picker + STT diagnostics work — this is clearly the right home for #76866.

While debugging continuous voice drops on macOS (see discussion on #75329), we found a path that device selection alone does not fix:

  • Continuous voice gates submission on WebAudio RMS → heardSpeech.
  • When AudioContext errors / flatlines (logged in desktop renderer), MediaRecorder can still produce audio, but handleTurn() drops the clip because heardSpeech stays false — no STT call, no toast.
  • So users can pick the correct mic and still get “utterance not captured.”

This PR already touches use-mic-recorder.ts. If you’re open to it, it would be high leverage to include a small robustness add-on in the same file:

  1. audioContext.resume() + meter-health detection
  2. Fallback VAD when the meter is unhealthy (timesliced MediaRecorder chunks)
  3. Conservative “non-trivial blob ⇒ heardSpeech” safety net
  4. Unit tests for dead-meter + live-chunks

Happy to sketch a minimal diff against your branch (no hardcoded device names; picker remains the source of deviceId). If you’d rather keep this PR strictly to picker/diagnostics, a follow-up fix(desktop): linked to #75329 is fine — just flagging so we don’t open a third competing PR on the same hook.

RCA comment: #75329 (comment)

@andrexibiza

Copy link
Copy Markdown
Contributor

Vox Lockin lane 09 verification receipt (feature PR for #46337):

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants