@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.
Files changed (37) hide show
  1. package/dist/env.d.ts +2 -2
  2. package/dist/github/mcp/auth.d.ts +2 -2
  3. package/dist/github/mcp/index.d.ts +2 -2
  4. package/dist/github/mcp/index.js +63 -26
  5. package/dist/github/mcp/schemas.d.ts +1 -1
  6. package/dist/github/mcp/utils.d.ts +2 -1
  7. package/dist/github/mcp/utils.js +16 -1
  8. package/dist/github/routes/setup.d.ts +2 -2
  9. package/dist/github/routes/tokenExchange.d.ts +2 -2
  10. package/dist/github/routes/webhooks.d.ts +2 -2
  11. package/dist/slack/dispatcher.js +11 -1
  12. package/dist/slack/routes/oauth.js +21 -1
  13. package/dist/slack/routes/users.js +29 -3
  14. package/dist/slack/services/agent-resolution.d.ts +1 -0
  15. package/dist/slack/services/agent-resolution.js +105 -18
  16. package/dist/slack/services/blocks/index.d.ts +9 -2
  17. package/dist/slack/services/blocks/index.js +27 -11
  18. package/dist/slack/services/commands/index.d.ts +1 -1
  19. package/dist/slack/services/commands/index.js +34 -124
  20. package/dist/slack/services/events/app-mention.d.ts +4 -14
  21. package/dist/slack/services/events/app-mention.js +40 -19
  22. package/dist/slack/services/events/block-actions.js +1 -1
  23. package/dist/slack/services/events/index.d.ts +1 -1
  24. package/dist/slack/services/events/modal-submission.js +14 -7
  25. package/dist/slack/services/events/streaming.js +9 -12
  26. package/dist/slack/services/events/utils.d.ts +26 -5
  27. package/dist/slack/services/events/utils.js +40 -15
  28. package/dist/slack/services/index.d.ts +4 -4
  29. package/dist/slack/services/index.js +3 -3
  30. package/dist/slack/services/link-prompt.d.ts +27 -0
  31. package/dist/slack/services/link-prompt.js +142 -0
  32. package/dist/slack/services/modals.d.ts +1 -0
  33. package/dist/slack/services/modals.js +6 -4
  34. package/dist/slack/services/resume-intent.d.ts +15 -0
  35. package/dist/slack/services/resume-intent.js +338 -0
  36. package/dist/slack/tracer.d.ts +1 -1
  37. package/package.json +2 -2
