@inkeep/agents-work-apps 0.53.1 → 0.53.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/env.d.ts +2 -2
  2. package/dist/github/mcp/index.d.ts +2 -2
  3. package/dist/github/routes/setup.d.ts +2 -2
  4. package/dist/github/routes/webhooks.d.ts +2 -2
  5. package/dist/slack/dispatcher.js +54 -40
  6. package/dist/slack/i18n/strings.d.ts +6 -5
  7. package/dist/slack/i18n/strings.js +7 -10
  8. package/dist/slack/routes/events.js +1 -1
  9. package/dist/slack/services/blocks/index.d.ts +3 -35
  10. package/dist/slack/services/blocks/index.js +5 -42
  11. package/dist/slack/services/commands/index.js +42 -104
  12. package/dist/slack/services/events/app-mention.js +8 -31
  13. package/dist/slack/services/events/block-actions.d.ts +1 -11
  14. package/dist/slack/services/events/block-actions.js +6 -49
  15. package/dist/slack/services/events/direct-message.d.ts +11 -0
  16. package/dist/slack/services/events/direct-message.js +148 -0
  17. package/dist/slack/services/events/execution.d.ts +20 -0
  18. package/dist/slack/services/events/execution.js +46 -0
  19. package/dist/slack/services/events/index.d.ts +5 -3
  20. package/dist/slack/services/events/index.js +5 -3
  21. package/dist/slack/services/events/modal-submission.d.ts +1 -21
  22. package/dist/slack/services/events/modal-submission.js +14 -294
  23. package/dist/slack/services/events/streaming.d.ts +1 -1
  24. package/dist/slack/services/events/streaming.js +69 -70
  25. package/dist/slack/services/events/utils.d.ts +2 -14
  26. package/dist/slack/services/events/utils.js +2 -13
  27. package/dist/slack/services/index.d.ts +7 -5
  28. package/dist/slack/services/index.js +8 -6
  29. package/dist/slack/services/link-prompt.d.ts +2 -2
  30. package/dist/slack/services/modals.d.ts +1 -18
  31. package/dist/slack/services/modals.js +1 -48
  32. package/dist/slack/services/resume-intent.js +43 -3
  33. package/dist/slack/socket-mode.js +1 -1
  34. package/dist/slack/tracer.d.ts +2 -4
  35. package/dist/slack/tracer.js +1 -3
  36. package/package.json +2 -2
package/dist/env.d.ts CHANGED
@@ -14,11 +14,11 @@ declare const envSchema: z.ZodObject<{
14
14
  pentest: "pentest";
15
15
  }>>;
16
16
  LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
17
- error: "error";
18
17
  trace: "trace";
19
18
  debug: "debug";
20
19
  info: "info";
21
20
  warn: "warn";
21
+ error: "error";
22
22
  }>>;
23
23
  INKEEP_AGENTS_RUN_DATABASE_URL: z.ZodOptional<z.ZodString>;
24
24
  INKEEP_AGENTS_MANAGE_UI_URL: z.ZodOptional<z.ZodString>;
