@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.
- package/dist/github/mcp/auth.d.ts +2 -2
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/mcp/index.js +60 -1
- package/dist/github/mcp/schemas.d.ts +1 -1
- package/dist/github/mcp/utils.d.ts +10 -1
- package/dist/github/mcp/utils.js +87 -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/routes/events.js +2 -2
- package/dist/slack/routes/oauth.js +3 -4
- package/dist/slack/routes/users.js +2 -2
- package/dist/slack/services/blocks/index.d.ts +34 -1
- package/dist/slack/services/blocks/index.js +160 -18
- package/dist/slack/services/events/streaming.js +134 -6
- package/dist/slack/services/events/utils.d.ts +2 -2
- package/dist/slack/services/index.d.ts +2 -2
- package/dist/slack/services/index.js +2 -2
- package/dist/slack/services/nango.js +1 -23
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as hono1 from "hono";
|
|
2
2
|
|
|
3
3
|
//#region src/github/mcp/auth.d.ts
|
|
4
|
-
declare const githubMcpAuth: () =>
|
|
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
|
|
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
|
-
},
|
|
9
|
+
}, hono_types9.BlankSchema, "/">;
|
|
10
10
|
//#endregion
|
|
11
11
|
export { app as default };
|
package/dist/github/mcp/index.js
CHANGED
|
@@ -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"),
|
|
@@ -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 };
|
package/dist/github/mcp/utils.js
CHANGED
|
@@ -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
|
|
2
|
+
import * as hono_types3 from "hono/types";
|
|
3
3
|
|
|
4
4
|
//#region src/github/routes/setup.d.ts
|
|
5
|
-
declare const app: Hono<
|
|
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
|
|
2
|
+
import * as hono_types5 from "hono/types";
|
|
3
3
|
|
|
4
4
|
//#region src/github/routes/tokenExchange.d.ts
|
|
5
|
-
declare const app: Hono<
|
|
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
|
|
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<
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
94
|
+
text: `*Approval required - \`${toolName}\`*`
|
|
102
95
|
}
|
|
103
96
|
}];
|
|
104
97
|
if (input && Object.keys(input).length > 0) {
|
|
105
|
-
const
|
|
106
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
11
|
+
slackUserId: string;
|
|
12
12
|
createdAt: string;
|
|
13
13
|
updatedAt: string;
|
|
14
|
-
|
|
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.
|
|
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.
|
|
36
|
+
"@inkeep/agents-core": "0.52.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@hono/zod-openapi": "^1.1.5",
|