diff --git a/apps/api/src/app/api/integrations/slack/events/process-mention/process-mention.ts b/apps/api/src/app/api/integrations/slack/events/process-mention/process-mention.ts index 860f0a0f7be..d7e18861fe5 100644 --- a/apps/api/src/app/api/integrations/slack/events/process-mention/process-mention.ts +++ b/apps/api/src/app/api/integrations/slack/events/process-mention/process-mention.ts @@ -2,7 +2,11 @@ import type { AppMentionEvent } from "@slack/types"; import { db } from "@superset/db/client"; import { integrationConnections } from "@superset/db/schema"; import { and, eq } from "drizzle-orm"; -import { formatErrorForSlack, runSlackAgent } from "../utils/run-agent"; +import { + formatErrorForSlack, + resolveUserMentions, + runSlackAgent, +} from "../utils/run-agent"; import { formatSideEffectsMessage } from "../utils/slack-blocks"; import { createSlackClient } from "../utils/slack-client"; @@ -70,8 +74,13 @@ export async function processSlackMention({ } try { + const resolve = await resolveUserMentions({ + texts: [event.text], + slack, + }); + const result = await runSlackAgent({ - prompt: event.text, + prompt: resolve(event.text), channelId: event.channel, threadTs, organizationId: connection.organizationId, diff --git a/apps/api/src/app/api/integrations/slack/events/utils/run-agent/index.ts b/apps/api/src/app/api/integrations/slack/events/utils/run-agent/index.ts index 50bffc25ad5..f036494b8f4 100644 --- a/apps/api/src/app/api/integrations/slack/events/utils/run-agent/index.ts +++ b/apps/api/src/app/api/integrations/slack/events/utils/run-agent/index.ts @@ -1,2 +1,6 @@ export type { SlackAgentResult } from "./run-agent"; -export { formatErrorForSlack, runSlackAgent } from "./run-agent"; +export { + formatErrorForSlack, + resolveUserMentions, + runSlackAgent, +} from "./run-agent"; diff --git a/apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts b/apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts index 02247077371..5ededcc6273 100644 --- a/apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts +++ b/apps/api/src/app/api/integrations/slack/events/utils/run-agent/run-agent.ts @@ -12,6 +12,47 @@ import { parseToolName, } from "./mcp-clients"; +/** + * Collect unique Slack user IDs from `<@U...>` mentions in text, + * resolve them via `users.info`, and return a replacer function. + */ +export async function resolveUserMentions({ + texts, + slack, +}: { + texts: string[]; + slack: WebClient; +}): Promise<(text: string) => string> { + const userIds = new Set(); + for (const text of texts) { + for (const match of text.matchAll(/<@(U[A-Z0-9]+)>/g)) { + if (match[1]) userIds.add(match[1]); + } + } + + if (userIds.size === 0) { + return (text) => text; + } + + const userMap = new Map(); + await Promise.all( + [...userIds].map(async (id) => { + try { + const info = await slack.users.info({ user: id }); + const name = info.user?.real_name || info.user?.name || info.user?.id; + if (name) { + userMap.set(id, name); + } + } catch (error) { + console.warn(`[slack-agent] Failed to resolve user ${id}:`, error); + } + }), + ); + + return (text: string) => + text.replace(/<@(U[A-Z0-9]+)>/g, (_, id) => `@${userMap.get(id) ?? id}`); +} + async function fetchThreadContext({ token, channelId, @@ -41,8 +82,22 @@ async function fetchThreadContext({ return ""; } + // Collect mention texts + message author IDs for resolution + const textsToResolve = messages.flatMap((msg) => { + const parts = [msg.text ?? ""]; + if (msg.user) parts.push(`<@${msg.user}>`); + return parts; + }); + const resolve = await resolveUserMentions({ + texts: textsToResolve, + slack, + }); + const formatted = messages - .map((msg) => `<${msg.user}>: ${msg.text}`) + .map( + (msg) => + `${msg.user ? resolve(`<@${msg.user}>`) : "unknown"}: ${resolve(msg.text ?? "")}`, + ) .join("\n"); return `--- Thread Context (${messages.length} previous messages) ---\n${formatted}\n--- End Thread Context ---`; @@ -248,9 +303,19 @@ async function handleGetChannelHistory({ return JSON.stringify({ messages: [] }); } + const textsToResolve = result.messages.flatMap((msg) => { + const parts = [msg.text ?? ""]; + if (msg.user) parts.push(`<@${msg.user}>`); + return parts; + }); + const resolve = await resolveUserMentions({ + texts: textsToResolve, + slack, + }); + const messages = result.messages.map((msg) => ({ - user: msg.user, - text: msg.text, + user: msg.user ? resolve(`<@${msg.user}>`) : msg.user, + text: msg.text ? resolve(msg.text) : msg.text, ts: msg.ts, thread_ts: msg.thread_ts, }));