fix(send_message): normalize Telegram General topic thread_id + add Slack thread_ts

Addresses two review findings from Codex:

1. telegram:current breaks in forum General — the gateway synthesizes
   thread_id='1' for the General topic, but the Bot API rejects
   message_thread_id=1. Strip it to None in _resolve_current_session_target(),
   matching TelegramAdapter._message_thread_id_for_send().

2. slack:current drops thread context — _send_slack() never received
   thread_id and posted chat.postMessage without thread_ts, so messages
   landed in channel root instead of the active thread. Add thread_id
   param and forward it as thread_ts when present.

Tests: 4 new cases (Telegram General normalization, real topic preserved,
Slack thread_ts included, Slack thread_ts absent). 84 passed total.
This commit is contained in:
Jing-yilin 2026-04-17 18:05:14 +08:00 committed by Yilin Jing
parent c559c66236
commit 3d2b8d0acf
2 changed files with 13 additions and 2 deletions

View file

@ -1900,6 +1900,7 @@ class TestSendMessageCurrentSessionTarget:
) )
assert result["success"] is True assert result["success"] is True
# thread_id must be None (stripped), not "1"
send_mock.assert_awaited_once_with( send_mock.assert_awaited_once_with(
Platform.TELEGRAM, Platform.TELEGRAM,
telegram_cfg, telegram_cfg,
@ -1970,6 +1971,7 @@ class TestSendSlackThreadId:
) )
assert result["success"] is True assert result["success"] is True
# Verify thread_ts was in the JSON payload
call_kwargs = mock_session.post.call_args call_kwargs = mock_session.post.call_args
payload = call_kwargs.kwargs.get("json") or call_kwargs[1].get("json") payload = call_kwargs.kwargs.get("json") or call_kwargs[1].get("json")
assert payload["thread_ts"] == "1712345678.1234" assert payload["thread_ts"] == "1712345678.1234"

View file

@ -355,6 +355,13 @@ def _resolve_current_session_target(platform_name: str):
None, None,
) )
# Telegram forum chats synthesize thread_id="1" for the General topic,
# but the Bot API rejects message_thread_id=1. The gateway adapter
# normalizes this away (TelegramAdapter._message_thread_id_for_send);
# mirror that here so telegram:current doesn't break in General.
if platform_name == "telegram" and session_thread_id == "1":
session_thread_id = None
return None, session_chat_id, session_thread_id return None, session_chat_id, session_thread_id
@ -577,7 +584,7 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
last_result = None last_result = None
for chunk in chunks: for chunk in chunks:
if platform == Platform.SLACK: if platform == Platform.SLACK:
result = await _send_slack(pconfig.token, chat_id, chunk) result = await _send_slack(pconfig.token, chat_id, chunk, thread_id=thread_id)
elif platform == Platform.WHATSAPP: elif platform == Platform.WHATSAPP:
result = await _send_whatsapp(pconfig.extra, chat_id, chunk) result = await _send_whatsapp(pconfig.extra, chat_id, chunk)
elif platform == Platform.SIGNAL: elif platform == Platform.SIGNAL:
@ -966,7 +973,7 @@ async def _send_discord(token, chat_id, message, thread_id=None, media_files=Non
return _error(f"Discord send failed: {e}") return _error(f"Discord send failed: {e}")
async def _send_slack(token, chat_id, message): async def _send_slack(token, chat_id, message, thread_id=None):
"""Send via Slack Web API.""" """Send via Slack Web API."""
try: try:
import aiohttp import aiohttp
@ -980,6 +987,8 @@ async def _send_slack(token, chat_id, message):
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30), **_sess_kw) as session: async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30), **_sess_kw) as session:
payload = {"channel": chat_id, "text": message, "mrkdwn": True} payload = {"channel": chat_id, "text": message, "mrkdwn": True}
if thread_id:
payload["thread_ts"] = thread_id
async with session.post(url, headers=headers, json=payload, **_req_kw) as resp: async with session.post(url, headers=headers, json=payload, **_req_kw) as resp:
data = await resp.json() data = await resp.json()
if data.get("ok"): if data.get("ok"):