@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.
Files changed (37) hide show
  1. package/dist/env.d.ts +2 -2
  2. package/dist/github/mcp/auth.d.ts +2 -2
  3. package/dist/github/mcp/index.d.ts +2 -2
  4. package/dist/github/mcp/index.js +63 -26
  5. package/dist/github/mcp/schemas.d.ts +1 -1
  6. package/dist/github/mcp/utils.d.ts +2 -1
  7. package/dist/github/mcp/utils.js +16 -1
  8. package/dist/github/routes/setup.d.ts +2 -2
  9. package/dist/github/routes/tokenExchange.d.ts +2 -2
  10. package/dist/github/routes/webhooks.d.ts +2 -2
  11. package/dist/slack/dispatcher.js +11 -1
  12. package/dist/slack/routes/oauth.js +21 -1
  13. package/dist/slack/routes/users.js +29 -3
  14. package/dist/slack/services/agent-resolution.d.ts +1 -0
  15. package/dist/slack/services/agent-resolution.js +105 -18
  16. package/dist/slack/services/blocks/index.d.ts +9 -2
  17. package/dist/slack/services/blocks/index.js +27 -11
  18. package/dist/slack/services/commands/index.d.ts +1 -1
  19. package/dist/slack/services/commands/index.js +34 -124
  20. package/dist/slack/services/events/app-mention.d.ts +4 -14
  21. package/dist/slack/services/events/app-mention.js +40 -19
  22. package/dist/slack/services/events/block-actions.js +1 -1
  23. package/dist/slack/services/events/index.d.ts +1 -1
  24. package/dist/slack/services/events/modal-submission.js +14 -7
  25. package/dist/slack/services/events/streaming.js +9 -12
  26. package/dist/slack/services/events/utils.d.ts +26 -5
  27. package/dist/slack/services/events/utils.js +40 -15
  28. package/dist/slack/services/index.d.ts +4 -4
  29. package/dist/slack/services/index.js +3 -3
  30. package/dist/slack/services/link-prompt.d.ts +27 -0
  31. package/dist/slack/services/link-prompt.js +142 -0
  32. package/dist/slack/services/modals.d.ts +1 -0
  33. package/dist/slack/services/modals.js +6 -4
  34. package/dist/slack/services/resume-intent.d.ts +15 -0
  35. package/dist/slack/services/resume-intent.js +338 -0
  36. package/dist/slack/tracer.d.ts +1 -1
  37. 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: "trace" | "debug" | "info" | "warn" | "error";
47
+ LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
48
48
  INKEEP_AGENTS_RUN_DATABASE_URL?: string | undefined;
49
49
  INKEEP_AGENTS_MANAGE_UI_URL?: string | undefined;
50
50
  GITHUB_APP_ID?: string | undefined;
@@ -1,7 +1,7 @@
1
- import * as hono1 from "hono";
1
+ import * as hono0 from "hono";
2
2
 
3
3
  //#region src/github/mcp/auth.d.ts
4
- declare const githubMcpAuth: () => hono1.MiddlewareHandler<{
4
+ declare const githubMcpAuth: () => hono0.MiddlewareHandler<{
5
5
  Variables: {
6
6
  toolId: string;
7
7
  };
@@ -1,11 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types9 from "hono/types";
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
- }, hono_types9.BlankSchema, "/">;
9
+ }, hono_types7.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import runDbClient_default from "../../db/runDbClient.js";
2
2
  import { githubMcpAuth } from "./auth.js";
3
3
  import { ReactionContentSchema } from "./schemas.js";
