@inkeep/agents-work-apps 0.51.0 → 0.53.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.
@@ -1,6 +1,7 @@
1
1
  import { getLogger } from "../../logger.js";
2
2
  import runDbClient_default from "../../db/runDbClient.js";
3
3
  import { getWorkspaceDefaultAgentFromNango } from "./nango.js";
4
+ import { fetchAgentsForProject } from "./events/utils.js";
4
5
  import { findWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
5
6
 
6
7
  //#region src/slack/services/agent-resolution.ts
@@ -11,6 +12,35 @@ import { findWorkAppSlackChannelAgentConfig } from "@inkeep/agents-core";
11
12
  * Priority: Channel default > Workspace default (all admin-controlled)
12
13
  */
13
14
  const logger = getLogger("slack-agent-resolution");
15
+ const AGENT_NAME_CACHE_TTL_MS = 300 * 1e3;
16
+ const AGENT_NAME_CACHE_MAX_SIZE = 500;
17
+ const agentNameCache = /* @__PURE__ */ new Map();
18
+ async function lookupAgentName(tenantId, projectId, agentId) {
19
+ const cacheKey = `${tenantId}:${projectId}:${agentId}`;
20
+ const cached = agentNameCache.get(cacheKey);
21
+ if (cached && cached.expiresAt > Date.now()) return cached.name || void 0;
22
+ const agents = await fetchAgentsForProject(tenantId, projectId);
23
+ for (const agent of agents) {
24
+ const key = `${tenantId}:${projectId}:${agent.id}`;
25
+ agentNameCache.set(key, {
26
+ name: agent.name || null,
27
+ expiresAt: Date.now() + AGENT_NAME_CACHE_TTL_MS
28
+ });
29
+ }
30
+ if (agentNameCache.size > AGENT_NAME_CACHE_MAX_SIZE) {
31
+ const now = Date.now();
32
+ for (const [key, entry] of agentNameCache) if (entry.expiresAt <= now) agentNameCache.delete(key);
33
+ if (agentNameCache.size > AGENT_NAME_CACHE_MAX_SIZE) {
34
+ const excess = agentNameCache.size - AGENT_NAME_CACHE_MAX_SIZE;
35
+ const keys = agentNameCache.keys();
36
+ for (let i = 0; i < excess; i++) {
37
+ const { value } = keys.next();
38
+ if (value) agentNameCache.delete(value);
39
+ }
40
+ }
41
+ }
42
+ return agents.find((a) => a.id === agentId)?.name || void 0;
43
+ }
14
44
  /**
15
45
  * Resolve the effective agent configuration.
16
46
  * Priority: Channel default > Workspace default
@@ -25,6 +55,7 @@ async function resolveEffectiveAgent(params) {
25
55
  teamId,
26
56
  channelId
27
57
  }, "Resolving effective agent");
58
+ let result = null;
28
59
  if (channelId) {
29
60
  const channelConfig = await findWorkAppSlackChannelAgentConfig(runDbClient_default)(tenantId, teamId, channelId);
30
61
  if (channelConfig?.enabled) {
@@ -33,7 +64,7 @@ async function resolveEffectiveAgent(params) {
33
64
  agentId: channelConfig.agentId,
34
65
  source: "channel"
35
66
  }, "Resolved agent from channel config");
36
- return {
67
+ result = {
37
68
  projectId: channelConfig.projectId,
38
69
  agentId: channelConfig.agentId,
39
70
  agentName: channelConfig.agentName || void 0,
@@ -42,27 +73,39 @@ async function resolveEffectiveAgent(params) {
42
73
  };
43
74
  }
44
75
  }
45
- const workspaceConfig = await getWorkspaceDefaultAgentFromNango(teamId);
46
- if (workspaceConfig?.agentId && workspaceConfig.projectId) {
47
- logger.info({
48
- teamId,
49
- agentId: workspaceConfig.agentId,
50
- source: "workspace"
51
- }, "Resolved agent from workspace config");
52
- return {
53
- projectId: workspaceConfig.projectId,
54
- agentId: workspaceConfig.agentId,
55
- agentName: workspaceConfig.agentName,
56
- source: "workspace",
57
- grantAccessToMembers: workspaceConfig.grantAccessToMembers ?? true
58
- };
76
+ if (!result) {
77
+ const workspaceConfig = await getWorkspaceDefaultAgentFromNango(teamId);
78
+ if (workspaceConfig?.agentId && workspaceConfig.projectId) {
79
+ logger.info({
80
+ teamId,
81
+ agentId: workspaceConfig.agentId,
82
+ source: "workspace"
83
+ }, "Resolved agent from workspace config");
84
+ result = {
85
+ projectId: workspaceConfig.projectId,
86
+ agentId: workspaceConfig.agentId,
87
+ agentName: workspaceConfig.agentName,
88
+ source: "workspace",
89
+ grantAccessToMembers: workspaceConfig.grantAccessToMembers ?? true
90
+ };
91
+ }
59
92
  }
60
- logger.debug({
93
+ if (result && (!result.agentName || result.agentName === result.agentId)) {
94
+ const name = await lookupAgentName(tenantId, result.projectId, result.agentId);
95
+ if (name) {
96
+ result.agentName = name;
97
+ logger.debug({
98
+ agentId: result.agentId,
99
+ agentName: name
100
+ }, "Enriched agent config with name from manage API");
101
+ }
102
+ }
103
+ if (!result) logger.debug({
61
104
  tenantId,
62
105
  teamId,
63
106
  channelId
64
107
  }, "No agent configuration found");
65
- return null;
108
+ return result;
66
109
  }
67
110
  /**
68
111
  * Get all agent configuration sources for display purposes.
@@ -92,10 +135,15 @@ async function getAgentConfigSources(params) {
92
135
  source: "workspace",
93
136
  grantAccessToMembers: wsConfig.grantAccessToMembers ?? true
94
137
  };
138
+ const effective = channelConfig || workspaceConfig;
139
+ if (effective && (!effective.agentName || effective.agentName === effective.agentId)) {
140
+ const name = await lookupAgentName(tenantId, effective.projectId, effective.agentId);
141
+ if (name) effective.agentName = name;
142
+ }
95
143
  return {
96
144
  channelConfig,
97
145
  workspaceConfig,
98
- effective: channelConfig || workspaceConfig
146
+ effective
99
147
  };
100
148
  }
101
149
 
@@ -17,6 +17,7 @@ declare function createContextBlock(params: ContextBlockParams): {
17
17
  interface FollowUpButtonParams {
18
18
  conversationId: string;
19
19
  agentId: string;
20
+ agentName?: string;
20
21
  projectId: string;
21
22
  tenantId: string;
22
23
  teamId: string;
@@ -109,7 +110,40 @@ declare function buildToolApprovalExpiredBlocks(params: {
109
110
  text: string;
110
111
  }[];
111
112
  }[];
113
+ declare function buildToolOutputErrorBlock(toolName: string, errorText: string): {
114
+ type: "context";
115
+ elements: {
116
+ type: "mrkdwn";
117
+ text: string;
118
+ }[];
119
+ };
120
+ declare function buildSummaryBreadcrumbBlock(labels: string[]): {
121
+ type: "context";
122
+ elements: {
123
+ type: "mrkdwn";
124
+ text: string;
125
+ }[];
126
+ };
127
+ declare function buildDataComponentBlocks(component: {
128
+ id: string;
129
+ data: Record<string, unknown>;
130
+ }): {
131
+ blocks: any[];
132
+ overflowJson?: string;
133
+ componentType?: string;
134
+ };
135
+ declare function buildDataArtifactBlocks(artifact: {
136
+ data: Record<string, unknown>;
137
+ }): {
138
+ blocks: any[];
139
+ overflowContent?: string;
140
+ artifactName?: string;
141
+ };
142
+ declare function buildCitationsBlock(citations: Array<{
143
+ title?: string;
144
+ url?: string;
145
+ }>): any[];
112
146
  declare function createJwtLinkMessage(linkUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
113
147
  declare function createCreateInkeepAccountMessage(acceptUrl: string, expiresInMinutes: number): Readonly<slack_block_builder0.SlackMessageDto>;
114
148
  //#endregion
115
- export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
149
+ export { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -44,10 +44,7 @@ function buildConversationResponseBlocks(params) {
44
44
  }
45
45
  }];
46
46
  if (!isError) {
47
- const contextBlock = createContextBlock({
48
- agentName,
49
- isPrivate: true
50
- });
47
+ const contextBlock = createContextBlock({ agentName });
51
48
  blocks.push(contextBlock);
52
49
  blocks.push({
53
50
  type: "actions",
@@ -88,31 +85,25 @@ const ToolApprovalButtonValueSchema = z.object({
88
85
  function buildToolApprovalBlocks(params) {
89
86
  const { toolName, input, buttonValue } = params;
90
87
  const blocks = [{
91
- type: "header",
92
- text: {
93
- type: "plain_text",
94
- text: "Tool Approval Required",
95
- emoji: false
96
- }
97
- }, {
98
88
  type: "section",
99
89
  text: {
100
90
  type: "mrkdwn",
101
- text: `The agent wants to use \`${toolName}\`.`
91
+ text: `*Approval required - \`${toolName}\`*`
102
92
  }
103
93
  }];
104
94
  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;
95
+ const fields = Object.entries(input).slice(0, 10).map(([k, v]) => {
96
+ const val = typeof v === "object" ? JSON.stringify(v) : String(v ?? "");
97
+ return {
98
+ type: "mrkdwn",
99
+ text: `*${k}:*\n${val.length > 80 ? `${val.slice(0, 80)}…` : val}`
100
+ };
101
+ });
107
102
  blocks.push({
108
103
  type: "section",
109
- text: {
110
- type: "mrkdwn",
111
- text: `\`\`\`json\n${truncated}\n\`\`\``
112
- }
104
+ fields
113
105
  });
114
106
  }
115
- blocks.push({ type: "divider" });
116
107
  blocks.push({
117
108
  type: "actions",
118
109
  elements: [{
@@ -120,7 +111,7 @@ function buildToolApprovalBlocks(params) {
120
111
  text: {
121
112
  type: "plain_text",
122
113
  text: "Approve",
123
- emoji: false
114
+ emoji: true
124
115
  },
125
116
  style: "primary",
126
117
  action_id: "tool_approval_approve",
@@ -130,7 +121,7 @@ function buildToolApprovalBlocks(params) {
130
121
  text: {
131
122
  type: "plain_text",
132
123
  text: "Deny",
133
- emoji: false
124
+ emoji: true
134
125
  },
135
126
  style: "danger",
136
127
  action_id: "tool_approval_deny",
@@ -158,6 +149,154 @@ function buildToolApprovalExpiredBlocks(params) {
158
149
  }]
159
150
  }];
160
151
  }
152
+ function buildToolOutputErrorBlock(toolName, errorText) {
153
+ return {
154
+ type: "context",
155
+ elements: [{
156
+ type: "mrkdwn",
157
+ text: `⚠️ *${toolName}* · failed: ${errorText.length > 100 ? `${errorText.slice(0, 100)}…` : errorText}`
158
+ }]
159
+ };
160
+ }
161
+ function buildSummaryBreadcrumbBlock(labels) {
162
+ return {
163
+ type: "context",
164
+ elements: [{
165
+ type: "mrkdwn",
166
+ text: labels.join(" → ")
167
+ }]
168
+ };
169
+ }
170
+ function isFlatRecord(obj) {
171
+ return Object.values(obj).every((v) => v === null || [
172
+ "string",
173
+ "number",
174
+ "boolean"
175
+ ].includes(typeof v));
176
+ }
177
+ function findSourcesArray(data) {
178
+ for (const value of Object.values(data)) if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && value[0] !== null && ("url" in value[0] || "href" in value[0])) return value;
179
+ return null;
180
+ }
181
+ function buildDataComponentBlocks(component) {
182
+ const { data } = component;
183
+ const componentType = typeof data.type === "string" ? data.type : void 0;
184
+ const blocks = [{
185
+ type: "header",
186
+ text: {
187
+ type: "plain_text",
188
+ text: `📊 ${componentType || "Data Component"}`,
189
+ emoji: true
190
+ }
191
+ }];
192
+ const payload = Object.fromEntries(Object.entries(data).filter(([k]) => k !== "type"));
193
+ let overflowJson;
194
+ if (Object.keys(payload).length > 0) if (isFlatRecord(payload)) {
195
+ const fields = Object.entries(payload).slice(0, 10).map(([k, v]) => {
196
+ const val = String(v ?? "");
197
+ return {
198
+ type: "mrkdwn",
199
+ text: `*${k}*\n${val.length > 80 ? `${val.slice(0, 80)}…` : val}`
200
+ };
201
+ });
202
+ blocks.push({
203
+ type: "section",
204
+ fields
205
+ });
206
+ } else {
207
+ const jsonStr = JSON.stringify(payload, null, 2);
208
+ if (jsonStr.length > 2900) overflowJson = jsonStr;
209
+ else blocks.push({
210
+ type: "section",
211
+ text: {
212
+ type: "mrkdwn",
213
+ text: `\`\`\`json\n${jsonStr}\n\`\`\``
214
+ }
215
+ });
216
+ }
217
+ if (componentType) blocks.push({
218
+ type: "context",
219
+ elements: [{
220
+ type: "mrkdwn",
221
+ text: `data component · type: ${componentType}`
222
+ }]
223
+ });
224
+ return {
225
+ blocks,
226
+ overflowJson,
227
+ componentType
228
+ };
229
+ }
230
+ function buildDataArtifactBlocks(artifact) {
231
+ const { data } = artifact;
232
+ const sourcesArray = findSourcesArray(data);
233
+ if (sourcesArray && sourcesArray.length > 0) {
234
+ const MAX_SOURCES = 10;
235
+ const lines = sourcesArray.slice(0, MAX_SOURCES).map((s) => {
236
+ const url = s.url || s.href;
237
+ const title = s.title || s.name || url;
238
+ return url ? `• <${url}|${title}>` : null;
239
+ }).filter((l) => l !== null);
240
+ if (lines.length > 0) {
241
+ const suffix = sourcesArray.length > MAX_SOURCES ? `\n_and ${sourcesArray.length - MAX_SOURCES} more_` : "";
242
+ return { blocks: [{
243
+ type: "section",
244
+ text: {
245
+ type: "mrkdwn",
246
+ text: `📚 *Sources*\n${lines.join("\n")}${suffix}`
247
+ }
248
+ }] };
249
+ }
250
+ }
251
+ const artifactType = typeof data.type === "string" ? data.type : void 0;
252
+ const name = typeof data.name === "string" && data.name ? data.name : artifactType || "Artifact";
253
+ const blocks = [{
254
+ type: "header",
255
+ text: {
256
+ type: "plain_text",
257
+ text: `📄 ${name}`,
258
+ emoji: true
259
+ }
260
+ }];
261
+ if (artifactType) blocks.push({
262
+ type: "context",
263
+ elements: [{
264
+ type: "mrkdwn",
265
+ text: `type: ${artifactType}`
266
+ }]
267
+ });
268
+ let overflowContent;
269
+ if (typeof data.description === "string" && data.description) if (data.description.length > 2900) overflowContent = data.description;
270
+ else blocks.push({
271
+ type: "section",
272
+ text: {
273
+ type: "mrkdwn",
274
+ text: data.description
275
+ }
276
+ });
277
+ return {
278
+ blocks,
279
+ overflowContent,
280
+ artifactName: name
281
+ };
282
+ }
283
+ function buildCitationsBlock(citations) {
284
+ const MAX_CITATIONS = 10;
285
+ const lines = citations.slice(0, MAX_CITATIONS).map((c) => {
286
+ const url = c.url;
287
+ const title = c.title || url;
288
+ return url ? `• <${url}|${title}>` : null;
289
+ }).filter((l) => l !== null);
290
+ if (lines.length === 0) return [];
291
+ const suffix = citations.length > MAX_CITATIONS ? `\n_and ${citations.length - MAX_CITATIONS} more_` : "";
292
+ return [{
293
+ type: "section",
294
+ text: {
295
+ type: "mrkdwn",
296
+ text: `📚 *Sources*\n${lines.join("\n")}${suffix}`
297
+ }
298
+ }];
299
+ }
161
300
  function createJwtLinkMessage(linkUrl, expiresInMinutes) {
162
301
  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();
163
302
  }
@@ -166,4 +305,4 @@ function createCreateInkeepAccountMessage(acceptUrl, expiresInMinutes) {
166
305
  }
167
306
 
168
307
  //#endregion
169
- export { ToolApprovalButtonValueSchema, buildConversationResponseBlocks, buildFollowUpButton, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
308
+ export { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage };
@@ -2,11 +2,11 @@ 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
6
  import { resolveEffectiveAgent } from "../agent-resolution.js";
6
7
  import { SlackStrings } from "../../i18n/strings.js";
7
8
  import { createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createJwtLinkMessage, createNotLinkedMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "../blocks/index.js";
8
9
  import { getSlackClient } from "../client.js";
9
- import { extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, getChannelAgentConfig, sendResponseUrlMessage } from "../events/utils.js";
10
10
  import { buildAgentSelectorModal } from "../modals.js";
11
11
  import { createInvitationInDb, deleteWorkAppSlackUserMapping, findWorkAppSlackUserMapping, findWorkAppSlackUserMappingBySlackUser, findWorkAppSlackWorkspaceByTeamId, flushTraces, getInProcessFetch, getOrganizationMemberByEmail, getPendingInvitationsByEmail, getWaitUntil, signSlackLinkToken, signSlackUserToken } from "@inkeep/agents-core";
12
12
 
@@ -412,7 +412,7 @@ async function executeAgentInBackground(payload, existingLink, targetAgent, ques
412
412
  }, "Agent execution completed via Slack");
413
413
  const contextBlock = createContextBlock({ agentName: targetAgent.name || targetAgent.id });
414
414
  await sendResponseUrlMessage(payload.responseUrl, {
415
- response_type: "ephemeral",
415
+ response_type: "in_channel",
416
416
  text: assistantMessage,
417
417
  blocks: [{
418
418
  type: "section",
@@ -1,10 +1,10 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
+ import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
4
5
  import { resolveEffectiveAgent } from "../agent-resolution.js";
5
6
  import { SlackStrings } from "../../i18n/strings.js";
6
7
  import { getSlackChannelInfo, getSlackClient, getSlackUserInfo, postMessageInThread } from "../client.js";
7
- import { checkIfBotThread, classifyError, findCachedUserMapping, formatChannelContext, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, timedOp } from "./utils.js";
8
8
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
9
9
  import { streamAgentResponse } from "./streaming.js";
10
10
  import { signSlackUserToken } from "@inkeep/agents-core";
@@ -1,10 +1,10 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
+ import { fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
4
5
  import { SlackStrings } from "../../i18n/strings.js";
5
6
  import { ToolApprovalButtonValueSchema, buildToolApprovalDoneBlocks } from "../blocks/index.js";
6
7
  import { getSlackClient } from "../client.js";
7
- import { fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, getChannelAgentConfig, sendResponseUrlMessage } from "./utils.js";
8
8
  import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "../modals.js";
9
9
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, setSpanWithError, tracer } from "../../tracer.js";
10
10
  import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
@@ -1,10 +1,10 @@
1
1
  import { env } from "../../../env.js";
2
2
  import { getLogger } from "../../../logger.js";
3
3
  import { findWorkspaceConnectionByTeamId } from "../nango.js";
4
+ import { classifyError, extractApiErrorMessage, findCachedUserMapping, generateSlackConversationId, getThreadContext, getUserFriendlyErrorMessage, markdownToMrkdwn, sendResponseUrlMessage } from "./utils.js";
4
5
  import { SlackStrings } from "../../i18n/strings.js";
5
6
  import { buildConversationResponseBlocks } from "../blocks/index.js";
6
7
  import { getSlackClient } from "../client.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
9
  import { getInProcessFetch, signSlackUserToken } from "@inkeep/agents-core";
10
10
 
@@ -36,13 +36,16 @@ async function handleModalSubmission(view) {
36
36
  const includeContext = includeContextValue?.selected_options?.some((o) => o.value === "include_context") ?? true;
37
37
  let agentId = metadata.selectedAgentId;
38
38
  let projectId = metadata.selectedProjectId;
39
+ let agentName = null;
39
40
  if (agentSelectValue?.selected_option?.value) try {
40
41
  const parsed = JSON.parse(agentSelectValue.selected_option.value);
41
42
  agentId = parsed.agentId;
42
43
  projectId = parsed.projectId;
44
+ agentName = parsed.agentName || null;
43
45
  } catch {
44
46
  logger.warn({ value: agentSelectValue.selected_option.value }, "Failed to parse agent select value");
45
47
  }
48
+ const agentDisplayName = agentName || agentId || "Agent";
46
49
  if (!agentId || !projectId) {
47
50
  logger.error({ metadata }, "Missing agent or project ID in modal submission");
48
51
  if (metadata.buttonResponseUrl) await sendResponseUrlMessage(metadata.buttonResponseUrl, {
@@ -112,7 +115,7 @@ async function handleModalSubmission(view) {
112
115
  });
113
116
  span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, conversationId);
114
117
  const apiBaseUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
115
- const thinkingText = SlackStrings.status.thinking(agentId);
118
+ const thinkingText = SlackStrings.status.thinking(agentDisplayName);
116
119
  if (metadata.buttonResponseUrl) await sendResponseUrlMessage(metadata.buttonResponseUrl, {
117
120
  text: thinkingText,
118
121
  response_type: "ephemeral",
@@ -139,6 +142,7 @@ async function handleModalSubmission(view) {
139
142
  slackClient,
140
143
  metadata,
141
144
  agentId,
145
+ agentDisplayName,
142
146
  projectId,
143
147
  tenantId,
144
148
  conversationId,
@@ -194,7 +198,8 @@ async function handleFollowUpSubmission(view) {
194
198
  span.end();
195
199
  return;
196
200
  }
197
- const { conversationId, agentId, projectId, tenantId, teamId, slackUserId, channel } = metadata;
201
+ const { conversationId, agentId, agentName, projectId, tenantId, teamId, slackUserId, channel } = metadata;
202
+ const agentDisplayName = agentName || agentId || "Agent";
198
203
  span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
199
204
  span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channel);
200
205
  span.setAttribute(SLACK_SPAN_KEYS.USER_ID, slackUserId);
@@ -234,7 +239,7 @@ async function handleFollowUpSubmission(view) {
234
239
  await slackClient.chat.postEphemeral({
235
240
  channel,
236
241
  user: slackUserId,
237
- text: SlackStrings.status.thinking(agentId)
242
+ text: SlackStrings.status.thinking(agentDisplayName)
238
243
  });
239
244
  const responseText = await callAgentApi({
240
245
  apiBaseUrl,
@@ -247,11 +252,12 @@ async function handleFollowUpSubmission(view) {
247
252
  const responseBlocks = buildConversationResponseBlocks({
248
253
  userMessage: question,
249
254
  responseText: responseText.text,
250
- agentName: agentId,
255
+ agentName: agentDisplayName,
251
256
  isError: responseText.isError,
252
257
  followUpParams: {
253
258
  conversationId,
254
259
  agentId,
260
+ agentName: agentDisplayName,
255
261
  projectId,
256
262
  tenantId,
257
263
  teamId,
@@ -370,15 +376,16 @@ async function callAgentApi(params) {
370
376
  });
371
377
  }
372
378
  async function postPrivateResponse(params) {
373
- const { slackClient, metadata, agentId, projectId, tenantId, conversationId, userMessage, responseText, isError } = params;
379
+ const { slackClient, metadata, agentId, agentDisplayName, projectId, tenantId, conversationId, userMessage, responseText, isError } = params;
374
380
  const responseBlocks = buildConversationResponseBlocks({
375
381
  userMessage,
376
382
  responseText,
377
- agentName: agentId,
383
+ agentName: agentDisplayName,
378
384
  isError,
379
385
  followUpParams: {
380
386
  conversationId,
381
387
  agentId,
388
+ agentName: agentDisplayName,
382
389
  projectId,
383
390
  tenantId,
384
391
  teamId: metadata.teamId,