@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
@@ -0,0 +1,365 @@
1
+ import { getLogger } from "../../logger.js";
2
+ import runDbClient_default from "../../db/runDbClient.js";
3
+ import { createConnectSession } from "../services/nango.js";
4
+ import "../services/index.js";
5
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
6
+ import { createWorkAppSlackUserMapping, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingByInkeepUserId, verifySlackLinkToken } from "@inkeep/agents-core";
7
+
8
+ //#region src/slack/routes/users.ts
9
+ /**
10
+ * Slack User Routes
11
+ *
12
+ * Endpoints for user linking:
13
+ * - GET /link-status - Check link status
14
+ * - POST /link/verify-token - Verify JWT link token (primary linking method)
15
+ * - POST /connect - Create Nango session
16
+ * - POST /disconnect - Disconnect user
17
+ */
18
+ const logger = getLogger("slack-users");
19
+ /**
20
+ * Verify the authenticated caller matches the requested userId.
21
+ * System tokens and API keys are allowed to act on behalf of any user.
22
+ */
23
+ function isAuthorizedForUser(c, requestedUserId) {
24
+ const sessionUserId = c.get("userId");
25
+ if (!sessionUserId) return false;
26
+ if (sessionUserId === requestedUserId) return true;
27
+ if (sessionUserId === "system" || sessionUserId.startsWith("apikey:")) return true;
28
+ if (sessionUserId === "dev-user" && (process.env.ENVIRONMENT === "development" || process.env.ENVIRONMENT === "test")) return true;
29
+ return false;
30
+ }
31
+ const app = new OpenAPIHono();
32
+ app.openapi(createRoute({
33
+ method: "get",
34
+ path: "/link-status",
35
+ summary: "Check Link Status",
36
+ description: "Check if a Slack user is linked to an Inkeep account",
37
+ operationId: "slack-link-status",
38
+ tags: [
39
+ "Work Apps",
40
+ "Slack",
41
+ "Users"
42
+ ],
43
+ request: { query: z.object({
44
+ slackUserId: z.string(),
45
+ slackTeamId: z.string(),
46
+ tenantId: z.string().optional().default("default")
47
+ }) },
48
+ responses: { 200: {
49
+ description: "Link status",
50
+ content: { "application/json": { schema: z.object({
51
+ linked: z.boolean(),
52
+ linkId: z.string().optional(),
53
+ linkedAt: z.string().optional(),
54
+ slackUsername: z.string().optional()
55
+ }) } }
56
+ } }
57
+ }), async (c) => {
58
+ const { slackUserId, slackTeamId, tenantId } = c.req.valid("query");
59
+ const sessionTenantId = c.get("tenantId");
60
+ if (sessionTenantId && sessionTenantId !== tenantId) return c.json({ linked: false });
61
+ const link = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, slackUserId, slackTeamId, "work-apps-slack");
62
+ if (link) return c.json({
63
+ linked: true,
64
+ linkId: link.id,
65
+ linkedAt: link.linkedAt,
66
+ slackUsername: link.slackUsername || void 0
67
+ });
68
+ return c.json({ linked: false });
69
+ });
70
+ app.openapi(createRoute({
71
+ method: "post",
72
+ path: "/link/verify-token",
73
+ summary: "Verify Link Token",
74
+ description: "Verify a JWT link token and create user mapping",
75
+ operationId: "slack-verify-link-token",
76
+ tags: [
77
+ "Work Apps",
78
+ "Slack",
79
+ "Users"
80
+ ],
81
+ request: { body: { content: { "application/json": { schema: z.object({
82
+ token: z.string().min(1),
83
+ userId: z.string().min(1),
84
+ userEmail: z.string().email().optional()
85
+ }) } } } },
86
+ responses: {
87
+ 200: {
88
+ description: "Link successful",
89
+ content: { "application/json": { schema: z.object({
90
+ success: z.boolean(),
91
+ linkId: z.string().optional(),
92
+ slackUsername: z.string().optional(),
93
+ slackTeamId: z.string().optional(),
94
+ tenantId: z.string().optional()
95
+ }) } }
96
+ },
97
+ 400: { description: "Invalid or expired token" },
98
+ 409: { description: "Account already linked" }
99
+ }
100
+ }), async (c) => {
101
+ const body = c.req.valid("json");
102
+ try {
103
+ const verifyResult = await verifySlackLinkToken(body.token);
104
+ if (!verifyResult.valid || !verifyResult.payload) {
105
+ logger.warn({ error: verifyResult.error }, "Invalid link token");
106
+ return c.json({ error: verifyResult.error || "Invalid or expired link token. Please run /inkeep link in Slack to get a new one." }, 400);
107
+ }
108
+ const { tenantId, slack } = verifyResult.payload;
109
+ const { teamId, userId: slackUserId, enterpriseId, username } = slack;
110
+ const sessionUserId = c.get("userId");
111
+ if (!(sessionUserId && sessionUserId !== "dev-user" && !sessionUserId.startsWith("apikey:") && sessionUserId !== "system")) {
112
+ logger.warn({ sessionUserId }, "Link token verification rejected: no valid session user");
113
+ return c.json({ error: "Session authentication required for account linking" }, 403);
114
+ }
115
+ const inkeepUserId = sessionUserId;
116
+ const existingLink = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, slackUserId, teamId, "work-apps-slack");
117
+ if (existingLink && existingLink.inkeepUserId === inkeepUserId) {
118
+ logger.info({
119
+ slackUserId,
120
+ tenantId,
121
+ inkeepUserId: body.userId
122
+ }, "Slack user already linked to same account");
123
+ return c.json({
124
+ success: true,
125
+ linkId: existingLink.id,
126
+ slackUsername: existingLink.slackUsername || void 0,
127
+ slackTeamId: teamId,
128
+ tenantId
129
+ });
130
+ }
131
+ if (existingLink) logger.info({
132
+ slackUserId,
133
+ existingUserId: existingLink.inkeepUserId,
134
+ newUserId: inkeepUserId,
135
+ tenantId
136
+ }, "Slack user already linked, updating to new user");
137
+ const slackUserMapping = await createWorkAppSlackUserMapping(runDbClient_default)({
138
+ tenantId,
139
+ clientId: "work-apps-slack",
140
+ slackUserId,
141
+ slackTeamId: teamId,
142
+ slackEnterpriseId: enterpriseId,
143
+ slackUsername: username,
144
+ slackEmail: body.userEmail,
145
+ inkeepUserId
146
+ });
147
+ logger.info({
148
+ slackUserId,
149
+ slackTeamId: teamId,
150
+ tenantId,
151
+ inkeepUserId: body.userId,
152
+ linkId: slackUserMapping.id
153
+ }, "Successfully linked Slack user to Inkeep account via JWT token");
154
+ return c.json({
155
+ success: true,
156
+ linkId: slackUserMapping.id,
157
+ slackUsername: username || void 0,
158
+ slackTeamId: teamId,
159
+ tenantId
160
+ });
161
+ } catch (error) {
162
+ const errorMessage = error instanceof Error ? error.message : String(error);
163
+ if (errorMessage.includes("duplicate key") || errorMessage.includes("unique constraint")) {
164
+ logger.warn({ userId: body.userId }, "Slack user already linked");
165
+ return c.json({ error: "This Slack account is already linked to an Inkeep account." }, 409);
166
+ }
167
+ logger.error({
168
+ error,
169
+ userId: body.userId
170
+ }, "Failed to verify link token");
171
+ return c.json({ error: "Failed to verify link. Please try again." }, 500);
172
+ }
173
+ });
174
+ app.openapi(createRoute({
175
+ method: "post",
176
+ path: "/connect",
177
+ summary: "Create Nango Connect Session",
178
+ description: "Create a Nango session for Slack OAuth flow. Used by the dashboard.",
179
+ operationId: "slack-user-connect",
180
+ tags: [
181
+ "Work Apps",
182
+ "Slack",
183
+ "Users"
184
+ ],
185
+ request: { body: { content: { "application/json": { schema: z.object({
186
+ userId: z.string().describe("Inkeep user ID"),
187
+ userEmail: z.string().optional().describe("User email"),
188
+ userName: z.string().optional().describe("User display name"),
189
+ tenantId: z.string().optional().describe("Tenant ID")
190
+ }) } } } },
191
+ responses: {
192
+ 200: {
193
+ description: "Connect session created",
194
+ content: { "application/json": { schema: z.object({
195
+ sessionToken: z.string().optional(),
196
+ connectUrl: z.string().optional()
197
+ }) } }
198
+ },
199
+ 400: { description: "Missing required userId" },
200
+ 500: { description: "Failed to create session" }
201
+ }
202
+ }), async (c) => {
203
+ const { userId, userEmail, userName, tenantId } = c.req.valid("json");
204
+ if (!userId) return c.json({ error: "userId is required" }, 400);
205
+ if (!isAuthorizedForUser(c, userId)) return c.json({ error: "Can only create sessions for your own account" }, 403);
206
+ logger.debug({
207
+ userId,
208
+ userEmail,
209
+ userName
210
+ }, "Creating Nango connect session");
211
+ const session = await createConnectSession({
212
+ userId,
213
+ userEmail,
214
+ userName,
215
+ tenantId: tenantId || c.get("tenantId") || ""
216
+ });
217
+ if (!session) return c.json({ error: "Failed to create session" }, 500);
218
+ return c.json(session);
219
+ });
220
+ app.openapi(createRoute({
221
+ method: "post",
222
+ path: "/disconnect",
223
+ summary: "Disconnect User",
224
+ description: "Unlink a Slack user from their Inkeep account.",
225
+ operationId: "slack-user-disconnect",
226
+ tags: [
227
+ "Work Apps",
228
+ "Slack",
229
+ "Users"
230
+ ],
231
+ request: { body: { content: { "application/json": { schema: z.object({
232
+ userId: z.string().optional().describe("Inkeep user ID"),
233
+ slackUserId: z.string().optional().describe("Slack user ID"),
234
+ slackTeamId: z.string().optional().describe("Slack team ID"),
235
+ tenantId: z.string().optional().describe("Tenant ID")
236
+ }) } } } },
237
+ responses: {
238
+ 200: {
239
+ description: "User disconnected",
240
+ content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
241
+ },
242
+ 400: { description: "Missing required identifiers" },
243
+ 404: { description: "No connection found" },
244
+ 500: { description: "Failed to disconnect" }
245
+ }
246
+ }), async (c) => {
247
+ const { userId, slackUserId, slackTeamId, tenantId } = c.req.valid("json");
248
+ if (!userId && !(slackUserId && slackTeamId)) return c.json({ error: "Either userId or (slackUserId + slackTeamId) is required" }, 400);
249
+ if (userId && !isAuthorizedForUser(c, userId)) return c.json({ error: "Can only disconnect your own account" }, 403);
250
+ try {
251
+ const effectiveTenantId = tenantId || c.get("tenantId") || "";
252
+ if (slackUserId && slackTeamId) {
253
+ const mapping = await findWorkAppSlackUserMapping(runDbClient_default)(effectiveTenantId, slackUserId, slackTeamId, "work-apps-slack");
254
+ if (!mapping) return c.json({ error: "No link found for this user" }, 404);
255
+ if (!isAuthorizedForUser(c, mapping.inkeepUserId)) return c.json({ error: "Can only disconnect your own account" }, 403);
256
+ if (await deleteWorkAppSlackUserMapping(runDbClient_default)(effectiveTenantId, slackUserId, slackTeamId, "work-apps-slack")) {
257
+ logger.info({
258
+ slackUserId,
259
+ slackTeamId,
260
+ tenantId: effectiveTenantId
261
+ }, "User unlinked");
262
+ return c.json({ success: true });
263
+ }
264
+ return c.json({ error: "Failed to unlink user" }, 500);
265
+ }
266
+ if (userId) {
267
+ const userMappings = await findWorkAppSlackUserMappingByInkeepUserId(runDbClient_default)(userId);
268
+ if (userMappings.length === 0) return c.json({ error: "No link found for this user" }, 404);
269
+ let deletedCount = 0;
270
+ for (const mapping of userMappings) if (await deleteWorkAppSlackUserMapping(runDbClient_default)(mapping.tenantId, mapping.slackUserId, mapping.slackTeamId, "work-apps-slack")) deletedCount++;
271
+ logger.info({
272
+ userId,
273
+ deletedCount
274
+ }, "User disconnected from Slack");
275
+ return c.json({ success: true });
276
+ }
277
+ return c.json({ error: "No connection found for this user" }, 404);
278
+ } catch (error) {
279
+ logger.error({
280
+ error,
281
+ userId,
282
+ slackUserId,
283
+ slackTeamId
284
+ }, "Failed to disconnect from Slack");
285
+ return c.json({ error: "Failed to disconnect" }, 500);
286
+ }
287
+ });
288
+ app.openapi(createRoute({
289
+ method: "get",
290
+ path: "/status",
291
+ summary: "Get Connection Status",
292
+ description: "Check if an Inkeep user has a linked Slack account.",
293
+ operationId: "slack-user-status",
294
+ tags: [
295
+ "Work Apps",
296
+ "Slack",
297
+ "Users"
298
+ ],
299
+ request: { query: z.object({ userId: z.string().describe("Inkeep user ID") }) },
300
+ responses: {
301
+ 200: {
302
+ description: "Connection status",
303
+ content: { "application/json": { schema: z.object({
304
+ connected: z.boolean(),
305
+ connection: z.object({
306
+ connectionId: z.string(),
307
+ appUserId: z.string(),
308
+ appUserEmail: z.string(),
309
+ slackDisplayName: z.string(),
310
+ linkedAt: z.string(),
311
+ tenantId: z.string(),
312
+ slackUserId: z.string(),
313
+ slackTeamId: z.string()
314
+ }).nullable()
315
+ }) } }
316
+ },
317
+ 400: { description: "Missing userId" },
318
+ 500: { description: "Failed to get status" }
319
+ }
320
+ }), async (c) => {
321
+ const { userId: appUserId } = c.req.valid("query");
322
+ if (!isAuthorizedForUser(c, appUserId)) return c.json({ error: "Can only query your own connection status" }, 403);
323
+ try {
324
+ const userMappings = await findWorkAppSlackUserMappingByInkeepUserId(runDbClient_default)(appUserId);
325
+ if (userMappings.length === 0) {
326
+ logger.debug({
327
+ appUserId,
328
+ connected: false
329
+ }, "Retrieved connection status from DB");
330
+ return c.json({
331
+ connected: false,
332
+ connection: null
333
+ });
334
+ }
335
+ const mostRecent = userMappings.sort((a, b) => new Date(b.linkedAt).getTime() - new Date(a.linkedAt).getTime())[0];
336
+ const connection = {
337
+ connectionId: mostRecent.id,
338
+ appUserId: mostRecent.inkeepUserId,
339
+ appUserEmail: mostRecent.slackEmail || "",
340
+ slackDisplayName: mostRecent.slackUsername || "",
341
+ linkedAt: mostRecent.linkedAt,
342
+ tenantId: mostRecent.tenantId,
343
+ slackUserId: mostRecent.slackUserId,
344
+ slackTeamId: mostRecent.slackTeamId
345
+ };
346
+ logger.debug({
347
+ appUserId,
348
+ connected: true
349
+ }, "Retrieved connection status from DB");
350
+ return c.json({
351
+ connected: true,
352
+ connection
353
+ });
354
+ } catch (error) {
355
+ logger.error({
356
+ error,
357
+ appUserId
358
+ }, "Failed to get connection status");
359
+ return c.json({ error: "Failed to get connection status" }, 500);
360
+ }
361
+ });
362
+ var users_default = app;
363
+
364
+ //#endregion
365
+ export { users_default as default };
@@ -0,0 +1,10 @@
1
+ import { ManageAppVariables } from "../types.js";
2
+ import { OpenAPIHono } from "@hono/zod-openapi";
3
+
4
+ //#region src/slack/routes/workspaces.d.ts
5
+
6
+ declare const app: OpenAPIHono<{
7
+ Variables: ManageAppVariables;
8
+ }, {}, "/">;
9
+ //#endregion
10
+ export { app as default };