@inkeep/agents-work-apps 0.47.5 → 0.48.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 (61) hide show
  1. package/dist/env.d.ts +24 -2
  2. package/dist/env.js +13 -2
  3. package/dist/github/mcp/index.d.ts +2 -2
  4. package/dist/github/mcp/index.js +23 -34
  5. package/dist/github/routes/setup.d.ts +2 -2
  6. package/dist/github/routes/webhooks.d.ts +2 -2
  7. package/dist/slack/i18n/index.d.ts +2 -0
  8. package/dist/slack/i18n/index.js +3 -0
  9. package/dist/slack/i18n/strings.d.ts +73 -0
  10. package/dist/slack/i18n/strings.js +67 -0
  11. package/dist/slack/index.d.ts +18 -0
  12. package/dist/slack/index.js +28 -0
  13. package/dist/slack/middleware/permissions.d.ts +31 -0
  14. package/dist/slack/middleware/permissions.js +167 -0
  15. package/dist/slack/routes/events.d.ts +10 -0
  16. package/dist/slack/routes/events.js +551 -0
  17. package/dist/slack/routes/index.d.ts +10 -0
  18. package/dist/slack/routes/index.js +47 -0
  19. package/dist/slack/routes/oauth.d.ts +20 -0
  20. package/dist/slack/routes/oauth.js +344 -0
  21. package/dist/slack/routes/users.d.ts +10 -0
  22. package/dist/slack/routes/users.js +365 -0
  23. package/dist/slack/routes/workspaces.d.ts +10 -0
  24. package/dist/slack/routes/workspaces.js +909 -0
  25. package/dist/slack/services/agent-resolution.d.ts +41 -0
  26. package/dist/slack/services/agent-resolution.js +99 -0
  27. package/dist/slack/services/blocks/index.d.ts +73 -0
  28. package/dist/slack/services/blocks/index.js +103 -0
  29. package/dist/slack/services/client.d.ts +108 -0
  30. package/dist/slack/services/client.js +232 -0
  31. package/dist/slack/services/commands/index.d.ts +19 -0
  32. package/dist/slack/services/commands/index.js +553 -0
  33. package/dist/slack/services/events/app-mention.d.ts +40 -0
  34. package/dist/slack/services/events/app-mention.js +304 -0
  35. package/dist/slack/services/events/block-actions.d.ts +40 -0
  36. package/dist/slack/services/events/block-actions.js +265 -0
  37. package/dist/slack/services/events/index.d.ts +6 -0
  38. package/dist/slack/services/events/index.js +7 -0
  39. package/dist/slack/services/events/modal-submission.d.ts +30 -0
  40. package/dist/slack/services/events/modal-submission.js +400 -0
  41. package/dist/slack/services/events/streaming.d.ts +26 -0
  42. package/dist/slack/services/events/streaming.js +272 -0
  43. package/dist/slack/services/events/utils.d.ts +146 -0
  44. package/dist/slack/services/events/utils.js +370 -0
  45. package/dist/slack/services/index.d.ts +16 -0
  46. package/dist/slack/services/index.js +16 -0
  47. package/dist/slack/services/modals.d.ts +86 -0
  48. package/dist/slack/services/modals.js +355 -0
  49. package/dist/slack/services/nango.d.ts +85 -0
  50. package/dist/slack/services/nango.js +476 -0
  51. package/dist/slack/services/security.d.ts +35 -0
  52. package/dist/slack/services/security.js +65 -0
  53. package/dist/slack/services/types.d.ts +26 -0
  54. package/dist/slack/services/types.js +1 -0
  55. package/dist/slack/services/workspace-tokens.d.ts +25 -0
  56. package/dist/slack/services/workspace-tokens.js +27 -0
  57. package/dist/slack/tracer.d.ts +40 -0
  58. package/dist/slack/tracer.js +39 -0
  59. package/dist/slack/types.d.ts +10 -0
  60. package/dist/slack/types.js +1 -0
  61. package/package.json +11 -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";
18
17
  trace: "trace";
