diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index d893b8115cf..9bc04c43653 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -2262,6 +2262,17 @@ class TelegramAdapter(BasePlatformAdapter): text = f"❓ {_html.escape(question)}" thread_id = self._metadata_thread_id(metadata) + if choices: + # Render full option text in the message body so mobile + # users can read long choices that would be truncated in + # inline button labels. Buttons keep short numeric labels + # (1, 2, …, Other) to avoid Telegram truncation. + option_lines = "\n".join( + f"{i + 1}. {_html.escape(str(c))}" + for i, c in enumerate(choices) + ) + text += f"\n\n{option_lines}" + kwargs: Dict[str, Any] = { "chat_id": int(chat_id), "text": text, @@ -2271,15 +2282,12 @@ class TelegramAdapter(BasePlatformAdapter): if choices: # Telegram caps callback_data at 64 bytes; keep "cl::" - # short. Button label is also capped (~64 chars in practice). + # short. rows = [] - for idx, choice in enumerate(choices): - label = str(choice) - if len(label) > 60: - label = label[:57] + "..." + for idx in range(len(choices)): rows.append([ InlineKeyboardButton( - f"{idx + 1}. {label}", + str(idx + 1), callback_data=f"cl:{clarify_id}:{idx}", ) ]) diff --git a/tests/gateway/test_telegram_clarify_buttons.py b/tests/gateway/test_telegram_clarify_buttons.py index b9e7bd5130f..56c0f9e60c4 100644 --- a/tests/gateway/test_telegram_clarify_buttons.py +++ b/tests/gateway/test_telegram_clarify_buttons.py @@ -100,6 +100,10 @@ class TestTelegramSendClarify: kwargs = adapter._bot.send_message.call_args[1] assert kwargs["chat_id"] == 12345 assert "Which option?" in kwargs["text"] + # Full option text rendered in the message body (not just buttons) + assert "1. alpha" in kwargs["text"] + assert "2. beta" in kwargs["text"] + assert "3. gamma" in kwargs["text"] # InlineKeyboardMarkup with N+1 buttons (3 choices + Other) markup = kwargs["reply_markup"] assert markup is not None @@ -144,13 +148,15 @@ class TestTelegramSendClarify: assert result.success is False @pytest.mark.asyncio - async def test_truncates_long_choice_label(self): + async def test_long_choice_rendered_in_body_not_truncated(self): + """Long choice text appears in full in the message body; + button labels stay short numeric (1, 2, …).""" adapter = _make_adapter() mock_msg = MagicMock() mock_msg.message_id = 102 adapter._bot.send_message = AsyncMock(return_value=mock_msg) - long_choice = "x" * 200 # > 60 char cap + long_choice = "x" * 200 result = await adapter.send_clarify( chat_id="12345", question="?", @@ -159,9 +165,12 @@ class TestTelegramSendClarify: session_key="sk4", ) assert result.success is True - # The truncation logic replaces with "..." past 57 chars; we don't - # inspect the mock's button labels directly (auto-MagicMock), but - # we can verify the call didn't raise on absurdly long input. + kwargs = adapter._bot.send_message.call_args[1] + # The full long choice text appears in the message body + assert long_choice in kwargs["text"] + # The button label should be short ("1"), not the long choice + # (we can't inspect mock button labels directly, but the send + # succeeded — old truncation code could raise on edge cases) @pytest.mark.asyncio async def test_html_escapes_question(self):