mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(telegram): accept both list and mapping shapes for group_topics config
The forum-topic skill-binding lookup assumed config.extra['group_topics']
was always a list of {chat_id, topics} entries. When an operator writes the
natural mapping shape ({"-100...": [...]}), iterating yields string keys and
chat_entry.get(...) raises AttributeError, breaking dispatch for that group.
Normalize both shapes to a common iterator and guard non-dict/non-list
entries so malformed config falls through cleanly instead of crashing.
This commit is contained in:
parent
42d0174699
commit
5505dbbf43
2 changed files with 118 additions and 4 deletions
|
|
@ -7707,11 +7707,31 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||
chat_topic = created_name
|
||||
|
||||
elif chat_type == "group" and thread_id_str:
|
||||
# Group/supergroup forum topic skill binding via config.extra['group_topics']
|
||||
group_topics_config: list = self.config.extra.get("group_topics", [])
|
||||
for chat_entry in group_topics_config:
|
||||
# Group/supergroup forum topic skill binding via config.extra['group_topics'].
|
||||
# Accept both supported shapes:
|
||||
# [{"chat_id": "-100...", "topics": [...]}]
|
||||
# and legacy/operator-edited mapping shape:
|
||||
# {"-100...": [{"thread_id": 12, ...}]}
|
||||
group_topics_config = self.config.extra.get("group_topics", [])
|
||||
if isinstance(group_topics_config, dict):
|
||||
group_topics_iter = [
|
||||
{"chat_id": cfg_chat_id, "topics": topics}
|
||||
for cfg_chat_id, topics in group_topics_config.items()
|
||||
]
|
||||
elif isinstance(group_topics_config, list):
|
||||
group_topics_iter = [
|
||||
entry for entry in group_topics_config if isinstance(entry, dict)
|
||||
]
|
||||
else:
|
||||
group_topics_iter = []
|
||||
for chat_entry in group_topics_iter:
|
||||
if str(chat_entry.get("chat_id", "")) == str(chat.id):
|
||||
for topic in chat_entry.get("topics", []):
|
||||
topics = chat_entry.get("topics", [])
|
||||
if not isinstance(topics, list):
|
||||
topics = []
|
||||
for topic in topics:
|
||||
if not isinstance(topic, dict):
|
||||
continue
|
||||
tid = topic.get("thread_id")
|
||||
if tid is not None and str(tid) == thread_id_str:
|
||||
chat_topic = topic.get("name")
|
||||
|
|
|
|||
|
|
@ -853,6 +853,100 @@ def test_group_topic_chat_id_int_string_coercion():
|
|||
assert event.source.chat_topic == "Dev"
|
||||
|
||||
|
||||
def test_group_topic_mapping_shape_config():
|
||||
"""Operator-edited mapping shape {chat_id: [topics]} must resolve like the list shape."""
|
||||
from gateway.platforms.base import MessageType
|
||||
|
||||
# Dict/mapping shape instead of the canonical list-of-entries shape.
|
||||
adapter = _make_adapter(group_topics_config={
|
||||
"-1001234567890": [
|
||||
{"name": "Engineering", "thread_id": 5, "skill": "software-development"},
|
||||
{"name": "Sales", "thread_id": 12, "skill": "sales-framework"},
|
||||
],
|
||||
})
|
||||
|
||||
msg = _make_mock_message(
|
||||
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)
|
||||
|
||||
assert event.auto_skill == "sales-framework"
|
||||
assert event.source.chat_topic == "Sales"
|
||||
|
||||
|
||||
def test_group_topic_malformed_config_does_not_crash():
|
||||
"""Non-dict entries / non-list topics must be skipped, not raise AttributeError."""
|
||||
from gateway.platforms.base import MessageType
|
||||
|
||||
# Junk list entries (str) are filtered out; a matching entry with a good
|
||||
# topic still resolves; non-dict topic entries within it are skipped.
|
||||
adapter = _make_adapter(group_topics_config=[
|
||||
"not-a-dict",
|
||||
{"chat_id": -1001234567890, "topics": ["also-not-a-dict",
|
||||
{"name": "Good", "thread_id": 5}]},
|
||||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=5,
|
||||
text="hi",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
assert event.auto_skill is None
|
||||
assert event.source.chat_topic == "Good"
|
||||
|
||||
|
||||
def test_group_topic_non_list_topics_does_not_crash():
|
||||
"""A matched entry whose topics is not a list must fall through, not raise."""
|
||||
from gateway.platforms.base import MessageType
|
||||
|
||||
adapter = _make_adapter(group_topics_config=[
|
||||
{"chat_id": -1001234567890, "topics": "oops-not-a-list"},
|
||||
])
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=5,
|
||||
text="hi",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
assert event.auto_skill is None
|
||||
assert event.source.chat_topic is None
|
||||
|
||||
|
||||
def test_group_topic_scalar_config_falls_through():
|
||||
"""A scalar (int/str) group_topics value must fall through cleanly, not raise."""
|
||||
from gateway.platforms.base import MessageType
|
||||
|
||||
adapter = _make_adapter(group_topics_config=42)
|
||||
|
||||
msg = _make_mock_message(
|
||||
chat_id=-1001234567890,
|
||||
chat_type=_ChatType.SUPERGROUP,
|
||||
thread_id=5,
|
||||
text="hi",
|
||||
is_topic_message=True,
|
||||
is_forum=True,
|
||||
)
|
||||
event = adapter._build_message_event(msg, MessageType.TEXT)
|
||||
|
||||
assert event.auto_skill is None
|
||||
assert event.source.chat_topic is None
|
||||
|
||||
|
||||
# ── _build_message_event: from_user=None fallback in DMs ──
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue