fix(dingtalk): strip leading mentions before command classification (#4724)

Group @bot /command messages are classified as commands like Feishu.
This commit is contained in:
richardmilles 2026-08-08 15:14:22 +02:00 committed by GitHub
parent 7b57609656
commit e16ef2969b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 75 additions and 2 deletions

View File

@ -14,7 +14,7 @@ from typing import Any
import httpx
from app.channels.base import Channel
from app.channels.commands import is_known_channel_command
from app.channels.commands import is_known_channel_command, strip_leading_mentions
from app.channels.connection_identity import attach_connection_identity
from app.channels.message_bus import InboundMessage, InboundMessageType, MessageBus, OutboundMessage, ResolvedAttachment
from deerflow.config.paths import VIRTUAL_PATH_PREFIX, get_paths
@ -428,8 +428,16 @@ class DingTalkChannel(Channel):
len(files),
)
if _is_dingtalk_command(text):
# DingTalk group chats often deliver "@bot /new" with the mention left
# in the text (Slack/Discord strip their own bot mention upstream).
# Skip a leading mention only for the command path so ordinary chat
# keeps @mentions intact for the agent; the stripped form also flows
# into the inbound so ChannelManager._handle_command parses the bare
# command. Mirrors FeishuChannel.
command_text = strip_leading_mentions(text)
if _is_dingtalk_command(command_text):
msg_type = InboundMessageType.COMMAND
text = command_text
else:
msg_type = InboundMessageType.CHAT

View File

@ -304,6 +304,71 @@ class TestOnChatbotMessage:
_run(go())
@pytest.mark.parametrize("text", ["@bot /new", "@_user_1 /help", "@bot /goal ship it"])
def test_leading_mention_before_command_classifies_and_strips(self, text):
"""DingTalk group chats leave "@bot /new" in the text; classify as COMMAND
and strip the mention so ChannelManager receives the bare command."""
async def go():
bus = MessageBus()
bus.publish_inbound = AsyncMock()
channel = DingTalkChannel(bus, config={})
channel._client_id = "test_key"
channel._main_loop = asyncio.get_event_loop()
channel._running = True
msg = _make_chatbot_message(
text=text,
conversation_type=_CONVERSATION_TYPE_GROUP,
sender_staff_id="user_002",
conversation_id="conv_group_001",
message_id="msg_mention_cmd",
)
channel._send_running_reply = AsyncMock()
channel._on_chatbot_message(msg)
await asyncio.sleep(0.1)
bus.publish_inbound.assert_awaited_once()
inbound = bus.publish_inbound.await_args.args[0]
assert inbound.msg_type == InboundMessageType.COMMAND, f"{text!r} should be COMMAND"
assert not inbound.text.startswith("@"), "leading mention must be stripped for dispatch"
assert inbound.text.split(maxsplit=1)[0] in KNOWN_CHANNEL_COMMANDS
_run(go())
def test_leading_mention_before_chat_keeps_mention(self):
"""A mentioned non-command stays CHAT and keeps the mention for the agent."""
async def go():
bus = MessageBus()
bus.publish_inbound = AsyncMock()
channel = DingTalkChannel(bus, config={})
channel._client_id = "test_key"
channel._main_loop = asyncio.get_event_loop()
channel._running = True
msg = _make_chatbot_message(
text="@bot please summarise this",
conversation_type=_CONVERSATION_TYPE_GROUP,
sender_staff_id="user_002",
conversation_id="conv_group_001",
message_id="msg_mention_chat",
)
channel._send_running_reply = AsyncMock()
channel._on_chatbot_message(msg)
await asyncio.sleep(0.1)
bus.publish_inbound.assert_awaited_once()
inbound = bus.publish_inbound.await_args.args[0]
assert inbound.msg_type == InboundMessageType.CHAT
assert inbound.text == "@bot please summarise this"
_run(go())
def test_group_message_integer_conversation_type_normalized(self):
"""SDK may deliver conversationType as int 2 — must still route as group."""