@inkeep/agents-work-apps 0.53.0 → 0.53.2
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/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
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/agents-work-apps",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.2",
|
|
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.53.
|
|
36
|
+
"@inkeep/agents-core": "0.53.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@hono/zod-openapi": "^1.1.5",
|