@openclaw/slack 2026.9.1-beta.1 → 2026.9.1

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 (65) hide show
  1. package/dist/{account-inspect-CN2hBsim.js → account-inspect-C3sePHWa.js} +3 -3
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{action-runtime-BoSeX09X.js → action-runtime-DND4Pyp2.js} +84 -66
  4. package/dist/action-runtime.runtime-CEhbmS-Z.js +2 -0
  5. package/dist/{actions-XdQyjG_p.js → actions-fh66A6lc.js} +198 -54
  6. package/dist/api.js +14 -14
  7. package/dist/{approval-handler.runtime-DxXfGX_Z.js → approval-handler.runtime-D7d8sKM0.js} +18 -12
  8. package/dist/{channel-C_vnTgxo.js → channel-4m8IAdMD.js} +70 -128
  9. package/dist/channel-config-api.js +1 -1
  10. package/dist/channel-plugin-api.js +1 -1
  11. package/dist/{channel-type-j-2lalcC.js → channel-type-3-SqEMVc.js} +2 -2
  12. package/dist/{channel.setup-aal8tokM.js → channel.setup-t_ghLGb_.js} +23 -22
  13. package/dist/{config-schema-CN3XZRX5.js → config-schema-bP3eV2ra.js} +25 -16
  14. package/dist/{conversation-binding-route-DFVLUdYF.js → conversation-binding-route-Vee8Dgbf.js} +2 -2
  15. package/dist/{cursor-pages-eX7p8Wwt.js → cursor-pages-ByFwo-Py.js} +7 -1
  16. package/dist/{directory-config-DqfQ42OV.js → directory-config-Bmw4ykSy.js} +4 -3
  17. package/dist/directory-contract-api.js +1 -1
  18. package/dist/{directory-live-lC6X_Ru-.js → directory-live-q32AW7N1.js} +3 -8
  19. package/dist/{doctor-contract-BAy7-EUQ.js → doctor-contract-BNjw9bxb.js} +5 -4
  20. package/dist/doctor-contract-api.js +1 -1
  21. package/dist/{group-policy-CU1tEGpS.js → group-policy-BKjMqT-8.js} +161 -260
  22. package/dist/{message-tool-api-CWosHSi5.js → message-tool-api-uNEfQYGY.js} +19 -2
  23. package/dist/message-tool-api.js +1 -1
  24. package/dist/{monitor-DrNCGneD.js → monitor-Chwu_SiZ.js} +3 -3
  25. package/dist/{outbound-adapter-DkT2vUE9.js → outbound-adapter-BB8wAjm5.js} +95 -91
  26. package/dist/{pipeline.runtime-CRRng_Nw.js → pipeline.runtime-BklS_NEj.js} +347 -505
  27. package/dist/{policy-BoP6t88v.js → policy-D7PKkiet.js} +7 -4
  28. package/dist/{probe-4_aHtVT3.js → probe-gj5yQgN2.js} +210 -3
  29. package/dist/{provider-D4nDgxCh.js → provider-Do50wMWn.js} +168 -153
  30. package/dist/{relay-source-CEmze11m.js → relay-source-DWuduiJ2.js} +6 -4
  31. package/dist/{replies-D9XMM_S0.js → replies-Dc6WVh_Q.js} +20 -16
  32. package/dist/{resolve-channels-DSamDgVs.js → resolve-channels-CU7Bg5Ka.js} +6 -10
  33. package/dist/{resolve-users-CIzwbaJR.js → resolve-users-DrhttvRQ.js} +5 -4
  34. package/dist/runtime-api.js +10 -10
  35. package/dist/{scopes-BBrUzpBH.js → scopes-B9HRguDe.js} +1 -1
  36. package/dist/{send-DtjEnlLh.js → send-Djswy6lf.js} +24 -16
  37. package/dist/send-results-eeOu_HYm.js +23 -0
  38. package/dist/{send.runtime-DQeNKa2J.js → send.runtime-__WVTWLQ.js} +1 -1
  39. package/dist/setup-plugin-api.js +1 -1
  40. package/dist/{setup-surface-CsBCM4rP.js → setup-surface-DO0e-MMS.js} +5 -5
  41. package/dist/{shared-D9GeeR9K.js → shared-DEQBEKBL.js} +7 -5
  42. package/dist/{slash-dispatch.runtime-Chr3YHnT.js → slash-dispatch.runtime-C3B4h5ZM.js} +2 -2
  43. package/dist/{target-parsing-BnMD2ZqZ.js → target-parsing-PbxGDbr9.js} +11 -6
  44. package/dist/{targets-Cx5W_n3W.js → targets-7FexXJ62.js} +1 -1
  45. package/dist/{thread-ts-DUGhaYKq.js → thread-ts-CukseRVL.js} +5 -1
  46. package/node_modules/@slack/bolt/node_modules/negotiator/lib/accept.js +54 -0
  47. package/node_modules/@slack/bolt/node_modules/negotiator/lib/charset.js +10 -34
  48. package/node_modules/@slack/bolt/node_modules/negotiator/lib/encoding.js +9 -30
  49. package/node_modules/@slack/bolt/node_modules/negotiator/lib/language.js +15 -36
  50. package/node_modules/@slack/bolt/node_modules/negotiator/lib/mediaType.js +16 -134
  51. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
  52. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/README.md +71 -0
  53. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
  54. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
  55. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
  56. package/node_modules/@slack/bolt/node_modules/negotiator/node_modules/content-type/package.json +52 -0
  57. package/node_modules/@slack/bolt/node_modules/negotiator/package.json +16 -10
  58. package/node_modules/typebox/build/format/email.mjs +1 -1
  59. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  60. package/node_modules/typebox/package.json +1 -1
  61. package/openclaw.plugin.json +15 -7
  62. package/package.json +5 -5
  63. package/dist/action-runtime.runtime-Dpa16J87.js +0 -2
  64. package/dist/slack-client-kind-hSfjXyUM.js +0 -94
  65. package/node_modules/@slack/bolt/node_modules/negotiator/HISTORY.md +0 -114
@@ -1,41 +1,41 @@
1
- import { n as resolveSlackConversationBindingRoute, r as resolveSlackReplyToMode, t as normalizeSlackRouteBindingConfig } from "./conversation-binding-route-DFVLUdYF.js";
2
- import { n as formatSlackTarget } from "./target-parsing-BnMD2ZqZ.js";
3
- import { B as SLACK_TEXT_LIMIT, G as truncateSlackText, K as truncateSlackTextByUtf8Bytes, L as SLACK_EDIT_TEXT_MAX_BYTES, N as buildSlackBlocksFallbackText, O as hasSlackNativeDataBlock, S as resolveSlackReplyRenderPlan, W as countSlackTextUtf8Bytes, Z as SLACK_SESSION_LINK_ACTION_ID, a as qualifySlackConversationId, ct as escapeSlackMrkdwn, d as normalizeSlackAllowOwnerEntry, o as qualifySlackRoutePeerId, ot as normalizeSlackOutboundText, p as resolveSlackAllowListMatch, s as resolveSlackEnterpriseMainDmSessionKey, u as normalizeAllowListLower } from "./group-policy-CU1tEGpS.js";
4
- import { r as formatSlackError } from "./probe-4_aHtVT3.js";
1
+ import { n as resolveSlackConversationBindingRoute, r as resolveSlackReplyToMode, t as normalizeSlackRouteBindingConfig } from "./conversation-binding-route-Vee8Dgbf.js";
2
+ import { n as formatSlackTarget } from "./target-parsing-PbxGDbr9.js";
3
+ import { I as countSlackTextUtf8Bytes, L as truncateSlackText, M as SLACK_TEXT_LIMIT, Q as normalizeSlackOutboundText, R as truncateSlackTextByUtf8Bytes, T as buildSlackBlocksFallbackText, U as SLACK_SESSION_LINK_ACTION_ID, a as qualifySlackConversationId, b as hasSlackNativeDataBlock, et as escapeSlackMrkdwn, k as SLACK_EDIT_TEXT_MAX_BYTES, m as resolveSlackReplyRenderPlan, o as qualifySlackRoutePeerId, s as resolveSlackEnterpriseMainDmSessionKey } from "./group-policy-BKjMqT-8.js";
4
+ import { C as normalizeAllowListLower, E as resolveSlackAllowListMatch, o as formatSlackError, w as normalizeSlackAllowOwnerEntry } from "./probe-gj5yQgN2.js";
5
+ import { n as resolveSlackReplyThreadTs } from "./thread-ts-CukseRVL.js";
5
6
  import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.js";
