From ff3ce2cdaa5a653cea2d9c6fd2df933d0166d1be Mon Sep 17 00:00:00 2001 From: Seventy7dot2 <22f3000994@ds.study.iitm.ac.in> Date: Sat, 14 Mar 2026 11:19:22 +0530 Subject: [PATCH 1/2] added user profile email to slack adapter --- .../docs/content/docs/guides/slack-nextjs.mdx | 7 +- examples/nextjs-chat/slack-manifest.yml | 1 + packages/adapter-slack/README.md | 124 ++++++++++-------- packages/adapter-slack/src/index.ts | 20 ++- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/apps/docs/content/docs/guides/slack-nextjs.mdx b/apps/docs/content/docs/guides/slack-nextjs.mdx index 42850559..20b88418 100644 --- a/apps/docs/content/docs/guides/slack-nextjs.mdx +++ b/apps/docs/content/docs/guides/slack-nextjs.mdx @@ -59,6 +59,7 @@ oauth_config: - reactions:read - reactions:write - users:read + - users:read.email settings: event_subscriptions: @@ -142,7 +143,7 @@ type Platform = keyof typeof bot.webhooks; export async function POST( request: Request, - context: RouteContext<"/api/webhooks/[platform]"> + context: RouteContext<"/api/webhooks/[platform]">, ) { const { platform } = await context.params; @@ -209,7 +210,9 @@ bot.onAction("info", async (event) => { ``` - The file extension must be `.tsx` (not `.ts`) when using JSX components like `Card` and `Button`. Make sure your `tsconfig.json` has `"jsx": "react-jsx"` and `"jsxImportSource": "chat"`. + The file extension must be `.tsx` (not `.ts`) when using JSX components like + `Card` and `Button`. Make sure your `tsconfig.json` has `"jsx": "react-jsx"` + and `"jsxImportSource": "chat"`. ## Deploy to Vercel diff --git a/examples/nextjs-chat/slack-manifest.yml b/examples/nextjs-chat/slack-manifest.yml index 2df83dde..790c4a2e 100644 --- a/examples/nextjs-chat/slack-manifest.yml +++ b/examples/nextjs-chat/slack-manifest.yml @@ -26,6 +26,7 @@ oauth_config: - reactions:write # User info for display names - users:read + - users:read.email settings: event_subscriptions: diff --git a/packages/adapter-slack/README.md b/packages/adapter-slack/README.md index 13971cf9..3618ef93 100644 --- a/packages/adapter-slack/README.md +++ b/packages/adapter-slack/README.md @@ -132,6 +132,7 @@ oauth_config: - reactions:read - reactions:write - users:read + - users:read.email settings: event_subscriptions: @@ -180,17 +181,17 @@ After creating the app, go to **Basic Information** → **App Credentials** and All options are auto-detected from environment variables when not provided. You can call `createSlackAdapter()` with no arguments if the env vars are set. -| Option | Required | Description | -|--------|----------|-------------| -| `botToken` | No | Bot token (`xoxb-...`). Auto-detected from `SLACK_BOT_TOKEN` | -| `signingSecret` | No* | Signing secret for webhook verification. Auto-detected from `SLACK_SIGNING_SECRET` | -| `clientId` | No | App client ID for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_ID` | -| `clientSecret` | No | App client secret for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_SECRET` | -| `encryptionKey` | No | AES-256-GCM key for encrypting stored tokens. Auto-detected from `SLACK_ENCRYPTION_KEY` | -| `installationKeyPrefix` | No | Prefix for the state key used to store workspace installations. Defaults to `slack:installation`. The full key is `{prefix}:{teamId}` | -| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) | +| Option | Required | Description | +| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `botToken` | No | Bot token (`xoxb-...`). Auto-detected from `SLACK_BOT_TOKEN` | +| `signingSecret` | No\* | Signing secret for webhook verification. Auto-detected from `SLACK_SIGNING_SECRET` | +| `clientId` | No | App client ID for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_ID` | +| `clientSecret` | No | App client secret for multi-workspace OAuth. Auto-detected from `SLACK_CLIENT_SECRET` | +| `encryptionKey` | No | AES-256-GCM key for encrypting stored tokens. Auto-detected from `SLACK_ENCRYPTION_KEY` | +| `installationKeyPrefix` | No | Prefix for the state key used to store workspace installations. Defaults to `slack:installation`. The full key is `{prefix}:{teamId}` | +| `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) | -*`signingSecret` is required — either via config or `SLACK_SIGNING_SECRET` env var. +\*`signingSecret` is required — either via config or `SLACK_SIGNING_SECRET` env var. ## Environment variables @@ -206,59 +207,59 @@ SLACK_ENCRYPTION_KEY=... # Optional, for token encryption ### Messaging -| Feature | Supported | -|---------|-----------| -| Post message | Yes | -| Edit message | Yes | -| Delete message | Yes | -| File uploads | Yes | -| Streaming | Native API | +| Feature | Supported | +| ------------------ | ------------------------- | +| Post message | Yes | +| Edit message | Yes | +| Delete message | Yes | +| File uploads | Yes | +| Streaming | Native API | | Scheduled messages | Yes (native, with cancel) | ### Rich content -| Feature | Supported | -|---------|-----------| -| Card format | Block Kit | -| Buttons | Yes | -| Link buttons | Yes | -| Select menus | Yes | -| Tables | Block Kit | -| Fields | Yes | -| Images in cards | Yes | -| Modals | Yes | +| Feature | Supported | +| --------------- | --------- | +| Card format | Block Kit | +| Buttons | Yes | +| Link buttons | Yes | +| Select menus | Yes | +| Tables | Block Kit | +| Fields | Yes | +| Images in cards | Yes | +| Modals | Yes | ### Conversations -| Feature | Supported | -|---------|-----------| -| Slash commands | Yes | -| Mentions | Yes | -| Add reactions | Yes | -| Remove reactions | Yes | -| Typing indicator | Yes | -| DMs | Yes | +| Feature | Supported | +| ------------------ | ------------ | +| Slash commands | Yes | +| Mentions | Yes | +| Add reactions | Yes | +| Remove reactions | Yes | +| Typing indicator | Yes | +| DMs | Yes | | Ephemeral messages | Yes (native) | ### Message history -| Feature | Supported | -|---------|-----------| -| Fetch messages | Yes | -| Fetch single message | Yes | -| Fetch thread info | Yes | -| Fetch channel messages | Yes | -| List threads | Yes | -| Fetch channel info | Yes | -| Post channel message | Yes | +| Feature | Supported | +| ---------------------- | --------- | +| Fetch messages | Yes | +| Fetch single message | Yes | +| Fetch thread info | Yes | +| Fetch channel messages | Yes | +| List threads | Yes | +| Fetch channel info | Yes | +| Post channel message | Yes | ### Platform-specific -| Feature | Supported | -|---------|-----------| -| Assistants API | Yes | -| Member joined channel | Yes | -| App Home tab | Yes | +| Feature | Supported | +| --------------------- | --------- | +| Assistants API | Yes | +| Member joined channel | Yes | +| App Home tab | Yes | ## Slack Assistants API @@ -286,13 +287,13 @@ bot.onAssistantContextChanged(async (event) => { The `SlackAdapter` exposes these methods for the Assistants API: -| Method | Description | -|--------|-------------| -| `setSuggestedPrompts(channelId, threadTs, prompts, title?)` | Show prompt suggestions in the thread | -| `setAssistantStatus(channelId, threadTs, status)` | Show a thinking/status indicator | -| `setAssistantTitle(channelId, threadTs, title)` | Set the thread title (shown in History) | -| `publishHomeView(userId, view)` | Publish a Home tab view for a user | -| `startTyping(threadId, status)` | Show a custom loading status (requires `assistant:write` scope) | +| Method | Description | +| ----------------------------------------------------------- | --------------------------------------------------------------- | +| `setSuggestedPrompts(channelId, threadTs, prompts, title?)` | Show prompt suggestions in the thread | +| `setAssistantStatus(channelId, threadTs, status)` | Show a thinking/status indicator | +| `setAssistantTitle(channelId, threadTs, title)` | Set the thread title (shown in History) | +| `publishHomeView(userId, view)` | Publish a Home tab view for a user | +| `startTyping(threadId, status)` | Show a custom loading status (requires `assistant:write` scope) | ### Required scopes and events @@ -318,7 +319,16 @@ When streaming in an assistant thread, you can attach Block Kit elements to the ```typescript await thread.stream(textStream, { stopBlocks: [ - { type: "actions", elements: [{ type: "button", text: { type: "plain_text", text: "Retry" }, action_id: "retry" }] }, + { + type: "actions", + elements: [ + { + type: "button", + text: { type: "plain_text", text: "Retry" }, + action_id: "retry", + }, + ], + }, ], }); ``` diff --git a/packages/adapter-slack/src/index.ts b/packages/adapter-slack/src/index.ts index ebc419d4..ac866600 100644 --- a/packages/adapter-slack/src/index.ts +++ b/packages/adapter-slack/src/index.ts @@ -351,6 +351,7 @@ type SlackInteractivePayload = /** Cached user info */ interface CachedUser { displayName: string; + email?: string; realName: string; } @@ -700,14 +701,18 @@ export class SlackAdapter implements Adapter { */ private async lookupUser( userId: string - ): Promise<{ displayName: string; realName: string }> { + ): Promise<{ displayName: string; realName: string; email?: string }> { const cacheKey = `slack:user:${userId}`; // Check cache first (via state adapter for serverless compatibility) if (this.chat) { const cached = await this.chat.getState().get(cacheKey); if (cached) { - return { displayName: cached.displayName, realName: cached.realName }; + return { + displayName: cached.displayName, + realName: cached.realName, + email: cached.email, + }; } } @@ -718,7 +723,7 @@ export class SlackAdapter implements Adapter { const user = result.user as { name?: string; real_name?: string; - profile?: { display_name?: string; real_name?: string }; + profile?: { display_name?: string; real_name?: string; email?: string }; }; // Slack user naming: profile.display_name > profile.real_name > real_name > name > userId @@ -730,6 +735,7 @@ export class SlackAdapter implements Adapter { userId; const realName = user?.real_name || user?.profile?.real_name || displayName; + const email = user?.profile?.email; // Cache the result via state adapter if (this.chat) { @@ -737,7 +743,7 @@ export class SlackAdapter implements Adapter { .getState() .set( cacheKey, - { displayName, realName }, + { displayName, realName, email }, SlackAdapter.USER_CACHE_TTL_MS ); @@ -757,8 +763,9 @@ export class SlackAdapter implements Adapter { userId, displayName, realName, + email, }); - return { displayName, realName }; + return { displayName, realName, email }; } catch (error) { this.logger.warn("Could not fetch user info", { userId, error }); // Fall back to user ID @@ -1868,12 +1875,14 @@ export class SlackAdapter implements Adapter { // since Slack events only include the user ID, not the username let userName = event.username || "unknown"; let fullName = event.username || "unknown"; + let userEmail = "unknown"; // If we have a user ID but no username, look up the user info if (event.user && !event.username) { const userInfo = await this.lookupUser(event.user); userName = userInfo.displayName; fullName = userInfo.realName; + userEmail = userInfo.email || "unknown"; } // Track thread participants for outgoing mention resolution (skip dupes) @@ -1911,6 +1920,7 @@ export class SlackAdapter implements Adapter { userId: event.user || event.bot_id || "unknown", userName, fullName, + email: userEmail, isBot: !!event.bot_id, isMe, }, From 24df83867b6cf389c14b9a9d5cc2ed1d612fc36b Mon Sep 17 00:00:00 2001 From: Seventy7dot2 <22f3000994@ds.study.iitm.ac.in> Date: Sat, 14 Mar 2026 11:32:47 +0530 Subject: [PATCH 2/2] feat slack-email: test added and minor changes --- packages/adapter-slack/src/index.test.ts | 43 ++++++++++++++++++++++++ packages/adapter-slack/src/index.ts | 1 + packages/chat/src/types.ts | 2 ++ 3 files changed, 46 insertions(+) diff --git a/packages/adapter-slack/src/index.test.ts b/packages/adapter-slack/src/index.test.ts index 91cd0989..3614830e 100644 --- a/packages/adapter-slack/src/index.test.ts +++ b/packages/adapter-slack/src/index.test.ts @@ -3018,6 +3018,49 @@ describe("fetchMessages", () => { }) ); }); + + it("fetches author email via lookupUser when fetching messages", async () => { + const adapter = createSlackAdapter({ + botToken: "xoxb-test-token", + signingSecret: secret, + logger: mockLogger, + botUserId: "U_BOT", + }); + + mockClientMethod( + adapter, + "conversations.replies", + vi.fn().mockResolvedValue({ + ok: true, + messages: [ + { + type: "message", + user: "U1", + text: "test message", + ts: "1000.000", + channel: "C123", + }, + ], + has_more: false, + }) + ); + mockClientMethod( + adapter, + "users.info", + vi.fn().mockResolvedValue({ + ok: true, + user: { name: "user1", profile: { email: "user1@example.com" } }, + }) + ); + + const state = createMockState(); + await adapter.initialize(createMockChatInstance(state)); + + const result = await adapter.fetchMessages("slack:C123:1234567890.000000"); + + expect(result.messages.length).toBe(1); + expect(result.messages[0].author.email).toBe("user1@example.com"); + }); }); // ============================================================================ diff --git a/packages/adapter-slack/src/index.ts b/packages/adapter-slack/src/index.ts index ac866600..59e51fe6 100644 --- a/packages/adapter-slack/src/index.ts +++ b/packages/adapter-slack/src/index.ts @@ -3382,6 +3382,7 @@ export class SlackAdapter implements Adapter { userId: event.user || event.bot_id || "unknown", userName, fullName, + email: "unknown", isBot: !!event.bot_id, isMe, }, diff --git a/packages/chat/src/types.ts b/packages/chat/src/types.ts index 3f95dd40..8326ef00 100644 --- a/packages/chat/src/types.ts +++ b/packages/chat/src/types.ts @@ -1051,6 +1051,8 @@ export interface RawMessage { } export interface Author { + /** Email address (if supported by platform and scopes) */ + email?: string; /** Display name */ fullName: string; /** Whether the author is a bot */