From 09b6dcc4f394b5c21b2de3b3fd7a41bc1c985df6 Mon Sep 17 00:00:00 2001 From: jvinals <4139778+jvinals@users.noreply.github.com> Date: Mon, 18 May 2026 20:07:09 -0700 Subject: [PATCH] fix(send_message): resolve Slack user IDs to DM channel IDs The _SLACK_TARGET_RE regex only matched IDs starting with C (channel), G (group), or D (direct message). Slack user IDs start with U, causing 'Could not resolve' errors when trying to send DMs to specific users. Changes: - Expand _SLACK_TARGET_RE to accept U-prefixed IDs (user IDs) - Add conversations.open fallback to resolve user IDs to DM channel IDs before sending, since chat.postMessage requires a conversation ID Fixes #ISSUE_NUMBER --- tools/send_message_tool.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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(