Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gateway/platforms/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -5391,6 +5391,10 @@ def _enqueue_text_event(self, event: MessageEvent) -> None:
if event.text:
existing.text = f"{existing.text}\n{event.text}" if existing.text else event.text
existing._last_chunk_len = chunk_len # type: ignore[attr-defined]
# Advance reply anchor to the latest message so the bot replies to
# the most recent user message when stacked prompts are batched.
if event.message_id:
existing.message_id = event.message_id
# Merge any media that might be attached
if event.media_urls:
existing.media_urls.extend(event.media_urls)
Expand Down
33 changes: 33 additions & 0 deletions tests/gateway/test_telegram_text_batching.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,39 @@ async def test_batch_cleans_up_after_flush(self):
assert len(adapter._pending_text_batches) == 0
assert len(adapter._pending_text_batch_tasks) == 0

@pytest.mark.asyncio
async def test_stacked_prompts_reply_anchor_advances_to_last_message(self):
"""Reply anchor must track the latest message_id when user stacks prompts.

When two distinct user messages arrive quickly (stacked prompts, not
Telegram split-chunks), the batched event should carry the *last*
message_id so the bot replies to the most recent message the user sent,
not the first one.
"""
adapter = _make_adapter()

event1 = _make_event("first prompt")
event1.message_id = "100"
event2 = _make_event("second prompt")
event2.message_id = "101"

adapter._enqueue_text_event(event1)
await asyncio.sleep(0.02) # within batch window
adapter._enqueue_text_event(event2)

await asyncio.sleep(0.2)

adapter.handle_message.assert_called_once()
dispatched = adapter.handle_message.call_args[0][0]
# Text must be merged
assert "first prompt" in dispatched.text
assert "second prompt" in dispatched.text
# Reply anchor must be the LAST message, not the first
assert dispatched.message_id == "101", (
f"Expected reply anchor '101' (last message) but got {dispatched.message_id!r}. "
"Bot would have replied to the wrong message."
)

@pytest.mark.asyncio
async def test_dm_topic_batching_recovers_thread_before_keying(self):
"""DM-topic text batches should use the recovered topic lane."""
Expand Down