@@ -44,7 +44,7 @@ declare const envSchema: z.ZodObject<{
44
44
  declare const env: {
45
45
  NODE_ENV: "development" | "production" | "test";
46
46
  ENVIRONMENT: "development" | "production" | "test" | "pentest";
47
- LOG_LEVEL: "error" | "trace" | "debug" | "info" | "warn";
47
+ LOG_LEVEL: "trace" | "debug" | "info" | "warn" | "error";
48
48
  INKEEP_AGENTS_RUN_DATABASE_URL?: string | undefined;
49
49
  INKEEP_AGENTS_MANAGE_UI_URL?: string | undefined;
50
50
  GITHUB_APP_ID?: string | undefined;
@@ -1,11 +1,11 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types7 from "hono/types";
2
+ import * as hono_types3 from "hono/types";
3
3
 
4
4
  //#region src/github/mcp/index.d.ts
5
5
  declare const app: Hono<{
6
6
  Variables: {
7
7
  toolId: string;
8
8
  };
9
- }, hono_types7.BlankSchema, "/">;
9
+ }, hono_types3.BlankSchema, "/">;
10
10
  //#endregion
11
11
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types5 from "hono/types";
2
+ import * as hono_types6 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/setup.d.ts
5
- declare const app: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types6.BlankEnv, hono_types6.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types3 from "hono/types";
2
+ import * as hono_types4 from "hono/types";
3
3
 
4
4
  //#region src/github/routes/webhooks.d.ts
5
5
  interface WebhookVerificationResult {
@@ -7,6 +7,6 @@ interface WebhookVerificationResult {
7
7
  error?: string;
8
8
  }
9
9
  declare function verifyWebhookSignature(payload: string, signature: string | undefined, secret: string): WebhookVerificationResult;
10
- declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
10
+ declare const app: Hono<hono_types4.BlankEnv, hono_types4.BlankSchema, "/">;
11
11
  //#endregion
12
12
  export { WebhookVerificationResult, app as default, verifyWebhookSignature };
@@ -4,18 +4,44 @@ import { sendResponseUrlMessage } from "./services/events/utils.js";
4
4
  import { getSlackClient } from "./services/client.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, handleToolApproval } from "./services/events/block-actions.js";
8
- import { handleFollowUpSubmission, handleModalSubmission } from "./services/events/modal-submission.js";
7
+ import { handleMessageShortcut, handleOpenAgentSelectorModal, handleToolApproval } from "./services/events/block-actions.js";
8
+ import { handleDirectMessage } from "./services/events/direct-message.js";
9
+ import { handleModalSubmission } from "./services/events/modal-submission.js";
9
10
  import "./services/events/index.js";
10
11
  import "./services/index.js";
11
12
  import { flushTraces } from "@inkeep/agents-core";
12
13
 
13
14
  //#region src/slack/dispatcher.ts
14
15
  const logger = getLogger("slack-dispatcher");
16
+ /**
17
+ * Simple in-memory deduplication for Slack events.
18
+ * Slack may deliver the same event more than once (Socket Mode reconnections,
19
+ * edge cases in the Events API). We track recent event IDs to prevent
20
+ * duplicate processing. Entries expire after 5 minutes.
21
+ */
22
+ const DEDUP_TTL_MS = 300 * 1e3;
23
+ const recentEventIds = /* @__PURE__ */ new Map();
24
+ function isDuplicateEvent(eventId) {
25
+ if (!eventId) return false;
26
+ const now = Date.now();
27
+ if (recentEventIds.has(eventId)) return true;
28
+ recentEventIds.set(eventId, now);
29
+ if (recentEventIds.size > 500) {
30
+ for (const [id, ts] of recentEventIds) if (now - ts > DEDUP_TTL_MS) recentEventIds.delete(id);
31
+ }
32
+ return false;
33
+ }
15
34
  async function dispatchSlackEvent(eventType, payload, options, span) {
16
35
  const { registerBackgroundWork } = options;
17
36
  let outcome = "ignored_unknown_event";
18
37
  if (eventType === "event_callback") {
38
+ const eventId = payload.event_id;
39
+ if (isDuplicateEvent(eventId)) {
40
+ outcome = "ignored_duplicate_event";
41
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
42
+ logger.info({ eventId }, "Ignoring duplicate event");
43
+ return { outcome };
44
+ }
19
45
  const teamId = payload.team_id;
20
46
  const event = payload.event;
21
47
  const innerEventType = event?.type || "unknown";
@@ -44,7 +70,7 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
44
70
  }, "Ignoring edited message");
45
71
  return { outcome };
46
72
  }
47
- if (event?.type === "app_mention" && event.channel && event.user && teamId) {
73
+ if (event?.type === "app_mention" && event.channel_type !== "im" && event.channel && event.user && teamId) {
48
74
  outcome = "handled";
49
75
  span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
50
76
  const question = (event.text || "").replace(/<@[A-Z0-9]+>/g, "").trim();
@@ -80,6 +106,31 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
80
106
  channel: event.channel,
81
107
  dispatchedAt
82
108
  }, "app_mention work registered");
109
+ } else if (event?.type === "message" && event.channel_type === "im" && event.channel && event.user && teamId) {
110
+ outcome = "handled";
111
+ span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
112
+ if (event.thread_ts) span.setAttribute(SLACK_SPAN_KEYS.THREAD_TS, event.thread_ts);
113
+ if (event.ts) span.setAttribute(SLACK_SPAN_KEYS.MESSAGE_TS, event.ts);
114
+ logger.info({
115
+ userId: event.user,
116
+ channel: event.channel,
117
+ teamId
118
+ }, "Handling event: message.im");
119
+ registerBackgroundWork(handleDirectMessage({
120
+ slackUserId: event.user,
121
+ channel: event.channel,
122
+ text: event.text || "",
123
+ threadTs: event.thread_ts,
124
+ messageTs: event.ts || "",
125
+ teamId
126
+ }).catch((err) => {
127
+ const errorMessage = err instanceof Error ? err.message : String(err);
128
+ const errorStack = err instanceof Error ? err.stack : void 0;
129
+ logger.error({
130
+ errorMessage,
131
+ errorStack
132
+ }, "Failed to handle direct message (outer catch)");
133
+ }).finally(() => flushTraces()));
83
134
  } else {
84
135
  outcome = "ignored_unknown_event";
85
136
  span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
@@ -209,29 +260,6 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
209
260
  }, "Failed to handle tool approval");