6
- import { a as editSlackRenderedMessage, b as formatSlackFileReferenceList, d as reactSlackMessage, i as editSlackMessage, m as removeSlackReaction, n as deleteSlackMessage, v as logVerbose$1, x as buildSlackEditTextPayload, y as formatSlackFileReference } from "./actions-XdQyjG_p.js";
7
- import { a as hasSlackTableBlock, c as resolveSlackMessageText$1, o as isSlackUnfurlAttachment, s as resolveSlackBlocksText } from "./slack-client-kind-hSfjXyUM.js";
8
- import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, m as buildSlackNativeDataDeliveryPlan, o as clearSlackThreadFailureNotice, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-DtjEnlLh.js";
9
- import { C as resolveStorePath, E as stripSlackMentionsForCommandDetection, S as resolveChannelResetConfig, _ as resolveSlackChatType, b as readSessionUpdatedAt, c as resolveSlackThreadTargets, d as authorizeSlackBotRoomMessage, f as resolveSlackCommandIngress, g as normalizeSlackChannelType, l as trackSlackDraftMessage, o as authorizeSlackDirectMessage, p as resolveSlackEffectiveAllowFrom, r as resolveSlackRoomContextHints, s as resolveSlackThreadContext, u as resolveConversationLabel$1, v as buildSlackAssistantThreadMetadata, w as updateLastRoute, x as resolveChannelContextVisibilityMode, y as parseSlackAssistantThreadMetadata } from "./provider-D4nDgxCh.js";
10
- import { n as resolveSlackChannelConfig } from "./policy-BoP6t88v.js";
11
- import { a as resolveDeliveredSlackReplyThreadTs, c as emitSlackMessageSentHooks, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as sanitizeSlackMonitorReplyPayload, t as createSlackReplyDeliveryPlan } from "./replies-D9XMM_S0.js";
7
+ import { D as resolveSlackMessageText$1, E as resolveSlackBlocksText, O as buildSlackEditTextPayload, S as formatSlackFileReferenceList, T as isSlackUnfurlAttachment, a as editSlackRenderedMessage, d as reactSlackMessage, i as editSlackMessage, m as removeSlackReaction, n as deleteSlackMessage, v as logVerbose$1, w as hasSlackTableBlock, x as formatSlackFileReference } from "./actions-fh66A6lc.js";
8
+ import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, m as buildSlackNativeDataDeliveryPlan, o as clearSlackThreadFailureNotice, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-Djswy6lf.js";
9
+ import { C as resolveStorePath, E as stripSlackMentionsForCommandDetection, S as resolveChannelResetConfig, _ as resolveSlackChatType, b as readSessionUpdatedAt, c as resolveSlackThreadTargets, d as authorizeSlackBotRoomMessage, f as resolveSlackCommandIngress, g as normalizeSlackChannelType, l as trackSlackDraftMessage, o as authorizeSlackDirectMessage, p as resolveSlackEffectiveAllowFrom, r as resolveSlackRoomContextHints, s as resolveSlackThreadContext, u as resolveConversationLabel$1, v as buildSlackAssistantThreadMetadata, w as updateLastRoute, x as resolveChannelContextVisibilityMode, y as parseSlackAssistantThreadMetadata } from "./provider-Do50wMWn.js";
10
+ import { n as resolveSlackChannelConfig } from "./policy-D7PKkiet.js";
11
+ import { a as resolveSlackThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as sanitizeSlackMonitorReplyPayload, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-Dc6WVh_Q.js";
12
12
  import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
14
- import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressWorkCounter, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
14
+ import { createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createDraftStreamLoop, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
16
16
  import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
17
17
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
18
+ import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
19
+ import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
18
20
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
19
- import { createHash } from "node:crypto";
20
21
  import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
21
22
  import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversation-runtime";
23
+ import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
22
24
  import fs from "node:fs/promises";
23
25
  import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
24
26
  import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
25
- import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
26
27
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
27
28
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
28
29
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
29
30
  import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
30
31
  import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
31
- import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
32
32
  import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
33
33
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
34
- import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
34
+ import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundReplyDispatchCounts, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
35
35
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
36
36
  import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
37
37
  import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
38
- import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
38
+ import { createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
39
39
  import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
40
40
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
41
41
  import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
@@ -93,7 +93,7 @@ async function startSlackStream(params) {
93
93
  try {
94
94
  const result = await streamer.append({
95
95
  ...text ? { markdown_text: text } : {},
96
- ...chunks?.length ? { chunks } : {}
96
+ ...chunks ? { chunks } : {}
97
97
  });
98
98
  if (result) {
99
99
  session.delivered = true;
@@ -121,7 +121,7 @@ async function appendSlackStream(params) {
121
121
  try {
122
122
  const result = await session.streamer.append({
123
123
  ...text ? { markdown_text: text } : {},
124
- ...chunks?.length ? { chunks } : {}
124
+ ...chunks ? { chunks } : {}
125
125
  });
126
126
  if (result) {
127
127
  session.delivered = true;
@@ -269,7 +269,8 @@ function resolveExplicitSlackProgressTitle(entry) {
269
269
  return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
270
270
  }
271
271
  function resolveSlackProgressStyle(entry) {
272
- return entry?.streaming?.progress?.style === "compact" ? "compact" : "card";
272
+ const progress = entry?.streaming?.progress;
273
+ return progress?.style ?? (progress?.toolProgress === false ? "compact" : "card");
273
274
  }
274
275
  function resolveSlackNativeProgressTaskCards(entry) {
275
276
  if (resolveSlackProgressStyle(entry) === "compact") return false;
@@ -479,6 +480,7 @@ function createSlackDraftStream(params) {
479
480
  };
480
481
  const { loop, update, discardPending, seal } = createFinalizableDraftStreamControlsForState({
481
482
  throttleMs,
483
+ coalesceInFlight: true,
482
484
  state: streamState,
483
485
  sendOrEditStreamMessage,
484
486
  emptyValue: "",
@@ -572,64 +574,11 @@ function createSlackDraftStream(params) {
572
574
  };
573
575
  }
574
576
  //#endregion
575
- //#region extensions/slack/src/stream-mode.ts
576
- function resolveSlackStreamingConfig(params) {
577
- return {
578
- mode: resolveSlackStreamingMode(params),
579
- nativeStreaming: resolveSlackNativeStreaming(params)
580
- };
581
- }
582
- function applyAppendOnlyStreamUpdate(params) {
583
- const incoming = params.incoming.trimEnd();
584
- if (!incoming) return {
585
- rendered: params.rendered,
586
- source: params.source,
587
- changed: false
588
- };
589
- if (!params.rendered) return {
590
- rendered: incoming,
591
- source: incoming,
592
- changed: true
593
- };
594
- if (incoming === params.source) return {
595
- rendered: params.rendered,
596
- source: params.source,
597
- changed: false
598
- };
599
- if (incoming.startsWith(params.rendered)) return {
600
- rendered: incoming,
601
- source: incoming,
602
- changed: incoming !== params.rendered
603
- };
604
- if (incoming.startsWith(params.source)) {
605
- const delta = incoming.slice(params.source.length);
606
- return {
607
- rendered: `${params.rendered}${delta}`,
608
- source: incoming,
609
- changed: delta.length > 0
610
- };
611
- }
612
- if (params.source.startsWith(incoming)) return {
613
- rendered: params.rendered,
614
- source: params.source,
615
- changed: false
616
- };
617
- const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
618
- return {
619
- rendered: `${params.rendered}${separator}${incoming}`,
620
- source: incoming,
621
- changed: true
622
- };
623
- }
624
- //#endregion
625
577
  //#region extensions/slack/src/progress-blocks.ts
626
578
  const SLACK_PROGRESS_FIELD_MAX = 1800;
627
579
  const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
628
- const DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS = 48;
629
580
  const SLACK_PROGRESS_CHUNK_TEXT_MAX = 256;
630
581
  const SLACK_PROGRESS_TASK_TITLE_MAX = 120;
631
- const SLACK_PROGRESS_PLAN_FALLBACK_TITLE = "Thinking";
632
- const SLACK_PROGRESS_LINE_DELTA_RE = /(?:^|\s)\+(\d+)\s+[−-](\d+)(?=\s|$)/u;
633
582
  function buildSessionSources(url) {
634
583
  return [{
635
584
  type: "url_source",
@@ -637,12 +586,6 @@ function buildSessionSources(url) {
637
586
  text: "Open in OpenClaw"
638
587
  }];
639
588
  }
640
- function field(text) {
641
- return {
642
- type: "mrkdwn",
643
- text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
644
- };
645
- }
646
589
  function resolveMaxLineChars(value, fallback) {
647
590
  return value && value > 0 ? Math.floor(value) : fallback;
648
591
  }
@@ -661,141 +604,58 @@ function compactTitle(value) {
661
604
  function compactChunkText(value) {
662
605
  return truncateSlackText(value.replace(/\s+/g, " ").trim(), SLACK_PROGRESS_CHUNK_TEXT_MAX);
663
606
  }
664
- function lineDetailParts(line) {
665
- return [line.detail, line.status && line.status !== "completed" && !line.detail?.includes(line.status) ? line.status : void 0].map((part) => part?.trim()).filter((part) => Boolean(part));
666
- }
667
- function legacyLineTitle(line) {
668
- return `${line.icon ?? "•"} *${escapeSlackMrkdwn(line.label)}*`;
669
- }
670
607
  function isAuthoredProgressLine(line) {
671
608
  return line.id === "reasoning" || line.id?.startsWith("commentary:") === true;
672
609
  }
673
- function legacyLineDetail(line, maxChars) {
674
- const detail = lineDetailParts(line).join(" · ");
675
- if (detail) return escapeSlackMrkdwn(compactDetail(detail, maxChars));
676
- if (isAuthoredProgressLine(line)) return normalizeSlackOutboundText(compactDetail(line.text.replace(/^(?:🧠|💬)\s+/u, ""), maxChars));
677
- return "—";
678
- }
679
- function lineTaskTitle(line) {
680
- const label = (line.kind === "command-output" ? line.toolName : void 0) || line.label.replace(/\s+/g, " ").trim() || line.toolName || line.kind || "Update";
681
- const fallback = line.text.replace(/\s+/g, " ").trim();
682
- if (fallback && fallback !== label) return compactTitle(lineDetailParts(line).length > 0 || line.status ? label : fallback);
683
- return compactTitle(label);
684
- }
685
- function lineTaskDetails(line, maxLineChars) {
686
- const detail = line.detail?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
687
- return detail && detail !== line.status?.trim() ? compactDetail(detail, maxLineChars) : void 0;
688
- }
689
- function lineTaskOutput(line) {
690
- const match = line.detail ? SLACK_PROGRESS_LINE_DELTA_RE.exec(line.detail) : null;
691
- if (match) return `+${match[1]} −${match[2]}`;
692
- const status = line.status?.replace(/\s+/g, " ").trim();
693
- return status && lineTaskStatus(line) === "error" ? status : void 0;
694
- }
695
- function lineTaskStatus(line) {
696
- const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
697
- if (!normalized) return "in_progress";
698
- if (normalized === "complete" || normalized === "completed" || normalized === "done" || normalized === "ok" || normalized === "success" || normalized === "succeeded" || normalized === "successful" || normalized === "exit 0") return "complete";
699
- if (normalized === "error" || normalized === "failed" || normalized === "failure" || normalized.startsWith("exit ")) return normalized === "exit 0" ? "complete" : "error";
700
- return "in_progress";
701
- }
702
- function slugTaskIdPart(value) {
703
- return value?.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "task";
704
- }
705
- function stableTaskIdPart(value, slugValue = value) {
706
- const suffix = createHash("sha256").update(value).digest("hex").slice(0, 8);
707
- return `${slugTaskIdPart(slugValue).slice(0, 48)}_${suffix}`;
708
- }
709
- function resolveLineTaskIdentity(line) {
710
- if (line.id?.trim()) return {
711
- id: stableTaskIdPart(line.id),
712
- contentDerived: false
610
+ function resolveProgressAttention(lines) {
611
+ const approval = lines.findLast((line) => line.kind === "approval" && line.status === "requested");
612
+ if (approval) return {
613
+ id: "openclaw_attention",
614
+ title: compactTitle(`Approval required: ${approval.detail || approval.label}`),
615
+ status: "pending"
713
616
  };
617
+ const failure = lines.findLast((line) => {
618
+ const status = line.status?.trim().toLowerCase();
619
+ return status === "error" || status === "failed" || status === "failure" || status?.startsWith("exit ") && status !== "exit 0";
620
+ });
621
+ if (!failure) return;
714
622
  return {
715
- id: stableTaskIdPart([
716
- line.kind,
717
- line.toolName,
718
- line.label,
719
- line.text
720
- ].join("\0"), line.toolName ?? line.kind ?? line.label),
721
- contentDerived: true
623
+ id: "openclaw_attention",
624
+ title: compactTitle([...new Set([
625
+ failure.label,
626
+ failure.detail,
627
+ failure.status
628
+ ].filter(Boolean))].join(" ")),
629
+ status: "error"
722
630
  };
723
631
  }
724
- function buildPlanTasks(params) {
725
- if (params.plan) return params.plan.slice(-50).map((entry, index) => ({
632
+ function buildSlackProgressStreamChunks(params) {
633
+ const title = compactChunkText(params.title?.trim() || params.label?.trim() || "Working");
634
+ if (!params.title && !params.label && !params.lines.length && !params.plan?.length && !params.sessionUrl) return;
635
+ const tasks = params.plan?.length ? params.plan.slice(-50).map((entry, index) => ({
726
636
  id: `plan_step_${index + 1}`,
727
637
  title: compactTitle(entry.step),
728
638
  status: entry.status === "completed" ? "complete" : entry.status
729
- }));
730
- const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_TASK_DETAIL_MAX_CHARS);
731
- const lines = params.lines.slice(-50);
732
- const identities = lines.map(resolveLineTaskIdentity);
733
- const contentIdOccurrences = /* @__PURE__ */ new Map();
734
- return lines.map((line, index) => {
735
- const identity = identities[index];
736
- let id = identity.id;
737
- if (identity.contentDerived) {
738
- const occurrence = (contentIdOccurrences.get(id) ?? 0) + 1;
739
- contentIdOccurrences.set(id, occurrence);
740
- id = `${id}_${occurrence}`;
741
- }
742
- const details = lineTaskDetails(line, maxLineChars);
743
- const output = lineTaskOutput(line);
744
- const task = {
745
- id,
746
- title: lineTaskTitle(line),
747
- status: lineTaskStatus(line)
748
- };
749
- if (details) task.details = details;
750
- if (output) task.output = output;
751
- return task;
752
- });
753
- }
754
- function resolvePlanTitle(params) {
755
- return compactChunkText(params.title?.trim() || params.label?.trim() || (params.tasks.at(-1)?.details ? `${params.tasks.at(-1)?.title} — ${params.tasks.at(-1)?.details}` : params.tasks.at(-1)?.title) || SLACK_PROGRESS_PLAN_FALLBACK_TITLE);
756
- }
757
- function buildSlackProgressStreamChunks(params) {
758
- const tasks = buildPlanTasks({
759
- lines: params.lines,
760
- plan: params.plan,
761
- maxLineChars: params.maxLineChars
762
- });
763
- if (tasks.length === 0) {
764
- const title = params.title?.trim() || params.label?.trim();
765
- if (!title) return;
766
- if (!params.sessionUrl && !params.diffStat) return [{
767
- type: "plan_update",
768
- title: compactChunkText(title)
769
- }];
770
- return [{
771
- type: "plan_update",
772
- title: compactChunkText(title)
773
- }, {
774
- type: "task_update",
775
- id: "openclaw_summary",
776
- title: "Completed",
777
- status: "complete",
778
- ...formatTaskDiffOutput(params.diffStat) ? { output: formatTaskDiffOutput(params.diffStat) } : {},
779
- ...params.sessionUrl ? { sources: buildSessionSources(params.sessionUrl) } : {}
780
- }];
781
- }
782
- const title = resolvePlanTitle({
783
- label: params.label,
784
- title: params.title,
785
- tasks
786
- });
639
+ })) : [{
640
+ id: "openclaw_summary",
641
+ title: compactTitle(title),
642
+ status: "in_progress"
643
+ }];
644
+ let attention = resolveProgressAttention(params.lines);
645
+ if (params.finalInProgressStatus === "error" && !tasks.some((task) => task.status === "in_progress")) attention = {
646
+ id: "openclaw_attention",
647
+ title: attention?.status === "error" ? attention.title : "Failed",
648
+ status: "error"
649
+ };
650
+ if (attention && !(params.finalInProgressStatus && attention.status === "pending")) tasks.push(attention);
787
651
  const finalTaskIndex = tasks.length - 1;
788
- const diffOutput = formatTaskDiffOutput(params.diffStat);
789
652
  const taskChunks = tasks.map((task, index) => {
790
653
  const chunk = {
791
654
  type: "task_update",
792
655
  id: task.id,
793
656
  title: task.title,
794
- status: task.status === "in_progress" ? params.finalInProgressStatus ?? (params.completeInProgress ? "complete" : task.status) : task.status
657
+ status: task.status === "in_progress" ? params.finalInProgressStatus ?? task.status : task.status
795
658
  };
796
- if (task.details) chunk.details = task.details;
797
- if (task.output) chunk.output = task.output;
798
- if (index === finalTaskIndex && diffOutput) chunk.output = [task.output, diffOutput].filter(Boolean).join(" · ");
799
659
  if (index === finalTaskIndex && params.sessionUrl) chunk.sources = buildSessionSources(params.sessionUrl);
800
660
  return chunk;
801
661
  });
@@ -804,113 +664,50 @@ function buildSlackProgressStreamChunks(params) {
804
664
  title
805
665
  }, ...taskChunks];
806
666
  }
807
- function formatDiffStat(diffStat) {
808
- if (!diffStat || diffStat.files === 0 && diffStat.added === 0 && diffStat.removed === 0) return;
809
- return [
810
- `📝 ${diffStat.files} files`,
811
- ...diffStat.added > 0 ? [`+${diffStat.added}`] : [],
812
- ...diffStat.removed > 0 ? [`−${diffStat.removed}`] : []
813
- ].join(" ");
814
- }
815
- function formatTaskDiffOutput(diffStat) {
816
- return diffStat && (diffStat.added > 0 || diffStat.removed > 0) ? `+${diffStat.added} −${diffStat.removed}` : void 0;
817
- }
818
- function buildActivityText(lines, maxLineChars) {
819
- const rendered = [];
820
- let length = 0;
821
- for (const line of lines.slice(-50).toReversed()) {
822
- const row = `${legacyLineTitle(line)} — ${legacyLineDetail(line, maxLineChars)}`;
823
- const nextLength = length + row.length + (rendered.length > 0 ? 1 : 0);
824
- if (nextLength > SLACK_PROGRESS_FIELD_MAX) break;
825
- rendered.push(row);
826
- length = nextLength;
827
- }
828
- return rendered.toReversed().join("\n");
829
- }
830
667
  function buildSlackProgressCardBlocks(params) {
831
668
  const maxLineChars = resolveMaxLineChars(params.maxLineChars, DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS);
832
- const planLines = formatPlanChecklistLines(params.plan ?? [], {
833
- maxLines: 50,
834
- maxLineChars
835
- });
836
- const narration = params.narration?.replace(/\s+/g, " ").trim();
837
- const activityText = buildActivityText(params.lines, maxLineChars);
838
- const diffStat = formatDiffStat(params.diffStat);
839
- const workingFooter = [
840
- ...params.toolCalls && params.toolCalls > 0 ? [`🛠️ ${params.toolCalls} tools`] : [],
841
- ...diffStat ? [diffStat] : [],
842
- ...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
843
- ].join(" · ");
844
- const footer = params.state === "working" ? workingFooter : diffStat;
845
- return [
846
- {
847
- type: "section",
848
- text: field(`${params.state === "working" ? "🔄" : params.state === "success" ? "✅" : "❌"} *${escapeSlackMrkdwn(params.title.trim() || "Working")}*`)
849
- },
850
- ...narration ? [{
851
- type: "section",
852
- text: field(`_${escapeSlackMrkdwn(narration)}_`)
853
- }] : [],
854
- ...planLines.length > 0 ? [{
855
- type: "section",
856
- text: field(planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"))
857
- }] : [],
858
- ...activityText ? [{
859
- type: "section",
860
- text: field(activityText)
861
- }] : [],
862
- ...footer ? [{
863
- type: "context",
864
- elements: [field(footer)]
865
- }] : [],
866
- ...params.state !== "working" && params.sessionUrl ? [{
867
- type: "actions",
868
- elements: [{
869
- type: "button",
870
- action_id: SLACK_SESSION_LINK_ACTION_ID,
871
- text: {
872
- type: "plain_text",
873
- text: "Open in OpenClaw"
874
- },
875
- url: params.sessionUrl
876
- }]
877
- }] : []
878
- ].slice(0, 50);
879
- }
880
- const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
881
- const SLACK_TASK_FIELD_SEPARATOR = " · ";
882
- function resolveTaskFieldDelta(previous, incoming) {
883
- if (!incoming) return {
884
- field: previous,
885
- delta: void 0
669
+ const statusLabels = {
670
+ completed: "Completed",
671
+ in_progress: "In progress",
672
+ pending: "Pending"
886
673
  };
887
- if (previous?.rendered.includes(incoming)) return {
888
- field: {
889
- rendered: previous.rendered,
890
- source: incoming
891
- },
892
- delta: void 0
893
- };
894
- const next = applyAppendOnlyStreamUpdate({
895
- incoming,
896
- rendered: previous?.rendered ?? "",
897
- source: previous?.source ?? "",
898
- separator: SLACK_TASK_FIELD_SEPARATOR
674
+ const planLines = (params.plan ?? []).slice(-50).map((entry) => `${statusLabels[entry.status]}: ${compactDetail(entry.step, maxLineChars)}`);
675
+ const narration = params.narration?.replace(/\s+/g, " ").trim();
676
+ const authoredText = params.lines.filter(isAuthoredProgressLine).map((line) => line.text.trim()).filter((text, index, values) => text && text !== narration && values.indexOf(text) === index).map((text) => normalizeSlackOutboundText(compactDetail(text, maxLineChars))).join("\n");
677
+ const attention = resolveProgressAttention(params.lines);
678
+ const blocks = [
679
+ `${params.state === "success" ? "Completed: " : params.state === "error" ? "Failed: " : ""}*${escapeSlackMrkdwn(params.title.trim() || "Working")}*`,
680
+ narration ? `_${escapeSlackMrkdwn(narration)}_` : "",
681
+ planLines.map((line) => escapeSlackMrkdwn(line)).join("\n"),
682
+ authoredText,
683
+ attention && !(params.state !== "working" && attention.status === "pending") ? escapeSlackMrkdwn(attention.title) : ""
684
+ ].filter(Boolean).map((text) => ({
685
+ type: "section",
686
+ text: {
687
+ type: "mrkdwn",
688
+ text: truncateSlackText(text, SLACK_PROGRESS_FIELD_MAX)
689
+ }
690
+ }));
691
+ if (params.state !== "working" && params.sessionUrl) blocks.push({
692
+ type: "actions",
693
+ elements: [{
694
+ type: "button",
695
+ action_id: SLACK_SESSION_LINK_ACTION_ID,
696
+ text: {
697
+ type: "plain_text",
698
+ text: "Open in OpenClaw"
699
+ },
700
+ url: params.sessionUrl
701
+ }]
899
702
  });
900
- const delta = next.changed ? next.rendered.slice(previous?.rendered.length ?? 0) : void 0;
901
- return {
902
- field: {
903
- rendered: next.rendered,
904
- source: next.source
905
- },
906
- delta
907
- };
703
+ return blocks.slice(0, 50);
908
704
  }
705
+ const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
909
706
  /**
910
707
  * Turns a full task snapshot into the delta Slack must receive. Native streams
911
708
  * key rows by persistent id with no removal chunk: unchanged rows are omitted,
912
- * changed rows carry only their unsent field text, and rows that dropped out
913
- * (plan shrinks, tool-line <-> plan source switches) get a final complete
709
+ * changed titles/statuses replace in place, and rows that dropped out
710
+ * (plan shrinks, summary <-> plan source switches) get a final complete
914
711
  * update or they linger in_progress forever.
915
712
  */
916
713
  function reconcileSlackNativeTaskChunks(params) {
@@ -931,26 +728,20 @@ function reconcileSlackNativeTaskChunks(params) {
931
728
  }
932
729
  const previousRow = params.previous.tasks.get(chunk.id);
933
730
  const status = chunk.status;
934
- const details = resolveTaskFieldDelta(previousRow?.details, chunk.details);
935
- const output = resolveTaskFieldDelta(previousRow?.output, chunk.output);
936
731
  const sourcesChanged = Boolean(chunk.sources) && !previousRow?.sourcesSent;
937
732
  const row = {
938
733
  title: chunk.title,
939
734
  status
940
735
  };
941
- if (details.field) row.details = details.field;
942
- if (output.field) row.output = output.field;
943
736
  if (sourcesChanged || previousRow?.sourcesSent) row.sourcesSent = true;
944
737
  nextTasks.set(chunk.id, row);
945
- if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
738
+ if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || sourcesChanged)) continue;
946
739
  const update = {
947
740
  type: "task_update",
948
741
  id: chunk.id,
949
742
  title: chunk.title,
950
743
  status
951
744
  };
952
- if (details.delta) update.details = details.delta;
953
- if (output.delta) update.output = output.delta;
954
745
  if (sourcesChanged) update.sources = chunk.sources;
955
746
  emitted.push(update);
956
747
  }
@@ -979,40 +770,63 @@ function reconcileSlackNativeTaskChunks(params) {
979
770
  }
980
771
  };
981
772
  }
982
- function buildSlackProgressStreamCompletionChunks(params) {
983
- return buildSlackProgressStreamChunks({
984
- ...params,
985
- completeInProgress: true
986
- });
987
- }
988
773
  //#endregion
989
- //#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
990
- function resolveStructuredProgressLines(lines) {
991
- return lines.map((line) => {
992
- if (typeof line !== "string") return line;
993
- const reasoning = line.startsWith("🧠 ");
994
- const text = line.replace(/^(?:🧠|💬)\s+/u, "").replace(/^_(.*)_$/su, "$1").trim();
774
+ //#region extensions/slack/src/stream-mode.ts
775
+ function resolveSlackStreamingConfig(params) {
776
+ return {
777
+ mode: resolveSlackStreamingMode(params),
778
+ nativeStreaming: resolveSlackNativeStreaming(params)
779
+ };
780
+ }
781
+ function applyAppendOnlyStreamUpdate(params) {
782
+ const incoming = params.incoming.trimEnd();
783
+ if (!incoming) return {
784
+ rendered: params.rendered,
785
+ source: params.source,
786
+ changed: false
787
+ };
788
+ if (!params.rendered) return {
789
+ rendered: incoming,
790
+ source: incoming,
791
+ changed: true
792
+ };
793
+ if (incoming === params.source) return {
794
+ rendered: params.rendered,
795
+ source: params.source,
796
+ changed: false
797
+ };
798
+ if (incoming.startsWith(params.rendered)) return {
799
+ rendered: incoming,
800
+ source: incoming,
801
+ changed: incoming !== params.rendered
802
+ };
803
+ if (incoming.startsWith(params.source)) {
804
+ const delta = incoming.slice(params.source.length);
995
805
  return {
996
- ...reasoning ? { id: "reasoning" } : {},
997
- kind: "item",
998
- text,
999
- label: reasoning ? "Reasoning" : "Update",
1000
- prefix: false
806
+ rendered: `${params.rendered}${delta}`,
807
+ source: incoming,
808
+ changed: delta.length > 0
1001
809
  };
1002
- });
810
+ }
811
+ if (params.source.startsWith(incoming)) return {
812
+ rendered: params.rendered,
813
+ source: params.source,
814
+ changed: false
815
+ };
816
+ const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
817
+ return {
818
+ rendered: `${params.rendered}${separator}${incoming}`,
819
+ source: incoming,
820
+ changed: true
821
+ };
1003
822
  }
1004
- function resolveNativeProgressPlan(snapshot) {
1005
- return snapshot.plan?.length ? snapshot.plan : void 0;
823
+ //#endregion
824
+ //#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
825
+ function resolveStructuredProgressLines(lines) {
826
+ return lines.filter((line) => typeof line !== "string");
1006
827
  }
1007
828
  function resolveNativeProgressLines(snapshot) {
1008
- const lines = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
1009
- if (snapshot.plan?.length || !snapshot.planExplanation) return lines;
1010
- const explanationLine = buildChannelProgressDraftLine({
1011
- event: "plan",
1012
- phase: "update",
1013
- explanation: snapshot.planExplanation
1014
- });
1015
- return explanationLine ? [...lines, explanationLine] : lines;
829
+ return resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id !== "reasoning" && line.id?.startsWith("commentary:") !== true);
1016
830
  }
1017
831
  function resolveNativeProgressNarration(snapshot) {
1018
832
  const paragraphs = resolveStructuredProgressLines(snapshot.lines).filter((line) => line.id === "reasoning" || line.id?.startsWith("commentary:") === true).map((line) => line.text.trim()).filter((text, index, values) => Boolean(text) && values.indexOf(text) === index);
@@ -1021,14 +835,6 @@ function resolveNativeProgressNarration(snapshot) {
1021
835
  function combineProgressHeadlineAndExplanation(headline, explanation) {
1022
836
  return headline && explanation && headline !== explanation ? `${headline} — ${explanation}` : headline ?? explanation;
1023
837
  }
1024
- function buildNativeProgressChunks(params) {
1025
- return buildSlackProgressStreamChunks({
1026
- title: params.title,
1027
- lines: resolveNativeProgressLines(params.snapshot),
1028
- plan: resolveNativeProgressPlan(params.snapshot),
1029
- maxLineChars: params.maxLineChars
1030
- });
1031
- }
1032
838
  //#endregion
1033
839
  //#region extensions/slack/src/monitor/message-handler/preview-finalize.ts
1034
840
  function buildExpectedSlackEditText(params) {
@@ -1066,7 +872,8 @@ function blocksMatch(expected, actual) {
1066
872
  const fallbackBlocks = fallbackPlan.fallbackMessages.flatMap((message) => message.blocks ?? []);
1067
873
  if (JSON.stringify(fallbackBlocks) === JSON.stringify(actual)) return true;
1068
874
  const fallbackText = fallbackPlan.fallbackMessages.map((message) => message.text).filter(Boolean).join("\n\n");
1069
- return normalizeSlackOutboundText(buildSlackBlocksFallbackText(actual)) === normalizeSlackOutboundText(fallbackText);
875
+ const actualText = buildSlackBlocksFallbackText(actual);
876
+ return normalizeSlackOutboundText(actualText) === normalizeSlackOutboundText(fallbackText);
1070
877
  } catch {
1071
878
  return false;
1072
879
  }
@@ -1157,6 +964,7 @@ function createSlackDraftProgressCardRuntime(params) {
1157
964
  return url.toString();
1158
965
  };
1159
966
  const resolveText = (snapshot) => latestFallbackText || formatChannelProgressDraftText({
967
+ presentation: "summary",
1160
968
  entry: account.config,
1161
969
  lines: [...snapshot.lines],
1162
970
  seed: params.progressSeed,
@@ -1173,11 +981,7 @@ function createSlackDraftProgressCardRuntime(params) {
1173
981
  plan: snapshot.plan,
1174
982
  lines: resolveStructuredProgressLines(snapshot.lines),
1175
983
  maxLineChars: params.maxLineChars,
1176
- diffStat: snapshot.diffStat,
1177
- ...state === "working" ? {
1178
- toolCalls: params.progressWorkCounter.toolCalls,
1179
- elapsedSeconds: params.progressWorkCounter.elapsedSeconds
1180
- } : { sessionUrl: resolveSessionUrl() }
984
+ ...state !== "working" ? { sessionUrl: resolveSessionUrl() } : {}
1181
985
  });
1182
986
  };
1183
987
  const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
@@ -1363,7 +1167,6 @@ function createSlackProgressRuntime(runtimeParams) {
1363
1167
  nativeStreamOrder = run.catch(() => void 0);
1364
1168
  return run;
1365
1169
  };
1366
- const progressWorkCounter = createChannelProgressWorkCounter();
1367
1170
  const progressSeed = `${account.accountId}:${message.channel}`;
1368
1171
  const slackProgressStyle = resolveSlackProgressStyle(account.config);
1369
1172
  const useDraftProgressCard = Boolean(draftStream) && isProgressMode && slackProgressStyle === "card";
@@ -1379,7 +1182,6 @@ function createSlackProgressRuntime(runtimeParams) {
1379
1182
  },
1380
1183
  draftStream,
1381
1184
  enabled: useDraftProgressCard,
1382
- progressWorkCounter,
1383
1185
  progressSeed,
1384
1186
  explicitTitle: explicitProgressTitle,
1385
1187
  maxLineChars: progressDraftMaxLineChars,
@@ -1399,12 +1201,17 @@ function createSlackProgressRuntime(runtimeParams) {
1399
1201
  if (isError) nativeProgressTerminalStatus = "error";
1400
1202
  if (!session || nativeProgressCompletionSent) return;
1401
1203
  const chunks = buildNativeProgressCompletionChunks(isError ? "error" : "complete");
1402
- if (!chunks?.length) return;
1204
+ const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(progressDraft.getSnapshot()));
1205
+ if (!chunks?.length && !narrationUpdate.delta) return;
1403
1206
  try {
1404
1207
  await appendSlackStream({
1405
1208
  session,
1406
1209
  chunks
1407
1210
  });
1211
+ if (narrationUpdate.next.changed) {
1212
+ nativeNarrationRenderedText = narrationUpdate.next.rendered;
1213
+ nativeNarrationSourceText = narrationUpdate.next.source;
1214
+ }
1408
1215
  nativeProgressCompletionSent = true;
1409
1216
  delivery.observedReplyDelivery ||= session.delivered;
1410
1217
  } catch (err) {
@@ -1413,11 +1220,6 @@ function createSlackProgressRuntime(runtimeParams) {
1413
1220
  }
1414
1221
  };
1415
1222
  const resolveNativeProgressTitle = (snapshot) => combineProgressHeadlineAndExplanation(explicitProgressTitle ?? snapshot.statusHeadline, snapshot.planExplanation);
1416
- const buildNativeProgressChunks$1 = (snapshot) => buildNativeProgressChunks({
1417
- snapshot,
1418
- title: resolveNativeProgressTitle(snapshot),
1419
- maxLineChars: progressDraftMaxLineChars
1420
- });
1421
1223
  const normalizeProgressText = (text) => text?.replace(/\s+/gu, " ").trim() ?? "";
1422
1224
  const isRenderedAsProgressTitle = (text) => {
1423
1225
  const candidate = normalizeProgressText(text);
@@ -1436,17 +1238,18 @@ function createSlackProgressRuntime(runtimeParams) {
1436
1238
  delta: next.changed ? next.rendered.slice(nativeNarrationRenderedText.length) : ""
1437
1239
  };
1438
1240
  };
1439
- const updateNativeProgressStream = () => withNativeStreamOrder(updateNativeProgressStreamNow);
1440
1241
  const updateNativeProgressStreamNow = async () => {
1441
1242
  const snapshot = progressDraft.getSnapshot();
1442
- const progressLines = resolveNativeProgressLines(snapshot);
1443
1243
  const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
1444
- const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
1445
- if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks && !narrationUpdate.delta) return false;
1244
+ if (!useNativeProgressStreaming || delivery.streamFailed || nativeUpdatesStopped) return false;
1446
1245
  if (!await nativeTransport.waitForStart()) return false;
1447
1246
  const reconciled = reconcileSlackNativeTaskChunks({
1448
1247
  previous: nativeStreamSnapshot,
1449
- chunks: buildNativeProgressChunks$1(snapshot)
1248
+ chunks: buildSlackProgressStreamChunks({
1249
+ title: resolveNativeProgressTitle(snapshot) ?? "Working",
1250
+ lines: resolveNativeProgressLines(snapshot),
1251
+ plan: snapshot.plan
1252
+ })
1450
1253
  });
1451
1254
  const chunks = reconciled.chunks;
1452
1255
  if (!chunks?.length && !narrationUpdate.delta) return false;
@@ -1470,6 +1273,21 @@ function createSlackProgressRuntime(runtimeParams) {
1470
1273
  return false;
1471
1274
  }
1472
1275
  };
1276
+ let nativeUpdatesStopped = false;
1277
+ const nativeUpdates = createDraftStreamLoop({
1278
+ throttleMs: 1e3,
1279
+ coalesceInFlight: true,
1280
+ emptyValue: false,
1281
+ isEmpty: (pending) => !pending,
1282
+ isStopped: () => nativeUpdatesStopped,
1283
+ sendOrEditStreamMessage: () => withNativeStreamOrder(updateNativeProgressStreamNow),
1284
+ onBackgroundFlushError: (err) => runtime.error?.(danger(`slack-stream: progress update failed: ${formatSlackError(err)}`))
1285
+ });
1286
+ const cancelNativeUpdates = async () => {
1287
+ nativeUpdatesStopped = true;
1288
+ nativeUpdates.stop();
1289
+ await nativeUpdates.waitForInFlight();
1290
+ };
1473
1291
  const appendNativeNarration = (payload, kind) => withNativeStreamOrder(() => appendNativeNarrationNow(payload, kind));
1474
1292
  const appendNativeNarrationNow = async (payload, kind) => {
1475
1293
  if (isRenderedAsProgressTitle(payload.text)) return false;
@@ -1489,24 +1307,28 @@ function createSlackProgressRuntime(runtimeParams) {
1489
1307
  return true;
1490
1308
  };
1491
1309
  const resetProgressTurnState = () => {
1492
- progressWorkCounter.reset();
1493
1310
  nativeNarrationRenderedText = "";
1494
1311
  nativeNarrationSourceText = "";
1495
1312
  };
1496
1313
  const progressDraft = createChannelProgressDraftCompositor({
1314
+ presentation: isProgressMode && slackProgressStyle === "card" ? "summary" : void 0,
1497
1315
  entry: account.config,
1498
1316
  mode: slackStreaming.mode,
1499
1317
  active: progressDraftActive,
1500
1318
  seed: progressSeed,
1501
1319
  formatLine: formatSlackProgressDraftLine,
1502
1320
  reasoningLinePrefix: "🧠 ",
1503
- commentaryLinePrefix: "",
1504
1321
  reasoningGate: previewToolProgressEnabled,
1505
- commentaryItalics: true,
1506
- buildProgressEventLine: (input, options) => input.event === "tool" || input.event === "item" || input.event === "command-output" ? buildChannelProgressDraftLineForEntry(account.config, input, options) : buildChannelProgressDraftLine(input, options),
1507
1322
  updateOnLineChange: useNativeProgressStreaming || useDraftProgressCard,
1508
1323
  update: async (previewText, options) => {
1509
- if (useNativeProgressStreaming) return await updateNativeProgressStream();
1324
+ if (useNativeProgressStreaming) {
1325
+ const priorSnapshot = nativeStreamSnapshot;
1326
+ const priorNarration = nativeNarrationRenderedText;
1327
+ nativeUpdates.update(true);
1328
+ if (options?.flush) await nativeUpdates.flush();
1329
+ else await nativeUpdates.waitForInFlight();
1330
+ return priorSnapshot !== nativeStreamSnapshot || priorNarration !== nativeNarrationRenderedText;
1331
+ }
1510
1332
  if (!draftStream) return false;
1511
1333
  const snapshot = progressDraft.getSnapshot();
1512
1334
  progressCard.setFallbackText(previewText);
@@ -1519,7 +1341,11 @@ function createSlackProgressRuntime(runtimeParams) {
1519
1341
  }
1520
1342
  });
1521
1343
  const commentaryProgressEnabled = progressDraft.commentaryProgressEnabled;
1522
- const deliverNativeFinal = (payload, kind) => withNativeStreamOrder(() => deliverNativeFinalNow(payload, kind));
1344
+ const deliverNativeFinal = async (payload, kind) => {
1345
+ progressDraft.markFinalReplyStarted();
1346
+ await cancelNativeUpdates();
1347
+ await withNativeStreamOrder(() => deliverNativeFinalNow(payload, kind));
1348
+ };
1523
1349
  const deliverNativeFinalNow = async (payload, kind) => {
1524
1350
  progressDraft.markFinalReplyStarted();
1525
1351
  const streamReady = await nativeTransport.waitForStart();
@@ -1544,20 +1370,23 @@ function createSlackProgressRuntime(runtimeParams) {
1544
1370
  const snapshot = progressDraft.getSnapshot();
1545
1371
  const lines = resolveNativeProgressLines(snapshot);
1546
1372
  const sessionUrl = progressCard.resolveSessionUrl();
1373
+ const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
1547
1374
  const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
1548
- if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !sessionUrl) return;
1549
- return reconcileSlackNativeTaskChunks({
1375
+ if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !narrationUpdate.delta && !sessionUrl) return;
1376
+ const completion = reconcileSlackNativeTaskChunks({
1550
1377
  previous: nativeStreamSnapshot,
1551
- chunks: buildSlackProgressStreamCompletionChunks({
1378
+ chunks: buildSlackProgressStreamChunks({
1552
1379
  title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
1553
1380
  lines,
1554
- plan: resolveNativeProgressPlan(snapshot),
1555
- maxLineChars: progressDraftMaxLineChars,
1381
+ plan: snapshot.plan,
1556
1382
  finalInProgressStatus,
1557
- diffStat: snapshot.diffStat,
1558
1383
  sessionUrl
1559
1384
  })
1560
1385
  }).chunks;
1386
+ return narrationUpdate.delta ? [{
1387
+ type: "markdown_text",
1388
+ text: narrationUpdate.delta
1389
+ }, ...completion ?? []] : completion;
1561
1390
  };
1562
1391
  const finishNativeProgressTurn = async (completionChunks) => {
1563
1392
  if (delivery.nativeProgressStreamStartPromise) await delivery.nativeProgressStreamStartPromise.catch(() => null);
@@ -1582,6 +1411,7 @@ function createSlackProgressRuntime(runtimeParams) {
1582
1411
  delivery.streamFailed = false;
1583
1412
  };
1584
1413
  const pushPlanProgress = async (steps, explanation) => {
1414
+ if (!previewToolProgressEnabled) return false;
1585
1415
  if (isProgressMode) {
1586
1416
  if (slackProgressStyle === "compact") return false;
1587
1417
  return await progressDraft.pushPlanProgress(steps, { explanation });
@@ -1660,6 +1490,10 @@ function createSlackProgressRuntime(runtimeParams) {
1660
1490
  progressDraft.reset();
1661
1491
  };
1662
1492
  const beginNewProgressTurn = async (options) => {
1493
+ if (useNativeProgressStreaming) {
1494
+ if (!nativeUpdatesStopped && options?.force !== true) return false;
1495
+ await cancelNativeUpdates();
1496
+ }
1663
1497
  const priorSnapshot = progressDraft.getSnapshot();
1664
1498
  const priorFallbackText = progressCard.resolveText(priorSnapshot);
1665
1499
  const completionChunks = useNativeProgressStreaming && !nativeProgressCompletionSent ? buildNativeProgressCompletionChunks(nativeProgressTerminalStatus) : void 0;
@@ -1674,6 +1508,8 @@ function createSlackProgressRuntime(runtimeParams) {
1674
1508
  nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
1675
1509
  nativeProgressCompletionSent = false;
1676
1510
  nativeProgressTerminalStatus = "complete";
1511
+ nativeUpdatesStopped = false;
1512
+ nativeUpdates.resetThrottleWindow();
1677
1513
  progressCard.reset();
1678
1514
  resetPreviewDeliveryState();
1679
1515
  delivery.resetDeliveryTracker();
@@ -1697,7 +1533,14 @@ function createSlackProgressRuntime(runtimeParams) {
1697
1533
  resetDraftDeliveryState();
1698
1534
  resetDraftProgressState();
1699
1535
  };
1700
- const onQueuedFollowupSettled = !useDraftProgressCard ? void 0 : async () => {
1536
+ const onQueuedFollowupSettled = !useDraftProgressCard && !useNativeProgressStreaming ? void 0 : async () => {
1537
+ if (useNativeProgressStreaming) {
1538
+ progressDraft.markFinalReplyStarted();
1539
+ await cancelNativeUpdates();
1540
+ await finishNativeProgressTurn(nativeProgressCompletionSent ? void 0 : buildNativeProgressCompletionChunks(nativeProgressTerminalStatus));
1541
+ progressDraft.markFinalReplyDelivered();
1542
+ return;
1543
+ }
1701
1544
  if (!progressCard.hasTerminalized) await draftStream?.clear();
1702
1545
  await dropDetachedProgressCards();
1703
1546
  };
@@ -1711,7 +1554,10 @@ function createSlackProgressRuntime(runtimeParams) {
1711
1554
  suppressDefaultToolProgressMessages,
1712
1555
  progressDraft,
1713
1556
  commentaryProgressEnabled,
1714
- progressWorkCounter,
1557
+ async cancel() {
1558
+ progressDraft.cancel();
1559
+ await cancelNativeUpdates();
1560
+ },
1715
1561
  get nativeProgressCompletionSent() {
1716
1562
  return nativeProgressCompletionSent;
1717
1563
  },
@@ -1816,24 +1662,17 @@ async function createSlackDispatchSetup(prepared) {
1816
1662
  await reactSlackMessage(message.channel, reactionMessageTs ?? "", emoji, {
1817
1663
  token: ctx.botToken,
1818
1664
  client: slackClient
1819
- }).catch((err) => {
1820
- if (formatErrorMessage(err).includes("already_reacted")) return;
1821
- throw err;
1822
1665
  });
1823
1666
  },
1824
1667
  removeReaction: async (emoji) => {
1825
1668
  await removeSlackReaction(message.channel, reactionMessageTs ?? "", emoji, {
1826
1669
  token: ctx.botToken,
1827
1670
  client: slackClient
1828
- }).catch((err) => {
1829
- if (formatErrorMessage(err).includes("no_reaction")) return;
1830
- throw err;
1831
1671
  });
1832
1672
  }
1833
1673
  },
1834
1674
  initialEmoji: prepared.ackReactionValue || "eyes",
1835
- emojis: void 0,
1836
- timing: DEFAULT_TIMING,
1675
+ presentation: "acknowledgement",
1837
1676
  onError: (err) => {
1838
1677
  logAckFailure({
1839
1678
  log: logVerbose,
@@ -2174,10 +2013,11 @@ function createSlackStreamingDeliveryRuntime(setup) {
2174
2013
  });
2175
2014
  state.observedReplyDelivery = true;
2176
2015
  if (params.kind === "final") state.observedFinalReplyDelivery = true;
2177
- const deliveredThreadTs = resolveDeliveredSlackReplyThreadTs({
2016
+ const deliveredThreadTs = resolveSlackReplyThreadTs({
2178
2017
  replyToMode: replyDeliveryMode,
2179
- payloadReplyToId: params.payload.replyToId,
2180
- replyThreadTs: deliveryReplyThreadTs
2018
+ replyToId: params.payload.replyToId,
2019
+ threadId: deliveryReplyThreadTs,
2020
+ replyToCurrent: params.payload.replyToCurrent
2181
2021
  });
2182
2022
  rememberDeliveredThreadTs(params.kind, deliveredThreadTs);
2183
2023
  replyPlan.markSent();
@@ -2219,6 +2059,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
2219
2059
  }
2220
2060
  const text = params.streamText ?? reply.trimmedText;
2221
2061
  const hookContent = reply.trimmedText;
2062
+ const chunks = params.kind === "final" ? [] : void 0;
2222
2063
  let plannedThreadTs;
2223
2064
  try {
2224
2065
  if (!state.streamSession && state.nativeProgressStreamStartPromise) await state.nativeProgressStreamStartPromise;
@@ -2256,6 +2097,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
2256
2097
  channel: message.channel,
2257
2098
  threadTs: streamThreadTs,
2258
2099
  text,
2100
+ chunks,
2259
2101
  ...params.taskDisplayMode ? { taskDisplayMode: params.taskDisplayMode } : {},
2260
2102
  ...slackIdentity ? { identity: slackIdentity } : {},
2261
2103
  teamId: await resolveSlackStreamRecipientTeamId({
@@ -2294,7 +2136,8 @@ function createSlackStreamingDeliveryRuntime(setup) {
2294
2136
  if (hookContent) recordStreamedDelivery(params.kind, hookContent);
2295
2137
  await appendSlackStream({
2296
2138
  session: state.streamSession,
2297
- text: `${params.appendSeparator === false ? "" : "\n"}${text}`
2139
+ text: `${params.appendSeparator === false ? "" : "\n"}${text}`,
2140
+ chunks
2298
2141
  });
2299
2142
  refreshStreamedAcknowledgements(state.streamSession);
2300
2143
  if (state.streamSession.delivered && state.streamSession.pendingText.length === 0) {
@@ -2459,7 +2302,11 @@ async function dispatchPreparedSlackMessage(prepared) {
2459
2302
  const slackBlocks = replyRenderPlan.mode === "single" ? replyRenderPlan.blocks : replyRenderPlan.blockPart?.blocks;
2460
2303
  const requiresSeparateFallbackDelivery = replyRenderPlan.mode === "split";
2461
2304
  const trimmedFinalText = replyRenderPlan.mode === "single" ? replyRenderPlan.text.trim() : replyRenderPlan.fallbackText.trim();
2462
- const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
2305
+ const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim(), { tableMode: resolveMarkdownTableMode({
2306
+ cfg,
2307
+ channel: "slack",
2308
+ accountId: account.accountId
2309
+ }) });
2463
2310
  const previewFinalTextFitsEdit = countSlackTextUtf8Bytes(previewFinalText) <= SLACK_EDIT_TEXT_MAX_BYTES;
2464
2311
  const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
2465
2312
  let ttsPreviewFinalization;
@@ -2630,10 +2477,10 @@ async function dispatchPreparedSlackMessage(prepared) {
2630
2477
  } : void 0,
2631
2478
  onToolStart: async (payload) => {
2632
2479
  if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
2633
- if (payload.phase === "start") progress.progressWorkCounter.noteToolCall(payload.name);
2634
2480
  return await progress.progressDraft.pushToolEvent(payload);
2635
2481
  },
2636
2482
  onItemEvent: async (payload) => {
2483
+ if (payload.kind === "preamble" && (payload.phase === "start" || payload.phase === "update") && !draftStream?.messageId() && !delivery.streamSession?.delivered) return false;
2637
2484
  if (progress.isProgressMode && payload.kind === "preamble") {
2638
2485
  if (progress.shouldYieldDraftProgress()) return false;
2639
2486
  const headlineVisible = await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
@@ -2672,7 +2519,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2672
2519
  } catch (err) {
2673
2520
  dispatchError = err;
2674
2521
  } finally {
2675
- progress.progressDraft.cancel();
2522
+ await progress.cancel();
2676
2523
  if (!progress.useDraftProgressCard) await draftStream?.discardPending();
2677
2524
  }
2678
2525
  let streamFallbackDelivered = false;
@@ -2705,13 +2552,15 @@ async function dispatchPreparedSlackMessage(prepared) {
2705
2552
  if (pendingFailureNotice && anyReplyDelivered) recordSlackThreadFailureNotice(pendingFailureNotice);
2706
2553
  if (dispatchError || agentRunFailed) await progress.finalizeDraftProgressCard("error");
2707
2554
  await progress.dropDetachedProgressCards();
2708
- if (statusReactionsEnabled) if (dispatchError || agentRunFailed) {
2709
- await statusReactions.setError();
2710
- statusReactions.restoreInitial();
2711
- } else if (anyReplyDelivered) {
2712
- await statusReactions.setDone();
2713
- statusReactions.restoreInitial();
2714
- } else await statusReactions.restoreInitial();
2555
+ if (statusReactionsEnabled) {
2556
+ if (dispatchError || agentRunFailed) {
2557
+ await statusReactions.setError();
2558
+ statusReactions.restoreInitial();
2559
+ } else if (anyReplyDelivered) {
2560
+ await statusReactions.setDone();
2561
+ statusReactions.restoreInitial();
2562
+ } else await statusReactions.restoreInitial();
2563
+ }
2715
2564
  const participationThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2716
2565
  if (anyReplyDelivered && participationThreadTs) recordSlackThreadParticipation(account.accountId, message.channel, participationThreadTs, {
2717
2566
  agentId: route.agentId,
@@ -2770,7 +2619,7 @@ function normalizeSlackAppContextEntities(value) {
2770
2619
  //#endregion
2771
2620
  //#region extensions/slack/src/monitor/thread.ts
2772
2621
  const THREAD_STARTER_CACHE = /* @__PURE__ */ new Map();
2773
- const THREAD_STARTER_CACHE_TTL_MS = 360 * 6e4;
2622
+ const THREAD_STARTER_CACHE_TTL_MS = 216e5;
2774
2623
  const THREAD_STARTER_CACHE_MAX = 2e3;
2775
2624
  function evictThreadStarterCache() {
2776
2625
  const now = asDateTimestampMs(Date.now());
@@ -2965,7 +2814,24 @@ async function discardSlackPreflightMedia(media) {
2965
2814
  const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
2966
2815
  const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
2967
2816
  const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
2968
- const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-XdQyjG_p.js").then((n) => n._));
2817
+ const MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS = 2e3;
2818
+ const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-fh66A6lc.js").then((n) => n._));
2819
+ function formatSlackUnavailableMedia(params) {
2820
+ let fileReferences = params.files?.map((file) => `${formatSlackFileReference(file)} unavailable (${file.reason})`).join(", ");
2821
+ if (fileReferences && fileReferences.length > MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS) fileReferences = `${truncateUtf16Safe(fileReferences, MAX_SLACK_UNAVAILABLE_FILE_TEXT_CHARS)}; … (file references truncated)`;
2822
+ const fileBlock = fileReferences ? `[Slack file: ${fileReferences}]` : void 0;
2823
+ const body = [params.body, fileBlock].filter(Boolean).join("\n");
2824
+ if (params.unavailableMediaCount === 0) return body;
2825
+ const notice = `[slack ${params.unavailableMediaCount > 1 ? `${params.unavailableMediaCount} attachments` : "attachment"} unavailable]`;
2826
+ return params.prependUnavailable ? [
2827
+ notice,
2828
+ fileBlock,
2829
+ params.body
2830
+ ].filter(Boolean).join("\n") : formatInboundMediaUnavailableText({
2831
+ body,
2832
+ notice
2833
+ });
2834
+ }
2969
2835
  function collectUniqueSlackMentionIds$1(texts) {
2970
2836
  const seen = /* @__PURE__ */ new Set();
2971
2837
  const mentionIds = [];
@@ -3016,7 +2882,6 @@ async function resolveSlackMessageContent(params) {
3016
2882
  })) : null;
3017
2883
  const effectiveDirectMedia = attachmentContent?.media.length ? attachmentContent.media : null;
3018
2884
  const mediaPlaceholder = effectiveDirectMedia?.map((item) => item.placeholder).join(" ");
3019
- const fileOnlyFallback = attachmentContent?.files?.map(formatSlackFileReference).join(", ");
3020
2885
  let botAttachmentText;
3021
2886
  if (params.isBotMessage && !attachmentContent?.text) {
3022
2887
  const botAttachmentTextParts = [];
@@ -3040,7 +2905,8 @@ async function resolveSlackMessageContent(params) {
3040
2905
  if (skippedLookups > 0) logVerbose(`slack: skipping ${skippedLookups} mention lookup(s) beyond per-message cap (${SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE})`);
3041
2906
  const { results } = await runTasksWithConcurrency({
3042
2907
  tasks: lookupIds.map((userId) => async () => {
3043
- const renderedName = normalizeOptionalString((await resolveUserName(userId))?.name);
2908
+ const user = await resolveUserName(userId);
2909
+ const renderedName = normalizeOptionalString(user?.name);
3044
2910
  return {
3045
2911
  userId,
3046
2912
  rendered: renderedName ? `<@${userId}> (${renderedName})` : null
@@ -3053,17 +2919,15 @@ async function resolveSlackMessageContent(params) {
3053
2919
  renderedMentions.set(result.userId, result.rendered);
3054
2920
  }
3055
2921
  }
3056
- let rawBody = [
3057
- renderSlackUserMentions(textParts[0], renderedMentions),
3058
- renderSlackUserMentions(textParts[1], renderedMentions),
3059
- renderSlackUserMentions(textParts[2], renderedMentions),
3060
- mediaPlaceholder,
3061
- fileOnlyFallback ? `[Slack file: ${fileOnlyFallback}]` : void 0
3062
- ].filter(Boolean).join("\n") || "";
3063
- const unavailableImageCount = attachmentContent?.unavailableImageCount ?? 0;
3064
- if (unavailableImageCount > 0) rawBody = formatInboundMediaUnavailableText({
3065
- body: rawBody,
3066
- notice: `[slack ${unavailableImageCount > 1 ? `${unavailableImageCount} forwarded images` : "forwarded image"} unavailable]`
2922
+ const rawBody = formatSlackUnavailableMedia({
2923
+ body: [
2924
+ renderSlackUserMentions(textParts[0], renderedMentions),
2925
+ renderSlackUserMentions(textParts[1], renderedMentions),
2926
+ renderSlackUserMentions(textParts[2], renderedMentions),
2927
+ mediaPlaceholder
2928
+ ].filter(Boolean).join("\n") || "",
2929
+ files: attachmentContent?.files,
2930
+ unavailableMediaCount: attachmentContent?.unavailableMediaCount ?? 0
3067
2931
  });
3068
2932
  return rawBody ? {
3069
2933
  rawBody,
@@ -3145,7 +3009,8 @@ async function resolveSlackDmHistoryContext(params) {
3145
3009
  //#endregion
3146
3010
  //#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
3147
3011
  function resolveSlackBaseConversationId(params) {
3148
- return qualifySlackConversationId(params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel, params.eventScope);
3012
+ const raw = params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel;
3013
+ return qualifySlackConversationId(raw, params.eventScope);
3149
3014
  }
3150
3015
  function resolveSlackInitialAgentRoute(params) {
3151
3016
  const route = resolveAgentRoute({
@@ -3296,7 +3161,7 @@ function formatSlackThreadLabelSnippet(text) {
3296
3161
  }
3297
3162
  //#endregion
3298
3163
  //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
3299
- const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-XdQyjG_p.js").then((n) => n._));
3164
+ const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-fh66A6lc.js").then((n) => n._));
3300
3165
  const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
3301
3166
  function resolveSlackThreadSessionFreshness(params) {
3302
3167
  return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
@@ -3408,14 +3273,24 @@ async function resolveSlackThreadContextData(params) {
3408
3273
  const snippet = formatSlackThreadLabelSnippet(starter.text);
3409
3274
  threadLabel = `Slack thread ${params.roomLabel}${snippet ? `: ${snippet}` : ""}`;
3410
3275
  if (shouldSeedInitialThreadContext && !params.effectiveDirectMedia && starter.files && starter.files.length > 0) {
3411
- const { resolveSlackMedia } = await loadSlackMediaModule();
3412
- threadStarterMedia = await resolveSlackMedia({
3276
+ const { resolveSlackAttachmentContent } = await loadSlackMediaModule();
3277
+ const attachmentContent = await resolveSlackAttachmentContent({
3413
3278
  files: starter.files,
3414
3279
  client: params.eventScope?.client ?? params.ctx.app.client,
3415
3280
  token: params.ctx.botToken,
3416
3281
  maxBytes: params.ctx.mediaMaxBytes
3417
3282
  });
3418
- if (threadStarterMedia) logVerbose(`slack: hydrated thread starter file ${threadStarterMedia.map((item) => item.placeholder).join(", ")} from root message`);
3283
+ threadStarterMedia = attachmentContent?.media.length ? attachmentContent.media : null;
3284
+ if (attachmentContent) threadStarterBody = formatSlackUnavailableMedia({
3285
+ body: threadStarterBody,
3286
+ files: attachmentContent.files,
3287
+ unavailableMediaCount: attachmentContent.unavailableMediaCount,
3288
+ prependUnavailable: true
3289
+ });
3290
+ if (threadStarterMedia) {
3291
+ const starterPlaceholders = threadStarterMedia.map((item) => item.placeholder).join(", ");
3292
+ logVerbose(`slack: hydrated thread starter file ${starterPlaceholders} from root message`);
3293
+ }
3419
3294
  }
3420
3295
  } else threadLabel = `Slack thread ${params.roomLabel}`;
3421
3296
  const includeBotStarterAsRootContext = shouldIncludeBotThreadStarterContext({
@@ -3435,14 +3310,20 @@ async function resolveSlackThreadContextData(params) {
3435
3310
  const threadInitialHistoryLimit = params.account.config?.thread?.initialHistoryLimit ?? 20;
3436
3311
  if (threadInitialHistoryLimit > 0 && shouldLoadInitialThreadHistory) {
3437
3312
  const currentBotRootTs = starter?.ts ?? params.threadTs;
3313
+ const threadHistory = await resolveSlackThreadHistory({
3314
+ channelId: params.message.channel,
3315
+ threadTs: params.threadTs,
3316
+ client: params.eventScope?.client ?? params.ctx.app.client,
3317
+ currentMessageTs: params.message.ts,
3318
+ limit: threadInitialHistoryLimit
3319
+ });
3320
+ const enrichedStarter = starter && threadStarterBody && threadStarterBody !== starter.text ? {
3321
+ ...starter,
3322
+ text: threadStarterBody,
3323
+ ts: currentBotRootTs
3324
+ } : null;
3438
3325
  const threadHistoryWithBotRoot = ensureSlackThreadHistoryHasBotRoot({
3439
- history: await resolveSlackThreadHistory({
3440
- channelId: params.message.channel,
3441
- threadTs: params.threadTs,
3442
- client: params.eventScope?.client ?? params.ctx.app.client,
3443
- currentMessageTs: params.message.ts,
3444
- limit: threadInitialHistoryLimit
3445
- }),
3326
+ history: enrichedStarter && !threadHistory.some((entry) => entry.ts === currentBotRootTs) ? [enrichedStarter, ...threadHistory.length >= threadInitialHistoryLimit ? threadHistory.slice(1) : threadHistory] : threadHistory,
3446
3327
  includeBotStarterAsRootContext,
3447
3328
  threadStarter: starter ? {
3448
3329
  ...starter,
@@ -3501,7 +3382,7 @@ async function resolveSlackThreadContextData(params) {
3501
3382
  });
3502
3383
  const role = isCurrentBot || isOtherBot || Boolean(historyMsg.botId) ? "assistant" : "user";
3503
3384
  const msgSenderName = isCurrentBot ? "Bot (this assistant)" : msgUser?.name ?? (historyMsg.botId ? `Bot (${historyMsg.botId})` : "Unknown");
3504
- const msgWithId = `${historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
3385
+ const msgWithId = `${historyMsg.ts === currentBotRootTs && threadStarterBody ? threadStarterBody : historyMsg.text}\n[slack message id: ${historyMsg.ts ?? "unknown"} channel: ${params.message.channel}]`;
3505
3386
  historyParts.push(formatInboundEnvelope({
3506
3387
  channel: "Slack",
3507
3388
  from: `${msgSenderName} (${role})`,
@@ -3528,7 +3409,7 @@ async function resolveSlackThreadContextData(params) {
3528
3409
  //#endregion
3529
3410
  //#region extensions/slack/src/monitor/message-handler/subteam-mentions.ts
3530
3411
  const SUBTEAM_MENTION_RE = /<!subteam\^([A-Z0-9]+)(?:\|[^>]*)?>/gi;
3531
- const SUBTEAM_MEMBER_CACHE_TTL_MS = 300 * 1e3;
3412
+ const SUBTEAM_MEMBER_CACHE_TTL_MS = 3e5;
3532
3413
  const subteamMemberCache = /* @__PURE__ */ new WeakMap();
3533
3414
  function normalizeSlackId(value) {
3534
3415
  return typeof value === "string" && value.trim() ? value.trim().toUpperCase() : void 0;
@@ -3599,7 +3480,7 @@ const SLACK_USER_MENTION_RE = /<@([^>|]+)(?:\|[^>]+)?>/g;
3599
3480
  const SLACK_SUBTEAM_MENTION_RE = /<!subteam\^([^>|]+)(?:\|[^>]+)?>/g;
3600
3481
  const SLACK_SUBTEAM_MENTION_MARKER = "<!subteam^";
3601
3482
  const SLACK_HISTORY_MEDIA_MAX_ATTACHMENTS = 4;
3602
- const SLACK_HISTORY_MEDIA_MAX_BYTES = 10 * 1024 * 1024;
3483
+ const SLACK_HISTORY_MEDIA_MAX_BYTES = 10485760;
3603
3484
  const SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS = 1e3;
3604
3485
  const SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS = 3e3;
3605
3486
  const SLACK_CHANNEL_ACCESS_DOCS_URL = "https://docs.openclaw.ai/channels/slack#access-control-and-routing";
@@ -3733,7 +3614,7 @@ function buildSlackHistoryMediaCandidateMessage(message) {
3733
3614
  async function resolveSlackHistoryMediaForPendingRecord(params) {
3734
3615
  const mediaMessage = buildSlackHistoryMediaCandidateMessage(params.message);
3735
3616
  if (!mediaMessage) return [];
3736
- return await toInboundMediaFactsWithMetadata((await resolveSlackMessageContent({
3617
+ const content = await resolveSlackMessageContent({
3737
3618
  message: mediaMessage,
3738
3619
  isThreadReply: params.isThreadReply,
3739
3620
  threadStarter: params.threadStarter,
@@ -3743,7 +3624,8 @@ async function resolveSlackHistoryMediaForPendingRecord(params) {
3743
3624
  mediaMaxBytes: Math.min(params.ctx.mediaMaxBytes, SLACK_HISTORY_MEDIA_MAX_BYTES),
3744
3625
  mediaReadIdleTimeoutMs: SLACK_HISTORY_MEDIA_IDLE_TIMEOUT_MS,
3745
3626
  mediaTotalTimeoutMs: SLACK_HISTORY_MEDIA_TOTAL_TIMEOUT_MS
3746
- }))?.effectiveDirectMedia, {
3627
+ });
3628
+ return await toInboundMediaFactsWithMetadata(content?.effectiveDirectMedia, {
3747
3629
  kind: "image",
3748
3630
  messageId: params.message.ts
3749
3631
  });
@@ -3839,24 +3721,15 @@ async function resolveSlackConversationContext(params) {
3839
3721
  };
3840
3722
  }
3841
3723
  async function authorizeSlackInboundMessage(params) {
3842
- const { ctx, account, message, conversation } = params;
3724
+ const { ctx, account, message, conversation, drop } = params;
3843
3725
  const { isDirectMessage, channelName, resolvedChannelType, isBotMessage, allowBotsMode } = conversation;
3844
3726
  if (isBotMessage) {
3845
3727
  if (message.user && ctx.botUserId && message.user === ctx.botUserId) return null;
3846
- if (allowBotsMode === "off") {
3847
- logVerbose(`slack: drop bot message ${message.bot_id ?? "unknown"} (allowBots=false)`);
3848
- return null;
3849
- }
3850
- }
3851
- if (isDirectMessage && !message.user) {
3852
- logVerbose("slack: drop dm message (missing user id)");
3853
- return null;
3728
+ if (allowBotsMode === "off") return drop("bot-disabled");
3854
3729
  }
3730
+ if (isDirectMessage && !message.user) return drop("missing-user");
3855
3731
  const senderId = message.user ?? (isBotMessage ? message.bot_id : void 0);
3856
- if (!senderId) {
3857
- logVerbose("slack: drop message (missing sender id)");
3858
- return null;
3859
- }
3732
+ if (!senderId) return drop("missing-sender");
3860
3733
  if (!ctx.isChannelAllowed({
3861
3734
  teamId: params.eventScope?.teamId ?? ctx.teamId,
3862
3735
  channelId: message.channel,
@@ -3866,7 +3739,8 @@ async function authorizeSlackInboundMessage(params) {
3866
3739
  if (conversation.isRoom && ctx.groupPolicy === "allowlist" && params.explicitBotMention && !isBotMessage && message.user) {
3867
3740
  let subject = "This OpenClaw bot";
3868
3741
  if (ctx.botUserId) try {
3869
- const botName = normalizeOptionalString((await ctx.resolveUserName(ctx.botUserId, params.eventScope))?.name);
3742
+ const botIdentity = await ctx.resolveUserName(ctx.botUserId, params.eventScope);
3743
+ const botName = normalizeOptionalString(botIdentity?.name);
3870
3744
  if (botName) subject = escapeSlackMrkdwn(botName);
3871
3745
  } catch (error) {
3872
3746
  logVerbose(`slack allowlist denial bot-name lookup failed: ${formatSlackError(error)}`);
@@ -3883,8 +3757,7 @@ async function authorizeSlackInboundMessage(params) {
3883
3757
  ctx.runtime.error?.(`slack allowlist denial notice failed for channel ${message.channel}: ${formatSlackError(error)}`);
3884
3758
  }
3885
3759
  }
3886
- logVerbose("slack: drop message (channel not allowed)");
3887
- return null;
3760
+ return drop("channel-not-allowed");
3888
3761
  }
3889
3762
  const allowFromLower = await resolveSlackEffectiveAllowFrom(ctx, {
3890
3763
  includePairingStore: isDirectMessage,
@@ -3892,10 +3765,8 @@ async function authorizeSlackInboundMessage(params) {
3892
3765
  });
3893
3766
  if (isDirectMessage) {
3894
3767
  const directUserId = message.user;
3895
- if (!directUserId) {
3896
- logVerbose("slack: drop dm message (missing user id)");
3897
- return null;
3898
- }
3768
+ if (!directUserId) return drop("missing-user");
3769
+ let dropReason = "dm-unauthorized";
3899
3770
  if (!await authorizeSlackDirectMessage({
3900
3771
  ctx,
3901
3772
  accountId: account.accountId,
@@ -3913,13 +3784,13 @@ async function authorizeSlackInboundMessage(params) {
3913
3784
  });
3914
3785
  },
3915
3786
  onDisabled: () => {
3916
- logVerbose("slack: drop dm (dms disabled)");
3787
+ dropReason = "dm-disabled";
3917
3788
  },
3918
3789
  onUnauthorized: ({ allowMatchMeta }) => {
3919
3790
  logVerbose(`Blocked unauthorized slack sender ${message.user} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`);
3920
3791
  },
3921
3792
  log: logVerbose
3922
- })) return null;
3793
+ })) return drop(dropReason);
3923
3794
  }
3924
3795
  return {
3925
3796
  senderId,
@@ -3928,6 +3799,19 @@ async function authorizeSlackInboundMessage(params) {
3928
3799
  }
3929
3800
  async function prepareSlackMessage(params) {
3930
3801
  const { ctx, account, message, opts } = params;
3802
+ const drop = (reason, parentUserId) => {
3803
+ ctx.logger.info({
3804
+ provider: "slack",
3805
+ accountId: account.accountId,
3806
+ teamId: opts.eventScope?.teamId ?? ctx.teamId,
3807
+ channelId: message.channel,
3808
+ messageTs: message.ts,
3809
+ source: opts.source,
3810
+ reason,
3811
+ ...parentUserId ? { parentUserId } : {}
3812
+ }, "Slack inbound event rejected during preparation");
3813
+ return null;
3814
+ };
3931
3815
  const slackClient = opts.eventScope?.client ?? ctx.app.client;
3932
3816
  const threadStarterWorkspaceScope = {
3933
3817
  accountId: account.accountId,
@@ -3951,7 +3835,8 @@ async function prepareSlackMessage(params) {
3951
3835
  conversation,
3952
3836
  explicitBotMention: opts.source === "app_mention" || Boolean(normalizedBotUserId && mentionMetadata.mentionedUserIds.includes(normalizedBotUserId)),
3953
3837
  eventScope: opts.eventScope,
3954
- onVisibleDrop: opts.onVisibleDrop
3838
+ onVisibleDrop: opts.onVisibleDrop,
3839
+ drop
3955
3840
  });
3956
3841
  if (!authorization) return null;
3957
3842
  const { senderId, allowFromLower } = authorization;
@@ -3959,7 +3844,8 @@ async function prepareSlackMessage(params) {
3959
3844
  const resolveSenderName = async () => {
3960
3845
  if (resolvedSenderName) return resolvedSenderName;
3961
3846
  if (message.user) {
3962
- const normalized = normalizeOptionalString((await ctx.resolveUserName(message.user, opts.eventScope))?.name);
3847
+ const sender = await ctx.resolveUserName(message.user, opts.eventScope);
3848
+ const normalized = normalizeOptionalString(sender?.name);
3963
3849
  if (normalized) {
3964
3850
  resolvedSenderName = normalized;
3965
3851
  return resolvedSenderName;
@@ -4055,13 +3941,7 @@ async function prepareSlackMessage(params) {
4055
3941
  if (shouldLogVerbose()) logVerbose(`slack: using configured ACP binding for ${configuredBinding.record.conversation.conversationId} -> ${configuredBindingSessionKey}`);
4056
3942
  } else {
4057
3943
  if (shouldLogVerbose()) logVerbose(`slack: configured ACP binding unavailable for ${configuredBinding.record.conversation.conversationId}: ${ensured.error}`);
4058
- logInboundDrop({
4059
- log: logVerbose,
4060
- channel: "slack",
4061
- reason: "configured ACP binding unavailable",
4062
- target: configuredBinding.record.conversation.conversationId
4063
- });
4064
- return null;
3944
+ return drop("configured-binding-unavailable");
4065
3945
  }
4066
3946
  }
4067
3947
  const senderNameForAuthPromise = ctx.allowNameMatching ? resolveSenderName().then((name) => ({
@@ -4176,14 +4056,7 @@ async function prepareSlackMessage(params) {
4176
4056
  channel: "slack",
4177
4057
  accountId: account.accountId
4178
4058
  });
4179
- if (message["_ambiguousThreadReply"]) {
4180
- ctx.logger.info({
4181
- channel: message.channel,
4182
- ts: message.ts,
4183
- parentUserId: message.parent_user_id
4184
- }, "skipping ambiguous slack thread reply");
4185
- return null;
4186
- }
4059
+ if (message["_ambiguousThreadReply"]) return drop("ambiguous-thread", message.parent_user_id);
4187
4060
  let canDetectMention = Boolean(ctx.botUserId) || mentionRegexes.length > 0;
4188
4061
  const textForCommandDetection = stripSlackMentionsForCommandDetection(message.text ?? "");
4189
4062
  const hasControlCommandInMessage = hasControlCommand(textForCommandDetection, cfg);
@@ -4216,10 +4089,7 @@ async function prepareSlackMessage(params) {
4216
4089
  });
4217
4090
  let messageIngress = await resolveMessageIngress();
4218
4091
  const senderGate = messageIngress.senderAccess.gate;
4219
- if (isRoomish && senderGate?.allowed === false) {
4220
- logVerbose(`Blocked unauthorized slack sender ${senderId} (not in sender allowlist)`);
4221
- return null;
4222
- }
4092
+ if (isRoomish && senderGate?.allowed === false) return drop("unauthorized-sender");
4223
4093
  if (isRoom && isBotMessage && allowBotsMode !== "off" && !await authorizeSlackBotRoomMessage({
4224
4094
  ctx,
4225
4095
  channelId: message.channel,
@@ -4228,7 +4098,7 @@ async function prepareSlackMessage(params) {
4228
4098
  channelUsers: channelConfig?.users,
4229
4099
  allowFromLower,
4230
4100
  eventScope: opts.eventScope
4231
- })) return null;
4101
+ })) return drop("unauthorized-bot");
4232
4102
  const threadContextAllowFromLower = isRoom ? channelUsersAllowlistConfigured ? normalizeAllowListLower(channelConfig?.users) : [] : isDirectMessage ? allowFromLower : [];
4233
4103
  const contextVisibilityMode = resolveChannelContextVisibilityMode({
4234
4104
  cfg: ctx.cfg,
@@ -4248,15 +4118,7 @@ async function prepareSlackMessage(params) {
4248
4118
  explicitKind: true
4249
4119
  });
4250
4120
  const commandAuthorized = messageIngress.commandAccess.authorized;
4251
- if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) {
4252
- logInboundDrop({
4253
- log: logVerbose,
4254
- channel: "slack",
4255
- reason: "control command (unauthorized)",
4256
- target: senderId
4257
- });
4258
- return null;
4259
- }
4121
+ if (isRoomish && messageIngress.commandAccess.shouldBlockControlCommand) return drop("control-command-unauthorized");
4260
4122
  const canSeedMentionedRoomThread = !seedTopLevelRoomThreadBySource && isRoom && !routing.isThreadReply && !hasBoundSession;
4261
4123
  let seededMentionRouting;
4262
4124
  const getSeededMentionRouting = () => {
@@ -4351,37 +4213,20 @@ async function prepareSlackMessage(params) {
4351
4213
  wasMentioned
4352
4214
  });
4353
4215
  if (isBotMessage && allowBotsMode === "mentions") {
4354
- if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) {
4355
- logVerbose("slack: drop bot message (allowBots=mentions, missing mention)");
4356
- return null;
4357
- }
4216
+ if (!(isDirectMessage || effectiveWasMentioned || shouldBypassMention)) return drop("bot-missing-mention");
4358
4217
  }
4359
4218
  if (isRoom && shouldRequireMention && !canDetectMention && !effectiveWasMentioned) {
4360
- ctx.logger.info({
4361
- channel: message.channel,
4362
- reason: "mention-detection-unavailable"
4363
- }, "skipping channel message");
4364
4219
  await recordDroppedHistory("slack-mention-detection-unavailable");
4365
- return null;
4220
+ return drop("mention-detection-unavailable");
4366
4221
  }
4367
4222
  const ignoreOtherMentions = channelConfig?.ignoreOtherMentions ?? false;
4368
4223
  if (isRoom && ignoreOtherMentions && Boolean(ctx.botUserId) && hasAnyMention && !wasMentioned) {
4369
- logInboundDrop({
4370
- log: logVerbose,
4371
- channel: "slack",
4372
- reason: "other-mention",
4373
- target: senderId
4374
- });
4375
4224
  await recordDroppedHistory("slack-other-mention");
4376
- return null;
4225
+ return drop("other-mention");
4377
4226
  }
4378
4227
  if (isRoom && shouldRequireMention && mentionDecision.shouldSkip) {
4379
- ctx.logger.info({
4380
- channel: message.channel,
4381
- reason: "no-mention"
4382
- }, "skipping channel message");
4383
4228
  await recordDroppedHistory("slack-no-mention");
4384
- return null;
4229
+ return drop("missing-mention");
4385
4230
  }
4386
4231
  const chatType = resolveSlackChatType(conversation.resolvedChannelType);
4387
4232
  const inboundEventKind = classifyChannelInboundEvent({
@@ -4396,7 +4241,7 @@ async function prepareSlackMessage(params) {
4396
4241
  });
4397
4242
  const threadStarter = await getThreadStarter();
4398
4243
  const resolvedMessageContent = await getMessageContent();
4399
- if (!resolvedMessageContent) return null;
4244
+ if (!resolvedMessageContent) return drop("empty-content");
4400
4245
  const { rawBody, effectiveDirectMedia } = resolvedMessageContent;
4401
4246
  const bodyForAgent = preflightAudioTranscript ? formatSlackAudioTranscriptForAgent({
4402
4247
  transcript: preflightAudioTranscript,
@@ -4559,10 +4404,7 @@ async function prepareSlackMessage(params) {
4559
4404
  messageId: threadContext.messageTs,
4560
4405
  inboundEventKind
4561
4406
  }, boundMessageThreadId);
4562
- if (messageIngress.ingress.admission !== "dispatch") {
4563
- logVerbose(`Blocked slack sender ${senderId} after final route binding`);
4564
- return null;
4565
- }
4407
+ if (messageIngress.ingress.admission !== "dispatch") return drop("final-route-denied");
4566
4408
  const agentContextEntities = isAgentViewMessage ? normalizeSlackAppContextEntities(message.app_context) : [];
4567
4409
  const ctxPayload = (ctx.buildContext ?? buildChannelInboundEventContext)({
4568
4410
  channelIngress: messageIngress,
@@ -4647,6 +4489,7 @@ async function prepareSlackMessage(params) {
4647
4489
  payload: { entities: agentContextEntities }
4648
4490
  }] : void 0,
4649
4491
  TransportThreadId: directThreadRoutedToDmSession ? threadContext.messageThreadId : void 0,
4492
+ ReplyThreading: isThreadReply ? { implicitCurrentMessage: "deny" } : void 0,
4650
4493
  SlackAssistantThread: assistantThreadContext ? true : void 0,
4651
4494
  SlackAgentThread: agentViewThreadTs ? true : void 0,
4652
4495
  SlackAssistantThreadContextChannelId: assistantThreadContext?.channelId,
@@ -4682,7 +4525,6 @@ async function prepareSlackMessage(params) {
4682
4525
  normalizeEntry: normalizeSlackAllowOwnerEntry
4683
4526
  }) : null;
4684
4527
  const replyTarget = `channel:${message.channel}`;
4685
- if (!replyTarget) return null;
4686
4528
  if (preflightAudioTranscript) await sendSlackPreflightAudioTranscriptEcho({
4687
4529
  transcript: preflightAudioTranscript,
4688
4530
  cfg,