fix(pairing): persist shortcut commands and avoid Feishu side effects

- AgentLoop._state_command now persists user message and assistant
  response for shortcut commands (e.g. /pairing) so WebUI history
  hydration after _turn_end no longer shows an empty chat.  /new is
  excluded because it intentionally clears the session.

- Feishu _on_message sends pairing codes for unauthorized DMs before
  any media side effects (reactions, downloads, transcription).
  Group chat unauthorized senders are still silently ignored early.

- Update test_feishu_reply to assert the new DM pairing behavior.
This commit is contained in:
chengyongru 2026-05-14 14:32:45 +08:00 committed by Xubin Ren
parent 589792f41e
commit b68e9fa21e
2 changed files with 40 additions and 1 deletions

View File

@ -1712,6 +1712,18 @@ class FeishuChannel(BaseChannel):
while len(self._processed_message_ids) > 1000:
self._processed_message_ids.popitem(last=False)
# Early permission check — avoid side effects for unauthorized users.
# Group chats are silently ignored; DMs get a pairing code.
if not self.is_allowed(sender_id):
if chat_type == "p2p":
await self._handle_message(
sender_id=sender_id,
chat_id=sender_id,
content="",
is_dm=True,
)
return
# Add reaction (non-blocking — tracked background task)
task = asyncio.create_task(
self._add_reaction(message_id, self.config.react_emoji)

View File

@ -911,7 +911,8 @@ def test_on_background_task_done_removes_from_set() -> None:
@pytest.mark.asyncio
async def test_on_message_ignores_unauthorized_sender_before_side_effects() -> None:
async def test_on_message_unauthorized_dm_sends_pairing_code_without_side_effects() -> None:
"""Unauthorized DM sender gets a pairing code but no media side effects."""
channel = _make_feishu_channel(group_policy="open")
channel.config.allow_from = ["ou_allowed"]
channel._add_reaction = AsyncMock()
@ -927,6 +928,32 @@ async def test_on_message_ignores_unauthorized_sender_before_side_effects() -> N
await channel._on_message(event)
channel._add_reaction.assert_not_awaited()
channel._download_and_save_media.assert_not_awaited()
channel.transcribe_audio.assert_not_awaited()
# _handle_message is called to issue the pairing code in DMs
channel._handle_message.assert_awaited_once()
@pytest.mark.asyncio
async def test_on_message_unauthorized_group_ignored_before_side_effects() -> None:
"""Unauthorized group chat sender is silently ignored before any side effects."""
channel = _make_feishu_channel(group_policy="open")
channel.config.allow_from = ["ou_allowed"]
channel._add_reaction = AsyncMock()
channel._download_and_save_media = AsyncMock(return_value=("/tmp/audio.ogg", "[audio]"))
channel.transcribe_audio = AsyncMock(return_value="transcript")
channel._handle_message = AsyncMock()
event = _make_feishu_event(
chat_type="group",
msg_type="audio",
content='{"file_key": "file_1"}',
sender_open_id="ou_blocked",
)
await channel._on_message(event)
channel._add_reaction.assert_not_awaited()
channel._download_and_save_media.assert_not_awaited()
channel.transcribe_audio.assert_not_awaited()