@inkeep/agents-work-apps 0.50.5 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/slack/dispatcher.js +24 -1
- package/dist/slack/i18n/strings.d.ts +1 -0
- package/dist/slack/i18n/strings.js +1 -0
- package/dist/slack/routes/users.js +12 -10
- package/dist/slack/routes/workspaces.js +85 -1
- package/dist/slack/services/blocks/index.d.ts +48 -1
- package/dist/slack/services/blocks/index.js +96 -19
- package/dist/slack/services/commands/index.d.ts +1 -1
- package/dist/slack/services/commands/index.js +98 -4
- package/dist/slack/services/events/app-mention.js +2 -2
- package/dist/slack/services/events/block-actions.d.ts +12 -1
- package/dist/slack/services/events/block-actions.js +126 -2
- package/dist/slack/services/events/index.d.ts +2 -2
- package/dist/slack/services/events/index.js +2 -2
- package/dist/slack/services/events/streaming.d.ts +1 -1
- package/dist/slack/services/events/streaming.js +71 -3
- package/dist/slack/services/events/utils.d.ts +1 -1
- package/dist/slack/services/events/utils.js +5 -2
- package/dist/slack/services/index.d.ts +3 -3
- package/dist/slack/services/index.js +3 -3
- package/dist/slack/tracer.d.ts +1 -0
- package/dist/slack/tracer.js +2 -1
- package/package.json +2 -2
package/dist/slack/dispatcher.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getSlackClient } from "./services/client.js";
|
|
|
4
4
|
import { sendResponseUrlMessage } from "./services/events/utils.js";
|
|
5
5
|
import { SLACK_SPAN_KEYS } from "./tracer.js";
|
|
6
6
|
import { handleAppMention } from "./services/events/app-mention.js";
|
|
7
|
-
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./services/events/block-actions.js";
|
|
7
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./services/events/block-actions.js";
|
|
8
8
|
import { handleFollowUpSubmission, handleModalSubmission } from "./services/events/modal-submission.js";
|
|
9
9
|
import "./services/events/index.js";
|
|
10
10
|
import "./services/index.js";
|
|
@@ -176,6 +176,29 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
|
|
|
176
176
|
}
|
|
177
177
|
})());
|
|
178
178
|
}
|
|
179
|
+
if ((action.action_id === "tool_approval_approve" || action.action_id === "tool_approval_deny") && action.value) {
|
|
180
|
+
anyHandled = true;
|
|
181
|
+
const approved = action.action_id === "tool_approval_approve";
|
|
182
|
+
const slackUserId = payload.user?.id || "";
|
|
183
|
+
logger.info({
|
|
184
|
+
teamId,
|
|
185
|
+
actionId: action.action_id,
|
|
186
|
+
approved
|
|
187
|
+
}, `Handling block_action: ${action.action_id}`);
|
|
188
|
+
registerBackgroundWork(handleToolApproval({
|
|
189
|
+
actionValue: action.value,
|
|
190
|
+
approved,
|
|
191
|
+
teamId,
|
|
192
|
+
slackUserId,
|
|
193
|
+
responseUrl
|
|
194
|
+
}).catch((err) => {
|
|
195
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
196
|
+
logger.error({
|
|
197
|
+
errorMessage,
|
|
198
|
+
actionId: action.action_id
|
|
199
|
+
}, "Failed to handle tool approval");
|
|
200
|
+
}).finally(() => flushTraces()));
|
|
201
|
+
}
|
|
179
202
|
if (action.action_id === "open_follow_up_modal" && action.value && triggerId) {
|
|
180
203
|
anyHandled = true;
|
|
181
204
|
logger.info({
|
|
@@ -45,6 +45,7 @@ declare const SlackStrings: {
|
|
|
45
45
|
};
|
|
46
46
|
readonly status: {
|
|
47
47
|
readonly thinking: (agentName: string) => string;
|
|
48
|
+
readonly readingThread: (agentName: string) => string;
|
|
48
49
|
readonly noAgentsAvailable: "No agents available";
|
|
49
50
|
readonly noProjectsConfigured: "No projects configured. Set up projects in the dashboard.";
|
|
50
51
|
};
|
|
@@ -41,6 +41,7 @@ const SlackStrings = {
|
|
|
41
41
|
usage: { mentionEmpty: "*Include a message to use your Inkeep agent:*\n\n• `@Inkeep <message>` — Message the default agent (reply appears in a thread)\n• `@Inkeep <message>` in a thread — Includes thread as context\n• `@Inkeep` in a thread — Uses the full thread as context\n\nUse `/inkeep help` for all available commands." },
|
|
42
42
|
status: {
|
|
43
43
|
thinking: (agentName) => `_${agentName} is thinking..._`,
|
|
44
|
+
readingThread: (agentName) => `_${agentName} is reading this thread..._`,
|
|
44
45
|
noAgentsAvailable: "No agents available",
|
|
45
46
|
noProjectsConfigured: "No projects configured. Set up projects in the dashboard."
|
|
46
47
|
},
|
|
@@ -131,12 +131,15 @@ app.openapi(createProtectedRoute({
|
|
|
131
131
|
tenantId
|
|
132
132
|
});
|
|
133
133
|
}
|
|
134
|
-
if (existingLink)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
if (existingLink) {
|
|
135
|
+
logger.info({
|
|
136
|
+
slackUserId,
|
|
137
|
+
existingUserId: existingLink.inkeepUserId,
|
|
138
|
+
newUserId: inkeepUserId,
|
|
139
|
+
tenantId
|
|
140
|
+
}, "Slack user already linked, updating to new user");
|
|
141
|
+
await deleteWorkAppSlackUserMapping(runDbClient_default)(tenantId, slackUserId, teamId, "work-apps-slack");
|
|
142
|
+
}
|
|
140
143
|
const slackUserMapping = await createWorkAppSlackUserMapping(runDbClient_default)({
|
|
141
144
|
tenantId,
|
|
142
145
|
clientId: "work-apps-slack",
|
|
@@ -162,10 +165,9 @@ app.openapi(createProtectedRoute({
|
|
|
162
165
|
tenantId
|
|
163
166
|
});
|
|
164
167
|
} catch (error) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return c.json({ error: "This Slack account is already linked to an Inkeep account." }, 409);
|
|
168
|
+
if (error instanceof Error && (error.message.includes("duplicate key") || error.message.includes("unique constraint")) || typeof error === "object" && error !== null && "cause" in error && typeof error.cause === "object" && error.cause?.code === "23505") {
|
|
169
|
+
logger.info({ userId: body.userId }, "Concurrent link resolved — mapping already exists");
|
|
170
|
+
return c.json({ success: true });
|
|
169
171
|
}
|
|
170
172
|
logger.error({
|
|
171
173
|
error,
|
|
@@ -5,7 +5,7 @@ import { getSlackChannels, getSlackClient, revokeSlackToken } from "../services/
|
|
|
5
5
|
import "../services/index.js";
|
|
6
6
|
import { requireChannelMemberOrAdmin, requireWorkspaceAdmin } from "../middleware/permissions.js";
|
|
7
7
|
import { OpenAPIHono, z } from "@hono/zod-openapi";
|
|
8
|
-
import { deleteAllWorkAppSlackChannelAgentConfigsByTeam, deleteAllWorkAppSlackUserMappingsByTeam, deleteWorkAppSlackChannelAgentConfig, deleteWorkAppSlackWorkspaceByNangoConnectionId, findWorkAppSlackChannelAgentConfig, listWorkAppSlackChannelAgentConfigsByTeam, listWorkAppSlackUserMappingsByTeam, upsertWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
|
|
8
|
+
import { deleteAllWorkAppSlackChannelAgentConfigsByTeam, deleteAllWorkAppSlackUserMappingsByTeam, deleteWorkAppSlackChannelAgentConfig, deleteWorkAppSlackWorkspaceByNangoConnectionId, findWorkAppSlackChannelAgentConfig, findWorkAppSlackWorkspaceByTeamId, listWorkAppSlackChannelAgentConfigsByTeam, listWorkAppSlackUserMappingsByTeam, updateWorkAppSlackWorkspace, upsertWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
|
|
9
9
|
import { createProtectedRoute, inheritedWorkAppsAuth } from "@inkeep/agents-core/middleware";
|
|
10
10
|
|
|
11
11
|
//#region src/slack/routes/workspaces.ts
|
|
@@ -48,6 +48,7 @@ const ChannelAgentConfigSchema = z.object({
|
|
|
48
48
|
grantAccessToMembers: z.boolean().optional()
|
|
49
49
|
});
|
|
50
50
|
const WorkspaceSettingsSchema = z.object({ defaultAgent: ChannelAgentConfigSchema.optional() });
|
|
51
|
+
const JoinFromWorkspaceSettingsSchema = z.object({ shouldAllowJoinFromWorkspace: z.boolean() });
|
|
51
52
|
app.openapi(createProtectedRoute({
|
|
52
53
|
method: "get",
|
|
53
54
|
path: "/",
|
|
@@ -222,6 +223,89 @@ app.openapi(createProtectedRoute({
|
|
|
222
223
|
}
|
|
223
224
|
return c.json({ success: true });
|
|
224
225
|
});
|
|
226
|
+
app.openapi(createProtectedRoute({
|
|
227
|
+
method: "get",
|
|
228
|
+
path: "/{teamId}/join-from-workspace",
|
|
229
|
+
summary: "Get Join From Workspace Setting",
|
|
230
|
+
description: "Get the join from workspace setting for the workspace",
|
|
231
|
+
operationId: "slack-get-join-from-workspace",
|
|
232
|
+
tags: [
|
|
233
|
+
"Work Apps",
|
|
234
|
+
"Slack",
|
|
235
|
+
"Workspaces"
|
|
236
|
+
],
|
|
237
|
+
permission: inheritedWorkAppsAuth(),
|
|
238
|
+
request: { params: z.object({ teamId: z.string() }) },
|
|
239
|
+
responses: {
|
|
240
|
+
200: {
|
|
241
|
+
description: "Join from workspace setting",
|
|
242
|
+
content: { "application/json": { schema: JoinFromWorkspaceSettingsSchema } }
|
|
243
|
+
},
|
|
244
|
+
404: { description: "Workspace not found" }
|
|
245
|
+
}
|
|
246
|
+
}), async (c) => {
|
|
247
|
+
const { teamId } = c.req.valid("param");
|
|
248
|
+
const sessionTenantId = c.get("tenantId");
|
|
249
|
+
if (!sessionTenantId) return c.json({ error: "Unauthorized" }, 401);
|
|
250
|
+
const workspace = await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(sessionTenantId, teamId);
|
|
251
|
+
if (!workspace) return c.json({ shouldAllowJoinFromWorkspace: false });
|
|
252
|
+
return c.json({ shouldAllowJoinFromWorkspace: workspace.shouldAllowJoinFromWorkspace ?? false });
|
|
253
|
+
});
|
|
254
|
+
app.openapi(createProtectedRoute({
|
|
255
|
+
method: "put",
|
|
256
|
+
path: "/{teamId}/join-from-workspace",
|
|
257
|
+
summary: "Update Join From Workspace Setting",
|
|
258
|
+
description: "Enable or disable join from workspace for the workspace",
|
|
259
|
+
operationId: "slack-update-join-from-workspace",
|
|
260
|
+
tags: [
|
|
261
|
+
"Work Apps",
|
|
262
|
+
"Slack",
|
|
263
|
+
"Workspaces"
|
|
264
|
+
],
|
|
265
|
+
permission: requireWorkspaceAdmin(),
|
|
266
|
+
request: {
|
|
267
|
+
params: z.object({ teamId: z.string() }),
|
|
268
|
+
body: { content: { "application/json": { schema: JoinFromWorkspaceSettingsSchema } } }
|
|
269
|
+
},
|
|
270
|
+
responses: {
|
|
271
|
+
200: {
|
|
272
|
+
description: "Join from workspace setting updated",
|
|
273
|
+
content: { "application/json": { schema: z.object({ success: z.boolean() }) } }
|
|
274
|
+
},
|
|
275
|
+
401: { description: "Unauthorized" },
|
|
276
|
+
404: { description: "Workspace not found" },
|
|
277
|
+
500: { description: "Failed to update setting" }
|
|
278
|
+
}
|
|
279
|
+
}), async (c) => {
|
|
280
|
+
const { teamId } = c.req.valid("param");
|
|
281
|
+
const { shouldAllowJoinFromWorkspace } = c.req.valid("json");
|
|
282
|
+
const sessionTenantId = c.get("tenantId");
|
|
283
|
+
if (!sessionTenantId) return c.json({ error: "Unauthorized" }, 401);
|
|
284
|
+
const workspace = await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(sessionTenantId, teamId);
|
|
285
|
+
if (!workspace) return c.json({ error: "Workspace not found" }, 404);
|
|
286
|
+
try {
|
|
287
|
+
if (!await updateWorkAppSlackWorkspace(runDbClient_default)(workspace.id, { shouldAllowJoinFromWorkspace })) {
|
|
288
|
+
logger.error({
|
|
289
|
+
teamId,
|
|
290
|
+
shouldAllowJoinFromWorkspace
|
|
291
|
+
}, "Failed to update join from workspace setting");
|
|
292
|
+
return c.json({ error: "Failed to update setting" }, 500);
|
|
293
|
+
}
|
|
294
|
+
logger.info({
|
|
295
|
+
teamId,
|
|
296
|
+
shouldAllowJoinFromWorkspace,
|
|
297
|
+
workspaceId: workspace.id
|
|
298
|
+
}, "Updated workspace join from workspace settings");
|
|
299
|
+
return c.json({ success: true });
|
|
300
|
+
} catch (error) {
|
|
301
|
+
logger.error({
|
|
302
|
+
teamId,
|
|
303
|
+
shouldAllowJoinFromWorkspace,
|
|
304
|
+
error
|
|
305
|
+
}, "Failed to update join from workspace setting");
|
|
306
|
+
return c.json({ error: "Failed to update setting" }, 500);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
225
309
|
app.openapi(createProtectedRoute({
|
|
226
310
|
method: "delete",
|
|
227
311
|
path: "/{teamId}",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import * as slack_block_builder0 from "slack-block-builder";
|
|
2
3
|
|
|
3
4
|
//#region src/slack/services/blocks/index.d.ts
|
|
@@ -63,6 +64,52 @@ interface AgentConfigSources {
|
|
|
63
64
|
} | null;
|
|
64
65
|
}
|
|
65
66
|
declare function createStatusMessage(email: string, linkedAt: string, dashboardUrl: string, agentConfigs: AgentConfigSources): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
67
|
+
interface ToolApprovalButtonValue {
|
|
68
|
+
toolCallId: string;
|
|
69
|
+
conversationId: string;
|
|
70
|
+
projectId: string;
|
|
71
|
+
agentId: string;
|
|
72
|
+
slackUserId: string;
|
|
73
|
+
channel: string;
|
|
74
|
+
threadTs: string;
|
|
75
|
+
toolName: string;
|
|
76
|
+
}
|
|
77
|
+
declare const ToolApprovalButtonValueSchema: z.ZodObject<{
|
|
78
|
+
toolCallId: z.ZodString;
|
|
79
|
+
conversationId: z.ZodString;
|
|
80
|
+
projectId: z.ZodString;
|
|
81
|
+
agentId: z.ZodString;
|
|
82
|
+
slackUserId: z.ZodString;
|
|
83
|
+
channel: z.ZodString;
|
|
84
|
+
threadTs: z.ZodString;
|
|
85
|
+
toolName: z.ZodString;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
declare function buildToolApprovalBlocks(params: {
|
|
88
|
+
toolName: string;
|
|
89
|
+
input?: Record<string, unknown>;
|
|
90
|
+
buttonValue: string;
|
|
91
|
+
}): any[];
|
|
92
|
+
declare function buildToolApprovalDoneBlocks(params: {
|
|
93
|
+
toolName: string;
|
|
94
|
+
approved: boolean;
|
|
95
|
+
actorUserId: string;
|
|
96
|
+
}): {
|
|
97
|
+
type: string;
|
|
98
|
+
elements: {
|
|
99
|
+
type: string;
|
|
100
|
+
text: string;
|
|
101
|
+
}[];
|
|
102
|
+
}[];
|
|
103
|
+
declare function buildToolApprovalExpiredBlocks(params: {
|
|
104
|
+
toolName: string;
|
|
105
|
+
}): {
|
|
106
|
+
type: string;
|
|
107
|
+
elements: {
|
|
108
|
+
type: string;
|
|
109
|
+
text: string;
|
|
110
|
+
}[];
|
|
111
|
+
}[];
|
|
66
112
|
declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
113
|
+
declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
|
|
67
114
|
//#endregion
|
|
68
|
-
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
115
|
+
export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
import { Blocks, Elements, Md, Message } from "slack-block-builder";
|
|
3
4
|
|
|
4
5
|
//#region src/slack/services/blocks/index.ts
|
|
@@ -34,24 +35,14 @@ function buildFollowUpButton(params) {
|
|
|
34
35
|
* Shows the user's message, a divider, the agent response, context, and a Follow Up button.
|
|
35
36
|
*/
|
|
36
37
|
function buildConversationResponseBlocks(params) {
|
|
37
|
-
const {
|
|
38
|
-
const blocks = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
text: `*You:* ${userMessage.length > 200 ? `${userMessage.slice(0, 200)}...` : userMessage}`
|
|
44
|
-
}]
|
|
45
|
-
},
|
|
46
|
-
{ type: "divider" },
|
|
47
|
-
{
|
|
48
|
-
type: "section",
|
|
49
|
-
text: {
|
|
50
|
-
type: "mrkdwn",
|
|
51
|
-
text: responseText
|
|
52
|
-
}
|
|
38
|
+
const { responseText, agentName, isError, followUpParams } = params;
|
|
39
|
+
const blocks = [{
|
|
40
|
+
type: "section",
|
|
41
|
+
text: {
|
|
42
|
+
type: "mrkdwn",
|
|
43
|
+
text: responseText
|
|
53
44
|
}
|
|
54
|
-
];
|
|
45
|
+
}];
|
|
55
46
|
if (!isError) {
|
|
56
47
|
const contextBlock = createContextBlock({
|
|
57
48
|
agentName,
|
|
@@ -66,7 +57,7 @@ function buildConversationResponseBlocks(params) {
|
|
|
66
57
|
return blocks;
|
|
67
58
|
}
|
|
68
59
|
function createUpdatedHelpMessage() {
|
|
69
|
-
return Message().blocks(Blocks.
|
|
60
|
+
return Message().blocks(Blocks.Header().text(SlackStrings.help.title), Blocks.Section().text(SlackStrings.help.publicSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.privateSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.otherCommands), Blocks.Divider(), Blocks.Context().elements(SlackStrings.help.docsLink)).buildToObject();
|
|
70
61
|
}
|
|
71
62
|
function createAlreadyLinkedMessage(email, linkedAt, dashboardUrl) {
|
|
72
63
|
return Message().blocks(Blocks.Section().text(Md.bold("Already linked") + "\n\nYour Slack account is connected to Inkeep.\n\n" + Md.bold("Account:") + ` ${email}\n` + Md.bold("Linked:") + ` ${new Date(linkedAt).toLocaleDateString()}\n\nTo switch accounts, first run \`/inkeep unlink\``), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
|
|
@@ -84,9 +75,95 @@ function createStatusMessage(email, linkedAt, dashboardUrl, agentConfigs) {
|
|
|
84
75
|
else agentLine = `${Md.bold("Agent:")} None configured\n${Md.italic("Ask your admin to set up an agent in the dashboard.")}`;
|
|
85
76
|
return Message().blocks(Blocks.Section().text(Md.bold("Connected to Inkeep") + `\n\n${Md.bold("Account:")} ${email}\n${Md.bold("Linked:")} ${new Date(linkedAt).toLocaleDateString()}\n` + agentLine), Blocks.Actions().elements(Elements.Button().text(SlackStrings.buttons.openDashboard).url(dashboardUrl).actionId("open_dashboard"))).buildToObject();
|
|
86
77
|
}
|
|
78
|
+
const ToolApprovalButtonValueSchema = z.object({
|
|
79
|
+
toolCallId: z.string(),
|
|
80
|
+
conversationId: z.string(),
|
|
81
|
+
projectId: z.string(),
|
|
82
|
+
agentId: z.string(),
|
|
83
|
+
slackUserId: z.string(),
|
|
84
|
+
channel: z.string(),
|
|
85
|
+
threadTs: z.string(),
|
|
86
|
+
toolName: z.string()
|
|
87
|
+
});
|
|
88
|
+
function buildToolApprovalBlocks(params) {
|
|
89
|
+
const { toolName, input, buttonValue } = params;
|
|
90
|
+
const blocks = [{
|
|
91
|
+
type: "header",
|
|
92
|
+
text: {
|
|
93
|
+
type: "plain_text",
|
|
94
|
+
text: "Tool Approval Required",
|
|
95
|
+
emoji: false
|
|
96
|
+
}
|
|
97
|
+
}, {
|
|
98
|
+
type: "section",
|
|
99
|
+
text: {
|
|
100
|
+
type: "mrkdwn",
|
|
101
|
+
text: `The agent wants to use \`${toolName}\`.`
|
|
102
|
+
}
|
|
103
|
+
}];
|
|
104
|
+
if (input && Object.keys(input).length > 0) {
|
|
105
|
+
const jsonStr = JSON.stringify(input, null, 2);
|
|
106
|
+
const truncated = jsonStr.length > 2900 ? `${jsonStr.slice(0, 2900)}…` : jsonStr;
|
|
107
|
+
blocks.push({
|
|
108
|
+
type: "section",
|
|
109
|
+
text: {
|
|
110
|
+
type: "mrkdwn",
|
|
111
|
+
text: `\`\`\`json\n${truncated}\n\`\`\``
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
blocks.push({ type: "divider" });
|
|
116
|
+
blocks.push({
|
|
117
|
+
type: "actions",
|
|
118
|
+
elements: [{
|
|
119
|
+
type: "button",
|
|
120
|
+
text: {
|
|
121
|
+
type: "plain_text",
|
|
122
|
+
text: "Approve",
|
|
123
|
+
emoji: false
|
|
124
|
+
},
|
|
125
|
+
style: "primary",
|
|
126
|
+
action_id: "tool_approval_approve",
|
|
127
|
+
value: buttonValue
|
|
128
|
+
}, {
|
|
129
|
+
type: "button",
|
|
130
|
+
text: {
|
|
131
|
+
type: "plain_text",
|
|
132
|
+
text: "Deny",
|
|
133
|
+
emoji: false
|
|
134
|
+
},
|
|
135
|
+
style: "danger",
|
|
136
|
+
action_id: "tool_approval_deny",
|
|
137
|
+
value: buttonValue
|
|
138
|
+
}]
|
|
139
|
+
});
|
|
140
|
+
return blocks;
|
|
141
|
+
}
|
|
142
|
+
function buildToolApprovalDoneBlocks(params) {
|
|
143
|
+
const { toolName, approved, actorUserId } = params;
|
|
144
|
+
return [{
|
|
145
|
+
type: "context",
|
|
146
|
+
elements: [{
|
|
147
|
+
type: "mrkdwn",
|
|
148
|
+
text: approved ? `✅ Approved \`${toolName}\` · <@${actorUserId}>` : `❌ Denied \`${toolName}\` · <@${actorUserId}>`
|
|
149
|
+
}]
|
|
150
|
+
}];
|
|
151
|
+
}
|
|
152
|
+
function buildToolApprovalExpiredBlocks(params) {
|
|
153
|
+
return [{
|
|
154
|
+
type: "context",
|
|
155
|
+
elements: [{
|
|
156
|
+
type: "mrkdwn",
|
|
157
|
+
text: `⏱️ Expired · \`${params.toolName}\``
|
|
158
|
+
}]
|
|
159
|
+
}];
|
|
160
|
+
}
|
|
87
161
|
function createJwtLinkMessage(linkUrl, expiresInMinutes) {
|
|
88
162
|
return Message().blocks(Blocks.Section().text(`${Md.bold("Link your Inkeep account")}\n\nConnect your Slack and Inkeep accounts to use Inkeep agents.`), Blocks.Actions().elements(Elements.Button().text("Link Account").url(linkUrl).actionId("link_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
89
163
|
}
|
|
164
|
+
function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
|
|
165
|
+
return Message().blocks(Blocks.Section().text(`${Md.bold("Create your Inkeep account")}\n\nYou've been invited to join Inkeep. Create an account to start using Inkeep agents in Slack.`), Blocks.Actions().elements(Elements.Button().text("Create Account").url(acceptUrl).actionId("create_account").primary()), Blocks.Context().elements(`This link expires in ${expiresInMinutes} minutes.`)).buildToObject();
|
|
166
|
+
}
|
|
90
167
|
|
|
91
168
|
//#endregion
|
|
92
|
-
export { buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
169
|
+
export { ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
|
|
@@ -2,7 +2,7 @@ import { SlackWorkspaceConnection } from "../nango.js";
|
|
|
2
2
|
import { SlackCommandPayload, SlackCommandResponse } from "../types.js";
|
|
3
3
|
|
|
4
4
|
//#region src/slack/services/commands/index.d.ts
|
|
5
|
-
declare function handleLinkCommand(payload: SlackCommandPayload, dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
|
|
5
|
+
declare function handleLinkCommand(payload: SlackCommandPayload, dashboardUrl: string, tenantId: string, botToken?: string): Promise<SlackCommandResponse>;
|
|
6
6
|
declare function handleUnlinkCommand(payload: SlackCommandPayload, tenantId: string): Promise<SlackCommandResponse>;
|
|
7
7
|
declare function handleStatusCommand(payload: SlackCommandPayload, dashboardUrl: string, tenantId: string): Promise<SlackCommandResponse>;
|
|
8
8
|
declare function handleHelpCommand(): Promise<SlackCommandResponse>;
|
|
@@ -4,22 +4,116 @@ import runDbClient_default from "../../../db/runDbClient.js";
|
|
|
4
4
|
import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
5
5
|
import { resolveEffectiveAgent } from "../agent-resolution.js";
|
|
6
6
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
7
|
-
import { createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
|
|
7
|
+
import { createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
|
|
8
8
|
import { getSlackClient } from "../client.js";
|
|
9
9
|
import { extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
|
|
10
10
|
import { buildAgentSelectorModal } from "../modals.js";
|
|
11
|
-
import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getInProcessFetch, getWaitUntil, signSlackLinkToken, signSlackUserToken } from "@inkeep/agents-core";
|
|
11
|
+
import { createInvitationInDb, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, findWorkAppSlackWorkspaceByTeamId, flushTraces, getInProcessFetch, getOrganizationMemberByEmail, getPendingInvitationsByEmail, getWaitUntil, signSlackLinkToken, signSlackUserToken } from "@inkeep/agents-core";
|
|
12
12
|
|
|
13
13
|
//#region src/slack/services/commands/index.ts
|
|
14
14
|
const DEFAULT_CLIENT_ID = "work-apps-slack";
|
|
15
15
|
const LINK_CODE_TTL_MINUTES = 10;
|
|
16
16
|
const logger = getLogger("slack-commands");
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Create an invitation for a Slack user who doesn't have an Inkeep account yet.
|
|
19
|
+
* Returns the invitation ID and email so the caller can direct the user
|
|
20
|
+
* to the accept-invitation page.
|
|
21
|
+
*
|
|
22
|
+
* Returns null if:
|
|
23
|
+
* - Workspace doesn't have shouldAllowJoinFromWorkspace enabled
|
|
24
|
+
* - User already has an Inkeep account (JWT link flow is sufficient)
|
|
25
|
+
* - Service account is not configured
|
|
26
|
+
*/
|
|
27
|
+
async function tryAutoInvite(payload, tenantId, botToken) {
|
|
28
|
+
try {
|
|
29
|
+
if (!(await findWorkAppSlackWorkspaceByTeamId(runDbClient_default)(tenantId, payload.teamId))?.shouldAllowJoinFromWorkspace) return null;
|
|
30
|
+
const slackClient = getSlackClient(botToken);
|
|
31
|
+
let userEmail;
|
|
32
|
+
try {
|
|
33
|
+
userEmail = (await slackClient.users.info({ user: payload.userId })).user?.profile?.email;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.warn({
|
|
36
|
+
error,
|
|
37
|
+
userId: payload.userId
|
|
38
|
+
}, "Failed to get user info from Slack");
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (!userEmail) {
|
|
42
|
+
logger.warn({ userId: payload.userId }, "No email found in Slack user profile");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (await getOrganizationMemberByEmail(runDbClient_default)(tenantId, userEmail)) {
|
|
46
|
+
logger.debug({
|
|
47
|
+
userId: payload.userId,
|
|
48
|
+
email: userEmail
|
|
49
|
+
}, "User already has Inkeep account, skipping auto-invite");
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const existingInvitation = (await getPendingInvitationsByEmail(runDbClient_default)(userEmail)).find((inv) => inv.organizationId === tenantId);
|
|
53
|
+
if (existingInvitation) {
|
|
54
|
+
logger.info({
|
|
55
|
+
userId: payload.userId,
|
|
56
|
+
tenantId,
|
|
57
|
+
invitationId: existingInvitation.id,
|
|
58
|
+
email: userEmail
|
|
59
|
+
}, "Reusing existing pending invitation for Slack user");
|
|
60
|
+
return {
|
|
61
|
+
invitationId: existingInvitation.id,
|
|
62
|
+
email: userEmail
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const invitation = await createInvitationInDb(runDbClient_default)({
|
|
66
|
+
organizationId: tenantId,
|
|
67
|
+
email: userEmail
|
|
68
|
+
});
|
|
69
|
+
logger.info({
|
|
70
|
+
userId: payload.userId,
|
|
71
|
+
tenantId,
|
|
72
|
+
invitationId: invitation.id,
|
|
73
|
+
email: userEmail
|
|
74
|
+
}, "Invitation created for Slack user without Inkeep account");
|
|
75
|
+
return {
|
|
76
|
+
invitationId: invitation.id,
|
|
77
|
+
email: userEmail
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.warn({
|
|
81
|
+
error,
|
|
82
|
+
userId: payload.userId,
|
|
83
|
+
tenantId
|
|
84
|
+
}, "Auto-invite attempt failed");
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function handleLinkCommand(payload, dashboardUrl, tenantId, botToken) {
|
|
18
89
|
const existingLink = await findWorkAppSlackUserMapping(runDbClient_default)(tenantId, payload.userId, payload.teamId, DEFAULT_CLIENT_ID);
|
|
19
90
|
if (existingLink) return {
|
|
20
91
|
response_type: "ephemeral",
|
|
21
92
|
...createAlreadyLinkedMessage(existingLink.slackEmail || existingLink.slackUsername || "Unknown", existingLink.linkedAt, dashboardUrl)
|
|
22
93
|
};
|
|
94
|
+
if (botToken) {
|
|
95
|
+
const autoInvite = await tryAutoInvite(payload, tenantId, botToken);
|
|
96
|
+
if (autoInvite) {
|
|
97
|
+
const manageUiUrl = env.INKEEP_AGENTS_MANAGE_UI_URL || "http://localhost:3000";
|
|
98
|
+
const linkToken = await signSlackLinkToken({
|
|
99
|
+
tenantId,
|
|
100
|
+
slackTeamId: payload.teamId,
|
|
101
|
+
slackUserId: payload.userId,
|
|
102
|
+
slackEnterpriseId: payload.enterpriseId,
|
|
103
|
+
slackUsername: payload.userName
|
|
104
|
+
});
|
|
105
|
+
const linkReturnUrl = `/link?token=${encodeURIComponent(linkToken)}`;
|
|
106
|
+
const acceptUrl = `${manageUiUrl}/accept-invitation/${autoInvite.invitationId}?email=${encodeURIComponent(autoInvite.email)}&returnUrl=${encodeURIComponent(linkReturnUrl)}`;
|
|
107
|
+
logger.info({
|
|
108
|
+
invitationId: autoInvite.invitationId,
|
|
109
|
+
email: autoInvite.email
|
|
110
|
+
}, "Directing new user to accept-invitation page with link returnUrl");
|
|
111
|
+
return {
|
|
112
|
+
response_type: "ephemeral",
|
|
113
|
+
...createCreateInkeepAccountMessage(acceptUrl, LINK_CODE_TTL_MINUTES)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
23
117
|
try {
|
|
24
118
|
const linkToken = await signSlackLinkToken({
|
|
25
119
|
tenantId,
|
|
@@ -363,7 +457,7 @@ async function handleCommand(payload) {
|
|
|
363
457
|
}, "Slack command received");
|
|
364
458
|
switch (subcommand) {
|
|
365
459
|
case "link":
|
|
366
|
-
case "connect": return handleLinkCommand(payload, dashboardUrl, tenantId);
|
|
460
|
+
case "connect": return handleLinkCommand(payload, dashboardUrl, tenantId, workspaceConnection.botToken);
|
|
367
461
|
case "status": return handleStatusCommand(payload, dashboardUrl, tenantId);
|
|
368
462
|
case "unlink":
|
|
369
463
|
case "logout":
|
|
@@ -196,7 +196,7 @@ async function handleAppMention(params) {
|
|
|
196
196
|
thinkingMessageTs = (await slackClient.chat.postMessage({
|
|
197
197
|
channel,
|
|
198
198
|
thread_ts: threadTs,
|
|
199
|
-
text:
|
|
199
|
+
text: SlackStrings.status.readingThread(agentDisplayName)
|
|
200
200
|
})).ts || void 0;
|
|
201
201
|
const conversationId$1 = generateSlackConversationId({
|
|
202
202
|
teamId,
|
|
@@ -274,7 +274,7 @@ Respond naturally as if you're joining the conversation to help.`;
|
|
|
274
274
|
thinkingMessageTs = (await slackClient.chat.postMessage({
|
|
275
275
|
channel,
|
|
276
276
|
thread_ts: replyThreadTs,
|
|
277
|
-
text:
|
|
277
|
+
text: SlackStrings.status.thinking(agentDisplayName)
|
|
278
278
|
})).ts || void 0;
|
|
279
279
|
const conversationId = generateSlackConversationId({
|
|
280
280
|
teamId,
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
* Handlers for Slack block action events (button clicks, selections, etc.)
|
|
4
4
|
* and message shortcuts
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Handle tool approval/denial button clicks.
|
|
8
|
+
* Called when a user clicks "Approve" or "Deny" on a tool approval message.
|
|
9
|
+
*/
|
|
10
|
+
declare function handleToolApproval(params: {
|
|
11
|
+
actionValue: string;
|
|
12
|
+
approved: boolean;
|
|
13
|
+
teamId: string;
|
|
14
|
+
slackUserId: string;
|
|
15
|
+
responseUrl?: string;
|
|
16
|
+
}): Promise<void>;
|
|
6
17
|
/**
|
|
7
18
|
* Handle opening the agent selector modal when user clicks "Select Agent" button
|
|
8
19
|
*/
|
|
@@ -37,4 +48,4 @@ declare function handleMessageShortcut(params: {
|
|
|
37
48
|
responseUrl?: string;
|
|
38
49
|
}): Promise<void>;
|
|
39
50
|
//#endregion
|
|
40
|
-
export { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal };
|
|
51
|
+
export { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { env } from "../../../env.js";
|
|
1
2
|
import { getLogger } from "../../../logger.js";
|
|
2
3
|
import { findWorkspaceConnectionByTeamId } from "../nango.js";
|
|
3
4
|
import { SlackStrings } from "../../i18n/strings.js";
|
|
5
|
+
import { ToolApprovalButtonValueSchema, buildToolApprovalDoneBlocks } from "../blocks/index.js";
|
|
4
6
|
import { getSlackClient } from "../client.js";
|
|
5
|
-
import { fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
|
|
7
|
+
import { fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
|
|
6
8
|
import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "../modals.js";
|
|
7
9
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
10
|
+
import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
|
|
8
11
|
|
|
9
12
|
//#region src/slack/services/events/block-actions.ts
|
|
10
13
|
/**
|
|
@@ -13,6 +16,127 @@ import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../
|
|
|
13
16
|
*/
|
|
14
17
|
const logger = getLogger("slack-block-actions");
|
|
15
18
|
/**
|
|
19
|
+
* Handle tool approval/denial button clicks.
|
|
20
|
+
* Called when a user clicks "Approve" or "Deny" on a tool approval message.
|
|
21
|
+
*/
|
|
22
|
+
async function handleToolApproval(params) {
|
|
23
|
+
return tracer.startActiveSpan(SLACK_SPAN_NAMES.TOOL_APPROVAL, async (span) => {
|
|
24
|
+
const { actionValue, approved, teamId, slackUserId, responseUrl } = params;
|
|
25
|
+
span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
|
|
26
|
+
span.setAttribute(SLACK_SPAN_KEYS.USER_ID, slackUserId);
|
|
27
|
+
try {
|
|
28
|
+
const buttonValue = ToolApprovalButtonValueSchema.parse(JSON.parse(actionValue));
|
|
29
|
+
const { toolCallId, conversationId, projectId, agentId, toolName } = buttonValue;
|
|
30
|
+
span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, conversationId);
|
|
31
|
+
const workspaceConnection = await findWorkspaceConnectionByTeamId(teamId);
|
|
32
|
+
if (!workspaceConnection?.botToken) {
|
|
33
|
+
logger.error({ teamId }, "No bot token for tool approval");
|
|
34
|
+
span.end();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const tenantId = workspaceConnection.tenantId;
|
|
38
|
+
const slackClient = getSlackClient(workspaceConnection.botToken);
|
|
39
|
+
if (slackUserId !== buttonValue.slackUserId) {
|
|
40
|
+
await slackClient.chat.postEphemeral({
|
|
41
|
+
channel: buttonValue.channel,
|
|
42
|
+
user: slackUserId,
|
|
43
|
+
thread_ts: buttonValue.threadTs,
|
|
44
|
+
text: "Only the user who started this conversation can approve or deny this action."
|
|
45
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to send ownership error notification"));
|
|
46
|
+
span.end();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const existingLink = await findCachedUserMapping(tenantId, slackUserId, teamId);
|
|
50
|
+
if (!existingLink) {
|
|
51
|
+
await slackClient.chat.postEphemeral({
|
|
52
|
+
channel: buttonValue.channel,
|
|
53
|
+
user: slackUserId,
|
|
54
|
+
thread_ts: buttonValue.threadTs,
|
|
55
|
+
text: "You need to link your Inkeep account first. Use `/inkeep link`."
|
|
56
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to send not-linked notification"));
|
|
57
|
+
span.end();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const slackUserToken = await signSlackUserToken({
|
|
61
|
+
inkeepUserId: existingLink.inkeepUserId,
|
|
62
|
+
tenantId,
|
|
63
|
+
slackTeamId: teamId,
|
|
64
|
+
slackUserId
|
|
65
|
+
});
|
|
66
|
+
const apiUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
|
|
67
|
+
const approvalResponse = await getInProcessFetch()(`${apiUrl}/run/api/chat`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
Authorization: `Bearer ${slackUserToken}`,
|
|
72
|
+
"x-inkeep-project-id": projectId,
|
|
73
|
+
"x-inkeep-agent-id": agentId
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({
|
|
76
|
+
conversationId,
|
|
77
|
+
messages: [{
|
|
78
|
+
role: "tool",
|
|
79
|
+
parts: [{
|
|
80
|
+
type: "tool-call",
|
|
81
|
+
toolCallId,
|
|
82
|
+
state: "approval-responded",
|
|
83
|
+
approval: {
|
|
84
|
+
id: toolCallId,
|
|
85
|
+
approved
|
|
86
|
+
}
|
|
87
|
+
}]
|
|
88
|
+
}]
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
if (!approvalResponse.ok) {
|
|
92
|
+
const errorBody = await approvalResponse.text().catch(() => "");
|
|
93
|
+
logger.error({
|
|
94
|
+
status: approvalResponse.status,
|
|
95
|
+
errorBody,
|
|
96
|
+
toolCallId,
|
|
97
|
+
conversationId
|
|
98
|
+
}, "Tool approval API call failed");
|
|
99
|
+
await slackClient.chat.postEphemeral({
|
|
100
|
+
channel: buttonValue.channel,
|
|
101
|
+
user: slackUserId,
|
|
102
|
+
thread_ts: buttonValue.threadTs,
|
|
103
|
+
text: `Failed to ${approved ? "approve" : "deny"} \`${toolName}\`. Please try again.`
|
|
104
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to send approval error notification"));
|
|
105
|
+
span.end();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (responseUrl) await sendResponseUrlMessage(responseUrl, {
|
|
109
|
+
text: approved ? `✅ Approved \`${toolName}\`` : `❌ Denied \`${toolName}\``,
|
|
110
|
+
replace_original: true,
|
|
111
|
+
blocks: buildToolApprovalDoneBlocks({
|
|
112
|
+
toolName,
|
|
113
|
+
approved,
|
|
114
|
+
actorUserId: slackUserId
|
|
115
|
+
})
|
|
116
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to update approval message"));
|
|
117
|
+
logger.info({
|
|
118
|
+
toolCallId,
|
|
119
|
+
conversationId,
|
|
120
|
+
approved,
|
|
121
|
+
slackUserId
|
|
122
|
+
}, "Tool approval processed");
|
|
123
|
+
span.end();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error instanceof Error) setSpanWithError(span, error);
|
|
126
|
+
logger.error({
|
|
127
|
+
error,
|
|
128
|
+
teamId,
|
|
129
|
+
slackUserId
|
|
130
|
+
}, "Failed to handle tool approval");
|
|
131
|
+
if (responseUrl) await sendResponseUrlMessage(responseUrl, {
|
|
132
|
+
text: "Something went wrong processing your request. Please try again.",
|
|
133
|
+
response_type: "ephemeral"
|
|
134
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to send error notification"));
|
|
135
|
+
span.end();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
16
140
|
* Handle opening the agent selector modal when user clicks "Select Agent" button
|
|
17
141
|
*/
|
|
18
142
|
async function handleOpenAgentSelectorModal(params) {
|
|
@@ -262,4 +386,4 @@ async function handleMessageShortcut(params) {
|
|
|
262
386
|
}
|
|
263
387
|
|
|
264
388
|
//#endregion
|
|
265
|
-
export { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal };
|
|
389
|
+
export { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InlineSelectorMetadata, handleAppMention } from "./app-mention.js";
|
|
2
|
-
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./block-actions.js";
|
|
2
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./block-actions.js";
|
|
3
3
|
import { handleFollowUpSubmission, handleModalSubmission } from "./modal-submission.js";
|
|
4
4
|
import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
|
|
5
5
|
import { StreamResult, streamAgentResponse } from "./streaming.js";
|
|
6
|
-
export { type InlineSelectorMetadata, SlackErrorType, type StreamResult, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, handleAppMention, handleFollowUpSubmission, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
|
|
6
|
+
export { type InlineSelectorMetadata, SlackErrorType, type StreamResult, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, handleAppMention, handleFollowUpSubmission, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
|
|
2
2
|
import { streamAgentResponse } from "./streaming.js";
|
|
3
3
|
import { handleAppMention } from "./app-mention.js";
|
|
4
|
-
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./block-actions.js";
|
|
4
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./block-actions.js";
|
|
5
5
|
import { handleFollowUpSubmission, handleModalSubmission } from "./modal-submission.js";
|
|
6
6
|
|
|
7
|
-
export { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, handleAppMention, handleFollowUpSubmission, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
|
|
7
|
+
export { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, handleAppMention, handleFollowUpSubmission, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { env } from "../../../env.js";
|
|
2
2
|
import { getLogger } from "../../../logger.js";
|
|
3
|
-
import { createContextBlock } from "../blocks/index.js";
|
|
3
|
+
import { buildToolApprovalBlocks, buildToolApprovalExpiredBlocks, createContextBlock } from "../blocks/index.js";
|
|
4
4
|
import { SlackErrorType, classifyError, extractApiErrorMessage, getUserFriendlyErrorMessage } from "./utils.js";
|
|
5
5
|
import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
|
|
6
6
|
import { getInProcessFetch } from "@inkeep/agents-core";
|
|
@@ -50,6 +50,10 @@ async function streamAgentResponse(params) {
|
|
|
50
50
|
projectId
|
|
51
51
|
}, "Starting streaming agent response");
|
|
52
52
|
const abortController = new AbortController();
|
|
53
|
+
const abortPromise = new Promise((_, reject) => {
|
|
54
|
+
abortController.signal.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("Stream timeout")), { once: true });
|
|
55
|
+
});
|
|
56
|
+
let reader;
|
|
53
57
|
const timeoutId = setTimeout(() => {
|
|
54
58
|
logger.warn({
|
|
55
59
|
channel,
|
|
@@ -177,7 +181,7 @@ async function streamAgentResponse(params) {
|
|
|
177
181
|
errorMessage
|
|
178
182
|
};
|
|
179
183
|
}
|
|
180
|
-
|
|
184
|
+
reader = response.body.getReader();
|
|
181
185
|
const decoder = new TextDecoder();
|
|
182
186
|
let buffer = "";
|
|
183
187
|
let fullText = "";
|
|
@@ -187,10 +191,11 @@ async function streamAgentResponse(params) {
|
|
|
187
191
|
recipient_user_id: slackUserId,
|
|
188
192
|
thread_ts: threadTs
|
|
189
193
|
});
|
|
194
|
+
const pendingApprovalMessages = [];
|
|
190
195
|
try {
|
|
191
196
|
let agentCompleted = false;
|
|
192
197
|
while (true) {
|
|
193
|
-
const { done, value } = await reader.read();
|
|
198
|
+
const { done, value } = await Promise.race([reader.read(), abortPromise]);
|
|
194
199
|
if (done) break;
|
|
195
200
|
buffer += decoder.decode(value, { stream: true });
|
|
196
201
|
const lines = buffer.split("\n");
|
|
@@ -208,6 +213,43 @@ async function streamAgentResponse(params) {
|
|
|
208
213
|
}
|
|
209
214
|
continue;
|
|
210
215
|
}
|
|
216
|
+
if (data.type === "tool-approval-request" && conversationId) {
|
|
217
|
+
const toolName = data.toolName || "Tool";
|
|
218
|
+
const toolCallId = data.toolCallId;
|
|
219
|
+
const input = data.input;
|
|
220
|
+
const buttonValue = {
|
|
221
|
+
toolCallId,
|
|
222
|
+
conversationId,
|
|
223
|
+
projectId,
|
|
224
|
+
agentId,
|
|
225
|
+
slackUserId,
|
|
226
|
+
channel,
|
|
227
|
+
threadTs,
|
|
228
|
+
toolName
|
|
229
|
+
};
|
|
230
|
+
const approvalPost = await slackClient.chat.postMessage({
|
|
231
|
+
channel,
|
|
232
|
+
thread_ts: threadTs,
|
|
233
|
+
text: `Tool approval required: \`${toolName}\``,
|
|
234
|
+
blocks: buildToolApprovalBlocks({
|
|
235
|
+
toolName,
|
|
236
|
+
input,
|
|
237
|
+
buttonValue: JSON.stringify(buttonValue)
|
|
238
|
+
})
|
|
239
|
+
}).catch((e) => {
|
|
240
|
+
logger.warn({
|
|
241
|
+
error: e,
|
|
242
|
+
toolCallId
|
|
243
|
+
}, "Failed to post tool approval message");
|
|
244
|
+
return null;
|
|
245
|
+
});
|
|
246
|
+
if (approvalPost?.ts) pendingApprovalMessages.push({
|
|
247
|
+
messageTs: approvalPost.ts,
|
|
248
|
+
toolName
|
|
249
|
+
});
|
|
250
|
+
clearTimeout(timeoutId);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
211
253
|
if (data.type === "text-start" || data.type === "text-end") continue;
|
|
212
254
|
if (data.type === "text-delta" && data.delta) {
|
|
213
255
|
fullText += data.delta;
|
|
@@ -256,7 +298,17 @@ async function streamAgentResponse(params) {
|
|
|
256
298
|
return { success: true };
|
|
257
299
|
} catch (streamError) {
|
|
258
300
|
clearTimeout(timeoutId);
|
|
301
|
+
reader?.cancel().catch(() => {});
|
|
259
302
|
if (streamError instanceof Error) setSpanWithError(span, streamError);
|
|
303
|
+
for (const { messageTs, toolName } of pendingApprovalMessages) await slackClient.chat.update({
|
|
304
|
+
channel,
|
|
305
|
+
ts: messageTs,
|
|
306
|
+
text: `⏱️ Expired · \`${toolName}\``,
|
|
307
|
+
blocks: buildToolApprovalExpiredBlocks({ toolName })
|
|
308
|
+
}).catch((e) => logger.warn({
|
|
309
|
+
error: e,
|
|
310
|
+
messageTs
|
|
311
|
+
}, "Failed to expire approval message"));
|
|
260
312
|
if (fullText.length > 0) {
|
|
261
313
|
span.setAttribute(SLACK_SPAN_KEYS.CONTENT_ALREADY_DELIVERED, true);
|
|
262
314
|
logger.warn({
|
|
@@ -275,6 +327,22 @@ async function streamAgentResponse(params) {
|
|
|
275
327
|
span.end();
|
|
276
328
|
return { success: true };
|
|
277
329
|
}
|
|
330
|
+
if (pendingApprovalMessages.length > 0) {
|
|
331
|
+
for (const { toolName } of pendingApprovalMessages) await slackClient.chat.postMessage({
|
|
332
|
+
channel,
|
|
333
|
+
thread_ts: threadTs,
|
|
334
|
+
text: `Approval for \`${toolName}\` has expired.`
|
|
335
|
+
}).catch((e) => logger.warn({ error: e }, "Failed to send approval expired notification"));
|
|
336
|
+
await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
|
|
337
|
+
if (thinkingMessageTs) try {
|
|
338
|
+
await slackClient.chat.delete({
|
|
339
|
+
channel,
|
|
340
|
+
ts: thinkingMessageTs
|
|
341
|
+
});
|
|
342
|
+
} catch {}
|
|
343
|
+
span.end();
|
|
344
|
+
return { success: true };
|
|
345
|
+
}
|
|
278
346
|
logger.error({ streamError }, "Error during Slack streaming");
|
|
279
347
|
await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
|
|
280
348
|
if (thinkingMessageTs) try {
|
|
@@ -11,9 +11,9 @@ declare function findCachedUserMapping(tenantId: string, slackUserId: string, te
|
|
|
11
11
|
id: string;
|
|
12
12
|
createdAt: string;
|
|
13
13
|
updatedAt: string;
|
|
14
|
+
slackUserId: string;
|
|
14
15
|
tenantId: string;
|
|
15
16
|
clientId: string;
|
|
16
|
-
slackUserId: string;
|
|
17
17
|
slackTeamId: string;
|
|
18
18
|
slackEnterpriseId: string | null;
|
|
19
19
|
inkeepUserId: string;
|
|
@@ -239,9 +239,12 @@ async function resolveChannelAgentConfig(teamId, channelId, workspace) {
|
|
|
239
239
|
async function sendResponseUrlMessage(responseUrl, message) {
|
|
240
240
|
try {
|
|
241
241
|
const payload = { text: message.text };
|
|
242
|
-
if (message.replace_original) payload.replace_original = true;
|
|
242
|
+
if (message.replace_original === true) payload.replace_original = true;
|
|
243
243
|
else if (message.delete_original) payload.delete_original = true;
|
|
244
|
-
else
|
|
244
|
+
else {
|
|
245
|
+
payload.replace_original = false;
|
|
246
|
+
if (message.response_type) payload.response_type = message.response_type;
|
|
247
|
+
}
|
|
245
248
|
if (message.blocks) payload.blocks = message.blocks;
|
|
246
249
|
logger.info({
|
|
247
250
|
hasBlocks: !!message.blocks,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
|
|
2
|
-
import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
2
|
+
import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
3
3
|
import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
|
|
4
4
|
import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
|
|
5
5
|
import { SlackCommandPayload, SlackCommandResponse } from "./types.js";
|
|
6
6
|
import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
|
|
7
7
|
import { InlineSelectorMetadata, handleAppMention } from "./events/app-mention.js";
|
|
8
|
-
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./events/block-actions.js";
|
|
8
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./events/block-actions.js";
|
|
9
9
|
import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
|
|
10
10
|
import { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, FollowUpModalMetadata, ModalMetadata, buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
|
|
11
11
|
import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
|
|
@@ -13,4 +13,4 @@ import { StreamResult, streamAgentResponse } from "./events/streaming.js";
|
|
|
13
13
|
import "./events/index.js";
|
|
14
14
|
import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
|
|
15
15
|
import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
|
|
16
|
-
export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, WorkspaceInstallData, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
|
16
|
+
export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, FollowUpButtonParams, FollowUpModalMetadata, InlineSelectorMetadata, ModalMetadata, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
|
|
2
2
|
import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
|
|
3
|
-
import { buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
3
|
+
import { ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
|
|
4
4
|
import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
|
|
5
5
|
import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
|
|
6
6
|
import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
|
|
7
7
|
import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
|
|
8
8
|
import { streamAgentResponse } from "./events/streaming.js";
|
|
9
9
|
import { handleAppMention } from "./events/app-mention.js";
|
|
10
|
-
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./events/block-actions.js";
|
|
10
|
+
import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./events/block-actions.js";
|
|
11
11
|
import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
|
|
12
12
|
import "./events/index.js";
|
|
13
13
|
import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
|
|
14
14
|
import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
|
|
15
15
|
|
|
16
|
-
export { SlackErrorType, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
|
16
|
+
export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildConversationResponseBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
|
package/dist/slack/tracer.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare const SLACK_SPAN_NAMES: {
|
|
|
15
15
|
readonly OPEN_FOLLOW_UP_MODAL: "slack.open_follow_up_modal";
|
|
16
16
|
readonly PROJECT_SELECT_UPDATE: "slack.project_select_update";
|
|
17
17
|
readonly CALL_AGENT_API: "slack.call_agent_api";
|
|
18
|
+
readonly TOOL_APPROVAL: "slack.tool_approval";
|
|
18
19
|
};
|
|
19
20
|
declare const SLACK_SPAN_KEYS: {
|
|
20
21
|
readonly TEAM_ID: "slack.team_id";
|
package/dist/slack/tracer.js
CHANGED
|
@@ -13,7 +13,8 @@ const SLACK_SPAN_NAMES = {
|
|
|
13
13
|
OPEN_AGENT_SELECTOR_MODAL: "slack.open_agent_selector_modal",
|
|
14
14
|
OPEN_FOLLOW_UP_MODAL: "slack.open_follow_up_modal",
|
|
15
15
|
PROJECT_SELECT_UPDATE: "slack.project_select_update",
|
|
16
|
-
CALL_AGENT_API: "slack.call_agent_api"
|
|
16
|
+
CALL_AGENT_API: "slack.call_agent_api",
|
|
17
|
+
TOOL_APPROVAL: "slack.tool_approval"
|
|
17
18
|
};
|
|
18
19
|
const SLACK_SPAN_KEYS = {
|
|
19
20
|
TEAM_ID: "slack.team_id",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inkeep/agents-work-apps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "First party integrations for Inkeep Agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"jose": "^6.1.0",
|
|
34
34
|
"minimatch": "^10.1.1",
|
|
35
35
|
"slack-block-builder": "^2.8.0",
|
|
36
|
-
"@inkeep/agents-core": "0.
|
|
36
|
+
"@inkeep/agents-core": "0.51.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@hono/zod-openapi": "^1.1.5",
|