@@ -0,0 +1,338 @@
1
+ import { env } from "../../env.js";
2
+ import { getLogger } from "../../logger.js";
3
+ import { findWorkspaceConnectionByTeamId } from "./nango.js";
4
+ import { generateSlackConversationId, sendResponseUrlMessage } from "./events/utils.js";
5
+ import { resolveEffectiveAgent } from "./agent-resolution.js";
6
+ import { createContextBlock } from "./blocks/index.js";
7
+ import { getSlackClient } from "./client.js";
8
+ import { streamAgentResponse } from "./events/streaming.js";
9
+ import { signSlackUserToken } from "@inkeep/agents-core";
10
+
11
+ //#region src/slack/services/resume-intent.ts
12
+ const logger = getLogger("slack-resume-intent");
13
+ function getChannelAuthClaims(agentConfig, channelId) {
14
+ return {
15
+ slackAuthorized: agentConfig?.grantAccessToMembers ?? false,
16
+ slackAuthSource: agentConfig?.source && agentConfig.source !== "none" ? agentConfig.source : void 0,
17
+ slackChannelId: channelId,
18
+ slackAuthorizedProjectId: agentConfig?.projectId
19
+ };
20
+ }
21
+ async function resumeSmartLinkIntent(params) {
22
+ const { intent, teamId, slackUserId, inkeepUserId, tenantId, slackEnterpriseId } = params;
23
+ const startTime = Date.now();
24
+ try {
25
+ const botToken = (await findWorkspaceConnectionByTeamId(teamId))?.botToken;
26
+ if (!botToken) {
27
+ logger.error({
28
+ teamId,
29
+ entryPoint: intent.entryPoint
30
+ }, "No bot token available for resume");
31
+ return;
32
+ }
33
+ const slackClient = getSlackClient(botToken);
34
+ const tokenCtx = {
35
+ inkeepUserId,
36
+ tenantId,
37
+ slackTeamId: teamId,
38
+ slackUserId,
39
+ slackEnterpriseId
40
+ };
41
+ let resolvedAgentId;
42
+ let deliveryMethod;
43
+ switch (intent.entryPoint) {
44
+ case "mention":
45
+ resolvedAgentId = intent.agentId;
46
+ deliveryMethod = "streaming";
47
+ await resumeMention(intent, slackClient, tokenCtx, teamId, tenantId);
48
+ break;
49
+ case "question_command":
50
+ deliveryMethod = intent.responseUrl ? "response_url" : "bot_token";
51
+ await resumeCommand(intent, slackClient, tokenCtx, teamId, tenantId);
52
+ break;
53
+ case "run_command":
54
+ deliveryMethod = intent.responseUrl ? "response_url" : "bot_token";
55
+ await resumeRunCommand(intent, slackClient, tokenCtx, teamId, tenantId);
56
+ break;
57
+ }
58
+ const durationMs = Date.now() - startTime;
59
+ logger.info({
60
+ event: "smart_link_intent_resumed",
61
+ entryPoint: intent.entryPoint,
62
+ channelId: intent.channelId,
63
+ agentId: resolvedAgentId || intent.agentId,
64
+ deliveryMethod,
65
+ durationMs
66
+ }, "Smart link intent resumed");
67
+ } catch (error) {
68
+ logger.error({
69
+ event: "smart_link_intent_failed",
70
+ entryPoint: intent.entryPoint,
71
+ error: error instanceof Error ? error.message : String(error)
72
+ }, "Smart link intent resume failed");
73
+ }
74
+ }
75
+ async function resumeMention(intent, slackClient, tokenCtx, teamId, tenantId) {
76
+ const { slackUserId } = tokenCtx;
77
+ if (!intent.agentId || !intent.projectId) {
78
+ logger.error({
79
+ entryPoint: intent.entryPoint,
80
+ channelId: intent.channelId,
81
+ hasAgentId: !!intent.agentId,
82
+ hasProjectId: !!intent.projectId
83
+ }, "Mention intent missing agentId or projectId");
84
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId, intent.threadTs);
85
+ return;
86
+ }
87
+ const replyThreadTs = intent.threadTs || intent.messageTs;
88
+ if (!replyThreadTs) {
89
+ logger.error({
90
+ entryPoint: intent.entryPoint,
91
+ channelId: intent.channelId
92
+ }, "Mention intent missing threadTs and messageTs");
93
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "We couldn't resume your question due to a technical issue. Please try mentioning @Inkeep again.");
94
+ return;
95
+ }
96
+ const agentConfig = await resolveEffectiveAgent({
97
+ tenantId,
98
+ teamId,
99
+ channelId: intent.channelId
100
+ });
101
+ const slackUserToken = await signSlackUserToken({
102
+ ...tokenCtx,
103
+ ...getChannelAuthClaims(agentConfig, intent.channelId)
104
+ });
105
+ const ackMessage = await slackClient.chat.postMessage({
106
+ channel: intent.channelId,
107
+ thread_ts: replyThreadTs,
108
+ text: "_Answering your question..._"
109
+ });
110
+ const conversationId = generateSlackConversationId({
111
+ teamId,
112
+ threadTs: replyThreadTs,
113
+ channel: intent.channelId,
114
+ isDM: false,
115
+ agentId: intent.agentId
116
+ });
117
+ await streamAgentResponse({
118
+ slackClient,
119
+ channel: intent.channelId,
120
+ threadTs: replyThreadTs,
121
+ thinkingMessageTs: ackMessage.ts || "",
122
+ slackUserId,
123
+ teamId,
124
+ jwtToken: slackUserToken,
125
+ projectId: intent.projectId,
126
+ agentId: intent.agentId,
127
+ question: intent.question,
128
+ agentName: intent.agentId,
129
+ conversationId
130
+ });
131
+ }
132
+ async function resumeCommand(intent, slackClient, tokenCtx, teamId, tenantId) {
133
+ const { slackUserId } = tokenCtx;
134
+ const resolvedAgent = await resolveEffectiveAgent({
135
+ tenantId,
136
+ teamId,
137
+ channelId: intent.channelId,
138
+ userId: slackUserId
139
+ });
140
+ if (!resolvedAgent) {
141
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "The agent couldn't be found. Try asking your question again.");
142
+ return;
143
+ }
144
+ await executeAndDeliver({
145
+ intent,
146
+ slackClient,
147
+ slackUserToken: await signSlackUserToken({
148
+ ...tokenCtx,
149
+ ...getChannelAuthClaims(resolvedAgent, intent.channelId)
150
+ }),
151
+ slackUserId,
152
+ teamId,
153
+ agentId: resolvedAgent.agentId,
154
+ agentName: resolvedAgent.agentName || resolvedAgent.agentId,
155
+ projectId: resolvedAgent.projectId
156
+ });
157
+ }
158
+ async function resumeRunCommand(intent, slackClient, tokenCtx, teamId, tenantId) {
159
+ const { slackUserId } = tokenCtx;
160
+ if (!intent.agentIdentifier) {
161
+ logger.error({
162
+ entryPoint: intent.entryPoint,
163
+ channelId: intent.channelId
164
+ }, "Run command intent missing agentIdentifier");
165
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId);
166
+ return;
167
+ }
168
+ const agentConfig = await resolveEffectiveAgent({
169
+ tenantId,
170
+ teamId,
171
+ channelId: intent.channelId
172
+ });
173
+ const slackUserToken = await signSlackUserToken({
174
+ ...tokenCtx,
175
+ ...getChannelAuthClaims(agentConfig, intent.channelId)
176
+ });
177
+ const agentInfo = await findAgentByIdentifierViaApi(tenantId, intent.agentIdentifier, slackUserToken);
178
+ if (!agentInfo) {
179
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, `Agent "${intent.agentIdentifier}" couldn't be found. Try asking your question again.`);
180
+ return;
181
+ }
182
+ await executeAndDeliver({
183
+ intent,
184
+ slackClient,
185
+ slackUserToken,
186
+ slackUserId,
187
+ teamId,
188
+ agentId: agentInfo.id,
189
+ agentName: agentInfo.name || agentInfo.id,
190
+ projectId: agentInfo.projectId
191
+ });
192
+ }
193
+ async function findAgentByIdentifierViaApi(tenantId, identifier, authToken) {
194
+ const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
195
+ const controller = new AbortController();
196
+ const timeout = setTimeout(() => controller.abort(), 1e4);
197
+ try {
198
+ const projectsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects`, {
199
+ method: "GET",
200
+ headers: {
201
+ "Content-Type": "application/json",
202
+ Authorization: `Bearer ${authToken}`
203
+ },
204
+ signal: controller.signal
205
+ });
206
+ if (!projectsResponse.ok) return null;
207
+ const projectsData = await projectsResponse.json();
208
+ const projects = projectsData.data || projectsData || [];
209
+ return (await Promise.all(projects.map(async (project) => {
210
+ try {
211
+ const agentsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects/${project.id}/agents`, {
212
+ method: "GET",
213
+ headers: {
214
+ "Content-Type": "application/json",
215
+ Authorization: `Bearer ${authToken}`
216
+ },
217
+ signal: controller.signal
218
+ });
219
+ if (!agentsResponse.ok) return [];
220
+ const agentsData = await agentsResponse.json();
221
+ return (agentsData.data || agentsData || []).map((agent) => ({
222
+ id: agent.id,
223
+ name: agent.name,
224
+ projectId: project.id
225
+ }));
226
+ } catch (error) {
227
+ logger.warn({
228
+ error: error instanceof Error ? error.message : String(error),
229
+ projectId: project.id
230
+ }, "Failed to fetch agents for project during identifier lookup");
231
+ return [];
232
+ }
233
+ }))).flat().find((a) => a.id === identifier || a.name?.toLowerCase() === identifier.toLowerCase()) || null;
234
+ } catch (error) {
235
+ const isTimeout = error instanceof Error && error.name === "AbortError";
236
+ logger.warn({
237
+ error: error instanceof Error ? error.message : String(error),
238
+ tenantId,
239
+ identifier,
240
+ isTimeout
241
+ }, "Failed to find agent by identifier");
242
+ return null;
243
+ } finally {
244
+ clearTimeout(timeout);
245
+ }
246
+ }
247
+ async function executeAndDeliver(params) {
248
+ const { intent, slackClient, slackUserToken, slackUserId, teamId, agentId, agentName, projectId } = params;
249
+ const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
250
+ const controller = new AbortController();
251
+ const timeout = setTimeout(() => controller.abort(), 3e4);
252
+ let response;
253
+ try {
254
+ response = await fetch(`${apiBaseUrl}/run/api/chat`, {
255
+ method: "POST",
256
+ headers: {
257
+ "Content-Type": "application/json",
258
+ Authorization: `Bearer ${slackUserToken}`,
259
+ "x-inkeep-project-id": projectId,
260
+ "x-inkeep-agent-id": agentId
261
+ },
262
+ body: JSON.stringify({
263
+ messages: [{
264
+ role: "user",
265
+ content: intent.question
266
+ }],
267
+ stream: false
268
+ }),
269
+ signal: controller.signal
270
+ });
271
+ } catch (error) {
272
+ clearTimeout(timeout);
273
+ if (error.name === "AbortError") logger.warn({
274
+ teamId,
275
+ timeoutMs: 3e4
276
+ }, "Resume agent execution timed out");
277
+ throw error;
278
+ } finally {
279
+ clearTimeout(timeout);
280
+ }
281
+ if (!response.ok) {
282
+ logger.error({
283
+ status: response.status,
284
+ agentId,
285
+ projectId
286
+ }, "Resume run API call failed");
287
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId, void 0, "Something went wrong while answering your question. Please try again.");
288
+ return;
289
+ }
290
+ const result = await response.json();
291
+ const assistantMessage = result.choices?.[0]?.message?.content || result.message?.content || "No response received";
292
+ const contextBlock = createContextBlock({ agentName });
293
+ if (intent.responseUrl) try {
294
+ await sendResponseUrlMessage(intent.responseUrl, {
295
+ response_type: "ephemeral",
296
+ text: assistantMessage,
297
+ blocks: [{
298
+ type: "section",
299
+ text: {
300
+ type: "mrkdwn",
301
+ text: assistantMessage
302
+ }
303
+ }, contextBlock]
304
+ });
305
+ return;
306
+ } catch {
307
+ logger.warn({ channelId: intent.channelId }, "response_url delivery failed, falling back to bot channel post");
308
+ }
309
+ await slackClient.chat.postMessage({
310
+ channel: intent.channelId,
311
+ text: assistantMessage,
312
+ blocks: [{
313
+ type: "section",
314
+ text: {
315
+ type: "mrkdwn",
316
+ text: assistantMessage
317
+ }
318
+ }, contextBlock]
319
+ });
320
+ }
321
+ async function postErrorToChannel(slackClient, channelId, slackUserId, threadTs, message = "The agent couldn't be found. Try asking your question again.") {
322
+ try {
323
+ await slackClient.chat.postEphemeral({
324
+ channel: channelId,
325
+ user: slackUserId,
326
+ thread_ts: threadTs,
327
+ text: message
328
+ });
329
+ } catch (error) {
330
+ logger.warn({
331
+ error,
332
+ channelId
333
+ }, "Failed to post error message to Slack");
334
+ }
335
+ }
336
+
337
+ //#endregion
338
+ export { resumeSmartLinkIntent };
@@ -40,6 +40,6 @@ declare const SLACK_SPAN_KEYS: {
40
40
  readonly AUTHORIZED: "slack.authorized";
41
41
  readonly AUTH_SOURCE: "slack.auth_source";
42
42
  };
43
- type SlackOutcome = 'handled' | 'ignored_bot_message' | 'ignored_unknown_event' | 'ignored_no_action_match' | 'ignored_slack_retry' | 'url_verification' | 'validation_error' | 'signature_invalid' | 'error';
43
+ type SlackOutcome = 'handled' | 'ignored_bot_message' | 'ignored_edited_message' | 'ignored_unknown_event' | 'ignored_no_action_match' | 'ignored_slack_retry' | 'url_verification' | 'validation_error' | 'signature_invalid' | 'error';
44
44
  //#endregion
45
45
  export { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, SlackOutcome, setSpanWithError, tracer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/agents-work-apps",
3
- "version": "0.52.0",
3
+ "version": "0.53.1",
4
4
  "description": "First party integrations for Inkeep Agents",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -33,7 +33,7 @@
33
33
  "jose": "^6.1.0",
34
34
  "minimatch": "^10.1.1",
35
35
  "slack-block-builder": "^2.8.0",
36
- "@inkeep/agents-core": "0.52.0"
36
+ "@inkeep/agents-core": "0.53.1"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@hono/zod-openapi": "^1.1.5",