@inkeep/agents-work-apps 0.53.0 → 0.53.1

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.
@@ -1,36 +1,23 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
- import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
4
+ import { checkIfBotThread, classifyError, findCachedUserMapping, formatAttachments, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
5
5
  import { resolveEffectiveAgent } from "../agent-resolution.js";
6
6
  import { SlackStrings } from "../../i18n/strings.js";
7
7
  import { getSlackChannelInfo, getSlackClient, getSlackUserInfo, postMessageInThread } from "../client.js";
8
+ import { buildLinkPromptMessage, resolveUnlinkedUserAction } from "../link-prompt.js";
8
9
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
9
10
  import { streamAgentResponse } from "./streaming.js";
10
11
  import { signSlackUserToken } from "@inkeep/agents-core";
11
12
 
12
13
  //#region src/slack/services/events/app-mention.ts
13
- /**
14
- * Handler for Slack @mention events
15
- *
16
- * Flow:
17
- * 1. Resolve workspace connection (single lookup, cached)
18
- * 2. Parallel: resolve agent config + check user link
19
- * 3. If no agent configured → prompt to set up in dashboard
20
- * 4. If not linked → prompt to link account
21
- * 5. Handle based on context:
22
- * - Channel + no query → Show usage hint
23
- * - Channel + query → Execute agent with streaming response
24
- * - Thread + no query → Auto-execute agent with thread context as query
25
- * - Thread + query → Execute agent with thread context included
26
- */
27
14
  const logger = getLogger("slack-app-mention");
28
15
  /**
29
16
  * Main handler for @mention events in Slack
30
17
  */
31
18
  async function handleAppMention(params) {
32
19
  return tracer.startActiveSpan(SLACK_SPAN_NAMES.APP_MENTION, async (span) => {
33
- const { slackUserId, channel, text, threadTs, messageTs, teamId, dispatchedAt } = params;
20
+ const { slackUserId, channel, text, attachments, threadTs, messageTs, teamId, dispatchedAt } = params;
34
21
  const handlerStartedAt = Date.now();
35
22
  const manageUiUrl = env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000";
36
23
  const dispatchDelayMs = dispatchedAt ? handlerStartedAt - dispatchedAt : void 0;
@@ -123,11 +110,36 @@ async function handleAppMention(params) {
123
110
  teamId,
124
111
  channel
125
112
  }, "User not linked — prompting account link");
113
+ const intent = {
114
+ entryPoint: "mention",
115
+ question: text.slice(0, 2e3),
116
+ channelId: channel,
117
+ threadTs: isInThread ? threadTs : void 0,
118
+ messageTs,
119
+ agentId: agentConfig.agentId,
120
+ projectId: agentConfig.projectId
121
+ };
122
+ const linkResult = await resolveUnlinkedUserAction({
123
+ tenantId,
124
+ teamId,
125
+ slackUserId,
126
+ botToken,
127
+ intent
128
+ });
129
+ const message = buildLinkPromptMessage(linkResult);
130
+ logger.info({
131
+ event: "smart_link_intent_captured",
132
+ entryPoint: "mention",
133
+ linkType: linkResult.type,
134
+ questionLength: intent.question.length,
135
+ channelId: channel
136
+ }, "Smart link intent captured");
126
137
  await slackClient.chat.postEphemeral({
127
138
  channel,
128
139
  user: slackUserId,
129
140
  thread_ts: isInThread ? threadTs : void 0,
130
- text: "*Link your account to use @Inkeep*\n\nRun `/inkeep link` to connect your Slack and Inkeep accounts."
141
+ text: "To get started, let's connect your Inkeep account with Slack.",
142
+ blocks: message.blocks
131
143
  });
132
144
  span.end();
133
145
  return;
@@ -241,6 +253,7 @@ Respond naturally as if you're joining the conversation to help.`;
241
253
  return;
242
254
  }
243
255
  let queryText = text;
256
+ const attachmentContext = formatAttachments(attachments);
244
257
  if (isInThread && threadTs) {
245
258
  const { result: [contextMessages, channelInfo] } = await timedOp(Promise.all([getThreadContext(slackClient, channel, threadTs), getSlackChannelInfo(slackClient, channel)]), {
246
259
  label: "thread context fetch",
@@ -250,7 +263,12 @@ Respond naturally as if you're joining the conversation to help.`;
250
263
  threadTs
251
264
  }
252
265
  });
253
- if (contextMessages) queryText = `The following is thread context from ${formatChannelContext(channelInfo)}:\n\n<slack_thread_context>\n${contextMessages}\n</slack_thread_context>\n\nMessage from ${slackUserId}: ${text}`;
266
+ if (contextMessages) {
267
+ const channelContext = formatChannelContext(channelInfo);
268
+ let messageContent = text;
269
+ if (attachmentContext) messageContent = `${text}\n\n<attached_content>\n${attachmentContext}\n</attached_content>`;
270
+ queryText = `The following is thread context from ${channelContext}:\n\n<slack_thread_context>\n${contextMessages}\n</slack_thread_context>\n\nMessage from ${slackUserId}: ${messageContent}`;
271
+ }
254
272
  } else {
255
273
  const { result: [channelInfo, userInfo] } = await timedOp(Promise.all([getSlackChannelInfo(slackClient, channel), getSlackUserInfo(slackClient, slackUserId)]), {
256
274
  label: "channel/user info fetch",
@@ -259,7 +277,10 @@ Respond naturally as if you're joining the conversation to help.`;
259
277
  channel
260
278
  }
261
279
  });
262
- queryText = `The following is a message from ${formatChannelContext(channelInfo)} from ${userInfo?.displayName || "User"}: """${text}"""`;
280
+ const channelContext = formatChannelContext(channelInfo);
281
+ const userName = userInfo?.displayName || "User";
282
+ if (attachmentContext) queryText = `The following is a message from ${channelContext} from ${userName}: """${text}"""\n\nThe message also includes the following shared/forwarded content:\n\n<attached_content>\n${attachmentContext}\n</attached_content>`;
283
+ else queryText = `The following is a message from ${channelContext} from ${userName}: """${text}"""`;
263
284
  }
264
285
  const slackUserToken = await signSlackUserToken({
265
286
  inkeepUserId: existingLink.inkeepUserId,
@@ -1,6 +1,6 @@
1
+ import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
1
2
  import { InlineSelectorMetadata, handleAppMention } from "./app-mention.js";
2
3
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./block-actions.js";
3
4
  import { handleFollowUpSubmission, handleModalSubmission } from "./modal-submission.js";
4
- import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
5
5
  import { StreamResult, streamAgentResponse } from "./streaming.js";
6
6
  export { type InlineSelectorMetadata, SlackErrorType, type StreamResult, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, handleAppMention, handleFollowUpSubmission, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
@@ -11,9 +11,9 @@ declare function findCachedUserMapping(tenantId: string, slackUserId: string, te
11
11
  id: string;
12
12
  createdAt: string;
13
13
  updatedAt: string;
14
+ slackUserId: string;
14
15
  tenantId: string;
15
16
  clientId: string;
16
- slackUserId: string;
17
17
  slackTeamId: string;
18
18
  slackEnterpriseId: string | null;
19
19
  inkeepUserId: string;
@@ -112,6 +112,24 @@ declare function checkIfBotThread(slackClient: {
112
112
  }>;
113
113
  };
114
114
  }, channel: string, threadTs: string): Promise<boolean>;
115
+ interface SlackAttachment {
116
+ text?: string;
117
+ fallback?: string;
118
+ pretext?: string;
119
+ author_name?: string;
120
+ author_id?: string;
121
+ channel_name?: string;
122
+ channel_id?: string;
123
+ title?: string;
124
+ is_msg_unfurl?: boolean;
125
+ is_share?: boolean;
126
+ from_url?: string;
127
+ fields?: Array<{
128
+ title?: string;
129
+ value?: string;
130
+ }>;
131
+ }
132
+ declare function formatAttachments(attachments: SlackAttachment[] | undefined): string;
115
133
  interface ThreadContextOptions {
116
134
  includeLastMessage?: boolean;
117
135
  resolveUserNames?: boolean;
@@ -128,6 +146,7 @@ declare function getThreadContext(slackClient: {
128
146
  user?: string;
129
147
  text?: string;
130
148
  ts?: string;
149
+ attachments?: SlackAttachment[];
131
150
  }>;
132
151
  }>;
133
152
  };
@@ -160,4 +179,4 @@ declare function formatChannelContext(channelInfo: {
160
179
  name?: string;
161
180
  } | null): string;
162
181
  //#endregion
163
- export { ProjectOption, SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
182
+ export { ProjectOption, SlackAttachment, SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatAttachments, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
@@ -34,17 +34,19 @@ async function findCachedUserMapping(tenantId, slackUserId, teamId, clientId = "
34
34
  const cached = userMappingCache.get(cacheKey);
35
35
  if (cached && cached.expiresAt > Date.now()) return cached.mapping;
36
36
  const mapping = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, slackUserId, teamId, clientId);
37
- if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
38
- evictExpiredEntries();
37
+ if (mapping) {
39
38
  if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
40
- const oldestKey = userMappingCache.keys().next().value;
41
- if (oldestKey) userMappingCache.delete(oldestKey);
39
+ evictExpiredEntries();
40
+ if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
41
+ const oldestKey = userMappingCache.keys().next().value;
42
+ if (oldestKey) userMappingCache.delete(oldestKey);
43
+ }
42
44
  }
45
+ userMappingCache.set(cacheKey, {
46
+ mapping,
47
+ expiresAt: Date.now() + USER_MAPPING_CACHE_TTL_MS
48
+ });
43
49
  }
44
- userMappingCache.set(cacheKey, {
45
- mapping,
46
- expiresAt: Date.now() + USER_MAPPING_CACHE_TTL_MS
47
- });
48
50
  return mapping;
49
51
  }
50
52
  /**
@@ -328,6 +330,27 @@ async function checkIfBotThread(slackClient, channel, threadTs) {
328
330
  return false;
329
331
  }
330
332
  }
333
+ function formatAttachments(attachments) {
334
+ if (!attachments || attachments.length === 0) return "";
335
+ const parts = [];
336
+ for (const att of attachments) {
337
+ const content = att.text || att.fallback;
338
+ if (!content) continue;
339
+ const isSharedMessage = att.is_msg_unfurl || att.is_share;
340
+ const meta = [];
341
+ if (att.author_name) meta.push(`from ${att.author_name}`);
342
+ if (att.channel_name) meta.push(`in #${att.channel_name}`);
343
+ else if (att.channel_id) meta.push(`in channel ${att.channel_id}`);
344
+ const label = isSharedMessage ? "Shared message" : "Attachment";
345
+ const metaSuffix = meta.length > 0 ? ` (${meta.join(", ")})` : "";
346
+ const sourceLine = att.from_url ? `\n[Source: ${att.from_url}]` : "";
347
+ parts.push(`[${label}${metaSuffix}]:\n\`\`\`\n${content}\n\`\`\`${sourceLine}`);
348
+ if (att.fields && att.fields.length > 0) {
349
+ for (const field of att.fields) if (field.title && field.value) parts.push(`${field.title}: ${field.value}`);
350
+ }
351
+ }
352
+ return parts.join("\n\n");
353
+ }
331
354
  async function getThreadContext(slackClient, channel, threadTs, options = {}) {
332
355
  const { includeLastMessage = false, resolveUserNames = true } = options;
333
356
  try {
@@ -377,7 +400,9 @@ async function getThreadContext(slackClient, channel, threadTs, options = {}) {
377
400
  else role = "Unknown";
378
401
  const prefix = isParent ? "[Thread Start] " : "";
379
402
  const messageText = msg.text || "";
380
- return `${prefix}${role}: """${messageText}"""`;
403
+ const attachmentText = formatAttachments(msg.attachments);
404
+ const fullText = attachmentText ? `${messageText}\n${attachmentText}` : messageText;
405
+ return `${prefix}${role}: """${fullText}"""`;
381
406
  }).join("\n\n")}`;
382
407
  } catch (threadError) {
383
408
  logger.warn({
@@ -412,4 +437,4 @@ function formatChannelContext(channelInfo) {
412
437
  }
413
438
 
414
439
  //#endregion
415
- export { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
440
+ export { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatAttachments, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
@@ -1,16 +1,16 @@
1
1
  import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
2
- import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
2
+ import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
3
  import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
4
4
  import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
5
5
  import { SlackCommandPayload, SlackCommandResponse } from "./types.js";
6
6
  import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
7
+ import { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, FollowUpModalMetadata, ModalMetadata, buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
8
+ import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
7
9
  import { InlineSelectorMetadata, handleAppMention } from "./events/app-mention.js";
8
10
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./events/block-actions.js";
9
11
  import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
10
- import { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, FollowUpModalMetadata, ModalMetadata, buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
11
- import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
12
12
  import { StreamResult, streamAgentResponse } from "./events/streaming.js";
13
13
  import "./events/index.js";
14
14
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
15
15
  import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
16
- export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -1,7 +1,7 @@
1
1
  import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
2
2
  import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
3
3
  import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
4
- import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
4
+ import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
5
5
  import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
6
6
  import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
7
7
  import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
@@ -13,4 +13,4 @@ import "./events/index.js";
13
13
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
14
14
  import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
15
15
 
16
- export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -0,0 +1,27 @@
1
+ import { SlackLinkIntent } from "@inkeep/agents-core";
2
+ import * as slack_block_builder0 from "slack-block-builder";
3
+
4
+ //#region src/slack/services/link-prompt.d.ts
5
+ type LinkPromptResult = {
6
+ type: 'auto_invite';
7
+ url: string;
8
+ email: string;
9
+ expiresInMinutes: number;
10
+ } | {
11
+ type: 'jwt_link';
12
+ url: string;
13
+ expiresInMinutes: number;
14
+ };
15
+ interface ResolveLinkActionParams {
16
+ tenantId: string;
17
+ teamId: string;
18
+ slackUserId: string;
19
+ botToken: string;
20
+ slackEnterpriseId?: string;
21
+ slackUsername?: string;
22
+ intent?: SlackLinkIntent;
23
+ }
24
+ declare function resolveUnlinkedUserAction(params: ResolveLinkActionParams): Promise<LinkPromptResult>;
25
+ declare function buildLinkPromptMessage(result: LinkPromptResult): Readonly<slack_block_builder0.SlackMessageDto>;
26
+ //#endregion
27
+ export { LinkPromptResult, ResolveLinkActionParams, buildLinkPromptMessage, resolveUnlinkedUserAction };
@@ -0,0 +1,142 @@
1
+ import { env } from "../../env.js";
2
+ import { getLogger } from "../../logger.js";
3
+ import runDbClient_default from "../../db/runDbClient.js";
4
+ import { createCreateInkeepAccountMessage, createSmartLinkMessage } from "./blocks/index.js";
5
+ import { getSlackClient } from "./client.js";
6
+ import { createInvitationInDb, findWorkAppSlackWorkspaceByTeamId, getOrganizationMemberByEmail, getPendingInvitationsByEmail, signSlackLinkToken } from "@inkeep/agents-core";
7
+
8
+ //#region src/slack/services/link-prompt.ts
9
+ const logger = getLogger("slack-link-prompt");
10
+ const LINK_CODE_TTL_MINUTES = 10;
11
+ async function resolveUnlinkedUserAction(params) {
12
+ const { tenantId, teamId, slackUserId, botToken, slackEnterpriseId, slackUsername, intent } = params;
13
+ const manageUiUrl = env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000";
14
+ const autoInvite = await tryAutoInvite({
15
+ tenantId,
16
+ teamId,
17
+ slackUserId,
18
+ botToken
19
+ });
20
+ if (autoInvite) {
21
+ const linkToken$1 = await signSlackLinkToken({
22
+ tenantId,
23
+ slackTeamId: teamId,
24
+ slackUserId,
25
+ slackEnterpriseId,
26
+ slackUsername,
27
+ intent
28
+ });
29
+ const authMethod = autoInvite.authMethod;
30
+ const linkReturnUrl = `/link?token=${encodeURIComponent(linkToken$1)}`;
31
+ const acceptUrl = authMethod === "email-password" ? `${manageUiUrl}/accept-invitation/${autoInvite.invitationId}?email=${encodeURIComponent(autoInvite.email)}&returnUrl=${encodeURIComponent(linkReturnUrl)}` : `${manageUiUrl}/login?invitation=${encodeURIComponent(autoInvite.invitationId)}&returnUrl=${encodeURIComponent(linkReturnUrl)}&email=${encodeURIComponent(autoInvite.email)}&authMethod=${encodeURIComponent(authMethod)}`;
32
+ logger.info({
33
+ invitationId: autoInvite.invitationId,
34
+ email: autoInvite.email,
35
+ hasIntent: !!intent
36
+ }, "Directing unlinked user to accept-invitation page");
37
+ return {
38
+ type: "auto_invite",
39
+ url: acceptUrl,
40
+ email: autoInvite.email,
41
+ expiresInMinutes: LINK_CODE_TTL_MINUTES
42
+ };
43
+ }
44
+ const linkToken = await signSlackLinkToken({
45
+ tenantId,
46
+ slackTeamId: teamId,
47
+ slackUserId,
48
+ slackEnterpriseId,
49
+ slackUsername,
50
+ intent
51
+ });
52
+ const linkUrl = `${manageUiUrl}/link?token=${encodeURIComponent(linkToken)}`;
53
+ logger.info({
54
+ slackUserId,
55
+ tenantId,
56
+ hasIntent: !!intent
57
+ }, "Generated JWT link token for unlinked user");
58
+ return {
59
+ type: "jwt_link",
60
+ url: linkUrl,
61
+ expiresInMinutes: LINK_CODE_TTL_MINUTES
62
+ };
63
+ }
64
+ function buildLinkPromptMessage(result) {
65
+ if (result.type === "auto_invite") return createCreateInkeepAccountMessage(result.url, result.expiresInMinutes);
66
+ return createSmartLinkMessage(result.url);
67
+ }
68
+ async function tryAutoInvite(params) {
69
+ const { tenantId, teamId, slackUserId, botToken } = params;
70
+ if (!botToken) return null;
71
+ try {
72
+ if (!(await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(tenantId, teamId))?.shouldAllowJoinFromWorkspace) {
73
+ logger.warn({
74
+ userId: slackUserId,
75
+ tenantId,
76
+ teamId
77
+ }, "Workspace should not allow join from workspace");
78
+ return null;
79
+ }
80
+ const slackClient = getSlackClient(botToken);
81
+ let userEmail;
82
+ try {
83
+ userEmail = (await slackClient.users.info({ user: slackUserId })).user?.profile?.email;
84
+ } catch (error) {
85
+ logger.warn({
86
+ error,
87
+ userId: slackUserId
88
+ }, "Failed to get user info from Slack");
89
+ return null;
90
+ }
91
+ if (!userEmail) {
92
+ logger.warn({ userId: slackUserId }, "No email found in Slack user profile");
93
+ return null;
94
+ }
95
+ if (await getOrganizationMemberByEmail(runDbClient_default)(tenantId, userEmail)) {
96
+ logger.debug({
97
+ userId: slackUserId,
98
+ email: userEmail
99
+ }, "User already has Inkeep account, skipping auto-invite");
100
+ return null;
101
+ }
102
+ const existingInvitation = (await getPendingInvitationsByEmail(runDbClient_default)(userEmail)).find((inv) => inv.organizationId === tenantId);
103
+ if (existingInvitation) {
104
+ logger.info({
105
+ userId: slackUserId,
106
+ tenantId,
107
+ invitationId: existingInvitation.id,
108
+ email: userEmail
109
+ }, "Reusing existing pending invitation for Slack user");
110
+ return {
111
+ invitationId: existingInvitation.id,
112
+ email: userEmail,
113
+ authMethod: existingInvitation.authMethod ?? "email-password"
114
+ };
115
+ }
116
+ const invitation = await createInvitationInDb(runDbClient_default)({
117
+ organizationId: tenantId,
118
+ email: userEmail
119
+ });
120
+ logger.info({
121
+ userId: slackUserId,
122
+ tenantId,
123
+ invitationId: invitation.id,
124
+ email: userEmail
125
+ }, "Invitation created for Slack user without Inkeep account");
126
+ return {
127
+ invitationId: invitation.id,
128
+ email: userEmail,
129
+ authMethod: invitation.authMethod
130
+ };
131
+ } catch (error) {
132
+ logger.warn({
133
+ error,
134
+ userId: slackUserId,
135
+ tenantId
136
+ }, "Auto-invite attempt failed");
137
+ return null;
138
+ }
139
+ }
140
+
141
+ //#endregion
142
+ export { buildLinkPromptMessage, resolveUnlinkedUserAction };
@@ -0,0 +1,15 @@
1
+ import { SlackLinkIntent } from "@inkeep/agents-core";
2
+
3
+ //#region src/slack/services/resume-intent.d.ts
4
+ interface ResumeSmartLinkIntentParams {
5
+ intent: SlackLinkIntent;
6
+ teamId: string;
7
+ slackUserId: string;
8
+ inkeepUserId: string;
9
+ tenantId: string;
10
+ slackEnterpriseId?: string;
11
+ slackUsername?: string;
12
+ }
13
+ declare function resumeSmartLinkIntent(params: ResumeSmartLinkIntentParams): Promise<void>;
14
+ //#endregion
15
+ export { ResumeSmartLinkIntentParams, resumeSmartLinkIntent };