From 0bf471dd68b3bcc3746ebab7d94e078d48b81e83 Mon Sep 17 00:00:00 2001 From: Juan Martitegui <6664687+juanmartitegui@users.noreply.github.com> Date: Wed, 29 Jul 2026 06:31:57 +0000 Subject: [PATCH] fix(gateway): preserve voice_only semantics for text input --- gateway/run.py | 8 +++----- tests/gateway/test_auto_voice_reply_format.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index d7f44c6a4b9..06a26d73aea 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -18239,11 +18239,9 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew (voice_mode == "all") or (voice_mode == "voice_only" and is_voice_input) # ``voice.auto_tts`` is synced into the adapter on gateway startup. - # Treat it as "voice accompanies text replies" unless a chat was - # explicitly turned off. The base adapter's own auto-TTS path only - # covers voice-input replies, so final text replies need the runner - # path here. - or (voice_mode != "off" and adapter_auto_tts) + # It is the fallback only when the chat has no explicit mode; + # otherwise the chat-level all/voice_only/off choice takes precedence. + or (voice_mode is None and adapter_auto_tts) ) if not should: logger.debug( diff --git a/tests/gateway/test_auto_voice_reply_format.py b/tests/gateway/test_auto_voice_reply_format.py index 16fdab8ef6e..824556ba21e 100644 --- a/tests/gateway/test_auto_voice_reply_format.py +++ b/tests/gateway/test_auto_voice_reply_format.py @@ -73,6 +73,25 @@ class TestAutoVoiceReplyFormat: voice_event, "hello", [], already_sent=True ) is True + def test_should_send_voice_reply_voice_only_still_requires_voice_input(self): + """Explicit voice_only must not widen to text input (#73508 regression). + + Persisted voice_only mode is synced into the adapter as an explicit + auto-TTS opt-in, so adapter_auto_tts is True for this chat. The + chat-level mode stays authoritative: text input gets no voice reply, + voice input still does. + """ + runner = _make_runner() + runner._voice_mode["telegram:123"] = "voice_only" + adapter = _make_adapter(Platform.TELEGRAM) + adapter._should_auto_tts_for_chat = MagicMock(return_value=True) + runner.adapters[Platform.TELEGRAM] = adapter + event = _make_event(Platform.TELEGRAM, chat_id="123") + + assert runner._should_send_voice_reply(event, "hello", []) is False + + voice_event = _make_event(Platform.TELEGRAM, chat_id="123", message_type=MessageType.VOICE) + assert runner._should_send_voice_reply(voice_event, "hello", [], already_sent=True) is True def _make_runner() -> GatewayRunner: with patch("gateway.run.GatewayRunner._load_voice_modes", return_value={}):