fix(telegram): route resumed DM topic sends directly

This commit is contained in:
Maxim Esipov
2026-05-18 14:01:51 +03:00
committed by Teknium
parent 2994bf494d
commit de4cb55bf3
3 changed files with 42 additions and 14 deletions

View File

@@ -45,10 +45,10 @@ def _thread_metadata_for_source(source, reply_to_message_id: str | None = None)
Most platforms route threaded sends with a generic ``thread_id`` metadata
value. Telegram private-chat topics created through Hermes' DM-topic helper
are exposed in updates as ``message_thread_id`` plus a reply anchor, but
outbound sends only render in the correct Telegram lane when the adapter
supplies both ``message_thread_id`` and ``reply_to_message_id``. Mark those
lanes so the Telegram adapter can avoid the known-bad partial routes.
are exposed in updates as ``message_thread_id`` plus a reply anchor. Live
user-message replies route with ``message_thread_id`` + ``reply_to_message_id``;
synthetic/resumed sends that have no reply anchor fall back to Telegram's
``direct_messages_topic_id`` when the Bot API supports it.
"""
thread_id = getattr(source, "thread_id", None)
if thread_id is None:
@@ -56,6 +56,9 @@ def _thread_metadata_for_source(source, reply_to_message_id: str | None = None)
metadata = {"thread_id": thread_id}
if _platform_name(getattr(source, "platform", None)) == "telegram" and getattr(source, "chat_type", None) == "dm":
metadata["telegram_dm_topic_reply_fallback"] = True
tid = str(thread_id)
if tid and tid not in {"", "1"}:
metadata["direct_messages_topic_id"] = tid
anchor = reply_to_message_id or getattr(source, "message_id", None)
if anchor is not None:
metadata["telegram_reply_to_message_id"] = str(anchor)
@@ -67,10 +70,9 @@ def _reply_anchor_for_event(event) -> str | None:
Telegram forum/supergroup topics should be routed by topic metadata, not by
replying to the triggering message. Hermes-created Telegram private-chat
topic lanes are different: Bot API sends reject their ``message_thread_id``
and do not route with ``direct_messages_topic_id``. Those lanes only remain
visible when sent with both the private topic thread id and a reply to the
triggering user message.
topic lanes prefer replying to the triggering user message so the answer
stays attached to the active lane; synthetic/resumed sends fall back to
``direct_messages_topic_id`` metadata when no message id is available.
"""
source = getattr(event, "source", None)
platform = _platform_name(getattr(source, "platform", None))

View File

@@ -560,9 +560,10 @@ class TelegramAdapter(BasePlatformAdapter):
Supergroup/forum topics use ``message_thread_id``. True Bot API Direct
Messages topics can opt in with explicit ``direct_messages_topic_id``
metadata. Hermes-created private-chat topic lanes are marked with
``telegram_dm_topic_reply_fallback`` and must send the private topic
thread id together with a reply anchor. Live testing showed that either
parameter alone can render outside the visible lane.
``telegram_dm_topic_reply_fallback``. Live replies send the private
topic thread id together with a reply anchor; synthetic/resumed sends
without an anchor use ``direct_messages_topic_id`` when metadata has it.
``message_thread_id`` alone can render outside the visible lane.
When ``reply_to_mode`` is ``"off"``, the reply anchor is suppressed for
DM topic fallback sends while preserving the ``message_thread_id`` so
@@ -574,6 +575,12 @@ class TelegramAdapter(BasePlatformAdapter):
if reply_to_message_id is None:
reply_to_message_id = cls._metadata_reply_to_message_id(metadata)
if reply_to_message_id is None:
direct_topic_id = cls._metadata_direct_messages_topic_id(metadata)
if direct_topic_id is not None:
return {
"message_thread_id": None,
"direct_messages_topic_id": int(direct_topic_id),
}
return {}
return {"message_thread_id": cls._message_thread_id_for_send(thread_id)}
direct_topic_id = cls._metadata_direct_messages_topic_id(metadata)