@inkeep/agents-work-apps 0.48.7 → 0.50.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.
@@ -4,10 +4,10 @@ import "./routes/setup.js";
4
4
  import "./routes/tokenExchange.js";
5
5
  import { WebhookVerificationResult, verifyWebhookSignature } from "./routes/webhooks.js";
6
6
  import { Hono } from "hono";
7
- import * as hono_types4 from "hono/types";
7
+ import * as hono_types0 from "hono/types";
8
8
 
9
9
  //#region src/github/index.d.ts
10
- declare function createGithubRoutes(): Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
11
- declare const githubRoutes: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
10
+ declare function createGithubRoutes(): Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
11
+ declare const githubRoutes: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
12
12
  //#endregion
13
13
  export { GenerateInstallationAccessTokenResult, GenerateTokenError, GenerateTokenResult, GitHubAppConfig, InstallationAccessToken, InstallationInfo, LookupInstallationError, LookupInstallationForRepoResult, LookupInstallationResult, WebhookVerificationResult, clearConfigCache, createAppJwt, createGithubRoutes, determineStatus, fetchInstallationDetails, fetchInstallationRepositories, generateInstallationAccessToken, getGitHubAppConfig, getGitHubAppName, getStateSigningSecret, getWebhookSecret, githubRoutes, isGitHubAppConfigured, isGitHubAppNameConfigured, isStateSigningConfigured, isWebhookConfigured, lookupInstallationForRepo, validateGitHubAppConfigOnStartup, validateGitHubInstallFlowConfigOnStartup, validateGitHubWebhookConfigOnStartup, verifyWebhookSignature };
@@ -1,11 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types0 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_types0.BlankSchema, "/">;
9
+ }, hono_types7.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -1,12 +1,13 @@
1
1
  import runDbClient_default from "../../db/runDbClient.js";
2
2
  import { githubMcpAuth } from "./auth.js";
3
- import { commitFileChanges, commitNewFile, fetchComments, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getGitHubClientFromRepo, visualizeUpdateOperations } from "./utils.js";
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";
5
+ import { z } from "@hono/zod-openapi";
4
6
  import { getMcpToolRepositoryAccessWithDetails } from "@inkeep/agents-core";
5
7
  import { Hono } from "hono";
6
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
9
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
8
10
  import { toFetchResponse, toReqRes } from "fetch-to-node";
9
- import { z } from "zod/v3";
10
11
 
11
12
  //#region src/github/mcp/index.ts
12
13
  const updateOperationSchema = z.object({
@@ -606,6 +607,157 @@ const getServer = async (toolId) => {
606
607
  };
607
608
  }
608
609
  });
