@inkeep/agents-work-apps 0.0.0-dev-20260211191741 → 0.0.0-dev-20260211220939

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/env.d.ts +24 -2
  2. package/dist/env.js +13 -2
  3. package/dist/github/index.d.ts +3 -3
  4. package/dist/github/mcp/index.d.ts +2 -2
  5. package/dist/github/routes/setup.d.ts +2 -2
  6. package/dist/github/routes/tokenExchange.d.ts +2 -2
  7. package/dist/github/routes/webhooks.d.ts +2 -2
  8. package/dist/slack/i18n/index.d.ts +2 -0
  9. package/dist/slack/i18n/index.js +3 -0
  10. package/dist/slack/i18n/strings.d.ts +73 -0
  11. package/dist/slack/i18n/strings.js +67 -0
  12. package/dist/slack/index.d.ts +18 -0
  13. package/dist/slack/index.js +28 -0
  14. package/dist/slack/middleware/permissions.d.ts +31 -0
  15. package/dist/slack/middleware/permissions.js +159 -0
  16. package/dist/slack/routes/events.d.ts +10 -0
  17. package/dist/slack/routes/events.js +390 -0
  18. package/dist/slack/routes/index.d.ts +10 -0
  19. package/dist/slack/routes/index.js +47 -0
  20. package/dist/slack/routes/oauth.d.ts +20 -0
  21. package/dist/slack/routes/oauth.js +325 -0
  22. package/dist/slack/routes/users.d.ts +10 -0
  23. package/dist/slack/routes/users.js +358 -0
  24. package/dist/slack/routes/workspaces.d.ts +10 -0
  25. package/dist/slack/routes/workspaces.js +875 -0
  26. package/dist/slack/services/agent-resolution.d.ts +41 -0
  27. package/dist/slack/services/agent-resolution.js +99 -0
  28. package/dist/slack/services/blocks/index.d.ts +73 -0
  29. package/dist/slack/services/blocks/index.js +103 -0
  30. package/dist/slack/services/client.d.ts +105 -0
  31. package/dist/slack/services/client.js +220 -0
  32. package/dist/slack/services/commands/index.d.ts +19 -0
  33. package/dist/slack/services/commands/index.js +538 -0
  34. package/dist/slack/services/events/app-mention.d.ts +40 -0
  35. package/dist/slack/services/events/app-mention.js +234 -0
  36. package/dist/slack/services/events/block-actions.d.ts +40 -0
  37. package/dist/slack/services/events/block-actions.js +221 -0
  38. package/dist/slack/services/events/index.d.ts +6 -0
  39. package/dist/slack/services/events/index.js +7 -0
  40. package/dist/slack/services/events/modal-submission.d.ts +30 -0
  41. package/dist/slack/services/events/modal-submission.js +346 -0
  42. package/dist/slack/services/events/streaming.d.ts +26 -0
  43. package/dist/slack/services/events/streaming.js +228 -0
  44. package/dist/slack/services/events/utils.d.ts +146 -0
  45. package/dist/slack/services/events/utils.js +369 -0
  46. package/dist/slack/services/index.d.ts +16 -0
  47. package/dist/slack/services/index.js +16 -0
  48. package/dist/slack/services/modals.d.ts +86 -0
  49. package/dist/slack/services/modals.js +355 -0
  50. package/dist/slack/services/nango.d.ts +85 -0
  51. package/dist/slack/services/nango.js +462 -0
  52. package/dist/slack/services/security.d.ts +35 -0
  53. package/dist/slack/services/security.js +65 -0
  54. package/dist/slack/services/types.d.ts +26 -0
  55. package/dist/slack/services/types.js +1 -0
  56. package/dist/slack/services/workspace-tokens.d.ts +25 -0
  57. package/dist/slack/services/workspace-tokens.js +27 -0
  58. package/dist/slack/types.d.ts +10 -0
  59. package/dist/slack/types.js +1 -0
  60. package/package.json +10 -2