19
18
  debug: "debug";
20
19
  info: "info";
21
20
  warn: "warn";
21
+ error: "error";
22
22
  }>>;
23
23
  INKEEP_AGENTS_RUN_DATABASE_URL: z.ZodString;
24
24
  INKEEP_AGENTS_MANAGE_UI_URL: z.ZodOptional<z.ZodString>;
@@ -28,11 +28,22 @@ declare const envSchema: z.ZodObject<{
28
28
  GITHUB_STATE_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
29
29
  GITHUB_APP_NAME: z.ZodOptional<z.ZodString>;
30
30
  GITHUB_MCP_API_KEY: z.ZodOptional<z.ZodString>;
31
+ SLACK_CLIENT_ID: z.ZodOptional<z.ZodString>;
32
+ SLACK_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
33
+ SLACK_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
34
+ SLACK_BOT_TOKEN: z.ZodOptional<z.ZodString>;
35
+ SLACK_APP_URL: z.ZodOptional<z.ZodString>;
36
+ NANGO_SECRET_KEY: z.ZodOptional<z.ZodString>;
37
+ NANGO_SLACK_SECRET_KEY: z.ZodOptional<z.ZodString>;
38
+ NANGO_SLACK_INTEGRATION_ID: z.ZodOptional<z.ZodString>;
39
+ NANGO_SERVER_URL: z.ZodOptional<z.ZodString>;
40
+ INKEEP_AGENTS_JWT_SIGNING_SECRET: z.ZodOptional<z.ZodString>;
41
+ INKEEP_AGENTS_API_URL: z.ZodOptional<z.ZodString>;
31
42
  }, z.core.$strip>;
32
43
  declare const env: {
33
44
  NODE_ENV: "development" | "production" | "test";
34
45
  ENVIRONMENT: "development" | "production" | "test" | "pentest";
35
- LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
46
+ LOG_LEVEL: "trace" | "debug" | "info" | "warn" | "error";
36
47
  INKEEP_AGENTS_RUN_DATABASE_URL: string;
37
48
  INKEEP_AGENTS_MANAGE_UI_URL?: string | undefined;
38
49
  GITHUB_APP_ID?: string | undefined;
@@ -41,6 +52,17 @@ declare const env: {
41
52
  GITHUB_STATE_SIGNING_SECRET?: string | undefined;
42
53
  GITHUB_APP_NAME?: string | undefined;
43
54
  GITHUB_MCP_API_KEY?: string | undefined;
55
+ SLACK_CLIENT_ID?: string | undefined;
56
+ SLACK_CLIENT_SECRET?: string | undefined;
57
+ SLACK_SIGNING_SECRET?: string | undefined;
58
+ SLACK_BOT_TOKEN?: string | undefined;
59
+ SLACK_APP_URL?: string | undefined;
60
+ NANGO_SECRET_KEY?: string | undefined;
61
+ NANGO_SLACK_SECRET_KEY?: string | undefined;
62
+ NANGO_SLACK_INTEGRATION_ID?: string | undefined;
63
+ NANGO_SERVER_URL?: string | undefined;
64
+ INKEEP_AGENTS_JWT_SIGNING_SECRET?: string | undefined;
65
+ INKEEP_AGENTS_API_URL?: string | undefined;
44
66
  };
45
67
  type Env = z.infer<typeof envSchema>;
46
68
  //#endregion
package/dist/env.js CHANGED
@@ -22,14 +22,25 @@ const envSchema = z.object({
22
22
  "warn",
23
23
  "error"
24
24
  ]).default("info").describe("Logging verbosity level"),
25
- INKEEP_AGENTS_RUN_DATABASE_URL: z.string().describe("PostgreSQL connection URL for the runtime database (Doltgres with Git version control)"),
25
+ INKEEP_AGENTS_RUN_DATABASE_URL: z.string().describe("PostgreSQL connection URL for the runtime database"),
26
26
  INKEEP_AGENTS_MANAGE_UI_URL: z.string().optional().describe("URL where the management UI is hosted"),
27
27
  GITHUB_APP_ID: z.string().optional().describe("GitHub App ID for GitHub integration"),
28
28
  GITHUB_APP_PRIVATE_KEY: z.string().optional().describe("GitHub App private key for authentication"),
29
29
  GITHUB_WEBHOOK_SECRET: z.string().optional().describe("Secret for validating GitHub webhook payloads"),
30
30
  GITHUB_STATE_SIGNING_SECRET: z.string().min(32, "GITHUB_STATE_SIGNING_SECRET must be at least 32 characters").optional().describe("Secret for signing GitHub OAuth state (minimum 32 characters)"),
31
31
  GITHUB_APP_NAME: z.string().optional().describe("Name of the GitHub App"),
32
- GITHUB_MCP_API_KEY: z.string().optional().describe("API key for the GitHub MCP")
32
+ GITHUB_MCP_API_KEY: z.string().optional().describe("API key for the GitHub MCP"),
33
+ SLACK_CLIENT_ID: z.string().optional().describe("Slack App Client ID"),
34
+ SLACK_CLIENT_SECRET: z.string().optional().describe("Slack App Client Secret"),
35
+ SLACK_SIGNING_SECRET: z.string().optional().describe("Slack App Signing Secret"),
36
+ SLACK_BOT_TOKEN: z.string().optional().describe("Slack Bot Token (for testing)"),
37
+ SLACK_APP_URL: z.string().optional().describe("Slack App Install URL"),
38
+ NANGO_SECRET_KEY: z.string().optional().describe("Nango Secret Key"),
39
+ NANGO_SLACK_SECRET_KEY: z.string().optional().describe("Nango Slack-specific Secret Key"),
40
+ NANGO_SLACK_INTEGRATION_ID: z.string().optional().describe("Nango Slack Integration ID"),
41
+ NANGO_SERVER_URL: z.string().optional().describe("Nango Server URL"),
42
+ INKEEP_AGENTS_JWT_SIGNING_SECRET: z.string().optional().describe("JWT signing secret shared with agents-api. Required in production (min 32 chars). Used for Slack user tokens and link tokens."),
43
+ INKEEP_AGENTS_API_URL: z.string().optional().describe("Inkeep Agents API URL")
33
44
  });
