@inkeep/agents-work-apps 0.53.2 → 0.53.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 (40) hide show
  1. package/dist/github/mcp/index.js +61 -1
  2. package/dist/github/mcp/utils.d.ts +18 -1
  3. package/dist/github/mcp/utils.js +52 -16
  4. package/dist/github/routes/setup.d.ts +2 -2
  5. package/dist/github/routes/tokenExchange.d.ts +2 -2
  6. package/dist/github/routes/webhooks.d.ts +2 -2
  7. package/dist/slack/dispatcher.js +54 -40
  8. package/dist/slack/i18n/strings.d.ts +6 -5
  9. package/dist/slack/i18n/strings.js +7 -10
  10. package/dist/slack/routes/events.js +1 -1
  11. package/dist/slack/routes/workspaces.js +3 -3
  12. package/dist/slack/services/blocks/index.d.ts +3 -35
  13. package/dist/slack/services/blocks/index.js +5 -42
  14. package/dist/slack/services/client.d.ts +21 -1
  15. package/dist/slack/services/client.js +43 -1
  16. package/dist/slack/services/commands/index.js +42 -104
  17. package/dist/slack/services/events/app-mention.js +8 -31
  18. package/dist/slack/services/events/block-actions.d.ts +1 -11
  19. package/dist/slack/services/events/block-actions.js +6 -49
  20. package/dist/slack/services/events/direct-message.d.ts +11 -0
  21. package/dist/slack/services/events/direct-message.js +148 -0
  22. package/dist/slack/services/events/execution.d.ts +20 -0
  23. package/dist/slack/services/events/execution.js +46 -0
  24. package/dist/slack/services/events/index.d.ts +5 -3
  25. package/dist/slack/services/events/index.js +5 -3
  26. package/dist/slack/services/events/modal-submission.d.ts +1 -21
  27. package/dist/slack/services/events/modal-submission.js +14 -294
  28. package/dist/slack/services/events/streaming.d.ts +1 -1
  29. package/dist/slack/services/events/streaming.js +69 -70
  30. package/dist/slack/services/events/utils.d.ts +2 -14
  31. package/dist/slack/services/events/utils.js +2 -13
  32. package/dist/slack/services/index.d.ts +8 -6
  33. package/dist/slack/services/index.js +9 -7
  34. package/dist/slack/services/modals.d.ts +1 -18
  35. package/dist/slack/services/modals.js +1 -48
  36. package/dist/slack/services/resume-intent.js +43 -3
  37. package/dist/slack/socket-mode.js +1 -1
  38. package/dist/slack/tracer.d.ts +2 -4
  39. package/dist/slack/tracer.js +1 -3
  40. package/package.json +2 -2
@@ -31,16 +31,55 @@ async function withTimeout(promise, ms, label) {
31
31
  if (timeoutId !== void 0) clearTimeout(timeoutId);
32
32
  }
33
33
  }
