fix(discord): voice correctness — callback wiring, warm-up clip fix, threading, drain, configurable timeouts - #73517
Merged
Merged
Conversation
_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
…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).
Contributor
૮ >ﻌ< ა ci reviewran on a3a6b4f ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 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) |
This was referenced Jul 29, 2026
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(discord): consolidated voice correctness salvage — 8 fixes
Consolidated salvage of the Discord voice correctness cluster. All contributor commits cherry-picked onto current
mainwith original authorship preserved in git log.What's fixed
_voice_input_callbackat adapterstart()and in the reconnect watcher; addjoin_voice_channel(text_channel_id=…)binding so programmatic joins route transcriptions without/voice joinVoiceMixernow inheritsdiscord.AudioSource— discord.py'svc.play()type-checks the source, duck-typingis_opus()was not enoughadelaypath) so the voice socket warm-up doesn't clip the first TTS wordmessage_referenceon voice replies so they thread to the message being answered (both thepayload_jsonand fallbackchannel.sendpaths)receiver.flush_pending()+ auth check) before disconnect instead of discarding in-flight speechdiscord.voice_channel_inactivity_timeout_seconds,discord.voice_playback_timeout_seconds;0disables auto-leave) replacing hardcodedVOICE_TIMEOUT = 300; playback no longer counts as inactivitychannel_promptinto voice-channel STT events so voice input gets the same per-channel context as typed messages (hand-written, ~10 lines)success=TrueSTT 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-botdoctorresolve_ffmpeg_executable()(FFMPEG_PATHoverride → shared repo discovery → winget fallback) used by playback, receiver PCM→WAV, and mixer decode. Follow-up refactor delegates PATH/prefix discovery to the existingtools.transcription_tools._find_ffmpeg_binaryso one helper owns discoveryAuthorship
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
duplicate)Verification
pytest -o addopts="" -qontests/gateway/test_voice_command.py test_discord_voice_mixer.py test_stt_config.py test_discord_send.py test_platform_reconnect.py→ 321 passedruff checkclean on all touched filesorigin/main; diff shows only the 15 intended filesscripts/release.py/AUTHOR_MAP hunk (frozen); usedcontributors/emails/insteadNotes for reviewers
play_in_voice_channel(timeout probing +try/finallyre-arm); fix(discord): prepend warm-up silence so TTS first word isn't clipped (#66827) #66851's warm-up and Fix Discord ffmpeg discovery on Windows #60627'sexecutable=were re-applied inside the new structure during conflict resolution — see the legacy-pathFFmpegPCMAudio(audio_path, executable=…, **ffmpeg_opts)call.ffmpeg_utils.pykeeps only Discord-specific concerns (FFMPEG_PATHenv override, winget fallback); generic discovery delegates totools.transcription_tools.Second wave (not in this PR, needs deeper review): #43003 + #42611 (concurrency/bounded transcription — dedupe against each other), #60403 (long-TTS clamp).
Infographic