210
261
  }).finally(() => flushTraces()));
211
262
  }
212
- if (action.action_id === "open_follow_up_modal" && action.value && triggerId) {
213
- anyHandled = true;
214
- logger.info({
215
- teamId,
216
- actionId: action.action_id
217
- }, "Handling block_action: open_follow_up_modal");
218
- registerBackgroundWork(handleOpenFollowUpModal({
219
- triggerId,
220
- actionValue: action.value,
221
- teamId,
222
- responseUrl: responseUrl || void 0
223
- }).catch(async (err) => {
224
- const errorMessage = err instanceof Error ? err.message : String(err);
225
- logger.error({
226
- errorMessage,
227
- actionId: action.action_id
228
- }, "Failed to open follow-up modal");
229
- if (responseUrl) await sendResponseUrlMessage(responseUrl, {
230
- text: "Sorry, something went wrong while opening the follow-up form. Please try again.",
231
- response_type: "ephemeral"
232
- }).catch((e) => logger.warn({ error: e }, "Failed to send error notification via response URL"));
233
- }).finally(() => flushTraces()));
234
- }
235
263
  }
236
264
  outcome = anyHandled ? "handled" : "ignored_no_action_match";
237
265
  span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
@@ -338,20 +366,6 @@ async function dispatchSlackEvent(eventType, payload, options, span) {
338
366
  }).finally(() => flushTraces()));
339
367
  return { outcome };
340
368
  }
341
- if (callbackId === "follow_up_modal") {
342
- const view = payload.view;
343
- outcome = "handled";
344
- span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
345
- logger.info({ callbackId }, "Handling view_submission: follow_up_modal");
346
- registerBackgroundWork(handleFollowUpSubmission(view).catch((err) => {
347
- const errorMessage = err instanceof Error ? err.message : String(err);
348
- logger.error({
349
- errorMessage,
350
- callbackId
351
- }, "Failed to handle follow-up submission");
352
- }).finally(() => flushTraces()));
353
- return { outcome };
354
- }
355
369
  outcome = "ignored_unknown_event";
356
370
  span.setAttribute(SLACK_SPAN_KEYS.OUTCOME, outcome);
357
371
  logger.info({ callbackId }, `Ignoring unhandled view_submission: ${callbackId}`);