34
+ /**
35
+ * Clean up the thinking acknowledgment message after streaming completes or fails.
36
+ * When the thinking message IS the thread anchor (slash commands at channel root),
37
+ * update it to show the user's question or invocation attribution instead of deleting,
38
+ * since deleting a thread anchor leaves "This message was deleted." as the root.
39
+ */
40
+ async function cleanupThinkingMessage(params) {
41
+ const { slackClient, channel, thinkingMessageTs, threadTs, slackUserId, agentName, question } = params;
42
+ if (!thinkingMessageTs) return;
43
+ try {
44
+ if (thinkingMessageTs === threadTs) {
45
+ const text = question ? `<@${slackUserId}> to ${agentName}: "${question}"` : `<@${slackUserId}> invoked _${agentName}_`;
46
+ await slackClient.chat.update({
47
+ channel,
48
+ ts: thinkingMessageTs,
49
+ text
50
+ });
51
+ } else await slackClient.chat.delete({
52
+ channel,
53
+ ts: thinkingMessageTs
54
+ });
55
+ } catch (error) {
56
+ logger.warn({
57
+ error,
58
+ channel,
59
+ thinkingMessageTs
60
+ }, "Failed to clean up thinking message");
61
+ }
62
+ }
34
63
  async function streamAgentResponse(params) {
35
64
  return tracer.startActiveSpan(SLACK_SPAN_NAMES.STREAM_AGENT_RESPONSE, async (span) => {
36
65
  const { slackClient, channel, threadTs, thinkingMessageTs, slackUserId, teamId, jwtToken, projectId, agentId, question, agentName, conversationId } = params;
66
+ const threadParam = threadTs ? { thread_ts: threadTs } : {};
67
+ const cleanupParams = {
68
+ slackClient,
69
+ channel,
70
+ thinkingMessageTs,
71
+ threadTs,
72
+ slackUserId,
73
+ agentName,
74
+ question
75
+ };
37
76
  span.setAttribute(SLACK_SPAN_KEYS.TEAM_ID, teamId);
38
77
  span.setAttribute(SLACK_SPAN_KEYS.CHANNEL_ID, channel);
39
78
  span.setAttribute(SLACK_SPAN_KEYS.USER_ID, slackUserId);
40
79
  span.setAttribute(SLACK_SPAN_KEYS.PROJECT_ID, projectId);
41
80
  span.setAttribute(SLACK_SPAN_KEYS.AGENT_ID, agentId);
42
81
  if (conversationId) span.setAttribute(SLACK_SPAN_KEYS.CONVERSATION_ID, conversationId);
43
- span.setAttribute(SLACK_SPAN_KEYS.THREAD_TS, threadTs);
82
+ if (threadTs) span.setAttribute(SLACK_SPAN_KEYS.THREAD_TS, threadTs);
44
83
  const apiUrl = env.INKEEP_AGENTS_API_URL || "http://localhost:3002";
45
84
  logger.info({
46
85
  conversationId,
@@ -89,15 +128,10 @@ async function streamAgentResponse(params) {
89
128
  const errorMessage$1 = getUserFriendlyErrorMessage(errorType$1, agentName);
90
129
  await slackClient.chat.postMessage({
91
130
  channel,
92
- thread_ts: threadTs,
131
+ ...threadParam,
93
132
  text: errorMessage$1
94
133
  });
95
- if (thinkingMessageTs) try {
96
- await slackClient.chat.delete({
97
- channel,
98
- ts: thinkingMessageTs
99
- });
100
- } catch {}
134
+ await cleanupThinkingMessage(cleanupParams);
101
135
  span.end();
102
136
  return {
103
137
  success: false,
@@ -109,15 +143,10 @@ async function streamAgentResponse(params) {
109
143
  const errorMessage = getUserFriendlyErrorMessage(errorType, agentName);
110
144
  await slackClient.chat.postMessage({
111
145
  channel,
112
- thread_ts: threadTs,
146
+ ...threadParam,
113
147
  text: errorMessage
114
148
  }).catch((e) => logger.warn({ error: e }, "Failed to send fetch error notification"));
115
- if (thinkingMessageTs) try {
116
- await slackClient.chat.delete({
117
- channel,
118
- ts: thinkingMessageTs
119
- });
120
- } catch {}
149
+ await cleanupThinkingMessage(cleanupParams);
121
150
  if (fetchError instanceof Error) setSpanWithError(span, fetchError);
122
151
  span.end();
123
152
  return {
@@ -138,15 +167,10 @@ async function streamAgentResponse(params) {
138
167
  const errorMessage = apiMessage ? `*Error.* ${apiMessage}` : getUserFriendlyErrorMessage(errorType, agentName);
139
168
  await slackClient.chat.postMessage({
140
169
  channel,
141
- thread_ts: threadTs,
170
+ ...threadParam,
142
171
  text: errorMessage
143
172
  });
144
- if (thinkingMessageTs) try {
145
- await slackClient.chat.delete({
146
- channel,
147
- ts: thinkingMessageTs
148
- });
149
- } catch {}
173
+ await cleanupThinkingMessage(cleanupParams);
150
174
  span.end();
151
175
  return {
152
176
  success: false,
@@ -165,15 +189,10 @@ async function streamAgentResponse(params) {
165
189
  const errorMessage = getUserFriendlyErrorMessage(errorType, agentName);
166
190
  await slackClient.chat.postMessage({
167
191
  channel,
168
- thread_ts: threadTs,
192
+ ...threadParam,
169
193
  text: errorMessage
170
194
  });
171
- if (thinkingMessageTs) try {
172
- await slackClient.chat.delete({
173
- channel,
174
- ts: thinkingMessageTs
175
- });
176
- } catch {}
195
+ await cleanupThinkingMessage(cleanupParams);
177
196
  span.end();
178
197
  return {
179
198
  success: false,
@@ -185,12 +204,15 @@ async function streamAgentResponse(params) {
185
204
  const decoder = new TextDecoder();
186
205
  let buffer = "";
187
206
  let fullText = "";
188
- const streamer = slackClient.chatStream({
207
+ const chatStreamArgs = {
189
208
  channel,
190
209
  recipient_team_id: teamId,
191
210
  recipient_user_id: slackUserId,
192
- thread_ts: threadTs
193
- });
211
+ ...threadTs ? { thread_ts: threadTs } : {}
212
+ };
213
+ const streamer = slackClient.chatStream(chatStreamArgs);
214
+ /** Tracks whether `chat.startStream` was called (i.e. a Slack streaming message exists). */
215
+ let streamerStarted = false;
194
216
  const pendingApprovalMessages = [];
195
217
  const toolCallIdToName = /* @__PURE__ */ new Map();
196
218
  const toolCallIdToInput = /* @__PURE__ */ new Map();
@@ -237,7 +259,7 @@ async function streamAgentResponse(params) {
237
259
  };
238
260
  const approvalPost = await slackClient.chat.postMessage({
239
261
  channel,
240
- thread_ts: threadTs,
262
+ ...threadParam,
241
263
  text: `Tool approval required: \`${toolName}\``,
242
264
  blocks: buildToolApprovalBlocks({
243
265
  toolName,
@@ -287,7 +309,7 @@ async function streamAgentResponse(params) {
287
309
  const label = componentType || "data-component";
288
310
  await retryWithBackoff(() => slackClient.files.uploadV2({
289
311
  channel_id: channel,
290
- thread_ts: threadTs,
312
+ ...threadParam,
291
313
  filename: `${label}.json`,
292
314
  content: overflowJson,
293
315
  initial_comment: `📊 ${label}`
@@ -300,7 +322,7 @@ async function streamAgentResponse(params) {
300
322
  }, "Failed to upload data component file"));
301
323
  } else await slackClient.chat.postMessage({
302
324
  channel,
303
- thread_ts: threadTs,
325
+ ...threadParam,
304
326
  text: "📊 Data component",
305
327
  blocks
306
328
  }).catch((e) => logger.warn({ error: e }, "Failed to post data component"));
@@ -331,7 +353,7 @@ async function streamAgentResponse(params) {
331
353
  const label = artifactName || "artifact";
332
354
  await retryWithBackoff(() => slackClient.files.uploadV2({
333
355
  channel_id: channel,
334
- thread_ts: threadTs,
356
+ ...threadParam,
335
357
  filename: `${label}.md`,
336
358
  content: overflowContent,
337
359
  initial_comment: `📄 ${label}`
@@ -344,7 +366,7 @@ async function streamAgentResponse(params) {
344
366
  }, "Failed to upload artifact file"));
345
367
  } else await slackClient.chat.postMessage({
346
368
  channel,
347
- thread_ts: threadTs,
369
+ ...threadParam,
348
370
  text: "📄 Data",
349
371
  blocks
350
372
  }).catch((e) => logger.warn({ error: e }, "Failed to post data artifact"));
@@ -368,14 +390,14 @@ async function streamAgentResponse(params) {
368
390
  if (data.type === "text-start" || data.type === "text-end") continue;
369
391
  if (data.type === "text-delta" && data.delta) {
370
392
  fullText += data.delta;
371
- await withTimeout(streamer.append({ markdown_text: data.delta }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.append");
393
+ if (await withTimeout(streamer.append({ markdown_text: data.delta }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.append") != null) streamerStarted = true;
372
394
  } else if (data.object === "chat.completion.chunk" && data.choices?.[0]?.delta?.content) {
373
395
  const content = data.choices[0].delta.content;
374
396
  try {
375
397
  if (JSON.parse(content).type === "data-operation") continue;
376
398
  } catch {}
377
399
  fullText += content;
378
- await withTimeout(streamer.append({ markdown_text: content }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.append");
400
+ if (await withTimeout(streamer.append({ markdown_text: content }), CHATSTREAM_OP_TIMEOUT_MS, "streamer.append") != null) streamerStarted = true;
379
401
  }
380
402
  } catch {}
381
403
  }
@@ -401,14 +423,7 @@ async function streamAgentResponse(params) {
401
423
  responseLength: fullText.length
402
424
  }, "Failed to finalize chatStream — content was already delivered");
403
425
  }
404
- if (thinkingMessageTs) try {
405
- await slackClient.chat.delete({
406
- channel,
407
- ts: thinkingMessageTs
408
- });
409
- } catch (deleteError) {
410
- logger.warn({ deleteError }, "Failed to delete acknowledgement message");
411
- }
426
+ await cleanupThinkingMessage(cleanupParams);
412
427
  logger.info({
413
428
  channel,
414
429
  threadTs,
@@ -442,46 +457,30 @@ async function streamAgentResponse(params) {
442
457
  threadTs,
443
458
  responseLength: fullText.length
444
459
  }, "Error during Slack streaming after content was already delivered — suppressing user-facing error");
445
- await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
446
- if (thinkingMessageTs) try {
447
- await slackClient.chat.delete({
448
- channel,
449
- ts: thinkingMessageTs
450
- });
451
- } catch {}
460
+ if (streamerStarted) await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
461
+ await cleanupThinkingMessage(cleanupParams);
452
462
  span.end();
453
463
  return { success: true };
454
464
  }
455
465
  if (pendingApprovalMessages.length > 0) {
456
466
  for (const { toolName } of pendingApprovalMessages) await slackClient.chat.postMessage({
457
467
  channel,
458
- thread_ts: threadTs,
468
+ ...threadParam,
459
469
  text: `Approval for \`${toolName}\` has expired.`
460
470
  }).catch((e) => logger.warn({ error: e }, "Failed to send approval expired notification"));
461
- await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
462
- if (thinkingMessageTs) try {
463
- await slackClient.chat.delete({
464
- channel,
465
- ts: thinkingMessageTs
466
- });
467
- } catch {}
471
+ if (streamerStarted) await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
472
+ await cleanupThinkingMessage(cleanupParams);
468
473
  span.end();
469
474
  return { success: true };
470
475
  }
471
476
  logger.error({ streamError }, "Error during Slack streaming");
472
- await withTimeout(streamer.stop(), CLEANUP_TIMEOUT_MS, "streamer.stop-cleanup").catch((e) => logger.warn({ error: e }, "Failed to stop streamer during error cleanup"));
473
- if (thinkingMessageTs) try {
474
- await slackClient.chat.delete({
475
- channel,
476
- ts: thinkingMessageTs
477
- });
478
- } catch {}
477
+ await cleanupThinkingMessage(cleanupParams);
479
478
  const errorType = classifyError(streamError);
480
479
  const errorMessage = getUserFriendlyErrorMessage(errorType, agentName);
481
480
  try {
482
481
  await slackClient.chat.postMessage({
483
482
  channel,
484
- thread_ts: threadTs,
483
+ ...threadParam,
485
484
  text: errorMessage
486
485
  });
487
486
  } catch (notifyError) {
@@ -11,9 +11,9 @@ declare function findCachedUserMapping(tenantId: string, slackUserId: string, te
11
11
  id: string;
12
12
  createdAt: string;
13
13
  updatedAt: string;
14
- slackUserId: string;
15
14
  tenantId: string;
16
15
  clientId: string;
16
+ slackUserId: string;
17
17
  slackTeamId: string;
18
18
  slackEnterpriseId: string | null;
19
19
  inkeepUserId: string;
@@ -72,21 +72,9 @@ declare function sendResponseUrlMessage(responseUrl: string, message: {
72
72
  delete_original?: boolean;
73
73
  blocks?: unknown[];
74
74
  }): Promise<void>;
75
- /**
76
- * Generate a deterministic conversation ID for Slack threads/DMs.
77
- * This ensures the same thread + agent combination gets the same conversation ID,
78
- * allowing the agent to maintain conversation history.
79
- *
80
- * Including agentId ensures switching agents in the same thread starts a fresh
81
- * conversation, avoiding sub-agent conflicts when the Run API tries to resume
82
- * a conversation that was started by a different agent.
83
- *
84
- * Format: slack-thread-{teamId}-{identifier}[-{agentId}]
85
- */
86
75
  declare function generateSlackConversationId(params: {
87
76
  teamId: string;
88
- threadTs?: string;
89
- channel: string;
77
+ messageTs: string;
90
78
  isDM?: boolean;
91
79
  agentId?: string;
92
80
  }): string;
@@ -276,20 +276,9 @@ async function sendResponseUrlMessage(responseUrl, message) {
276
276
  logger.error({ errorMessage }, "Failed to send response_url message");
277
277
  }
278
278
  }
279
- /**
280
- * Generate a deterministic conversation ID for Slack threads/DMs.
281
- * This ensures the same thread + agent combination gets the same conversation ID,
282
- * allowing the agent to maintain conversation history.
283
- *
284
- * Including agentId ensures switching agents in the same thread starts a fresh
285
- * conversation, avoiding sub-agent conflicts when the Run API tries to resume
286
- * a conversation that was started by a different agent.
287
- *
288
- * Format: slack-thread-{teamId}-{identifier}[-{agentId}]
289
- */
290
279
  function generateSlackConversationId(params) {
291
- const { teamId, threadTs, channel, isDM, agentId } = params;
292
- const base = isDM ? `slack-dm-${teamId}-${channel}` : `slack-thread-${teamId}-${threadTs || channel}`;
280
+ const { teamId, messageTs, isDM, agentId } = params;
281
+ const base = `${isDM ? "slack-dm" : "slack-trigger"}-${teamId}-${messageTs}`;
293
282
  return agentId ? `${base}-${agentId}` : base;
294
283
  }
295
284
  /**
@@ -1,16 +1,18 @@
1
1
  import { AgentResolutionParams, ResolvedAgentConfig, getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
2
- import { AgentConfigSources, ContextBlockParams, FollowUpButtonParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
- import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
2
+ import { AgentConfigSources, ContextBlockParams, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
3
+ import { checkUserIsChannelMember, getBotMemberChannels, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
4
4
  import { DefaultAgentConfig, SlackWorkspaceConnection, WorkspaceInstallData, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
5
5
  import { SlackCommandPayload, SlackCommandResponse } from "./types.js";
6
6
  import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
7
- import { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, FollowUpModalMetadata, ModalMetadata, buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
7
+ import { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ModalMetadata, buildAgentSelectorModal, buildMessageShortcutModal } from "./modals.js";
8
8
  import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
9
9
  import { InlineSelectorMetadata, handleAppMention } from "./events/app-mention.js";
10
- import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./events/block-actions.js";
11
- import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
10
+ import { handleMessageShortcut, handleOpenAgentSelectorModal, handleToolApproval } from "./events/block-actions.js";
11
+ import { handleDirectMessage } from "./events/direct-message.js";
12
12
  import { StreamResult, streamAgentResponse } from "./events/streaming.js";
13
+ import { PublicExecutionParams, executeAgentPublicly } from "./events/execution.js";
14
+ import { handleModalSubmission } from "./events/modal-submission.js";
13
15
  import "./events/index.js";
14
16
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
15
17
  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, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
18
+ export { AgentConfigSources, AgentOption, AgentResolutionParams, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ContextBlockParams, DefaultAgentConfig, InlineSelectorMetadata, ModalMetadata, PublicExecutionParams, ResolvedAgentConfig, SlackCommandPayload, SlackCommandResponse, SlackErrorType, SlackWorkspaceConnection, StreamResult, ToolApprovalButtonValue, ToolApprovalButtonValueSchema, WorkspaceInstallData, buildAgentSelectorModal, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, executeAgentPublicly, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotMemberChannels, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleDirectMessage, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -1,16 +1,18 @@
1
1
  import { clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createConnectSession, deleteWorkspaceInstallation, findWorkspaceConnectionByTeamId, getConnectionAccessToken, getSlackIntegrationId, getSlackNango, getWorkspaceDefaultAgentFromNango, listWorkspaceInstallations, setWorkspaceDefaultAgent, storeWorkspaceInstallation, updateConnectionMetadata } from "./nango.js";
2
2
  import { SlackErrorType, checkIfBotThread, classifyError, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, generateSlackConversationId, getChannelAgentConfig, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, markdownToMrkdwn, sendResponseUrlMessage } from "./events/utils.js";
3
3
  import { getAgentConfigSources, resolveEffectiveAgent } from "./agent-resolution.js";
4
- import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
5
- import { checkUserIsChannelMember, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
6
- import { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal } from "./modals.js";
7
- import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
4
+ import { ToolApprovalButtonValueSchema, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, createAlreadyLinkedMessage, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage } from "./blocks/index.js";
5
+ import { checkUserIsChannelMember, getBotMemberChannels, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackTeamInfo, getSlackUserInfo, postMessage, postMessageInThread, revokeSlackToken } from "./client.js";
8
6
  import { streamAgentResponse } from "./events/streaming.js";
7
+ import { executeAgentPublicly } from "./events/execution.js";
8
+ import { buildAgentSelectorModal, buildMessageShortcutModal } from "./modals.js";
9
+ import { handleAgentPickerCommand, handleCommand, handleHelpCommand, handleLinkCommand, handleQuestionCommand, handleStatusCommand, handleUnlinkCommand } from "./commands/index.js";
9
10
  import { handleAppMention } from "./events/app-mention.js";
10
- import { handleMessageShortcut, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleToolApproval } from "./events/block-actions.js";
11
- import { handleFollowUpSubmission, handleModalSubmission } from "./events/modal-submission.js";
11
+ import { handleMessageShortcut, handleOpenAgentSelectorModal, handleToolApproval } from "./events/block-actions.js";
12
+ import { handleDirectMessage } from "./events/direct-message.js";
13
+ import { handleModalSubmission } from "./events/modal-submission.js";
12
14
  import "./events/index.js";
13
15
  import { parseSlackCommandBody, parseSlackEventBody, verifySlackRequest } from "./security.js";
14
16
  import { getBotTokenForTeam, setBotTokenForTeam } from "./workspace-tokens.js";
15
17
 
16
- export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildConversationResponseBlocks, buildDataArtifactBlocks, buildDataComponentBlocks, buildFollowUpButton, buildFollowUpModal, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleFollowUpSubmission, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleOpenFollowUpModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
18
+ export { SlackErrorType, ToolApprovalButtonValueSchema, buildAgentSelectorModal, buildCitationsBlock, buildDataArtifactBlocks, buildDataComponentBlocks, buildMessageShortcutModal, buildSummaryBreadcrumbBlock, buildToolApprovalBlocks, buildToolApprovalDoneBlocks, buildToolApprovalExpiredBlocks, buildToolOutputErrorBlock, checkIfBotThread, checkUserIsChannelMember, classifyError, clearWorkspaceConnectionCache, computeWorkspaceConnectionId, createAlreadyLinkedMessage, createConnectSession, createContextBlock, createCreateInkeepAccountMessage, createErrorMessage, createNotLinkedMessage, createSmartLinkMessage, createStatusMessage, createUnlinkSuccessMessage, createUpdatedHelpMessage, deleteWorkspaceInstallation, executeAgentPublicly, extractApiErrorMessage, fetchAgentsForProject, fetchProjectsForTenant, findCachedUserMapping, findWorkspaceConnectionByTeamId, generateSlackConversationId, getAgentConfigSources, getBotMemberChannels, getBotTokenForTeam, getChannelAgentConfig, getConnectionAccessToken, getSlackChannelInfo, getSlackChannels, getSlackClient, getSlackIntegrationId, getSlackNango, getSlackTeamInfo, getSlackUserInfo, getThreadContext, getUserFriendlyErrorMessage, getWorkspaceDefaultAgent, getWorkspaceDefaultAgentFromNango, handleAgentPickerCommand, handleAppMention, handleCommand, handleDirectMessage, handleHelpCommand, handleLinkCommand, handleMessageShortcut, handleModalSubmission, handleOpenAgentSelectorModal, handleQuestionCommand, handleStatusCommand, handleToolApproval, handleUnlinkCommand, listWorkspaceInstallations, markdownToMrkdwn, parseSlackCommandBody, parseSlackEventBody, postMessage, postMessageInThread, resolveEffectiveAgent, revokeSlackToken, sendResponseUrlMessage, setBotTokenForTeam, setWorkspaceDefaultAgent, storeWorkspaceInstallation, streamAgentResponse, updateConnectionMetadata, verifySlackRequest };
@@ -21,16 +21,6 @@ interface ModalMetadata {
21
21
  buttonResponseUrl?: string;
22
22
  messageContext?: string;
23
23
  }
24
- interface FollowUpModalMetadata {
25
- conversationId: string;
26
- agentId: string;
27
- agentName?: string;
28
- projectId: string;
29
- tenantId: string;
30
- teamId: string;
31
- slackUserId: string;
32
- channel: string;
33
- }
34
24
  interface BuildAgentSelectorModalParams {
35
25
  projects: Array<{
36
26
  id: string;
@@ -53,13 +43,6 @@ interface BuildAgentSelectorModalParams {
53
43
  * All responses from this modal are private (ephemeral).
54
44
  */
55
45
  declare function buildAgentSelectorModal(params: BuildAgentSelectorModalParams): ModalView;
56
- /**
57
- * Build a follow-up modal for continuing a conversation.
58
- *
59
- * Shows only a prompt input. Agent and project are carried from the previous turn
60
- * via metadata. The conversationId ensures the agent has full history.
61
- */
62
- declare function buildFollowUpModal(metadata: FollowUpModalMetadata): ModalView;
63
46
  interface BuildMessageShortcutModalParams {
64
47
  projects: Array<{
65
48
  id: string;
@@ -84,4 +67,4 @@ interface BuildMessageShortcutModalParams {
84
67
  */
85
68
  declare function buildMessageShortcutModal(params: BuildMessageShortcutModalParams): ModalView;
86
69
  //#endregion
87
- export { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, FollowUpModalMetadata, ModalMetadata, buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal };
70
+ export { AgentOption, BuildAgentSelectorModalParams, BuildMessageShortcutModalParams, ModalMetadata, buildAgentSelectorModal, buildMessageShortcutModal };
@@ -164,53 +164,6 @@ function buildAgentSelectorModal(params) {
164
164
  };
165
165
  }
166
166
  /**
167
- * Build a follow-up modal for continuing a conversation.
168
- *
169
- * Shows only a prompt input. Agent and project are carried from the previous turn
170
- * via metadata. The conversationId ensures the agent has full history.
171
- */
172
- function buildFollowUpModal(metadata) {
173
- const blocks = [{
174
- type: "input",
175
- block_id: "question_block",
176
- element: {
177
- type: "plain_text_input",
178
- action_id: "question_input",
179
- multiline: true,
180
- placeholder: {
181
- type: "plain_text",
182
- text: SlackStrings.placeholders.enterPrompt
183
- }
184
- },
185
- label: {
186
- type: "plain_text",
187
- text: SlackStrings.labels.prompt,
188
- emoji: true
189
- }
190
- }];
191
- return {
192
- type: "modal",
193
- callback_id: "follow_up_modal",
194
- private_metadata: JSON.stringify(metadata),
195
- title: {
196
- type: "plain_text",
197
- text: SlackStrings.modals.followUp,
198
- emoji: true
199
- },
200
- submit: {
201
- type: "plain_text",
202
- text: SlackStrings.buttons.send,
203
- emoji: true
204
- },
205
- close: {
206
- type: "plain_text",
207
- text: SlackStrings.buttons.cancel,
208
- emoji: true
209
- },
210
- blocks
211
- };
212
- }
213
- /**
214
167
  * Build the modal for message shortcut (context menu on a message).
215
168
  *
216
169
  * Shows:
@@ -354,4 +307,4 @@ function buildMessageShortcutModal(params) {
354
307
  }
355
308
 
356
309
  //#endregion
357
- export { buildAgentSelectorModal, buildFollowUpModal, buildMessageShortcutModal };
310
+ export { buildAgentSelectorModal, buildMessageShortcutModal };
@@ -6,6 +6,7 @@ import { resolveEffectiveAgent } from "./agent-resolution.js";
6
6
  import { createContextBlock } from "./blocks/index.js";
7
7
  import { getSlackClient } from "./client.js";
8
8
  import { streamAgentResponse } from "./events/streaming.js";
9
+ import { executeAgentPublicly } from "./events/execution.js";
9
10
  import { signSlackUserToken } from "@inkeep/agents-core";
10
11
 
11
12
  //#region src/slack/services/resume-intent.ts
@@ -54,6 +55,11 @@ async function resumeSmartLinkIntent(params) {
54
55
  deliveryMethod = intent.responseUrl ? "response_url" : "bot_token";
55
56
  await resumeRunCommand(intent, slackClient, tokenCtx, teamId, tenantId);
56
57
  break;
58
+ case "dm":
59
+ resolvedAgentId = intent.agentId;
60
+ deliveryMethod = "streaming";
61
+ await resumeDirectMessage(intent, slackClient, tokenCtx, teamId);
62
+ break;
57
63
  }
58
64
  const durationMs = Date.now() - startTime;
59
65
  logger.info({
@@ -109,9 +115,7 @@ async function resumeMention(intent, slackClient, tokenCtx, teamId, tenantId) {
109
115
  });
110
116
  const conversationId = generateSlackConversationId({
111
117
  teamId,
112
- threadTs: replyThreadTs,
113
- channel: intent.channelId,
114
- isDM: false,
118
+ messageTs: intent.messageTs || replyThreadTs,
115
119
  agentId: intent.agentId
116
120
  });
117
121
  await streamAgentResponse({
@@ -129,6 +133,42 @@ async function resumeMention(intent, slackClient, tokenCtx, teamId, tenantId) {
129
133
  conversationId
130
134
  });
131
135
  }
136
+ async function resumeDirectMessage(intent, slackClient, tokenCtx, teamId) {
137
+ const { slackUserId } = tokenCtx;
138
+ if (!intent.agentId || !intent.projectId) {
139
+ logger.error({
140
+ entryPoint: intent.entryPoint,
141
+ channelId: intent.channelId,
142
+ hasAgentId: !!intent.agentId,
143
+ hasProjectId: !!intent.projectId
144
+ }, "DM intent missing agentId or projectId");
145
+ await postErrorToChannel(slackClient, intent.channelId, slackUserId);
146
+ return;
147
+ }
148
+ const slackUserToken = await signSlackUserToken({
149
+ ...tokenCtx,
150
+ slackAuthorized: false
151
+ });
152
+ const conversationId = generateSlackConversationId({
153
+ teamId,
154
+ messageTs: intent.messageTs || "",
155
+ agentId: intent.agentId,
156
+ isDM: true
157
+ });
158
+ await executeAgentPublicly({
159
+ slackClient,
160
+ channel: intent.channelId,
161
+ threadTs: intent.messageTs,
162
+ slackUserId,
163
+ teamId,
164
+ jwtToken: slackUserToken,
165
+ projectId: intent.projectId,
166
+ agentId: intent.agentId,
167
+ agentName: intent.agentId,
168
+ question: intent.question,
169
+ conversationId
170
+ });
171
+ }
132
172
  async function resumeCommand(intent, slackClient, tokenCtx, teamId, tenantId) {
133
173
  const { slackUserId } = tokenCtx;
134
174
  const resolvedAgent = await resolveEffectiveAgent({
@@ -1,7 +1,7 @@
1
1
  import { __toESM } from "../_virtual/rolldown_runtime.js";
2
2
  import { getLogger } from "../logger.js";
3
- import { handleCommand } from "./services/commands/index.js";
4
3
  import { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, tracer } from "./tracer.js";
4
+ import { handleCommand } from "./services/commands/index.js";
5
5
  import "./services/index.js";
6
6
  import { dispatchSlackEvent } from "./dispatcher.js";
7
7
  import { flushTraces } from "@inkeep/agents-core";
@@ -6,15 +6,13 @@ declare const tracer: _opentelemetry_api0.Tracer;
6
6
  declare const SLACK_SPAN_NAMES: {
7
7
  readonly WEBHOOK: "slack.webhook";
8
8
  readonly APP_MENTION: "slack.app_mention";
9
+ readonly DIRECT_MESSAGE: "slack.direct_message";
9
10
  readonly BLOCK_ACTION: "slack.block_action";
10
11
  readonly MODAL_SUBMISSION: "slack.modal_submission";
11
- readonly FOLLOW_UP_SUBMISSION: "slack.follow_up_submission";
12
12
  readonly MESSAGE_SHORTCUT: "slack.message_shortcut";
13
13
  readonly STREAM_AGENT_RESPONSE: "slack.stream_agent_response";
14
14
  readonly OPEN_AGENT_SELECTOR_MODAL: "slack.open_agent_selector_modal";
15
- readonly OPEN_FOLLOW_UP_MODAL: "slack.open_follow_up_modal";
16
15
  readonly PROJECT_SELECT_UPDATE: "slack.project_select_update";
17
- readonly CALL_AGENT_API: "slack.call_agent_api";
18
16
  readonly TOOL_APPROVAL: "slack.tool_approval";
19
17
  };
20
18
  declare const SLACK_SPAN_KEYS: {
@@ -40,6 +38,6 @@ declare const SLACK_SPAN_KEYS: {
40
38
  readonly AUTHORIZED: "slack.authorized";
41
39
  readonly AUTH_SOURCE: "slack.auth_source";
42
40
  };
43
- type SlackOutcome = 'handled' | 'ignored_bot_message' | 'ignored_edited_message' | 'ignored_unknown_event' | 'ignored_no_action_match' | 'ignored_slack_retry' | 'url_verification' | 'validation_error' | 'signature_invalid' | 'error';
41
+ type SlackOutcome = 'handled' | 'ignored_bot_message' | 'ignored_edited_message' | 'ignored_unknown_event' | 'ignored_no_action_match' | 'ignored_slack_retry' | 'ignored_duplicate_event' | 'url_verification' | 'validation_error' | 'signature_invalid' | 'error';
44
42
  //#endregion
45
43
  export { SLACK_SPAN_KEYS, SLACK_SPAN_NAMES, SlackOutcome, setSpanWithError, tracer };
@@ -5,15 +5,13 @@ const tracer = getTracer("agents-work-apps-slack");
5
5
  const SLACK_SPAN_NAMES = {
6
6
  WEBHOOK: "slack.webhook",
7
7
  APP_MENTION: "slack.app_mention",
8
+ DIRECT_MESSAGE: "slack.direct_message",
8
9
  BLOCK_ACTION: "slack.block_action",
9
10
  MODAL_SUBMISSION: "slack.modal_submission",
10
- FOLLOW_UP_SUBMISSION: "slack.follow_up_submission",
11
11
  MESSAGE_SHORTCUT: "slack.message_shortcut",
12
12
  STREAM_AGENT_RESPONSE: "slack.stream_agent_response",
13
13
  OPEN_AGENT_SELECTOR_MODAL: "slack.open_agent_selector_modal",
14
- OPEN_FOLLOW_UP_MODAL: "slack.open_follow_up_modal",
15
14
  PROJECT_SELECT_UPDATE: "slack.project_select_update",
16
- CALL_AGENT_API: "slack.call_agent_api",
17
15
  TOOL_APPROVAL: "slack.tool_approval"
18
16
  };
19
17
  const SLACK_SPAN_KEYS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/agents-work-apps",
3
- "version": "0.53.2",
3
+ "version": "0.53.4",
4
4
  "description": "First party integrations for Inkeep Agents",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -33,7 +33,7 @@
33
33
  "jose": "^6.1.0",
34
34
  "minimatch": "^10.1.1",
35
35
  "slack-block-builder": "^2.8.0",
36
- "@inkeep/agents-core": "0.53.2"
36
+ "@inkeep/agents-core": "0.53.4"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@hono/zod-openapi": "^1.1.5",