4
- import { commitFileChanges, commitNewFile, createIssueCommentReaction, createPullRequestReviewCommentReaction, deleteIssueCommentReaction, deletePullRequestReviewCommentReaction, 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 branch_name = `docs-writer-ai-update-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
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-comment-reaction", `Add a reaction to a comment on a pull request. Supports general pull request comments and inline PR review comments. ${getAvailableRepositoryString(repositoryAccess)}`, {
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
- comment_id: z.number().describe("The ID of the comment to react to"),
673
- comment_type: z.enum(["issue_comment", "review_comment"]).describe("The type of comment: \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments"),
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, comment_id, comment_type, reaction }) => {
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 ${comment_type} comment ${comment_id} in ${owner}/${repo}\n\nReaction ID: ${(comment_type === "issue_comment" ? await createIssueCommentReaction(githubClient, owner, repo, comment_id, reaction) : await createPullRequestReviewCommentReaction(githubClient, owner, repo, comment_id, reaction)).id}`
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: `Comment ${comment_id} not found in ${owner}/${repo}.`
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 comment exists.`
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 comment ${comment_id} in ${owner}/${repo}. Your GitHub App may not have sufficient permissions to create reactions.`
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-comment-reaction", `Remove a reaction from a comment on a pull request. Requires the reaction ID (returned when adding a reaction or available from comment data). ${getAvailableRepositoryString(repositoryAccess)}`, {
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
- comment_id: z.number().describe("The ID of the comment the reaction belongs to"),
731
- comment_type: z.enum(["issue_comment", "review_comment"]).describe("The type of comment: \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments"),
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, comment_id, comment_type, reaction_id }) => {
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 (comment_type === "issue_comment") await deleteIssueCommentReaction(githubClient, owner, repo, comment_id, reaction_id);
748
- else await deletePullRequestReviewCommentReaction(githubClient, owner, repo, comment_id, reaction_id);
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 ${comment_type} comment ${comment_id} in ${owner}/${repo}`
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: `Comment ${comment_id} or reaction ${reaction_id} not found in ${owner}/${repo}.`
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-comment-reactions", `List all reactions on a comment, including each reaction's ID (needed for removal). Supports both general issue/PR comments and inline PR review comments. ${getAvailableRepositoryString(repositoryAccess)}`, {
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
- comment_id: z.number().describe("The ID of the comment to list reactions for"),
776
- comment_type: z.enum(["issue_comment", "review_comment"]).describe("The type of comment: \"issue_comment\" for general pull request comments, \"review_comment\" for inline PR review comments")
777
- }, async ({ owner, repo, comment_id, comment_type }) => {
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
- const reactions = comment_type === "issue_comment" ? await listIssueCommentReactions(githubClient, owner, repo, comment_id) : await listPullRequestReviewCommentReactions(githubClient, owner, repo, comment_id);
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 ${comment_type} comment ${comment_id} in ${owner}/${repo}.`
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 ${comment_type} comment ${comment_id} in ${owner}/${repo}:\n\n${formatted}`
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: `Comment ${comment_id} not found in ${owner}/${repo}.`
843
+ text: `Target ${target_id} (${target_type}) not found in ${owner}/${repo}.`
807
844
  }],
808
845
  isError: true
809
846
  };
@@ -76,8 +76,8 @@ declare const ChangedFileSchema: z.ZodObject<{
76
76
  path: z.ZodString;
77
77
  status: z.ZodEnum<{
78
78
  added: "added";
79
- removed: "removed";
80
79
  modified: "modified";
80
+ removed: "removed";
81
81
  renamed: "renamed";
82
82
  copied: "copied";
83
83
  changed: "changed";
@@ -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 };
@@ -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 hono_types3 from "hono/types";
2
+ import * as hono_types5 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
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 hono_types5 from "hono/types";
2
+ import * as hono_types8 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/tokenExchange.d.ts
5
- declare const app: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
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 hono_types7 from "hono/types";
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<hono_types7.BlankEnv, hono_types7.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -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
- logger.warn({ error: verifyResult.error }, "Invalid link token");
109
- return c.json({ error: verifyResult.error || "Invalid or expired link token. Please run /inkeep link in Slack to get a new one." }, 400);
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,
@@ -8,6 +8,7 @@
8
8
  /** Configuration for a resolved agent */
9
9
  interface ResolvedAgentConfig {
10
10
  projectId: string;
11
+ projectName?: string;
11
12
  agentId: string;
12
13
  agentName?: string;
13
14
  source: 'channel' | 'workspace' | 'none';
@@ -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
- return {
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
- const workspaceConfig = await getWorkspaceDefaultAgentFromNango(teamId);
46
- if (workspaceConfig?.agentId && workspaceConfig.projectId) {
47
- logger.info({
48
- teamId,
49
- agentId: workspaceConfig.agentId,
50
- source: "workspace"
51
- }, "Resolved agent from workspace config");
52
- return {
53
- projectId: workspaceConfig.projectId,
54
- agentId: workspaceConfig.agentId,
55
- agentName: workspaceConfig.agentName,
56
- source: "workspace",
57
- grantAccessToMembers: workspaceConfig.grantAccessToMembers ?? true
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
- logger.debug({
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 null;
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: channelConfig || workspaceConfig
185
+ effective
99
186
  };
100
187
  }
101
188