@inkeep/agents-work-apps 0.51.0 → 0.52.0

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.
@@ -1,7 +1,7 @@
1
- import * as hono0 from "hono";
1
+ import * as hono1 from "hono";
2
2
 
3
3
  //#region src/github/mcp/auth.d.ts
4
- declare const githubMcpAuth: () => hono0.MiddlewareHandler<{
4
+ declare const githubMcpAuth: () => hono1.MiddlewareHandler<{
5
5
  Variables: {
6
6
  toolId: string;
7
7
  };
@@ -1,11 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types3 from "hono/types";
2
+ import * as hono_types9 from "hono/types";
3
3
 
4
4
  //#region src/github/mcp/index.d.ts
5
5
  declare const app: Hono<{
6
6
  Variables: {
7
7
  toolId: string;
8
8
  };
9
- }, hono_types3.BlankSchema, "/">;
9
+ }, hono_types9.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import runDbClient_default from "../../db/runDbClient.js";
2
2
  import { githubMcpAuth } from "./auth.js";
3
3
  import { ReactionContentSchema } from "./schemas.js";
4
- import { commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchComments, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, visualizeUpdateOperations } from "./utils.js";
4
+ import { commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchBranchChangedFiles, fetchComments, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, visualizeUpdateOperations } from "./utils.js";
5
5
  import { z } from "@hono/zod-openapi";
6
6
  import { getMcpToolRepositoryAccessWithDetails } from "@inkeep/agents-core";
7
7
  import { Hono } from "hono";
@@ -267,6 +267,65 @@ const getServer = async (toolId) => {
267
267
  };
268
268
  }
269
269
  });
270
+ server.tool("get-changed-files-for-branch", `Get the list of files changed on a branch compared to a base ref, without requiring a pull request. Returns file paths, status, additions/deletions, and optionally patches and full file contents. ${getAvailableRepositoryString(repositoryAccess)}`, {
271
+ owner: z.string().describe("Repository owner name"),
272
+ repo: z.string().describe("Repository name"),
273
+ head: z.string().describe("The branch or ref to check for changes (e.g. \"feat/my-feature\")"),
274
+ base: z.string().optional().describe("The base branch or ref to compare against (defaults to the repository default branch)"),
275
+ file_paths: z.array(z.string()).optional().describe("Optional list of file path glob patterns to filter results"),
276
+ include_contents: z.boolean().default(false).describe("Whether to include full file contents for each changed file"),
277
+ include_patch: z.boolean().default(false).describe("Whether to include the patch/diff text for each changed file")
278
+ }, async ({ owner, repo, head, base, file_paths, include_contents, include_patch }) => {
279
+ try {
280
+ let githubClient;
281
+ try {
282
+ githubClient = getGitHubClientFromRepo(owner, repo, installationIdMap);
283
+ } catch (error) {
284
+ return {
285
+ content: [{
286
+ type: "text",
287
+ text: `Error accessing GitHub: ${error instanceof Error ? error.message : "Unknown error"}`
288
+ }],
289
+ isError: true
290
+ };
291
+ }
292
+ const baseRef = base ?? (await githubClient.rest.repos.get({
293
+ owner,
294
+ repo
295
+ })).data.default_branch;
296
+ const files = await fetchBranchChangedFiles(githubClient, owner, repo, baseRef, head, {
297
+ pathFilters: file_paths ?? [],
298
+ includeContents: include_contents,
299
+ includePatch: include_patch
300
+ });
301
+ if (files.length === 0) return { content: [{
302
+ type: "text",
303
+ text: `No changed files found between ${baseRef} and ${head} in ${owner}/${repo}.${file_paths?.length ? `\n\nFilters applied: ${file_paths.join(", ")}` : ""}`
304
+ }] };
305
+ const output = await formatFileDiff(0, files, include_contents);
306
+ return { content: [{
307
+ type: "text",
308
+ text: `## Changed files: ${baseRef}...${head} in ${owner}/${repo}\n\nFound ${files.length} changed file(s).\n\n` + output
309
+ }] };
310
+ } catch (error) {
311
+ if (error instanceof Error && "status" in error) {
312
+ if (error.status === 404) return {
313
+ content: [{
314
+ type: "text",
315
+ text: `Repository ${owner}/${repo} not found, or one of the refs ("${base ?? "default"}", "${head}") does not exist.`
316
+ }],
317
+ isError: true
318
+ };
319
+ }
320
+ return {
321
+ content: [{
322
+ type: "text",
323
+ text: `Error fetching changed files: ${error instanceof Error ? error.message : "Unknown error"}`
324
+ }],
325
+ isError: true
326
+ };
327
+ }
328
+ });
270
329
  server.tool("create-branch", `Create a new branch in a repository. ${getAvailableRepositoryString(repositoryAccess)}`, {
271
330
  owner: z.string().describe("Repository owner name"),
272
331
  repo: z.string().describe("Repository name"),
@@ -76,8 +76,8 @@ declare const ChangedFileSchema: z.ZodObject<{
76
76
  path: z.ZodString;
77
77
  status: z.ZodEnum<{
78
78
  added: "added";
79
- modified: "modified";
80
79
  removed: "removed";
80
+ modified: "modified";
81
81
  renamed: "renamed";
82
82
  copied: "copied";
83
83
  changed: "changed";
@@ -152,6 +152,15 @@ declare function fetchPrFiles(octokit: Octokit, owner: string, repo: string, prN
152
152
  * Get file-based diffs with all commit messages that impacted each file.
153
153
  */
154
154
  declare function fetchPrFileDiffs(octokit: Octokit, owner: string, repo: string, prNumber: number): Promise<ChangedFile[]>;
155
+ /**
156
+ * Fetch files changed on a branch compared to a base ref, without requiring a PR.
157
+ * Uses the GitHub Compare API (`repos.compareCommitsWithBasehead`).
158
+ */
159
+ declare function fetchBranchChangedFiles(octokit: Octokit, owner: string, repo: string, base: string, head: string, options?: {
160
+ pathFilters?: string[];
161
+ includeContents?: boolean;
162
+ includePatch?: boolean;
163
+ }): Promise<ChangedFile[]>;
155
164
  /**
156
165
  * Fetch all PR comments (both issue comments and review comments)
157
166
  */
@@ -247,4 +256,4 @@ declare function listIssueCommentReactions(octokit: Octokit, owner: string, repo
247
256
  declare function listPullRequestReviewCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
248
257
  declare function formatFileDiff(pullRequestNumber: number, files: ChangedFile[], includeContents?: boolean): Promise<string>;
249
258
  //#endregion
250
- export { CommitData, LLMUpdateOperation, PullCommit, ReactionDetail, applyOperation, applyOperations, commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, validateLineNumbers, visualizeUpdateOperations };
259
+ export { CommitData, LLMUpdateOperation, PullCommit, ReactionDetail, applyOperation, applyOperations, commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchBranchChangedFiles, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, validateLineNumbers, visualizeUpdateOperations };
@@ -265,6 +265,92 @@ async function fetchPrFileDiffs(octokit, owner, repo, prNumber) {
265
265
  }
266
266
  }
267
267
  /**
268
+ * Fetch files changed on a branch compared to a base ref, without requiring a PR.
269
+ * Uses the GitHub Compare API (`repos.compareCommitsWithBasehead`).
270
+ */
271
+ async function fetchBranchChangedFiles(octokit, owner, repo, base, head, options = {}) {
272
+ const { pathFilters = [], includeContents = false, includePatch = false } = options;
273
+ logger.info({
274
+ owner,
275
+ repo,
276
+ base,
277
+ head,
278
+ pathFilters,
279
+ includeContents,
280
+ includePatch
281
+ }, `Fetching changed files between ${base}...${head}`);
282
+ const totalCommits = (await octokit.rest.repos.compareCommitsWithBasehead({
283
+ owner,
284
+ repo,
285
+ basehead: `${base}...${head}`,
286
+ per_page: 1
287
+ })).data.total_commits;
288
+ const collectedFiles = [];
289
+ let page = 1;
290
+ const perPage = 100;
291
+ while (true) {
292
+ const files = (await octokit.rest.repos.compareCommitsWithBasehead({
293
+ owner,
294
+ repo,
295
+ basehead: `${base}...${head}`,
296
+ per_page: perPage,
297
+ page
298
+ })).data.files ?? [];
299
+ if (files.length === 0) break;
300
+ for (const file of files) {
301
+ if (pathFilters.length > 0 && !pathFilters.some((filter) => minimatch(file.filename, filter))) continue;
302
+ collectedFiles.push({
303
+ commit_messages: [],
304
+ path: file.filename,
305
+ status: file.status,
306
+ additions: file.additions,
307
+ deletions: file.deletions,
308
+ patch: includePatch ? file.patch : void 0,
309
+ previousPath: file.previous_filename
310
+ });
311
+ }
312
+ if (files.length < perPage) break;
313
+ page++;
314
+ }
315
+ if (includeContents) {
316
+ const BATCH_SIZE = 10;
317
+ const filesToFetch = collectedFiles.filter((f) => f.status !== "removed");
318
+ for (let i = 0; i < filesToFetch.length; i += BATCH_SIZE) {
319
+ const batch = filesToFetch.slice(i, i + BATCH_SIZE);
320
+ await Promise.all(batch.map(async (changedFile) => {
321
+ try {
322
+ const { data: content } = await octokit.rest.repos.getContent({
323
+ owner,
324
+ repo,
325
+ path: changedFile.path,
326
+ ref: head
327
+ });
328
+ if ("content" in content && content.encoding === "base64") changedFile.contents = Buffer.from(content.content, "base64").toString("utf-8");
329
+ } catch (error) {
330
+ logger.warn({
331
+ owner,
332
+ repo,
333
+ base,
334
+ head,
335
+ file: changedFile.path
336
+ }, `Failed to fetch contents for ${changedFile.path}: ${error}`);
337
+ }
338
+ }));
339
+ }
340
+ }
341
+ logger.info({
342
+ owner,
343
+ repo,
344
+ base,
345
+ head,
346
+ totalCommits,
347
+ pathFilters,
348
+ includeContents,
349
+ fileCount: collectedFiles.length
350
+ }, `Found ${collectedFiles.length} changed files between ${base}...${head} (${totalCommits} commits)`);
351
+ return collectedFiles;
352
+ }
353
+ /**
268
354
  * Fetch all PR comments (both issue comments and review comments)
269
355
  */
270
356
  async function fetchComments(octokit, owner, repo, prNumber) {
@@ -694,4 +780,4 @@ async function formatFileDiff(pullRequestNumber, files, includeContents = false)
694
780
  }
695
781
 
696
782
  //#endregion
697
- export { applyOperation, applyOperations, commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, validateLineNumbers, visualizeUpdateOperations };
783
+ export { applyOperation, applyOperations, commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchBranchChangedFiles, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, listIssueCommentReactions, listPullRequestReviewCommentReactions, validateLineNumbers, visualizeUpdateOperations };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types4 from "hono/types";
2
+ import * as hono_types3 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_types3.BlankEnv, hono_types3.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types6 from "hono/types";
2
+ import * as hono_types5 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/tokenExchange.d.ts
5
- declare const app: Hono<hono_types6.BlankEnv, hono_types6.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types8 from "hono/types";
2
+ import * as hono_types7 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_types8.BlankEnv, hono_types8.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types7.BlankEnv, hono_types7.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -70,7 +70,7 @@ app.post("/events", async (c) => {
70
70
  retryReason
71
71
  }, "Acknowledging Slack retry without re-processing");
72
72
  span.end();
73
- return c.json({ ok: true });
73
+ return c.body(null, 200);
74
74
  });
75
75
  const waitUntil = await getWaitUntil();
76
76
  return tracer.startActiveSpan(SLACK_SPAN_NAMES.WEBHOOK, async (span) => {
@@ -128,7 +128,7 @@ app.post("/events", async (c) => {
128
128
  return c.json(result.response);
129
129
  }
130
130
  span.end();
131
- return c.json({ ok: true });
131
+ return c.body(null, 200);
132
132
  } catch (error) {
133
133
  outcome = "error";
134
134
  span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
@@ -6,7 +6,7 @@ import { getSlackClient, getSlackTeamInfo, getSlackUserInfo } from "../services/
6
6
  import { getBotTokenForTeam, setBotTokenForTeam } from "../services/workspace-tokens.js";
7
7
  import "../services/index.js";
8
8
  import { OpenAPIHono, z } from "@hono/zod-openapi";
9
- import { createWorkAppSlackWorkspace } from "@inkeep/agents-core";
9
+ import { createWorkAppSlackWorkspace, isUniqueConstraintError } from "@inkeep/agents-core";
10
10
  import * as crypto$1 from "node:crypto";
11
11
  import { createProtectedRoute, noAuth } from "@inkeep/agents-core/middleware";
12
12
 
@@ -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",
@@ -252,8 +253,7 @@ app.openapi(createProtectedRoute({
252
253
  tenantId
253
254
  }, "Persisted workspace installation to database");
254
255
  } catch (dbError) {
255
- const dbErrorMessage = dbError instanceof Error ? dbError.message : String(dbError);
256
- if (dbErrorMessage.includes("duplicate key") || dbErrorMessage.includes("unique constraint")) logger.info({
256
+ if (isUniqueConstraintError(dbError)) logger.info({
257
257
  teamId: workspaceData.teamId,
258
258
  tenantId
259
259
  }, "Workspace already exists in database");
@@ -261,7 +261,6 @@ app.openapi(createProtectedRoute({
261
261
  const pgCode = dbError && typeof dbError === "object" && "code" in dbError ? dbError.code : void 0;
262
262
  logger.error({
263
263
  err: dbError,
264
- dbErrorMessage,
265
264
  pgCode,
266
265
  teamId: workspaceData.teamId,
267
266
  tenantId,
@@ -3,7 +3,7 @@ import runDbClient_default from "../../db/runDbClient.js";
3
3
  import { createConnectSession } from "../services/nango.js";
4
4
  import "../services/index.js";
5
5
  import { OpenAPIHono, z } from "@hono/zod-openapi";
6
- import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, verifySlackLinkToken } from "@inkeep/agents-core";
6
+ import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, isUniqueConstraintError, verifySlackLinkToken } from "@inkeep/agents-core";
7
7
  import { createProtectedRoute, inheritedWorkAppsAuth } from "@inkeep/agents-core/middleware";
8
8
 
9
9
  //#region src/slack/routes/users.ts
@@ -165,7 +165,7 @@ app.openapi(createProtectedRoute({
165
165
  tenantId
166
166
  });
167
167
  } catch (error) {
168
- if (error instanceof Error && (error.message.includes("duplicate key") || error.message.includes("unique constraint")) || typeof error === "object" && error !== null && "cause" in error && typeof error.cause === "object" && error.cause?.code === "23505") {
168
+ if (isUniqueConstraintError(error)) {
169
169
  logger.info({ userId: body.userId }, "Concurrent link resolved — mapping already exists");
170
170
  return c.json({ success: true });
171
171
  }
@@ -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,13 @@ async function streamAgentResponse(params) {
192
192
  thread_ts: threadTs
193
193
  });
194
194
  const pendingApprovalMessages = [];
195
+ const toolCallIdToName = /* @__PURE__ */ new Map();
196
+ const toolErrors = [];
197
+ const citations = [];
198
+ const summaryLabels = [];
199
+ let richMessageCount = 0;
200
+ let richMessageCapWarned = false;
201
+ const MAX_RICH_MESSAGES = 20;
195
202
  try {
196
203
  let agentCompleted = false;
197
204
  while (true) {
@@ -245,11 +252,117 @@ async function streamAgentResponse(params) {
245
252
  });
246
253
  if (approvalPost?.ts) pendingApprovalMessages.push({
247
254
  messageTs: approvalPost.ts,
248
- toolName
255
+ toolName,
256
+ toolCallId
249
257
  });
250
258
  clearTimeout(timeoutId);
251
259
  continue;
252
260
  }
261
+ if (data.type === "tool-input-available" && data.toolCallId && data.toolName) {
262
+ toolCallIdToName.set(String(data.toolCallId), String(data.toolName));
263
+ continue;
264
+ }
265
+ if (data.type === "tool-output-denied" && data.toolCallId) {
266
+ const idx = pendingApprovalMessages.findIndex((m) => m.toolCallId === data.toolCallId);
267
+ if (idx !== -1) pendingApprovalMessages.splice(idx, 1);
268
+ continue;
269
+ }
270
+ if (data.type === "tool-output-error" && data.toolCallId) {
271
+ const toolName = toolCallIdToName.get(String(data.toolCallId)) || "Tool";
272
+ toolErrors.push({
273
+ toolName,
274
+ errorText: String(data.errorText || "Unknown error")
275
+ });
276
+ continue;
277
+ }
278
+ if (data.type === "data-component" && data.data && typeof data.data === "object") {
279
+ if (richMessageCount < MAX_RICH_MESSAGES) {
280
+ const { blocks, overflowJson, componentType } = buildDataComponentBlocks({
281
+ id: String(data.id || ""),
282
+ data: data.data
283
+ });
284
+ if (overflowJson) {
285
+ const label = componentType || "data-component";
286
+ await retryWithBackoff(() => slackClient.files.uploadV2({
287
+ channel_id: channel,
288
+ thread_ts: threadTs,
289
+ filename: `${label}.json`,
290
+ content: overflowJson,
291
+ initial_comment: `📊 ${label}`
292
+ }), { label: "slack-file-upload" }).catch((e) => logger.warn({
293
+ error: e,
294
+ channel,
295
+ threadTs,
296
+ agentId,
297
+ componentType: label
298
+ }, "Failed to upload data component file"));
299
+ } else await slackClient.chat.postMessage({
300
+ channel,
301
+ thread_ts: threadTs,
302
+ text: "📊 Data component",
303
+ blocks
304
+ }).catch((e) => logger.warn({ error: e }, "Failed to post data component"));
305
+ richMessageCount++;
306
+ } else if (!richMessageCapWarned) {
307
+ logger.warn({
308
+ channel,
309
+ threadTs,
310
+ agentId,
311
+ eventType: "data-component",
312
+ MAX_RICH_MESSAGES
313
+ }, "MAX_RICH_MESSAGES cap reached — additional rich content will be dropped");
314
+ richMessageCapWarned = true;
315
+ }
316
+ continue;
317
+ }
318
+ if (data.type === "data-artifact" && data.data && typeof data.data === "object") {
319
+ const artifactData = data.data;
320
+ if (typeof artifactData.type === "string" && artifactData.type.toLowerCase() === "citation") {
321
+ const summary = artifactData.artifactSummary;
322
+ if (summary?.url && !citations.some((c) => c.url === summary.url)) citations.push({
323
+ title: summary.title,
324
+ url: summary.url
325
+ });
326
+ } else if (richMessageCount < MAX_RICH_MESSAGES) {
327
+ const { blocks, overflowContent, artifactName } = buildDataArtifactBlocks({ data: artifactData });
328
+ if (overflowContent) {
329
+ const label = artifactName || "artifact";
330
+ await retryWithBackoff(() => slackClient.files.uploadV2({
331
+ channel_id: channel,
332
+ thread_ts: threadTs,
333
+ filename: `${label}.md`,
334
+ content: overflowContent,
335
+ initial_comment: `📄 ${label}`
336
+ }), { label: "slack-file-upload" }).catch((e) => logger.warn({
337
+ error: e,
338
+ channel,
339
+ threadTs,
340
+ agentId,
341
+ artifactName: label
342
+ }, "Failed to upload artifact file"));
343
+ } else await slackClient.chat.postMessage({
344
+ channel,
345
+ thread_ts: threadTs,
346
+ text: "📄 Data",
347
+ blocks
348
+ }).catch((e) => logger.warn({ error: e }, "Failed to post data artifact"));
349
+ richMessageCount++;
350
+ } else if (!richMessageCapWarned) {
351
+ logger.warn({
352
+ channel,
353
+ threadTs,
354
+ agentId,
355
+ eventType: "data-artifact",
356
+ MAX_RICH_MESSAGES
357
+ }, "MAX_RICH_MESSAGES cap reached — additional rich content will be dropped");
358
+ richMessageCapWarned = true;
359
+ }
360
+ continue;
361
+ }
362
+ if (data.type === "data-summary" && data.data?.label) {
363
+ summaryLabels.push(String(data.data.label));
364
+ continue;
365
+ }
253
366
  if (data.type === "text-start" || data.type === "text-end") continue;
254
367
  if (data.type === "text-delta" && data.delta) {
255
368
  fullText += data.delta;
@@ -267,9 +380,12 @@ async function streamAgentResponse(params) {
267
380
  if (agentCompleted) break;
268
381
  }
269
382
  clearTimeout(timeoutId);
270
- const contextBlock = createContextBlock({ agentName });
383
+ const stopBlocks = [];
384
+ for (const { toolName, errorText } of toolErrors) stopBlocks.push(buildToolOutputErrorBlock(toolName, errorText));
385
+ if (summaryLabels.length > 0) stopBlocks.push(buildSummaryBreadcrumbBlock(summaryLabels));
386
+ stopBlocks.push(createContextBlock({ agentName }));
271
387
  try {
272
- await withTimeout(streamer.stop({ blocks: [contextBlock] }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.stop");
388
+ await withTimeout(streamer.stop({ blocks: stopBlocks.slice(0, 50) }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.stop");
273
389
  } catch (stopError) {
274
390
  span.setAttribute(SLACK_SPAN_KEYS.STREAM_FINALIZATION_FAILED, true);
275
391
  logger.warn({
@@ -279,6 +395,15 @@ async function streamAgentResponse(params) {
279
395
  responseLength: fullText.length
280
396
  }, "Failed to finalize chatStream — content was already delivered");
281
397
  }
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
+ }
282
407
  if (thinkingMessageTs) try {
283
408
  await slackClient.chat.delete({
284
409
  channel,
@@ -292,7 +417,10 @@ async function streamAgentResponse(params) {
292
417
  threadTs,
293
418
  responseLength: fullText.length,
294
419
  agentId,
295
- conversationId
420
+ conversationId,
421
+ toolErrorCount: toolErrors.length,
422
+ citationCount: citations.length,
423
+ richMessageCount
296
424
  }, "Streaming completed");
297
425
  span.end();
298
426
  return { success: true };
@@ -8,10 +8,10 @@ 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
+ slackUserId: string;
12
12
  createdAt: string;
13
13
  updatedAt: string;
14
- slackUserId: string;
14
+ id: string;
15
15
  tenantId: string;
16
16
  clientId: string;
17
17
  slackTeamId: 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.51.0",
3
+ "version": "0.52.0",
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.51.0"
36
+ "@inkeep/agents-core": "0.52.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@hono/zod-openapi": "^1.1.5",