@@ -0,0 +1,41 @@
1
+ //#region src/slack/services/agent-resolution.d.ts
2
+ /**
3
+ * Slack Agent Resolution Service
4
+ *
5
+ * Determines which agent to use for a given Slack interaction.
6
+ * Priority: Channel default > Workspace default (all admin-controlled)
7
+ */
8
+ /** Configuration for a resolved agent */
9
+ interface ResolvedAgentConfig {
10
+ projectId: string;
11
+ agentId: string;
12
+ agentName?: string;
13
+ source: 'channel' | 'workspace' | 'none';
14
+ }
15
+ interface AgentResolutionParams {
16
+ tenantId: string;
17
+ teamId: string;
18
+ channelId?: string;
19
+ userId?: string;
20
+ }
21
+ /**
22
+ * Resolve the effective agent configuration.
23
+ * Priority: Channel default > Workspace default
24
+ *
25
+ * @param params - Resolution parameters including tenant, team, and channel IDs
26
+ * @returns The resolved agent configuration, or null if no agent is configured
27
+ */
28
+ declare function resolveEffectiveAgent(params: AgentResolutionParams): Promise<ResolvedAgentConfig | null>;
29
+ /**
30
+ * Get all agent configuration sources for display purposes.
31
+ *
32
+ * @param params - Resolution parameters
33
+ * @returns Object containing channel, workspace configs, and the effective choice
34
+ */
35
+ declare function getAgentConfigSources(params: AgentResolutionParams): Promise<{
36
+ channelConfig: ResolvedAgentConfig | null;
37
+ workspaceConfig: ResolvedAgentConfig | null;
38
+ effective: ResolvedAgentConfig | null;
39
+ }>;
40
+ //#endregion
41
+ export { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent };
@@ -0,0 +1,99 @@
1
+ import { getLogger } from "../../logger.js";
2
+ import runDbClient_default from "../../db/runDbClient.js";
3
+ import { getWorkspaceDefaultAgentFromNango } from "./nango.js";
4
+ import { findWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
5
+
6
+ //#region src/slack/services/agent-resolution.ts
7
+ /**
8
+ * Slack Agent Resolution Service
9
+ *
10
+ * Determines which agent to use for a given Slack interaction.
11
+ * Priority: Channel default > Workspace default (all admin-controlled)
12
+ */
13
+ const logger = getLogger("slack-agent-resolution");
14
+ /**
15
+ * Resolve the effective agent configuration.
16
+ * Priority: Channel default > Workspace default
17
+ *
18
+ * @param params - Resolution parameters including tenant, team, and channel IDs
19
+ * @returns The resolved agent configuration, or null if no agent is configured
20
+ */
21
+ async function resolveEffectiveAgent(params) {
22
+ const { tenantId, teamId, channelId } = params;
23
+ logger.debug({
24
+ tenantId,
25
+ teamId,
26
+ channelId
27
+ }, "Resolving effective agent");
28
+ if (channelId) {
29
+ const channelConfig = await findWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
30
+ if (channelConfig?.enabled) {
31
+ logger.info({
32
+ channelId,
33
+ agentId: channelConfig.agentId,
34
+ source: "channel"
35
+ }, "Resolved agent from channel config");
36
+ return {
37
+ projectId: channelConfig.projectId,
38
+ agentId: channelConfig.agentId,
39
+ agentName: channelConfig.agentName || void 0,
40
+ source: "channel"
41
+ };
42
+ }
43
+ }
44
+ const workspaceConfig = await getWorkspaceDefaultAgentFromNango(teamId);
45
+ if (workspaceConfig?.agentId && workspaceConfig.projectId) {
46
+ logger.info({
47
+ teamId,
48
+ agentId: workspaceConfig.agentId,
49
+ source: "workspace"
50
+ }, "Resolved agent from workspace config");
51
+ return {
52
+ projectId: workspaceConfig.projectId,
53
+ agentId: workspaceConfig.agentId,
54
+ agentName: workspaceConfig.agentName,
55
+ source: "workspace"
56
+ };
57
+ }
58
+ logger.debug({
59
+ tenantId,
60
+ teamId,
61
+ channelId
62
+ }, "No agent configuration found");
63
+ return null;
64
+ }
65
+ /**
66
+ * Get all agent configuration sources for display purposes.
67
+ *
68
+ * @param params - Resolution parameters
69
+ * @returns Object containing channel, workspace configs, and the effective choice
70
+ */
71
+ async function getAgentConfigSources(params) {
72
+ const { tenantId, teamId, channelId } = params;
73
+ let channelConfig = null;
74
+ let workspaceConfig = null;
75
+ if (channelId) {
76
+ const config = await findWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
77
+ if (config?.enabled) channelConfig = {
78
+ projectId: config.projectId,
79
+ agentId: config.agentId,
80
+ agentName: config.agentName || void 0,
81
+ source: "channel"
82
+ };
83
+ }
84
+ const wsConfig = await getWorkspaceDefaultAgentFromNango(teamId);
85
+ if (wsConfig?.agentId && wsConfig.projectId) workspaceConfig = {
86
+ projectId: wsConfig.projectId,
87
+ agentId: wsConfig.agentId,
88
+ agentName: wsConfig.agentName,
89
+ source: "workspace"
90
+ };
91
+ return {
92
+ channelConfig,
93
+ workspaceConfig,
94
+ effective: channelConfig || workspaceConfig
95
+ };
96
+ }
97
+
98
+ //#endregion
99
+ export { getAgentConfigSources, resolveEffectiveAgent };
@@ -0,0 +1,73 @@
1
+ import * as slack_block_builder0 from "slack-block-builder";
2
+
3
+ //#region src/slack/services/blocks/index.d.ts
4
+ declare function createErrorMessage(message: string): Readonly<slack_block_builder0.SlackMessageDto>;
5
+ interface ContextBlockParams {
6
+ agentName: string;
7
+ isPrivate?: boolean;
8
+ }
9
+ declare function createContextBlock(params: ContextBlockParams): {
10
+ type: "context";
11
+ elements: {
12
+ type: "mrkdwn";
13
+ text: string;
14
+ }[];
15
+ };
16
+ interface FollowUpButtonParams {
17
+ conversationId: string;
18
+ agentId: string;
19
+ projectId: string;
20
+ tenantId: string;
21
+ teamId: string;
22
+ slackUserId: string;
23
+ channel: string;
24
+ }
25
+ declare function buildFollowUpButton(params: FollowUpButtonParams): {
26
+ type: "button";
27
+ text: {
28
+ type: "plain_text";
29
+ text: "Follow Up";
30
+ emoji: boolean;
31
+ };
32
+ action_id: string;
33
+ value: string;
34
+ }[];
35
+ /**
36
+ * Build Block Kit blocks for a private conversational response.
37
+ * Shows the user's message, a divider, the agent response, context, and a Follow Up button.
38
+ */
39
+ declare function buildConversationResponseBlocks(params: {
40
+ userMessage: string;
41
+ responseText: string;
42
+ agentName: string;
43
+ isError: boolean;
44
+ followUpParams: FollowUpButtonParams;
45
+ }): any[];
46
+ declare function createAgentListMessage(agents: Array<{
47
+ id: string;
48
+ name: string | null;
49
+ projectName: string | null;
50
+ }>, dashboardUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
51
+ declare function createUpdatedHelpMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
52
+ declare function createAlreadyLinkedMessage(email: string, linkedAt: string, dashboardUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
53
+ declare function createUnlinkSuccessMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
54
+ declare function createNotLinkedMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
55
+ interface AgentConfigSources {
56
+ channelConfig: {
57
+ agentName?: string;
58
+ agentId: string;
59
+ } | null;
60
+ workspaceConfig: {
61
+ agentName?: string;
62
+ agentId: string;
63
+ } | null;
64
+ effective: {
65
+ agentName?: string;
66
+ agentId: string;
67
+ source: string;
68
+ } | null;
69
+ }
70
+ declare function createStatusMessage(email: string, linkedAt: string, dashboardUrl: string, agentConfigs: AgentConfigSources): Readonly<slack_block_builder0.SlackMessageDto>;
71
+ declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
72
+ //#endregion
73
+ export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -0,0 +1,103 @@
1
+ import { SlackStrings } from "../../i18n/strings.js";
2
+ import { Blocks, Elements, Md, Message } from "slack-block-builder";
3
+
4
+ //#region src/slack/services/blocks/index.ts
5
+ function createErrorMessage(message) {
6
+ return Message().blocks(Blocks.Section().text(`❌ ${message}`)).buildToObject();
7
+ }
8
+ function createContextBlock(params) {
9
+ const { agentName, isPrivate = false } = params;
10
+ let text = SlackStrings.context.poweredBy(agentName);
11
+ if (isPrivate) text = `${SlackStrings.context.privateResponse} • ${text}`;
12
+ return {
13
+ type: "context",
14
+ elements: [{
15
+ type: "mrkdwn",
16
+ text
17
+ }]
18
+ };
19
+ }
20
+ function buildFollowUpButton(params) {
21
+ return [{
22
+ type: "button",
23
+ text: {
24
+ type: "plain_text",
25
+ text: SlackStrings.buttons.followUp,
26
+ emoji: true
27
+ },
28
+ action_id: "open_follow_up_modal",
29
+ value: JSON.stringify(params)
30
+ }];
31
+ }
32
+ /**
33
+ * Build Block Kit blocks for a private conversational response.
34
+ * Shows the user's message, a divider, the agent response, context, and a Follow Up button.
35
+ */
36
+ function buildConversationResponseBlocks(params) {
37
+ const { userMessage, responseText, agentName, isError, followUpParams } = params;
38
+ const blocks = [
39
+ {
40
+ type: "context",
41
+ elements: [{
42
+ type: "mrkdwn",
43
+ text: `💬 *You:* ${userMessage.length > 200 ? `${userMessage.slice(0, 200)}...` : userMessage}`
44
+ }]
45
+ },
46
+ { type: "divider" },
47
+ {
48
+ type: "section",
49
+ text: {
50
+ type: "mrkdwn",
51
+ text: responseText
52
+ }
53
+ }
54
+ ];
55
+ if (!isError) {
56
+ const contextBlock = createContextBlock({
57
+ agentName,
58
+ isPrivate: true
59
+ });
60
+ blocks.push(contextBlock);
61
+ blocks.push({
62
+ type: "actions",
63
+ elements: buildFollowUpButton(followUpParams)
64
+ });
65
+ }
66
+ return blocks;
67
+ }
68
+ function createAgentListMessage(agents, dashboardUrl) {
69
+ const agentList = agents.slice(0, 15).map((a) => `• ${Md.bold(a.name || a.id)} ${a.projectName ? `(${Md.italic(a.projectName)})` : ""}`).join("\n");
70
+ const moreText = agents.length > 15 ? `\n\n${SlackStrings.agentList.andMore(agents.length - 15)}` : "";
71
+ return Message().blocks(Blocks.Section().text(`${Md.bold(SlackStrings.agentList.title)}\n\n` + agentList + moreText + `
72
+
73
+ ${Md.bold(SlackStrings.agentList.usage)}\n• ${SlackStrings.agentList.runUsage}`), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("view_agents"))).buildToObject();
74
+ }
75
+ function createUpdatedHelpMessage() {
76
+ return Message().blocks(Blocks.Section().text(`${Md.bold(SlackStrings.help.title)}`), Blocks.Section().text(SlackStrings.help.publicSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.privateSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.otherCommands)).buildToObject();
77
+ }
78
+ function createAlreadyLinkedMessage(email, linkedAt, dashboardUrl) {
79
+ return Message().blocks(Blocks.Section().text(Md.bold("✅ Already Linked!") + "\n\nYour Slack account is already connected to Inkeep.\n\n" + Md.bold("Inkeep Account:") + ` ${email}\n` + Md.bold("Linked:") + ` ${new Date(linkedAt).toLocaleDateString()}\n\nTo switch accounts, first run \`/inkeep unlink\``), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
80
+ }
81
+ function createUnlinkSuccessMessage() {
82
+ return Message().blocks(Blocks.Section().text(Md.bold("✅ Account Unlinked") + "\n\nYour Slack account has been disconnected from Inkeep.\n\nTo use Inkeep agents again, run `/inkeep link` to connect a new account.")).buildToObject();
83
+ }
84
+ function createNotLinkedMessage() {
85
+ return Message().blocks(Blocks.Section().text(Md.bold("❌ Not Linked") + "\n\nYour Slack account is not connected to Inkeep.\n\nRun `/inkeep link` to connect your account.")).buildToObject();
86
+ }
87
+ function createStatusMessage(email, linkedAt, dashboardUrl, agentConfigs) {
88
+ const { effective } = agentConfigs;
89
+ let agentLine;
90
+ if (effective) agentLine = `${Md.bold("Agent:")} ${effective.agentName || effective.agentId}`;
91
+ else agentLine = `${Md.bold("Agent:")} None configured\n${Md.italic("Ask your admin to set up an agent in the dashboard.")}`;
92
+ return Message().blocks(Blocks.Section().text(Md.bold("✅ Connected to Inkeep") + `\n\n${Md.bold("Account:")} ${email}\n${Md.bold("Linked:")} ${new Date(linkedAt).toLocaleDateString()}\n` + agentLine), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
93
+ }
94
+ function createJwtLinkMessage(linkUrl, expiresInMinutes) {
95
+ return Message().blocks(Blocks.Section().text(`${Md.bold("🔗 Link your Inkeep account")}\n\nConnect your Slack and Inkeep accounts to unlock AI-powered assistance:`), Blocks.Section().text(`${Md.bold("What you can do after linking:")}\n• Ask questions with \`/inkeep [question]\` or \`@Inkeep\`
96
+ • Get personalized responses from AI agents
97
+ • Set your own default agent preferences`), Blocks.Section().text(`${Md.bold("How to link:")}\n1. Click the button below
98
+ 2. Sign in to Inkeep (or create an account)
99
+ 3. Done! Come back here and start asking questions`), Blocks.Actions().elements(Elements.Button().text("🔗 Link Account").url(linkUrl).actionId("link_account").primary()), Blocks.Context().elements(`${Md.emoji("clock")} This link expires in ${expiresInMinutes} minutes`)).buildToObject();
100
+ }
101
+
102
+ //#endregion
103
+ export { buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -0,0 +1,105 @@
1
+ import * as _slack_web_api0 from "@slack/web-api";
2
+ import { WebClient } from "@slack/web-api";
3
+
4
+ //#region src/slack/services/client.d.ts
5
+
6
+ /**
7
+ * Create a Slack WebClient with the provided bot token.
8
+ *
9
+ * @param token - Bot OAuth token from Nango connection
10
+ * @returns Configured Slack WebClient instance
11
+ */
12
+ declare function getSlackClient(token: string): WebClient;
13
+ /**
14
+ * Fetch user profile information from Slack.
15
+ *
16
+ * @param client - Authenticated Slack WebClient
17
+ * @param userId - Slack user ID (e.g., U0ABC123)
18
+ * @returns User profile object, or null if not found
19
+ */
20
+ declare function getSlackUserInfo(client: WebClient, userId: string): Promise<{
21
+ id: string | undefined;
22
+ name: string | undefined;
23
+ realName: string | undefined;
24
+ displayName: string | undefined;
25
+ email: string | undefined;
26
+ isAdmin: boolean | undefined;
27
+ isOwner: boolean | undefined;
28
+ avatar: string | undefined;
29
+ } | null>;
30
+ /**
31
+ * Fetch workspace (team) information from Slack.
32
+ *
33
+ * @param client - Authenticated Slack WebClient
34
+ * @returns Team info object, or null if not available
35
+ */
36
+ declare function getSlackTeamInfo(client: WebClient): Promise<{
37
+ id: string | undefined;
38
+ name: string | undefined;
39
+ domain: string | undefined;
40
+ icon: string | undefined;
41
+ url: string | undefined;
42
+ } | null>;
43
+ /**
44
+ * List channels in the workspace (public, private, and shared).
45
+ *
46
+ * Note: The bot must be a member of private channels to see them.
47
+ * Users can invite the bot with `/invite @BotName` in the private channel.
48
+ *
49
+ * @param client - Authenticated Slack WebClient
50
+ * @param limit - Maximum number of channels to return (default 20)
51
+ * @returns Array of channel objects with id, name, member count, and privacy status
52
+ */
53
+ declare function getSlackChannels(client: WebClient, limit?: number): Promise<{
54
+ id: string | undefined;
55
+ name: string | undefined;
56
+ memberCount: number | undefined;
57
+ isBotMember: boolean | undefined;
58
+ isPrivate: boolean;
59
+ isShared: boolean;
60
+ }[]>;
61
+ /**
62
+ * Post a message to a Slack channel.
63
+ *
64
+ * @param client - Authenticated Slack WebClient
65
+ * @param channel - Channel ID to post to
66
+ * @param text - Fallback text for notifications
67
+ * @param blocks - Optional Block Kit blocks for rich formatting
68
+ * @returns Slack API response with message timestamp
69
+ */
70
+ declare function postMessage(client: WebClient, channel: string, text: string, blocks?: unknown[]): Promise<_slack_web_api0.ChatPostMessageResponse>;
71
+ /**
72
+ * Post a message as a reply in a thread.
73
+ *
74
+ * @param client - Authenticated Slack WebClient
75
+ * @param channel - Channel ID containing the thread
76
+ * @param threadTs - Thread parent message timestamp
77
+ * @param text - Fallback text for notifications
78
+ * @param blocks - Optional Block Kit blocks for rich formatting
79
+ * @returns Slack API response with message timestamp
80
+ */
81
+ declare function postMessageInThread(client: WebClient, channel: string, threadTs: string, text: string, blocks?: unknown[]): Promise<_slack_web_api0.ChatPostMessageResponse>;
82
+ /**
83
+ * Check if a user is a member of a Slack channel.
84
+ *
85
+ * Uses conversations.members to verify membership. Handles pagination
86
+ * for channels with many members.
87
+ *
88
+ * @param client - Authenticated Slack WebClient
89
+ * @param channelId - Channel ID to check membership for
90
+ * @param userId - Slack user ID to check
91
+ * @returns true if user is a member, false otherwise
92
+ */
93
+ declare function checkUserIsChannelMember(client: WebClient, channelId: string, userId: string): Promise<boolean>;
94
+ /**
95
+ * Revoke a Slack bot token.
96
+ *
97
+ * This should be called when uninstalling a workspace to ensure
98
+ * the token can no longer be used to make API calls.
99
+ *
100
+ * @param token - Bot OAuth token to revoke
101
+ * @returns true if revocation succeeded or token was already invalid
102
+ */
103
+ declare function revokeSlackToken(token: string): Promise<boolean>;
104
+ //#endregion
105
+ export { checkUserIsChannelMember, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken };
@@ -0,0 +1,220 @@
1
+ import { getLogger } from "../../logger.js";
2
+ import { WebClient } from "@slack/web-api";
3
+
4
+ //#region src/slack/services/client.ts
5
+ /**
6
+ * Slack Web API Client Utilities
7
+ *
8
+ * Wrapper functions for common Slack Web API operations.
9
+ * Tokens are fetched from Nango at runtime and passed to these functions.
10
+ */
11
+ const logger = getLogger("slack-client");
12
+ /**
13
+ * Create a Slack WebClient with the provided bot token.
14
+ *
15
+ * @param token - Bot OAuth token from Nango connection
16
+ * @returns Configured Slack WebClient instance
17
+ */
18
+ function getSlackClient(token) {
19
+ return new WebClient(token);
20
+ }
21
+ /**
22
+ * Fetch user profile information from Slack.
23
+ *
24
+ * @param client - Authenticated Slack WebClient
25
+ * @param userId - Slack user ID (e.g., U0ABC123)
26
+ * @returns User profile object, or null if not found
27
+ */
28
+ async function getSlackUserInfo(client, userId) {
29
+ try {
30
+ const result = await client.users.info({ user: userId });
31
+ if (result.ok && result.user) return {
32
+ id: result.user.id,
33
+ name: result.user.name,
34
+ realName: result.user.real_name,
35
+ displayName: result.user.profile?.display_name,
36
+ email: result.user.profile?.email,
37
+ isAdmin: result.user.is_admin,
38
+ isOwner: result.user.is_owner,
39
+ avatar: result.user.profile?.image_72
40
+ };
41
+ return null;
42
+ } catch (error) {
43
+ logger.error({
44
+ error,
45
+ userId
46
+ }, "Failed to fetch Slack user info");
47
+ return null;
48
+ }
49
+ }
50
+ /**
51
+ * Fetch workspace (team) information from Slack.
52
+ *
53
+ * @param client - Authenticated Slack WebClient
54
+ * @returns Team info object, or null if not available
55
+ */
56
+ async function getSlackTeamInfo(client) {
57
+ try {
58
+ const result = await client.team.info();
59
+ if (result.ok && result.team) return {
60
+ id: result.team.id,
61
+ name: result.team.name,
62
+ domain: result.team.domain,
63
+ icon: result.team.icon?.image_68,
64
+ url: result.team.url
65
+ };
66
+ return null;
67
+ } catch (error) {
68
+ logger.error({ error }, "Failed to fetch Slack team info");
69
+ return null;
70
+ }
71
+ }
72
+ /**
73
+ * List channels in the workspace (public, private, and shared).
74
+ *
75
+ * Note: The bot must be a member of private channels to see them.
76
+ * Users can invite the bot with `/invite @BotName` in the private channel.
77
+ *
78
+ * @param client - Authenticated Slack WebClient
79
+ * @param limit - Maximum number of channels to return (default 20)
80
+ * @returns Array of channel objects with id, name, member count, and privacy status
81
+ */
82
+ async function getSlackChannels(client, limit = 20) {
83
+ try {
84
+ const result = await client.conversations.list({
85
+ types: "public_channel,private_channel",
86
+ exclude_archived: true,
87
+ limit
88
+ });
89
+ if (result.ok && result.channels) return result.channels.map((ch) => ({
90
+ id: ch.id,
91
+ name: ch.name,
92
+ memberCount: ch.num_members,
93
+ isBotMember: ch.is_member,
94
+ isPrivate: ch.is_private ?? false,
95
+ isShared: ch.is_shared ?? ch.is_ext_shared ?? false
96
+ }));
97
+ return [];
98
+ } catch (error) {
99
+ logger.error({ error }, "Failed to fetch Slack channels");
100
+ return [];
101
+ }
102
+ }
103
+ /**
104
+ * Post a message to a Slack channel.
105
+ *
106
+ * @param client - Authenticated Slack WebClient
107
+ * @param channel - Channel ID to post to
108
+ * @param text - Fallback text for notifications
109
+ * @param blocks - Optional Block Kit blocks for rich formatting
110
+ * @returns Slack API response with message timestamp
111
+ */
112
+ async function postMessage(client, channel, text, blocks) {
113
+ try {
114
+ const args = {
115
+ channel,
116
+ text
117
+ };
118
+ if (blocks) args.blocks = blocks;
119
+ return await client.chat.postMessage(args);
120
+ } catch (error) {
121
+ logger.error({
122
+ error,
123
+ channel
124
+ }, "Failed to post Slack message");
125
+ throw error;
126
+ }
127
+ }
128
+ /**
129
+ * Post a message as a reply in a thread.
130
+ *
131
+ * @param client - Authenticated Slack WebClient
132
+ * @param channel - Channel ID containing the thread
133
+ * @param threadTs - Thread parent message timestamp
134
+ * @param text - Fallback text for notifications
135
+ * @param blocks - Optional Block Kit blocks for rich formatting
136
+ * @returns Slack API response with message timestamp
137
+ */
138
+ async function postMessageInThread(client, channel, threadTs, text, blocks) {
139
+ try {
140
+ const args = {
141
+ channel,
142
+ text,
143
+ thread_ts: threadTs
144
+ };
145
+ if (blocks) args.blocks = blocks;
146
+ return await client.chat.postMessage(args);
147
+ } catch (error) {
148
+ logger.error({
149
+ error,
150
+ channel,
151
+ threadTs
152
+ }, "Failed to post Slack message in thread");
153
+ throw error;
154
+ }
155
+ }
156
+ /**
157
+ * Check if a user is a member of a Slack channel.
158
+ *
159
+ * Uses conversations.members to verify membership. Handles pagination
160
+ * for channels with many members.
161
+ *
162
+ * @param client - Authenticated Slack WebClient
163
+ * @param channelId - Channel ID to check membership for
164
+ * @param userId - Slack user ID to check
165
+ * @returns true if user is a member, false otherwise
166
+ */
167
+ async function checkUserIsChannelMember(client, channelId, userId) {
168
+ try {
169
+ let cursor;
170
+ do {
171
+ const result = await client.conversations.members({
172
+ channel: channelId,
173
+ limit: 200,
174
+ cursor
175
+ });
176
+ if (!result.ok || !result.members) return false;
177
+ if (result.members.includes(userId)) return true;
178
+ cursor = result.response_metadata?.next_cursor;
179
+ } while (cursor);
180
+ return false;
181
+ } catch (error) {
182
+ logger.error({
183
+ error,
184
+ channelId,
185
+ userId
186
+ }, "Failed to check channel membership");
187
+ return false;
188
+ }
189
+ }
190
+ /**
191
+ * Revoke a Slack bot token.
192
+ *
193
+ * This should be called when uninstalling a workspace to ensure
194
+ * the token can no longer be used to make API calls.
195
+ *
196
+ * @param token - Bot OAuth token to revoke
197
+ * @returns true if revocation succeeded or token was already invalid
198
+ */
199
+ async function revokeSlackToken(token) {
200
+ try {
201
+ const result = await new WebClient(token).auth.revoke();
202
+ if (result.ok) {
203
+ logger.info({}, "Successfully revoked Slack token");
204
+ return true;
205
+ }
206
+ logger.warn({ error: result.error }, "Token revocation returned non-ok status");
207
+ return false;
208
+ } catch (error) {
209
+ const errorMessage = error instanceof Error ? error.message : String(error);
210
+ if (errorMessage.includes("token_revoked") || errorMessage.includes("invalid_auth")) {
211
+ logger.info({}, "Token already revoked or invalid");
212
+ return true;
213
+ }
214
+ logger.error({ error }, "Failed to revoke Slack token");
215
+ return false;
216
+ }
217
+ }
218
+
219
+ //#endregion
220
+ export { checkUserIsChannelMember, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken };
@@ -0,0 +1,19 @@
1
+ import { SlackWorkspaceConnection } from "../nango.js";
2
+ import { SlackCommandPayload, SlackCommandResponse } from "../types.js";
3
+
4
+ //#region src/slack/services/commands/index.d.ts
5
+ declare function handleLinkCommand(payload: SlackCommandPayload, dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
6
+ declare function handleUnlinkCommand(payload: SlackCommandPayload, tenantId: string): Promise<SlackCommandResponse>;
7
+ declare function handleStatusCommand(payload: SlackCommandPayload, dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
8
+ declare function handleHelpCommand(): Promise<SlackCommandResponse>;
9
+ /**
10
+ * Handle `/inkeep` with no arguments - opens the agent picker modal
11
+ * Similar to @mention behavior in channels
12
+ */
13
+ declare function handleAgentPickerCommand(payload: SlackCommandPayload, tenantId: string, workspaceConnection?: SlackWorkspaceConnection | null): Promise<SlackCommandResponse>;
14
+ declare function handleQuestionCommand(payload: SlackCommandPayload, question: string, _dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
15
+ declare function handleRunCommand(payload: SlackCommandPayload, agentIdentifier: string, question: string, _dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
16
+ declare function handleAgentListCommand(payload: SlackCommandPayload, dashboardUrl: string, _tenantId: string): Promise<SlackCommandResponse>;
17
+ declare function handleCommand(payload: SlackCommandPayload): Promise<SlackCommandResponse>;
18
+ //#endregion
19
+ export { handleAgentListCommand, handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand };