@openclaw/slack 2026.8.1-beta.2 → 2026.8.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/{account-inspect-Drw2gAiN.js → account-inspect-YJvFBbCR.js} +1 -1
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-CK4AVI2_.js → accounts-BlzxuYle.js} +5 -1
  4. package/dist/{accounts.runtime-Cb7QwWBz.js → accounts.runtime-CbabBL8x.js} +1 -1
  5. package/dist/{action-runtime-BkGe7ny_.js → action-runtime-D6jg2CQd.js} +64 -68
  6. package/dist/action-runtime.runtime-JtoOK9l5.js +2 -0
  7. package/dist/{actions-BTDXwwE3.js → actions-DFk0ANVB.js} +52 -212
  8. package/dist/{actions.runtime-DLFyUkvg.js → actions.runtime-CYLsYRai.js} +1 -1
  9. package/dist/api.js +14 -14
  10. package/dist/{approval-handler.runtime-CJaNzAq2.js → approval-handler.runtime-BNvAoKdK.js} +4 -2
  11. package/dist/{channel-D_tovcUL.js → channel-CA5w7qDF.js} +78 -39
  12. package/dist/channel-config-api.js +1 -1
  13. package/dist/channel-plugin-api.js +1 -1
  14. package/dist/{channel-type-B9MSdoGN.js → channel-type-CZWv55Wu.js} +3 -7
  15. package/dist/{channel.setup-Bch9dYmA.js → channel.setup-CpyYsEXH.js} +4 -4
  16. package/dist/{config-schema-xto5jFIx.js → config-schema-CN3XZRX5.js} +23 -7
  17. package/dist/conversation-binding-route-DFVLUdYF.js +115 -0
  18. package/dist/{directory-config-CNBz8lJa.js → directory-config-BGYyuzWh.js} +1 -1
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-CFKUtW9E.js → directory-live-DjUJQLbq.js} +2 -2
  21. package/dist/{group-policy-c4RP9bjc.js → group-policy-DyfB_ZIE.js} +38 -24
  22. package/dist/{message-tool-api-BykQB4AU.js → message-tool-api-CqMr5Rmc.js} +2 -2
  23. package/dist/message-tool-api.js +1 -1
  24. package/dist/{monitor-1nF1NtqT.js → monitor-Chrecrb9.js} +3 -3
  25. package/dist/{outbound-adapter-S81tsswN.js → outbound-adapter-DuRqAQXK.js} +18 -8
  26. package/dist/{pipeline.runtime-BvYsM5JO.js → pipeline.runtime-Bbfd5VFW.js} +376 -324
  27. package/dist/{policy-TapwbeaT.js → policy-BKXSxrIS.js} +1 -1
  28. package/dist/{probe-HoEg_WxB.js → probe-BVA65leh.js} +1 -1
  29. package/dist/{provider-CDNvBXUi.js → provider-CUUiwsg2.js} +65 -44
  30. package/dist/{relay-source-C9leF11u.js → relay-source-mhpMMRG-.js} +1 -1
  31. package/dist/{replies-CeOfv82I.js → replies-SnWyXcsL.js} +36 -25
  32. package/dist/{resolve-channels-CNPrxaXY.js → resolve-channels-BSpqC8jU.js} +1 -1
  33. package/dist/{resolve-users-Dzomuwnr.js → resolve-users-Dvz9pDe0.js} +1 -1
  34. package/dist/{runtime-api-Cj9nxBNv.js → runtime-api-DBi7xc_g.js} +2 -2
  35. package/dist/runtime-api.js +11 -11
  36. package/dist/{scopes-cYXKkrRD.js → scopes-BSk4GZvZ.js} +1 -1
  37. package/dist/security-contract-api.js +1 -1
  38. package/dist/{send-Nor0iBwY.js → send-CtnOxKCc.js} +58 -78
  39. package/dist/{send.runtime-BmgNe6S9.js → send.runtime-kYctMHSd.js} +1 -1
  40. package/dist/setup-plugin-api.js +1 -1
  41. package/dist/{setup-surface-CbX1-Knh.js → setup-surface-BPFfp6So.js} +6 -6
  42. package/dist/{shared-zdT5AMp-.js → shared-DJBaslN7.js} +13 -7
  43. package/dist/{block-text-YtiNkqmx.js → slack-client-kind-BXyOOICI.js} +17 -2
  44. package/dist/{slash-dispatch.runtime-Bnr9GR1S.js → slash-dispatch.runtime-Cf2FTrnf.js} +2 -2
  45. package/openclaw.plugin.json +43 -1
  46. package/package.json +4 -6
  47. package/dist/account-reply-mode-DrUXogvO.js +0 -14
  48. package/dist/action-runtime.runtime-D9I6uc0Q.js +0 -2
  49. package/node_modules/p-map/index.d.ts +0 -155
  50. package/node_modules/p-map/index.js +0 -285
  51. package/node_modules/p-map/license +0 -9
  52. package/node_modules/p-map/package.json +0 -57
  53. package/node_modules/p-map/readme.md +0 -190
  54. package/dist/{security-audit-DR097212.js → security-audit-B_yKr7MW.js} +1 -1
@@ -1,35 +1,36 @@
1
- import { t as resolveSlackReplyToMode } from "./account-reply-mode-DrUXogvO.js";
2
- import { a as parseSlackTarget, n as formatSlackTarget } from "./target-parsing-BnMD2ZqZ.js";
3
- import "./targets-Cx5W_n3W.js";
4
- import { B as SLACK_TEXT_LIMIT, G as truncateSlackText, K as truncateSlackTextByUtf8Bytes, L as SLACK_EDIT_TEXT_MAX_BYTES, M as normalizeSlackOutboundText, N as buildSlackBlocksFallbackText, T as hasSlackNativeDataBlock, W as countSlackTextUtf8Bytes, Z as SLACK_SESSION_LINK_ACTION_ID, at as escapeSlackMrkdwn, c as normalizeSlackAllowOwnerEntry, s as normalizeAllowListLower, u as resolveSlackAllowListMatch, y as resolveSlackReplyRenderPlan } from "./group-policy-c4RP9bjc.js";
5
- import { r as formatSlackError } from "./probe-HoEg_WxB.js";
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_EDIT_TEXT_MAX_BYTES, F as normalizeSlackOutboundText, I as buildSlackBlocksFallbackText, J as truncateSlackText, O as hasSlackNativeDataBlock, S as resolveSlackReplyRenderPlan, U as SLACK_TEXT_LIMIT, Y as truncateSlackTextByUtf8Bytes, a as qualifySlackConversationId, ct as escapeSlackMrkdwn, d as normalizeSlackAllowOwnerEntry, et as SLACK_SESSION_LINK_ACTION_ID, o as qualifySlackRoutePeerId, p as resolveSlackAllowListMatch, q as countSlackTextUtf8Bytes, s as resolveSlackEnterpriseMainDmSessionKey, u as normalizeAllowListLower } from "./group-policy-DyfB_ZIE.js";
4
+ import { r as formatSlackError } from "./probe-BVA65leh.js";
6
5
  import { n as resolveSlackStreamingMode, t as resolveSlackNativeStreaming } from "./streaming-compat-B-e0mqM0.js";
