@inkeep/agents-work-apps 0.50.1 → 0.50.4

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 (64) hide show
  1. package/dist/_virtual/rolldown_runtime.js +32 -0
  2. package/dist/env.d.ts +4 -2
  3. package/dist/env.js +2 -1
  4. package/dist/github/mcp/index.d.ts +2 -2
  5. package/dist/github/routes/setup.d.ts +2 -2
  6. package/dist/github/routes/tokenExchange.d.ts +2 -2
  7. package/dist/github/routes/webhooks.d.ts +2 -2
  8. package/dist/node_modules/.pnpm/@slack_logger@4.0.0/node_modules/@slack/logger/dist/index.js +89 -0
  9. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/package.js +85 -0
  10. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/SlackWebSocket.js +223 -0
  11. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/SocketModeClient.js +367 -0
  12. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/UnrecoverableSocketModeStartError.js +20 -0
  13. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/errors.js +71 -0
  14. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/index.js +44 -0
  15. package/dist/node_modules/.pnpm/@slack_socket-mode@2.0.5/node_modules/@slack/socket-mode/dist/src/logger.js +32 -0
  16. package/dist/node_modules/.pnpm/eventemitter3@5.0.1/node_modules/eventemitter3/index.js +241 -0
  17. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/index.js +23 -0
  18. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js +107 -0
  19. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js +29 -0
  20. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js +226 -0
  21. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js +150 -0
  22. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js +57 -0
  23. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js +342 -0
  24. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js +457 -0
  25. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js +505 -0
  26. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js +123 -0
  27. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js +46 -0
  28. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js +203 -0
  29. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js +385 -0
  30. package/dist/node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js +985 -0
  31. package/dist/slack/dispatcher.d.ts +16 -0
  32. package/dist/slack/dispatcher.js +335 -0
  33. package/dist/slack/i18n/strings.d.ts +5 -5
  34. package/dist/slack/i18n/strings.js +9 -9
  35. package/dist/slack/index.d.ts +3 -1
  36. package/dist/slack/index.js +4 -2
  37. package/dist/slack/middleware/permissions.js +120 -107
  38. package/dist/slack/routes/events.js +10 -328
  39. package/dist/slack/routes/oauth.js +6 -3
  40. package/dist/slack/routes/users.js +12 -6
  41. package/dist/slack/routes/workspaces.js +39 -39
  42. package/dist/slack/services/agent-resolution.d.ts +1 -0
  43. package/dist/slack/services/agent-resolution.js +8 -4
  44. package/dist/slack/services/blocks/index.js +7 -11
  45. package/dist/slack/services/commands/index.js +15 -7
  46. package/dist/slack/services/dev-config.d.ts +23 -0
  47. package/dist/slack/services/dev-config.js +91 -0
  48. package/dist/slack/services/events/app-mention.js +25 -21
  49. package/dist/slack/services/events/index.d.ts +2 -2
  50. package/dist/slack/services/events/index.js +2 -2
  51. package/dist/slack/services/events/modal-submission.js +18 -10
  52. package/dist/slack/services/events/streaming.js +7 -5
  53. package/dist/slack/services/events/utils.d.ts +2 -1
  54. package/dist/slack/services/events/utils.js +16 -9
  55. package/dist/slack/services/index.d.ts +2 -2
  56. package/dist/slack/services/index.js +3 -3
  57. package/dist/slack/services/modals.js +4 -4
  58. package/dist/slack/services/nango.d.ts +3 -0
  59. package/dist/slack/services/nango.js +84 -2
  60. package/dist/slack/socket-mode.d.ts +4 -0
  61. package/dist/slack/socket-mode.js +130 -0
  62. package/dist/slack/tracer.d.ts +2 -0
  63. package/dist/slack/tracer.js +3 -1
  64. package/package.json +3 -2
@@ -1,11 +1,11 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
+ import { resolveEffectiveAgent } from "../agent-resolution.js";
4
5
  import { SlackStrings } from "../../i18n/strings.js";
5
6
  import { getSlackChannelInfo, getSlackClient, getSlackUserInfo, postMessageInThread } from "../client.js";
6
- import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, resolveChannelAgentConfig, timedOp } from "./utils.js";
7
+ import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
7
8
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
8
- import { getBotTokenForTeam } from "../workspace-tokens.js";
9
9
  import { streamAgentResponse } from "./streaming.js";
10
10
  import { signSlackUserToken } from "@inkeep/agents-core";
11
11
 
@@ -60,19 +60,18 @@ async function handleAppMention(params) {
60
60
  label: "workspace connection lookup",
61
61
  context: { teamId }
62
62
  });
63
- const botToken = workspaceConnection?.botToken || getBotTokenForTeam(teamId) || env.SLACK_BOT_TOKEN;
64
- if (!botToken) {
63
+ if (!workspaceConnection?.botToken) {
65
64
  logger.error({ teamId }, "No bot token available — cannot respond to @mention");
66
65
  span.end();
67
66
  return;
68
67
  }
69
- const tenantId = workspaceConnection?.tenantId;
68
+ const { botToken, tenantId } = workspaceConnection;
70
69
  if (!tenantId) {
71
70
  logger.error({ teamId }, "Workspace connection has no tenantId — workspace may need reinstall");
72
71
  await getSlackClient(botToken).chat.postEphemeral({
73
72
  channel,
74
73
  user: slackUserId,
75
- text: "⚠️ This workspace is not properly configured. Please reinstall the Slack app from the Inkeep dashboard."
74
+ text: "This workspace is not properly configured. Please reinstall the Slack app from the Inkeep dashboard."
76
75
  }).catch((e) => logger.warn({
77
76
  error: e,
78
77
  channel
@@ -88,7 +87,11 @@ async function handleAppMention(params) {
88
87
  const hasQuery = Boolean(text && text.trim().length > 0);
89
88
  let thinkingMessageTs;
90
89
  try {
91
- const { result: [agentConfig, existingLink] } = await timedOp(Promise.all([resolveChannelAgentConfig(teamId, channel, workspaceConnection), findCachedUserMapping(tenantId, slackUserId, teamId)]), {
90
+ const { result: [agentConfig, existingLink] } = await timedOp(Promise.all([resolveEffectiveAgent({
91
+ tenantId,
92
+ teamId,
93
+ channelId: channel
94
+ }), findCachedUserMapping(tenantId, slackUserId, teamId)]), {
92
95
  label: "agent config / user mapping lookup",
93
96
  context: {
94
97
  teamId,
@@ -104,13 +107,15 @@ async function handleAppMention(params) {
104
107
  channel,
105
108
  user: slackUserId,
106
109
  thread_ts: isInThread ? threadTs : void 0,
107
- text: `⚙️ No agents configured for this workspace.\n\n👉 *<${dashboardUrl}|Set up agents in the dashboard>*`
110
+ text: `No agents configured for this workspace. *<${dashboardUrl}|Set up agents in the dashboard>*`
108
111
  });
109
112
  span.end();
110
113
  return;
111
114
  }
112
115
  span.setAttribute(SLACK_SPAN_KEYS.AGENT_ID, agentConfig.agentId);
113
116
  span.setAttribute(SLACK_SPAN_KEYS.PROJECT_ID, agentConfig.projectId);
117
+ span.setAttribute(SLACK_SPAN_KEYS.AUTHORIZED, agentConfig.grantAccessToMembers);
118
+ span.setAttribute(SLACK_SPAN_KEYS.AUTH_SOURCE, agentConfig.source);
114
119
  const agentDisplayName = agentConfig.agentName || agentConfig.agentId;
115
120
  if (!existingLink) {
116
121
  logger.info({
@@ -122,11 +127,7 @@ async function handleAppMention(params) {
122
127
  channel,
123
128
  user: slackUserId,
124
129
  thread_ts: isInThread ? threadTs : void 0,
125
- text: `🔗 *Link your account to use @Inkeep*
126
-
127
- Run \`/inkeep link\` to connect your Slack and Inkeep accounts.
128
-
129
- This workspace uses: *${agentDisplayName}*`
130
+ text: "*Link your account to use @Inkeep*\n\nRun `/inkeep link` to connect your Slack and Inkeep accounts."
130
131
  });
131
132
  span.end();
132
133
  return;
@@ -162,12 +163,7 @@ This workspace uses: *${agentDisplayName}*`
162
163
  channel,
163
164
  user: slackUserId,
164
165
  thread_ts: threadTs,
165
- text: `💬 *Continue the conversation*
166
-
167
- Just type your follow-up — no need to mention me in this thread.
168
- Or use \`@Inkeep <prompt>\` to run a new prompt.
169
-
170
- _Using: ${agentDisplayName}_`
166
+ text: "*Continue the conversation*\n\nType your follow-up directly in this thread — no need to mention me.\nOr use `@Inkeep <prompt>` to start a new prompt."
171
167
  });
172
168
  span.end();
173
169
  return;
@@ -191,7 +187,11 @@ _Using: ${agentDisplayName}_`
191
187
  inkeepUserId: existingLink.inkeepUserId,
192
188
  tenantId,
193
189
  slackTeamId: teamId,
194
- slackUserId
190
+ slackUserId,
191
+ slackAuthorized: agentConfig?.grantAccessToMembers ?? false,
192
+ slackAuthSource: agentConfig?.source === "none" ? void 0 : agentConfig?.source,
193
+ slackChannelId: channel,
194
+ slackAuthorizedProjectId: agentConfig?.projectId
195
195
  });
196
196
  thinkingMessageTs = (await slackClient.chat.postMessage({
197
197
  channel,
@@ -265,7 +265,11 @@ Respond naturally as if you're joining the conversation to help.`;
265
265
  inkeepUserId: existingLink.inkeepUserId,
266
266
  tenantId,
267
267
  slackTeamId: teamId,
268
- slackUserId
268
+ slackUserId,
269
+ slackAuthorized: agentConfig?.grantAccessToMembers ?? false,
270
+ slackAuthSource: agentConfig?.source === "none" ? void 0 : agentConfig?.source,
271
+ slackChannelId: channel,
272
+ slackAuthorizedProjectId: agentConfig?.projectId
269
273
  });
270
274
  thinkingMessageTs = (await slackClient.chat.postMessage({
271
275
  channel,
@@ -1,6 +1,6 @@
1
1
  import { InlineSelectorMetadata, handleAppMention } from "./app-mention.js";
2
2
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./block-actions.js";
3
3
  import { handleFollowUpSubmission, handleModalSubmission } from "./modal-submission.js";
4
- import { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
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, 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, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
@@ -1,7 +1,7 @@
1
- import { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
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
4
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./block-actions.js";
5
5
  import { handleFollowUpSubmission, handleModalSubmission } from "./modal-submission.js";
6
6
 
7
- export { SlackErrorType, checkIfBotThread, classifyError, 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, markdownToMrkdwn, sendResponseUrlMessage, streamAgentResponse };
@@ -4,9 +4,9 @@ import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
4
  import { SlackStrings } from "../../i18n/strings.js";
5
5
  import { buildConversationResponseBlocks } from "../blocks/index.js";
6
6
  import { getSlackClient } from "../client.js";
7
- import { classifyError, findCachedUserMapping, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
7
+ import { classifyError, extractApiErrorMessage, findCachedUserMapping, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
8
8
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
9
- import { signSlackUserToken } from "@inkeep/agents-core";
9
+ import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
10
10
 
11
11
  //#region src/slack/services/events/modal-submission.ts
12
12
  /**
@@ -29,7 +29,7 @@ async function handleModalSubmission(view) {
29
29
  span.setAttribute(SLACK_SPAN_KEYS.USER_ID, metadata.slackUserId || "");
30
30
  span.setAttribute(SLACK_SPAN_KEYS.TENANT_ID, metadata.tenantId || "");
31
31
  const values = view.state?.values || {};
32
- const agentSelectValue = values.agent_select_block?.agent_select;
32
+ const agentSelectValue = Object.values(values).map((block) => block.agent_select).find(Boolean);
33
33
  const questionValue = values.question_block?.question_input;
34
34
  const includeContextValue = values.context_block?.include_context_checkbox;
35
35
  const question = questionValue?.value || "";
@@ -54,6 +54,7 @@ async function handleModalSubmission(view) {
54
54
  }
55
55
  span.setAttribute(SLACK_SPAN_KEYS.AGENT_ID, agentId);
56
56
  span.setAttribute(SLACK_SPAN_KEYS.PROJECT_ID, projectId);
57
+ span.setAttribute(SLACK_SPAN_KEYS.AUTHORIZED, false);
57
58
  const tenantId = metadata.tenantId;
58
59
  const [workspaceConnection, existingLink] = await Promise.all([findWorkspaceConnectionByTeamId(metadata.teamId), findCachedUserMapping(tenantId, metadata.slackUserId, metadata.teamId)]);
59
60
  if (!workspaceConnection?.botToken) {
@@ -90,7 +91,7 @@ async function handleModalSubmission(view) {
90
91
  await slackClient.chat.postEphemeral({
91
92
  channel: metadata.channel,
92
93
  user: metadata.slackUserId,
93
- text: "🔗 You need to link your account first. Use `/inkeep link` to get started."
94
+ text: "Link your account first. Run `/inkeep link` to connect."
94
95
  });
95
96
  span.end();
96
97
  return;
@@ -99,7 +100,8 @@ async function handleModalSubmission(view) {
99
100
  inkeepUserId: existingLink.inkeepUserId,
100
101
  tenantId,
101
102
  slackTeamId: metadata.teamId,
102
- slackUserId: metadata.slackUserId
103
+ slackUserId: metadata.slackUserId,
104
+ slackAuthorized: false
103
105
  });
104
106
  const conversationId = generateSlackConversationId({
105
107
  teamId: metadata.teamId,
@@ -200,6 +202,7 @@ async function handleFollowUpSubmission(view) {
200
202
  span.setAttribute(SLACK_SPAN_KEYS.AGENT_ID, agentId);
201
203
  span.setAttribute(SLACK_SPAN_KEYS.PROJECT_ID, projectId);
202
204
  span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, conversationId);
205
+ span.setAttribute(SLACK_SPAN_KEYS.AUTHORIZED, false);
203
206
  const [workspaceConnection, existingLink] = await Promise.all([findWorkspaceConnectionByTeamId(teamId), findCachedUserMapping(tenantId, slackUserId, teamId)]);
204
207
  if (!workspaceConnection?.botToken) {
205
208
  logger.error({ teamId }, "No bot token for follow-up submission");
@@ -215,7 +218,7 @@ async function handleFollowUpSubmission(view) {
215
218
  await slackClient.chat.postEphemeral({
216
219
  channel,
217
220
  user: slackUserId,
218
- text: "🔗 You need to link your account first. Use `/inkeep link` to get started."
221
+ text: "Link your account first. Run `/inkeep link` to connect."
219
222
  });
220
223
  span.end();
221
224
  return;
@@ -224,7 +227,8 @@ async function handleFollowUpSubmission(view) {
224
227
  inkeepUserId: existingLink.inkeepUserId,
225
228
  tenantId,
226
229
  slackTeamId: teamId,
227
- slackUserId
230
+ slackUserId,
231
+ slackAuthorized: false
228
232
  });
229
233
  const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
230
234
  await slackClient.chat.postEphemeral({
@@ -305,7 +309,7 @@ async function callAgentApi(params) {
305
309
  const timeout = setTimeout(() => controller.abort(), 3e4);
306
310
  let response;
307
311
  try {
308
- response = await fetch(`${apiBaseUrl}/run/api/chat`, {
312
+ response = await getInProcessFetch()(`${apiBaseUrl}/run/api/chat`, {
309
313
  method: "POST",
310
314
  headers: {
311
315
  "Content-Type": "application/json",
@@ -348,11 +352,15 @@ async function callAgentApi(params) {
348
352
  isError: false
349
353
  };
350
354
  }
351
- const errorText = getUserFriendlyErrorMessage(classifyError(null, response.status), agentId);
355
+ const errorBody = await response.text().catch(() => "");
356
+ const apiMessage = extractApiErrorMessage(errorBody);
357
+ const errorType = classifyError(null, response.status);
358
+ const errorText = apiMessage ? `*Error.* ${apiMessage}` : getUserFriendlyErrorMessage(errorType, agentId);
352
359
  logger.warn({
353
360
  status: response.status,
354
361
  statusText: response.statusText,
355
- agentId
362
+ agentId,
363
+ errorBody
356
364
  }, "Agent API returned error");
357
365
  apiSpan.end();
358
366
  return {
@@ -1,8 +1,9 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { createContextBlock } from "../blocks/index.js";
4
- import { SlackErrorType, classifyError, getUserFriendlyErrorMessage } from "./utils.js";
4
+ import { SlackErrorType, classifyError, extractApiErrorMessage, getUserFriendlyErrorMessage } from "./utils.js";
5
5
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
6
+ import { getInProcessFetch } from "@inkeep/agents-core";
6
7
 
7
8
  //#region src/slack/services/events/streaming.ts
8
9
  /**
@@ -12,8 +13,8 @@ import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../
12
13
  * Streams responses incrementally to Slack using chatStream API.
13
14
  */
14
15
  const logger = getLogger("slack-streaming");
15
- const STREAM_TIMEOUT_MS = 12e4;
16
- const CHATSTREAM_OP_TIMEOUT_MS = 1e4;
16
+ const STREAM_TIMEOUT_MS = 6e5;
17
+ const CHATSTREAM_OP_TIMEOUT_MS = 2e4;
17
18
  /** Shorter timeout for best-effort cleanup in error paths to bound total error handling time. */
18
19
  const CLEANUP_TIMEOUT_MS = 3e3;
19
20
  /**
@@ -59,7 +60,7 @@ async function streamAgentResponse(params) {
59
60
  }, STREAM_TIMEOUT_MS);
60
61
  let response;
61
62
  try {
62
- response = await fetch(`${apiUrl.replace(/\/$/, "")}/run/api/chat`, {
63
+ response = await getInProcessFetch()(`${apiUrl.replace(/\/$/, "")}/run/api/chat`, {
63
64
  method: "POST",
64
65
  headers: {
65
66
  "Content-Type": "application/json",
@@ -128,8 +129,9 @@ async function streamAgentResponse(params) {
128
129
  status: response.status,
129
130
  errorBody
130
131
  }, "Agent streaming request failed");
132
+ const apiMessage = extractApiErrorMessage(errorBody);
131
133
  const errorType = classifyError(null, response.status);
132
- const errorMessage = getUserFriendlyErrorMessage(errorType, agentName);
134
+ const errorMessage = apiMessage ? `*Error.* ${apiMessage}` : getUserFriendlyErrorMessage(errorType, agentName);
133
135
  await slackClient.chat.postMessage({
134
136
  channel,
135
137
  thread_ts: threadTs,
@@ -50,6 +50,7 @@ declare function classifyError(error: unknown, httpStatus?: number): SlackErrorT
50
50
  /**
51
51
  * Get a user-friendly error message based on error type
52
52
  */
53
+ declare function extractApiErrorMessage(responseBody: string): string | null;
53
54
  declare function getUserFriendlyErrorMessage(errorType: SlackErrorType, agentName?: string): string;
54
55
  type ProjectOption = {
55
56
  id: string;
@@ -157,4 +158,4 @@ declare function formatChannelContext(channelInfo: {
157
158
  name?: string;
158
159
  } | null): string;
159
160
  //#endregion
160
- export { ProjectOption, SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
161
+ export { ProjectOption, SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
@@ -2,7 +2,7 @@ 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 { InternalServices, findWorkAppSlackChannelAgentConfig, findWorkAppSlackUserMapping, generateInternalServiceToken } from "@inkeep/agents-core";
5
+ import { InternalServices, findWorkAppSlackChannelAgentConfig, findWorkAppSlackUserMapping, generateInternalServiceToken, getInProcessFetch } from "@inkeep/agents-core";
6
6
 
7
7
  //#region src/slack/services/events/utils.ts
8
8
  /**
@@ -94,14 +94,21 @@ function classifyError(error, httpStatus) {
94
94
  /**
95
95
  * Get a user-friendly error message based on error type
96
96
  */
97
+ function extractApiErrorMessage(responseBody) {
98
+ try {
99
+ const parsed = JSON.parse(responseBody);
100
+ if (typeof parsed.message === "string" && parsed.message.length > 0) return parsed.message;
101
+ } catch {}
102
+ return null;
103
+ }
97
104
  function getUserFriendlyErrorMessage(errorType, agentName) {
98
105
  const agent = agentName || "The agent";
99
106
  switch (errorType) {
100
- case SlackErrorType.TIMEOUT: return `⏱️ *Request timed out*\n\n${agent} took too long to respond. This can happen with complex queries.\n\n*Try:*\n• Simplifying your question\n• Breaking it into smaller parts\n• Trying again in a moment`;
101
- case SlackErrorType.RATE_LIMIT: return `⚠️ *Too many requests*\n\nYou've hit the rate limit. Please wait a moment before trying again.\n\n*Tip:* Space out your requests to avoid this.`;
102
- case SlackErrorType.AUTH_ERROR: return `🔐 *Authentication issue*\n\nThere was a problem with your account connection.\n\n*Try:*\n• Running \`/inkeep link\` to re-link your account\n• Contacting your workspace admin if the issue persists`;
103
- case SlackErrorType.API_ERROR: return `❌ *Something went wrong*\n\n${agent} encountered an error processing your request.\n\n*Try:*\n• Rephrasing your question\n• Trying again in a moment\n• Using \`/inkeep help\` for more options`;
104
- default: return `❌ *Unexpected error*\n\nSomething went wrong while processing your request.\n\n*Try:*\n• Trying again in a moment\n• Using \`/inkeep help\` for more options`;
107
+ case SlackErrorType.TIMEOUT: return `*Request timed out.* ${agent} took too long to respond. Try again with a simpler question.`;
108
+ case SlackErrorType.RATE_LIMIT: return "*Rate limited.* Wait a moment and try again.";
109
+ case SlackErrorType.AUTH_ERROR: return "*Authentication error.* Run `/inkeep link` to reconnect your account.";
110
+ case SlackErrorType.API_ERROR: return `*Something went wrong.* ${agent} encountered an error. Try again in a moment.`;
111
+ default: return "*Unexpected error.* Something went wrong. Try again in a moment.";
105
112
  }
106
113
  }
107
114
  const INTERNAL_FETCH_TIMEOUT_MS = 1e4;
@@ -114,7 +121,7 @@ async function fetchProjectsForTenant(tenantId) {
114
121
  const controller = new AbortController();
115
122
  const timeout = setTimeout(() => controller.abort(), INTERNAL_FETCH_TIMEOUT_MS);
116
123
  try {
117
- const response = await fetch(`${apiUrl}/manage/tenants/${tenantId}/projects?limit=50`, {
124
+ const response = await getInProcessFetch()(`${apiUrl}/manage/tenants/${tenantId}/projects?limit=50`, {
118
125
  method: "GET",
119
126
  headers: {
120
127
  Authorization: `Bearer ${token}`,
@@ -160,7 +167,7 @@ async function fetchAgentsForProject(tenantId, projectId) {
160
167
  const controller = new AbortController();
161
168
  const timeout = setTimeout(() => controller.abort(), INTERNAL_FETCH_TIMEOUT_MS);
162
169
  try {
163
- const response = await fetch(`${apiUrl}/manage/tenants/${tenantId}/projects/${projectId}/agents?limit=50`, {
170
+ const response = await getInProcessFetch()(`${apiUrl}/manage/tenants/${tenantId}/projects/${projectId}/agents?limit=50`, {
164
171
  method: "GET",
165
172
  headers: {
166
173
  Authorization: `Bearer ${token}`,
@@ -402,4 +409,4 @@ function formatChannelContext(channelInfo) {
402
409
  }
403
410
 
404
411
  //#endregion
405
- export { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
412
+ export { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, formatChannelContext, formatChannelLabel, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, resolveChannelAgentConfig, sendResponseUrlMessage, timedOp };
@@ -8,9 +8,9 @@ import { InlineSelectorMetadata, handleAppMention } from "./events/app-mention.j
8
8
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } 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
- import { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
11
+ import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
12
12
  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, 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, 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 };
@@ -2,15 +2,15 @@ import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConn
2
2
  import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
3
3
  import { buildConversationResponseBlocks, buildFollowUpButton, createAlreadyLinkedMessage, createContextBlock, 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
- import { SlackErrorType, checkIfBotThread, classifyError, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
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
- import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
9
8
  import { streamAgentResponse } from "./events/streaming.js";
10
9
  import { handleAppMention } from "./events/app-mention.js";
11
10
  import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal } from "./events/block-actions.js";
12
11
  import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
13
12
  import "./events/index.js";
14
13
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
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, 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, 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 };
@@ -66,7 +66,7 @@ function buildAgentSelectorModal(params) {
66
66
  }
67
67
  }, {
68
68
  type: "input",
69
- block_id: "agent_select_block",
69
+ block_id: selectedProjectId ? `agent_select_block_${selectedProjectId}` : "agent_select_block",
70
70
  element: {
71
71
  type: "static_select",
72
72
  action_id: "agent_select",
@@ -137,7 +137,7 @@ function buildAgentSelectorModal(params) {
137
137
  type: "context",
138
138
  elements: [{
139
139
  type: "mrkdwn",
140
- text: `⚙️ <${manageUiBaseUrl}${dashboardUrl}|Open Dashboard>`
140
+ text: `<${manageUiBaseUrl}${dashboardUrl}|Open Dashboard>`
141
141
  }]
142
142
  });
143
143
  return {
@@ -283,7 +283,7 @@ function buildMessageShortcutModal(params) {
283
283
  },
284
284
  {
285
285
  type: "input",
286
- block_id: "agent_select_block",
286
+ block_id: selectedProjectId ? `agent_select_block_${selectedProjectId}` : "agent_select_block",
287
287
  element: {
288
288
  type: "static_select",
289
289
  action_id: "agent_select",
@@ -325,7 +325,7 @@ function buildMessageShortcutModal(params) {
325
325
  type: "context",
326
326
  elements: [{
327
327
  type: "mrkdwn",
328
- text: `⚙️ <${manageUiBaseUrl}${dashboardUrl}|Open Dashboard>`
328
+ text: `<${manageUiBaseUrl}${dashboardUrl}|Open Dashboard>`
329
329
  }]
330
330
  });
331
331
  return {
@@ -18,6 +18,7 @@ interface DefaultAgentConfig {
18
18
  agentName?: string;
19
19
  projectId: string;
20
20
  projectName?: string;
21
+ grantAccessToMembers?: boolean;
21
22
  }
22
23
  interface SlackWorkspaceConnection {
23
24
  connectionId: string;
@@ -30,6 +31,8 @@ interface SlackWorkspaceConnection {
30
31
  /**
31
32
  * Find a workspace connection by Slack team ID.
32
33
  * Uses PostgreSQL first (O(1)) with in-memory caching, then falls back to Nango.
34
+ * In development mode with .slack-dev.json present, returns a local connection
35
+ * built from the dev config file instead of hitting Nango.
33
36
  *
34
37
  * Performance: This function is called on every @mention and command.
35
38
  * The PostgreSQL-first approach with caching provides O(1) lookups.
@@ -1,6 +1,7 @@
1
1
  import { env } from "../../env.js";
2
2
  import { getLogger } from "../../logger.js";
3
3
  import runDbClient_default from "../../db/runDbClient.js";
4
+ import { getDevDefaultAgent, isSlackDevMode, loadSlackDevConfig, saveSlackDevConfig } from "./dev-config.js";
4
5
  import { findWorkAppSlackWorkspaceBySlackTeamId } from "@inkeep/agents-core";
5
6
  import { Nango } from "@nangohq/node";
6
7
 
@@ -78,6 +79,10 @@ function getSlackIntegrationId() {
78
79
  return env.NANGO_SLACK_INTEGRATION_ID || "slack-agent";
79
80
  }
80
81
  async function createConnectSession(params) {
82
+ if (isSlackDevMode()) {
83
+ logger.debug({}, "Skipping Nango connect session in dev mode");
84
+ return null;
85
+ }
81
86
  try {
82
87
  const nango = getSlackNango();
83
88
  const integrationId = getSlackIntegrationId();
@@ -105,6 +110,7 @@ async function createConnectSession(params) {
105
110
  }
106
111
  }
107
112
  async function getConnectionAccessToken(connectionId) {
113
+ if (isSlackDevMode()) return loadSlackDevConfig()?.botToken ?? null;
108
114
  try {
109
115
  const nango = getSlackNango();
110
116
  const integrationId = getSlackIntegrationId();
@@ -117,9 +123,27 @@ async function getConnectionAccessToken(connectionId) {
117
123
  return null;
118
124
  }
119
125
  }
126
+ function buildDevWorkspaceConnection(devConfig, teamId) {
127
+ const connection = {
128
+ connectionId: `dev:${teamId}`,
129
+ teamId,
130
+ teamName: devConfig.teamName || "dev",
131
+ botToken: devConfig.botToken,
132
+ tenantId: "default",
133
+ defaultAgent: getDevDefaultAgent(devConfig) ?? void 0
134
+ };
135
+ evictWorkspaceCache();
136
+ workspaceConnectionCache.set(teamId, {
137
+ connection,
138
+ expiresAt: Date.now() + CACHE_TTL_MS
139
+ });
140
+ return connection;
141
+ }
120
142
  /**
121
143
  * Find a workspace connection by Slack team ID.
122
144
  * Uses PostgreSQL first (O(1)) with in-memory caching, then falls back to Nango.
145
+ * In development mode with .slack-dev.json present, returns a local connection
146
+ * built from the dev config file instead of hitting Nango.
123
147
  *
124
148
  * Performance: This function is called on every @mention and command.
125
149
  * The PostgreSQL-first approach with caching provides O(1) lookups.
@@ -130,6 +154,15 @@ async function findWorkspaceConnectionByTeamId(teamId) {
130
154
  logger.debug({ teamId }, "Workspace connection cache hit");
131
155
  return cached.connection;
132
156
  }
157
+ if (isSlackDevMode()) {
158
+ const devConfig = loadSlackDevConfig();
159
+ if (devConfig) {
160
+ logger.debug({ teamId }, "Using .slack-dev.json for workspace connection");
161
+ return buildDevWorkspaceConnection(devConfig, teamId);
162
+ }
163
+ logger.debug({ teamId }, "No .slack-dev.json found returning null");
164
+ return null;
165
+ }
133
166
  try {
134
167
  const dbWorkspace = await findWorkAppSlackWorkspaceBySlackTeamId(runDbClient_default)(teamId);
135
168
  if (dbWorkspace?.nangoConnectionId) {
@@ -155,7 +188,7 @@ async function findWorkspaceConnectionByTeamId(teamId) {
155
188
  }
156
189
  }
157
190
  logger.debug({ teamId }, "PostgreSQL lookup failed, falling back to Nango iteration");
158
- return findWorkspaceConnectionByTeamIdFromNango(teamId);
191
+ return await findWorkspaceConnectionByTeamIdFromNango(teamId);
159
192
  } catch (error) {
160
193
  logger.error({
161
194
  error,
@@ -165,6 +198,7 @@ async function findWorkspaceConnectionByTeamId(teamId) {
165
198
  }
166
199
  }
167
200
  async function getWorkspaceDefaultAgentFromNangoByConnectionId(connectionId) {
201
+ if (isSlackDevMode()) return getDevDefaultAgent(loadSlackDevConfig());
168
202
  try {
169
203
  const nango = getSlackNango();
170
204
  const integrationId = getSlackIntegrationId();
@@ -234,10 +268,23 @@ function clearWorkspaceConnectionCache(teamId) {
234
268
  else workspaceConnectionCache.clear();
235
269
  }
236
270
  async function updateConnectionMetadata(connectionId, metadata) {
271
+ if (isSlackDevMode()) {
272
+ const devConfig = loadSlackDevConfig();
273
+ if (!devConfig) return false;
274
+ devConfig.metadata = {
275
+ ...devConfig.metadata,
276
+ ...metadata
277
+ };
278
+ return saveSlackDevConfig(devConfig);
279
+ }
237
280
  try {
238
281
  const nango = getSlackNango();
239
282
  const integrationId = getSlackIntegrationId();
240
- await nango.updateMetadata(integrationId, connectionId, metadata);
283
+ const lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
284
+ await nango.updateMetadata(integrationId, connectionId, {
285
+ ...metadata,
286
+ last_updated_at: lastUpdatedAt
287
+ });
241
288
  return true;
242
289
  } catch (error) {
243
290
  logger.error({
@@ -248,6 +295,17 @@ async function updateConnectionMetadata(connectionId, metadata) {
248
295
  }
249
296
  }
250
297
  async function setWorkspaceDefaultAgent(teamId, defaultAgent) {
298
+ if (isSlackDevMode()) {
299
+ const devConfig = loadSlackDevConfig();
300
+ if (!devConfig) return false;
301
+ devConfig.metadata = {
302
+ ...devConfig.metadata,
303
+ default_agent: defaultAgent ? JSON.stringify(defaultAgent) : ""
304
+ };
305
+ const saved = saveSlackDevConfig(devConfig);
306
+ if (saved) clearWorkspaceConnectionCache(teamId);
307
+ return saved;
308
+ }
251
309
  try {
252
310
  const workspace = await findWorkspaceConnectionByTeamId(teamId);
253
311
  if (!workspace) {
@@ -266,6 +324,7 @@ async function setWorkspaceDefaultAgent(teamId, defaultAgent) {
266
324
  }
267
325
  }
268
326
  async function getWorkspaceDefaultAgentFromNango(teamId) {
327
+ if (isSlackDevMode()) return getDevDefaultAgent(loadSlackDevConfig());
269
328
  try {
270
329
  return (await findWorkspaceConnectionByTeamId(teamId))?.defaultAgent || null;
271
330
  } catch (error) {
@@ -294,6 +353,13 @@ async function storeWorkspaceInstallation(data) {
294
353
  teamId: data.teamId,
295
354
  enterpriseId: data.enterpriseId
296
355
  });
356
+ if (isSlackDevMode()) {
357
+ logger.debug({ connectionId }, "Skipping Nango store in dev mode");
358
+ return {
359
+ connectionId: `dev:${data.teamId}`,
360
+ success: true
361
+ };
362
+ }
297
363
  try {
298
364
  const integrationId = getSlackIntegrationId();
299
365
  const secretKey = env.NANGO_SLACK_SECRET_KEY || env.NANGO_SECRET_KEY;
@@ -406,6 +472,18 @@ async function storeWorkspaceInstallation(data) {
406
472
  * List all workspace installations from Nango.
407
473
  */
408
474
  async function listWorkspaceInstallations() {
475
+ if (isSlackDevMode()) {
476
+ const devConfig = loadSlackDevConfig();
477
+ if (!devConfig) return [];
478
+ return [{
479
+ connectionId: `dev:${devConfig.teamId}`,
480
+ teamId: devConfig.teamId,
481
+ teamName: devConfig.teamName,
482
+ botToken: devConfig.botToken,
483
+ tenantId: "default",
484
+ defaultAgent: getDevDefaultAgent(devConfig) ?? void 0
485
+ }];
486
+ }
409
487
  try {
410
488
  const nango = getSlackNango();
411
489
  const integrationId = getSlackIntegrationId();
@@ -445,6 +523,10 @@ async function listWorkspaceInstallations() {
445
523
  * Delete a workspace installation from Nango.
446
524
  */
447
525
  async function deleteWorkspaceInstallation(connectionId) {
526
+ if (isSlackDevMode()) {
527
+ logger.debug({ connectionId }, "Skipping Nango delete in dev mode");
528
+ return true;
529
+ }
448
530
  try {
449
531
  const nango = getSlackNango();
450
532
  const integrationId = getSlackIntegrationId();
@@ -0,0 +1,4 @@
1
+ //#region src/slack/socket-mode.d.ts
2
+ declare function startSocketMode(appToken: string): Promise<void>;
3
+ //#endregion
4
+ export { startSocketMode };