@inkeep/agents-work-apps 0.0.0-dev-20260211191741 → 0.0.0-dev-20260211220939

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 (60) hide show
  1. package/dist/env.d.ts +24 -2
  2. package/dist/env.js +13 -2
  3. package/dist/github/index.d.ts +3 -3
  4. package/dist/github/mcp/index.d.ts +2 -2
  5. package/dist/github/routes/setup.d.ts +2 -2
  6. package/dist/github/routes/tokenExchange.d.ts +2 -2
  7. package/dist/github/routes/webhooks.d.ts +2 -2
  8. package/dist/slack/i18n/index.d.ts +2 -0
  9. package/dist/slack/i18n/index.js +3 -0
  10. package/dist/slack/i18n/strings.d.ts +73 -0
  11. package/dist/slack/i18n/strings.js +67 -0
  12. package/dist/slack/index.d.ts +18 -0
  13. package/dist/slack/index.js +28 -0
  14. package/dist/slack/middleware/permissions.d.ts +31 -0
  15. package/dist/slack/middleware/permissions.js +159 -0
  16. package/dist/slack/routes/events.d.ts +10 -0
  17. package/dist/slack/routes/events.js +390 -0
  18. package/dist/slack/routes/index.d.ts +10 -0
  19. package/dist/slack/routes/index.js +47 -0
  20. package/dist/slack/routes/oauth.d.ts +20 -0
  21. package/dist/slack/routes/oauth.js +325 -0
  22. package/dist/slack/routes/users.d.ts +10 -0
  23. package/dist/slack/routes/users.js +358 -0
  24. package/dist/slack/routes/workspaces.d.ts +10 -0
  25. package/dist/slack/routes/workspaces.js +875 -0
  26. package/dist/slack/services/agent-resolution.d.ts +41 -0
  27. package/dist/slack/services/agent-resolution.js +99 -0
  28. package/dist/slack/services/blocks/index.d.ts +73 -0
  29. package/dist/slack/services/blocks/index.js +103 -0
  30. package/dist/slack/services/client.d.ts +105 -0
  31. package/dist/slack/services/client.js +220 -0
  32. package/dist/slack/services/commands/index.d.ts +19 -0
  33. package/dist/slack/services/commands/index.js +538 -0
  34. package/dist/slack/services/events/app-mention.d.ts +40 -0
  35. package/dist/slack/services/events/app-mention.js +234 -0
  36. package/dist/slack/services/events/block-actions.d.ts +40 -0
  37. package/dist/slack/services/events/block-actions.js +221 -0
  38. package/dist/slack/services/events/index.d.ts +6 -0
  39. package/dist/slack/services/events/index.js +7 -0
  40. package/dist/slack/services/events/modal-submission.d.ts +30 -0
  41. package/dist/slack/services/events/modal-submission.js +346 -0
  42. package/dist/slack/services/events/streaming.d.ts +26 -0
  43. package/dist/slack/services/events/streaming.js +228 -0
  44. package/dist/slack/services/events/utils.d.ts +146 -0
  45. package/dist/slack/services/events/utils.js +369 -0
  46. package/dist/slack/services/index.d.ts +16 -0
  47. package/dist/slack/services/index.js +16 -0
  48. package/dist/slack/services/modals.d.ts +86 -0
  49. package/dist/slack/services/modals.js +355 -0
  50. package/dist/slack/services/nango.d.ts +85 -0
  51. package/dist/slack/services/nango.js +462 -0
  52. package/dist/slack/services/security.d.ts +35 -0
  53. package/dist/slack/services/security.js +65 -0
  54. package/dist/slack/services/types.d.ts +26 -0
  55. package/dist/slack/services/types.js +1 -0
  56. package/dist/slack/services/workspace-tokens.d.ts +25 -0
  57. package/dist/slack/services/workspace-tokens.js +27 -0
  58. package/dist/slack/types.d.ts +10 -0
  59. package/dist/slack/types.js +1 -0
  60. package/package.json +10 -2
