@inkeep/agents-work-apps 0.52.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/auth.d.ts +2 -2
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/mcp/index.js +63 -26
- package/dist/github/mcp/schemas.d.ts +1 -1
- package/dist/github/mcp/utils.d.ts +2 -1
- package/dist/github/mcp/utils.js +16 -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 +11 -1
- package/dist/slack/routes/oauth.js +21 -1
- 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 +105 -18
- package/dist/slack/services/blocks/index.d.ts +9 -2
- package/dist/slack/services/blocks/index.js +27 -11
- package/dist/slack/services/commands/index.d.ts +1 -1
- package/dist/slack/services/commands/index.js +34 -124
- 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/block-actions.js +1 -1
- package/dist/slack/services/events/index.d.ts +1 -1
- package/dist/slack/services/events/modal-submission.js +14 -7
- package/dist/slack/services/events/streaming.js +9 -12
- package/dist/slack/services/events/utils.d.ts +26 -5
- package/dist/slack/services/events/utils.js +40 -15
- package/dist/slack/services/index.d.ts +4 -4
- package/dist/slack/services/index.js +3 -3
- package/dist/slack/services/link-prompt.d.ts +27 -0
- package/dist/slack/services/link-prompt.js +142 -0
- package/dist/slack/services/modals.d.ts +1 -0
- package/dist/slack/services/modals.js +6 -4
- package/dist/slack/services/resume-intent.d.ts +15 -0
- package/dist/slack/services/resume-intent.js +338 -0
- package/dist/slack/tracer.d.ts +1 -1
- package/package.json +2 -2
|
@@ -17,6 +17,7 @@ declare function createContextBlock(params: ContextBlockParams): {
|
|
|
17
17
|
interface FollowUpButtonParams {
|
|
18
18
|
conversationId: string;
|
|
19
19
|
agentId: string;
|
|
20
|
+
agentName?: string;
|
|
20
21
|
projectId: string;
|
|
21
22
|
tenantId: string;
|
|
22
23
|
teamId: string;
|
|
@@ -52,18 +53,25 @@ interface AgentConfigSources {
|
|
|
52
53
|
channelConfig: {
|
|
53
54
|
agentName?: string;
|
|
54
55
|
agentId: string;
|
|
56
|
+
projectId: string;
|
|
57
|
+
projectName?: string;
|
|
55
58
|
} | null;
|
|
56
59
|
workspaceConfig: {
|
|
57
60
|
agentName?: string;
|
|
58
61
|
agentId: string;
|
|
62
|
+
projectId: string;
|
|
63
|
+
projectName?: string;
|
|
59
64
|
} | null;
|
|
60
65
|
effective: {
|
|
61
66
|
agentName?: string;
|
|
62
67
|
agentId: string;
|
|
68
|
+
projectId: string;
|
|
69
|
+
projectName?: string;
|
|
63
70
|
source: string;
|
|
64
71
|
} | null;
|
|
65
72
|
}
|
|
66
73
|
declare function createStatusMessage(email: string, linkedAt: string, dashboardUrl: string, agentConfigs: AgentConfigSources): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
74
|
+
declare function createSmartLinkMessage(linkUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
67
75
|
interface ToolApprovalButtonValue {
|
|
68
76
|
toolCallId: string;
|
|
69
77
|
conversationId: string;
|
|
@@ -142,7 +150,6 @@ declare function buildCitationsBlock(citations: Array<{
|
|
|
142
150
|
title?: string;
|
|
143
151
|
url?: string;
|
|
144
152
|
}>): any[];
|
|
145
|
-
declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
146
153
|
declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
147
154
|
//#endregion
|
|
148
|
-
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
155
|
+
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -44,10 +44,7 @@ function buildConversationResponseBlocks(params) {
|
|
|
44
44
|
}
|
|
45
45
|
}];
|
|
46
46
|
if (!isError) {
|
|
47
|
-
const contextBlock = createContextBlock({
|
|
48
|
-
agentName,
|
|
49
|
-
isPrivate: true
|
|
50
|
-
});
|
|
47
|
+
const contextBlock = createContextBlock({ agentName });
|
|
51
48
|
blocks.push(contextBlock);
|
|
52
49
|
blocks.push({
|
|
53
50
|
type: "actions",
|
|
@@ -70,10 +67,32 @@ function createNotLinkedMessage() {
|
|
|
70
67
|
}
|
|
71
68
|
function createStatusMessage(email, linkedAt, dashboardUrl, agentConfigs) {
|
|
72
69
|
const { effective } = agentConfigs;
|
|
70
|
+
const baseUrl = dashboardUrl.replace(/\/work-apps\/slack$/, "");
|
|
73
71
|
let agentLine;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
let projectLine;
|
|
73
|
+
if (effective) {
|
|
74
|
+
const agentDisplayName = effective.agentName || effective.agentId;
|
|
75
|
+
const agentUrl = `${baseUrl}/projects/${effective.projectId}/agents/${effective.agentId}`;
|
|
76
|
+
agentLine = `${Md.bold("Agent:")} <${agentUrl}|${agentDisplayName}>`;
|
|
77
|
+
const projectDisplayName = effective.projectName || effective.projectId;
|
|
78
|
+
const projectUrl = `${baseUrl}/projects/${effective.projectId}/agents`;
|
|
79
|
+
projectLine = `${Md.bold("Project:")} <${projectUrl}|${projectDisplayName}>`;
|
|
80
|
+
} else {
|
|
81
|
+
agentLine = `${Md.bold("Agent:")} None configured\n${Md.italic("Ask your admin to set up an agent in the dashboard.")}`;
|
|
82
|
+
projectLine = "";
|
|
83
|
+
}
|
|
84
|
+
const lines = [
|
|
85
|
+
Md.bold("Connected to Inkeep"),
|
|
86
|
+
"",
|
|
87
|
+
`${Md.bold("Account:")} ${email}`,
|
|
88
|
+
`${Md.bold("Linked:")} ${new Date(linkedAt).toLocaleDateString()}`,
|
|
89
|
+
agentLine
|
|
90
|
+
];
|
|
91
|
+
if (projectLine) lines.push(projectLine);
|
|
92
|
+
return Message().blocks(Blocks.Section().text(lines.join("\n")), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
|
|
93
|
+
}
|
|
94
|
+
function createSmartLinkMessage(linkUrl) {
|
|
95
|
+
return Message().blocks(Blocks.Section().text("To get started, let's connect your Inkeep account with Slack."), Blocks.Actions().elements(Elements.Button().text("Link Account").url(linkUrl).actionId("smart_link_account").primary()), Blocks.Context().elements("🕐 This only needs to happen once.")).buildToObject();
|
|
77
96
|
}
|
|
78
97
|
const ToolApprovalButtonValueSchema = z.object({
|
|
79
98
|
toolCallId: z.string(),
|
|
@@ -300,12 +319,9 @@ function buildCitationsBlock(citations) {
|
|
|
300
319
|
}
|
|
301
320
|
}];
|
|
302
321
|
}
|
|
303
|
-
function createJwtLinkMessage(linkUrl, expiresInMinutes) {
|
|
304
|
-
return Message().blocks(Blocks.Section().text(`${Md.bold("Link your Inkeep account")}\n\nConnect your Slack and Inkeep accounts to use Inkeep agents.`), Blocks.Actions().elements(Elements.Button().text("Link Account").url(linkUrl).actionId("link_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
305
|
-
}
|
|
306
322
|
function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
|
|
307
323
|
return Message().blocks(Blocks.Section().text(`${Md.bold("Create your Inkeep account")}\n\nYou've been invited to join Inkeep. Create an account to start using Inkeep agents in Slack.`), Blocks.Actions().elements(Elements.Button().text("Create Account").url(acceptUrl).actionId("create_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
308
324
|
}
|
|
309
325
|
|
|
310
326
|
//#endregion
|
|
311
|
-
export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage,
|
|
327
|
+
export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -11,7 +11,7 @@ declare function handleHelpCommand(): Promise<SlackCommandResponse>;
|
|
|
11
11
|
* Similar to @mention behavior in channels
|
|
12
12
|
*/
|
|
13
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>;
|
|
14
|
+
declare function handleQuestionCommand(payload: SlackCommandPayload, question: string, _dashboardUrl: string, tenantId: string, botToken: string): Promise<SlackCommandResponse>;
|
|
15
15
|
declare function handleCommand(payload: SlackCommandPayload): Promise<SlackCommandResponse>;
|
|
16
16
|
//#endregion
|
|
17
17
|
export { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand };
|
|
@@ -2,134 +2,35 @@ import { env } from "../../../env.js";
|
|
|
2
2
|
import { getLogger } from "../../../logger.js";
|
|
3
3
|
import runDbClient_default from "../../../db/runDbClient.js";
|
|
4
4
|
import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
5
|
+
import { extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
|
|
5
6
|
import { resolveEffectiveAgent } from "../agent-resolution.js";
|
|
6
7
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
7
|
-
import { createAlreadyLinkedMessage, createContextBlock,
|
|
8
|
+
import { createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
|
|
8
9
|
import { getSlackClient } from "../client.js";
|
|
9
|
-
import {
|
|
10
|
+
import { buildLinkPromptMessage, resolveUnlinkedUserAction } from "../link-prompt.js";
|
|
10
11
|
import { buildAgentSelectorModal } from "../modals.js";
|
|
11
|
-
import {
|
|
12
|
+
import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getInProcessFetch, getWaitUntil, signSlackUserToken } from "@inkeep/agents-core";
|
|
12
13
|
|
|
13
14
|
//#region src/slack/services/commands/index.ts
|
|
14
15
|
const DEFAULT_CLIENT_ID = "work-apps-slack";
|
|
15
|
-
const LINK_CODE_TTL_MINUTES = 10;
|
|
16
16
|
const logger = getLogger("slack-commands");
|
|
17
|
-
/**
|
|
18
|
-
* Create an invitation for a Slack user who doesn't have an Inkeep account yet.
|
|
19
|
-
* Returns the invitation ID and email so the caller can direct the user
|
|
20
|
-
* to the accept-invitation page.
|
|
21
|
-
*
|
|
22
|
-
* Returns null if:
|
|
23
|
-
* - Workspace doesn't have shouldAllowJoinFromWorkspace enabled
|
|
24
|
-
* - User already has an Inkeep account (JWT link flow is sufficient)
|
|
25
|
-
* - Service account is not configured
|
|
26
|
-
*/
|
|
27
|
-
async function tryAutoInvite(payload, tenantId, botToken) {
|
|
28
|
-
try {
|
|
29
|
-
if (!(await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(tenantId, payload.teamId))?.shouldAllowJoinFromWorkspace) return null;
|
|
30
|
-
const slackClient = getSlackClient(botToken);
|
|
31
|
-
let userEmail;
|
|
32
|
-
try {
|
|
33
|
-
userEmail = (await slackClient.users.info({ user: payload.userId })).user?.profile?.email;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
logger.warn({
|
|
36
|
-
error,
|
|
37
|
-
userId: payload.userId
|
|
38
|
-
}, "Failed to get user info from Slack");
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
if (!userEmail) {
|
|
42
|
-
logger.warn({ userId: payload.userId }, "No email found in Slack user profile");
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (await getOrganizationMemberByEmail(runDbClient_default)(tenantId, userEmail)) {
|
|
46
|
-
logger.debug({
|
|
47
|
-
userId: payload.userId,
|
|
48
|
-
email: userEmail
|
|
49
|
-
}, "User already has Inkeep account, skipping auto-invite");
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const existingInvitation = (await getPendingInvitationsByEmail(runDbClient_default)(userEmail)).find((inv) => inv.organizationId === tenantId);
|
|
53
|
-
if (existingInvitation) {
|
|
54
|
-
logger.info({
|
|
55
|
-
userId: payload.userId,
|
|
56
|
-
tenantId,
|
|
57
|
-
invitationId: existingInvitation.id,
|
|
58
|
-
email: userEmail
|
|
59
|
-
}, "Reusing existing pending invitation for Slack user");
|
|
60
|
-
return {
|
|
61
|
-
invitationId: existingInvitation.id,
|
|
62
|
-
email: userEmail
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const invitation = await createInvitationInDb(runDbClient_default)({
|
|
66
|
-
organizationId: tenantId,
|
|
67
|
-
email: userEmail
|
|
68
|
-
});
|
|
69
|
-
logger.info({
|
|
70
|
-
userId: payload.userId,
|
|
71
|
-
tenantId,
|
|
72
|
-
invitationId: invitation.id,
|
|
73
|
-
email: userEmail
|
|
74
|
-
}, "Invitation created for Slack user without Inkeep account");
|
|
75
|
-
return {
|
|
76
|
-
invitationId: invitation.id,
|
|
77
|
-
email: userEmail
|
|
78
|
-
};
|
|
79
|
-
} catch (error) {
|
|
80
|
-
logger.warn({
|
|
81
|
-
error,
|
|
82
|
-
userId: payload.userId,
|
|
83
|
-
tenantId
|
|
84
|
-
}, "Auto-invite attempt failed");
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
17
|
async function handleLinkCommand(payload, dashboardUrl, tenantId, botToken) {
|
|
89
18
|
const existingLink = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
|
|
90
19
|
if (existingLink) return {
|
|
91
20
|
response_type: "ephemeral",
|
|
92
21
|
...createAlreadyLinkedMessage(existingLink.slackEmail || existingLink.slackUsername || "Unknown", existingLink.linkedAt, dashboardUrl)
|
|
93
22
|
};
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const linkToken = await signSlackLinkToken({
|
|
23
|
+
try {
|
|
24
|
+
return {
|
|
25
|
+
response_type: "ephemeral",
|
|
26
|
+
...buildLinkPromptMessage(await resolveUnlinkedUserAction({
|
|
99
27
|
tenantId,
|
|
100
|
-
|
|
28
|
+
teamId: payload.teamId,
|
|
101
29
|
slackUserId: payload.userId,
|
|
30
|
+
botToken: botToken || "",
|
|
102
31
|
slackEnterpriseId: payload.enterpriseId,
|
|
103
32
|
slackUsername: payload.userName
|
|
104
|
-
})
|
|
105
|
-
const linkReturnUrl = `/link?token=${encodeURIComponent(linkToken)}`;
|
|
106
|
-
const acceptUrl = `${manageUiUrl}/accept-invitation/${autoInvite.invitationId}?email=${encodeURIComponent(autoInvite.email)}&returnUrl=${encodeURIComponent(linkReturnUrl)}`;
|
|
107
|
-
logger.info({
|
|
108
|
-
invitationId: autoInvite.invitationId,
|
|
109
|
-
email: autoInvite.email
|
|
110
|
-
}, "Directing new user to accept-invitation page with link returnUrl");
|
|
111
|
-
return {
|
|
112
|
-
response_type: "ephemeral",
|
|
113
|
-
...createCreateInkeepAccountMessage(acceptUrl, LINK_CODE_TTL_MINUTES)
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const linkToken = await signSlackLinkToken({
|
|
119
|
-
tenantId,
|
|
120
|
-
slackTeamId: payload.teamId,
|
|
121
|
-
slackUserId: payload.userId,
|
|
122
|
-
slackEnterpriseId: payload.enterpriseId,
|
|
123
|
-
slackUsername: payload.userName
|
|
124
|
-
});
|
|
125
|
-
const linkUrl = `${env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000"}/link?token=${encodeURIComponent(linkToken)}`;
|
|
126
|
-
logger.info({
|
|
127
|
-
slackUserId: payload.userId,
|
|
128
|
-
tenantId
|
|
129
|
-
}, "Generated JWT link token");
|
|
130
|
-
return {
|
|
131
|
-
response_type: "ephemeral",
|
|
132
|
-
...createJwtLinkMessage(linkUrl, LINK_CODE_TTL_MINUTES)
|
|
33
|
+
}))
|
|
133
34
|
};
|
|
134
35
|
} catch (error) {
|
|
135
36
|
logger.error({
|
|
@@ -278,23 +179,27 @@ async function handleAgentPickerCommand(payload, tenantId, workspaceConnection)
|
|
|
278
179
|
};
|
|
279
180
|
}
|
|
280
181
|
}
|
|
281
|
-
async function generateLinkCodeWithIntent(payload, tenantId) {
|
|
182
|
+
async function generateLinkCodeWithIntent(payload, tenantId, botToken, intent) {
|
|
282
183
|
try {
|
|
283
|
-
const
|
|
184
|
+
const linkResult = await resolveUnlinkedUserAction({
|
|
284
185
|
tenantId,
|
|
285
|
-
|
|
186
|
+
teamId: payload.teamId,
|
|
286
187
|
slackUserId: payload.userId,
|
|
188
|
+
botToken,
|
|
287
189
|
slackEnterpriseId: payload.enterpriseId,
|
|
288
|
-
slackUsername: payload.userName
|
|
190
|
+
slackUsername: payload.userName,
|
|
191
|
+
intent
|
|
289
192
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
193
|
+
if (!!intent) logger.info({
|
|
194
|
+
event: "smart_link_intent_captured",
|
|
195
|
+
entryPoint: intent.entryPoint,
|
|
196
|
+
linkType: linkResult.type,
|
|
197
|
+
questionLength: intent.question.length,
|
|
198
|
+
channelId: payload.channelId
|
|
199
|
+
}, "Smart link intent captured");
|
|
295
200
|
return {
|
|
296
201
|
response_type: "ephemeral",
|
|
297
|
-
...
|
|
202
|
+
...buildLinkPromptMessage(linkResult)
|
|
298
203
|
};
|
|
299
204
|
} catch (error) {
|
|
300
205
|
logger.error({
|
|
@@ -308,9 +213,14 @@ async function generateLinkCodeWithIntent(payload, tenantId) {
|
|
|
308
213
|
};
|
|
309
214
|
}
|
|
310
215
|
}
|
|
311
|
-
async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId) {
|
|
216
|
+
async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId, botToken) {
|
|
312
217
|
const existingLink = await findWorkAppSlackUserMappingBySlackUser(runDbClient_default)(payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
|
|
313
|
-
if (!existingLink) return generateLinkCodeWithIntent(payload, tenantId
|
|
218
|
+
if (!existingLink) return generateLinkCodeWithIntent(payload, tenantId, botToken, {
|
|
219
|
+
entryPoint: "question_command",
|
|
220
|
+
question: question.slice(0, 2e3),
|
|
221
|
+
channelId: payload.channelId,
|
|
222
|
+
responseUrl: payload.responseUrl
|
|
223
|
+
});
|
|
314
224
|
const userTenantId = existingLink.tenantId;
|
|
315
225
|
const resolvedAgent = await resolveEffectiveAgent({
|
|
316
226
|
tenantId: userTenantId,
|
|
@@ -412,7 +322,7 @@ async function executeAgentInBackground(payload, existingLink, targetAgent, ques
|
|
|
412
322
|
}, "Agent execution completed via Slack");
|
|
413
323
|
const contextBlock = createContextBlock({ agentName: targetAgent.name || targetAgent.id });
|
|
414
324
|
await sendResponseUrlMessage(payload.responseUrl, {
|
|
415
|
-
response_type: "
|
|
325
|
+
response_type: "in_channel",
|
|
416
326
|
text: assistantMessage,
|
|
417
327
|
blocks: [{
|
|
418
328
|
type: "section",
|
|
@@ -464,7 +374,7 @@ async function handleCommand(payload) {
|
|
|
464
374
|
case "disconnect": return handleUnlinkCommand(payload, tenantId);
|
|
465
375
|
case "help": return handleHelpCommand();
|
|
466
376
|
case "": return handleAgentPickerCommand(payload, tenantId, workspaceConnection);
|
|
467
|
-
default: return handleQuestionCommand(payload, text, dashboardUrl, tenantId);
|
|
377
|
+
default: return handleQuestionCommand(payload, text, dashboardUrl, tenantId, workspaceConnection.botToken);
|
|
468
378
|
}
|
|
469
379
|
}
|
|
470
380
|
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
+
import { SlackAttachment } from "./utils.js";
|
|
2
|
+
|
|
1
3
|
//#region src/slack/services/events/app-mention.d.ts
|
|
2
|
-
|
|
3
|
-
* Handler for Slack @mention events
|
|
4
|
-
*
|
|
5
|
-
* Flow:
|
|
6
|
-
* 1. Resolve workspace connection (single lookup, cached)
|
|
7
|
-
* 2. Parallel: resolve agent config + check user link
|
|
8
|
-
* 3. If no agent configured → prompt to set up in dashboard
|
|
9
|
-
* 4. If not linked → prompt to link account
|
|
10
|
-
* 5. Handle based on context:
|
|
11
|
-
* - Channel + no query → Show usage hint
|
|
12
|
-
* - Channel + query → Execute agent with streaming response
|
|
13
|
-
* - Thread + no query → Auto-execute agent with thread context as query
|
|
14
|
-
* - Thread + query → Execute agent with thread context included
|
|
15
|
-
*/
|
|
4
|
+
|
|
16
5
|
/**
|
|
17
6
|
* Metadata passed to the agent selector modal via button value
|
|
18
7
|
*/
|
|
@@ -32,6 +21,7 @@ declare function handleAppMention(params: {
|
|
|
32
21
|
slackUserId: string;
|
|
33
22
|
channel: string;
|
|
34
23
|
text: string;
|
|
24
|
+
attachments?: SlackAttachment[];
|
|
35
25
|
threadTs: string;
|
|
36
26
|
messageTs: string;
|
|
37
27
|
teamId: string;
|
|
@@ -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, formatAttachments, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
|
|
4
5
|
import { resolveEffectiveAgent } from "../agent-resolution.js";
|
|
5
6
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
6
7
|
import { getSlackChannelInfo, getSlackClient, getSlackUserInfo, postMessageInThread } from "../client.js";
|
|
7
|
-
import {
|
|
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,10 +1,10 @@
|
|
|
1
1
|
import { env } from "../../../env.js";
|
|
2
2
|
import { getLogger } from "../../../logger.js";
|
|
3
3
|
import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
4
|
+
import { fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
|
|
4
5
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
5
6
|
import { ToolApprovalButtonValueSchema, buildToolApprovalDoneBlocks } from "../blocks/index.js";
|
|
6
7
|
import { getSlackClient } from "../client.js";
|
|
7
|
-
import { fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
|
|
8
8
|
import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "../modals.js";
|
|
9
9
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
10
10
|
import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
|
|
@@ -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 };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { env } from "../../../env.js";
|
|
2
2
|
import { getLogger } from "../../../logger.js";
|
|
3
3
|
import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
4
|
+
import { classifyError, extractApiErrorMessage, findCachedUserMapping, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
|
|
4
5
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
5
6
|
import { buildConversationResponseBlocks } from "../blocks/index.js";
|
|
6
7
|
import { getSlackClient } from "../client.js";
|
|
7
|
-
import { classifyError, extractApiErrorMessage, findCachedUserMapping, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
|
|
8
8
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
9
9
|
import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
|
|
10
10
|
|
|
@@ -36,13 +36,16 @@ async function handleModalSubmission(view) {
|
|
|
36
36
|
const includeContext = includeContextValue?.selected_options?.some((o) => o.value === "include_context") ?? true;
|
|
37
37
|
let agentId = metadata.selectedAgentId;
|
|
38
38
|
let projectId = metadata.selectedProjectId;
|
|
39
|
+
let agentName = null;
|
|
39
40
|
if (agentSelectValue?.selected_option?.value) try {
|
|
40
41
|
const parsed = JSON.parse(agentSelectValue.selected_option.value);
|
|
41
42
|
agentId = parsed.agentId;
|
|
42
43
|
projectId = parsed.projectId;
|
|
44
|
+
agentName = parsed.agentName || null;
|
|
43
45
|
} catch {
|
|
44
46
|
logger.warn({ value: agentSelectValue.selected_option.value }, "Failed to parse agent select value");
|
|
45
47
|
}
|
|
48
|
+
const agentDisplayName = agentName || agentId || "Agent";
|
|
46
49
|
if (!agentId || !projectId) {
|
|
47
50
|
logger.error({ metadata }, "Missing agent or project ID in modal submission");
|
|
48
51
|
if (metadata.buttonResponseUrl) await sendResponseUrlMessage(metadata.buttonResponseUrl, {
|
|
@@ -112,7 +115,7 @@ async function handleModalSubmission(view) {
|
|
|
112
115
|
});
|
|
113
116
|
span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, conversationId);
|
|
114
117
|
const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
|
|
115
|
-
const thinkingText = SlackStrings.status.thinking(
|
|
118
|
+
const thinkingText = SlackStrings.status.thinking(agentDisplayName);
|
|
116
119
|
if (metadata.buttonResponseUrl) await sendResponseUrlMessage(metadata.buttonResponseUrl, {
|
|
117
120
|
text: thinkingText,
|
|
118
121
|
response_type: "ephemeral",
|
|
@@ -139,6 +142,7 @@ async function handleModalSubmission(view) {
|
|
|
139
142
|
slackClient,
|
|
140
143
|
metadata,
|
|
141
144
|
agentId,
|
|
145
|
+
agentDisplayName,
|
|
142
146
|
projectId,
|
|
143
147
|
tenantId,
|
|
144
148
|
conversationId,
|
|
@@ -194,7 +198,8 @@ async function handleFollowUpSubmission(view) {
|
|
|
194
198
|
span.end();
|
|
195
199
|
return;
|
|
196
200
|
}
|
|
197
|
-
const { conversationId, agentId, projectId, tenantId, teamId, slackUserId, channel } = metadata;
|
|
201
|
+
const { conversationId, agentId, agentName, projectId, tenantId, teamId, slackUserId, channel } = metadata;
|
|
202
|
+
const agentDisplayName = agentName || agentId || "Agent";
|
|
198
203
|
span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
|
|
199
204
|
span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channel);
|
|
200
205
|
span.setAttribute(SLACK_SPAN_KEYS.USER_ID, slackUserId);
|
|
@@ -234,7 +239,7 @@ async function handleFollowUpSubmission(view) {
|
|
|
234
239
|
await slackClient.chat.postEphemeral({
|
|
235
240
|
channel,
|
|
236
241
|
user: slackUserId,
|
|
237
|
-
text: SlackStrings.status.thinking(
|
|
242
|
+
text: SlackStrings.status.thinking(agentDisplayName)
|
|
238
243
|
});
|
|
239
244
|
const responseText = await callAgentApi({
|
|
240
245
|
apiBaseUrl,
|
|
@@ -247,11 +252,12 @@ async function handleFollowUpSubmission(view) {
|
|
|
247
252
|
const responseBlocks = buildConversationResponseBlocks({
|
|
248
253
|
userMessage: question,
|
|
249
254
|
responseText: responseText.text,
|
|
250
|
-
agentName:
|
|
255
|
+
agentName: agentDisplayName,
|
|
251
256
|
isError: responseText.isError,
|
|
252
257
|
followUpParams: {
|
|
253
258
|
conversationId,
|
|
254
259
|
agentId,
|
|
260
|
+
agentName: agentDisplayName,
|
|
255
261
|
projectId,
|
|
256
262
|
tenantId,
|
|
257
263
|
teamId,
|
|
@@ -370,15 +376,16 @@ async function callAgentApi(params) {
|
|
|
370
376
|
});
|
|
371
377
|
}
|
|
372
378
|
async function postPrivateResponse(params) {
|
|
373
|
-
const { slackClient, metadata, agentId, projectId, tenantId, conversationId, userMessage, responseText, isError } = params;
|
|
379
|
+
const { slackClient, metadata, agentId, agentDisplayName, projectId, tenantId, conversationId, userMessage, responseText, isError } = params;
|
|
374
380
|
const responseBlocks = buildConversationResponseBlocks({
|
|
375
381
|
userMessage,
|
|
376
382
|
responseText,
|
|
377
|
-
agentName:
|
|
383
|
+
agentName: agentDisplayName,
|
|
378
384
|
isError,
|
|
379
385
|
followUpParams: {
|
|
380
386
|
conversationId,
|
|
381
387
|
agentId,
|
|
388
|
+
agentName: agentDisplayName,
|
|
382
389
|
projectId,
|
|
383
390
|
tenantId,
|
|
384
391
|
teamId: metadata.teamId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { env } from "../../../env.js";
|
|
2
2
|
import { getLogger } from "../../../logger.js";
|
|
3
|
-
import { buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createContextBlock } from "../blocks/index.js";
|
|
4
3
|
import { SlackErrorType, classifyError, extractApiErrorMessage, getUserFriendlyErrorMessage } from "./utils.js";
|
|
4
|
+
import { buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createContextBlock } from "../blocks/index.js";
|
|
5
5
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
6
6
|
import { getInProcessFetch, retryWithBackoff } from "@inkeep/agents-core";
|
|
7
7
|
|
|
@@ -193,6 +193,7 @@ async function streamAgentResponse(params) {
|
|
|
193
193
|
});
|
|
194
194
|
const pendingApprovalMessages = [];
|
|
195
195
|
const toolCallIdToName = /* @__PURE__ */ new Map();
|
|
196
|
+
const toolCallIdToInput = /* @__PURE__ */ new Map();
|
|
196
197
|
const toolErrors = [];
|
|
197
198
|
const citations = [];
|
|
198
199
|
const summaryLabels = [];
|
|
@@ -221,9 +222,9 @@ async function streamAgentResponse(params) {
|
|
|
221
222
|
continue;
|
|
222
223
|
}
|
|
223
224
|
if (data.type === "tool-approval-request" && conversationId) {
|
|
224
|
-
const toolName = data.toolName || "Tool";
|
|
225
225
|
const toolCallId = data.toolCallId;
|
|
226
|
-
const
|
|
226
|
+
const toolName = toolCallIdToName.get(toolCallId) || "Tool";
|
|
227
|
+
const input = toolCallIdToInput.get(toolCallId);
|
|
227
228
|
const buttonValue = {
|
|
228
229
|
toolCallId,
|
|
229
230
|
conversationId,
|
|
@@ -260,6 +261,7 @@ async function streamAgentResponse(params) {
|
|
|
260
261
|
}
|
|
261
262
|
if (data.type === "tool-input-available" && data.toolCallId && data.toolName) {
|
|
262
263
|
toolCallIdToName.set(String(data.toolCallId), String(data.toolName));
|
|
264
|
+
if (data.input && typeof data.input === "object") toolCallIdToInput.set(String(data.toolCallId), data.input);
|
|
263
265
|
continue;
|
|
264
266
|
}
|
|
265
267
|
if (data.type === "tool-output-denied" && data.toolCallId) {
|
|
@@ -383,6 +385,10 @@ async function streamAgentResponse(params) {
|
|
|
383
385
|
const stopBlocks = [];
|
|
384
386
|
for (const { toolName, errorText } of toolErrors) stopBlocks.push(buildToolOutputErrorBlock(toolName, errorText));
|
|
385
387
|
if (summaryLabels.length > 0) stopBlocks.push(buildSummaryBreadcrumbBlock(summaryLabels));
|
|
388
|
+
if (citations.length > 0) {
|
|
389
|
+
const citationBlocks = buildCitationsBlock(citations);
|
|
390
|
+
stopBlocks.push(...citationBlocks);
|
|
391
|
+
}
|
|
386
392
|
stopBlocks.push(createContextBlock({ agentName }));
|
|
387
393
|
try {
|
|
388
394
|
await withTimeout(streamer.stop({ blocks: stopBlocks.slice(0, 50) }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.stop");
|
|
@@ -395,15 +401,6 @@ async function streamAgentResponse(params) {
|
|
|
395
401
|
responseLength: fullText.length
|
|
396
402
|
}, "Failed to finalize chatStream — content was already delivered");
|
|
397
403
|
}
|
|
398
|
-
if (citations.length > 0) {
|
|
399
|
-
const citationBlocks = buildCitationsBlock(citations);
|
|
400
|
-
if (citationBlocks.length > 0) await slackClient.chat.postMessage({
|
|
401
|
-
channel,
|
|
402
|
-
thread_ts: threadTs,
|
|
403
|
-
text: "📚 Sources",
|
|
404
|
-
blocks: citationBlocks
|
|
405
|
-
}).catch((e) => logger.warn({ error: e }, "Failed to post citations"));
|
|
406
|
-
}
|
|
407
404
|
if (thinkingMessageTs) try {
|
|
408
405
|
await slackClient.chat.delete({
|
|
409
406
|
channel,
|