mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 21:26:39 +00:00
fix(channels): decode Slack entity escapes in inbound message text (#6045)
* fix(channels): decode Slack entity escapes in inbound message text Slack sends a user-typed &, < and > as &, < and > in event text, so raw <...> always marks a control sequence. The Slack channel passed that text to the agent as-is, so code, comparisons and names like R&D reached the agent as 'a < b', 'R&D'. Decode the three entities after bot-mention stripping (amp last, so an escaped entity is decoded once) and keep mentions and links untouched. * test(channels): cover Slack entities inside link labels Signed-off-by: Willem Jiang <willem.jiang@gmail.com> --------- Signed-off-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
81fa17c15a
commit
4394c23b66
@ -79,6 +79,16 @@ def _strip_leading_slack_bot_mention(text: str, bot_user_id: str | None) -> str:
|
||||
return text[end + 1 :].lstrip()
|
||||
|
||||
|
||||
def _unescape_slack_text(text: str) -> str:
|
||||
"""Decode the three entities Slack escapes in message text.
|
||||
|
||||
Slack sends a user-typed ``&``, ``<`` and ``>`` as ``&``, ``<`` and
|
||||
``>`` so that raw ``<...>`` always marks a control sequence (mention,
|
||||
link). ``&`` is decoded last so an escaped entity is decoded only once.
|
||||
"""
|
||||
return text.replace("<", "<").replace(">", ">").replace("&", "&")
|
||||
|
||||
|
||||
class SlackChannel(Channel):
|
||||
"""Slack IM channel using Socket Mode (WebSocket, no public IP).
|
||||
|
||||
@ -349,6 +359,9 @@ class SlackChannel(Channel):
|
||||
text = event.get("text", "").strip()
|
||||
if event.get("type") == "app_mention":
|
||||
text = _strip_leading_slack_bot_mention(text, self._bot_user_id)
|
||||
# Decode after mention stripping: a real mention is a raw <@...>, while
|
||||
# a user-typed "<@...>" arrives as <@...> and must stay text.
|
||||
text = _unescape_slack_text(text)
|
||||
if not text:
|
||||
return
|
||||
|
||||
|
||||
@ -8855,6 +8855,49 @@ class TestSlackAllowedUsers:
|
||||
assert inbound.text == "/help"
|
||||
assert inbound.msg_type == InboundMessageType.COMMAND
|
||||
|
||||
def _inbound_text_for(self, event: dict) -> str:
|
||||
from app.channels.slack import SlackChannel
|
||||
|
||||
bus = MessageBus()
|
||||
bus.publish_inbound = AsyncMock()
|
||||
channel = SlackChannel(bus=bus, config={"bot_user_id": "UBOT"})
|
||||
channel._loop = self._immediate_loop()
|
||||
channel._add_reaction = MagicMock()
|
||||
channel._send_running_reply = MagicMock()
|
||||
|
||||
with patch(
|
||||
"app.channels.slack.asyncio.run_coroutine_threadsafe",
|
||||
side_effect=self._submit_coro,
|
||||
):
|
||||
channel._handle_message_event({"user": "U123456", "channel": "C123", "ts": "1710000000.000100", **event})
|
||||
|
||||
return bus.get_inbound_nowait().text
|
||||
|
||||
def test_inbound_text_decodes_slack_entity_escapes(self):
|
||||
# Slack delivers a user-typed &, < and > as &, < and >.
|
||||
text = self._inbound_text_for(
|
||||
{
|
||||
"type": "app_mention",
|
||||
"text": '<@UBOT> if a < b && c > d: print("R&D")',
|
||||
}
|
||||
)
|
||||
|
||||
assert text == 'if a < b && c > d: print("R&D")'
|
||||
|
||||
def test_inbound_text_decodes_an_escaped_entity_only_once(self):
|
||||
# The user literally typed "<", which Slack sends as "&lt;".
|
||||
assert self._inbound_text_for({"text": "write &lt; in HTML"}) == "write < in HTML"
|
||||
|
||||
def test_inbound_text_keeps_slack_control_sequences(self):
|
||||
text = self._inbound_text_for({"text": "ask <@U999> about <https://example.com|the doc> > now"})
|
||||
|
||||
assert text == "ask <@U999> about <https://example.com|the doc> > now"
|
||||
|
||||
def test_inbound_text_decodes_entities_inside_slack_link_labels(self):
|
||||
text = self._inbound_text_for({"text": "see <https://x.com|a&b>"})
|
||||
|
||||
assert text == "see <https://x.com|a&b>"
|
||||
|
||||
def test_app_mention_strips_labelled_leading_bot_mention(self):
|
||||
from app.channels.slack import SlackChannel
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user