diff --git a/tools/send_message_tool.py b/tools/send_message_tool.py index bfe1a630707..5c31160afbe 100644 --- a/tools/send_message_tool.py +++ b/tools/send_message_tool.py @@ -27,7 +27,7 @@ _FEISHU_TARGET_RE = re.compile(r"^\s*((?:oc|ou|on|chat|open)_[-A-Za-z0-9]+)(?::( # because the API requires a conversation ID. To DM a user you must first call # conversations.open to obtain a D... ID. Without this gate, Slack IDs fall # through to channel-name resolution, which only matches by name and fails. -_SLACK_TARGET_RE = re.compile(r"^\s*([CGD][A-Z0-9]{8,})\s*$") +_SLACK_TARGET_RE = re.compile(r"^\s*([CGDU][A-Z0-9]{8,})\s*$") # Session-derived Slack thread targets use ":". _SLACK_THREAD_TARGET_RE = re.compile(r"^\s*([CGD][A-Z0-9]{8,}):([^\s:]+)\s*$") _WEIXIN_TARGET_RE = re.compile(r"^\s*((?:wxid|gh|v\d+|wm|wb)_[A-Za-z0-9_-]+|[A-Za-z0-9._-]+@chatroom|filehelper)\s*$") @@ -275,6 +275,28 @@ def _handle_send(args): if duplicate_skip: return json.dumps(duplicate_skip) + # Slack: resolve user IDs (U...) to DM channel IDs via conversations.open + if platform_name == "slack" and chat_id and chat_id.startswith("U"): + try: + import aiohttp + async def _open_slack_dm(token, user_id): + url = "https://slack.com/api/conversations.open" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session: + async with session.post(url, headers=headers, json={"users": [user_id]}) as resp: + data = await resp.json() + if data.get("ok"): + return data["channel"]["id"] + return None + from model_tools import _run_async + dm_channel = _run_async(_open_slack_dm(pconfig.token, chat_id)) + if dm_channel: + chat_id = dm_channel + else: + return json.dumps({"error": f"Could not open DM with Slack user {chat_id}. Check bot permissions (im:write)."}) + except Exception as e: + return json.dumps({"error": f"Failed to open Slack DM: {e}"}) + try: from model_tools import _run_async result = _run_async(