@inkeep/agents-work-apps 0.0.0-dev-20260223213559 → 0.0.0-dev-20260224023234

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 CHANGED
@@ -14,11 +14,11 @@ declare const envSchema: z.ZodObject<{
14
14
  pentest: "pentest";
15
15
  }>>;
16
16
  LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
17
+ error: "error";
17
18
  trace: "trace";
18
19
  debug: "debug";
19
20
  info: "info";
20
21
  warn: "warn";
21
- error: "error";
22
22
  }>>;
23
23
  INKEEP_AGENTS_RUN_DATABASE_URL: z.ZodOptional<z.ZodString>;
24
24
  INKEEP_AGENTS_MANAGE_UI_URL: z.ZodOptional<z.ZodString>;
@@ -44,7 +44,7 @@ declare const envSchema: z.ZodObject<{
44
44
  declare const env: {
45
45
  NODE_ENV: "development" | "production" | "test";
46
46
  ENVIRONMENT: "development" | "production" | "test" | "pentest";
47
- LOG_LEVEL: "trace" | "debug" | "info" | "warn" | "error";
47
+ LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
48
48
  INKEEP_AGENTS_RUN_DATABASE_URL?: string | undefined;
49
49
  INKEEP_AGENTS_MANAGE_UI_URL?: string | undefined;
50
50
  GITHUB_APP_ID?: string | undefined;
@@ -1,7 +1,7 @@
1
- import * as hono1 from "hono";
1
+ import * as hono0 from "hono";
2
2
 
3
3
  //#region src/github/mcp/auth.d.ts
4
- declare const githubMcpAuth: () => hono1.MiddlewareHandler<{
4
+ declare const githubMcpAuth: () => hono0.MiddlewareHandler<{
5
5
  Variables: {
6
6
  toolId: string;
7
7
  };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types4 from "hono/types";
2
+ import * as hono_types0 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types2 from "hono/types";
2
+ import * as hono_types4 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/tokenExchange.d.ts
5
- declare const app: Hono<hono_types2.BlankEnv, hono_types2.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types0 from "hono/types";
2
+ import * as hono_types2 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/webhooks.d.ts
5
5
  interface WebhookVerificationResult {
@@ -7,6 +7,6 @@ interface WebhookVerificationResult {
7
7
  error?: string;
8
8
  }
9
9
  declare function verifyWebhookSignature(payload: string, signature: string | undefined, secret: string): WebhookVerificationResult;
10
- declare const app: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types2.BlankEnv, hono_types2.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -96,6 +96,7 @@ app.openapi(createProtectedRoute({
96
96
  "chat:write",
97
97
  "chat:write.public",
98
98
  "commands",
99
+ "files:write",
99
100
  "groups:history",
100
101
  "groups:read",
101
102
  "im:history",
@@ -109,7 +109,40 @@ declare function buildToolApprovalExpiredBlocks(params: {
109
109
  text: string;
110
110
  }[];
111
111
  }[];
112
+ declare function buildToolOutputErrorBlock(toolName: string, errorText: string): {
113
+ type: "context";
114
+ elements: {
115
+ type: "mrkdwn";
116
+ text: string;
117
+ }[];
118
+ };
119
+ declare function buildSummaryBreadcrumbBlock(labels: string[]): {
120
+ type: "context";
121
+ elements: {
122
+ type: "mrkdwn";
123
+ text: string;
124
+ }[];
125
+ };
126
+ declare function buildDataComponentBlocks(component: {
127
+ id: string;
128
+ data: Record<string, unknown>;
129
+ }): {
130
+ blocks: any[];
131
+ overflowJson?: string;
132
+ componentType?: string;
133
+ };
134
+ declare function buildDataArtifactBlocks(artifact: {
135
+ data: Record<string, unknown>;
136
+ }): {
137
+ blocks: any[];
138
+ overflowContent?: string;
139
+ artifactName?: string;
140
+ };
141
+ declare function buildCitationsBlock(citations: Array<{
142
+ title?: string;
143
+ url?: string;
144
+ }>): any[];
112
145
  declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
113
146
  declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
114
147
  //#endregion
115
- export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
148
+ export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -88,31 +88,25 @@ const ToolApprovalButtonValueSchema = z.object({
88
88
  function buildToolApprovalBlocks(params) {
89
89
  const { toolName, input, buttonValue } = params;
90
90
  const blocks = [{
91
- type: "header",
92
- text: {
93
- type: "plain_text",
94
- text: "Tool Approval Required",
95
- emoji: false
96
- }
97
- }, {
98
91
  type: "section",
99
92
  text: {
100
93
  type: "mrkdwn",
101
- text: `The agent wants to use \`${toolName}\`.`
94
+ text: `*Approval required - \`${toolName}\`*`
102
95
  }
103
96
  }];
104
97
  if (input && Object.keys(input).length > 0) {
105
- const jsonStr = JSON.stringify(input, null, 2);
106
- const truncated = jsonStr.length > 2900 ? `${jsonStr.slice(0, 2900)}…` : jsonStr;
98
+ const fields = Object.entries(input).slice(0, 10).map(([k, v]) => {
99
+ const val = typeof v === "object" ? JSON.stringify(v) : String(v ?? "");
100
+ return {
101
+ type: "mrkdwn",
102
+ text: `*${k}:*\n${val.length > 80 ? `${val.slice(0, 80)}…` : val}`
103
+ };
104
+ });
107
105
  blocks.push({
108
106
  type: "section",
109
- text: {
110
- type: "mrkdwn",
111
- text: `\`\`\`json\n${truncated}\n\`\`\``
112
- }
107
+ fields
113
108
  });
114
109
  }
115
- blocks.push({ type: "divider" });
116
110
  blocks.push({
117
111
  type: "actions",
118
112
  elements: [{
@@ -120,7 +114,7 @@ function buildToolApprovalBlocks(params) {
120
114
  text: {
121
115
  type: "plain_text",
122
116
  text: "Approve",
123
- emoji: false
117
+ emoji: true
124
118
  },
125
119
  style: "primary",
126
120
  action_id: "tool_approval_approve",
@@ -130,7 +124,7 @@ function buildToolApprovalBlocks(params) {
130
124
  text: {
131
125
  type: "plain_text",
132
126
  text: "Deny",
133
- emoji: false
127
+ emoji: true
134
128
  },
135
129
  style: "danger",
136
130
  action_id: "tool_approval_deny",
@@ -158,6 +152,154 @@ function buildToolApprovalExpiredBlocks(params) {
158
152
  }]
159
153
  }];
160
154
  }
155
+ function buildToolOutputErrorBlock(toolName, errorText) {
156
+ return {
157
+ type: "context",
158
+ elements: [{
159
+ type: "mrkdwn",
160
+ text: `⚠️ *${toolName}* · failed: ${errorText.length > 100 ? `${errorText.slice(0, 100)}…` : errorText}`
161
+ }]
162
+ };
163
+ }
164
+ function buildSummaryBreadcrumbBlock(labels) {
165
+ return {
166
+ type: "context",
167
+ elements: [{
168
+ type: "mrkdwn",
169
+ text: labels.join(" → ")
170
+ }]
171
+ };
172
+ }
173
+ function isFlatRecord(obj) {
174
+ return Object.values(obj).every((v) => v === null || [
175
+ "string",
176
+ "number",
177
+ "boolean"
178
+ ].includes(typeof v));
179
+ }
180
+ function findSourcesArray(data) {
181
+ for (const value of Object.values(data)) if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && value[0] !== null && ("url" in value[0] || "href" in value[0])) return value;
182
+ return null;
183
+ }
184
+ function buildDataComponentBlocks(component) {
185
+ const { data } = component;
186
+ const componentType = typeof data.type === "string" ? data.type : void 0;
187
+ const blocks = [{
188
+ type: "header",
189
+ text: {
190
+ type: "plain_text",
191
+ text: `📊 ${componentType || "Data Component"}`,
192
+ emoji: true
193
+ }
194
+ }];
195
+ const payload = Object.fromEntries(Object.entries(data).filter(([k]) => k !== "type"));
196
+ let overflowJson;
197
+ if (Object.keys(payload).length > 0) if (isFlatRecord(payload)) {
198
+ const fields = Object.entries(payload).slice(0, 10).map(([k, v]) => {
199
+ const val = String(v ?? "");
200
+ return {
201
+ type: "mrkdwn",
202
+ text: `*${k}*\n${val.length > 80 ? `${val.slice(0, 80)}…` : val}`
203
+ };
204
+ });
205
+ blocks.push({
206
+ type: "section",
207
+ fields
208
+ });
209
+ } else {
210
+ const jsonStr = JSON.stringify(payload, null, 2);
211
+ if (jsonStr.length > 2900) overflowJson = jsonStr;
212
+ else blocks.push({
213
+ type: "section",
214
+ text: {
215
+ type: "mrkdwn",
216
+ text: `\`\`\`json\n${jsonStr}\n\`\`\``
217
+ }
218
+ });
219
+ }
220
+ if (componentType) blocks.push({
221
+ type: "context",
222
+ elements: [{
223
+ type: "mrkdwn",
224
+ text: `data component · type: ${componentType}`
225
+ }]
226
+ });
227
+ return {
228
+ blocks,
229
+ overflowJson,
230
+ componentType
231
+ };
232
+ }
233
+ function buildDataArtifactBlocks(artifact) {
234
+ const { data } = artifact;
235
+ const sourcesArray = findSourcesArray(data);
236
+ if (sourcesArray && sourcesArray.length > 0) {
237
+ const MAX_SOURCES = 10;
238
+ const lines = sourcesArray.slice(0, MAX_SOURCES).map((s) => {
239
+ const url = s.url || s.href;
240
+ const title = s.title || s.name || url;
241
+ return url ? `• <${url}|${title}>` : null;
242
+ }).filter((l) => l !== null);
243
+ if (lines.length > 0) {
244
+ const suffix = sourcesArray.length > MAX_SOURCES ? `\n_and ${sourcesArray.length - MAX_SOURCES} more_` : "";
245
+ return { blocks: [{
246
+ type: "section",
247
+ text: {
248
+ type: "mrkdwn",
249
+ text: `📚 *Sources*\n${lines.join("\n")}${suffix}`
250
+ }
251
+ }] };
252
+ }
253
+ }
254
+ const artifactType = typeof data.type === "string" ? data.type : void 0;
255
+ const name = typeof data.name === "string" && data.name ? data.name : artifactType || "Artifact";
256
+ const blocks = [{
257
+ type: "header",
258
+ text: {
259
+ type: "plain_text",
260
+ text: `📄 ${name}`,
261
+ emoji: true
262
+ }
263
+ }];
264
+ if (artifactType) blocks.push({
265
+ type: "context",
266
+ elements: [{
267
+ type: "mrkdwn",
268
+ text: `type: ${artifactType}`
269
+ }]
270
+ });
271
+ let overflowContent;
272
+ if (typeof data.description === "string" && data.description) if (data.description.length > 2900) overflowContent = data.description;
273
+ else blocks.push({
274
+ type: "section",
275
+ text: {
276
+ type: "mrkdwn",
277
+ text: data.description
278
+ }
279
+ });
280
+ return {
281
+ blocks,
282
+ overflowContent,
283
+ artifactName: name
284
+ };
285
+ }
286
+ function buildCitationsBlock(citations) {
287
+ const MAX_CITATIONS = 10;
288
+ const lines = citations.slice(0, MAX_CITATIONS).map((c) => {
289
+ const url = c.url;
290
+ const title = c.title || url;
291
+ return url ? `• <${url}|${title}>` : null;
292
+ }).filter((l) => l !== null);
293
+ if (lines.length === 0) return [];
294
+ const suffix = citations.length > MAX_CITATIONS ? `\n_and ${citations.length - MAX_CITATIONS} more_` : "";
295
+ return [{
296
+ type: "section",
297
+ text: {
298
+ type: "mrkdwn",
299
+ text: `📚 *Sources*\n${lines.join("\n")}${suffix}`
300
+ }
301
+ }];
302
+ }
161
303
  function createJwtLinkMessage(linkUrl, expiresInMinutes) {
162
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();
163
305
  }
@@ -166,4 +308,4 @@ function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
166
308
  }
167
309
 
168
310
  //#endregion
169
- export { ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
311
+ export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -1,9 +1,9 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
- import { buildToolApprovalBlocks, buildToolApprovalExpiredBlocks, createContextBlock } from "../blocks/index.js";
3
+ import { buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createContextBlock } from "../blocks/index.js";
4
4
  import { SlackErrorType, classifyError, extractApiErrorMessage, getUserFriendlyErrorMessage } from "./utils.js";
5
5
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
6
- import { getInProcessFetch } from "@inkeep/agents-core";
6
+ import { getInProcessFetch, retryWithBackoff } from "@inkeep/agents-core";
7
7
 
8
8
  //#region src/slack/services/events/streaming.ts
9
9
  /**
@@ -192,6 +192,14 @@ async function streamAgentResponse(params) {
192
192
  thread_ts: threadTs
193
193
  });
194
194
  const pendingApprovalMessages = [];
195
+ const toolCallIdToName = /* @__PURE__ */ new Map();
196
+ const toolCallIdToInput = /* @__PURE__ */ new Map();
197
+ const toolErrors = [];
198
+ const citations = [];
199
+ const summaryLabels = [];
200
+ let richMessageCount = 0;
201
+ let richMessageCapWarned = false;
202
+ const MAX_RICH_MESSAGES = 20;
195
203
  try {
196
204
  let agentCompleted = false;
197
205
  while (true) {
@@ -214,9 +222,9 @@ async function streamAgentResponse(params) {
214
222
  continue;
215
223
  }
216
224
  if (data.type === "tool-approval-request" && conversationId) {
217
- const toolName = data.toolName || "Tool";
218
225
  const toolCallId = data.toolCallId;
219
- const input = data.input;
226
+ const toolName = toolCallIdToName.get(toolCallId) || "Tool";
227
+ const input = toolCallIdToInput.get(toolCallId);
220
228
  const buttonValue = {
221
229
  toolCallId,
222
230
  conversationId,
@@ -245,11 +253,118 @@ async function streamAgentResponse(params) {
245
253
  });
246
254
  if (approvalPost?.ts) pendingApprovalMessages.push({
247
255
  messageTs: approvalPost.ts,
248
- toolName
256
+ toolName,
257
+ toolCallId
249
258
  });
250
259
  clearTimeout(timeoutId);
251
260
  continue;
252
261
  }
262
+ if (data.type === "tool-input-available" && data.toolCallId && data.toolName) {
263
+ toolCallIdToName.set(String(data.toolCallId), String(data.toolName));
264
+ if (data.input && typeof data.input === "object") toolCallIdToInput.set(String(data.toolCallId), data.input);
265
+ continue;
266
+ }
267
+ if (data.type === "tool-output-denied" && data.toolCallId) {
268
+ const idx = pendingApprovalMessages.findIndex((m) => m.toolCallId === data.toolCallId);
269
+ if (idx !== -1) pendingApprovalMessages.splice(idx, 1);
270
+ continue;
271
+ }
272
+ if (data.type === "tool-output-error" && data.toolCallId) {
273
+ const toolName = toolCallIdToName.get(String(data.toolCallId)) || "Tool";
274
+ toolErrors.push({
275
+ toolName,
276
+ errorText: String(data.errorText || "Unknown error")
277
+ });
278
+ continue;
279
+ }
280
+ if (data.type === "data-component" && data.data && typeof data.data === "object") {
281
+ if (richMessageCount < MAX_RICH_MESSAGES) {
282
+ const { blocks, overflowJson, componentType } = buildDataComponentBlocks({
283
+ id: String(data.id || ""),
284
+ data: data.data
285
+ });
286
+ if (overflowJson) {
287
+ const label = componentType || "data-component";
288
+ await retryWithBackoff(() => slackClient.files.uploadV2({
289
+ channel_id: channel,
290
+ thread_ts: threadTs,
291
+ filename: `${label}.json`,
292
+ content: overflowJson,
293
+ initial_comment: `📊 ${label}`
294
+ }), { label: "slack-file-upload" }).catch((e) => logger.warn({
295
+ error: e,
296
+ channel,
297
+ threadTs,
298
+ agentId,
299
+ componentType: label
300
+ }, "Failed to upload data component file"));
301
+ } else await slackClient.chat.postMessage({
302
+ channel,
303
+ thread_ts: threadTs,
304
+ text: "📊 Data component",
305
+ blocks
306
+ }).catch((e) => logger.warn({ error: e }, "Failed to post data component"));
307
+ richMessageCount++;
308
+ } else if (!richMessageCapWarned) {
309
+ logger.warn({
310
+ channel,
311
+ threadTs,
312
+ agentId,
313
+ eventType: "data-component",
314
+ MAX_RICH_MESSAGES
315
+ }, "MAX_RICH_MESSAGES cap reached — additional rich content will be dropped");
316
+ richMessageCapWarned = true;
317
+ }
318
+ continue;
319
+ }
320
+ if (data.type === "data-artifact" && data.data && typeof data.data === "object") {
321
+ const artifactData = data.data;
322
+ if (typeof artifactData.type === "string" && artifactData.type.toLowerCase() === "citation") {
323
+ const summary = artifactData.artifactSummary;
324
+ if (summary?.url && !citations.some((c) => c.url === summary.url)) citations.push({
325
+ title: summary.title,
326
+ url: summary.url
327
+ });
328
+ } else if (richMessageCount < MAX_RICH_MESSAGES) {
329
+ const { blocks, overflowContent, artifactName } = buildDataArtifactBlocks({ data: artifactData });
330
+ if (overflowContent) {
331
+ const label = artifactName || "artifact";
332
+ await retryWithBackoff(() => slackClient.files.uploadV2({
333
+ channel_id: channel,
334
+ thread_ts: threadTs,
335
+ filename: `${label}.md`,
336
+ content: overflowContent,
337
+ initial_comment: `📄 ${label}`
338
+ }), { label: "slack-file-upload" }).catch((e) => logger.warn({
339
+ error: e,
340
+ channel,
341
+ threadTs,
342
+ agentId,
343
+ artifactName: label
344
+ }, "Failed to upload artifact file"));
345
+ } else await slackClient.chat.postMessage({
346
+ channel,
347
+ thread_ts: threadTs,
348
+ text: "📄 Data",
349
+ blocks
350
+ }).catch((e) => logger.warn({ error: e }, "Failed to post data artifact"));
351
+ richMessageCount++;
352
+ } else if (!richMessageCapWarned) {
353
+ logger.warn({
354
+ channel,
355
+ threadTs,
356
+ agentId,
357
+ eventType: "data-artifact",
358
+ MAX_RICH_MESSAGES
359
+ }, "MAX_RICH_MESSAGES cap reached — additional rich content will be dropped");
360
+ richMessageCapWarned = true;
361
+ }
362
+ continue;
363
+ }
364
+ if (data.type === "data-summary" && data.data?.label) {
365
+ summaryLabels.push(String(data.data.label));
366
+ continue;
367
+ }
253
368
  if (data.type === "text-start" || data.type === "text-end") continue;
254
369
  if (data.type === "text-delta" && data.delta) {
255
370
  fullText += data.delta;
@@ -267,9 +382,16 @@ async function streamAgentResponse(params) {
267
382
  if (agentCompleted) break;
268
383
  }
269
384
  clearTimeout(timeoutId);
270
- const contextBlock = createContextBlock({ agentName });
385
+ const stopBlocks = [];
386
+ for (const { toolName, errorText } of toolErrors) stopBlocks.push(buildToolOutputErrorBlock(toolName, errorText));
387
+ if (summaryLabels.length > 0) stopBlocks.push(buildSummaryBreadcrumbBlock(summaryLabels));
388
+ if (citations.length > 0) {
389
+ const citationBlocks = buildCitationsBlock(citations);
390
+ stopBlocks.push(...citationBlocks);
391
+ }
392
+ stopBlocks.push(createContextBlock({ agentName }));
271
393
  try {
272
- await withTimeout(streamer.stop({ blocks: [contextBlock] }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.stop");
394
+ await withTimeout(streamer.stop({ blocks: stopBlocks.slice(0, 50) }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.stop");
273
395
  } catch (stopError) {
274
396
  span.setAttribute(SLACK_SPAN_KEYS.STREAM_FINALIZATION_FAILED, true);
275
397
  logger.warn({
@@ -292,7 +414,10 @@ async function streamAgentResponse(params) {
292
414
  threadTs,
293
415
  responseLength: fullText.length,
294
416
  agentId,
295
- conversationId
417
+ conversationId,
418
+ toolErrorCount: toolErrors.length,
419
+ citationCount: citations.length,
420
+ richMessageCount
296
421
  }, "Streaming completed");
297
422
  span.end();
298
423
  return { success: true };
@@ -8,12 +8,12 @@ import { AgentOption } from "../modals.js";
8
8
  * Called on every @mention and /inkeep command — caching avoids redundant DB queries.
9
9
  */
10
10
  declare function findCachedUserMapping(tenantId: string, slackUserId: string, teamId: string, clientId?: string): Promise<{
11
+ id: string;
11
12
  createdAt: string;
12
13
  updatedAt: string;
13
- id: 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;
@@ -1,5 +1,5 @@
1
1
  import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
2
- import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
2
+ import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, 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";
@@ -13,4 +13,4 @@ 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, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -1,6 +1,6 @@
1
1
  import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
2
2
  import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
3
- import { ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
+ import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
4
4
  import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
5
5
  import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
6
6
  import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.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, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -2,7 +2,7 @@ import { env } from "../../env.js";
2
2
  import { getLogger } from "../../logger.js";
3
3
  import runDbClient_default from "../../db/runDbClient.js";
4
4
  import { getDevDefaultAgent, isSlackDevMode, loadSlackDevConfig, saveSlackDevConfig } from "./dev-config.js";
5
- import { findWorkAppSlackWorkspaceBySlackTeamId } from "@inkeep/agents-core";
5
+ import { findWorkAppSlackWorkspaceBySlackTeamId, retryWithBackoff } from "@inkeep/agents-core";
6
6
  import { Nango } from "@nangohq/node";
7
7
 
8
8
  //#region src/slack/services/nango.ts
@@ -30,28 +30,6 @@ const workspaceConnectionCache = /* @__PURE__ */ new Map();
30
30
  const CACHE_TTL_MS = 6e4;
31
31
  const logger = getLogger("slack-nango");
32
32
  /**
33
- * Retry a function with exponential backoff for transient failures.
34
- * Retries on AbortError (timeout) and 5xx HTTP errors.
35
- */
36
- async function retryWithBackoff(fn, maxAttempts = 3) {
37
- for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
38
- return await fn();
39
- } catch (error) {
40
- const isTimeout = error.name === "AbortError";
41
- const isServerError = typeof error.status === "number" && error.status >= 500;
42
- if (!(isTimeout || isServerError) || attempt === maxAttempts) throw error;
43
- const delay = Math.min(500 * 2 ** (attempt - 1), 2e3) + Math.random() * 100;
44
- logger.warn({
45
- attempt,
46
- maxAttempts,
47
- isTimeout,
48
- delay: Math.round(delay)
49
- }, "Retrying Nango API call after transient failure");
50
- await new Promise((resolve) => setTimeout(resolve, delay));
51
- }
52
- throw new Error("Unreachable");
53
- }
54
- /**
55
33
  * Evict expired entries from workspace cache to bound memory.
56
34
  */
57
35
  function evictWorkspaceCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/agents-work-apps",
3
- "version": "0.0.0-dev-20260223213559",
3
+ "version": "0.0.0-dev-20260224023234",
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.0.0-dev-20260223213559"
36
+ "@inkeep/agents-core": "0.0.0-dev-20260224023234"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@hono/zod-openapi": "^1.1.5",