34
45
  const parseEnv = () => {
35
46
  try {
@@ -1,11 +1,11 @@
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/mcp/index.d.ts
5
5
  declare const app: Hono<{
6
6
  Variables: {
7
7
  toolId: string;
8
8
  };
9
- }, hono_types3.BlankSchema, "/">;
9
+ }, hono_types5.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -108,8 +108,9 @@ const getServer = async (toolId) => {
108
108
  owner: z.string().describe("Repository owner name"),
109
109
  repo: z.string().describe("Repository name"),
110
110
  file_path: z.string().describe("Path to the file. the path is relative to the root of the repository"),
111
- branch_name: z.string().optional().describe("The name of the branch to get the file content for (defaults to master/main branch). If you are analyzing a pr you created, you should use the branch name from the pr.")
112
- }, async ({ owner, repo, file_path, branch_name }) => {
111
+ branch_name: z.string().optional().describe("The name of the branch to get the file content for (defaults to master/main branch). If you are analyzing a pr you created, you should use the branch name from the pr."),
112
+ include_line_numbers: z.boolean().optional().describe("Whether to include line numbers in the response (defaults to false)")
113
+ }, async ({ owner, repo, file_path, branch_name, include_line_numbers = false }) => {
113
114
  try {
114
115
  let githubClient;
115
116
  try {
@@ -130,14 +131,20 @@ const getServer = async (toolId) => {
130
131
  ref: branch_name
131
132
  });
132
133
  if ("content" in response.data && !Array.isArray(response.data)) {
133
- const fileData = response.data;
134
- const output_mapping = Buffer.from(fileData.content, "base64").toString("utf-8").split("\n").map((line, index) => ({
135
- line: index + 1,
136
- content: line
137
- }));
134
+ if (include_line_numbers) {
135
+ const fileData = response.data;
136
+ const output_mapping = Buffer.from(fileData.content, "base64").toString("utf-8").split("\n").map((line, index) => ({
137
+ line: index + 1,
138
+ content: line
139
+ }));
140
+ return { content: [{
141
+ type: "text",
142
+ text: JSON.stringify(output_mapping)
143
+ }] };
144
+ }
138
145
  return { content: [{
139
146
  type: "text",
140
- text: JSON.stringify(output_mapping)
147
+ text: Buffer.from(response.data.content, "base64").toString("utf-8")
141
148
  }] };
142
149
  }
143
150
  return {
@@ -665,40 +672,22 @@ const getServer = async (toolId) => {
665
672
  });
666
673
  return server;
667
674
  };
668
- const SERVER_CACHE_TTL_MS = 300 * 1e3;
669
- const SERVER_CACHE_MAX_SIZE = 100;
670
- const serverCache = /* @__PURE__ */ new Map();
671
- const getCachedServer = async (toolId) => {
672
- const cached = serverCache.get(toolId);
673
- if (cached && cached.expiresAt > Date.now()) {
674
- serverCache.delete(toolId);
675
- serverCache.set(toolId, cached);
676
- return cached.server;
677
- }
678
- serverCache.delete(toolId);
679
- const server = await getServer(toolId);
680
- serverCache.set(toolId, {
681
- server,
682
- expiresAt: Date.now() + SERVER_CACHE_TTL_MS
683
- });
684
- if (serverCache.size > SERVER_CACHE_MAX_SIZE) {
685
- const firstKey = serverCache.keys().next().value;
686
- if (firstKey !== void 0) serverCache.delete(firstKey);
687
- }
688
- return server;
689
- };
690
675
  const app = new Hono();
691
676
  app.use("/", githubMcpAuth());
692
677
  app.post("/", async (c) => {
693
678
  if (!process.env.GITHUB_APP_ID || !process.env.GITHUB_APP_PRIVATE_KEY) return c.json({ error: "GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY must be set" }, 500);
694
679
  const toolId = c.get("toolId");
695
680
  const body = await c.req.json();
696
- const server = await getCachedServer(toolId);
681
+ const server = await getServer(toolId);
697
682
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
698
- server.connect(transport);
683
+ await server.connect(transport);
699
684
  const { req, res } = toReqRes(c.req.raw);
700
- await transport.handleRequest(req, res, body);
701
- return toFetchResponse(res);
685
+ try {
686
+ await transport.handleRequest(req, res, body);
687
+ return toFetchResponse(res);
688
+ } finally {
689
+ await server.close();
690
+ }
702
691
  });
703
692
  app.delete("/", async (c) => {
704
693
  return c.json({
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types4 from "hono/types";
2
+ import * as hono_types6 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types6.BlankEnv, hono_types6.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types6 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_types6.BlankEnv, hono_types6.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -0,0 +1,2 @@
1
+ import { SlackStrings, SlackStringsType } from "./strings.js";
2
+ export { SlackStrings, type SlackStringsType };
@@ -0,0 +1,3 @@
1
+ import { SlackStrings } from "./strings.js";
2
+
3
+ export { SlackStrings };
@@ -0,0 +1,73 @@
1
+ //#region src/slack/i18n/strings.d.ts
2
+ /**
3
+ * Slack UI/UX Internationalization Strings
4
+ *
5
+ * Centralized strings for all Slack-facing UI text.
6
+ * Update this file to change text across the entire Slack integration.
7
+ */
8
+ declare const SlackStrings: {
9
+ readonly buttons: {
10
+ readonly triggerAgent: "Trigger Agent";
11
+ readonly send: "Send";
12
+ readonly followUp: "Follow Up";
13
+ readonly cancel: "Cancel";
14
+ readonly openDashboard: "⚙️ Open Dashboard";
15
+ };
16
+ readonly modals: {
17
+ readonly triggerAgent: "Trigger Agent";
18
+ readonly triggerAgentThread: "Trigger Agent (Thread)";
19
+ readonly askAboutMessage: "Ask About Message";
20
+ readonly followUp: "Follow Up";
21
+ };
22
+ readonly labels: {
23
+ readonly project: "Project";
24
+ readonly agent: "Agent";
25
+ readonly prompt: "Prompt";
26
+ readonly additionalInstructions: "Additional Instructions";
27
+ readonly context: "Context";
28
+ };
29
+ readonly placeholders: {
30
+ readonly selectProject: "Select a project...";
31
+ readonly selectAgent: "Select an agent...";
32
+ readonly enterPrompt: "Enter your prompt or question...";
33
+ readonly additionalInstructionsOptional: "Additional instructions (optional)...";
34
+ readonly additionalInstructionsMessage: "Additional instructions or question about this message...";
35
+ };
36
+ readonly visibility: {
37
+ readonly includeThreadContext: "Include thread context";
38
+ };
39
+ readonly context: {
40
+ readonly poweredBy: (agentName: string) => string;
41
+ readonly privateResponse: "_Private response_";
42
+ };
43
+ readonly usage: {
44
+ readonly mentionEmpty: string;
45
+ };
46
+ readonly status: {
47
+ readonly thinking: (agentName: string) => string;
48
+ readonly noAgentsAvailable: "No agents available";
49
+ readonly noProjectsConfigured: "⚙️ No projects configured. Please set up projects in the dashboard.";
50
+ };
51
+ readonly errors: {
52
+ readonly generic: "Sorry, something went wrong. Please try again.";
53
+ readonly failedToOpenSelector: "❌ Failed to open agent selector. Please try again.";
54
+ };
55
+ readonly help: {
56
+ readonly title: "Inkeep — How to Use";
57
+ readonly publicSection: string;
58
+ readonly privateSection: string;
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;
66
+ };
67
+ readonly messageContext: {
68
+ readonly label: "Message:";
69
+ };
70
+ };
71
+ type SlackStringsType = typeof SlackStrings;
72
+ //#endregion
73
+ export { SlackStrings, SlackStringsType };
@@ -0,0 +1,67 @@
1
+ //#region src/slack/i18n/strings.ts
2
+ /**
3
+ * Slack UI/UX Internationalization Strings
4
+ *
5
+ * Centralized strings for all Slack-facing UI text.
6
+ * Update this file to change text across the entire Slack integration.
7
+ */
8
+ const SlackStrings = {
9
+ buttons: {
10
+ triggerAgent: "Trigger Agent",
11
+ send: "Send",
12
+ followUp: "Follow Up",
13
+ cancel: "Cancel",
14
+ openDashboard: "⚙️ Open Dashboard"
15
+ },
16
+ modals: {
17
+ triggerAgent: "Trigger Agent",
18
+ triggerAgentThread: "Trigger Agent (Thread)",
19
+ askAboutMessage: "Ask About Message",
20
+ followUp: "Follow Up"
21
+ },
22
+ labels: {
23
+ project: "Project",
24
+ agent: "Agent",
25
+ prompt: "Prompt",
26
+ additionalInstructions: "Additional Instructions",
27
+ context: "Context"
28
+ },
29
+ placeholders: {
30
+ selectProject: "Select a project...",
31
+ selectAgent: "Select an agent...",
32
+ enterPrompt: "Enter your prompt or question...",
33
+ additionalInstructionsOptional: "Additional instructions (optional)...",
34
+ additionalInstructionsMessage: "Additional instructions or question about this message..."
35
+ },
36
+ visibility: { includeThreadContext: "Include thread context" },
37
+ context: {
38
+ poweredBy: (agentName) => `Powered by *${agentName}* via Inkeep`,
39
+ privateResponse: "_Private response_"
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." },
42
+ status: {
43
+ thinking: (agentName) => `_${agentName} is thinking..._`,
44
+ noAgentsAvailable: "No agents available",
45
+ noProjectsConfigured: "⚙️ No projects configured. Please set up projects in the dashboard."
46
+ },
47
+ errors: {
48
+ generic: "Sorry, something went wrong. Please try again.",
49
+ failedToOpenSelector: "❌ Failed to open agent selector. Please try again."
50
+ },
51
+ help: {
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`
62
+ },
63
+ messageContext: { label: "Message:" }
64
+ };
65
+
66
+ //#endregion
67
+ export { SlackStrings };
@@ -0,0 +1,18 @@
1
+ import { ManageAppVariables, WorkAppsVariables } from "./types.js";
2
+ import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./services/nango.js";
3
+ import { getChannelAgentConfig, getWorkspaceDefaultAgent } from "./services/events/utils.js";
4
+ import "./services/events/index.js";
5
+ import { getBotTokenForTeam, setBotTokenForTeam } from "./services/workspace-tokens.js";
6
+ import "./routes/oauth.js";
7
+ import { OpenAPIHono } from "@hono/zod-openapi";
8
+
9
+ //#region src/slack/index.d.ts
10
+
11
+ declare function createSlackRoutes(): OpenAPIHono<{
12
+ Variables: WorkAppsVariables;
13
+ }, {}, "/">;
14
+ declare const slackRoutes: OpenAPIHono<{
15
+ Variables: WorkAppsVariables;
16
+ }, {}, "/">;
17
+ //#endregion
18
+ export { DefaultAgentConfig, ManageAppVariables, SlackWorkspaceConnection, WorkAppsVariables, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, createSlackRoutes, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setBotTokenForTeam, setWorkspaceDefaultAgent, slackRoutes, storeWorkspaceInstallation, updateConnectionMetadata };
@@ -0,0 +1,28 @@
1
+ import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./services/nango.js";
2
+ import { getChannelAgentConfig, getWorkspaceDefaultAgent } from "./services/events/utils.js";
3
+ import { getBotTokenForTeam, setBotTokenForTeam } from "./services/workspace-tokens.js";
4
+ import "./services/events/index.js";
5
+ import "./routes/oauth.js";
6
+ import routes_default from "./routes/index.js";
7
+ import { OpenAPIHono } from "@hono/zod-openapi";
8
+
9
+ //#region src/slack/index.ts
10
+ /**
11
+ * Slack Work App
12
+ *
13
+ * Provides Slack integration for Inkeep Agents including:
14
+ * - OAuth-based workspace installation
15
+ * - User account linking via JWT tokens
16
+ * - Slash commands for agent interaction
17
+ * - @mention support for workspace-wide agent access
18
+ * - Channel-specific agent configuration
19
+ */
20
+ function createSlackRoutes() {
21
+ const app = new OpenAPIHono();
22
+ app.route("/", routes_default);
23
+ return app;
24
+ }
25
+ const slackRoutes = createSlackRoutes();
26
+
27
+ //#endregion
28
+ export { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, createSlackRoutes, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setBotTokenForTeam, setWorkspaceDefaultAgent, slackRoutes, storeWorkspaceInstallation, updateConnectionMetadata };
@@ -0,0 +1,31 @@
1
+ import { ManageAppVariables } from "../types.js";
2
+ import * as hono0 from "hono";
3
+
4
+ //#region src/slack/middleware/permissions.d.ts
5
+ /**
6
+ * Check if user has admin role (owner or admin)
7
+ */
8
+ declare function isOrgAdmin(tenantRole: string | undefined): boolean;
9
+ /**
10
+ * Middleware that requires Inkeep org admin/owner role.
11
+ * Use for workspace-level settings that only Inkeep organization admins can modify.
12
+ */
13
+ declare const requireWorkspaceAdmin: <Env extends {
14
+ Variables: ManageAppVariables;
15
+ } = {
16
+ Variables: ManageAppVariables;
17
+ }>() => hono0.MiddlewareHandler<Env, string, {}, Response>;
18
+ /**
19
+ * Middleware that requires either:
20
+ * 1. Org admin/owner role (can modify any channel), OR
21
+ * 2. Member role AND membership in the specific Slack channel
22
+ *
23
+ * Use for channel-level settings where members can configure their own channels.
24
+ */
25
+ declare const requireChannelMemberOrAdmin: <Env extends {
26
+ Variables: ManageAppVariables;
27
+ } = {
28
+ Variables: ManageAppVariables;
29
+ }>() => hono0.MiddlewareHandler<Env, string, {}, Response>;
30
+ //#endregion
31
+ export { isOrgAdmin, requireChannelMemberOrAdmin, requireWorkspaceAdmin };
@@ -0,0 +1,167 @@
1
+ import { getLogger } from "../../logger.js";
2
+ import runDbClient_default from "../../db/runDbClient.js";
3
+ import { findWorkspaceConnectionByTeamId } from "../services/nango.js";
4
+ import { checkUserIsChannelMember, getSlackClient } from "../services/client.js";
5
+ import { OrgRoles, createApiError, findWorkAppSlackUserMappingByInkeepUserId, getUserOrganizationsFromDb } from "@inkeep/agents-core";
6
+ import { createMiddleware } from "hono/factory";
7
+
8
+ //#region src/slack/middleware/permissions.ts
9
+ const logger = getLogger("slack-permissions");
10
+ /**
11
+ * Check if user has admin role (owner or admin)
12
+ */
13
+ function isOrgAdmin(tenantRole) {
14
+ return tenantRole === OrgRoles.OWNER || tenantRole === OrgRoles.ADMIN;
15
+ }
16
+ /**
17
+ * Resolve tenantId and tenantRole from a Slack teamId.
18
+ * Looks up the workspace connection to find the owning tenant,
19
+ * then checks the user's org membership to determine their role.
20
+ *
21
+ * This is needed because /work-apps/* routes don't go through requireTenantAccess
22
+ * middleware (which normally sets tenantRole on /manage/* routes).
23
+ */
24
+ async function resolveWorkAppTenantContext(c, teamId, userId) {
25
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
26
+ if (!workspace?.tenantId) throw createApiError({
27
+ code: "not_found",
28
+ message: "Slack workspace not found or not associated with a tenant",
29
+ instance: c.req.path
30
+ });
31
+ const orgAccess = (await getUserOrganizationsFromDb(runDbClient_default)(userId)).find((org) => org.organizationId === workspace.tenantId);
32
+ if (!orgAccess) throw createApiError({
33
+ code: "forbidden",
34
+ message: "Access denied to this organization",
35
+ instance: c.req.path
36
+ });
37
+ c.set("tenantId", workspace.tenantId);
38
+ c.set("tenantRole", orgAccess.role);
39
+ }
40
+ /**
41
+ * Middleware that requires Inkeep org admin/owner role.
42
+ * Use for workspace-level settings that only Inkeep organization admins can modify.
43
+ */
44
+ const requireWorkspaceAdmin = () => createMiddleware(async (c, next) => {
45
+ if (process.env.ENVIRONMENT === "test" && c.req.header("x-test-bypass-auth") === "true") {
46
+ await next();
47
+ return;
48
+ }
49
+ const userId = c.get("userId");
50
+ if (!userId) throw createApiError({
51
+ code: "unauthorized",
52
+ message: "User context not found",
53
+ instance: c.req.path
54
+ });
55
+ if (userId === "system" || userId.startsWith("apikey:")) {
56
+ await next();
57
+ return;
58
+ }
59
+ const teamId = c.req.param("teamId") || c.req.param("workspaceId");
60
+ if (teamId && !c.get("tenantRole")) await resolveWorkAppTenantContext(c, teamId, userId);
61
+ const tenantId = c.get("tenantId");
62
+ const tenantRole = c.get("tenantRole");
63
+ if (!tenantId) throw createApiError({
64
+ code: "unauthorized",
65
+ message: "Organization context not found",
66
+ instance: c.req.path
67
+ });
68
+ if (!isOrgAdmin(tenantRole)) {
69
+ logger.warn({
70
+ userId,
71
+ tenantId,
72
+ tenantRole,
73
+ path: c.req.path
74
+ }, "User does not have admin role for workspace operation");
75
+ throw createApiError({
76
+ code: "forbidden",
77
+ message: "Only organization administrators can modify workspace settings",
78
+ instance: c.req.path,
79
+ extensions: {
80
+ requiredRole: "admin or owner",
81
+ currentRole: tenantRole
82
+ }
83
+ });
84
+ }
85
+ await next();
86
+ });
87
+ /**
88
+ * Middleware that requires either:
89
+ * 1. Org admin/owner role (can modify any channel), OR
90
+ * 2. Member role AND membership in the specific Slack channel
91
+ *
92
+ * Use for channel-level settings where members can configure their own channels.
93
+ */
94
+ const requireChannelMemberOrAdmin = () => createMiddleware(async (c, next) => {
95
+ if (process.env.ENVIRONMENT === "test" && c.req.header("x-test-bypass-auth") === "true") {
96
+ await next();
97
+ return;
98
+ }
99
+ const userId = c.get("userId");
100
+ if (!userId) throw createApiError({
101
+ code: "unauthorized",
102
+ message: "User context not found",
103
+ instance: c.req.path
104
+ });
105
+ if (userId === "system" || userId.startsWith("apikey:")) {
106
+ await next();
107
+ return;
108
+ }
109
+ const teamId = c.req.param("teamId");
110
+ if (teamId && !c.get("tenantRole")) await resolveWorkAppTenantContext(c, teamId, userId);
111
+ const tenantId = c.get("tenantId");
112
+ const tenantRole = c.get("tenantRole");
113
+ if (!tenantId) throw createApiError({
114
+ code: "unauthorized",
115
+ message: "Organization context not found",
116
+ instance: c.req.path
117
+ });
118
+ if (isOrgAdmin(tenantRole)) {
119
+ await next();
120
+ return;
121
+ }
122
+ const channelId = c.req.param("channelId");
123
+ if (!teamId || !channelId) throw createApiError({
124
+ code: "bad_request",
125
+ message: "Team ID and Channel ID are required",
126
+ instance: c.req.path
127
+ });
128
+ const userMapping = (await findWorkAppSlackUserMappingByInkeepUserId(runDbClient_default)(userId)).find((m) => m.tenantId === tenantId && m.slackTeamId === teamId);
129
+ if (!userMapping) throw createApiError({
130
+ code: "forbidden",
131
+ message: "You must link your Slack account to modify channel settings. Use /inkeep link in Slack.",
132
+ instance: c.req.path
133
+ });
134
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
135
+ if (!workspace?.botToken) throw createApiError({
136
+ code: "not_found",
137
+ message: "Slack workspace not found or not properly configured",
138
+ instance: c.req.path
139
+ });
140
+ if (!await checkUserIsChannelMember(getSlackClient(workspace.botToken), channelId, userMapping.slackUserId)) {
141
+ logger.info({
142
+ userId,
143
+ slackUserId: userMapping.slackUserId,
144
+ channelId,
145
+ teamId
146
+ }, "User is not a member of the channel");
147
+ throw createApiError({
148
+ code: "forbidden",
149
+ message: "You can only configure channels you are a member of",
150
+ instance: c.req.path,
151
+ extensions: {
152
+ channelId,
153
+ reason: "not_channel_member"
154
+ }
155
+ });
156
+ }
157
+ logger.debug({
158
+ userId,
159
+ slackUserId: userMapping.slackUserId,
160
+ channelId,
161
+ teamId
162
+ }, "User verified as channel member");
163
+ await next();
164
+ });
165
+
166
+ //#endregion
167
+ export { isOrgAdmin, requireChannelMemberOrAdmin, requireWorkspaceAdmin };
@@ -0,0 +1,10 @@
1
+ import { WorkAppsVariables } from "../types.js";
2
+ import { OpenAPIHono } from "@hono/zod-openapi";
3
+
4
+ //#region src/slack/routes/events.d.ts
5
+
6
+ declare const app: OpenAPIHono<{
7
+ Variables: WorkAppsVariables;
8
+ }, {}, "/">;
9
+ //#endregion
10
+ export { app as default };