Skip to content

fix(discord): voice correctness — callback wiring, warm-up clip fix, threading, drain, configurable timeouts - #73517

Merged
teknium1 merged 13 commits into
mainfrom
fix/voice-discord
Jul 28, 2026
Merged

fix(discord): voice correctness — callback wiring, warm-up clip fix, threading, drain, configurable timeouts#73517
teknium1 merged 13 commits into
mainfrom
fix/voice-discord

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

fix(discord): consolidated voice correctness salvage — 8 fixes

Consolidated salvage of the Discord voice correctness cluster. All contributor commits cherry-picked onto current main with original authorship preserved in git log.

What's fixed

# Fix Source PR Author Closes
1 Wire _voice_input_callback at adapter start() and in the reconnect watcher; add join_voice_channel(text_channel_id=…) binding so programmatic joins route transcriptions without /voice join #61407 @isheng-eqi #60623
2 VoiceMixer now inherits discord.AudioSource — discord.py's vc.play() type-checks the source, duck-typing is_opus() was not enough #44023 @Cdddo #50899
3 Prepend warm-up lead silence (mixer path + legacy adelay path) so the voice socket warm-up doesn't clip the first TTS word #66851 @PRATHAMESH75 #66827
4 Preserve message_reference on voice replies so they thread to the message being answered (both the payload_json and fallback channel.send paths) #68733 @ooiuuii #68728
5 Drain pending voice input buffers (receiver.flush_pending() + auth check) before disconnect instead of discarding in-flight speech #68901 @toomij99
6 Configurable voice inactivity + playback timeouts (discord.voice_channel_inactivity_timeout_seconds, discord.voice_playback_timeout_seconds; 0 disables auto-leave) replacing hardcoded VOICE_TIMEOUT = 300; playback no longer counts as inactivity #50533 @namredips #17790
7 Pass the bound text channel's channel_prompt into voice-channel STT events so voice input gets the same per-channel context as typed messages (hand-written, ~10 lines) #50149
8 Guard success=True STT results with empty/whitespace transcripts: sentinel note instead of empty quotes the agent replies to / loops on. Reimplemented against the current plain-quoted note wording; concept + original tests by @deacon-botdoctor #41603 @deacon-botdoctor (credit) #41603
9 ffmpeg discovery on Windows: resolve_ffmpeg_executable() (FFMPEG_PATH override → shared repo discovery → winget fallback) used by playback, receiver PCM→WAV, and mixer decode. Follow-up refactor delegates PATH/prefix discovery to the existing tools.transcription_tools._find_ffmpeg_binary so one helper owns discovery #60627 @LauraGPT #60624

Authorship

Cherry-picked commits keep original authors: @isheng-eqi (×3), @Cdddo (×2), @PRATHAMESH75, @ooiuuii (luyifan), @toomij99 (Ariel Tov Ben), @namredips (Jeffrey Cox), @LauraGPT. Contributor email mappings added under contributors/emails/. Rebase-merge to preserve per-commit authorship.

Credit also to @boroughnexus-cto — earliest submitter of the callback-wiring idea in #11160 (Apr 2026, startup-only); #61407 supersedes it with reconnect + channel-binding coverage.

Post-merge closes

Verification

  • pytest -o addopts="" -q on tests/gateway/test_voice_command.py test_discord_voice_mixer.py test_stt_config.py test_discord_send.py test_platform_reconnect.py321 passed
  • Sabotage check: disabling the empty-transcript guard turns its test red; restoring turns it green
  • ruff check clean on all touched files
  • Stale-base gate: branch base is 0 commits behind origin/main; diff shows only the 15 intended files
  • Dropped from fix(discord): make voice timeouts configurable #50533: its stray scripts/release.py/AUTHOR_MAP hunk (frozen); used contributors/emails/ instead

Notes for reviewers

Second wave (not in this PR, needs deeper review): #43003 + #42611 (concurrency/bounded transcription — dedupe against each other), #60403 (long-TTS clamp).

Infographic

fix(discord): voice correctness — callback wiring, warm-up clip fix, threading, drain, configurable timeouts

isheng-eqi and others added 13 commits July 28, 2026 09:21
_voice_input_callback was only set in _handle_voice_channel_join, not at adapter connect or reconnect. Voice transcription was logged but never forwarded as an inbound message without explicit /voice join.

Wire the callback at both connect and reconnect paths.

Fixes #60623
…t tests

PR #61407 accesses self._handle_voice_channel_input in _platform_reconnect_watcher. The test mock runner created via _make_runner() must have this attribute.
- Wire adapter._voice_input_callback at connect and reconnect so voice
  transcription is forwarded without requiring /voice join (#60623).
- Add optional text_channel_id and source params to DiscordAdapter
  .join_voice_channel() so automatic/programmatic voice joins can
  establish the text-channel binding needed by _handle_voice_channel_input.
- Add TestVoiceInputCallbackWiring: asserts callback wiring on startup
  and reconnect for Discord adapters with voice attributes.
…bility

VoiceMixer duck-typed the discord.AudioSource interface (is_opus, read,
cleanup) but never inherited from it. discord.py's vc.play() does an
isinstance check and rejects non-AudioSource objects, causing the voice
fx mixer to silently fail with:

  "Voice mixer failed to start: source must be an AudioSource not
   VoiceMixer"

Added missing `import discord` and changed the class definition from
`class VoiceMixer:` to `class VoiceMixer(discord.AudioSource):`.
Discord's voice socket needs a brief warm-up before receiving clients
actually hear audio; the first ~100-200ms is lost, clipping the first
word/syllable of TTS playback. Prepend a configurable lead of silence to
speech on both playback paths:

- Mixer path: new _lead_silence_bytes() helper prepends PCM silence
  (BYTES_PER_MS constant added to voice_mixer.py) before play_speech, on
  both the reply and the pre-tool ack.
- Legacy FFmpegPCMAudio path: apply -af adelay=<ms>:all=1.

Tunable via discord.voice_fx.lead_silence_ms (default 200, 0 disables).

Fixes #66827
…ption_tools helper

Keep one owner for PATH/local-prefix ffmpeg discovery: ffmpeg_utils now
delegates to tools.transcription_tools._find_ffmpeg_binary and only adds
the Discord-specific FFMPEG_PATH override and Windows winget fallback on
top (follow-up to PR #60627 by @LauraGPT, fixes #60624).
…d empty transcripts

Two hand-written fixes in the voice input path:

- _handle_voice_channel_input now resolves the bound text channel's
  channel_prompt via the adapter's _resolve_channel_prompt so voice input
  gets the same per-channel context as typed messages (fixes #50149).
- _enrich_message_with_transcription now guards success=True results whose
  transcript is empty/whitespace-only (silence, cut-off, inaudible audio):
  instead of emitting empty quotes the agent gets a clear sentinel note.
  Reimplemented against the current plain-quoted note wording; original
  concept and tests by @deacon-botdoctor in PR #41603 (fixes #41603).
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on a3a6b4f

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence upload failed.

Failed to upload diff-665a0833239e-onboarding-overlay-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-665a0833239e-onboarding-overlay-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso)

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/discord Discord bot adapter tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants