@inkeep/agents-work-apps 0.51.0 → 0.53.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/index.d.ts +2 -2
- package/dist/github/mcp/index.js +120 -25
- package/dist/github/mcp/utils.d.ts +11 -1
- package/dist/github/mcp/utils.js +102 -1
- package/dist/slack/dispatcher.js +10 -1
- package/dist/slack/routes/events.js +2 -2
- package/dist/slack/routes/oauth.js +23 -4
- package/dist/slack/routes/users.js +2 -2
- package/dist/slack/services/agent-resolution.js +66 -18
- package/dist/slack/services/blocks/index.d.ts +35 -1
- package/dist/slack/services/blocks/index.js +161 -22
- package/dist/slack/services/commands/index.js +2 -2
- package/dist/slack/services/events/app-mention.js +1 -1
- package/dist/slack/services/events/block-actions.js +1 -1
- package/dist/slack/services/events/modal-submission.js +14 -7
- package/dist/slack/services/events/streaming.js +133 -8
- package/dist/slack/services/events/utils.d.ts +5 -3
- package/dist/slack/services/events/utils.js +5 -5
- package/dist/slack/services/index.d.ts +2 -2
- package/dist/slack/services/index.js +3 -3
- package/dist/slack/services/modals.d.ts +1 -0
- package/dist/slack/services/modals.js +6 -4
- package/dist/slack/services/nango.js +1 -23
- package/dist/slack/tracer.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import * as
|
|
2
|
+
import * as hono_types0 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_types0.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, listIssueReactions, 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"),
|
|
@@ -607,13 +666,17 @@ const getServer = async (toolId) => {
|
|
|
607
666
|
};
|
|
608
667
|
}
|
|
609
668
|
});
|
|
610
|
-
server.tool("add-
|
|
669
|
+
server.tool("add-reaction", `Add a reaction to a pull request body, a general PR comment, or an inline PR review comment. ${getAvailableRepositoryString(repositoryAccess)}`, {
|
|
611
670
|
owner: z.string().describe("Repository owner name"),
|
|
612
671
|
repo: z.string().describe("Repository name"),
|
|
613
|
-
|
|
614
|
-
|
|
672
|
+
target_id: z.number().describe("The target to react to: a comment ID for issue_comment/review_comment, or the pull request number for pull_request"),
|
|
673
|
+
target_type: z.enum([
|
|
674
|
+
"pull_request",
|
|
675
|
+
"issue_comment",
|
|
676
|
+
"review_comment"
|
|
677
|
+
]).describe("The type of target: \"pull_request\" for the PR body itself, \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments"),
|
|
615
678
|
reaction: ReactionContentSchema.describe("The reaction emoji to add: +1, -1, laugh, hooray, confused, heart, rocket, or eyes")
|
|
616
|
-
}, async ({ owner, repo,
|
|
679
|
+
}, async ({ owner, repo, target_id, target_type, reaction }) => {
|
|
617
680
|
try {
|
|
618
681
|
let githubClient;
|
|
619
682
|
try {
|
|
@@ -627,9 +690,23 @@ const getServer = async (toolId) => {
|
|
|
627
690
|
isError: true
|
|
628
691
|
};
|
|
629
692
|
}
|
|
693
|
+
let result;
|
|
694
|
+
if (target_type === "pull_request") {
|
|
695
|
+
const { data } = await githubClient.rest.reactions.createForIssue({
|
|
696
|
+
owner,
|
|
697
|
+
repo,
|
|
698
|
+
issue_number: target_id,
|
|
699
|
+
content: reaction
|
|
700
|
+
});
|
|
701
|
+
result = {
|
|
702
|
+
id: data.id,
|
|
703
|
+
content: data.content
|
|
704
|
+
};
|
|
705
|
+
} else if (target_type === "issue_comment") result = await createIssueCommentReaction(githubClient, owner, repo, target_id, reaction);
|
|
706
|
+
else result = await createPullRequestReviewCommentReaction(githubClient, owner, repo, target_id, reaction);
|
|
630
707
|
return { content: [{
|
|
631
708
|
type: "text",
|
|
632
|
-
text: `Successfully added ${reaction} reaction to ${
|
|
709
|
+
text: `Successfully added ${reaction} reaction to ${target_type === "pull_request" ? `PR #${target_id}` : `${target_type} ${target_id}`} in ${owner}/${repo}\n\nReaction ID: ${result.id}`
|
|
633
710
|
}] };
|
|
634
711
|
} catch (error) {
|
|
635
712
|
if (error instanceof Error && "status" in error) {
|
|
@@ -637,21 +714,21 @@ const getServer = async (toolId) => {
|
|
|
637
714
|
if (apiError.status === 404) return {
|
|
638
715
|
content: [{
|
|
639
716
|
type: "text",
|
|
640
|
-
text: `
|
|
717
|
+
text: `Target ${target_id} (${target_type}) not found in ${owner}/${repo}.`
|
|
641
718
|
}],
|
|
642
719
|
isError: true
|
|
643
720
|
};
|
|
644
721
|
if (apiError.status === 422) return {
|
|
645
722
|
content: [{
|
|
646
723
|
type: "text",
|
|
647
|
-
text: `Invalid reaction. Ensure the reaction type is valid and the
|
|
724
|
+
text: `Invalid reaction. Ensure the reaction type is valid and the target exists.`
|
|
648
725
|
}],
|
|
649
726
|
isError: true
|
|
650
727
|
};
|
|
651
728
|
if (apiError.status === 403) return {
|
|
652
729
|
content: [{
|
|
653
730
|
type: "text",
|
|
654
|
-
text: `Access denied when adding reaction to
|
|
731
|
+
text: `Access denied when adding reaction to ${target_type} ${target_id} in ${owner}/${repo}. Your GitHub App may not have sufficient permissions to create reactions.`
|
|
655
732
|
}],
|
|
656
733
|
isError: true
|
|
657
734
|
};
|
|
@@ -665,13 +742,17 @@ const getServer = async (toolId) => {
|
|
|
665
742
|
};
|
|
666
743
|
}
|
|
667
744
|
});
|
|
668
|
-
server.tool("remove-
|
|
745
|
+
server.tool("remove-reaction", `Remove a reaction from a pull request body, a general PR comment, or an inline PR review comment. Requires the reaction ID (returned when adding a reaction or from list-reactions). ${getAvailableRepositoryString(repositoryAccess)}`, {
|
|
669
746
|
owner: z.string().describe("Repository owner name"),
|
|
670
747
|
repo: z.string().describe("Repository name"),
|
|
671
|
-
|
|
672
|
-
|
|
748
|
+
target_id: z.number().describe("The target the reaction belongs to: a comment ID for issue_comment/review_comment, or the pull request number for pull_request"),
|
|
749
|
+
target_type: z.enum([
|
|
750
|
+
"pull_request",
|
|
751
|
+
"issue_comment",
|
|
752
|
+
"review_comment"
|
|
753
|
+
]).describe("The type of target: \"pull_request\" for the PR body itself, \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments"),
|
|
673
754
|
reaction_id: z.number().describe("The ID of the reaction to remove")
|
|
674
|
-
}, async ({ owner, repo,
|
|
755
|
+
}, async ({ owner, repo, target_id, target_type, reaction_id }) => {
|
|
675
756
|
try {
|
|
676
757
|
let githubClient;
|
|
677
758
|
try {
|
|
@@ -685,18 +766,24 @@ const getServer = async (toolId) => {
|
|
|
685
766
|
isError: true
|
|
686
767
|
};
|
|
687
768
|
}
|
|
688
|
-
if (
|
|
689
|
-
|
|
769
|
+
if (target_type === "pull_request") await githubClient.rest.reactions.deleteForIssue({
|
|
770
|
+
owner,
|
|
771
|
+
repo,
|
|
772
|
+
issue_number: target_id,
|
|
773
|
+
reaction_id
|
|
774
|
+
});
|
|
775
|
+
else if (target_type === "issue_comment") await deleteIssueCommentReaction(githubClient, owner, repo, target_id, reaction_id);
|
|
776
|
+
else await deletePullRequestReviewCommentReaction(githubClient, owner, repo, target_id, reaction_id);
|
|
690
777
|
return { content: [{
|
|
691
778
|
type: "text",
|
|
692
|
-
text: `Successfully removed reaction ${reaction_id} from ${
|
|
779
|
+
text: `Successfully removed reaction ${reaction_id} from ${target_type === "pull_request" ? `PR #${target_id}` : `${target_type} ${target_id}`} in ${owner}/${repo}`
|
|
693
780
|
}] };
|
|
694
781
|
} catch (error) {
|
|
695
782
|
if (error instanceof Error && "status" in error) {
|
|
696
783
|
if (error.status === 404) return {
|
|
697
784
|
content: [{
|
|
698
785
|
type: "text",
|
|
699
|
-
text: `
|
|
786
|
+
text: `Target ${target_id} (${target_type}) or reaction ${reaction_id} not found in ${owner}/${repo}.`
|
|
700
787
|
}],
|
|
701
788
|
isError: true
|
|
702
789
|
};
|
|
@@ -710,12 +797,16 @@ const getServer = async (toolId) => {
|
|
|
710
797
|
};
|
|
711
798
|
}
|
|
712
799
|
});
|
|
713
|
-
server.tool("list-
|
|
800
|
+
server.tool("list-reactions", `List all reactions on a pull request body, a general PR comment, or an inline PR review comment. Returns each reaction's ID (needed for removal). ${getAvailableRepositoryString(repositoryAccess)}`, {
|
|
714
801
|
owner: z.string().describe("Repository owner name"),
|
|
715
802
|
repo: z.string().describe("Repository name"),
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
803
|
+
target_id: z.number().describe("The target to list reactions for: a comment ID for issue_comment/review_comment, or the pull request number for pull_request"),
|
|
804
|
+
target_type: z.enum([
|
|
805
|
+
"pull_request",
|
|
806
|
+
"issue_comment",
|
|
807
|
+
"review_comment"
|
|
808
|
+
]).describe("The type of target: \"pull_request\" for the PR body itself, \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments")
|
|
809
|
+
}, async ({ owner, repo, target_id, target_type }) => {
|
|
719
810
|
try {
|
|
720
811
|
let githubClient;
|
|
721
812
|
try {
|
|
@@ -729,22 +820,26 @@ const getServer = async (toolId) => {
|
|
|
729
820
|
isError: true
|
|
730
821
|
};
|
|
731
822
|
}
|
|
732
|
-
|
|
823
|
+
let reactions = [];
|
|
824
|
+
if (target_type === "pull_request") reactions = await listIssueReactions(githubClient, owner, repo, target_id);
|
|
825
|
+
else if (target_type === "issue_comment") reactions = await listIssueCommentReactions(githubClient, owner, repo, target_id);
|
|
826
|
+
else reactions = await listPullRequestReviewCommentReactions(githubClient, owner, repo, target_id);
|
|
827
|
+
const label = target_type === "pull_request" ? `PR #${target_id}` : `${target_type} ${target_id}`;
|
|
733
828
|
if (reactions.length === 0) return { content: [{
|
|
734
829
|
type: "text",
|
|
735
|
-
text: `No reactions found on ${
|
|
830
|
+
text: `No reactions found on ${label} in ${owner}/${repo}.`
|
|
736
831
|
}] };
|
|
737
832
|
const formatted = reactions.map((r) => `• ${r.content} by @${r.user} (reaction_id: ${r.id})`).join("\n");
|
|
738
833
|
return { content: [{
|
|
739
834
|
type: "text",
|
|
740
|
-
text: `Found ${reactions.length} reaction(s) on ${
|
|
835
|
+
text: `Found ${reactions.length} reaction(s) on ${label} in ${owner}/${repo}:\n\n${formatted}`
|
|
741
836
|
}] };
|
|
742
837
|
} catch (error) {
|
|
743
838
|
if (error instanceof Error && "status" in error) {
|
|
744
839
|
if (error.status === 404) return {
|
|
745
840
|
content: [{
|
|
746
841
|
type: "text",
|
|
747
|
-
text: `
|
|
842
|
+
text: `Target ${target_id} (${target_type}) not found in ${owner}/${repo}.`
|
|
748
843
|
}],
|
|
749
844
|
isError: true
|
|
750
845
|
};
|
|
@@ -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
|
*/
|
|
@@ -245,6 +254,7 @@ interface ReactionDetail {
|
|
|
245
254
|
}
|
|
246
255
|
declare function listIssueCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
|
|
247
256
|
declare function listPullRequestReviewCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
|
|
257
|
+
declare function listIssueReactions(octokit: Octokit, owner: string, repo: string, issueNumber: number): Promise<ReactionDetail[]>;
|
|
248
258
|
declare function formatFileDiff(pullRequestNumber: number, files: ChangedFile[], includeContents?: boolean): Promise<string>;
|
|
249
259
|
//#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 };
|
|
260
|
+
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, listIssueReactions, 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) {
|
|
@@ -672,6 +758,21 @@ async function listPullRequestReviewCommentReactions(octokit, owner, repo, comme
|
|
|
672
758
|
});
|
|
673
759
|
return reactions;
|
|
674
760
|
}
|
|
761
|
+
async function listIssueReactions(octokit, owner, repo, issueNumber) {
|
|
762
|
+
const reactions = [];
|
|
763
|
+
for await (const response of octokit.paginate.iterator(octokit.rest.reactions.listForIssue, {
|
|
764
|
+
owner,
|
|
765
|
+
repo,
|
|
766
|
+
issue_number: issueNumber,
|
|
767
|
+
per_page: 100
|
|
768
|
+
})) for (const r of response.data) reactions.push({
|
|
769
|
+
id: r.id,
|
|
770
|
+
content: r.content,
|
|
771
|
+
user: r.user?.login ?? "unknown",
|
|
772
|
+
createdAt: r.created_at
|
|
773
|
+
});
|
|
774
|
+
return reactions;
|
|
775
|
+
}
|
|
675
776
|
async function formatFileDiff(pullRequestNumber, files, includeContents = false) {
|
|
676
777
|
let output = `## File Patches for PR #${pullRequestNumber}\n\n`;
|
|
677
778
|
output += `Found ${files.length} file(s) matching the requested paths.\n\n`;
|
|
@@ -694,4 +795,4 @@ async function formatFileDiff(pullRequestNumber, files, includeContents = false)
|
|
|
694
795
|
}
|
|
695
796
|
|
|
696
797
|
//#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 };
|
|
798
|
+
export { applyOperation, applyOperations, commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, fetchBranchChangedFiles, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, listIssueCommentReactions, listIssueReactions, listPullRequestReviewCommentReactions, validateLineNumbers, visualizeUpdateOperations };
|
package/dist/slack/dispatcher.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLogger } from "../logger.js";
|
|
2
2
|
import { findWorkspaceConnectionByTeamId } from "./services/nango.js";
|
|
3
|
-
import { getSlackClient } from "./services/client.js";
|
|
4
3
|
import { sendResponseUrlMessage } from "./services/events/utils.js";
|
|
4
|
+
import { getSlackClient } from "./services/client.js";
|
|
5
5
|
import { SLACK_SPAN_KEYS } from "./tracer.js";
|
|
6
6
|
import { handleAppMention } from "./services/events/app-mention.js";
|
|
7
7
|
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./services/events/block-actions.js";
|
|
@@ -35,6 +35,15 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
|
|
|
35
35
|
}, "Ignoring bot message");
|
|
36
36
|
return { outcome };
|
|
37
37
|
}
|
|
38
|
+
if (event?.edited) {
|
|
39
|
+
outcome = "ignored_edited_message";
|
|
40
|
+
span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
|
|
41
|
+
logger.info({
|
|
42
|
+
teamId,
|
|
43
|
+
innerEventType
|
|
44
|
+
}, "Ignoring edited message");
|
|
45
|
+
return { outcome };
|
|
46
|
+
}
|
|
38
47
|
if (event?.type === "app_mention" && event.channel && event.user && teamId) {
|
|
39
48
|
outcome = "handled";
|
|
40
49
|
span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
|
|
@@ -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, listWorkAppSlackWorkspacesByTenant } 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",
|
|
@@ -212,6 +213,26 @@ app.openapi(createProtectedRoute({
|
|
|
212
213
|
appId: tokenData.app_id,
|
|
213
214
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
214
215
|
};
|
|
216
|
+
if (tenantId && workspaceData.teamId) {
|
|
217
|
+
let existingWorkspaces;
|
|
218
|
+
try {
|
|
219
|
+
existingWorkspaces = await listWorkAppSlackWorkspacesByTenant(runDbClient_default)(tenantId);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
logger.error({
|
|
222
|
+
err,
|
|
223
|
+
tenantId
|
|
224
|
+
}, "Failed to check existing workspaces");
|
|
225
|
+
return c.redirect(`${dashboardUrl}?error=workspace_check_failed`);
|
|
226
|
+
}
|
|
227
|
+
if (existingWorkspaces.some((w) => w.slackTeamId !== workspaceData.teamId)) {
|
|
228
|
+
logger.warn({
|
|
229
|
+
tenantId,
|
|
230
|
+
newTeamId: workspaceData.teamId,
|
|
231
|
+
existingTeamIds: existingWorkspaces.map((w) => w.slackTeamId)
|
|
232
|
+
}, "Tenant already has a different Slack workspace, rejecting installation");
|
|
233
|
+
return c.redirect(`${dashboardUrl}?error=workspace_limit_reached`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
215
236
|
if (workspaceData.teamId && workspaceData.botToken) {
|
|
216
237
|
clearWorkspaceConnectionCache(workspaceData.teamId);
|
|
217
238
|
const nangoResult = await storeWorkspaceInstallation({
|
|
@@ -252,8 +273,7 @@ app.openapi(createProtectedRoute({
|
|
|
252
273
|
tenantId
|
|
253
274
|
}, "Persisted workspace installation to database");
|
|
254
275
|
} catch (dbError) {
|
|
255
|
-
|
|
256
|
-
if (dbErrorMessage.includes("duplicate key") || dbErrorMessage.includes("unique constraint")) logger.info({
|
|
276
|
+
if (isUniqueConstraintError(dbError)) logger.info({
|
|
257
277
|
teamId: workspaceData.teamId,
|
|
258
278
|
tenantId
|
|
259
279
|
}, "Workspace already exists in database");
|
|
@@ -261,7 +281,6 @@ app.openapi(createProtectedRoute({
|
|
|
261
281
|
const pgCode = dbError && typeof dbError === "object" && "code" in dbError ? dbError.code : void 0;
|
|
262
282
|
logger.error({
|
|
263
283
|
err: dbError,
|
|
264
|
-
dbErrorMessage,
|
|
265
284
|
pgCode,
|
|
266
285
|
teamId: workspaceData.teamId,
|
|
267
286
|
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
|
}
|