@@ -0,0 +1,875 @@
1
+ import { getLogger } from "../../logger.js";
2
+ import runDbClient_default from "../../db/runDbClient.js";
3
+ import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent } from "../services/nango.js";
4
+ import { getSlackChannels, getSlackClient, revokeSlackToken } from "../services/client.js";
5
+ import "../services/index.js";
6
+ import { requireChannelMemberOrAdmin, requireWorkspaceAdmin } from "../middleware/permissions.js";
7
+ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
8
+ import { deleteAllWorkAppSlackChannelAgentConfigsByTeam, deleteAllWorkAppSlackUserMappingsByTeam, deleteWorkAppSlackChannelAgentConfig, deleteWorkAppSlackWorkspaceByNangoConnectionId, findWorkAppSlackChannelAgentConfig, listWorkAppSlackChannelAgentConfigsByTeam, listWorkAppSlackUserMappingsByTeam, upsertWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
9
+
10
+ //#region src/slack/routes/workspaces.ts
11
+ /**
12
+ * Slack Workspace Routes
13
+ *
14
+ * RESTful endpoints for managing Slack workspaces and their configurations:
15
+ * - GET /workspaces - List all installed workspaces
16
+ * - GET /workspaces/:teamId - Get workspace details
17
+ * - PUT /workspaces/:teamId/settings - Update workspace settings (default agent) [ADMIN ONLY]
18
+ * - DELETE /workspaces/:teamId - Uninstall workspace [ADMIN ONLY]
19
+ * - GET /workspaces/:teamId/channels - List channels
20
+ * - GET /workspaces/:teamId/channels/:channelId/settings - Get channel config
21
+ * - PUT /workspaces/:teamId/channels/:channelId/settings - Set channel default agent [ADMIN or CHANNEL MEMBER]
22
+ * - DELETE /workspaces/:teamId/channels/:channelId/settings - Remove channel config [ADMIN or CHANNEL MEMBER]
23
+ * - GET /workspaces/:teamId/users - List linked users
24
+ *
25
+ * Permission Model:
26
+ * - Read operations (GET): Authenticated users only (tenant isolation via verifyTenantOwnership in handler)
27
+ * - Workspace settings (PUT): Inkeep org admin/owner only (requireWorkspaceAdmin middleware)
28
+ * - Channel settings (PUT/DELETE): Inkeep org admin/owner OR channel member (requireChannelMemberOrAdmin middleware)
29
+ * - Workspace uninstall (DELETE): Inkeep org admin/owner only (requireWorkspaceAdmin middleware)
30
+ */
31
+ const logger = getLogger("slack-workspaces");
32
+ /**
33
+ * Verify workspace belongs to the authenticated user's tenant.
34
+ * Returns true if access is allowed, false if denied.
35
+ */
36
+ function verifyTenantOwnership(c, workspaceTenantId) {
37
+ const sessionTenantId = c.get("tenantId");
38
+ if (!sessionTenantId) return false;
39
+ return sessionTenantId === workspaceTenantId;
40
+ }
41
+ const app = new OpenAPIHono();
42
+ app.use("/:teamId/settings", async (c, next) => {
43
+ if (c.req.method === "PUT") return requireWorkspaceAdmin()(c, next);
44
+ return next();
45
+ });
46
+ app.use("/:teamId", async (c, next) => {
47
+ if (c.req.method === "DELETE") return requireWorkspaceAdmin()(c, next);
48
+ return next();
49
+ });
50
+ app.use("/:teamId/channels/:channelId/settings", async (c, next) => {
51
+ if (c.req.method === "PUT" || c.req.method === "DELETE") return requireChannelMemberOrAdmin()(c, next);
52
+ return next();
53
+ });
54
+ app.use("/:teamId/test-message", async (c, next) => {
55
+ if (c.req.method === "POST") return requireWorkspaceAdmin()(c, next);
56
+ return next();
57
+ });
58
+ const ChannelAgentConfigSchema = z.object({
59
+ projectId: z.string(),
60
+ agentId: z.string(),
61
+ agentName: z.string().optional(),
62
+ projectName: z.string().optional()
63
+ });
64
+ const WorkspaceSettingsSchema = z.object({ defaultAgent: ChannelAgentConfigSchema.optional() });
65
+ app.openapi(createRoute({
66
+ method: "get",
67
+ path: "/",
68
+ summary: "List Workspaces",
69
+ description: "List all installed Slack workspaces for the tenant",
70
+ operationId: "slack-list-workspaces",
71
+ tags: [
72
+ "Work Apps",
73
+ "Slack",
74
+ "Workspaces"
75
+ ],
76
+ responses: { 200: {
77
+ description: "List of workspaces",
78
+ content: { "application/json": { schema: z.object({ workspaces: z.array(z.object({
79
+ connectionId: z.string(),
80
+ teamId: z.string(),
81
+ teamName: z.string().optional(),
82
+ tenantId: z.string(),
83
+ hasDefaultAgent: z.boolean(),
84
+ defaultAgentName: z.string().optional()
85
+ })) }) } }
86
+ } }
87
+ }), async (c) => {
88
+ try {
89
+ const allWorkspaces = await listWorkspaceInstallations();
90
+ const sessionTenantId = c.get("tenantId");
91
+ const workspaces = sessionTenantId ? allWorkspaces.filter((w) => w.tenantId === sessionTenantId) : [];
92
+ logger.info({
93
+ count: workspaces.length,
94
+ tenantId: sessionTenantId
95
+ }, "Listed workspace installations");
96
+ return c.json({ workspaces: workspaces.map((w) => ({
97
+ connectionId: w.connectionId,
98
+ teamId: w.teamId,
99
+ teamName: w.teamName,
100
+ tenantId: w.tenantId,
101
+ hasDefaultAgent: !!w.defaultAgent,
102
+ defaultAgentName: w.defaultAgent?.agentName
103
+ })) });
104
+ } catch (error) {
105
+ logger.error({ error }, "Failed to list workspaces");
106
+ return c.json({ workspaces: [] });
107
+ }
108
+ });
109
+ app.openapi(createRoute({
110
+ method: "get",
111
+ path: "/:teamId",
112
+ summary: "Get Workspace",
113
+ description: "Get details of a specific Slack workspace",
114
+ operationId: "slack-get-workspace",
115
+ tags: [
116
+ "Work Apps",
117
+ "Slack",
118
+ "Workspaces"
119
+ ],
120
+ request: { params: z.object({ teamId: z.string() }) },
121
+ responses: {
122
+ 200: {
123
+ description: "Workspace details",
124
+ content: { "application/json": { schema: z.object({
125
+ teamId: z.string(),
126
+ teamName: z.string().optional(),
127
+ tenantId: z.string(),
128
+ connectionId: z.string(),
129
+ defaultAgent: ChannelAgentConfigSchema.optional()
130
+ }) } }
131
+ },
132
+ 404: { description: "Workspace not found" }
133
+ }
134
+ }), async (c) => {
135
+ const { teamId } = c.req.valid("param");
136
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
137
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ error: "Workspace not found" }, 404);
138
+ let defaultAgent;
139
+ const nangoDefault = await getWorkspaceDefaultAgentFromNango(teamId);
140
+ if (nangoDefault) defaultAgent = {
141
+ projectId: nangoDefault.projectId,
142
+ agentId: nangoDefault.agentId,
143
+ agentName: nangoDefault.agentName
144
+ };
145
+ return c.json({
146
+ teamId: workspace.teamId,
147
+ teamName: workspace.teamName,
148
+ tenantId: workspace.tenantId,
149
+ connectionId: workspace.connectionId,
150
+ defaultAgent
151
+ });
152
+ });
153
+ app.openapi(createRoute({
154
+ method: "get",
155
+ path: "/:teamId/settings",
156
+ summary: "Get Workspace Settings",
157
+ description: "Get settings for a Slack workspace including default agent",
158
+ operationId: "slack-get-workspace-settings",
159
+ tags: [
160
+ "Work Apps",
161
+ "Slack",
162
+ "Workspaces"
163
+ ],
164
+ request: { params: z.object({ teamId: z.string() }) },
165
+ responses: { 200: {
166
+ description: "Workspace settings",
167
+ content: { "application/json": { schema: WorkspaceSettingsSchema } }
168
+ } }
169
+ }), async (c) => {
170
+ const { teamId } = c.req.valid("param");
171
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
172
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ defaultAgent: void 0 });
173
+ let defaultAgent;
174
+ const nangoDefault = await getWorkspaceDefaultAgentFromNango(teamId);
175
+ if (nangoDefault) defaultAgent = {
176
+ projectId: nangoDefault.projectId,
177
+ agentId: nangoDefault.agentId,
178
+ agentName: nangoDefault.agentName,
179
+ projectName: nangoDefault.projectName
180
+ };
181
+ return c.json({ defaultAgent });
182
+ });
183
+ app.openapi(createRoute({
184
+ method: "put",
185
+ path: "/:teamId/settings",
186
+ summary: "Update Workspace Settings",
187
+ description: "Update workspace settings including default agent",
188
+ operationId: "slack-update-workspace-settings",
189
+ tags: [
190
+ "Work Apps",
191
+ "Slack",
192
+ "Workspaces"
193
+ ],
194
+ request: {
195
+ params: z.object({ teamId: z.string() }),
196
+ body: { content: { "application/json": { schema: WorkspaceSettingsSchema } } }
197
+ },
198
+ responses: {
199
+ 200: {
200
+ description: "Settings updated",
201
+ content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
202
+ },
203
+ 500: {
204
+ description: "Failed to update settings",
205
+ content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
206
+ }
207
+ }
208
+ }), async (c) => {
209
+ const { teamId } = c.req.valid("param");
210
+ const body = c.req.valid("json");
211
+ if (body.defaultAgent) {
212
+ if (!await setWorkspaceDefaultAgent(teamId, body.defaultAgent)) {
213
+ logger.warn({ teamId }, "Failed to persist workspace settings to Nango");
214
+ return c.json({ success: false }, 500);
215
+ }
216
+ logger.info({
217
+ teamId,
218
+ agentId: body.defaultAgent.agentId,
219
+ agentName: body.defaultAgent.agentName
220
+ }, "Saved workspace default agent to Nango");
221
+ } else {
222
+ await setWorkspaceDefaultAgent(teamId, null);
223
+ logger.info({ teamId }, "Cleared workspace default agent");
224
+ }
225
+ return c.json({ success: true });
226
+ });
227
+ app.openapi(createRoute({
228
+ method: "delete",
229
+ path: "/:teamId",
230
+ summary: "Uninstall Workspace",
231
+ description: "Uninstall Slack app from workspace. Accepts either teamId or connectionId.",
232
+ operationId: "slack-delete-workspace",
233
+ tags: [
234
+ "Work Apps",
235
+ "Slack",
236
+ "Workspaces"
237
+ ],
238
+ request: { params: z.object({ teamId: z.string() }) },
239
+ responses: {
240
+ 200: {
241
+ description: "Workspace uninstalled",
242
+ content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
243
+ },
244
+ 400: { description: "Invalid connectionId format" },
245
+ 404: { description: "Workspace not found" },
246
+ 500: { description: "Failed to uninstall workspace" }
247
+ }
248
+ }), async (c) => {
249
+ const { teamId: workspaceIdentifier } = c.req.valid("param");
250
+ let teamId;
251
+ let connectionId;
252
+ try {
253
+ if (workspaceIdentifier.includes(":")) {
254
+ connectionId = workspaceIdentifier;
255
+ const teamMatch = workspaceIdentifier.match(/T:([A-Z0-9]+)/);
256
+ if (!teamMatch) return c.json({ error: "Invalid connectionId format" }, 400);
257
+ teamId = teamMatch[1];
258
+ } else {
259
+ teamId = workspaceIdentifier;
260
+ connectionId = computeWorkspaceConnectionId({
261
+ teamId,
262
+ enterpriseId: void 0
263
+ });
264
+ }
265
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
266
+ if (!workspace) return c.json({ error: "Workspace not found" }, 404);
267
+ if (workspace.botToken) if (await revokeSlackToken(workspace.botToken)) logger.info({ teamId }, "Revoked Slack bot token");
268
+ else logger.warn({ teamId }, "Failed to revoke Slack bot token, continuing with uninstall");
269
+ const tenantId = workspace.tenantId || "default";
270
+ const deletedChannelConfigs = await deleteAllWorkAppSlackChannelAgentConfigsByTeam(runDbClient_default)(tenantId, teamId);
271
+ if (deletedChannelConfigs > 0) logger.info({
272
+ teamId,
273
+ deletedChannelConfigs
274
+ }, "Deleted channel configs for uninstalled workspace");
275
+ const deletedMappings = await deleteAllWorkAppSlackUserMappingsByTeam(runDbClient_default)(tenantId, teamId);
276
+ if (deletedMappings > 0) logger.info({
277
+ teamId,
278
+ deletedMappings
279
+ }, "Deleted user mappings for uninstalled workspace");
280
+ if (await deleteWorkAppSlackWorkspaceByNangoConnectionId(runDbClient_default)(connectionId)) logger.info({ connectionId }, "Deleted workspace from database");
281
+ if (!await deleteWorkspaceInstallation(connectionId)) logger.error({ connectionId }, "deleteWorkspaceInstallation returned false (DB already cleaned up)");
282
+ clearWorkspaceConnectionCache(teamId);
283
+ logger.info({
284
+ connectionId,
285
+ teamId
286
+ }, "Deleted workspace installation and cleared cache");
287
+ return c.json({ success: true });
288
+ } catch (error) {
289
+ logger.error({
290
+ error,
291
+ workspaceIdentifier
292
+ }, "Failed to uninstall workspace");
293
+ return c.json({ error: "Failed to uninstall workspace" }, 500);
294
+ }
295
+ });
296
+ app.openapi(createRoute({
297
+ method: "get",
298
+ path: "/:teamId/channels",
299
+ summary: "List Channels",
300
+ description: "List Slack channels in the workspace that the bot can see",
301
+ operationId: "slack-list-channels",
302
+ tags: [
303
+ "Work Apps",
304
+ "Slack",
305
+ "Channels"
306
+ ],
307
+ request: {
308
+ params: z.object({ teamId: z.string() }),
309
+ query: z.object({
310
+ limit: z.coerce.number().optional().default(100),
311
+ cursor: z.string().optional(),
312
+ types: z.string().optional()
313
+ })
314
+ },
315
+ responses: {
316
+ 200: {
317
+ description: "List of channels",
318
+ content: { "application/json": { schema: z.object({
319
+ channels: z.array(z.object({
320
+ id: z.string(),
321
+ name: z.string(),
322
+ isPrivate: z.boolean(),
323
+ isShared: z.boolean(),
324
+ memberCount: z.number().optional(),
325
+ hasAgentConfig: z.boolean(),
326
+ agentConfig: ChannelAgentConfigSchema.optional()
327
+ })),
328
+ nextCursor: z.string().optional()
329
+ }) } }
330
+ },
331
+ 404: { description: "Workspace not found" }
332
+ }
333
+ }), async (c) => {
334
+ const { teamId } = c.req.valid("param");
335
+ const { limit } = c.req.valid("query");
336
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
337
+ if (!workspace?.botToken || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ error: "Workspace not found or no bot token" }, 404);
338
+ const tenantId = workspace.tenantId || "default";
339
+ const slackClient = getSlackClient(workspace.botToken);
340
+ try {
341
+ const channels = await getSlackChannels(slackClient, limit);
342
+ let channelConfigs = [];
343
+ try {
344
+ channelConfigs = await listWorkAppSlackChannelAgentConfigsByTeam(runDbClient_default)(tenantId, teamId);
345
+ } catch (configError) {
346
+ logger.warn({
347
+ error: configError,
348
+ teamId
349
+ }, "Failed to fetch channel configs, table may not exist yet");
350
+ }
351
+ const configMap = new Map(channelConfigs.map((c$1) => [c$1.slackChannelId, c$1]));
352
+ const channelsWithConfig = channels.map((channel) => {
353
+ const config = channel.id ? configMap.get(channel.id) : void 0;
354
+ return {
355
+ id: channel.id || "",
356
+ name: channel.name || "",
357
+ isPrivate: channel.isPrivate ?? false,
358
+ isShared: channel.isShared ?? false,
359
+ memberCount: channel.memberCount,
360
+ hasAgentConfig: !!config,
361
+ agentConfig: config ? {
362
+ projectId: config.projectId,
363
+ agentId: config.agentId,
364
+ agentName: config.agentName || void 0
365
+ } : void 0
366
+ };
367
+ });
368
+ return c.json({
369
+ channels: channelsWithConfig,
370
+ nextCursor: void 0
371
+ });
372
+ } catch (error) {
373
+ logger.error({
374
+ error,
375
+ teamId
376
+ }, "Failed to list channels");
377
+ return c.json({ error: "Failed to list channels" }, 500);
378
+ }
379
+ });
380
+ app.openapi(createRoute({
381
+ method: "get",
382
+ path: "/:teamId/channels/:channelId/settings",
383
+ summary: "Get Channel Settings",
384
+ description: "Get default agent configuration for a specific channel",
385
+ operationId: "slack-get-channel-settings",
386
+ tags: [
387
+ "Work Apps",
388
+ "Slack",
389
+ "Channels"
390
+ ],
391
+ request: { params: z.object({
392
+ teamId: z.string(),
393
+ channelId: z.string()
394
+ }) },
395
+ responses: { 200: {
396
+ description: "Channel settings",
397
+ content: { "application/json": { schema: z.object({
398
+ channelId: z.string(),
399
+ agentConfig: ChannelAgentConfigSchema.optional()
400
+ }) } }
401
+ } }
402
+ }), async (c) => {
403
+ const { teamId, channelId } = c.req.valid("param");
404
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
405
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({
406
+ channelId,
407
+ agentConfig: void 0
408
+ });
409
+ const tenantId = workspace.tenantId || "default";
410
+ const config = await findWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
411
+ return c.json({
412
+ channelId,
413
+ agentConfig: config ? {
414
+ projectId: config.projectId,
415
+ agentId: config.agentId,
416
+ agentName: config.agentName || void 0
417
+ } : void 0
418
+ });
419
+ });
420
+ app.openapi(createRoute({
421
+ method: "put",
422
+ path: "/:teamId/channels/:channelId/settings",
423
+ summary: "Set Channel Default Agent",
424
+ description: "Set or update the default agent for a specific channel",
425
+ operationId: "slack-set-channel-settings",
426
+ tags: [
427
+ "Work Apps",
428
+ "Slack",
429
+ "Channels"
430
+ ],
431
+ request: {
432
+ params: z.object({
433
+ teamId: z.string(),
434
+ channelId: z.string()
435
+ }),
436
+ body: { content: { "application/json": { schema: z.object({
437
+ agentConfig: ChannelAgentConfigSchema,
438
+ channelName: z.string().optional(),
439
+ channelType: z.string().optional()
440
+ }) } } }
441
+ },
442
+ responses: { 200: {
443
+ description: "Channel settings updated",
444
+ content: { "application/json": { schema: z.object({
445
+ success: z.boolean(),
446
+ configId: z.string()
447
+ }) } }
448
+ } }
449
+ }), async (c) => {
450
+ const { teamId, channelId } = c.req.valid("param");
451
+ const body = c.req.valid("json");
452
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
453
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({
454
+ success: false,
455
+ configId: ""
456
+ });
457
+ const tenantId = workspace.tenantId || "default";
458
+ const config = await upsertWorkAppSlackChannelAgentConfig(runDbClient_default)({
459
+ tenantId,
460
+ slackTeamId: teamId,
461
+ slackChannelId: channelId,
462
+ slackChannelName: body.channelName,
463
+ slackChannelType: body.channelType,
464
+ projectId: body.agentConfig.projectId,
465
+ agentId: body.agentConfig.agentId,
466
+ agentName: body.agentConfig.agentName,
467
+ enabled: true
468
+ });
469
+ logger.info({
470
+ teamId,
471
+ channelId,
472
+ agentId: body.agentConfig.agentId
473
+ }, "Set channel default agent");
474
+ return c.json({
475
+ success: true,
476
+ configId: config.id
477
+ });
478
+ });
479
+ app.use("/:teamId/channels/bulk", async (c, next) => {
480
+ if (c.req.method === "PUT" || c.req.method === "DELETE") return requireWorkspaceAdmin()(c, next);
481
+ return next();
482
+ });
483
+ app.openapi(createRoute({
484
+ method: "put",
485
+ path: "/:teamId/channels/bulk",
486
+ summary: "Bulk Set Channel Agents",
487
+ description: "Apply the same agent configuration to multiple channels at once",
488
+ operationId: "slack-bulk-set-channel-agents",
489
+ tags: [
490
+ "Work Apps",
491
+ "Slack",
492
+ "Channels"
493
+ ],
494
+ request: {
495
+ params: z.object({ teamId: z.string() }),
496
+ body: { content: { "application/json": { schema: z.object({
497
+ channelIds: z.array(z.string()).min(1),
498
+ agentConfig: ChannelAgentConfigSchema
499
+ }) } } }
500
+ },
501
+ responses: {
502
+ 200: {
503
+ description: "Channels updated",
504
+ content: { "application/json": { schema: z.object({
505
+ success: z.boolean(),
506
+ updated: z.number(),
507
+ failed: z.number(),
508
+ errors: z.array(z.object({
509
+ channelId: z.string(),
510
+ error: z.string()
511
+ })).optional()
512
+ }) } }
513
+ },
514
+ 400: { description: "Invalid request" },
515
+ 404: { description: "Workspace not found" }
516
+ }
517
+ }), async (c) => {
518
+ const { teamId } = c.req.valid("param");
519
+ const body = c.req.valid("json");
520
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
521
+ if (!workspace?.botToken || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ error: "Workspace not found or no bot token" }, 404);
522
+ const tenantId = workspace.tenantId || "default";
523
+ const channels = await getSlackChannels(getSlackClient(workspace.botToken), 500);
524
+ const channelMap = new Map(channels.map((ch) => [ch.id, ch]));
525
+ let updated = 0;
526
+ const errors = [];
527
+ await Promise.all(body.channelIds.map(async (channelId) => {
528
+ try {
529
+ const channel = channelMap.get(channelId);
530
+ if (!channel) {
531
+ errors.push({
532
+ channelId,
533
+ error: "Channel not found"
534
+ });
535
+ return;
536
+ }
537
+ await upsertWorkAppSlackChannelAgentConfig(runDbClient_default)({
538
+ tenantId,
539
+ slackTeamId: teamId,
540
+ slackChannelId: channelId,
541
+ slackChannelName: channel.name || channelId,
542
+ slackChannelType: "public",
543
+ projectId: body.agentConfig.projectId,
544
+ agentId: body.agentConfig.agentId,
545
+ agentName: body.agentConfig.agentName,
546
+ enabled: true
547
+ });
548
+ updated++;
549
+ } catch (error) {
550
+ const errorMessage = error instanceof Error ? error.message : String(error);
551
+ errors.push({
552
+ channelId,
553
+ error: errorMessage
554
+ });
555
+ }
556
+ }));
557
+ logger.info({
558
+ teamId,
559
+ agentId: body.agentConfig.agentId,
560
+ updated,
561
+ failed: errors.length
562
+ }, "Bulk set channel agents");
563
+ return c.json({
564
+ success: errors.length === 0,
565
+ updated,
566
+ failed: errors.length,
567
+ errors: errors.length > 0 ? errors : void 0
568
+ });
569
+ });
570
+ app.openapi(createRoute({
571
+ method: "delete",
572
+ path: "/:teamId/channels/bulk",
573
+ summary: "Bulk Remove Channel Configs",
574
+ description: "Remove agent configuration from multiple channels at once",
575
+ operationId: "slack-bulk-delete-channel-agents",
576
+ tags: [
577
+ "Work Apps",
578
+ "Slack",
579
+ "Channels"
580
+ ],
581
+ request: {
582
+ params: z.object({ teamId: z.string() }),
583
+ body: { content: { "application/json": { schema: z.object({ channelIds: z.array(z.string()).min(1) }) } } }
584
+ },
585
+ responses: { 200: {
586
+ description: "Configs removed",
587
+ content: { "application/json": { schema: z.object({
588
+ success: z.boolean(),
589
+ removed: z.number()
590
+ }) } }
591
+ } }
592
+ }), async (c) => {
593
+ const { teamId } = c.req.valid("param");
594
+ const body = c.req.valid("json");
595
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
596
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({
597
+ success: false,
598
+ removed: 0
599
+ });
600
+ const tenantId = workspace.tenantId || "default";
601
+ let removed = 0;
602
+ await Promise.all(body.channelIds.map(async (channelId) => {
603
+ if (await deleteWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId)) removed++;
604
+ }));
605
+ logger.info({
606
+ teamId,
607
+ removed
608
+ }, "Bulk removed channel agent configs");
609
+ return c.json({
610
+ success: true,
611
+ removed
612
+ });
613
+ });
614
+ app.openapi(createRoute({
615
+ method: "delete",
616
+ path: "/:teamId/channels/:channelId/settings",
617
+ summary: "Remove Channel Config",
618
+ description: "Remove the default agent configuration for a channel",
619
+ operationId: "slack-delete-channel-settings",
620
+ tags: [
621
+ "Work Apps",
622
+ "Slack",
623
+ "Channels"
624
+ ],
625
+ request: { params: z.object({
626
+ teamId: z.string(),
627
+ channelId: z.string()
628
+ }) },
629
+ responses: { 200: {
630
+ description: "Channel config removed",
631
+ content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
632
+ } }
633
+ }), async (c) => {
634
+ const { teamId, channelId } = c.req.valid("param");
635
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
636
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ success: false });
637
+ const tenantId = workspace.tenantId || "default";
638
+ const deleted = await deleteWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
639
+ logger.info({
640
+ teamId,
641
+ channelId,
642
+ deleted
643
+ }, "Removed channel agent config");
644
+ return c.json({ success: deleted });
645
+ });
646
+ app.openapi(createRoute({
647
+ method: "get",
648
+ path: "/:teamId/users",
649
+ summary: "List Linked Users",
650
+ description: "List all users linked to Inkeep in this workspace",
651
+ operationId: "slack-list-linked-users",
652
+ tags: [
653
+ "Work Apps",
654
+ "Slack",
655
+ "Users"
656
+ ],
657
+ request: { params: z.object({ teamId: z.string() }) },
658
+ responses: { 200: {
659
+ description: "List of linked users",
660
+ content: { "application/json": { schema: z.object({ linkedUsers: z.array(z.object({
661
+ id: z.string(),
662
+ slackUserId: z.string(),
663
+ slackTeamId: z.string(),
664
+ slackUsername: z.string().optional(),
665
+ slackEmail: z.string().optional(),
666
+ userId: z.string(),
667
+ linkedAt: z.string(),
668
+ lastUsedAt: z.string().optional()
669
+ })) }) } }
670
+ } }
671
+ }), async (c) => {
672
+ const { teamId } = c.req.valid("param");
673
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
674
+ if (!workspace || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({ linkedUsers: [] });
675
+ const tenantId = workspace.tenantId || "default";
676
+ const linkedUsers = await listWorkAppSlackUserMappingsByTeam(runDbClient_default)(tenantId, teamId);
677
+ logger.info({
678
+ teamId,
679
+ tenantId,
680
+ count: linkedUsers.length
681
+ }, "Fetched linked users");
682
+ return c.json({ linkedUsers: linkedUsers.map((link) => ({
683
+ id: link.id,
684
+ slackUserId: link.slackUserId,
685
+ slackTeamId: link.slackTeamId,
686
+ slackUsername: link.slackUsername || void 0,
687
+ slackEmail: link.slackEmail || void 0,
688
+ userId: link.inkeepUserId,
689
+ linkedAt: link.linkedAt,
690
+ lastUsedAt: link.lastUsedAt || void 0
691
+ })) });
692
+ });
693
+ app.openapi(createRoute({
694
+ method: "get",
695
+ path: "/:teamId/health",
696
+ summary: "Check Workspace Health",
697
+ description: "Verify the bot token is valid and check permissions. Returns bot info and permission status.",
698
+ operationId: "slack-workspace-health",
699
+ tags: [
700
+ "Work Apps",
701
+ "Slack",
702
+ "Workspaces"
703
+ ],
704
+ request: { params: z.object({ teamId: z.string() }) },
705
+ responses: {
706
+ 200: {
707
+ description: "Health check result",
708
+ content: { "application/json": { schema: z.object({
709
+ healthy: z.boolean(),
710
+ botId: z.string().optional(),
711
+ botName: z.string().optional(),
712
+ teamId: z.string().optional(),
713
+ teamName: z.string().optional(),
714
+ permissions: z.object({
715
+ canPostMessages: z.boolean(),
716
+ canReadChannels: z.boolean(),
717
+ canReadHistory: z.boolean()
718
+ }),
719
+ error: z.string().optional()
720
+ }) } }
721
+ },
722
+ 404: { description: "Workspace not found" }
723
+ }
724
+ }), async (c) => {
725
+ const { teamId } = c.req.valid("param");
726
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
727
+ if (!workspace?.botToken || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({
728
+ healthy: false,
729
+ permissions: {
730
+ canPostMessages: false,
731
+ canReadChannels: false,
732
+ canReadHistory: false
733
+ },
734
+ error: "Workspace not found or no bot token available"
735
+ });
736
+ try {
737
+ const slackClient = getSlackClient(workspace.botToken);
738
+ const authResult = await slackClient.auth.test();
739
+ if (!authResult.ok) return c.json({
740
+ healthy: false,
741
+ permissions: {
742
+ canPostMessages: false,
743
+ canReadChannels: false,
744
+ canReadHistory: false
745
+ },
746
+ error: "Bot token is invalid or revoked"
747
+ });
748
+ const permissions = {
749
+ canPostMessages: true,
750
+ canReadChannels: true,
751
+ canReadHistory: true
752
+ };
753
+ try {
754
+ await slackClient.conversations.list({ limit: 1 });
755
+ } catch {
756
+ permissions.canReadChannels = false;
757
+ }
758
+ logger.info({
759
+ teamId,
760
+ botId: authResult.user_id,
761
+ permissions
762
+ }, "Workspace health check completed");
763
+ return c.json({
764
+ healthy: true,
765
+ botId: authResult.user_id,
766
+ botName: authResult.user,
767
+ teamId: authResult.team_id,
768
+ teamName: authResult.team,
769
+ permissions
770
+ });
771
+ } catch (error) {
772
+ const errorMessage = error instanceof Error ? error.message : String(error);
773
+ logger.error({
774
+ error: errorMessage,
775
+ teamId
776
+ }, "Health check failed");
777
+ return c.json({
778
+ healthy: false,
779
+ permissions: {
780
+ canPostMessages: false,
781
+ canReadChannels: false,
782
+ canReadHistory: false
783
+ },
784
+ error: errorMessage
785
+ });
786
+ }
787
+ });
788
+ app.openapi(createRoute({
789
+ method: "post",
790
+ path: "/:teamId/test-message",
791
+ summary: "Send Test Message",
792
+ description: "Send a test message to verify the bot is working correctly.",
793
+ operationId: "slack-test-message",
794
+ tags: [
795
+ "Work Apps",
796
+ "Slack",
797
+ "Workspaces"
798
+ ],
799
+ request: {
800
+ params: z.object({ teamId: z.string() }),
801
+ body: { content: { "application/json": { schema: z.object({
802
+ channelId: z.string(),
803
+ message: z.string().optional()
804
+ }) } } }
805
+ },
806
+ responses: {
807
+ 200: {
808
+ description: "Test message sent",
809
+ content: { "application/json": { schema: z.object({
810
+ success: z.boolean(),
811
+ messageTs: z.string().optional(),
812
+ error: z.string().optional()
813
+ }) } }
814
+ },
815
+ 400: { description: "Invalid request" },
816
+ 404: { description: "Workspace not found" }
817
+ }
818
+ }), async (c) => {
819
+ const { teamId } = c.req.valid("param");
820
+ const { channelId, message } = c.req.valid("json");
821
+ const workspace = await findWorkspaceConnectionByTeamId(teamId);
822
+ if (!workspace?.botToken || !verifyTenantOwnership(c, workspace.tenantId)) return c.json({
823
+ success: false,
824
+ error: "Workspace not found or no bot token available"
825
+ }, 404);
826
+ try {
827
+ const slackClient = getSlackClient(workspace.botToken);
828
+ const testMessage = message || "✅ *Test message from Inkeep*\n\nYour Slack integration is working correctly!";
829
+ const result = await slackClient.chat.postMessage({
830
+ channel: channelId,
831
+ text: testMessage,
832
+ blocks: [{
833
+ type: "section",
834
+ text: {
835
+ type: "mrkdwn",
836
+ text: testMessage
837
+ }
838
+ }, {
839
+ type: "context",
840
+ elements: [{
841
+ type: "mrkdwn",
842
+ text: "_This is a test message from the Inkeep dashboard_"
843
+ }]
844
+ }]
845
+ });
846
+ if (!result.ok) return c.json({
847
+ success: false,
848
+ error: result.error || "Failed to send message"
849
+ });
850
+ logger.info({
851
+ teamId,
852
+ channelId,
853
+ messageTs: result.ts
854
+ }, "Test message sent");
855
+ return c.json({
856
+ success: true,
857
+ messageTs: result.ts
858
+ });
859
+ } catch (error) {
860
+ const errorMessage = error instanceof Error ? error.message : String(error);
861
+ logger.error({
862
+ error: errorMessage,
863
+ teamId,
864
+ channelId
865
+ }, "Failed to send test message");
866
+ return c.json({
867
+ success: false,
868
+ error: errorMessage
869
+ });
870
+ }
871
+ });
872
+ var workspaces_default = app;
873
+
874
+ //#endregion
875
+ export { workspaces_default as default };