610
+ 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)}`, {
611
+ owner: z.string().describe("Repository owner name"),
612
+ repo: z.string().describe("Repository name"),
613
+ comment_id: z.number().describe("The ID of the comment to react to"),
614
+ 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"),
615
+ reaction: ReactionContentSchema.describe("The reaction emoji to add: +1, -1, laugh, hooray, confused, heart, rocket, or eyes")
616
+ }, async ({ owner, repo, comment_id, comment_type, reaction }) => {
617
+ try {
618
+ let githubClient;
619
+ try {
620
+ githubClient = getGitHubClientFromRepo(owner, repo, installationIdMap);
621
+ } catch (error) {
622
+ return {
623
+ content: [{
624
+ type: "text",
625
+ text: `Error accessing GitHub: ${error instanceof Error ? error.message : "Unknown error"}`
626
+ }],
627
+ isError: true
628
+ };
629
+ }
630
+ return { content: [{
631
+ type: "text",
632
+ 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}`
633
+ }] };
634
+ } catch (error) {
635
+ if (error instanceof Error && "status" in error) {
636
+ const apiError = error;
637
+ if (apiError.status === 404) return {
638
+ content: [{
639
+ type: "text",
640
+ text: `Comment ${comment_id} not found in ${owner}/${repo}.`
641
+ }],
642
+ isError: true
643
+ };
644
+ if (apiError.status === 422) return {
645
+ content: [{
646
+ type: "text",
647
+ text: `Invalid reaction. Ensure the reaction type is valid and the comment exists.`
648
+ }],
649
+ isError: true
650
+ };
651
+ if (apiError.status === 403) return {
652
+ content: [{
653
+ type: "text",
654
+ text: `Access denied when adding reaction to comment ${comment_id} in ${owner}/${repo}. Your GitHub App may not have sufficient permissions to create reactions.`
655
+ }],
656
+ isError: true
657
+ };
658
+ }
659
+ return {
660
+ content: [{
661
+ type: "text",
662
+ text: `Error adding reaction: ${error instanceof Error ? error.message : "Unknown error"}`
663
+ }],
664
+ isError: true
665
+ };
666
+ }
667
+ });
668
+ 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)}`, {
669
+ owner: z.string().describe("Repository owner name"),
670
+ repo: z.string().describe("Repository name"),
671
+ comment_id: z.number().describe("The ID of the comment the reaction belongs to"),
672
+ 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
+ reaction_id: z.number().describe("The ID of the reaction to remove")
674
+ }, async ({ owner, repo, comment_id, comment_type, reaction_id }) => {
675
+ try {
676
+ let githubClient;
677
+ try {
678
+ githubClient = getGitHubClientFromRepo(owner, repo, installationIdMap);
679
+ } catch (error) {
680
+ return {
681
+ content: [{
682
+ type: "text",
683
+ text: `Error accessing GitHub: ${error instanceof Error ? error.message : "Unknown error"}`
684
+ }],
685
+ isError: true
686
+ };
687
+ }
688
+ if (comment_type === "issue_comment") await deleteIssueCommentReaction(githubClient, owner, repo, comment_id, reaction_id);
689
+ else await deletePullRequestReviewCommentReaction(githubClient, owner, repo, comment_id, reaction_id);
690
+ return { content: [{
691
+ type: "text",
692
+ text: `Successfully removed reaction ${reaction_id} from ${comment_type} comment ${comment_id} in ${owner}/${repo}`
693
+ }] };
694
+ } catch (error) {
695
+ if (error instanceof Error && "status" in error) {
696
+ if (error.status === 404) return {
697
+ content: [{
698
+ type: "text",
699
+ text: `Comment ${comment_id} or reaction ${reaction_id} not found in ${owner}/${repo}.`
700
+ }],
701
+ isError: true
702
+ };
703
+ }
704
+ return {
705
+ content: [{
706
+ type: "text",
707
+ text: `Error removing reaction: ${error instanceof Error ? error.message : "Unknown error"}`
708
+ }],
709
+ isError: true
710
+ };
711
+ }
712
+ });
713
+ 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)}`, {
714
+ owner: z.string().describe("Repository owner name"),
715
+ repo: z.string().describe("Repository name"),
716
+ comment_id: z.number().describe("The ID of the comment to list reactions for"),
717
+ 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")
718
+ }, async ({ owner, repo, comment_id, comment_type }) => {
719
+ try {
720
+ let githubClient;
721
+ try {
722
+ githubClient = getGitHubClientFromRepo(owner, repo, installationIdMap);
723
+ } catch (error) {
724
+ return {
725
+ content: [{
726
+ type: "text",
727
+ text: `Error accessing GitHub: ${error instanceof Error ? error.message : "Unknown error"}`
728
+ }],
729
+ isError: true
730
+ };
731
+ }
732
+ const reactions = comment_type === "issue_comment" ? await listIssueCommentReactions(githubClient, owner, repo, comment_id) : await listPullRequestReviewCommentReactions(githubClient, owner, repo, comment_id);
733
+ if (reactions.length === 0) return { content: [{
734
+ type: "text",
735
+ text: `No reactions found on ${comment_type} comment ${comment_id} in ${owner}/${repo}.`
736
+ }] };
737
+ const formatted = reactions.map((r) => `• ${r.content} by @${r.user} (reaction_id: ${r.id})`).join("\n");
738
+ return { content: [{
739
+ type: "text",
740
+ text: `Found ${reactions.length} reaction(s) on ${comment_type} comment ${comment_id} in ${owner}/${repo}:\n\n${formatted}`
741
+ }] };
742
+ } catch (error) {
743
+ if (error instanceof Error && "status" in error) {
744
+ if (error.status === 404) return {
745
+ content: [{
746
+ type: "text",
747
+ text: `Comment ${comment_id} not found in ${owner}/${repo}.`
748
+ }],
749
+ isError: true
750
+ };
751
+ }
752
+ return {
753
+ content: [{
754
+ type: "text",
755
+ text: `Error listing reactions: ${error instanceof Error ? error.message : "Unknown error"}`
756
+ }],
757
+ isError: true
758
+ };
759
+ }
760
+ });
609
761
  server.tool("visualize-update-operations", `Apply a list of operations to a piece of documentation and return a mapping of line number to line content. ${getAvailableRepositoryString(repositoryAccess)}`, {
610
762
  owner: z.string().describe("Repository owner name"),
611
763
  repo: z.string().describe("Repository name"),
@@ -11,6 +11,46 @@ declare const RepositorySchema: z.ZodObject<{
11
11
  url: z.ZodString;
12
12
  defaultBranch: z.ZodString;
13
13
  }, z.core.$strip>;
14
+ declare const ReactionContentSchema: z.ZodEnum<{
15
+ "+1": "+1";
16
+ [-1]: "-1";
17
+ laugh: "laugh";
18
+ hooray: "hooray";
19
+ confused: "confused";
20
+ heart: "heart";
21
+ rocket: "rocket";
22
+ eyes: "eyes";
23
+ }>;
24
+ declare const ReactionSchema: z.ZodObject<{
25
+ id: z.ZodNumber;
26
+ user: z.ZodString;
27
+ content: z.ZodEnum<{
28
+ "+1": "+1";
29
+ [-1]: "-1";
30
+ laugh: "laugh";
31
+ hooray: "hooray";
32
+ confused: "confused";
33
+ heart: "heart";
34
+ rocket: "rocket";
35
+ eyes: "eyes";
36
+ }>;
37
+ createdAt: z.ZodString;
38
+ }, z.core.$strip>;
39
+ declare const ReactionsSchema: z.ZodArray<z.ZodObject<{
40
+ id: z.ZodNumber;
41
+ user: z.ZodString;
42
+ content: z.ZodEnum<{
43
+ "+1": "+1";
44
+ [-1]: "-1";
45
+ laugh: "laugh";
46
+ hooray: "hooray";
47
+ confused: "confused";
48
+ heart: "heart";
49
+ rocket: "rocket";
50
+ eyes: "eyes";
51
+ }>;
52
+ createdAt: z.ZodString;
53
+ }, z.core.$strip>>;
14
54
  declare const PullRequestSchema: z.ZodObject<{
15
55
  number: z.ZodNumber;
16
56
  title: z.ZodString;
@@ -36,8 +76,8 @@ declare const ChangedFileSchema: z.ZodObject<{
36
76
  path: z.ZodString;
37
77
  status: z.ZodEnum<{
38
78
  added: "added";
39
- modified: "modified";
40
79
  removed: "removed";
80
+ modified: "modified";
41
81
  renamed: "renamed";
42
82
  copied: "copied";
43
83
  changed: "changed";
@@ -73,6 +113,21 @@ declare const CommentSchema: z.ZodObject<{
73
113
  DISMISSED: "DISMISSED";
74
114
  PENDING: "PENDING";
75
115
  }>>;
116
+ reactions: z.ZodOptional<z.ZodArray<z.ZodObject<{
117
+ id: z.ZodNumber;
118
+ user: z.ZodString;
119
+ content: z.ZodEnum<{
120
+ "+1": "+1";
121
+ [-1]: "-1";
122
+ laugh: "laugh";
123
+ hooray: "hooray";
124
+ confused: "confused";
125
+ heart: "heart";
126
+ rocket: "rocket";
127
+ eyes: "eyes";
128
+ }>;
129
+ createdAt: z.ZodString;
130
+ }, z.core.$strip>>>;
76
131
  }, z.core.$strip>;
77
132
  declare const GitHubEventSchema: z.ZodObject<{
78
133
  type: z.ZodString;
@@ -83,6 +138,9 @@ type Repository = z.infer<typeof RepositorySchema>;
83
138
  type PullRequest = z.infer<typeof PullRequestSchema>;
84
139
  type ChangedFile = z.infer<typeof ChangedFileSchema>;
85
140
  type Comment = z.infer<typeof CommentSchema>;
141
+ type Reaction = z.infer<typeof ReactionSchema>;
142
+ type Reactions = z.infer<typeof ReactionsSchema>;
86
143
  type GitHubEvent = z.infer<typeof GitHubEventSchema>;
144
+ type ReactionContent = z.infer<typeof ReactionContentSchema>;
87
145
  //#endregion
88
- export { ChangedFile, ChangedFileSchema, Comment, CommentSchema, GitHubEvent, GitHubEventSchema, GitHubUser, GitHubUserSchema, PullRequest, PullRequestSchema, Repository, RepositorySchema };
146
+ export { ChangedFile, ChangedFileSchema, Comment, CommentSchema, GitHubEvent, GitHubEventSchema, GitHubUser, GitHubUserSchema, PullRequest, PullRequestSchema, Reaction, ReactionContent, ReactionContentSchema, ReactionSchema, Reactions, ReactionsSchema, Repository, RepositorySchema };
@@ -9,6 +9,23 @@ const RepositorySchema = z.object({
9
9
  url: z.string().url(),
10
10
  defaultBranch: z.string()
11
11
  });
12
+ const ReactionContentSchema = z.enum([
13
+ "+1",
14
+ "-1",
15
+ "laugh",
16
+ "hooray",
17
+ "confused",
18
+ "heart",
19
+ "rocket",
20
+ "eyes"
21
+ ]);
22
+ const ReactionSchema = z.object({
23
+ id: z.number(),
24
+ user: z.string(),
25
+ content: ReactionContentSchema,
26
+ createdAt: z.string()
27
+ });
28
+ const ReactionsSchema = z.array(ReactionSchema);
12
29
  const PullRequestSchema = z.object({
13
30
  number: z.number(),
14
31
  title: z.string(),
@@ -66,7 +83,8 @@ const CommentSchema = z.object({
66
83
  "COMMENTED",
67
84
  "DISMISSED",
68
85
  "PENDING"
69
- ]).optional()
86
+ ]).optional(),
87
+ reactions: ReactionsSchema.optional()
70
88
  });
71
89
  const GitHubEventSchema = z.object({
72
90
  type: z.string(),
@@ -74,4 +92,4 @@ const GitHubEventSchema = z.object({
74
92
  });
75
93
 
76
94
  //#endregion
77
- export { ChangedFileSchema, CommentSchema, GitHubEventSchema, GitHubUserSchema, PullRequestSchema, RepositorySchema };
95
+ export { ChangedFileSchema, CommentSchema, GitHubEventSchema, GitHubUserSchema, PullRequestSchema, ReactionContentSchema, ReactionSchema, ReactionsSchema, RepositorySchema };
@@ -1,4 +1,4 @@
1
- import { ChangedFile, Comment, PullRequest } from "./schemas.js";
1
+ import { ChangedFile, Comment, PullRequest, ReactionContent } from "./schemas.js";
2
2
  import { Octokit } from "@octokit/rest";
3
3
 
4
4
  //#region src/github/mcp/utils.d.ts
@@ -227,6 +227,24 @@ declare function commitNewFile({
227
227
  content: string;
228
228
  commitMessage: string;
229
229
  }): Promise<string>;
230
+ declare function createIssueCommentReaction(octokit: Octokit, owner: string, repo: string, commentId: number, content: ReactionContent): Promise<{
231
+ id: number;
232
+ content: string;
233
+ }>;
234
+ declare function deleteIssueCommentReaction(octokit: Octokit, owner: string, repo: string, commentId: number, reactionId: number): Promise<void>;
235
+ declare function createPullRequestReviewCommentReaction(octokit: Octokit, owner: string, repo: string, commentId: number, content: ReactionContent): Promise<{
236
+ id: number;
237
+ content: string;
238
+ }>;
239
+ declare function deletePullRequestReviewCommentReaction(octokit: Octokit, owner: string, repo: string, commentId: number, reactionId: number): Promise<void>;
240
+ interface ReactionDetail {
241
+ id: number;
242
+ content: ReactionContent;
243
+ user: string;
244
+ createdAt: string;
245
+ }
246
+ declare function listIssueCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
247
+ declare function listPullRequestReviewCommentReactions(octokit: Octokit, owner: string, repo: string, commentId: number): Promise<ReactionDetail[]>;
230
248
  declare function formatFileDiff(pullRequestNumber: number, files: ChangedFile[], includeContents?: boolean): Promise<string>;
231
249
  //#endregion
232
- export { CommitData, LLMUpdateOperation, PullCommit, applyOperation, applyOperations, commitFileChanges, commitNewFile, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, validateLineNumbers, visualizeUpdateOperations };
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 };
@@ -39,6 +39,42 @@ function mapUser(user) {
39
39
  return { login: user.login };
40
40
  }
41
41
  /**
42
+ * Fetch detailed reactions for an issue comment (with user attribution)
43
+ */
44
+ async function fetchIssueCommentReactions(octokit, owner, repo, commentId) {
45
+ const reactions = [];
46
+ for await (const response of octokit.paginate.iterator(octokit.rest.reactions.listForIssueComment, {
47
+ owner,
48
+ repo,
49
+ comment_id: commentId,
50
+ per_page: 100
51
+ })) for (const reaction of response.data) reactions.push({
52
+ id: reaction.id,
53
+ user: reaction.user?.login ?? "[deleted]",
54
+ content: reaction.content,
55
+ createdAt: reaction.created_at
56
+ });
57
+ return reactions;
58
+ }
59
+ /**
60
+ * Fetch detailed reactions for a pull request review comment (with user attribution)
61
+ */
62
+ async function fetchReviewCommentReactions(octokit, owner, repo, commentId) {
63
+ const reactions = [];
64
+ for await (const response of octokit.paginate.iterator(octokit.rest.reactions.listForPullRequestReviewComment, {
65
+ owner,
66
+ repo,
67
+ comment_id: commentId,
68
+ per_page: 100
69
+ })) for (const reaction of response.data) reactions.push({
70
+ id: reaction.id,
71
+ user: reaction.user?.login ?? "[deleted]",
72
+ content: reaction.content,
73
+ createdAt: reaction.created_at
74
+ });
75
+ return reactions;
76
+ }
77
+ /**
42
78
  * Fetch pull request details
43
79
  */
44
80
  async function fetchPrInfo(octokit, owner, repo, prNumber) {
@@ -241,6 +277,18 @@ async function fetchComments(octokit, owner, repo, prNumber) {
241
277
  issue_number: prNumber,
242
278
  per_page: 100
243
279
  })) for (const comment of response.data) {
280
+ let reactions;
281
+ if (comment.reactions && comment.reactions.total_count > 0) try {
282
+ reactions = await fetchIssueCommentReactions(octokit, owner, repo, comment.id);
283
+ } catch (error) {
284
+ logger.warn({
285
+ owner,
286
+ repo,
287
+ prNumber,
288
+ commentId: comment.id
289
+ }, `Failed to fetch issue comment reactions: ${error}`);
290
+ reactions = void 0;
291
+ }
244
292
  if (!comment.user) continue;
245
293
  results.push({
246
294
  id: comment.id,
@@ -248,7 +296,8 @@ async function fetchComments(octokit, owner, repo, prNumber) {
248
296
  author: mapUser(comment.user),
249
297
  createdAt: comment.created_at,
250
298
  updatedAt: comment.updated_at,
251
- type: "issue"
299
+ type: "issue",
300
+ reactions
252
301
  });
253
302
  }
254
303
  return results;
@@ -262,6 +311,18 @@ async function fetchComments(octokit, owner, repo, prNumber) {
262
311
  per_page: 100
263
312
  })) for (const comment of response.data) {
264
313
  const isSuggestion = /```suggestion\b/.test(comment.body);
314
+ let reactions;
315
+ if (comment.reactions && comment.reactions.total_count > 0) try {
316
+ reactions = await fetchReviewCommentReactions(octokit, owner, repo, comment.id);
317
+ } catch (error) {
318
+ logger.warn({
319
+ owner,
320
+ repo,
321
+ prNumber,
322
+ commentId: comment.id
323
+ }, `Failed to fetch review comment reactions: ${error}`);
324
+ reactions = void 0;
325
+ }
265
326
  results.push({
266
327
  id: comment.id,
267
328
  body: comment.body,
@@ -272,7 +333,8 @@ async function fetchComments(octokit, owner, repo, prNumber) {
272
333
  path: comment.path,
273
334
  line: comment.line || comment.original_line,
274
335
  diffHunk: comment.diff_hunk,
275
- isSuggestion
336
+ isSuggestion,
337
+ reactions
276
338
  });
277
339
  }
278
340
  return results;
@@ -331,34 +393,24 @@ function generatePrMarkdown(pr, fileDiffs, comments, owner, repo) {
331
393
  markdown += "</files>\n\n";
332
394
  if (comments.length > 0) {
333
395
  markdown += "<comments>\n";
334
- const reviewSummaries = comments.filter((c) => c.type === "review_summary");
335
- const issueComments = comments.filter((c) => c.type === "issue");
336
- const reviewComments = comments.filter((c) => c.type === "review");
337
- for (const review of reviewSummaries) {
338
- markdown += `[${review.state}] @${review.author.login} (${new Date(review.createdAt).toLocaleDateString()})\n`;
339
- if (review.body) markdown += `${review.body}\n`;
340
- markdown += "\n";
341
- }
342
- for (const comment of issueComments) {
343
- markdown += `@${comment.author.login} (${new Date(comment.createdAt).toLocaleDateString()})\n`;
344
- markdown += `${comment.body}\n\n`;
345
- }
346
- if (reviewComments.length > 0) {
347
- const commentsByFile = /* @__PURE__ */ new Map();
348
- for (const comment of reviewComments) {
349
- const path = comment.path || "unknown";
350
- const existing = commentsByFile.get(path) || [];
351
- existing.push(comment);
352
- commentsByFile.set(path, existing);
353
- }
354
- for (const [path, fileComments] of commentsByFile) {
355
- markdown += `${path}:\n`;
356
- for (const comment of fileComments) {
357
- const lineInfo = comment.line ? ` line ${comment.line}` : "";
358
- markdown += ` @${comment.author.login}${lineInfo} (${new Date(comment.createdAt).toLocaleDateString()})\n`;
359
- markdown += ` ${comment.body}\n\n`;
360
- }
396
+ const sorted = [...comments].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
397
+ for (const comment of sorted) {
398
+ const date = new Date(comment.createdAt).toLocaleDateString();
399
+ const author = comment.author.login;
400
+ if (comment.type === "review_summary") markdown += `[review_summary] user:${author} comment_id:${comment.id} (${date})\n`;
401
+ else if (comment.type === "review") {
402
+ const lineInfo = comment.line ? `:${comment.line}` : "";
403
+ markdown += `[review_comment] user:${author} on ${comment.path}${lineInfo} comment_id:${comment.id} (${date})\n`;
404
+ } else markdown += `[issue_comment] user:${author} comment_id:${comment.id} (${date})\n`;
405
+ markdown += `${comment.body}\n`;
406
+ if (comment.reactions && comment.reactions.length > 0) {
407
+ const reactionCounts = /* @__PURE__ */ new Map();
408
+ for (const r of comment.reactions) reactionCounts.set(r.content, (reactionCounts.get(r.content) || 0) + 1);
409
+ const parts = [];
410
+ for (const [emoji, count] of reactionCounts) parts.push(count > 1 ? `${emoji} x${count}` : emoji);
411
+ markdown += `reactions: ${parts.join(", ")}\n`;
361
412
  }
413
+ markdown += "\n";
362
414
  }
363
415
  markdown += "</comments>\n";
364
416
  }
@@ -550,6 +602,76 @@ async function commitNewFile({ githubClient, owner, repo, filePath, branchName,
550
602
  throw new Error(`Failed to commit new file: ${error instanceof Error ? error.message : "Unknown error"}`);
551
603
  }
552
604
  }
605
+ async function createIssueCommentReaction(octokit, owner, repo, commentId, content) {
606
+ const { data } = await octokit.rest.reactions.createForIssueComment({
607
+ owner,
608
+ repo,
609
+ comment_id: commentId,
610
+ content
611
+ });
612
+ return {
613
+ id: data.id,
614
+ content: data.content
615
+ };
616
+ }
617
+ async function deleteIssueCommentReaction(octokit, owner, repo, commentId, reactionId) {
618
+ await octokit.rest.reactions.deleteForIssueComment({
619
+ owner,
620
+ repo,
621
+ comment_id: commentId,
622
+ reaction_id: reactionId
623
+ });
624
+ }
625
+ async function createPullRequestReviewCommentReaction(octokit, owner, repo, commentId, content) {
626
+ const { data } = await octokit.rest.reactions.createForPullRequestReviewComment({
627
+ owner,
628
+ repo,
629
+ comment_id: commentId,
630
+ content
631
+ });
632
+ return {
633
+ id: data.id,
634
+ content: data.content
635
+ };
636
+ }
637
+ async function deletePullRequestReviewCommentReaction(octokit, owner, repo, commentId, reactionId) {
638
+ await octokit.rest.reactions.deleteForPullRequestComment({
639
+ owner,
640
+ repo,
641
+ comment_id: commentId,
642
+ reaction_id: reactionId
643
+ });
644
+ }
645
+ async function listIssueCommentReactions(octokit, owner, repo, commentId) {
646
+ const reactions = [];
647
+ for await (const response of octokit.paginate.iterator(octokit.rest.reactions.listForIssueComment, {
648
+ owner,
649
+ repo,
650
+ comment_id: commentId,
651
+ per_page: 100
652
+ })) for (const r of response.data) reactions.push({
653
+ id: r.id,
654
+ content: r.content,
655
+ user: r.user?.login ?? "unknown",
656
+ createdAt: r.created_at
657
+ });
658
+ return reactions;
659
+ }
660
+ async function listPullRequestReviewCommentReactions(octokit, owner, repo, commentId) {
661
+ const reactions = [];
662
+ for await (const response of octokit.paginate.iterator(octokit.rest.reactions.listForPullRequestReviewComment, {
663
+ owner,
664
+ repo,
665
+ comment_id: commentId,
666
+ per_page: 100
667
+ })) for (const r of response.data) reactions.push({
668
+ id: r.id,
669
+ content: r.content,
670
+ user: r.user?.login ?? "unknown",
671
+ createdAt: r.created_at
672
+ });
673
+ return reactions;
674
+ }
553
675
  async function formatFileDiff(pullRequestNumber, files, includeContents = false) {
554
676
  let output = `## File Patches for PR #${pullRequestNumber}\n\n`;
555
677
  output += `Found ${files.length} file(s) matching the requested paths.\n\n`;
@@ -572,4 +694,4 @@ async function formatFileDiff(pullRequestNumber, files, includeContents = false)
572
694
  }
573
695
 
574
696
  //#endregion
575
- export { applyOperation, applyOperations, commitFileChanges, commitNewFile, fetchComments, fetchCommitDetails, fetchPrCommits, fetchPrFileDiffs, fetchPrFiles, fetchPrInfo, formatFileDiff, generatePrMarkdown, getFilePathsInRepo, getGitHubClientFromInstallationId, getGitHubClientFromRepo, validateLineNumbers, visualizeUpdateOperations };
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 };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types8 from "hono/types";
2
+ import * as hono_types3 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types8.BlankEnv, hono_types8.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -5,8 +5,8 @@ import { getStateSigningSecret, isStateSigningConfigured } from "../config.js";
5
5
  import { createAppJwt, determineStatus, fetchInstallationDetails, fetchInstallationRepositories } from "../installation.js";
6
6
  import { createInstallation, generateId, getInstallationByGitHubId, listProjectsMetadata, setProjectAccessMode, syncRepositories, updateInstallationStatusByGitHubId } from "@inkeep/agents-core";
7
7
  import { Hono } from "hono";
8
- import { jwtVerify } from "jose";
9
8
  import { z } from "zod";
9
+ import { jwtVerify } from "jose";
10
10
 
11
11
  //#region src/github/routes/setup.ts
12
12
  const logger = getLogger("github-setup");
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types0 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_types0.BlankEnv, hono_types0.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_types2 from "hono/types";
2
+ import * as hono_types5 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_types2.BlankEnv, hono_types2.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -57,12 +57,7 @@ declare const SlackStrings: {
57
57
  readonly publicSection: string;
58
58
  readonly privateSection: string;
59
59
  readonly otherCommands: string;
60
- };
61
- readonly agentList: {
62
- readonly title: "🤖 Available Agents";
63
- readonly usage: "Usage:";
64
- readonly runUsage: "`/inkeep run \"agent name\" question` - Run a specific agent";
65
- readonly andMore: (count: number) => string;
60
+ readonly docsLink: "📖 <https://docs.inkeep.com/talk-to-your-agents/slack/overview|Learn more>";
66
61
  };
67
62
  readonly messageContext: {
68
63
  readonly label: "Message:";
@@ -38,7 +38,7 @@ const SlackStrings = {
38
38
  poweredBy: (agentName) => `Powered by *${agentName}* via Inkeep`,
39
39
  privateResponse: "_Private response_"
40
40
  },
41
- usage: { mentionEmpty: "*To use your Inkeep agent, include a message:*\n\n• `@Inkeep <message>` — Send a message to your agent (reply appears in a thread)\n• `@Inkeep <message>` in a thread — Includes the thread as context for your agent\n• `@Inkeep` in a thread — Triggers your agent using the full thread as context\n\n💡 Use `/inkeep help` for all available commands." },
41
+ usage: { mentionEmpty: "*To use your Inkeep agent, include a message:*\n\n• `@Inkeep <message>` — Send a message to the default agent for the channel (reply appears in a thread)\n• `@Inkeep <message>` in a thread — Includes the thread as context for your agent\n• `@Inkeep` in a thread — Triggers your agent using the full thread as context\n\n💡 Use `/inkeep help` for all available commands." },
42
42
  status: {
43
43
  thinking: (agentName) => `_${agentName} is thinking..._`,
44
44
  noAgentsAvailable: "No agents available",
@@ -50,15 +50,10 @@ const SlackStrings = {
50
50
  },
51
51
  help: {
52
52
  title: "Inkeep — How to Use",
53
- publicSection: "🔊 *Public* — everyone in the channel can see the response\n\n• `@Inkeep <message>` — Send a message to your agent\n• `@Inkeep <message>` in a thread — Includes thread as context\n• `@Inkeep` in a thread — Uses the full thread as context",
54
- privateSection: "🔒 *Private* — only you can see the response\n\n• `/inkeep <message>` — Send a message to your agent\n• `/inkeep` — Open the agent picker to choose an agent and prompt",
55
- otherCommands: "⚙️ *Other Commands*\n\n• `/inkeep run \"agent name\" <message>` — Use a specific agent\n• `/inkeep list` — List available agents\n• `/inkeep status` — Check your connection and agent config\n• `/inkeep link` / `/inkeep unlink` — Manage account connection\n• `/inkeep help` — Show this message"
56
- },
57
- agentList: {
58
- title: "🤖 Available Agents",
59
- usage: "Usage:",
60
- runUsage: "`/inkeep run \"agent name\" question` - Run a specific agent",
61
- andMore: (count) => `...and ${count} more`
53
+ publicSection: "🔊 *Public* — everyone in the channel can see the response\n\n• `@Inkeep <message>` — message the default agent in this channel\n• `@Inkeep <message>` in a thread — Includes thread as context\n• `@Inkeep` in a thread — Uses the full thread as context",
54
+ privateSection: "🔒 *Private* — only you can see the response\n\n• `/inkeep <message>` — message the default agent in this channel\n• `/inkeep` — Open the agent picker to choose an agent and write a prompt",
55
+ otherCommands: "⚙️ *Other Commands*\n\n• `/inkeep status` — Check your connection and agent config\n• `/inkeep link` / `/inkeep unlink` — Manage account connection\n• `/inkeep help` — Show this message",
56
+ docsLink: "📖 <https://docs.inkeep.com/talk-to-your-agents/slack/overview|Learn more>"
62
57
  },
63
58
  messageContext: { label: "Message:" }
64
59
  };
@@ -43,11 +43,6 @@ declare function buildConversationResponseBlocks(params: {
43
43
  isError: boolean;
44
44
  followUpParams: FollowUpButtonParams;
45
45
  }): any[];
46
- declare function createAgentListMessage(agents: Array<{
47
- id: string;
48
- name: string | null;
49
- projectName: string | null;
50
- }>, dashboardUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
51
46
  declare function createUpdatedHelpMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
52
47
  declare function createAlreadyLinkedMessage(email: string, linkedAt: string, dashboardUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
53
48
  declare function createUnlinkSuccessMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
@@ -70,4 +65,4 @@ interface AgentConfigSources {
70
65
  declare function createStatusMessage(email: string, linkedAt: string, dashboardUrl: string, agentConfigs: AgentConfigSources): Readonly<slack_block_builder0.SlackMessageDto>;
71
66
  declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
72
67
  //#endregion
73
- export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
68
+ export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -65,15 +65,8 @@ function buildConversationResponseBlocks(params) {
65
65
  }
66
66
  return blocks;
67
67
  }
68
- function createAgentListMessage(agents, dashboardUrl) {
69
- const agentList = agents.slice(0, 15).map((a) => `• ${Md.bold(a.name || a.id)} ${a.projectName ? `(${Md.italic(a.projectName)})` : ""}`).join("\n");
70
- const moreText = agents.length > 15 ? `\n\n${SlackStrings.agentList.andMore(agents.length - 15)}` : "";
71
- return Message().blocks(Blocks.Section().text(`${Md.bold(SlackStrings.agentList.title)}\n\n` + agentList + moreText + `
72
-
73
- ${Md.bold(SlackStrings.agentList.usage)}\n• ${SlackStrings.agentList.runUsage}`), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("view_agents"))).buildToObject();
74
- }
75
68
  function createUpdatedHelpMessage() {
76
- return Message().blocks(Blocks.Section().text(`${Md.bold(SlackStrings.help.title)}`), Blocks.Section().text(SlackStrings.help.publicSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.privateSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.otherCommands)).buildToObject();
69
+ return Message().blocks(Blocks.Section().text(`${Md.bold(SlackStrings.help.title)}`), Blocks.Section().text(SlackStrings.help.publicSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.privateSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.otherCommands), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.docsLink)).buildToObject();
77
70
  }
78
71
  function createAlreadyLinkedMessage(email, linkedAt, dashboardUrl) {
79
72
  return Message().blocks(Blocks.Section().text(Md.bold("✅ Already Linked!") + "\n\nYour Slack account is already connected to Inkeep.\n\n" + Md.bold("Inkeep Account:") + ` ${email}\n` + Md.bold("Linked:") + ` ${new Date(linkedAt).toLocaleDateString()}\n\nTo switch accounts, first run \`/inkeep unlink\``), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
@@ -100,4 +93,4 @@ function createJwtLinkMessage(linkUrl, expiresInMinutes) {
100
93
  }
101
94
 
102
95
  //#endregion
103
- export { buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
96
+ export { buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -12,8 +12,6 @@ declare function handleHelpCommand(): Promise<SlackCommandResponse>;
12
12
  */
13
13
  declare function handleAgentPickerCommand(payload: SlackCommandPayload, tenantId: string, workspaceConnection?: SlackWorkspaceConnection | null): Promise<SlackCommandResponse>;
14
14
  declare function handleQuestionCommand(payload: SlackCommandPayload, question: string, _dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
15
- declare function handleRunCommand(payload: SlackCommandPayload, agentIdentifier: string, question: string, _dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
16
- declare function handleAgentListCommand(payload: SlackCommandPayload, dashboardUrl: string, _tenantId: string): Promise<SlackCommandResponse>;
17
15
  declare function handleCommand(payload: SlackCommandPayload): Promise<SlackCommandResponse>;
18
16
  //#endregion
19
- export { handleAgentListCommand, handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand };
17
+ export { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand };
@@ -4,115 +4,16 @@ import runDbClient_default from "../../../db/runDbClient.js";
4
4
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
5
5
  import { resolveEffectiveAgent } from "../agent-resolution.js";
6
6
  import { SlackStrings } from "../../i18n/strings.js";
7
- import { createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
7
+ import { createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
8
8
  import { getSlackClient } from "../client.js";
9
9
  import { fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
10
10
  import { buildAgentSelectorModal } from "../modals.js";
11
11
  import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getWaitUntil, signSlackLinkToken, signSlackUserToken } from "@inkeep/agents-core";
12
12
 
13
13
  //#region src/slack/services/commands/index.ts
14
- /**
15
- * Fetch all agents from the manage API.
16
- * This uses the proper ref-middleware and Dolt branch resolution.
17
- * Requires an auth token to access the manage API.
18
- */
19
- const INTERNAL_FETCH_TIMEOUT_MS = 1e4;
20
- async function fetchAgentsFromManageApi(tenantId, authToken) {
21
- const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
22
- const controller = new AbortController();
23
- const timeout = setTimeout(() => controller.abort(), INTERNAL_FETCH_TIMEOUT_MS);
24
- try {
25
- const projectsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects`, {
26
- method: "GET",
27
- headers: {
28
- "Content-Type": "application/json",
29
- Authorization: `Bearer ${authToken}`
30
- },
31
- signal: controller.signal
32
- });
33
- if (!projectsResponse.ok) {
34
- logger.error({
35
- status: projectsResponse.status,
36
- tenantId
37
- }, "Failed to fetch projects from manage API");
38
- return [];
39
- }
40
- const projectsData = await projectsResponse.json();
41
- const projects = projectsData.data || projectsData || [];
42
- logger.info({
43
- projectCount: projects.length,
44
- tenantId
45
- }, "Fetched projects from manage API");
46
- return (await Promise.all(projects.map(async (project) => {
47
- try {
48
- const agentsResponse = await fetch(`${apiBaseUrl}/manage/tenants/${tenantId}/projects/${project.id}/agents`, {
49
- method: "GET",
50
- headers: {
51
- "Content-Type": "application/json",
52
- Authorization: `Bearer ${authToken}`
53
- },
54
- signal: controller.signal
55
- });
56
- if (agentsResponse.ok) {
57
- const agentsData = await agentsResponse.json();
58
- return (agentsData.data || agentsData || []).map((agent) => ({
59
- id: agent.id,
60
- name: agent.name,
61
- projectId: project.id,
62
- projectName: project.name
63
- }));
64
- }
65
- logger.warn({
66
- status: agentsResponse.status,
67
- projectId: project.id
68
- }, "Failed to fetch agents for project");
69
- return [];
70
- } catch (error) {
71
- logger.error({
72
- error,
73
- projectId: project.id
74
- }, "Failed to fetch agents for project");
75
- return [];
76
- }
77
- }))).flat();
78
- } catch (error) {
79
- logger.error({
80
- error,
81
- tenantId
82
- }, "Failed to fetch agents from manage API");
83
- return [];
84
- } finally {
85
- clearTimeout(timeout);
86
- }
87
- }
88
- /**
89
- * Find an agent by name or ID from the manage API.
90
- */
91
- async function findAgentByIdentifier(tenantId, identifier, authToken) {
92
- return (await fetchAgentsFromManageApi(tenantId, authToken)).find((a) => a.id === identifier || a.name?.toLowerCase() === identifier.toLowerCase()) || null;
93
- }
94
14
  const DEFAULT_CLIENT_ID = "work-apps-slack";
95
15
  const LINK_CODE_TTL_MINUTES = 10;
96
16
  const logger = getLogger("slack-commands");
97
- /**
98
- * Parse agent name and question from command text.
99
- * Agent name must be in quotes: "agent name" question
100
- */
101
- function parseAgentAndQuestion(text) {
102
- if (!text.trim()) return {
103
- agentName: null,
104
- question: null
105
- };
106
- const quotedMatch = text.match(/^["']([^"']+)["']\s+(.+)$/);
107
- if (quotedMatch) return {
108
- agentName: quotedMatch[1].trim(),
109
- question: quotedMatch[2].trim()
110
- };
111
- return {
112
- agentName: null,
113
- question: null
114
- };
115
- }
116
17
  async function handleLinkCommand(payload, dashboardUrl, tenantId) {
117
18
  const existingLink = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
118
19
  if (existingLink) return {
@@ -325,7 +226,7 @@ async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId)
325
226
  });
326
227
  if (!resolvedAgent) return {
327
228
  response_type: "ephemeral",
328
- ...createErrorMessage("No default agent configured. Ask your admin to set a workspace default in the dashboard.\n\nUse `/inkeep list` to see available agents.")
229
+ ...createErrorMessage("No default agent configured. Ask your admin to set a workspace default in the dashboard.")
329
230
  };
330
231
  const questionWork = executeAgentInBackground(payload, existingLink, {
331
232
  id: resolvedAgent.agentId,
@@ -431,85 +332,6 @@ async function executeAgentInBackground(payload, existingLink, targetAgent, ques
431
332
  });
432
333
  }
433
334
  }
434
- async function handleRunCommand(payload, agentIdentifier, question, _dashboardUrl, tenantId) {
435
- const existingLink = await findWorkAppSlackUserMappingBySlackUser(runDbClient_default)(payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
436
- if (!existingLink) return generateLinkCodeWithIntent(payload, tenantId);
437
- const userTenantId = existingLink.tenantId;
438
- try {
439
- const targetAgent = await findAgentByIdentifier(userTenantId, agentIdentifier, await signSlackUserToken({
440
- inkeepUserId: existingLink.inkeepUserId,
441
- tenantId: userTenantId,
442
- slackTeamId: payload.teamId,
443
- slackUserId: payload.userId,
444
- slackEnterpriseId: payload.enterpriseId
445
- }));
446
- if (!targetAgent) return {
447
- response_type: "ephemeral",
448
- ...createErrorMessage(`Agent "${agentIdentifier}" not found. Use \`/inkeep list\` to see available agents.`)
449
- };
450
- const runWork = executeAgentInBackground(payload, existingLink, targetAgent, question, userTenantId).catch((error) => {
451
- logger.error({ error }, "Background execution promise rejected");
452
- }).finally(() => flushTraces());
453
- const waitUntil = await getWaitUntil();
454
- if (waitUntil) waitUntil(runWork);
455
- return {};
456
- } catch (error) {
457
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
458
- logger.error({
459
- error: errorMessage,
460
- tenantId: userTenantId
461
- }, "Failed to run agent");
462
- return {
463
- response_type: "ephemeral",
464
- ...createErrorMessage("Failed to run agent. Please try again or visit the dashboard.")
465
- };
466
- }
467
- }
468
- async function handleAgentListCommand(payload, dashboardUrl, _tenantId) {
469
- const existingLink = await findWorkAppSlackUserMappingBySlackUser(runDbClient_default)(payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
470
- if (!existingLink) return {
471
- response_type: "ephemeral",
472
- ...createNotLinkedMessage()
473
- };
474
- const userTenantId = existingLink.tenantId;
475
- logger.info({
476
- slackUserId: payload.userId,
477
- existingLinkTenantId: existingLink.tenantId,
478
- existingLinkInkeepUserId: existingLink.inkeepUserId
479
- }, "Found user mapping for list command");
480
- try {
481
- const allAgents = await fetchAgentsFromManageApi(userTenantId, await signSlackUserToken({
482
- inkeepUserId: existingLink.inkeepUserId,
483
- tenantId: userTenantId,
484
- slackTeamId: payload.teamId,
485
- slackUserId: payload.userId,
486
- slackEnterpriseId: payload.enterpriseId
487
- }));
488
- logger.info({
489
- slackUserId: payload.userId,
490
- tenantId: userTenantId,
491
- agentCount: allAgents.length
492
- }, "Listed agents for linked Slack user");
493
- if (allAgents.length === 0) return {
494
- response_type: "ephemeral",
495
- ...createErrorMessage("No agents found. Create an agent in the Inkeep dashboard first.")
496
- };
497
- return {
498
- response_type: "ephemeral",
499
- ...createAgentListMessage(allAgents, dashboardUrl.replace("/work-apps/slack", ""))
500
- };
501
- } catch (error) {
502
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
503
- logger.error({
504
- error: errorMessage,
505
- tenantId: userTenantId
506
- }, "Failed to list agents");
507
- return {
508
- response_type: "ephemeral",
509
- ...createErrorMessage("Failed to list agents. Please try again or visit the dashboard.")
510
- };
511
- }
512
- }
513
335
  async function handleCommand(payload) {
514
336
  const text = payload.text.trim();
515
337
  const subcommand = text.split(/\s+/)[0]?.toLowerCase() || "";
@@ -538,15 +360,6 @@ async function handleCommand(payload) {
538
360
  case "unlink":
539
361
  case "logout":
540
362
  case "disconnect": return handleUnlinkCommand(payload, tenantId);
541
- case "list": return handleAgentListCommand(payload, dashboardUrl, tenantId);
542
- case "run": {
543
- const parsed = parseAgentAndQuestion(text.slice(4).trim());
544
- if (!parsed.agentName || !parsed.question) return {
545
- response_type: "ephemeral",
546
- ...createErrorMessage("Usage: `/inkeep run \"agent name\" [question]`\n\nExample: `/inkeep run \"my agent\" What is the weather?`\n\nAgent name must be in quotes.")
547
- };
548
- return handleRunCommand(payload, parsed.agentName, parsed.question, dashboardUrl, tenantId);
549
- }
550
363
  case "help": return handleHelpCommand();
551
364
  case "": return handleAgentPickerCommand(payload, tenantId, workspaceConnection);
552
365
  default: return handleQuestionCommand(payload, text, dashboardUrl, tenantId);
@@ -554,4 +367,4 @@ async function handleCommand(payload) {
554
367
  }
555
368
 
556
369
  //#endregion
557
- export { handleAgentListCommand, handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand };
370
+ export { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand };
@@ -8,9 +8,9 @@ import { AgentOption } from "../modals.js";
8
8
  * Called on every @mention and /inkeep command — caching avoids redundant DB queries.
9
9
  */
10
10
  declare function findCachedUserMapping(tenantId: string, slackUserId: string, teamId: string, clientId?: string): Promise<{
11
+ id: string;
11
12
  createdAt: string;
12
13
  updatedAt: string;
13
- id: string;
14
14
  tenantId: string;
15
15
  clientId: string;
16
16
  slackUserId: string;
@@ -1,9 +1,9 @@
1
1
  import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
2
- import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
2
+ import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
3
  import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
4
4
  import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
5
5
  import { SlackCommandPayload, SlackCommandResponse } from "./types.js";
6
- import { handleAgentListCommand, handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
6
+ import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
7
7
  import { InlineSelectorMetadata, handleAppMention } from "./events/app-mention.js";
8
8
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./events/block-actions.js";
9
9
  import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
@@ -13,4 +13,4 @@ import { StreamResult, streamAgentResponse } from "./events/streaming.js";
13
13
  import "./events/index.js";
14
14
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
15
15
  import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
16
- export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, WorkspaceInstallData, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAgentListMessage, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentListCommand, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, WorkspaceInstallData, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -1,10 +1,10 @@
1
1
  import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
2
2
  import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
3
- import { buildConversationResponseBlocks, buildFollowUpButton, createAgentListMessage, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
+ import { buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
4
4
  import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
5
5
  import { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
6
6
  import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
7
- import { handleAgentListCommand, handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
7
+ import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
8
8
  import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
9
9
  import { streamAgentResponse } from "./events/streaming.js";
10
10
  import { handleAppMention } from "./events/app-mention.js";
@@ -13,4 +13,4 @@ import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-
13
13
  import "./events/index.js";
14
14
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
15
15
 
16
- export { SlackErrorType, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAgentListMessage, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentListCommand, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleRunCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
16
+ export { SlackErrorType, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/agents-work-apps",
3
- "version": "0.48.7",
3
+ "version": "0.50.0",
4
4
  "description": "First party integrations for Inkeep Agents",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -33,7 +33,7 @@
33
33
  "jose": "^6.1.0",
34
34
  "minimatch": "^10.1.1",
35
35
  "slack-block-builder": "^2.8.0",
36
- "@inkeep/agents-core": "0.48.7"
36
+ "@inkeep/agents-core": "0.50.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@hono/zod-openapi": "^1.1.5",