7
- import { b as formatSlackFileReference, i as editSlackRenderedMessage, p as removeSlackReaction, r as editSlackMessage, t as deleteSlackMessage, u as reactSlackMessage, v as resolveSlackThreadHistory, x as buildSlackEditTextPayload, y as resolveSlackThreadStarter } from "./actions-BTDXwwE3.js";
8
- import { a as resolveSlackMessageText } from "./block-text-YtiNkqmx.js";
9
- import { c as hasSlackThreadFailureNotice, d as recordSlackThreadFailureNotice, f as recordSlackThreadParticipation, l as hasSlackThreadParticipation, o as clearSlackThreadFailureNotice, p as buildSlackNativeDataDeliveryPlan, r as sendMessageSlack, u as hasSlackThreadParticipationWithPersistence } from "./send-Nor0iBwY.js";
10
- import { C as resolveChannelResetConfig, D as stripSlackMentionsForCommandDetection, S as resolveChannelContextVisibilityMode, T as updateLastRoute, _ as normalizeSlackChannelType, b as parseSlackAssistantThreadMetadata, c as resolveConversationLabel$1, d as resolveSlackEffectiveAllowFrom, g as resolveSlackEnterpriseMainDmSessionKey, h as qualifySlackRoutePeerId, l as authorizeSlackBotRoomMessage, m as qualifySlackConversationId, o as authorizeSlackDirectMessage, r as resolveSlackRoomContextHints, s as trackSlackDraftMessage, u as resolveSlackCommandIngress, v as resolveSlackChatType, w as resolveStorePath, x as readSessionUpdatedAt, y as buildSlackAssistantThreadMetadata } from "./provider-CDNvBXUi.js";
11
- import { n as resolveSlackChannelConfig } from "./policy-TapwbeaT.js";
12
- import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-CeOfv82I.js";
6
+ import { b as formatSlackFileReferenceList, i as editSlackRenderedMessage, p as removeSlackReaction, r as editSlackMessage, t as deleteSlackMessage, u as reactSlackMessage, v as logVerbose$1, x as buildSlackEditTextPayload, y as formatSlackFileReference } from "./actions-DFk0ANVB.js";
7
+ import { a as hasSlackTableBlock, c as resolveSlackMessageText$1, o as isSlackUnfurlAttachment, s as resolveSlackBlocksText } from "./slack-client-kind-BXyOOICI.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-CtnOxKCc.js";
9
+ import { S as updateLastRoute, _ as parseSlackAssistantThreadMetadata, b as resolveChannelResetConfig, c as resolveConversationLabel$1, d as resolveSlackEffectiveAllowFrom, g as buildSlackAssistantThreadMetadata, h as resolveSlackChatType, l as authorizeSlackBotRoomMessage, m as normalizeSlackChannelType, o as authorizeSlackDirectMessage, r as resolveSlackRoomContextHints, s as trackSlackDraftMessage, u as resolveSlackCommandIngress, v as readSessionUpdatedAt, w as stripSlackMentionsForCommandDetection, x as resolveStorePath, y as resolveChannelContextVisibilityMode } from "./provider-CUUiwsg2.js";
10
+ import { n as resolveSlackChannelConfig } from "./policy-BKXSxrIS.js";
11
+ import { a as resolveDeliveredSlackReplyThreadTs, i as readSlackReplyBlocks, n as deliverReplies, o as resolveSlackThreadTs, s as emitSlackMessageSentHooks, t as createSlackReplyDeliveryPlan } from "./replies-SnWyXcsL.js";
13
12
  import { asOptionalRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
14
13
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
15
- import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftCompositor, createChannelProgressReceiptTracker, createFinalizableDraftStreamControlsForState, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, formatPlanChecklistLines, isChannelProgressDraftWorkToolName, resolveAgentOutboundIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftConfig, resolveChannelProgressDraftMaxLineChars, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages } from "openclaw/plugin-sdk/channel-outbound";
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";
16
15
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
17
16
  import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
18
17
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
19
18
  import { createHash } from "node:crypto";
19
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
20
+ import { ensureConfiguredBindingRouteReady } from "openclaw/plugin-sdk/conversation-runtime";
20
21
  import fs from "node:fs/promises";
