mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-30 06:41:51 +00:00
fix: avoid Telegram group reply thread session splits
This commit is contained in:
parent
d69f0c1a99
commit
7b2bcba167
3 changed files with 115 additions and 16 deletions
|
|
@ -4795,25 +4795,28 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
elif chat.type == ChatType.CHANNEL:
|
||||
chat_type = "channel"
|
||||
|
||||
# Resolve DM topic name and skill binding.
|
||||
# In private chats, only preserve thread ids for real topic messages
|
||||
# (is_topic_message=True). Telegram puts message_thread_id on every
|
||||
# DM that is a reply, even when the user is just replying to a
|
||||
# previous message in the same DM — that bogus id then routes to a
|
||||
# nonexistent thread and Telegram returns 'Message thread not found'
|
||||
# on send (#3206).
|
||||
# Resolve Telegram topic name and skill binding.
|
||||
# Only preserve message_thread_id when Telegram marks the message as
|
||||
# a real topic/forum message. Telegram can also populate
|
||||
# message_thread_id for ordinary reply UI anchors; treating those as
|
||||
# durable session threads fragments workflows such as CAPTCHA/login
|
||||
# handoffs where the user later replies "done" in the same group.
|
||||
# Private chats have the same pitfall: only real DM topic messages
|
||||
# (is_topic_message=True) should keep the thread id, otherwise sends
|
||||
# can hit Telegram's 'Message thread not found' error (#3206).
|
||||
thread_id_raw = message.message_thread_id
|
||||
is_topic_message = bool(getattr(message, "is_topic_message", False))
|
||||
is_forum_group = getattr(chat, "is_forum", False) is True
|
||||
thread_id_str = None
|
||||
if thread_id_raw is not None:
|
||||
if chat_type == "group":
|
||||
if chat_type == "group" and (is_topic_message or is_forum_group):
|
||||
thread_id_str = str(thread_id_raw)
|
||||
elif chat_type == "dm" and is_topic_message:
|
||||
thread_id_str = str(thread_id_raw)
|
||||
# For forum groups without an explicit topic, default to the
|
||||
# General-topic id so the gateway routes back to the General topic
|
||||
# rather than dropping into the bot's main channel (#22423).
|
||||
if chat_type == "group" and thread_id_str is None and getattr(chat, "is_forum", False):
|
||||
if chat_type == "group" and thread_id_str is None and is_forum_group:
|
||||
thread_id_str = self._GENERAL_TOPIC_THREAD_ID
|
||||
chat_topic = None
|
||||
topic_skill = None
|
||||
|
|
|
|||
|
|
@ -449,13 +449,15 @@ def test_cache_dm_topic_from_message_no_overwrite():
|
|||
|
||||
def _make_mock_message(chat_id=111, chat_type="private", text="hello", thread_id=None,
|
||||
user_id=42, user_name="Test User", forum_topic_created=None,
|
||||
is_topic_message=None):
|
||||
is_topic_message=None, is_forum=None):
|
||||
"""Create a mock Telegram Message for _build_message_event tests."""
|
||||
chat = SimpleNamespace(
|
||||
id=chat_id,
|
||||
type=chat_type,
|
||||
title=None,
|
||||
)
|
||||
if is_forum is not None:
|
||||
chat.is_forum = is_forum
|
||||
# Add full_name attribute for DM chats
|
||||
if not hasattr(chat, "full_name"):
|
||||
chat.full_name = user_name
|
||||
|
|
@ -594,7 +596,12 @@ def test_group_topic_skill_binding():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=5, text="hello"
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=5,
|
||||
text="hello",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
@ -617,7 +624,12 @@ def test_group_topic_skill_binding_second_topic():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=12, text="deal update"
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=12,
|
||||
text="deal update",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
@ -639,7 +651,12 @@ def test_group_topic_no_skill_binding():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=1, text="hey"
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=1,
|
||||
text="hey",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
@ -661,7 +678,12 @@ def test_group_topic_unmapped_thread_id():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=999, text="random"
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=999,
|
||||
text="random",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
@ -683,7 +705,12 @@ def test_group_topic_unmapped_chat_id():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1009999999999, chat_type=_ChatType.SUPERGROUP, thread_id=5, text="wrong group"
|
||||
chat_id=-1009999999999,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=5,
|
||||
text="wrong group",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
@ -720,7 +747,12 @@ def test_group_topic_chat_id_int_string_coercion():
|
|||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890, chat_type=_ChatType.SUPERGROUP, thread_id=7, text="test"
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=7,
|
||||
text="test",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,70 @@ def _make_adapter():
|
|||
return adapter
|
||||
|
||||
|
||||
def test_non_forum_group_reply_thread_id_does_not_fork_session_key():
|
||||
"""Reply-derived thread ids in ordinary groups must not create topic lanes."""
|
||||
from gateway.platforms import telegram as telegram_mod
|
||||
|
||||
adapter = _make_adapter()
|
||||
message = SimpleNamespace(
|
||||
text="Done",
|
||||
caption=None,
|
||||
chat=SimpleNamespace(
|
||||
id=-100123,
|
||||
type=telegram_mod.ChatType.SUPERGROUP,
|
||||
is_forum=False,
|
||||
title="Regular group",
|
||||
),
|
||||
from_user=SimpleNamespace(id=456, full_name="Alice"),
|
||||
message_thread_id=461,
|
||||
is_topic_message=False,
|
||||
reply_to_message=SimpleNamespace(
|
||||
message_id=460,
|
||||
text="Please complete the CAPTCHA/login, then reply done.",
|
||||
caption=None,
|
||||
),
|
||||
message_id=462,
|
||||
date=None,
|
||||
)
|
||||
|
||||
event = adapter._build_message_event(message, msg_type=MessageType.TEXT)
|
||||
|
||||
assert event.source.chat_id == "-100123"
|
||||
assert event.source.chat_type == "group"
|
||||
assert event.source.thread_id is None
|
||||
assert build_session_key(event.source) == "agent:main:telegram:group:-100123:456"
|
||||
|
||||
|
||||
def test_forum_group_topic_message_preserves_thread_session_key():
|
||||
"""Real Telegram forum-topic messages should still route by topic id."""
|
||||
from gateway.platforms import telegram as telegram_mod
|
||||
|
||||
adapter = _make_adapter()
|
||||
message = SimpleNamespace(
|
||||
text="hello from topic",
|
||||
caption=None,
|
||||
chat=SimpleNamespace(
|
||||
id=-100123,
|
||||
type=telegram_mod.ChatType.SUPERGROUP,
|
||||
is_forum=True,
|
||||
title="Forum group",
|
||||
),
|
||||
from_user=SimpleNamespace(id=456, full_name="Alice"),
|
||||
message_thread_id=17585,
|
||||
is_topic_message=True,
|
||||
reply_to_message=None,
|
||||
message_id=10,
|
||||
date=None,
|
||||
)
|
||||
|
||||
event = adapter._build_message_event(message, msg_type=MessageType.TEXT)
|
||||
|
||||
assert event.source.chat_id == "-100123"
|
||||
assert event.source.chat_type == "group"
|
||||
assert event.source.thread_id == "17585"
|
||||
assert build_session_key(event.source) == "agent:main:telegram:group:-100123:17585"
|
||||
|
||||
|
||||
def test_forum_general_topic_without_message_thread_id_keeps_thread_context():
|
||||
"""Forum General-topic messages should keep synthetic thread context."""
|
||||
from gateway.platforms import telegram as telegram_mod
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue