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