fix(feishu): keep topic replies in threads

Route Feishu topic progress, status, approval, stream, and fallback messages through threaded replies by preserving the originating message id as the reply target. Add regressions for tool progress topic metadata and Feishu metadata-driven reply routing.
This commit is contained in:
Yuqian 2026-05-04 22:28:22 +08:00 committed by kshitij
parent 48c241840a
commit 441ef75d15
4 changed files with 121 additions and 6 deletions

View file

@ -1962,6 +1962,45 @@ class TestAdapterBehavior(unittest.TestCase):
self.assertEqual(result.message_id, "om_reply")
self.assertTrue(captured["request"].request_body.reply_in_thread)
@patch.dict(os.environ, {}, clear=True)
def test_send_uses_metadata_reply_target_for_threaded_feishu_topic(self):
from gateway.config import PlatformConfig
from gateway.platforms.feishu import FeishuAdapter
adapter = FeishuAdapter(PlatformConfig())
captured = {}
class _MessageAPI:
def reply(self, request):
captured["request"] = request
return SimpleNamespace(
success=lambda: True,
data=SimpleNamespace(message_id="om_reply"),
)
adapter._client = SimpleNamespace(
im=SimpleNamespace(v1=SimpleNamespace(message=_MessageAPI()))
)
async def _direct(func, *args, **kwargs):
return func(*args, **kwargs)
with patch("gateway.platforms.feishu.asyncio.to_thread", side_effect=_direct):
result = asyncio.run(
adapter.send(
chat_id="oc_chat",
content="status update",
metadata={
"thread_id": "omt-thread",
"reply_to_message_id": "om_trigger",
},
)
)
self.assertTrue(result.success)
self.assertEqual(captured["request"].message_id, "om_trigger")
self.assertTrue(captured["request"].request_body.reply_in_thread)
@patch.dict(os.environ, {}, clear=True)
def test_send_retries_transient_failure(self):
from gateway.config import PlatformConfig

View file

@ -303,6 +303,50 @@ async def test_run_agent_progress_uses_event_message_id_for_slack_dm(monkeypatch
assert all(call["metadata"] == {"thread_id": "1234567890.000001"} for call in adapter.typing)
@pytest.mark.asyncio
async def test_run_agent_feishu_progress_replies_inside_existing_thread(monkeypatch, tmp_path):
"""Feishu needs reply_to plus reply_in_thread metadata for topic-scoped progress."""
monkeypatch.setenv("HERMES_TOOL_PROGRESS_MODE", "all")
fake_dotenv = types.ModuleType("dotenv")
fake_dotenv.load_dotenv = lambda *args, **kwargs: None
monkeypatch.setitem(sys.modules, "dotenv", fake_dotenv)
fake_run_agent = types.ModuleType("run_agent")
fake_run_agent.AIAgent = FakeAgent
monkeypatch.setitem(sys.modules, "run_agent", fake_run_agent)
adapter = ProgressCaptureAdapter(platform=Platform.FEISHU)
runner = _make_runner(adapter)
gateway_run = importlib.import_module("gateway.run")
monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
monkeypatch.setattr(gateway_run, "_resolve_runtime_agent_kwargs", lambda: {"api_key": "***"})
source = SessionSource(
platform=Platform.FEISHU,
chat_id="oc_chat",
chat_type="group",
thread_id="topic_17585",
)
result = await runner._run_agent(
message="hello",
context_prompt="",
history=[],
source=source,
session_id="sess-feishu-progress",
session_key="agent:main:feishu:group:oc_chat:topic_17585",
event_message_id="om_triggering_user_message",
)
assert result["final_response"] == "done"
assert adapter.sent
assert adapter.sent[0]["reply_to"] == "om_triggering_user_message"
assert adapter.sent[0]["metadata"] == {"thread_id": "topic_17585"}
assert adapter.edits
assert adapter.edits[0]["message_id"] == "progress-1"
# ---------------------------------------------------------------------------
# Preview truncation tests (all/new mode respects tool_preview_length)
# ---------------------------------------------------------------------------