mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(gateway): bind Telegram handoffs to DM topics
This commit is contained in:
parent
bcfc7458fa
commit
8341b72122
2 changed files with 69 additions and 13 deletions
|
|
@ -6814,26 +6814,39 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
|||
str(home.thread_id) if home.thread_id else None
|
||||
)
|
||||
|
||||
# Determine chat_type for the destination source. If we created a
|
||||
# thread, key the session_key as a thread (build_session_key sets
|
||||
# thread sessions to user-shared by default, which is what we
|
||||
# want — the synthetic turn and any later real-user message both
|
||||
# land on the same key without needing a user_id).
|
||||
if new_thread_id:
|
||||
# Determine chat_type/user_id for the destination source.
|
||||
#
|
||||
# Telegram private-chat DM topics are represented differently from
|
||||
# group/forum threads by the inbound adapter. A handoff-created topic
|
||||
# in a positive Telegram chat_id must therefore use the same DM-topic
|
||||
# source shape as the user's next real message; otherwise the synthetic
|
||||
# handoff turn binds a generic `thread` session key while real replies
|
||||
# arrive on a `dm` session key.
|
||||
home_chat_id = str(home.chat_id)
|
||||
is_telegram_private_chat = False
|
||||
if platform == Platform.TELEGRAM:
|
||||
try:
|
||||
is_telegram_private_chat = int(home_chat_id) > 0
|
||||
except (TypeError, ValueError):
|
||||
is_telegram_private_chat = False
|
||||
|
||||
if new_thread_id and not is_telegram_private_chat:
|
||||
dest_chat_type = "thread"
|
||||
dest_user_id = "system:handoff"
|
||||
else:
|
||||
# No thread — assume DM-style for the home channel. For
|
||||
# group/channel home channels without thread support
|
||||
# (Matrix/WhatsApp/Signal), the platform's own keying makes
|
||||
# the synthetic turn shared anyway (single-DM platforms).
|
||||
# No thread — assume DM-style for the home channel. For Telegram
|
||||
# private-chat topics, use the real user id (same as chat_id) so
|
||||
# topic-mode checks and binding persistence see the same identity as
|
||||
# subsequent inbound user messages.
|
||||
dest_chat_type = "dm"
|
||||
dest_user_id = home_chat_id if is_telegram_private_chat else "system:handoff"
|
||||
|
||||
dest_source = SessionSource(
|
||||
platform=platform,
|
||||
chat_id=str(home.chat_id),
|
||||
chat_id=home_chat_id,
|
||||
chat_name=home.name,
|
||||
chat_type=dest_chat_type,
|
||||
user_id="system:handoff",
|
||||
user_id=dest_user_id,
|
||||
user_name="Handoff",
|
||||
thread_id=effective_thread_id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from unittest.mock import AsyncMock, MagicMock
|
|||
import pytest
|
||||
|
||||
from hermes_state import SessionDB
|
||||
from gateway.config import GatewayConfig, Platform, PlatformConfig
|
||||
from gateway.config import GatewayConfig, HomeChannel, Platform, PlatformConfig
|
||||
from gateway.platforms.base import MessageEvent
|
||||
from gateway.session import SessionEntry, SessionSource, build_session_key
|
||||
|
||||
|
|
@ -800,6 +800,49 @@ async def test_first_message_inside_topic_records_topic_binding(tmp_path, monkey
|
|||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handoff_to_telegram_dm_topic_uses_dm_lane_not_generic_thread(tmp_path):
|
||||
"""Handoff-created Telegram DM topics must use the real DM-topic lane.
|
||||
|
||||
A positive Telegram chat_id is a private chat. If handoff treats the new
|
||||
topic as generic chat_type="thread" with user_id="system:handoff", the
|
||||
synthetic turn lands under agent:...:thread:chat:topic while real user
|
||||
replies arrive as chat_type="dm" with user_id=chat_id. Recovery then sees
|
||||
the topic as unbound and can rewrite it to another recent topic.
|
||||
"""
|
||||
session_db = SessionDB(db_path=tmp_path / "state.db")
|
||||
session_db.enable_telegram_topic_mode(chat_id="208214988", user_id="208214988")
|
||||
runner = _make_runner(session_db=session_db)
|
||||
runner.config.platforms[Platform.TELEGRAM].home_channel = HomeChannel(
|
||||
platform=Platform.TELEGRAM,
|
||||
chat_id="208214988",
|
||||
name="Tester DM",
|
||||
)
|
||||
adapter = runner.adapters[Platform.TELEGRAM]
|
||||
adapter.create_handoff_thread = AsyncMock(return_value="17585")
|
||||
adapter.send.return_value = SimpleNamespace(success=True)
|
||||
captured = {}
|
||||
|
||||
async def fake_handle_message(event):
|
||||
captured["source"] = event.source
|
||||
return "handoff ok"
|
||||
|
||||
runner._handle_message = AsyncMock(side_effect=fake_handle_message)
|
||||
|
||||
await runner._process_handoff({
|
||||
"id": "cli-session",
|
||||
"title": "CLI work",
|
||||
"handoff_platform": "telegram",
|
||||
})
|
||||
|
||||
expected_source = _make_source(thread_id="17585")
|
||||
expected_key = build_session_key(expected_source)
|
||||
runner.session_store.switch_session.assert_called_once_with(expected_key, "cli-session")
|
||||
assert captured["source"].chat_type == "dm"
|
||||
assert captured["source"].user_id == "208214988"
|
||||
assert captured["source"].thread_id == "17585"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_topic_root_command_creates_and_pins_system_topic(tmp_path, monkeypatch):
|
||||
import gateway.run as gateway_run
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue