@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.
- package/dist/env.d.ts +2 -2
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/mcp/index.js +2 -1
- package/dist/github/routes/setup.d.ts +2 -2
- package/dist/github/routes/tokenExchange.d.ts +2 -2
- package/dist/github/routes/webhooks.d.ts +2 -2
- package/dist/slack/dispatcher.js +1 -0
- package/dist/slack/routes/users.js +29 -3
- package/dist/slack/services/agent-resolution.d.ts +1 -0
- package/dist/slack/services/agent-resolution.js +40 -1
- package/dist/slack/services/blocks/index.d.ts +8 -2
- package/dist/slack/services/blocks/index.js +26 -7
- package/dist/slack/services/commands/index.d.ts +1 -1
- package/dist/slack/services/commands/index.js +32 -122
- package/dist/slack/services/events/app-mention.d.ts +4 -14
- package/dist/slack/services/events/app-mention.js +40 -19
- package/dist/slack/services/events/index.d.ts +1 -1
- package/dist/slack/services/events/utils.d.ts +21 -2
- package/dist/slack/services/events/utils.js +35 -10
- package/dist/slack/services/index.d.ts +4 -4
- package/dist/slack/services/index.js +2 -2
- package/dist/slack/services/link-prompt.d.ts +27 -0
- package/dist/slack/services/link-prompt.js +142 -0
- package/dist/slack/services/resume-intent.d.ts +15 -0
- package/dist/slack/services/resume-intent.js +338 -0
- package/package.json +2 -2
|
@@ -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: "
|
|
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)
|
|
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
|
-
|
|
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 (
|
|
38
|
-
evictExpiredEntries();
|
|
37
|
+
if (mapping) {
|
|
39
38
|
if (userMappingCache.size >= USER_MAPPING_CACHE_MAX_SIZE) {
|
|
40
|
-
|
|
41
|
-
if (
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 };
|