22
+ import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
21
23
  import { formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
22
- import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
23
24
  import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
24
25
  import { filterSupplementalContextItems, resolvePinnedMainDmOwnerFromAllowlist, shouldIncludeSupplementalContext } from "openclaw/plugin-sdk/security-runtime";
25
26
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
26
- import { ensureConfiguredBindingRouteReady, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute } from "openclaw/plugin-sdk/conversation-runtime";
27
27
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
28
28
  import { mergePairLoopGuardConfig } from "openclaw/plugin-sdk/pair-loop-guard-runtime";
29
29
  import { enqueueRoutedSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
30
30
  import { resolveChannelImplicitMentions } from "openclaw/plugin-sdk/channel-ingress-runtime";
31
+ import { asDateTimestampMs, asFiniteNumberInRange, parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
31
32
  import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
32
- import { buildChannelInboundEventContext, buildMentionRegexes, classifyChannelInboundEvent, dispatchChannelInboundTurn, formatInboundEnvelope, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, implicitMentionKindWhen, logInboundDrop, matchesMentionWithExplicit, readAgentRunTerminalOutcome, recordDroppedChannelInboundHistory, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, resolveUnmentionedGroupInboundPolicy, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
33
+ 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";
33
34
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
34
35
  import { resolveGatewayPublicOrigin } from "openclaw/plugin-sdk/config-contracts";
35
36
  import { buildControlUiSessionPath } from "openclaw/plugin-sdk/session-discussion";
@@ -37,8 +38,7 @@ import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypin
37
38
  import { hasControlCommand } from "openclaw/plugin-sdk/command-detection";
38
39
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
39
40
  import { shouldHandleTextCommands } from "openclaw/plugin-sdk/command-surface";
40
- import { createChannelPreflightAudio } from "openclaw/plugin-sdk/media-understanding-runtime";
41
- import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
41
+ import { createChannelPreflightAudio, formatAudioTranscriptForAgent } from "openclaw/plugin-sdk/media-understanding-runtime";
42
42
  //#region extensions/slack/src/streaming.ts
43
43
  /**
44
44
  * Thrown when Slack definitively rejects a stream flush/finalize while text
@@ -267,7 +267,11 @@ function resolveExplicitSlackProgressTitle(entry) {
267
267
  const trimmed = label.trim();
268
268
  return trimmed && trimmed.toLowerCase() !== "auto" ? trimmed : void 0;
269
269
  }
270
+ function resolveSlackProgressStyle(entry) {
271
+ return entry?.streaming?.progress?.style === "compact" ? "compact" : "card";
272
+ }
270
273
  function resolveSlackNativeProgressTaskCards(entry) {
274
+ if (resolveSlackProgressStyle(entry) === "compact") return false;
271
275
  const streaming = entry?.streaming;
272
276
  if (!streaming || typeof streaming !== "object" || Array.isArray(streaming)) return true;
273
277
  const progressConfig = streaming.progress;
@@ -567,6 +571,56 @@ function createSlackDraftStream(params) {
567
571
  };
568
572
  }
569
573
  //#endregion
574
+ //#region extensions/slack/src/stream-mode.ts
575
+ function resolveSlackStreamingConfig(params) {
576
+ return {
577
+ mode: resolveSlackStreamingMode(params),
578
+ nativeStreaming: resolveSlackNativeStreaming(params)
579
+ };
580
+ }
581
+ function applyAppendOnlyStreamUpdate(params) {
582
+ const incoming = params.incoming.trimEnd();
583
+ if (!incoming) return {
584
+ rendered: params.rendered,
585
+ source: params.source,
586
+ changed: false
587
+ };
588
+ if (!params.rendered) return {
589
+ rendered: incoming,
590
+ source: incoming,
591
+ changed: true
592
+ };
593
+ if (incoming === params.source) return {
594
+ rendered: params.rendered,
595
+ source: params.source,
596
+ changed: false
597
+ };
598
+ if (incoming.startsWith(params.rendered)) return {
599
+ rendered: incoming,
600
+ source: incoming,
601
+ changed: incoming !== params.rendered
602
+ };
603
+ if (incoming.startsWith(params.source)) {
604
+ const delta = incoming.slice(params.source.length);
605
+ return {
606
+ rendered: `${params.rendered}${delta}`,
607
+ source: incoming,
608
+ changed: delta.length > 0
609
+ };
610
+ }
611
+ if (params.source.startsWith(incoming)) return {
612
+ rendered: params.rendered,
613
+ source: params.source,
614
+ changed: false
615
+ };
616
+ const separator = params.separator ?? (params.rendered.endsWith("\n") ? "" : "\n");
617
+ return {
618
+ rendered: `${params.rendered}${separator}${incoming}`,
619
+ source: incoming,
620
+ changed: true
621
+ };
622
+ }
623
+ //#endregion
570
624
  //#region extensions/slack/src/progress-blocks.ts
571
625
  const SLACK_PROGRESS_FIELD_MAX = 1800;
572
626
  const DEFAULT_SLACK_PROGRESS_DETAIL_MAX_CHARS = 120;
@@ -628,12 +682,14 @@ function lineTaskTitle(line) {
628
682
  return compactTitle(label);
629
683
  }
630
684
  function lineTaskDetails(line, maxLineChars) {
631
- const detail = (lineDetailParts(line).join(" · ") || line.status?.trim())?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
632
- return detail ? compactDetail(detail, maxLineChars) : void 0;
685
+ const detail = line.detail?.replace(SLACK_PROGRESS_LINE_DELTA_RE, "").replace(/\s+·\s*$/u, "").trim();
686
+ return detail && detail !== line.status?.trim() ? compactDetail(detail, maxLineChars) : void 0;
633
687
  }
634
688
  function lineTaskOutput(line) {
635
- const match = SLACK_PROGRESS_LINE_DELTA_RE.exec(lineDetailParts(line).join(" · "));
636
- return match ? `+${match[1]} −${match[2]}` : void 0;
689
+ const match = line.detail ? SLACK_PROGRESS_LINE_DELTA_RE.exec(line.detail) : null;
690
+ if (match) return `+${match[1]} −${match[2]}`;
691
+ const status = line.status?.replace(/\s+/g, " ").trim();
692
+ return status && lineTaskStatus(line) === "error" ? status : void 0;
637
693
  }
638
694
  function lineTaskStatus(line) {
639
695
  const normalized = line.status?.replace(/\s+/g, " ").trim().toLowerCase();
@@ -738,7 +794,7 @@ function buildSlackProgressStreamChunks(params) {
738
794
  };
739
795
  if (task.details) chunk.details = task.details;
740
796
  if (task.output) chunk.output = task.output;
741
- if (index === finalTaskIndex && diffOutput) chunk.output = diffOutput;
797
+ if (index === finalTaskIndex && diffOutput) chunk.output = [task.output, diffOutput].filter(Boolean).join(" · ");
742
798
  if (index === finalTaskIndex && params.sessionUrl) chunk.sources = buildSessionSources(params.sessionUrl);
743
799
  return chunk;
744
800
  });
@@ -784,8 +840,7 @@ function buildSlackProgressCardBlocks(params) {
784
840
  ...diffStat ? [diffStat] : [],
785
841
  ...params.elapsedSeconds && params.elapsedSeconds > 0 ? [`⏱ ${params.elapsedSeconds}s`] : []
786
842
  ].join(" · ");
787
- const terminalFooter = [params.receiptSummary?.trim(), diffStat].filter(Boolean).join(" · ");
788
- const footer = params.state === "working" ? workingFooter : terminalFooter;
843
+ const footer = params.state === "working" ? workingFooter : diffStat;
789
844
  return [
790
845
  {
791
846
  type: "section",
@@ -821,35 +876,106 @@ function buildSlackProgressCardBlocks(params) {
821
876
  }] : []
822
877
  ].slice(0, 50);
823
878
  }
879
+ const EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT = { tasks: /* @__PURE__ */ new Map() };
880
+ const SLACK_TASK_FIELD_SEPARATOR = " · ";
881
+ function resolveTaskFieldDelta(previous, incoming) {
882
+ if (!incoming) return {
883
+ field: previous,
884
+ delta: void 0
885
+ };
886
+ if (previous?.rendered.includes(incoming)) return {
887
+ field: {
888
+ rendered: previous.rendered,
889
+ source: incoming
890
+ },
891
+ delta: void 0
892
+ };
893
+ const next = applyAppendOnlyStreamUpdate({
894
+ incoming,
895
+ rendered: previous?.rendered ?? "",
896
+ source: previous?.source ?? "",
897
+ separator: SLACK_TASK_FIELD_SEPARATOR
898
+ });
899
+ const delta = next.changed ? next.rendered.slice(previous?.rendered.length ?? 0) : void 0;
900
+ return {
901
+ field: {
902
+ rendered: next.rendered,
903
+ source: next.source
904
+ },
905
+ delta
906
+ };
907
+ }
824
908
  /**
825
- * Slack native streams key task rows by persistent id with no removal chunk.
826
- * When the task source switches representation (tool lines <-> typed plan) or
827
- * a snapshot drops ids, previously emitted non-terminal rows must receive a
828
- * final update or they linger in_progress forever.
909
+ * Turns a full task snapshot into the delta Slack must receive. Native streams
910
+ * key rows by persistent id with no removal chunk: unchanged rows are omitted,
911
+ * changed rows carry only their unsent field text, and rows that dropped out
912
+ * (plan shrinks, tool-line <-> plan source switches) get a final complete
913
+ * update or they linger in_progress forever.
829
914
  */
830
915
  function reconcileSlackNativeTaskChunks(params) {
831
916
  const nextTasks = /* @__PURE__ */ new Map();
832
- for (const chunk of params.chunks ?? []) if (chunk.type === "task_update") nextTasks.set(chunk.id, {
833
- title: chunk.title,
834
- status: chunk.status
835
- });
836
- const terminalized = [...params.previousTasks].filter(([id, task]) => !nextTasks.has(id) && task.status !== "complete" && task.status !== "error").map(([id, task]) => {
837
- const entry = {
838
- title: task.title,
839
- status: "complete"
917
+ let planTitle = params.previous.planTitle;
918
+ const emitted = [];
919
+ for (const chunk of params.chunks ?? []) {
920
+ if (chunk.type === "plan_update") {
921
+ if (chunk.title !== planTitle) {
922
+ planTitle = chunk.title;
923
+ emitted.push(chunk);
924
+ }
925
+ continue;
926
+ }
927
+ if (chunk.type !== "task_update") {
928
+ emitted.push(chunk);
929
+ continue;
930
+ }
931
+ const previousRow = params.previous.tasks.get(chunk.id);
932
+ const status = chunk.status;
933
+ const details = resolveTaskFieldDelta(previousRow?.details, chunk.details);
934
+ const output = resolveTaskFieldDelta(previousRow?.output, chunk.output);
935
+ const sourcesChanged = Boolean(chunk.sources) && !previousRow?.sourcesSent;
936
+ const row = {
937
+ title: chunk.title,
938
+ status
840
939
  };
841
- nextTasks.set(id, entry);
842
- return {
940
+ if (details.field) row.details = details.field;
941
+ if (output.field) row.output = output.field;
942
+ if (sourcesChanged || previousRow?.sourcesSent) row.sourcesSent = true;
943
+ nextTasks.set(chunk.id, row);
944
+ if (!(!previousRow || previousRow.title !== chunk.title || previousRow.status !== status || Boolean(details.delta) || Boolean(output.delta) || sourcesChanged)) continue;
945
+ const update = {
946
+ type: "task_update",
947
+ id: chunk.id,
948
+ title: chunk.title,
949
+ status
950
+ };
951
+ if (details.delta) update.details = details.delta;
952
+ if (output.delta) update.output = output.delta;
953
+ if (sourcesChanged) update.sources = chunk.sources;
954
+ emitted.push(update);
955
+ }
956
+ for (const [id, row] of params.previous.tasks) {
957
+ if (nextTasks.has(id)) continue;
958
+ if (row.status === "complete" || row.status === "error") {
959
+ nextTasks.set(id, row);
960
+ continue;
961
+ }
962
+ nextTasks.set(id, {
963
+ ...row,
964
+ status: "complete"
965
+ });
966
+ emitted.push({
843
967
  type: "task_update",
844
968
  id,
845
- title: task.title,
969
+ title: row.title,
846
970
  status: "complete"
847
- };
848
- });
849
- for (const [id, task] of params.previousTasks) if (!nextTasks.has(id)) nextTasks.set(id, task);
971
+ });
972
+ }
850
973
  return {
851
- chunks: params.chunks?.length ? [...params.chunks, ...terminalized] : terminalized.length ? terminalized : params.chunks,
852
- tasks: nextTasks
974
+ chunks: emitted.length > 0 ? emitted : void 0,
975
+ snapshot: {
976
+ ...planTitle ? { planTitle } : {},
977
+ tasks: nextTasks
978
+ }
853
979
  };
854
980
  }
855
981
  function buildSlackProgressStreamCompletionChunks(params) {
@@ -859,56 +985,6 @@ function buildSlackProgressStreamCompletionChunks(params) {
859
985
  });
860
986
  }
861
987
  //#endregion
862
- //#region extensions/slack/src/stream-mode.ts
863
- function resolveSlackStreamingConfig(params) {
864
- return {
865
- mode: resolveSlackStreamingMode(params),
866
- nativeStreaming: resolveSlackNativeStreaming(params)
867
- };
868
- }
869
- function applyAppendOnlyStreamUpdate(params) {
870
- const incoming = params.incoming.trimEnd();
871
- if (!incoming) return {
872
- rendered: params.rendered,
873
- source: params.source,
874
- changed: false
875
- };
876
- if (!params.rendered) return {
877
- rendered: incoming,
878
- source: incoming,
879
- changed: true
880
- };
881
- if (incoming === params.source) return {
882
- rendered: params.rendered,
883
- source: params.source,
884
- changed: false
885
- };
886
- if (incoming.startsWith(params.rendered)) return {
887
- rendered: incoming,
888
- source: incoming,
889
- changed: incoming !== params.rendered
890
- };
891
- if (incoming.startsWith(params.source)) {
892
- const delta = incoming.slice(params.source.length);
893
- return {
894
- rendered: `${params.rendered}${delta}`,
895
- source: incoming,
896
- changed: delta.length > 0
897
- };
898
- }
899
- if (params.source.startsWith(incoming)) return {
900
- rendered: params.rendered,
901
- source: params.source,
902
- changed: false
903
- };
904
- const separator = params.rendered.endsWith("\n") ? "" : "\n";
905
- return {
906
- rendered: `${params.rendered}${separator}${incoming}`,
907
- source: incoming,
908
- changed: true
909
- };
910
- }
911
- //#endregion
912
988
  //#region extensions/slack/src/monitor/message-handler/dispatch-progress-render.ts
913
989
  function resolveStructuredProgressLines(lines) {
914
990
  return lines.map((line) => {
@@ -1098,12 +1174,9 @@ function createSlackDraftProgressCardRuntime(params) {
1098
1174
  maxLineChars: params.maxLineChars,
1099
1175
  diffStat: snapshot.diffStat,
1100
1176
  ...state === "working" ? {
1101
- toolCalls: params.progressReceipt.toolCalls,
1102
- elapsedSeconds: params.progressReceipt.elapsedSeconds
1103
- } : {
1104
- receiptSummary: params.progressReceipt.buildSummaryLine(),
1105
- sessionUrl: resolveSessionUrl()
1106
- }
1177
+ toolCalls: params.progressWorkCounter.toolCalls,
1178
+ elapsedSeconds: params.progressWorkCounter.elapsedSeconds
1179
+ } : { sessionUrl: resolveSessionUrl() }
1107
1180
  });
1108
1181
  };
1109
1182
  const finalize = async (status, snapshot = params.getSnapshot(), fallbackText = resolveText(snapshot)) => {
@@ -1276,12 +1349,11 @@ function createSlackProgressRuntime(runtimeParams) {
1276
1349
  previewStreamingEnabled
1277
1350
  });
1278
1351
  let previewToolProgressSuppressed = false;
1279
- let nativeTaskState = /* @__PURE__ */ new Map();
1352
+ let nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
1280
1353
  let appendRenderedText = "";
1281
1354
  let appendSourceText = "";
1282
1355
  let nativeProgressCompletionSent = false;
1283
1356
  let nativeProgressTerminalStatus = "complete";
1284
- let nativeProgressChunkKey;
1285
1357
  let nativeNarrationRenderedText = "";
1286
1358
  let nativeNarrationSourceText = "";
1287
1359
  let nativeStreamOrder = Promise.resolve();
@@ -1290,9 +1362,10 @@ function createSlackProgressRuntime(runtimeParams) {
1290
1362
  nativeStreamOrder = run.catch(() => void 0);
1291
1363
  return run;
1292
1364
  };
1293
- const progressReceipt = createChannelProgressReceiptTracker();
1365
+ const progressWorkCounter = createChannelProgressWorkCounter();
1294
1366
  const progressSeed = `${account.accountId}:${message.channel}`;
1295
- const useDraftProgressCard = Boolean(draftStream) && isProgressMode;
1367
+ const slackProgressStyle = resolveSlackProgressStyle(account.config);
1368
+ const useDraftProgressCard = Boolean(draftStream) && isProgressMode && slackProgressStyle === "card";
1296
1369
  const explicitProgressTitle = resolveExplicitSlackProgressTitle(account.config);
1297
1370
  const progressDraftMaxLineChars = resolveChannelProgressDraftMaxLineChars(account.config);
1298
1371
  const progressCard = createSlackDraftProgressCardRuntime({
@@ -1305,7 +1378,7 @@ function createSlackProgressRuntime(runtimeParams) {
1305
1378
  },
1306
1379
  draftStream,
1307
1380
  enabled: useDraftProgressCard,
1308
- progressReceipt,
1381
+ progressWorkCounter,
1309
1382
  progressSeed,
1310
1383
  explicitTitle: explicitProgressTitle,
1311
1384
  maxLineChars: progressDraftMaxLineChars,
@@ -1367,16 +1440,14 @@ function createSlackProgressRuntime(runtimeParams) {
1367
1440
  const snapshot = progressDraft.getSnapshot();
1368
1441
  const progressLines = resolveNativeProgressLines(snapshot);
1369
1442
  const narrationUpdate = resolveNarrationUpdate(resolveNativeProgressNarration(snapshot));
1370
- const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1443
+ const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
1371
1444
  if (!useNativeProgressStreaming || delivery.streamFailed || progressLines.length === 0 && !snapshot.plan?.length && !snapshot.statusHeadline && !explicitProgressTitle && !hasRetirableNativeTasks && !narrationUpdate.delta) return false;
1372
1445
  if (!await nativeTransport.waitForStart()) return false;
1373
1446
  const reconciled = reconcileSlackNativeTaskChunks({
1374
- previousTasks: nativeTaskState,
1447
+ previous: nativeStreamSnapshot,
1375
1448
  chunks: buildNativeProgressChunks$1(snapshot)
1376
1449
  });
1377
- const chunkKey = JSON.stringify(reconciled.chunks ?? []);
1378
- const taskChunksChanged = chunkKey !== nativeProgressChunkKey;
1379
- const chunks = taskChunksChanged ? reconciled.chunks : void 0;
1450
+ const chunks = reconciled.chunks;
1380
1451
  if (!chunks?.length && !narrationUpdate.delta) return false;
1381
1452
  try {
1382
1453
  const hadSession = Boolean(delivery.streamSession);
@@ -1390,10 +1461,7 @@ function createSlackProgressRuntime(runtimeParams) {
1390
1461
  nativeNarrationRenderedText = narrationUpdate.next.rendered;
1391
1462
  nativeNarrationSourceText = narrationUpdate.next.source;
1392
1463
  }
1393
- if (taskChunksChanged) {
1394
- nativeProgressChunkKey = chunkKey;
1395
- nativeTaskState = reconciled.tasks;
1396
- }
1464
+ if (chunks?.length) nativeStreamSnapshot = reconciled.snapshot;
1397
1465
  return true;
1398
1466
  } catch (err) {
1399
1467
  runtime.error?.(danger(`slack-stream: native progress stream failed: ${formatSlackError(err)}, falling back`));
@@ -1420,7 +1488,7 @@ function createSlackProgressRuntime(runtimeParams) {
1420
1488
  return true;
1421
1489
  };
1422
1490
  const resetProgressTurnState = () => {
1423
- progressReceipt.reset();
1491
+ progressWorkCounter.reset();
1424
1492
  nativeNarrationRenderedText = "";
1425
1493
  nativeNarrationSourceText = "";
1426
1494
  };
@@ -1476,10 +1544,10 @@ function createSlackProgressRuntime(runtimeParams) {
1476
1544
  const snapshot = progressDraft.getSnapshot();
1477
1545
  const lines = resolveNativeProgressLines(snapshot);
1478
1546
  const sessionUrl = progressCard.resolveSessionUrl();
1479
- const hasRetirableNativeTasks = [...nativeTaskState.values()].some((task) => task.status !== "complete" && task.status !== "error");
1547
+ const hasRetirableNativeTasks = [...nativeStreamSnapshot.tasks.values()].some((task) => task.status !== "complete" && task.status !== "error");
1480
1548
  if (lines.length === 0 && !snapshot.plan?.length && !hasRetirableNativeTasks && !snapshot.diffStat && !sessionUrl) return;
1481
1549
  return reconcileSlackNativeTaskChunks({
1482
- previousTasks: nativeTaskState,
1550
+ previous: nativeStreamSnapshot,
1483
1551
  chunks: buildSlackProgressStreamCompletionChunks({
1484
1552
  title: resolveNativeProgressTitle(snapshot) ?? (lines.length === 0 && !snapshot.plan?.length ? "Working" : void 0),
1485
1553
  lines,
@@ -1514,7 +1582,10 @@ function createSlackProgressRuntime(runtimeParams) {
1514
1582
  delivery.streamFailed = false;
1515
1583
  };
1516
1584
  const pushPlanProgress = async (steps, explanation) => {
1517
- if (isProgressMode) return await progressDraft.pushPlanProgress(steps, { explanation });
1585
+ if (isProgressMode) {
1586
+ if (slackProgressStyle === "compact") return false;
1587
+ return await progressDraft.pushPlanProgress(steps, { explanation });
1588
+ }
1518
1589
  if (previewToolProgressSuppressed || !draftStream) return false;
1519
1590
  const text = formatChannelProgressDraftText({
1520
1591
  entry: account.config,
@@ -1580,7 +1651,6 @@ function createSlackProgressRuntime(runtimeParams) {
1580
1651
  progressDraft.mergeReasoningProgress(normalized, { snapshot: true });
1581
1652
  return visible;
1582
1653
  }
1583
- progressReceipt.noteReasoning();
1584
1654
  return await progressDraft.pushReasoningProgress(payload.text, { snapshot: payload.isReasoningSnapshot === true });
1585
1655
  };
1586
1656
  const resetDraftDeliveryState = () => {
@@ -1604,10 +1674,9 @@ function createSlackProgressRuntime(runtimeParams) {
1604
1674
  await dropDetachedProgressCards();
1605
1675
  }
1606
1676
  resetProgressTurnState();
1607
- nativeTaskState = /* @__PURE__ */ new Map();
1677
+ nativeStreamSnapshot = EMPTY_SLACK_NATIVE_STREAM_SNAPSHOT;
1608
1678
  nativeProgressCompletionSent = false;
1609
1679
  nativeProgressTerminalStatus = "complete";
1610
- nativeProgressChunkKey = void 0;
1611
1680
  progressCard.reset();
1612
1681
  resetPreviewDeliveryState();
1613
1682
  delivery.resetDeliveryTracker();
@@ -1645,7 +1714,7 @@ function createSlackProgressRuntime(runtimeParams) {
1645
1714
  suppressDefaultToolProgressMessages,
1646
1715
  progressDraft,
1647
1716
  commentaryProgressEnabled,
1648
- progressReceipt,
1717
+ progressWorkCounter,
1649
1718
  get nativeProgressCompletionSent() {
1650
1719
  return nativeProgressCompletionSent;
1651
1720
  },
@@ -1980,11 +2049,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
1980
2049
  observedFinalReplyDelivery: false
1981
2050
  };
1982
2051
  const streamedDeliveries = [];
1983
- const streamedFailuresOwnedByDispatcher = {
1984
- tool: 0,
1985
- block: 0,
1986
- final: 0
1987
- };
1988
2052
  const refreshStreamedAcknowledgements = (session) => {
1989
2053
  if (session.pendingText.length === 0) for (const delivery of streamedDeliveries) delivery.acknowledged = true;
1990
2054
  };
@@ -2167,10 +2231,7 @@ function createSlackStreamingDeliveryRuntime(setup) {
2167
2231
  return deliveryReplyThreadTs;
2168
2232
  };
2169
2233
  const deliverBufferedStreamFallback = async (params) => {
2170
- if (!await deliverPendingStreamFallback(params.session, params.err)) {
2171
- streamedFailuresOwnedByDispatcher[params.kind] += 1;
2172
- return false;
2173
- }
2234
+ if (!await deliverPendingStreamFallback(params.session, params.err)) return false;
2174
2235
  replyPlan.markSent();
2175
2236
  if (params.kind === "final") state.observedFinalReplyDelivery = true;
2176
2237
  deliveryTracker.markDelivered({
@@ -2329,23 +2390,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
2329
2390
  });
2330
2391
  }
2331
2392
  };
2332
- const reconcileCounts = (counts) => {
2333
- const next = { ...counts };
2334
- let changed = false;
2335
- for (const kind of [
2336
- "tool",
2337
- "block",
2338
- "final"
2339
- ]) {
2340
- const failedStreamedCount = streamedDeliveries.filter((delivery) => delivery.kind === kind && delivery.outcome === "failure").length;
2341
- const additionalFailedStreamed = Math.max(0, failedStreamedCount - streamedFailuresOwnedByDispatcher[kind]);
2342
- if (additionalFailedStreamed > 0) {
2343
- next[kind] = Math.max(0, (next[kind] ?? 0) - additionalFailedStreamed);
2344
- changed = true;
2345
- }
2346
- }
2347
- return changed ? next : counts;
2348
- };
2349
2393
  return Object.assign(state, {
2350
2394
  acknowledgeStoppedStreamedDeliveries,
2351
2395
  deliverNormally,
@@ -2357,7 +2401,6 @@ function createSlackStreamingDeliveryRuntime(setup) {
2357
2401
  isStreamingEligible,
2358
2402
  markPreviewPayloadDelivered,
2359
2403
  rememberDeliveredThreadTs,
2360
- reconcileCounts,
2361
2404
  resetDeliveryTracker: () => {
2362
2405
  deliveryTracker = createSlackEventDeliveryTracker();
2363
2406
  }
@@ -2463,63 +2506,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2463
2506
  const previewFinalText = replyRenderPlan.mode === "single" && replyRenderPlan.textIsSlackMrkdwn ? trimmedFinalText : normalizeSlackOutboundText((replySourceText ?? "").trim());
2464
2507
  const previewFinalTextFitsEdit = countSlackTextUtf8Bytes(previewFinalText) <= SLACK_EDIT_TEXT_MAX_BYTES;
2465
2508
  const shouldRestoreTtsSupplementTextForPreviewFallback = Boolean(ttsSupplement) && ttsSupplement?.visibleTextAlreadyDelivered !== true && Boolean(draftStream) && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.text?.trim();
2466
- if (info.kind === "final" && ttsSupplement && draftStream && !hasSlackCustomIdentity && !draftPreviewCommitted.value && !delivery.observedFinalReplyDelivery && previewStreamingEnabled && !payload.isError && !requiresSeparateFallbackDelivery && previewFinalTextFitsEdit && trimmedFinalText.length > 0) {
2467
- await draftStream.flush();
2468
- const channelId = draftStream.channelId();
2469
- const messageId = draftStream.messageId();
2470
- if (channelId && messageId) {
2471
- const finalThreadTs = delivery.usedReplyThreadTs ?? statusThreadTs;
2472
- await draftStream.seal();
2473
- try {
2474
- if (!await draftStream.finalizeMessage(messageId, async () => {
2475
- await finalizeSlackPreviewEdit({
2476
- client: slackClient,
2477
- token: ctx.botToken,
2478
- accountId: account.accountId,
2479
- channelId,
2480
- messageId,
2481
- text: previewFinalText,
2482
- ...slackBlocks?.length ? { blocks: slackBlocks } : {},
2483
- threadTs: finalThreadTs
2484
- });
2485
- })) throw new Error("Slack preview moved below a newer conversation message");
2486
- } catch (err) {
2487
- logVerbose(`slack: preview final edit failed; falling back to standard send (${formatSlackError(err)})`);
2488
- await draftStream.discardPending();
2489
- let delivered = false;
2490
- try {
2491
- await delivery.deliverNormally({
2492
- payload: payload.text?.trim() ? payload : {
2493
- ...payload,
2494
- text: ttsSupplement.spokenText
2495
- },
2496
- kind: info.kind,
2497
- forcedThreadTs: finalThreadTs
2498
- });
2499
- delivered = true;
2500
- } finally {
2501
- if (delivered) await draftStream.clear();
2502
- }
2503
- return;
2504
- }
2505
- draftPreviewCommitted.value = true;
2506
- delivery.observedFinalReplyDelivery = true;
2507
- delivery.observedReplyDelivery = true;
2508
- replyPlan.markSent();
2509
- await delivery.deliverNormally({
2510
- payload: buildTtsSupplementMediaPayload(payload),
2511
- kind: info.kind,
2512
- forcedThreadTs: finalThreadTs
2513
- });
2514
- delivery.markPreviewPayloadDelivered({
2515
- kind: info.kind,
2516
- payload,
2517
- threadTs: finalThreadTs
2518
- });
2519
- progress.progressDraft.markFinalReplyDelivered();
2520
- return;
2521
- }
2522
- }
2509
+ let ttsPreviewFinalization;
2523
2510
  await deliverWithFinalizableLivePreviewAdapter({
2524
2511
  kind: info.kind,
2525
2512
  payload,
@@ -2552,6 +2539,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2552
2539
  payload,
2553
2540
  threadTs: edit.threadTs
2554
2541
  })) return;
2542
+ if (ttsSupplement) ttsPreviewFinalization = { threadTs: edit.threadTs };
2555
2543
  if (!await draftStream?.finalizeMessage(preview.messageId, async () => {
2556
2544
  await finalizeSlackPreviewEdit({
2557
2545
  client: slackClient,
@@ -2607,11 +2595,12 @@ async function dispatchPreparedSlackMessage(prepared) {
2607
2595
  }),
2608
2596
  deliverNormally: async () => {
2609
2597
  await delivery.deliverNormally({
2610
- payload: shouldRestoreTtsSupplementTextForPreviewFallback ? {
2598
+ payload: shouldRestoreTtsSupplementTextForPreviewFallback || ttsPreviewFinalization && !payload.text?.trim() ? {
2611
2599
  ...payload,
2612
2600
  text: ttsSupplement?.spokenText
2613
2601
  } : payload,
2614
- kind: info.kind
2602
+ kind: info.kind,
2603
+ ...ttsPreviewFinalization?.threadTs ? { forcedThreadTs: ttsPreviewFinalization.threadTs } : {}
2615
2604
  });
2616
2605
  }
2617
2606
  });
@@ -2619,8 +2608,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2619
2608
  };
2620
2609
  let dispatchError;
2621
2610
  let agentRunFailed = false;
2622
- let queuedFinal = false;
2623
- let counts = {};
2611
+ let settledDispatchResult;
2624
2612
  try {
2625
2613
  const turnResult = await dispatchChannelInboundTurn({
2626
2614
  cfg,
@@ -2672,7 +2660,6 @@ async function dispatchPreparedSlackMessage(prepared) {
2672
2660
  return false;
2673
2661
  } : void 0,
2674
2662
  onReasoningEnd: async () => {
2675
- progress.progressReceipt.closeReasoning();
2676
2663
  await progress.onDraftBoundary?.();
2677
2664
  return false;
2678
2665
  },
@@ -2686,18 +2673,14 @@ async function dispatchPreparedSlackMessage(prepared) {
2686
2673
  } : void 0,
2687
2674
  onToolStart: async (payload) => {
2688
2675
  if (statusReactionsEnabled) await statusReactions.setTool(payload.name);
2689
- if (payload.phase === "start") progress.progressReceipt.noteToolCall(payload.name);
2676
+ if (payload.phase === "start") progress.progressWorkCounter.noteToolCall(payload.name);
2690
2677
  return await progress.progressDraft.pushToolEvent(payload);
2691
2678
  },
2692
2679
  onItemEvent: async (payload) => {
2693
2680
  if (progress.isProgressMode && payload.kind === "preamble") {
2694
2681
  if (progress.shouldYieldDraftProgress()) return false;
2695
2682
  const headlineVisible = await progress.progressDraft.pushPreambleHeadline(payload.progressText, { itemId: payload.itemId });
2696
- if (progress.commentaryProgressEnabled) {
2697
- const accepted = await progress.progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId });
2698
- if (accepted) progress.progressReceipt.noteCommentary(payload.itemId, payload.progressText);
2699
- return accepted || headlineVisible;
2700
- }
2683
+ if (progress.commentaryProgressEnabled) return await progress.progressDraft.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId }) || headlineVisible;
2701
2684
  return headlineVisible;
2702
2685
  }
2703
2686
  return await progress.progressDraft.pushItemEvent(payload);
@@ -2719,8 +2702,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2719
2702
  });
2720
2703
  if (turnResult.dispatched) {
2721
2704
  const result = turnResult.dispatchResult;
2722
- queuedFinal = result.queuedFinal;
2723
- counts = result.counts;
2705
+ settledDispatchResult = result;
2724
2706
  const agentRunOutcome = readAgentRunTerminalOutcome(result);
2725
2707
  agentRunFailed = agentRunOutcome === "failed";
2726
2708
  if (agentRunOutcome === "completed" && !sawTerminalFailurePayload && prepared.ctxPayload.ChatType === "channel") clearSlackThreadFailureNotice({
@@ -2759,12 +2741,7 @@ async function dispatchPreparedSlackMessage(prepared) {
2759
2741
  if (!finalStream.delivered) dispatchError ??= err;
2760
2742
  }
2761
2743
  }
2762
- counts = delivery.reconcileCounts(counts);
2763
- queuedFinal = queuedFinal && (counts.final ?? 0) > 0;
2764
- const anyReplyDelivered = hasVisibleInboundReplyDispatch({
2765
- queuedFinal,
2766
- counts
2767
- }, {
2744
+ const anyReplyDelivered = hasVisibleInboundReplyDispatch(settledDispatchResult, {
2768
2745
  observedReplyDelivery: delivery.observedReplyDelivery,
2769
2746
  fallbackDelivered: streamFallbackDelivered
2770
2747
  });
@@ -2786,11 +2763,11 @@ async function dispatchPreparedSlackMessage(prepared) {
2786
2763
  if (dispatchError) throw toErrorObject(dispatchError, "Slack dispatch failed");
2787
2764
  if (!anyReplyDelivered && !draftPreviewCommitted.value && !(agentRunFailed && progress.useDraftProgressCard)) {
2788
2765
  await draftStream?.clear();
2789
- await progress.dropDetachedProgressCards();
2766
+ await draftStream?.dropDetachedMessages();
2790
2767
  return;
2791
2768
  }
2792
2769
  if (shouldLogVerbose()) {
2793
- const finalCount = counts.final;
2770
+ const finalCount = resolveInboundReplyDispatchCounts(settledDispatchResult).final;
2794
2771
  logVerbose(`slack: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${prepared.replyTarget}`);
2795
2772
  }
2796
2773
  }
@@ -2834,6 +2811,153 @@ function normalizeSlackAppContextEntities(value) {
2834
2811
  });
2835
2812
  }
2836
2813
  //#endregion
2814
+ //#region extensions/slack/src/monitor/thread.ts
2815
+ const THREAD_STARTER_CACHE = /* @__PURE__ */ new Map();
2816
+ const THREAD_STARTER_CACHE_TTL_MS = 360 * 6e4;
2817
+ const THREAD_STARTER_CACHE_MAX = 2e3;
2818
+ function evictThreadStarterCache() {
2819
+ const now = asDateTimestampMs(Date.now());
2820
+ if (now === void 0) {
2821
+ THREAD_STARTER_CACHE.clear();
2822
+ return;
2823
+ }
2824
+ for (const [cacheKey, entry] of THREAD_STARTER_CACHE.entries()) if (asDateTimestampMs(entry.expiresAt) === void 0 || entry.expiresAt <= now) THREAD_STARTER_CACHE.delete(cacheKey);
2825
+ pruneMapToMaxSize(THREAD_STARTER_CACHE, THREAD_STARTER_CACHE_MAX);
2826
+ }
2827
+ function formatSlackFilePlaceholder(files) {
2828
+ return `[attached: ${formatSlackFileReferenceList(files)}]`;
2829
+ }
2830
+ function pushUniqueText(parts, value, options = {}) {
2831
+ const text = options.preserveWhitespace ? typeof value === "string" && value.trim().length > 0 ? value : void 0 : normalizeOptionalString(value);
2832
+ if (text && !parts.includes(text)) parts.push(text);
2833
+ }
2834
+ function resolveSlackBlocksFallbackText(blocks) {
2835
+ return resolveSlackBlocksText(blocks)?.text;
2836
+ }
2837
+ function resolveSlackAttachmentFallbackText(attachments) {
2838
+ if (!Array.isArray(attachments) || attachments.length === 0) return;
2839
+ const parts = [];
2840
+ for (const attachment of attachments) {
2841
+ const excludeTableBlocks = isSlackUnfurlAttachment(attachment);
2842
+ const fallbackBlocks = (blocks) => excludeTableBlocks ? blocks?.filter((block) => !hasSlackTableBlock([block])) : blocks;
2843
+ pushUniqueText(parts, attachment.pretext);
2844
+ pushUniqueText(parts, attachment.title);
2845
+ pushUniqueText(parts, attachment.text);
2846
+ if (!(hasSlackTableBlock(attachment.blocks) && normalizeOptionalString(attachment.fallback) === "[no preview available]")) pushUniqueText(parts, attachment.fallback);
2847
+ for (const field of attachment.fields ?? []) {
2848
+ pushUniqueText(parts, field.title);
2849
+ pushUniqueText(parts, field.value);
2850
+ }
2851
+ pushUniqueText(parts, resolveSlackBlocksFallbackText(fallbackBlocks(attachment.blocks)), { preserveWhitespace: true });
2852
+ pushUniqueText(parts, resolveSlackBlocksFallbackText(fallbackBlocks(attachment.message_blocks)), { preserveWhitespace: true });
2853
+ }
2854
+ return parts.length > 0 ? parts.join("\n") : void 0;
2855
+ }
2856
+ function resolveSlackMessageText(message) {
2857
+ const messageText = normalizeOptionalString(message.text) ?? resolveSlackAttachmentFallbackText(message.attachments);
2858
+ return resolveSlackMessageText$1({
2859
+ ...message,
2860
+ text: messageText
2861
+ }, { preserveMessageTextWhitespace: true });
2862
+ }
2863
+ async function resolveSlackThreadStarter(params) {
2864
+ evictThreadStarterCache();
2865
+ const cacheKey = JSON.stringify([
2866
+ params.workspaceScope.accountId,
2867
+ params.workspaceScope.teamId,
2868
+ params.channelId,
2869
+ params.threadTs
2870
+ ]);
2871
+ const cached = THREAD_STARTER_CACHE.get(cacheKey);
2872
+ if (cached) {
2873
+ const now = asDateTimestampMs(Date.now());
2874
+ if (now !== void 0 && cached.expiresAt > now) return cached.value;
2875
+ THREAD_STARTER_CACHE.delete(cacheKey);
2876
+ }
2877
+ try {
2878
+ const message = (await params.client.conversations.replies({
2879
+ channel: params.channelId,
2880
+ ts: params.threadTs,
2881
+ limit: 1,
2882
+ inclusive: true
2883
+ }))?.messages?.[0];
2884
+ const text = message ? resolveSlackMessageText(message) : void 0;
2885
+ const files = message?.files?.length ? message.files : void 0;
2886
+ if (!message || !text && !files) return null;
2887
+ const starter = {
2888
+ text: text || formatSlackFilePlaceholder(files),
2889
+ userId: message.user,
2890
+ botId: message.bot_id,
2891
+ ts: message.ts,
2892
+ files
2893
+ };
2894
+ const expiresAt = resolveExpiresAtMsFromDurationMs(THREAD_STARTER_CACHE_TTL_MS);
2895
+ if (expiresAt !== void 0) {
2896
+ if (THREAD_STARTER_CACHE.has(cacheKey)) THREAD_STARTER_CACHE.delete(cacheKey);
2897
+ THREAD_STARTER_CACHE.set(cacheKey, {
2898
+ value: starter,
2899
+ expiresAt
2900
+ });
2901
+ evictThreadStarterCache();
2902
+ }
2903
+ return starter;
2904
+ } catch (err) {
2905
+ logVerbose$1(`slack thread starter fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
2906
+ return null;
2907
+ }
2908
+ }
2909
+ const SLACK_THREAD_HISTORY_MAX_PAGES = 3;
2910
+ /**
2911
+ * Fetches the most recent messages in a Slack thread (excluding the current message).
2912
+ * Used to populate thread context when a new thread session starts.
2913
+ *
2914
+ * Uses cursor pagination and keeps only the latest N retained messages when the full
2915
+ * thread fits in the bounded fetch window.
2916
+ */
2917
+ async function resolveSlackThreadHistory(params) {
2918
+ const maxMessages = params.limit ?? 20;
2919
+ if (!Number.isFinite(maxMessages) || maxMessages <= 0) return [];
2920
+ const fetchLimit = 200;
2921
+ const retained = [];
2922
+ let cursor;
2923
+ let pagesFetched = 0;
2924
+ try {
2925
+ do {
2926
+ pagesFetched += 1;
2927
+ const response = await params.client.conversations.replies({
2928
+ channel: params.channelId,
2929
+ ts: params.threadTs,
2930
+ limit: fetchLimit,
2931
+ inclusive: true,
2932
+ ...cursor ? { cursor } : {}
2933
+ });
2934
+ for (const msg of response.messages ?? []) {
2935
+ const text = resolveSlackMessageText(msg);
2936
+ if (!text && !msg.files?.length) continue;
2937
+ if (params.currentMessageTs && msg.ts === params.currentMessageTs) continue;
2938
+ retained.push([msg, text]);
2939
+ }
2940
+ if (retained.length > maxMessages) retained.splice(0, retained.length - maxMessages);
2941
+ const next = response.response_metadata?.next_cursor;
2942
+ cursor = typeof next === "string" && next.trim().length > 0 ? next.trim() : void 0;
2943
+ } while (cursor && pagesFetched < SLACK_THREAD_HISTORY_MAX_PAGES);
2944
+ if (cursor) {
2945
+ logVerbose$1(`slack thread history capped channel=${params.channelId} ts=${params.threadTs} pages=${SLACK_THREAD_HISTORY_MAX_PAGES}`);
2946
+ return [];
2947
+ }
2948
+ return retained.map(([message, text]) => ({
2949
+ text: text ?? formatSlackFilePlaceholder(message.files),
2950
+ userId: message.user,
2951
+ botId: message.bot_id,
2952
+ ts: message.ts,
2953
+ files: message.files
2954
+ }));
2955
+ } catch (err) {
2956
+ logVerbose$1(`slack thread history fetch failed channel=${params.channelId} ts=${params.threadTs}: ${formatErrorMessage(err)}`);
2957
+ return [];
2958
+ }
2959
+ }
2960
+ //#endregion
2837
2961
  //#region extensions/slack/src/monitor/message-handler/preflight-audio.ts
2838
2962
  function isSlackAudioFile(file) {
2839
2963
  if (file.subtype === "slack_audio") return true;
@@ -2849,7 +2973,7 @@ function findCaptionlessSlackAudioFile(message) {
2849
2973
  return message.files?.slice(0, 8).find(isSlackAudioFile);
2850
2974
  }
2851
2975
  function formatSlackAudioTranscriptForAgent(params) {
2852
- return [`[Audio transcript (machine-generated, untrusted)]: ${JSON.stringify(params.transcript)}`, params.rawBody].filter(Boolean).join("\n");
2976
+ return [formatAudioTranscriptForAgent(params.transcript), params.rawBody].filter(Boolean).join("\n");
2853
2977
  }
2854
2978
  async function resolveSlackPreflightAudioTranscript(params) {
2855
2979
  const mediaIndex = params.media.findIndex((entry) => entry.contentType?.toLowerCase().startsWith("audio/"));
@@ -2884,7 +3008,7 @@ async function discardSlackPreflightMedia(media) {
2884
3008
  const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
2885
3009
  const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
2886
3010
  const SLACK_USER_MENTION_RE$1 = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
2887
- const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-BTDXwwE3.js").then((n) => n._));
3011
+ const loadSlackMediaModule$1 = createLazyRuntimeModule(() => import("./actions-DFk0ANVB.js").then((n) => n._));
2888
3012
  function collectUniqueSlackMentionIds$1(texts) {
2889
3013
  const seen = /* @__PURE__ */ new Set();
2890
3014
  const mentionIds = [];
@@ -2965,7 +3089,7 @@ async function resolveSlackMessageContent(params) {
2965
3089
  botAttachmentText = botAttachmentTextParts.length > 0 ? botAttachmentTextParts.join("\n") : void 0;
2966
3090
  }
2967
3091
  const textParts = [
2968
- resolveSlackMessageText(params.message),
3092
+ resolveSlackMessageText$1(params.message),
2969
3093
  attachmentContent?.text,
2970
3094
  botAttachmentText
2971
3095
  ];
@@ -3083,62 +3207,6 @@ async function resolveSlackDmHistoryContext(params) {
3083
3207
  }
3084
3208
  //#endregion
3085
3209
  //#region extensions/slack/src/monitor/message-handler/prepare-routing.ts
3086
- const slackRouteBindingConfigCache = /* @__PURE__ */ new WeakMap();
3087
- function slackTargetDefaultKindForPeer(kind) {
3088
- return kind === "direct" ? "user" : "channel";
3089
- }
3090
- function slackTargetKindMatchesPeer(peerKind, targetKind) {
3091
- if (targetKind === "user") return peerKind === "direct";
3092
- return peerKind === "channel" || peerKind === "group";
3093
- }
3094
- function normalizeSlackRouteBindingPeer(peer) {
3095
- const rawId = peer.id.trim();
3096
- if (!rawId || rawId === "*") return peer;
3097
- const target = (() => {
3098
- try {
3099
- return parseSlackTarget(rawId, { defaultKind: slackTargetDefaultKindForPeer(peer.kind) });
3100
- } catch {
3101
- return;
3102
- }
3103
- })();
3104
- if (!target || !slackTargetKindMatchesPeer(peer.kind, target.kind)) return peer;
3105
- const normalizedId = target.teamId ? `team:${target.teamId}:${target.kind}:${target.id}` : target.id;
3106
- return normalizedId === peer.id ? peer : {
3107
- ...peer,
3108
- id: normalizedId
3109
- };
3110
- }
3111
- function normalizeSlackRouteBindingConfig(cfg) {
3112
- const bindings = cfg.bindings;
3113
- const cached = slackRouteBindingConfigCache.get(cfg);
3114
- if (cached && cached.bindingsRef === bindings) return cached.normalizedCfg;
3115
- if (!Array.isArray(bindings)) return cfg;
3116
- let changed = false;
3117
- const normalizedBindings = bindings.map((binding) => {
3118
- if (binding.type === "acp" || binding.match.channel.trim().toLowerCase() !== "slack") return binding;
3119
- const peer = binding.match.peer;
3120
- if (!peer) return binding;
3121
- const normalizedPeer = normalizeSlackRouteBindingPeer(peer);
3122
- if (normalizedPeer === peer) return binding;
3123
- changed = true;
3124
- return {
3125
- ...binding,
3126
- match: {
3127
- ...binding.match,
3128
- peer: normalizedPeer
3129
- }
3130
- };
3131
- });
3132
- const normalizedCfg = changed ? {
3133
- ...cfg,
3134
- bindings: normalizedBindings
3135
- } : cfg;
3136
- slackRouteBindingConfigCache.set(cfg, {
3137
- bindingsRef: bindings,
3138
- normalizedCfg
3139
- });
3140
- return normalizedCfg;
3141
- }
3142
3210
  function resolveSlackBaseConversationId(params) {
3143
3211
  return qualifySlackConversationId(params.isDirectMessage ? `user:${params.message.user ?? "unknown"}` : params.message.channel, params.eventScope);
3144
3212
  }
@@ -3197,44 +3265,18 @@ function resolveSlackRoutingContext(params) {
3197
3265
  eventScope
3198
3266
  });
3199
3267
  const runtimeBindingThreadId = routedThreadId ?? (isDirectMessage && isThreadReply ? threadTs : void 0);
3200
- const boundThreadRoute = !eventScope && runtimeBindingThreadId ? resolveRuntimeConversationBindingRoute({
3201
- route,
3202
- conversation: {
3203
- channel: "slack",
3204
- accountId: account.accountId,
3205
- conversationId: runtimeBindingThreadId,
3206
- parentConversationId: baseConversationId
3207
- }
3208
- }) : null;
3209
- const runtimeRoute = eventScope ? {
3210
- route,
3211
- bindingRecord: null,
3212
- boundSessionKey: void 0
3213
- } : boundThreadRoute?.boundSessionKey || boundThreadRoute?.bindingRecord ? boundThreadRoute : resolveRuntimeConversationBindingRoute({
3268
+ const bindingRoute = resolveSlackConversationBindingRoute({
3269
+ cfg: ctx.cfg,
3214
3270
  route,
3215
- conversation: {
3216
- channel: "slack",
3217
- accountId: account.accountId,
3218
- conversationId: baseConversationId
3219
- }
3271
+ accountId: account.accountId,
3272
+ baseConversationId,
3273
+ runtimeBindingThreadId,
3274
+ bindingsEnabled: !eventScope
3220
3275
  });
3221
- let configuredBinding = null;
3222
- let configuredBindingSessionKey = "";
3223
- if (runtimeRoute.boundSessionKey || runtimeRoute.bindingRecord) route = runtimeRoute.route;
3224
- else if (!eventScope) {
3225
- const configuredRoute = resolveConfiguredBindingRoute({
3226
- cfg: ctx.cfg,
3227
- route,
3228
- conversation: {
3229
- channel: "slack",
3230
- accountId: account.accountId,
3231
- conversationId: baseConversationId
3232
- }
3233
- });
3234
- configuredBinding = configuredRoute.bindingResolution;
3235
- configuredBindingSessionKey = configuredRoute.boundSessionKey ?? "";
3236
- route = configuredRoute.route;
3237
- }
3276
+ const runtimeRoute = bindingRoute.runtimeRoute;
3277
+ const configuredBinding = bindingRoute.configuredRoute?.bindingResolution ?? null;
3278
+ const configuredBindingSessionKey = bindingRoute.configuredRoute?.boundSessionKey ?? "";
3279
+ route = bindingRoute.route;
3238
3280
  const threadKeys = runtimeRoute.boundSessionKey || configuredBindingSessionKey ? {
3239
3281
  sessionKey: route.sessionKey,
3240
3282
  parentSessionKey: void 0
@@ -3317,7 +3359,7 @@ function formatSlackThreadLabelSnippet(text) {
3317
3359
  }
3318
3360
  //#endregion
3319
3361
  //#region extensions/slack/src/monitor/message-handler/prepare-thread-context.ts
3320
- const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-BTDXwwE3.js").then((n) => n._));
3362
+ const loadSlackMediaModule = createLazyRuntimeModule(() => import("./actions-DFk0ANVB.js").then((n) => n._));
3321
3363
  const SLACK_THREAD_CONTEXT_USER_LOOKUP_CONCURRENCY = 4;
3322
3364
  function resolveSlackThreadSessionFreshness(params) {
3323
3365
  return params.ctx.channelRuntime?.session?.resolveEntryResetFreshness?.({
@@ -4468,6 +4510,7 @@ async function prepareSlackMessage(params) {
4468
4510
  workspaceName
4469
4511
  }) : void 0;
4470
4512
  const senderName = await resolveSenderName();
4513
+ const conversationAvatar = isDirectMessage && message.user ? ctx.resolveUserAvatar(message.user, opts.eventScope) : void 0;
4471
4514
  const preview = truncateUtf16Safe(bodyForAgent.replace(/\s+/g, " "), 160);
4472
4515
  const inboundLabel = isDirectMessage ? `Slack DM from ${senderName}` : `Slack message in ${roomLabel} from ${senderName}`;
4473
4516
  const slackFrom = isDirectMessage ? `slack:${message.user}` : isRoom ? `slack:channel:${message.channel}` : `slack:group:${message.channel}`;
@@ -4600,10 +4643,19 @@ async function prepareSlackMessage(params) {
4600
4643
  conversation: {
4601
4644
  kind: chatType,
4602
4645
  id: message.channel,
4646
+ routePeer: {
4647
+ kind: chatType,
4648
+ id: qualifySlackRoutePeerId({
4649
+ id: isDirectMessage ? message.user ?? "unknown" : message.channel,
4650
+ kind: isDirectMessage ? "user" : "channel",
4651
+ eventScope: opts.eventScope
4652
+ })
4653
+ },
4603
4654
  label: envelopeFrom,
4604
4655
  spaceId: opts.eventScope?.teamId || ctx.teamId || void 0,
4605
4656
  threadId: boundMessageThreadId,
4606
- nativeChannelId: message.channel
4657
+ nativeChannelId: message.channel,
4658
+ avatar: conversationAvatar
4607
4659
  },
4608
4660
  route: {
4609
4661
  agentId: route.agentId,