@@ -9,7 +9,6 @@ declare const SlackStrings: {
9
9
  readonly buttons: {
10
10
  readonly triggerAgent: "Trigger Agent";
11
11
  readonly send: "Send";
12
- readonly followUp: "Follow Up";
13
12
  readonly cancel: "Cancel";
14
13
  readonly openDashboard: "Open Dashboard";
15
14
  };
@@ -17,7 +16,6 @@ declare const SlackStrings: {
17
16
  readonly triggerAgent: "Trigger Agent";
18
17
  readonly triggerAgentThread: "Trigger Agent (Thread)";
19
18
  readonly askAboutMessage: "Ask About Message";
20
- readonly followUp: "Follow Up";
21
19
  };
22
20
  readonly labels: {
23
21
  readonly project: "Project";
@@ -38,7 +36,6 @@ declare const SlackStrings: {
38
36
  };
39
37
  readonly context: {
40
38
  readonly poweredBy: (agentName: string) => string;
41
- readonly privateResponse: "_Private response_";
42
39
  };
43
40
  readonly usage: {
44
41
  readonly mentionEmpty: string;
@@ -50,13 +47,17 @@ declare const SlackStrings: {
50
47
  readonly noProjectsConfigured: "No projects configured. Set up projects in the dashboard.";
51
48
  };
52
49
  readonly errors: {
53
- readonly generic: "Something went wrong. Please try again.";
50
+ readonly generic: "Something went wrong processing your request. Please try again.";
54
51
  readonly failedToOpenSelector: "Failed to open agent selector. Please try again.";
52
+ readonly noAgentConfigured: "No agent is configured for this workspace. Ask your admin to set up a default agent in the Inkeep dashboard.";
53
+ };
54
+ readonly linkPrompt: {
55
+ readonly intro: "To get started, let's connect your Inkeep account with Slack.";
55
56
  };
56
57
  readonly help: {
57
58
  readonly title: "Inkeep — How to Use";
58
59
  readonly publicSection: string;
59
- readonly privateSection: string;
60
+ readonly slashSection: string;
60
61
  readonly otherCommands: string;
61
62
  readonly docsLink: "<https://docs.inkeep.com/talk-to-your-agents/slack/overview|Learn more>";
62
63
  };
@@ -9,15 +9,13 @@ const SlackStrings = {
9
9
  buttons: {
10
10
  triggerAgent: "Trigger Agent",
11
11
  send: "Send",
12
- followUp: "Follow Up",
13
12
  cancel: "Cancel",
14
13
  openDashboard: "Open Dashboard"
15
14
  },
16
15
  modals: {
17
16
  triggerAgent: "Trigger Agent",
18
17
  triggerAgentThread: "Trigger Agent (Thread)",
19
- askAboutMessage: "Ask About Message",
20
- followUp: "Follow Up"
18
+ askAboutMessage: "Ask About Message"
21
19
  },
22
20
  labels: {
23
21
  project: "Project",
@@ -34,10 +32,7 @@ const SlackStrings = {
34
32
  additionalInstructionsMessage: "Additional instructions or question about this message..."
35
33
  },
36
34
  visibility: { includeThreadContext: "Include thread context" },
37
- context: {
38
- poweredBy: (agentName) => `Powered by *${agentName}* via Inkeep`,
39
- privateResponse: "_Private response_"
40
- },
35
+ context: { poweredBy: (agentName) => `Powered by *${agentName}* via Inkeep` },
41
36
  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
37
  status: {
43
38
  thinking: (agentName) => `_${agentName} is thinking..._`,
@@ -46,13 +41,15 @@ const SlackStrings = {
46
41
  noProjectsConfigured: "No projects configured. Set up projects in the dashboard."
47
42
  },
48
43
  errors: {
49
- generic: "Something went wrong. Please try again.",
50
- failedToOpenSelector: "Failed to open agent selector. Please try again."
44
+ generic: "Something went wrong processing your request. Please try again.",
45
+ failedToOpenSelector: "Failed to open agent selector. Please try again.",
46
+ noAgentConfigured: "No agent is configured for this workspace. Ask your admin to set up a default agent in the Inkeep dashboard."
51
47
  },
48
+ linkPrompt: { intro: "To get started, let's connect your Inkeep account with Slack." },
52
49
  help: {
53
50
  title: "Inkeep — How to Use",
54
51
  publicSection: "*Public* — visible to everyone in the channel\n\n• `@Inkeep <message>` — Message the default agent in this channel\n• `@Inkeep <message>` in a thread — Includes thread as context\n• `@Inkeep` in a thread — Uses the full thread as context",
55
- privateSection: "*Private* — only visible to you\n\n• `/inkeep <message>` — Message the default agent in this channel\n• `/inkeep` — Open the agent picker to choose an agent and write a prompt",
52
+ slashSection: "*Slash Commands* — visible to everyone in the channel\n\n• `/inkeep <message>` — Message the default agent in this channel\n• `/inkeep` — Open the agent picker to choose an agent and write a prompt",
56
53
  otherCommands: "*Other Commands*\n\n• `/inkeep status` — Check your connection and agent config\n• `/inkeep link` / `/inkeep unlink` — Manage account connection\n• `/inkeep help` — Show this message",
57
54
  docsLink: "<https://docs.inkeep.com/talk-to-your-agents/slack/overview|Learn more>"
58
55
  },
@@ -3,8 +3,8 @@ import { getLogger } from "../../logger.js";
3
3
  import runDbClient_default from "../../db/runDbClient.js";
4
4
  import { findWorkspaceConnectionByTeamId, getSlackIntegrationId, getSlackNango, updateConnectionMetadata } from "../services/nango.js";
5
5
  import { getSlackClient, getSlackUserInfo } from "../services/client.js";
6
- import { handleCommand } from "../services/commands/index.js";
7
6
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, tracer } from "../tracer.js";
7
+ import { handleCommand } from "../services/commands/index.js";
8
8
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "../services/security.js";
9
9
  import "../services/index.js";
10
10
  import { dispatchSlackEvent } from "../dispatcher.js";
@@ -5,7 +5,6 @@ import * as slack_block_builder0 from "slack-block-builder";
5
5
  declare function createErrorMessage(message: string): Readonly<slack_block_builder0.SlackMessageDto>;
6
6
  interface ContextBlockParams {
7
7
  agentName: string;
8
- isPrivate?: boolean;
9
8
  }
10
9
  declare function createContextBlock(params: ContextBlockParams): {
11
10
  type: "context";
@@ -14,37 +13,6 @@ declare function createContextBlock(params: ContextBlockParams): {
14
13
  text: string;
15
14
  }[];
16
15
  };
17
- interface FollowUpButtonParams {
18
- conversationId: string;
19
- agentId: string;
20
- agentName?: string;
21
- projectId: string;
22
- tenantId: string;
23
- teamId: string;
24
- slackUserId: string;
25
- channel: string;
26
- }
27
- declare function buildFollowUpButton(params: FollowUpButtonParams): {
28
- type: "button";
29
- text: {
30
- type: "plain_text";
31
- text: "Follow Up";
32
- emoji: boolean;
33
- };
34
- action_id: string;
35
- value: string;
36
- }[];
37
- /**
38
- * Build Block Kit blocks for a private conversational response.
39
- * Shows the user's message, a divider, the agent response, context, and a Follow Up button.
40
- */
41
- declare function buildConversationResponseBlocks(params: {
42
- userMessage: string;
43
- responseText: string;
44
- agentName: string;
45
- isError: boolean;
46
- followUpParams: FollowUpButtonParams;
47
- }): any[];
48
16
  declare function createUpdatedHelpMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
49
17
  declare function createAlreadyLinkedMessage(email: string, linkedAt: string, dashboardUrl: string): Readonly<slack_block_builder0.SlackMessageDto>;
50
18
  declare function createUnlinkSuccessMessage(): Readonly<slack_block_builder0.SlackMessageDto>;
@@ -79,7 +47,7 @@ interface ToolApprovalButtonValue {
79
47
  agentId: string;
80
48
  slackUserId: string;
81
49
  channel: string;
82
- threadTs: string;
50
+ threadTs?: string;
83
51
  toolName: string;
84
52
  }
85
53
  declare const ToolApprovalButtonValueSchema: z.ZodObject<{
@@ -89,7 +57,7 @@ declare const ToolApprovalButtonValueSchema: z.ZodObject<{
89
57
  agentId: z.ZodString;
90
58
  slackUserId: z.ZodString;
91
59
  channel: z.ZodString;
92
- threadTs: z.ZodString;
60
+ threadTs: z.ZodOptional<z.ZodString>;
93
61
  toolName: z.ZodString;
94
62
  }, z.core.$strip>;
95
63
  declare function buildToolApprovalBlocks(params: {
@@ -152,4 +120,4 @@ declare function buildCitationsBlock(citations: Array<{
152
120
  }>): any[];
153
121
  declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
154
122
  //#endregion
155
- export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
123
+ export { AgentConfigSources, ContextBlockParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -7,54 +7,17 @@ function createErrorMessage(message) {
7
7
  return Message().blocks(Blocks.Section().text(message)).buildToObject();
8
8
  }
9
9
  function createContextBlock(params) {
10
- const { agentName, isPrivate = false } = params;
11
- let text = SlackStrings.context.poweredBy(agentName);
12
- if (isPrivate) text = `${SlackStrings.context.privateResponse} • ${text}`;
10
+ const { agentName } = params;
13
11
  return {
14
12
  type: "context",
15
13
  elements: [{
16
14
  type: "mrkdwn",
17
- text
15
+ text: SlackStrings.context.poweredBy(agentName)
18
16
  }]
19
17
  };
20
18
  }
21
- function buildFollowUpButton(params) {
22
- return [{
23
- type: "button",
24
- text: {
25
- type: "plain_text",
26
- text: SlackStrings.buttons.followUp,
27
- emoji: true
28
- },
29
- action_id: "open_follow_up_modal",
30
- value: JSON.stringify(params)
31
- }];
32
- }
33
- /**
34
- * Build Block Kit blocks for a private conversational response.
35
- * Shows the user's message, a divider, the agent response, context, and a Follow Up button.
36
- */
37
- function buildConversationResponseBlocks(params) {
38
- const { responseText, agentName, isError, followUpParams } = params;
39
- const blocks = [{
40
- type: "section",
41
- text: {
42
- type: "mrkdwn",
43
- text: responseText
44
- }
45
- }];
46
- if (!isError) {
47
- const contextBlock = createContextBlock({ agentName });
48
- blocks.push(contextBlock);
49
- blocks.push({
50
- type: "actions",
51
- elements: buildFollowUpButton(followUpParams)
52
- });
53
- }
54
- return blocks;
55
- }
56
19
  function createUpdatedHelpMessage() {
57
- 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();
20
+ return Message().blocks(Blocks.Header().text(SlackStrings.help.title), Blocks.Section().text(SlackStrings.help.publicSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.slashSection), Blocks.Divider(), Blocks.Section().text(SlackStrings.help.otherCommands), Blocks.Divider(), Blocks.Context().elements(SlackStrings.help.docsLink)).buildToObject();
58
21
  }
59
22
  function createAlreadyLinkedMessage(email, linkedAt, dashboardUrl) {
60
23
  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();
@@ -101,7 +64,7 @@ const ToolApprovalButtonValueSchema = z.object({
101
64
  agentId: z.string(),
102
65
  slackUserId: z.string(),
103
66
  channel: z.string(),
104
- threadTs: z.string(),
67
+ threadTs: z.string().optional(),
105
68
  toolName: z.string()
106
69
  });
107
70
  function buildToolApprovalBlocks(params) {
@@ -324,4 +287,4 @@ function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
324
287
  }
325
288
 
326
289
  //#endregion
327
- export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
290
+ export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -2,14 +2,15 @@ import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import runDbClient_default from "../../../db/runDbClient.js";
4
4
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
5
- import { extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
5
+ import { fetchAgentsForProject, fetchProjectsForTenant, generateSlackConversationId, getChannelAgentConfig } from "../events/utils.js";
6
6
  import { resolveEffectiveAgent } from "../agent-resolution.js";
7
7
  import { SlackStrings } from "../../i18n/strings.js";
8
- import { createAlreadyLinkedMessage, createContextBlock, createErrorMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
8
+ import { createAlreadyLinkedMessage, createErrorMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
9
9
  import { getSlackClient } from "../client.js";
10
+ import { executeAgentPublicly } from "../events/execution.js";
10
11
  import { buildLinkPromptMessage, resolveUnlinkedUserAction } from "../link-prompt.js";
11
12
  import { buildAgentSelectorModal } from "../modals.js";
12
- import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getInProcessFetch, getWaitUntil, signSlackUserToken } from "@inkeep/agents-core";
13
+ import { deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, flushTraces, getWaitUntil, signSlackUserToken } from "@inkeep/agents-core";
13
14
 
14
15
  //#region src/slack/services/commands/index.ts
15
16
  const DEFAULT_CLIENT_ID = "work-apps-slack";
@@ -232,117 +233,54 @@ async function handleQuestionCommand(payload, question, _dashboardUrl, tenantId,
232
233
  response_type: "ephemeral",
233
234
  ...createErrorMessage("No default agent configured. Ask your admin to set a workspace default in the dashboard.")
234
235
  };
235
- const questionWork = executeAgentInBackground(payload, existingLink, {
236
- id: resolvedAgent.agentId,
237
- name: resolvedAgent.agentName || null,
238
- projectId: resolvedAgent.projectId
239
- }, question, userTenantId, {
236
+ const slackClient = getSlackClient(botToken);
237
+ const slackUserToken = await signSlackUserToken({
238
+ inkeepUserId: existingLink.inkeepUserId,
239
+ tenantId: userTenantId,
240
+ slackTeamId: payload.teamId,
241
+ slackUserId: payload.userId,
242
+ slackEnterpriseId: payload.enterpriseId,
240
243
  slackAuthorized: resolvedAgent.grantAccessToMembers,
241
244
  slackAuthSource: resolvedAgent.source === "none" ? void 0 : resolvedAgent.source,
242
245
  slackChannelId: payload.channelId,
243
246
  slackAuthorizedProjectId: resolvedAgent.projectId
244
- }).catch((error) => {
247
+ });
248
+ const now = Date.now();
249
+ const messageTs = `${Math.floor(now / 1e3)}.${String(now % 1e3).padStart(3, "0")}000`;
250
+ const conversationId = generateSlackConversationId({
251
+ teamId: payload.teamId,
252
+ messageTs,
253
+ agentId: resolvedAgent.agentId
254
+ });
255
+ const questionWork = executeAgentPublicly({
256
+ slackClient,
257
+ channel: payload.channelId,
258
+ slackUserId: payload.userId,
259
+ teamId: payload.teamId,
260
+ jwtToken: slackUserToken,
261
+ projectId: resolvedAgent.projectId,
262
+ agentId: resolvedAgent.agentId,
263
+ agentName: resolvedAgent.agentName || resolvedAgent.agentId,
264
+ question,
265
+ conversationId
266
+ }).catch(async (error) => {
245
267
  logger.error({ error }, "Background execution promise rejected");
246
- }).finally(() => flushTraces());
247
- const waitUntil = await getWaitUntil();
248
- if (waitUntil) waitUntil(questionWork);
249
- return {};
250
- }
251
- async function executeAgentInBackground(payload, existingLink, targetAgent, question, tenantId, channelAuth) {
252
- try {
253
- const slackUserToken = await signSlackUserToken({
254
- inkeepUserId: existingLink.inkeepUserId,
255
- tenantId,
256
- slackTeamId: payload.teamId,
257
- slackUserId: payload.userId,
258
- slackEnterpriseId: payload.enterpriseId,
259
- ...channelAuth
260
- });
261
- const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
262
- const controller = new AbortController();
263
- const timeout = setTimeout(() => controller.abort(), 3e4);
264
- let response;
265
- try {
266
- response = await getInProcessFetch()(`${apiBaseUrl}/run/api/chat`, {
268
+ if (payload.responseUrl) try {
269
+ await fetch(payload.responseUrl, {
267
270
  method: "POST",
268
- headers: {
269
- "Content-Type": "application/json",
270
- Authorization: `Bearer ${slackUserToken}`,
271
- "x-inkeep-project-id": targetAgent.projectId,
272
- "x-inkeep-agent-id": targetAgent.id
273
- },
271
+ headers: { "Content-Type": "application/json" },
274
272
  body: JSON.stringify({
275
- messages: [{
276
- role: "user",
277
- content: question
278
- }],
279
- stream: false
280
- }),
281
- signal: controller.signal
282
- });
283
- } catch (error) {
284
- clearTimeout(timeout);
285
- if (error.name === "AbortError") {
286
- logger.warn({
287
- teamId: payload.teamId,
288
- timeoutMs: 3e4
289
- }, "Background agent execution timed out");
290
- await sendResponseUrlMessage(payload.responseUrl, {
291
273
  response_type: "ephemeral",
292
- text: "Request timed out. Please try again."
293
- });
294
- return;
295
- }
296
- throw error;
297
- } finally {
298
- clearTimeout(timeout);
299
- }
300
- if (!response.ok) {
301
- const errorText = await response.text();
302
- logger.error({
303
- status: response.status,
304
- error: errorText,
305
- agentId: targetAgent.id,
306
- projectId: targetAgent.projectId
307
- }, "Run API call failed");
308
- const apiMessage = extractApiErrorMessage(errorText);
309
- const errorMessage = apiMessage ? `*Error.* ${apiMessage}` : `Failed to run agent: ${response.status} ${response.statusText}`;
310
- await sendResponseUrlMessage(payload.responseUrl, {
311
- response_type: "ephemeral",
312
- text: errorMessage
313
- });
314
- } else {
315
- const result = await response.json();
316
- const assistantMessage = result.choices?.[0]?.message?.content || result.message?.content || "No response received";
317
- logger.info({
318
- slackUserId: payload.userId,
319
- agentId: targetAgent.id,
320
- projectId: targetAgent.projectId,
321
- tenantId
322
- }, "Agent execution completed via Slack");
323
- const contextBlock = createContextBlock({ agentName: targetAgent.name || targetAgent.id });
324
- await sendResponseUrlMessage(payload.responseUrl, {
325
- response_type: "in_channel",
326
- text: assistantMessage,
327
- blocks: [{
328
- type: "section",
329
- text: {
330
- type: "mrkdwn",
331
- text: assistantMessage
332
- }
333
- }, contextBlock]
274
+ text: SlackStrings.errors.generic
275
+ })
334
276
  });
277
+ } catch (e) {
278
+ logger.warn({ e }, "Failed to send error via response_url");
335
279
  }
336
- } catch (error) {
337
- logger.error({
338
- error,
339
- slackUserId: payload.userId
340
- }, "Background agent execution failed");
341
- await sendResponseUrlMessage(payload.responseUrl, {
342
- response_type: "ephemeral",
343
- text: "An error occurred while running the agent. Please try again."
344
- });
345
- }
280
+ }).finally(() => flushTraces());
281
+ const waitUntil = await getWaitUntil();
282
+ if (waitUntil) waitUntil(questionWork);
283
+ return {};
346
284
  }
347
285
  async function handleCommand(payload) {
348
286
  const text = payload.text.trim();