mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-23 10:42:00 +00:00
fix(telegram): stop typing indicator lingering after final reply
After the agent's final response, the '...typing' bubble persisted ~5s. send() re-triggers send_typing() after every delivery so the bubble survives intermediate progress messages (Telegram clears typing on each delivered message). But that re-trigger also fired on the FINAL send, re-arming Telegram's ~5s timer AFTER the gateway had already torn down its typing-refresh loop — and Telegram exposes no stop-typing API, so nothing cancelled it. Gate the post-send re-trigger on the absence of metadata['notify'] (set only on the final user-visible reply via _mark_notify_metadata). Both the rich-message and legacy send paths are covered; intermediate progress sends still re-trigger so the bubble stays alive mid-response. Fixes #48678
This commit is contained in:
parent
c0409a87ff
commit
565b7c8d9d
2 changed files with 54 additions and 9 deletions
|
|
@ -2517,11 +2517,17 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
rich_result = await self._try_send_rich(chat_id, content, reply_to, metadata)
|
||||
if rich_result is not None:
|
||||
if rich_result.success:
|
||||
# Re-trigger typing like the legacy success path does.
|
||||
try:
|
||||
await self.send_typing(chat_id, metadata=metadata)
|
||||
except Exception:
|
||||
pass # Typing failures are non-fatal
|
||||
# Re-trigger typing like the legacy success path does,
|
||||
# but ONLY for intermediate sends. On the final reply
|
||||
# (metadata["notify"]) the gateway has already torn down
|
||||
# the typing refresh loop; re-arming Telegram's ~5s timer
|
||||
# here would leave the "...typing" bubble lingering after
|
||||
# the answer (no Bot API call cancels it). See #48678.
|
||||
if not (metadata or {}).get("notify"):
|
||||
try:
|
||||
await self.send_typing(chat_id, metadata=metadata)
|
||||
except Exception:
|
||||
pass # Typing failures are non-fatal
|
||||
return rich_result
|
||||
|
||||
# Format and split message if needed
|
||||
|
|
@ -2746,10 +2752,16 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
# so without this the "...typing" bubble disappears mid-response
|
||||
# (especially noticeable when the agent sends intermediate progress
|
||||
# messages like "Checking:" before running tools).
|
||||
try:
|
||||
await self.send_typing(chat_id, metadata=metadata)
|
||||
except Exception:
|
||||
pass # Typing failures are non-fatal
|
||||
# Skip this on the FINAL reply (metadata["notify"]): the gateway has
|
||||
# already cancelled the typing refresh loop by the time the final
|
||||
# send returns, so re-arming Telegram's ~5s timer here would leave
|
||||
# the indicator lingering after the answer with nothing to cancel
|
||||
# it (Telegram exposes no stop-typing API). See #48678.
|
||||
if not (metadata or {}).get("notify"):
|
||||
try:
|
||||
await self.send_typing(chat_id, metadata=metadata)
|
||||
except Exception:
|
||||
pass # Typing failures are non-fatal
|
||||
|
||||
return SendResult(
|
||||
success=True,
|
||||
|
|
|
|||
|
|
@ -213,6 +213,39 @@ async def test_legacy_send_keeps_chunk_indicators_outside_fenced_code_lines(adap
|
|||
assert not re.match(r"^```\s+\(\d+/\d+\)$", line), text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_final_send_does_not_retrigger_typing(adapter):
|
||||
"""The final reply (metadata['notify']) must NOT re-arm Telegram's typing
|
||||
timer. The gateway has already torn down the refresh loop by then, so a
|
||||
re-trigger here would leave the '...typing' bubble lingering after the
|
||||
answer (Telegram has no stop-typing API). See #48678."""
|
||||
adapter._bot = MagicMock()
|
||||
adapter._bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=1))
|
||||
adapter._bot.send_chat_action = AsyncMock()
|
||||
adapter._rich_messages_enabled = False
|
||||
|
||||
result = await adapter.send("12345", "All done.", metadata={"notify": True})
|
||||
|
||||
assert result.success is True
|
||||
adapter._bot.send_chat_action.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_intermediate_send_still_retriggers_typing(adapter):
|
||||
"""Intermediate/progress sends (no notify marker) keep re-triggering typing
|
||||
so the '...typing' bubble survives across progress messages while the agent
|
||||
is still working."""
|
||||
adapter._bot = MagicMock()
|
||||
adapter._bot.send_message = AsyncMock(return_value=SimpleNamespace(message_id=1))
|
||||
adapter._bot.send_chat_action = AsyncMock()
|
||||
adapter._rich_messages_enabled = False
|
||||
|
||||
result = await adapter.send("12345", "Checking:", metadata={"expect_edits": True})
|
||||
|
||||
assert result.success is True
|
||||
adapter._bot.send_chat_action.assert_awaited()
|
||||
|
||||
|
||||
# =========================================================================
|
||||
# format_message - bold and italic
|
||||
# =========================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue