@inkeep/agents-work-apps 0.52.0 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/env.d.ts +2 -2
- package/dist/github/mcp/auth.d.ts +2 -2
- package/dist/github/mcp/index.d.ts +2 -2
- package/dist/github/mcp/index.js +63 -26
- package/dist/github/mcp/schemas.d.ts +1 -1
- package/dist/github/mcp/utils.d.ts +2 -1
- package/dist/github/mcp/utils.js +16 -1
- package/dist/github/routes/setup.d.ts +2 -2
- package/dist/github/routes/tokenExchange.d.ts +2 -2
- package/dist/github/routes/webhooks.d.ts +2 -2
- package/dist/slack/dispatcher.js +11 -1
- package/dist/slack/routes/oauth.js +21 -1
- package/dist/slack/routes/users.js +29 -3
- package/dist/slack/services/agent-resolution.d.ts +1 -0
- package/dist/slack/services/agent-resolution.js +105 -18
- package/dist/slack/services/blocks/index.d.ts +9 -2
- package/dist/slack/services/blocks/index.js +27 -11
- package/dist/slack/services/commands/index.d.ts +1 -1
- package/dist/slack/services/commands/index.js +34 -124
- package/dist/slack/services/events/app-mention.d.ts +4 -14
- package/dist/slack/services/events/app-mention.js +40 -19
- package/dist/slack/services/events/block-actions.js +1 -1
- package/dist/slack/services/events/index.d.ts +1 -1
- package/dist/slack/services/events/modal-submission.js +14 -7
- package/dist/slack/services/events/streaming.js +9 -12
- package/dist/slack/services/events/utils.d.ts +26 -5
- package/dist/slack/services/events/utils.js +40 -15
- package/dist/slack/services/index.d.ts +4 -4
- package/dist/slack/services/index.js +3 -3
- package/dist/slack/services/link-prompt.d.ts +27 -0
- package/dist/slack/services/link-prompt.js +142 -0
- package/dist/slack/services/modals.d.ts +1 -0
- package/dist/slack/services/modals.js +6 -4
- package/dist/slack/services/resume-intent.d.ts +15 -0
- package/dist/slack/services/resume-intent.js +338 -0
- package/dist/slack/tracer.d.ts +1 -1
- package/package.json +2 -2
package/dist/env.d.ts
CHANGED
|
@@ -14,11 +14,11 @@ declare const envSchema: z.ZodObject<{
|
|
|
14
14
|
pentest: "pentest";
|
|
15
15
|
}>>;
|
|
16
16
|
LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
|
|
17
|
+
error: "error";
|
|
17
18
|
trace: "trace";
|
|
18
19
|
debug: "debug";
|
|
19
20
|
info: "info";
|
|
20
21
|
warn: "warn";
|
|
21
|
-
error: "error";
|
|
22
22
|
}>>;
|
|
23
23
|
INKEEP_AGENTS_RUN_DATABASE_URL: z.ZodOptional<z.ZodString>;
|
|
24
24
|
INKEEP_AGENTS_MANAGE_UI_URL: z.ZodOptional<z.ZodString>;
|
|
@@ -44,7 +44,7 @@ declare const envSchema: z.ZodObject<{
|
|
|
44
44
|
declare const env: {
|
|
45
45
|
NODE_ENV: "development" | "production" | "test";
|
|
46
46
|
ENVIRONMENT: "development" | "production" | "test" | "pentest";
|
|
47
|
-
LOG_LEVEL: "
|
|
47
|
+
LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
|
|
48
48
|
INKEEP_AGENTS_RUN_DATABASE_URL?: string | undefined;
|
|
49
49
|
INKEEP_AGENTS_MANAGE_UI_URL?: string | undefined;
|
|
50
50
|
GITHUB_APP_ID?: string | undefined;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as hono0 from "hono";
|
|
2
2
|
|
|
3
3
|
//#region src/github/mcp/auth.d.ts
|
|
4
|
-
declare const githubMcpAuth: () =>
|
|
4
|
+
declare const githubMcpAuth: () => hono0.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_types7 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_types7.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, fetchBranchChangedFiles, 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";
|
|
@@ -331,7 +331,8 @@ const getServer = async (toolId) => {
|
|
|
331
331
|
repo: z.string().describe("Repository name"),
|
|
332
332
|
from_branch: z.string().optional().describe("Branch to create from (defaults to default branch)")
|
|
333
333
|
}, async ({ owner, repo, from_branch }) => {
|
|
334
|
-
const
|
|
334
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
335
|
+
const branch_name = `docs-writer-ai-update-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${suffix}`;
|
|
335
336
|
try {
|
|
336
337
|
const githubClient = getGitHubClientFromRepo(owner, repo, installationIdMap);
|
|
337
338
|
const repoInfo = await githubClient.rest.repos.get({
|
|
@@ -666,13 +667,17 @@ const getServer = async (toolId) => {
|
|
|
666
667
|
};
|
|
667
668
|
}
|
|
668
669
|
});
|
|
669
|
-
server.tool("add-
|
|
670
|
+
server.tool("add-reaction", `Add a reaction to a pull request body, a general PR comment, or an inline PR review comment. ${getAvailableRepositoryString(repositoryAccess)}`, {
|
|
670
671
|
owner: z.string().describe("Repository owner name"),
|
|
671
672
|
repo: z.string().describe("Repository name"),
|
|
672
|
-
|
|
673
|
-
|
|
673
|
+
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"),
|
|
674
|
+
target_type: z.enum([
|
|
675
|
+
"pull_request",
|
|
676
|
+
"issue_comment",
|
|
677
|
+
"review_comment"
|
|
678
|
+
]).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"),
|
|
674
679
|
reaction: ReactionContentSchema.describe("The reaction emoji to add: +1, -1, laugh, hooray, confused, heart, rocket, or eyes")
|
|
675
|
-
}, async ({ owner, repo,
|
|
680
|
+
}, async ({ owner, repo, target_id, target_type, reaction }) => {
|
|
676
681
|
try {
|
|
677
682
|
let githubClient;
|
|
678
683
|
try {
|
|
@@ -686,9 +691,23 @@ const getServer = async (toolId) => {
|
|
|
686
691
|
isError: true
|
|
687
692
|
};
|
|
688
693
|
}
|
|
694
|
+
let result;
|
|
695
|
+
if (target_type === "pull_request") {
|
|
696
|
+
const { data } = await githubClient.rest.reactions.createForIssue({
|
|
697
|
+
owner,
|
|
698
|
+
repo,
|
|
699
|
+
issue_number: target_id,
|
|
700
|
+
content: reaction
|
|
701
|
+
});
|
|
702
|
+
result = {
|
|
703
|
+
id: data.id,
|
|
704
|
+
content: data.content
|
|
705
|
+
};
|
|
706
|
+
} else if (target_type === "issue_comment") result = await createIssueCommentReaction(githubClient, owner, repo, target_id, reaction);
|
|
707
|
+
else result = await createPullRequestReviewCommentReaction(githubClient, owner, repo, target_id, reaction);
|
|
689
708
|
return { content: [{
|
|
690
709
|
type: "text",
|
|
691
|
-
text: `Successfully added ${reaction} reaction to ${
|
|
710
|
+
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}`
|
|
692
711
|
}] };
|
|
693
712
|
} catch (error) {
|
|
694
713
|
if (error instanceof Error && "status" in error) {
|
|
@@ -696,21 +715,21 @@ const getServer = async (toolId) => {
|
|
|
696
715
|
if (apiError.status === 404) return {
|
|
697
716
|
content: [{
|
|
698
717
|
type: "text",
|
|
699
|
-
text: `
|
|
718
|
+
text: `Target ${target_id} (${target_type}) not found in ${owner}/${repo}.`
|
|
700
719
|
}],
|
|
701
720
|
isError: true
|
|
702
721
|
};
|
|
703
722
|
if (apiError.status === 422) return {
|
|
704
723
|
content: [{
|
|
705
724
|
type: "text",
|
|
706
|
-
text: `Invalid reaction. Ensure the reaction type is valid and the
|
|
725
|
+
text: `Invalid reaction. Ensure the reaction type is valid and the target exists.`
|
|
707
726
|
}],
|
|
708
727
|
isError: true
|
|
709
728
|
};
|
|
710
729
|
if (apiError.status === 403) return {
|
|
711
730
|
content: [{
|
|
712
731
|
type: "text",
|
|
713
|
-
text: `Access denied when adding reaction to
|
|
732
|
+
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.`
|
|
714
733
|
}],
|
|
715
734
|
isError: true
|
|
716
735
|
};
|
|
@@ -724,13 +743,17 @@ const getServer = async (toolId) => {
|
|
|
724
743
|
};
|
|
725
744
|
}
|
|
726
745
|
});
|
|
727
|
-
server.tool("remove-
|
|
746
|
+
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)}`, {
|
|
728
747
|
owner: z.string().describe("Repository owner name"),
|
|
729
748
|
repo: z.string().describe("Repository name"),
|
|
730
|
-
|
|
731
|
-
|
|
749
|
+
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"),
|
|
750
|
+
target_type: z.enum([
|
|
751
|
+
"pull_request",
|
|
752
|
+
"issue_comment",
|
|
753
|
+
"review_comment"
|
|
754
|
+
]).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"),
|
|
732
755
|
reaction_id: z.number().describe("The ID of the reaction to remove")
|
|
733
|
-
}, async ({ owner, repo,
|
|
756
|
+
}, async ({ owner, repo, target_id, target_type, reaction_id }) => {
|
|
734
757
|
try {
|
|
735
758
|
let githubClient;
|
|
736
759
|
try {
|
|
@@ -744,18 +767,24 @@ const getServer = async (toolId) => {
|
|
|
744
767
|
isError: true
|
|
745
768
|
};
|
|
746
769
|
}
|
|
747
|
-
if (
|
|
748
|
-
|
|
770
|
+
if (target_type === "pull_request") await githubClient.rest.reactions.deleteForIssue({
|
|
771
|
+
owner,
|
|
772
|
+
repo,
|
|
773
|
+
issue_number: target_id,
|
|
774
|
+
reaction_id
|
|
775
|
+
});
|
|
776
|
+
else if (target_type === "issue_comment") await deleteIssueCommentReaction(githubClient, owner, repo, target_id, reaction_id);
|
|
777
|
+
else await deletePullRequestReviewCommentReaction(githubClient, owner, repo, target_id, reaction_id);
|
|
749
778
|
return { content: [{
|
|
750
779
|
type: "text",
|
|
751
|
-
text: `Successfully removed reaction ${reaction_id} from ${
|
|
780
|
+
text: `Successfully removed reaction ${reaction_id} from ${target_type === "pull_request" ? `PR #${target_id}` : `${target_type} ${target_id}`} in ${owner}/${repo}`
|
|
752
781
|
}] };
|
|
753
782
|
} catch (error) {
|
|
754
783
|
if (error instanceof Error && "status" in error) {
|
|
755
784
|
if (error.status === 404) return {
|
|
756
785
|
content: [{
|
|
757
786
|
type: "text",
|
|
758
|
-
text: `
|
|
787
|
+
text: `Target ${target_id} (${target_type}) or reaction ${reaction_id} not found in ${owner}/${repo}.`
|
|
759
788
|
}],
|
|
760
789
|
isError: true
|
|
761
790
|
};
|
|
@@ -769,12 +798,16 @@ const getServer = async (toolId) => {
|
|
|
769
798
|
};
|
|
770
799
|
}
|
|
771
800
|
});
|
|
772
|
-
server.tool("list-
|
|
801
|
+
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)}`, {
|
|
773
802
|
owner: z.string().describe("Repository owner name"),
|
|
774
803
|
repo: z.string().describe("Repository name"),
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
804
|
+
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"),
|
|
805
|
+
target_type: z.enum([
|
|
806
|
+
"pull_request",
|
|
807
|
+
"issue_comment",
|
|
808
|
+
"review_comment"
|
|
809
|
+
]).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")
|
|
810
|
+
}, async ({ owner, repo, target_id, target_type }) => {
|
|
778
811
|
try {
|
|
779
812
|
let githubClient;
|
|
780
813
|
try {
|
|
@@ -788,22 +821,26 @@ const getServer = async (toolId) => {
|
|
|
788
821
|
isError: true
|
|
789
822
|
};
|
|
790
823
|
}
|
|
791
|
-
|
|
824
|
+
let reactions = [];
|
|
825
|
+
if (target_type === "pull_request") reactions = await listIssueReactions(githubClient, owner, repo, target_id);
|
|
826
|
+
else if (target_type === "issue_comment") reactions = await listIssueCommentReactions(githubClient, owner, repo, target_id);
|
|
827
|
+
else reactions = await listPullRequestReviewCommentReactions(githubClient, owner, repo, target_id);
|
|
828
|
+
const label = target_type === "pull_request" ? `PR #${target_id}` : `${target_type} ${target_id}`;
|
|
792
829
|
if (reactions.length === 0) return { content: [{
|
|
793
830
|
type: "text",
|
|
794
|
-
text: `No reactions found on ${
|
|
831
|
+
text: `No reactions found on ${label} in ${owner}/${repo}.`
|
|
795
832
|
}] };
|
|
796
833
|
const formatted = reactions.map((r) => `• ${r.content} by @${r.user} (reaction_id: ${r.id})`).join("\n");
|
|
797
834
|
return { content: [{
|
|
798
835
|
type: "text",
|
|
799
|
-
text: `Found ${reactions.length} reaction(s) on ${
|
|
836
|
+
text: `Found ${reactions.length} reaction(s) on ${label} in ${owner}/${repo}:\n\n${formatted}`
|
|
800
837
|
}] };
|
|
801
838
|
} catch (error) {
|
|
802
839
|
if (error instanceof Error && "status" in error) {
|
|
803
840
|
if (error.status === 404) return {
|
|
804
841
|
content: [{
|
|
805
842
|
type: "text",
|
|
806
|
-
text: `
|
|
843
|
+
text: `Target ${target_id} (${target_type}) not found in ${owner}/${repo}.`
|
|
807
844
|
}],
|
|
808
845
|
isError: true
|
|
809
846
|
};
|
|
@@ -254,6 +254,7 @@ interface ReactionDetail {
|
|
|
254
254
|
}
|
|
255
255
|
declare function listIssueCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
|
|
256
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[]>;
|
|
257
258
|
declare function formatFileDiff(pullRequestNumber: number, files: ChangedFile[], includeContents?: boolean): Promise<string>;
|
|
258
259
|
//#endregion
|
|
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 };
|
|
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
|
@@ -758,6 +758,21 @@ async function listPullRequestReviewCommentReactions(octokit, owner, repo, comme
|
|
|
758
758
|
});
|
|
759
759
|
return reactions;
|
|
760
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
|
+
}
|
|
761
776
|
async function formatFileDiff(pullRequestNumber, files, includeContents = false) {
|
|
762
777
|
let output = `## File Patches for PR #${pullRequestNumber}\n\n`;
|
|
763
778
|
output += `Found ${files.length} file(s) matching the requested paths.\n\n`;
|
|
@@ -780,4 +795,4 @@ async function formatFileDiff(pullRequestNumber, files, includeContents = false)
|
|
|
780
795
|
}
|
|
781
796
|
|
|
782
797
|
//#endregion
|
|
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 };
|
|
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 };
|
|
@@ -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/setup.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,7 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import * as
|
|
2
|
+
import * as hono_types8 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_types8.BlankEnv, hono_types8.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_types3 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_types3.BlankEnv, hono_types3.BlankSchema, "/">;
|
|
11
11
|
//#endregion
|
|
12
12
|
export { WebhookVerificationResult, app as default, verifyWebhookSignature };
|
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);
|
|
@@ -53,6 +62,7 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
|
|
|
53
62
|
slackUserId: event.user,
|
|
54
63
|
channel: event.channel,
|
|
55
64
|
text: question,
|
|
65
|
+
attachments: event.attachments,
|
|
56
66
|
threadTs: event.thread_ts || event.ts || "",
|
|
57
67
|
messageTs: event.ts || "",
|
|
58
68
|
teamId,
|
|
@@ -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, isUniqueConstraintError } 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
|
|
|
@@ -213,6 +213,26 @@ app.openapi(createProtectedRoute({
|
|
|
213
213
|
appId: tokenData.app_id,
|
|
214
214
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
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
|
+
}
|
|
216
236
|
if (workspaceData.teamId && workspaceData.botToken) {
|
|
217
237
|
clearWorkspaceConnectionCache(workspaceData.teamId);
|
|
218
238
|
const nangoResult = await storeWorkspaceInstallation({
|
|
@@ -2,8 +2,9 @@ import { getLogger } from "../../logger.js";
|
|
|
2
2
|
import runDbClient_default from "../../db/runDbClient.js";
|
|
3
3
|
import { createConnectSession } from "../services/nango.js";
|
|
4
4
|
import "../services/index.js";
|
|
5
|
+
import { resumeSmartLinkIntent } from "../services/resume-intent.js";
|
|
5
6
|
import { OpenAPIHono, z } from "@hono/zod-openapi";
|
|
6
|
-
import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, isUniqueConstraintError, verifySlackLinkToken } from "@inkeep/agents-core";
|
|
7
|
+
import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, flushTraces, getWaitUntil, isUniqueConstraintError, verifySlackLinkToken } from "@inkeep/agents-core";
|
|
7
8
|
import { createProtectedRoute, inheritedWorkAppsAuth } from "@inkeep/agents-core/middleware";
|
|
8
9
|
|
|
9
10
|
//#region src/slack/routes/users.ts
|
|
@@ -105,8 +106,13 @@ app.openapi(createProtectedRoute({
|
|
|
105
106
|
try {
|
|
106
107
|
const verifyResult = await verifySlackLinkToken(body.token);
|
|
107
108
|
if (!verifyResult.valid || !verifyResult.payload) {
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
const isExpired = verifyResult.error?.includes("\"exp\" claim timestamp check failed");
|
|
110
|
+
const errorMessage = isExpired ? "Token expired. Please run /inkeep link in Slack to get a new one." : verifyResult.error || "Invalid or expired link token. Please run /inkeep link in Slack to get a new one.";
|
|
111
|
+
logger.warn({
|
|
112
|
+
error: verifyResult.error,
|
|
113
|
+
isExpired
|
|
114
|
+
}, "Invalid link token");
|
|
115
|
+
return c.json({ error: errorMessage }, 400);
|
|
110
116
|
}
|
|
111
117
|
const { tenantId, slack } = verifyResult.payload;
|
|
112
118
|
const { teamId, userId: slackUserId, enterpriseId, username } = slack;
|
|
@@ -157,6 +163,26 @@ app.openapi(createProtectedRoute({
|
|
|
157
163
|
inkeepUserId: body.userId,
|
|
158
164
|
linkId: slackUserMapping.id
|
|
159
165
|
}, "Successfully linked Slack user to Inkeep account via JWT token");
|
|
166
|
+
const { intent } = verifyResult.payload;
|
|
167
|
+
if (intent) {
|
|
168
|
+
logger.info({
|
|
169
|
+
event: "smart_link_intent_resume_triggered",
|
|
170
|
+
entryPoint: intent.entryPoint,
|
|
171
|
+
questionLength: intent.question.length
|
|
172
|
+
}, "Smart link intent detected in verify-token");
|
|
173
|
+
const resumeWork = resumeSmartLinkIntent({
|
|
174
|
+
intent,
|
|
175
|
+
teamId,
|
|
176
|
+
slackUserId,
|
|
177
|
+
inkeepUserId,
|
|
178
|
+
tenantId,
|
|
179
|
+
slackEnterpriseId: enterpriseId,
|
|
180
|
+
slackUsername: username
|
|
181
|
+
}).catch((error) => logger.error({ error }, "Resume smart link intent failed")).finally(() => flushTraces());
|
|
182
|
+
const waitUntil = await getWaitUntil();
|
|
183
|
+
if (waitUntil) waitUntil(resumeWork);
|
|
184
|
+
else logger.warn({ entryPoint: intent.entryPoint }, "waitUntil not available, resume work may not complete");
|
|
185
|
+
}
|
|
160
186
|
return c.json({
|
|
161
187
|
success: true,
|
|
162
188
|
linkId: slackUserMapping.id,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getLogger } from "../../logger.js";
|
|
2
2
|
import runDbClient_default from "../../db/runDbClient.js";
|
|
3
3
|
import { getWorkspaceDefaultAgentFromNango } from "./nango.js";
|
|
4
|
+
import { fetchAgentsForProject, fetchProjectsForTenant } from "./events/utils.js";
|
|
4
5
|
import { findWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
|
|
5
6
|
|
|
6
7
|
//#region src/slack/services/agent-resolution.ts
|
|
@@ -11,6 +12,64 @@ import { findWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
|
|
|
11
12
|
* Priority: Channel default > Workspace default (all admin-controlled)
|
|
12
13
|
*/
|
|
13
14
|
const logger = getLogger("slack-agent-resolution");
|
|
15
|
+
const AGENT_NAME_CACHE_TTL_MS = 300 * 1e3;
|
|
16
|
+
const AGENT_NAME_CACHE_MAX_SIZE = 500;
|
|
17
|
+
const agentNameCache = /* @__PURE__ */ new Map();
|
|
18
|
+
async function lookupAgentName(tenantId, projectId, agentId) {
|
|
19
|
+
const cacheKey = `${tenantId}:${projectId}:${agentId}`;
|
|
20
|
+
const cached = agentNameCache.get(cacheKey);
|
|
21
|
+
if (cached && cached.expiresAt > Date.now()) return cached.name || void 0;
|
|
22
|
+
const agents = await fetchAgentsForProject(tenantId, projectId);
|
|
23
|
+
for (const agent of agents) {
|
|
24
|
+
const key = `${tenantId}:${projectId}:${agent.id}`;
|
|
25
|
+
agentNameCache.set(key, {
|
|
26
|
+
name: agent.name || null,
|
|
27
|
+
expiresAt: Date.now() + AGENT_NAME_CACHE_TTL_MS
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (agentNameCache.size > AGENT_NAME_CACHE_MAX_SIZE) {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
for (const [key, entry] of agentNameCache) if (entry.expiresAt <= now) agentNameCache.delete(key);
|
|
33
|
+
if (agentNameCache.size > AGENT_NAME_CACHE_MAX_SIZE) {
|
|
34
|
+
const excess = agentNameCache.size - AGENT_NAME_CACHE_MAX_SIZE;
|
|
35
|
+
const keys = agentNameCache.keys();
|
|
36
|
+
for (let i = 0; i < excess; i++) {
|
|
37
|
+
const { value } = keys.next();
|
|
38
|
+
if (value) agentNameCache.delete(value);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return agents.find((a) => a.id === agentId)?.name || void 0;
|
|
43
|
+
}
|
|
44
|
+
const PROJECT_NAME_CACHE_TTL_MS = 300 * 1e3;
|
|
45
|
+
const PROJECT_NAME_CACHE_MAX_SIZE = 200;
|
|
46
|
+
const projectNameCache = /* @__PURE__ */ new Map();
|
|
47
|
+
async function lookupProjectName(tenantId, projectId) {
|
|
48
|
+
const cacheKey = `${tenantId}:${projectId}`;
|
|
49
|
+
const cached = projectNameCache.get(cacheKey);
|
|
50
|
+
if (cached && cached.expiresAt > Date.now()) return cached.name || void 0;
|
|
51
|
+
const projects = await fetchProjectsForTenant(tenantId);
|
|
52
|
+
for (const project of projects) {
|
|
53
|
+
const key = `${tenantId}:${project.id}`;
|
|
54
|
+
projectNameCache.set(key, {
|
|
55
|
+
name: project.name || null,
|
|
56
|
+
expiresAt: Date.now() + PROJECT_NAME_CACHE_TTL_MS
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (projectNameCache.size > PROJECT_NAME_CACHE_MAX_SIZE) {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
for (const [key, entry] of projectNameCache) if (entry.expiresAt <= now) projectNameCache.delete(key);
|
|
62
|
+
if (projectNameCache.size > PROJECT_NAME_CACHE_MAX_SIZE) {
|
|
63
|
+
const excess = projectNameCache.size - PROJECT_NAME_CACHE_MAX_SIZE;
|
|
64
|
+
const keys = projectNameCache.keys();
|
|
65
|
+
for (let i = 0; i < excess; i++) {
|
|
66
|
+
const { value } = keys.next();
|
|
67
|
+
if (value) projectNameCache.delete(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return projects.find((p) => p.id === projectId)?.name || void 0;
|
|
72
|
+
}
|
|
14
73
|
/**
|
|
15
74
|
* Resolve the effective agent configuration.
|
|
16
75
|
* Priority: Channel default > Workspace default
|
|
@@ -25,6 +84,7 @@ async function resolveEffectiveAgent(params) {
|
|
|
25
84
|
teamId,
|
|
26
85
|
channelId
|
|
27
86
|
}, "Resolving effective agent");
|
|
87
|
+
let result = null;
|
|
28
88
|
if (channelId) {
|
|
29
89
|
const channelConfig = await findWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
|
|
30
90
|
if (channelConfig?.enabled) {
|
|
@@ -33,7 +93,7 @@ async function resolveEffectiveAgent(params) {
|
|
|
33
93
|
agentId: channelConfig.agentId,
|
|
34
94
|
source: "channel"
|
|
35
95
|
}, "Resolved agent from channel config");
|
|
36
|
-
|
|
96
|
+
result = {
|
|
37
97
|
projectId: channelConfig.projectId,
|
|
38
98
|
agentId: channelConfig.agentId,
|
|
39
99
|
agentName: channelConfig.agentName || void 0,
|
|
@@ -42,27 +102,44 @@ async function resolveEffectiveAgent(params) {
|
|
|
42
102
|
};
|
|
43
103
|
}
|
|
44
104
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
if (!result) {
|
|
106
|
+
const workspaceConfig = await getWorkspaceDefaultAgentFromNango(teamId);
|
|
107
|
+
if (workspaceConfig?.agentId && workspaceConfig.projectId) {
|
|
108
|
+
logger.info({
|
|
109
|
+
teamId,
|
|
110
|
+
agentId: workspaceConfig.agentId,
|
|
111
|
+
source: "workspace"
|
|
112
|
+
}, "Resolved agent from workspace config");
|
|
113
|
+
result = {
|
|
114
|
+
projectId: workspaceConfig.projectId,
|
|
115
|
+
projectName: workspaceConfig.projectName,
|
|
116
|
+
agentId: workspaceConfig.agentId,
|
|
117
|
+
agentName: workspaceConfig.agentName,
|
|
118
|
+
source: "workspace",
|
|
119
|
+
grantAccessToMembers: workspaceConfig.grantAccessToMembers ?? true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
59
122
|
}
|
|
60
|
-
|
|
123
|
+
if (result && (!result.agentName || result.agentName === result.agentId)) {
|
|
124
|
+
const name = await lookupAgentName(tenantId, result.projectId, result.agentId);
|
|
125
|
+
if (name) {
|
|
126
|
+
result.agentName = name;
|
|
127
|
+
logger.debug({
|
|
128
|
+
agentId: result.agentId,
|
|
129
|
+
agentName: name
|
|
130
|
+
}, "Enriched agent config with name from manage API");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (result && !result.projectName) {
|
|
134
|
+
const projectName = await lookupProjectName(tenantId, result.projectId);
|
|
135
|
+
if (projectName) result.projectName = projectName;
|
|
136
|
+
}
|
|
137
|
+
if (!result) logger.debug({
|
|
61
138
|
tenantId,
|
|
62
139
|
teamId,
|
|
63
140
|
channelId
|
|
64
141
|
}, "No agent configuration found");
|
|
65
|
-
return
|
|
142
|
+
return result;
|
|
66
143
|
}
|
|
67
144
|
/**
|
|
68
145
|
* Get all agent configuration sources for display purposes.
|
|
@@ -87,15 +164,25 @@ async function getAgentConfigSources(params) {
|
|
|
87
164
|
const wsConfig = await getWorkspaceDefaultAgentFromNango(teamId);
|
|
88
165
|
if (wsConfig?.agentId && wsConfig.projectId) workspaceConfig = {
|
|
89
166
|
projectId: wsConfig.projectId,
|
|
167
|
+
projectName: wsConfig.projectName,
|
|
90
168
|
agentId: wsConfig.agentId,
|
|
91
169
|
agentName: wsConfig.agentName,
|
|
92
170
|
source: "workspace",
|
|
93
171
|
grantAccessToMembers: wsConfig.grantAccessToMembers ?? true
|
|
94
172
|
};
|
|
173
|
+
const effective = channelConfig || workspaceConfig;
|
|
174
|
+
if (effective && (!effective.agentName || effective.agentName === effective.agentId)) {
|
|
175
|
+
const name = await lookupAgentName(tenantId, effective.projectId, effective.agentId);
|
|
176
|
+
if (name) effective.agentName = name;
|
|
177
|
+
}
|
|
178
|
+
if (effective && !effective.projectName) {
|
|
179
|
+
const projectName = await lookupProjectName(tenantId, effective.projectId);
|
|
180
|
+
if (projectName) effective.projectName = projectName;
|
|
181
|
+
}
|
|
95
182
|
return {
|
|
96
183
|
channelConfig,
|
|
97
184
|
workspaceConfig,
|
|
98
|
-
effective
|
|
185
|
+
effective
|
|
99
186
|
};
|
|
100
187
|
}
|
|
101
188
|
|