@openclaw/discord 2026.5.26 → 2026.5.27

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 (66) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/api.js +12 -12
  3. package/dist/{approval-handler.runtime-BlGs6D0-.js → approval-handler.runtime-BDxD97LJ.js} +18 -5
  4. package/dist/{audit-DE8lNdMv.js → audit-BdUjE2tr.js} +3 -3
  5. package/dist/{channel-XYTSE8wN.js → channel-9YoMy5Jf.js} +16 -16
  6. package/dist/{channel-actions-DICb5UUl.js → channel-actions-DZfkB0nd.js} +17 -2
  7. package/dist/{channel-actions.runtime-92Esj8jy.js → channel-actions.runtime-suaDJHH0.js} +5 -5
  8. package/dist/channel-plugin-api.js +1 -1
  9. package/dist/{channel.setup-JaozRxYz.js → channel.setup-B-kHacpx.js} +3 -3
  10. package/dist/{components-Bpf9ITyn.js → components-Dlc81IU5.js} +1 -1
  11. package/dist/contract-api.js +3 -3
  12. package/dist/{conversation-identity-Be5JQPP9.js → conversation-identity-DAEgiGDV.js} +2 -2
  13. package/dist/{directory-config-oK2sOZ2m.js → directory-config-Cgp0csDd.js} +1 -1
  14. package/dist/directory-contract-api.js +1 -1
  15. package/dist/{directory-live-BRjo48ro.js → directory-live-C-ECRrM8.js} +1 -1
  16. package/dist/{doctor-DBPfLj15.js → doctor-Q80i7GdG.js} +2 -2
  17. package/dist/{doctor-contract-CLMMYrdF.js → doctor-contract-8-Ia3d_X.js} +1 -1
  18. package/dist/doctor-contract-api.js +1 -1
  19. package/dist/{handle-action.guild-admin-_LgqCYcN.js → handle-action.guild-admin-DWFTAcfd.js} +30 -12
  20. package/dist/index.js +1 -1
  21. package/dist/{manager.runtime-Bs0ngLSw.js → manager.runtime-D6V2SPKr.js} +3 -3
  22. package/dist/{message-handler-B9n5CtIS.js → message-handler-B5-UG_oD.js} +7 -7
  23. package/dist/{message-handler.preflight-Cu8cFBgW.js → message-handler.preflight-Ddww-wnF.js} +12 -13
  24. package/dist/{message-handler.process-DO17vBlT.js → message-handler.process-HWGh2NOP.js} +379 -352
  25. package/dist/{message-utils-DxHZcpPf.js → message-utils-4w0_DPFE.js} +1 -1
  26. package/dist/{outbound-adapter-C8lzfSt6.js → outbound-adapter-DYUYRaBd.js} +7 -8
  27. package/dist/{pluralkit-CTbOoDPp.js → pluralkit-BS1MuvYs.js} +1 -1
  28. package/dist/{preview-streaming-CQ7PsV9J.js → preview-streaming-DXT8oJdo.js} +1 -1
  29. package/dist/{provider-D3RwHRhI.js → provider-CO6pih5z.js} +21 -24
  30. package/dist/{provider-session.runtime-DbNYskMy.js → provider-session.runtime-BD5XLPI8.js} +3 -3
  31. package/dist/provider.runtime-pUGk7VR5.js +2 -0
  32. package/dist/{resolve-channels-CIV0C8ST.js → resolve-channels-pD06YNCU.js} +1 -1
  33. package/dist/{resolve-users-LZKYHrJx.js → resolve-users-BiWLqNNO.js} +1 -1
  34. package/dist/runtime-api.actions.js +2 -2
  35. package/dist/runtime-api.js +18 -18
  36. package/dist/runtime-api.lookup.js +4 -4
  37. package/dist/runtime-api.monitor-BjgSsR6H.js +5 -0
  38. package/dist/runtime-api.monitor.js +4 -4
  39. package/dist/runtime-api.send.js +5 -5
  40. package/dist/runtime-api.threads.js +3 -3
  41. package/dist/{runtime-DBkHf0qH.js → runtime-xSazIM0F.js} +144 -9
  42. package/dist/{send-CM1NFFrZ.js → send-BzXZ8iUI.js} +6 -3
  43. package/dist/{send.components-swKESEWc.js → send.components-AK8K4TwB.js} +4 -4
  44. package/dist/{send.outbound-BHQPWbwU.js → send.outbound-ZrMnBa8C.js} +3 -3
  45. package/dist/{send.receipt-D_6lR7zH.js → send.receipt-BzfsP3Bb.js} +1 -1
  46. package/dist/{send.shared-aYGYz83q.js → send.shared-ehnDGwXx.js} +81 -3
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{shared-BwF8ShpE.js → shared-ToNRC7ax.js} +2 -2
  49. package/dist/{subagent-hooks-DHA_1pBI.js → subagent-hooks-Di_2iXU8.js} +2 -2
  50. package/dist/subagent-hooks-api.js +1 -1
  51. package/dist/{system-events-Cr3EqBah.js → system-events-DbqKnNPF.js} +1 -1
  52. package/dist/{target-resolver-CVgOsap6.js → target-resolver-DXPvq5-L.js} +2 -2
  53. package/dist/targets-BBVHRaeO.js +3 -0
  54. package/dist/test-api.js +3 -3
  55. package/dist/{thread-bindings-Dw4wcHWn.js → thread-bindings-Bw40FTRZ.js} +4 -4
  56. package/dist/{thread-bindings.discord-api-xCfun-pQ.js → thread-bindings.discord-api-irWYI8YX.js} +4 -4
  57. package/dist/{thread-bindings.manager-UJ5FvZfO.js → thread-bindings.manager-LoYZzlss.js} +3 -3
  58. package/dist/{transcripts-source-emawQzBc.js → transcripts-source-CwahHAYt.js} +1 -1
  59. package/dist/transcripts-source-api.js +1 -1
  60. package/dist/{typing-BhIpRSfR.js → typing-Cv09OhaY.js} +1 -1
  61. package/npm-shrinkwrap.json +3 -3
  62. package/openclaw.plugin.json +2 -0
  63. package/package.json +6 -6
  64. package/dist/provider.runtime-rUg1sHKP.js +0 -2
  65. package/dist/runtime-api.monitor-CUn-x5uG.js +0 -5
  66. package/dist/targets-CNDNKpqQ.js +0 -3
@@ -1,39 +1,36 @@
1
- import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-D_6lR7zH.js";
1
+ import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-BzfsP3Bb.js";
2
2
  import { f as resolveDiscordMaxLinesPerMessage } from "./accounts-dXTfmnSZ.js";
3
- import { N as createDiscordRestClient, P as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-aYGYz83q.js";
3
+ import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-ehnDGwXx.js";
4
4
  import { S as resolveTimestampMs, a as normalizeDiscordSlug, r as normalizeDiscordAllowList } from "./allow-list-BnkWtVpA.js";
5
- import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-CM1NFFrZ.js";
6
- import "./targets-CNDNKpqQ.js";
7
- import { t as resolveDiscordConversationIdentity } from "./conversation-identity-Be5JQPP9.js";
5
+ import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-BzXZ8iUI.js";
6
+ import "./targets-BBVHRaeO.js";
7
+ import { t as resolveDiscordConversationIdentity } from "./conversation-identity-DAEgiGDV.js";
8
8
  import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-CEPlt2uz.js";
9
- import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-C8lzfSt6.js";
10
- import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-CQ7PsV9J.js";
9
+ import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-DYUYRaBd.js";
10
+ import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-DXT8oJdo.js";
11
11
  import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-l_PsHQvX.js";
12
- import { a as resolveReplyContext, i as buildGuildLabel, n as deliverDiscordReply, r as buildDirectLabel, x as resolveDiscordThreadStarter, y as resolveDiscordAutoThreadReplyPlan } from "./provider-D3RwHRhI.js";
13
- import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-DxHZcpPf.js";
14
- import { t as sendTyping } from "./typing-BhIpRSfR.js";
12
+ import { a as resolveReplyContext, i as buildGuildLabel, n as deliverDiscordReply, r as buildDirectLabel, x as resolveDiscordThreadStarter, y as resolveDiscordAutoThreadReplyPlan } from "./provider-CO6pih5z.js";
13
+ import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-4w0_DPFE.js";
14
+ import { t as sendTyping } from "./typing-Cv09OhaY.js";
15
15
  import { n as buildDiscordInboundAccessContext, r as createDiscordSupplementalContextAccessChecker } from "./inbound-context-B5EsqsSr.js";
16
16
  import { buildAgentSessionKey, normalizeAccountId, resolveAccountEntry, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
17
17
  import { MessageFlags } from "discord-api-types/v10";
18
18
  import path from "node:path";
19
19
  import { evaluateSupplementalContextVisibility } from "openclaw/plugin-sdk/security-runtime";
20
20
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
21
- import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
21
+ import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, isReplyPayloadNonTerminalToolErrorWarning, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
22
22
  import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
23
23
  import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
24
24
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
25
25
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
26
26
  import { convertMarkdownTables, stripInlineDirectiveTagsForDelivery, stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-chunking";
27
- import { createChannelMessageReplyPipeline, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-message";
28
- import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewChunk, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages, resolveTranscriptBackedChannelFinalText } from "openclaw/plugin-sdk/channel-streaming";
27
+ import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelMessageReplyPipeline, createChannelProgressDraftGate, createFinalizableDraftLifecycle, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelMessageSourceReplyDeliveryMode, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewChunk, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages, resolveTranscriptBackedChannelFinalText } from "openclaw/plugin-sdk/channel-outbound";
29
28
  import { recordInboundSession, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/conversation-runtime";
30
29
  import { EmbeddedBlockChunker, formatReasoningMessage, resolveAckReaction, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
31
30
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
32
31
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
33
32
  import { loadSessionStore, readLatestAssistantTextFromSessionTranscript, readSessionUpdatedAt, resolveAndPersistSessionFile, resolveSessionStoreEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
34
- import { buildChannelInboundEventContext, formatInboundEnvelope, resolveEnvelopeFormatOptions, toHistoryMediaEntries, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
35
- import { createFinalizableDraftLifecycle } from "openclaw/plugin-sdk/channel-lifecycle";
36
- import { hasFinalInboundReplyDispatch, recordChannelBotPairLoopAndCheckSuppression, runPreparedInboundReplyTurn } from "openclaw/plugin-sdk/inbound-reply-dispatch";
33
+ import { buildChannelInboundEventContext, dispatchChannelInboundReply, formatInboundEnvelope, hasFinalInboundReplyDispatch, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toHistoryMediaEntries, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
37
34
  import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
38
35
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
39
36
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
@@ -165,30 +162,18 @@ async function buildDiscordMessageProcessContext(params) {
165
162
  })
166
163
  });
167
164
  const replyContext = resolveReplyContext(message, resolveDiscordMessageText);
168
- const replyVisibility = replyContext ? evaluateSupplementalContextVisibility({
165
+ const replySenderAllowed = replyContext ? isSupplementalContextSenderAllowed({
166
+ id: replyContext.senderId,
167
+ name: replyContext.senderName,
168
+ tag: replyContext.senderTag,
169
+ memberRoleIds: replyContext.memberRoleIds
170
+ }) : true;
171
+ const replyVisible = evaluateSupplementalContextVisibility({
169
172
  mode: contextVisibilityMode,
170
173
  kind: "quote",
171
- senderAllowed: isSupplementalContextSenderAllowed({
172
- id: replyContext.senderId,
173
- name: replyContext.senderName,
174
- tag: replyContext.senderTag,
175
- memberRoleIds: replyContext.memberRoleIds
176
- })
177
- }) : null;
178
- const filteredReplyContext = replyContext && replyVisibility?.include ? replyContext : null;
179
- const replyContextForPromptBody = Boolean(botUserId && filteredReplyContext?.senderId === botUserId) ? null : filteredReplyContext;
180
- if (replyContext && !filteredReplyContext && isGuildMessage) logVerbose(`discord: drop reply context (mode=${contextVisibilityMode})`);
181
- const mediaListForContext = [...mediaList];
182
- if (replyContextForPromptBody) {
183
- const referencedReplyMediaList = await resolveReferencedReplyMediaList(message, mediaMaxBytes, {
184
- fetchImpl: discordRestFetch,
185
- ssrfPolicy: cfg.browser?.ssrfPolicy,
186
- readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
187
- totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
188
- abortSignal
189
- });
190
- if (!isContextAborted(abortSignal)) mediaListForContext.push(...referencedReplyMediaList);
191
- }
174
+ senderAllowed: replySenderAllowed
175
+ }).include;
176
+ if (replyContext && !replyVisible && isGuildMessage) logVerbose(`discord: drop reply context (mode=${contextVisibilityMode})`);
192
177
  if (forumContextLine) combinedBody = `${combinedBody}\n${forumContextLine}`;
193
178
  let threadStarterBody;
194
179
  let threadLabel;
@@ -230,7 +215,7 @@ async function buildDiscordMessageProcessContext(params) {
230
215
  }
231
216
  if (!threadParentInheritanceEnabled) parentSessionKey = void 0;
232
217
  }
233
- const preflightAudioIndex = preflightAudioTranscript === void 0 ? -1 : mediaListForContext.findIndex((media) => media.contentType?.startsWith("audio/"));
218
+ const preflightAudioIndex = preflightAudioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.contentType?.startsWith("audio/"));
234
219
  const threadKeys = resolveThreadSessionKeys({
235
220
  baseSessionKey,
236
221
  threadId: threadChannel ? messageChannelId : void 0,
@@ -280,10 +265,10 @@ async function buildDiscordMessageProcessContext(params) {
280
265
  storePath,
281
266
  sessionKey: effectiveSessionKey
282
267
  });
283
- const ctxPayload = buildChannelInboundEventContext({
268
+ const ctxPayload = await buildChannelInboundEventContext({
284
269
  channel: "discord",
285
- provider: "discord",
286
- surface: "discord",
270
+ resolveSupplementalMedia: true,
271
+ contextVisibility: contextVisibilityMode,
287
272
  accountId: route.accountId,
288
273
  messageId: canonicalMessageId ?? message.id,
289
274
  messageIdFull: canonicalMessageId && canonicalMessageId !== message.id ? message.id : void 0,
@@ -302,11 +287,7 @@ async function buildDiscordMessageProcessContext(params) {
302
287
  id: messageChannelId,
303
288
  label: fromLabel,
304
289
  spaceId: isGuildMessage ? (guildInfo?.id ?? guildSlug) || void 0 : void 0,
305
- threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? void 0,
306
- routePeer: {
307
- kind: isDirectMessage ? "direct" : "channel",
308
- id: isDirectMessage ? author.id : messageChannelId
309
- }
290
+ threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? void 0
310
291
  },
311
292
  route: {
312
293
  agentId: route.agentId,
@@ -318,7 +299,7 @@ async function buildDiscordMessageProcessContext(params) {
318
299
  },
319
300
  reply: {
320
301
  to: effectiveTo,
321
- originatingTo
302
+ ...originatingTo !== effectiveTo ? { originatingTo } : {}
322
303
  },
323
304
  message: {
324
305
  inboundEventKind: ctx.inboundEventKind,
@@ -326,7 +307,6 @@ async function buildDiscordMessageProcessContext(params) {
326
307
  rawBody: preflightAudioTranscript ?? baseText,
327
308
  bodyForAgent: preflightAudioTranscript ?? baseText ?? text,
328
309
  commandBody: preflightAudioTranscript ?? baseText,
329
- envelopeFrom: fromLabel,
330
310
  inboundHistory
331
311
  },
332
312
  access: {
@@ -337,12 +317,7 @@ async function buildDiscordMessageProcessContext(params) {
337
317
  requireMention: ctx.shouldRequireMention,
338
318
  effectiveWasMentioned: ctx.effectiveWasMentioned
339
319
  },
340
- commands: {
341
- authorized: commandAuthorized,
342
- allowTextCommands: ctx.allowTextCommands,
343
- useAccessGroups: false,
344
- authorizers: []
345
- }
320
+ commands: { authorized: commandAuthorized }
346
321
  },
347
322
  commandTurn: {
348
323
  kind: "text-slash",
@@ -350,16 +325,29 @@ async function buildDiscordMessageProcessContext(params) {
350
325
  authorized: commandAuthorized,
351
326
  body: preflightAudioTranscript ?? baseText
352
327
  },
353
- media: toInboundMediaFacts(mediaListForContext, { transcribed: (_media, index) => index === preflightAudioIndex }),
328
+ media: toInboundMediaFacts(mediaList, { transcribed: (_media, index) => index === preflightAudioIndex }),
354
329
  supplemental: {
355
- quote: filteredReplyContext ? {
356
- id: filteredReplyContext.id,
357
- body: replyContextForPromptBody?.body,
358
- sender: filteredReplyContext.sender
330
+ quote: replyContext && replyVisible ? {
331
+ id: replyContext.id,
332
+ body: replyContext.body,
333
+ sender: replyContext.sender,
334
+ senderAllowed: replySenderAllowed,
335
+ isSelf: Boolean(botUserId && replyContext.senderId === botUserId),
336
+ media: async () => {
337
+ const referencedReplyMediaList = await resolveReferencedReplyMediaList(message, mediaMaxBytes, {
338
+ fetchImpl: discordRestFetch,
339
+ ssrfPolicy: cfg.browser?.ssrfPolicy,
340
+ readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
341
+ totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
342
+ abortSignal
343
+ });
344
+ return isContextAborted(abortSignal) ? [] : toInboundMediaFacts(referencedReplyMediaList);
345
+ }
359
346
  } : void 0,
360
347
  thread: {
361
348
  starterBody: !effectivePreviousTimestamp ? threadStarterBody : void 0,
362
- label: threadLabel
349
+ label: threadLabel,
350
+ senderAllowed: true
363
351
  },
364
352
  groupSystemPrompt: isGuildMessage ? groupSystemPrompt : void 0
365
353
  },
@@ -809,6 +797,10 @@ function formatDiscordReplyDeliveryFailure(params) {
809
797
  const context = [`target=${params.target}`, params.sessionKey ? `session=${params.sessionKey}` : void 0].filter(Boolean).join(" ");
810
798
  return `discord ${params.kind} reply failed (${context}): ${String(params.err)}`;
811
799
  }
800
+ function isFallbackOnlyToolWarningFinal(payload) {
801
+ if (payload.isError !== true || !isReplyPayloadNonTerminalToolErrorWarning(payload)) return false;
802
+ return !resolveSendableOutboundReplyParts(payload).hasMedia;
803
+ }
812
804
  function formatDiscordReplySkip(params) {
813
805
  const context = [`target=${params.target}`, params.sessionKey ? `session=${params.sessionKey}` : void 0].filter(Boolean).join(" ");
814
806
  return `discord ${params.kind} reply skipped (${params.reason}): ${context}`;
@@ -850,7 +842,7 @@ async function processDiscordMessage(ctx, observer) {
850
842
  }
851
843
  const boundThreadId = ctx.threadBinding?.conversation?.conversationId?.trim();
852
844
  if (boundThreadId && typeof threadBindings.touchThread === "function") threadBindings.touchThread({ threadId: boundThreadId });
853
- const { createReplyDispatcherWithTyping, dispatchInboundMessage, settleReplyDispatcher } = await loadReplyRuntime();
845
+ const { dispatchReplyWithBufferedBlockDispatcher } = await loadReplyRuntime();
854
846
  const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
855
847
  cfg,
856
848
  ctx: {
@@ -1090,128 +1082,133 @@ async function processDiscordMessage(ctx, observer) {
1090
1082
  draftPreview.markFinalReplyStarted();
1091
1083
  observer?.onFinalReplyStart?.();
1092
1084
  };
1093
- const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } = createReplyDispatcherWithTyping({
1094
- ...replyPipeline,
1095
- humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
1096
- deliver: async (payload, info) => {
1097
- if (isProcessAborted(abortSignal)) {
1098
- logVerbose(formatDiscordReplySkip({
1099
- kind: info.kind,
1100
- reason: "aborted before delivery",
1101
- target: deliverTarget,
1102
- sessionKey: ctxPayload.SessionKey
1103
- }));
1104
- return;
1105
- }
1106
- const isFinal = info.kind === "final";
1107
- if (payload.isReasoning) {
1108
- logVerbose(formatDiscordReplySkip({
1109
- kind: info.kind,
1110
- reason: "reasoning payload",
1111
- target: deliverTarget,
1112
- sessionKey: ctxPayload.SessionKey
1113
- }));
1114
- return;
1115
- }
1116
- if (isFinal) draftPreview.markFinalReplyStarted();
1117
- const finalText = isFinal && typeof payload.text === "string" ? await resolveTranscriptBackedChannelFinalText({
1118
- finalText: payload.text,
1119
- resolveCandidateText: resolveCurrentTurnTranscriptFinalText
1120
- }) : payload.text;
1121
- const effectivePayload = finalText !== payload.text ? {
1122
- ...payload,
1123
- text: finalText
1124
- } : payload;
1125
- const draftStream = draftPreview.draftStream;
1126
- if (draftStream && draftPreview.isProgressMode && info.kind === "block") {
1127
- if (!resolveSendableOutboundReplyParts(effectivePayload).hasMedia && !payload.isError) return;
1128
- }
1129
- if (draftStream && isFinal && (!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted) && !payload.isError) {
1130
- const hasMedia = resolveSendableOutboundReplyParts(effectivePayload).hasMedia;
1131
- const ttsSupplement = getReplyPayloadTtsSupplement(effectivePayload);
1132
- const previewSourceText = finalText ?? ttsSupplement?.spokenText;
1133
- const previewFinalText = draftPreview.resolvePreviewFinalText(previewSourceText);
1134
- const previewReplyToId = replyReference.peek();
1135
- const hasExplicitReplyDirective = Boolean(effectivePayload.replyToTag || effectivePayload.replyToCurrent) || typeof previewSourceText === "string" && /\[\[\s*reply_to(?:_current|\s*:)/i.test(previewSourceText);
1136
- if ((await deliverWithFinalizableLivePreviewAdapter({
1137
- kind: info.kind,
1138
- payload: effectivePayload,
1139
- adapter: defineFinalizableLivePreviewAdapter({
1140
- draft: {
1141
- flush: () => draftPreview.flush(),
1142
- clear: () => draftStream.clear(),
1143
- discardPending: () => draftStream.discardPending(),
1144
- seal: () => draftStream.seal(),
1145
- id: draftStream.messageId
1146
- },
1147
- buildFinalEdit: () => {
1148
- if (draftPreview.finalizedViaPreviewMessage || hasMedia && !ttsSupplement || typeof previewFinalText !== "string" || hasExplicitReplyDirective || payload.isError) return;
1149
- return {
1150
- content: previewFinalText,
1151
- ...finalPreviewFlags ? { flags: finalPreviewFlags } : {}
1152
- };
1153
- },
1154
- editFinal: async (previewMessageId, edit) => {
1155
- if (isProcessAborted(abortSignal)) throw new Error("process aborted");
1156
- notifyFinalReplyStart();
1157
- await editMessageDiscord(deliverChannelId, previewMessageId, edit, {
1158
- cfg,
1159
- accountId,
1160
- rest: deliveryRest
1161
- });
1162
- },
1163
- onPreviewFinalized: () => {
1164
- draftPreview.markFinalReplyDelivered();
1165
- draftPreview.markPreviewFinalized();
1166
- replyReference.markSent();
1167
- observer?.onFinalReplyDelivered?.();
1168
- },
1169
- buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(effectivePayload) : void 0,
1170
- deliverSupplemental: async (supplementalPayload) => {
1171
- if (isProcessAborted(abortSignal)) return false;
1172
- const supplementalReplyToId = previewReplyToId ?? replyReference.peek() ?? (replyToMode === "all" ? typeof message.id === "string" && message.id ? message.id : ctxPayload.MessageSid : void 0);
1173
- await deliverDiscordReply({
1174
- cfg,
1175
- replies: [supplementalPayload],
1176
- target: deliverTarget,
1177
- token,
1178
- accountId,
1179
- rest: deliveryRest,
1180
- runtime,
1181
- replyToId: supplementalReplyToId,
1182
- replyToMode,
1183
- textLimit,
1184
- maxLinesPerMessage,
1185
- tableMode,
1186
- chunkMode,
1187
- sessionKey: ctxPayload.SessionKey,
1188
- threadBindings,
1189
- mediaLocalRoots,
1190
- kind: info.kind
1191
- });
1192
- return true;
1193
- },
1194
- logPreviewEditFailure: (err) => {
1195
- logVerbose(`discord: preview final edit failed; falling back to standard send (${String(err)})`);
1196
- }
1197
- }),
1198
- deliverNormally: async () => {
1199
- if (isProcessAborted(abortSignal)) return false;
1200
- const fallbackPayload = ttsSupplement && ttsSupplement.visibleTextAlreadyDelivered !== true && !effectivePayload.text?.trim() ? {
1201
- ...effectivePayload,
1202
- text: ttsSupplement.spokenText
1203
- } : effectivePayload;
1204
- const replyToId = replyReference.use();
1085
+ let userFacingFinalDelivered = false;
1086
+ let userFacingFinalDeliveryFailed = false;
1087
+ let pendingToolWarningFinal;
1088
+ const markUserFacingFinalDelivered = () => {
1089
+ userFacingFinalDelivered = true;
1090
+ userFacingFinalDeliveryFailed = false;
1091
+ pendingToolWarningFinal = void 0;
1092
+ draftPreview.markFinalReplyDelivered();
1093
+ observer?.onFinalReplyDelivered?.();
1094
+ };
1095
+ const beforeDiscordPayloadDelivery = (payload, info) => {
1096
+ if (isProcessAborted(abortSignal)) {
1097
+ logVerbose(formatDiscordReplySkip({
1098
+ kind: info.kind,
1099
+ reason: "aborted before delivery",
1100
+ target: deliverTarget,
1101
+ sessionKey: ctxPayload.SessionKey
1102
+ }));
1103
+ return null;
1104
+ }
1105
+ if (payload.isReasoning) {
1106
+ logVerbose(formatDiscordReplySkip({
1107
+ kind: info.kind,
1108
+ reason: "reasoning payload",
1109
+ target: deliverTarget,
1110
+ sessionKey: ctxPayload.SessionKey
1111
+ }));
1112
+ return null;
1113
+ }
1114
+ if (draftPreview.draftStream && draftPreview.isProgressMode && info.kind === "block") {
1115
+ if (!resolveSendableOutboundReplyParts(payload).hasMedia && !payload.isError) return null;
1116
+ }
1117
+ if (info.kind === "final" && !isFallbackOnlyToolWarningFinal(payload)) draftPreview.markFinalReplyStarted();
1118
+ return payload;
1119
+ };
1120
+ const deliverDiscordPayload = async (payload, info, options) => {
1121
+ if (isProcessAborted(abortSignal)) {
1122
+ logVerbose(formatDiscordReplySkip({
1123
+ kind: info.kind,
1124
+ reason: "aborted before delivery",
1125
+ target: deliverTarget,
1126
+ sessionKey: ctxPayload.SessionKey
1127
+ }));
1128
+ return { visibleReplySent: false };
1129
+ }
1130
+ const isFinal = info.kind === "final";
1131
+ if (payload.isReasoning) {
1132
+ logVerbose(formatDiscordReplySkip({
1133
+ kind: info.kind,
1134
+ reason: "reasoning payload",
1135
+ target: deliverTarget,
1136
+ sessionKey: ctxPayload.SessionKey
1137
+ }));
1138
+ return { visibleReplySent: false };
1139
+ }
1140
+ if (isFinal && !options?.allowFallbackOnlyToolWarning && isFallbackOnlyToolWarningFinal(payload)) {
1141
+ if (!userFacingFinalDelivered && (!finalReplyStartNotified || userFacingFinalDeliveryFailed)) pendingToolWarningFinal = {
1142
+ payload,
1143
+ info
1144
+ };
1145
+ return { visibleReplySent: false };
1146
+ }
1147
+ if (isFinal) draftPreview.markFinalReplyStarted();
1148
+ const finalText = isFinal && typeof payload.text === "string" ? await resolveTranscriptBackedChannelFinalText({
1149
+ finalText: payload.text,
1150
+ resolveCandidateText: resolveCurrentTurnTranscriptFinalText
1151
+ }) : payload.text;
1152
+ const effectivePayload = finalText !== payload.text ? {
1153
+ ...payload,
1154
+ text: finalText
1155
+ } : payload;
1156
+ const draftStream = draftPreview.draftStream;
1157
+ if (draftStream && draftPreview.isProgressMode && info.kind === "block") {
1158
+ if (!resolveSendableOutboundReplyParts(effectivePayload).hasMedia && !payload.isError) return { visibleReplySent: false };
1159
+ }
1160
+ if (draftStream && isFinal && (!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted) && !payload.isError) {
1161
+ const hasMedia = resolveSendableOutboundReplyParts(effectivePayload).hasMedia;
1162
+ const ttsSupplement = getReplyPayloadTtsSupplement(effectivePayload);
1163
+ const previewSourceText = finalText ?? ttsSupplement?.spokenText;
1164
+ const previewFinalText = draftPreview.resolvePreviewFinalText(previewSourceText);
1165
+ const previewReplyToId = replyReference.peek();
1166
+ const hasExplicitReplyDirective = Boolean(effectivePayload.replyToTag || effectivePayload.replyToCurrent) || typeof previewSourceText === "string" && /\[\[\s*reply_to(?:_current|\s*:)/i.test(previewSourceText);
1167
+ if ((await deliverWithFinalizableLivePreviewAdapter({
1168
+ kind: info.kind,
1169
+ payload: effectivePayload,
1170
+ adapter: defineFinalizableLivePreviewAdapter({
1171
+ draft: {
1172
+ flush: () => draftPreview.flush(),
1173
+ clear: () => draftStream.clear(),
1174
+ discardPending: () => draftStream.discardPending(),
1175
+ seal: () => draftStream.seal(),
1176
+ id: draftStream.messageId
1177
+ },
1178
+ buildFinalEdit: () => {
1179
+ if (draftPreview.finalizedViaPreviewMessage || hasMedia && !ttsSupplement || typeof previewFinalText !== "string" || hasExplicitReplyDirective || payload.isError) return;
1180
+ return {
1181
+ content: previewFinalText,
1182
+ ...finalPreviewFlags ? { flags: finalPreviewFlags } : {}
1183
+ };
1184
+ },
1185
+ editFinal: async (previewMessageId, edit) => {
1186
+ if (isProcessAborted(abortSignal)) throw new Error("process aborted");
1205
1187
  notifyFinalReplyStart();
1188
+ await editMessageDiscord(deliverChannelId, previewMessageId, edit, {
1189
+ cfg,
1190
+ accountId,
1191
+ rest: deliveryRest
1192
+ });
1193
+ },
1194
+ onPreviewFinalized: () => {
1195
+ markUserFacingFinalDelivered();
1196
+ draftPreview.markPreviewFinalized();
1197
+ replyReference.markSent();
1198
+ },
1199
+ buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(effectivePayload) : void 0,
1200
+ deliverSupplemental: async (supplementalPayload) => {
1201
+ if (isProcessAborted(abortSignal)) return false;
1202
+ const supplementalReplyToId = previewReplyToId ?? replyReference.peek() ?? (replyToMode === "all" ? typeof message.id === "string" && message.id ? message.id : ctxPayload.MessageSid : void 0);
1206
1203
  await deliverDiscordReply({
1207
1204
  cfg,
1208
- replies: [fallbackPayload],
1205
+ replies: [supplementalPayload],
1209
1206
  target: deliverTarget,
1210
1207
  token,
1211
1208
  accountId,
1212
1209
  rest: deliveryRest,
1213
1210
  runtime,
1214
- replyToId,
1211
+ replyToId: supplementalReplyToId,
1215
1212
  replyToMode,
1216
1213
  textLimit,
1217
1214
  maxLinesPerMessage,
@@ -1224,91 +1221,135 @@ async function processDiscordMessage(ctx, observer) {
1224
1221
  });
1225
1222
  return true;
1226
1223
  },
1227
- onNormalDelivered: () => {
1228
- draftPreview.markFinalReplyDelivered();
1229
- replyReference.markSent();
1230
- observer?.onFinalReplyDelivered?.();
1224
+ logPreviewEditFailure: (err) => {
1225
+ logVerbose(`discord: preview final edit failed; falling back to standard send (${String(err)})`);
1231
1226
  }
1232
- })).kind !== "normal-skipped") return;
1233
- }
1234
- if (isProcessAborted(abortSignal)) {
1235
- logVerbose(formatDiscordReplySkip({
1236
- kind: info.kind,
1237
- reason: "aborted before delivery",
1238
- target: deliverTarget,
1239
- sessionKey: ctxPayload.SessionKey
1240
- }));
1241
- return;
1242
- }
1243
- const replyToId = replyReference.use();
1244
- if (isFinal) notifyFinalReplyStart();
1245
- await deliverDiscordReply({
1246
- cfg,
1247
- replies: [effectivePayload],
1248
- target: deliverTarget,
1249
- token,
1250
- accountId,
1251
- rest: deliveryRest,
1252
- runtime,
1253
- replyToId,
1254
- replyToMode,
1255
- textLimit,
1256
- maxLinesPerMessage,
1257
- tableMode,
1258
- chunkMode,
1259
- sessionKey: ctxPayload.SessionKey,
1260
- threadBindings,
1261
- mediaLocalRoots,
1262
- kind: info.kind
1263
- });
1264
- replyReference.markSent();
1265
- if (isFinal && payload.isError !== true) {
1266
- draftPreview.markFinalReplyDelivered();
1267
- observer?.onFinalReplyDelivered?.();
1268
- }
1269
- },
1270
- onError: (err, info) => {
1271
- runtime.error(danger(formatDiscordReplyDeliveryFailure({
1227
+ }),
1228
+ deliverNormally: async () => {
1229
+ if (isProcessAborted(abortSignal)) return false;
1230
+ const fallbackPayload = ttsSupplement && ttsSupplement.visibleTextAlreadyDelivered !== true && !effectivePayload.text?.trim() ? {
1231
+ ...effectivePayload,
1232
+ text: ttsSupplement.spokenText
1233
+ } : effectivePayload;
1234
+ const replyToId = replyReference.use();
1235
+ notifyFinalReplyStart();
1236
+ await deliverDiscordReply({
1237
+ cfg,
1238
+ replies: [fallbackPayload],
1239
+ target: deliverTarget,
1240
+ token,
1241
+ accountId,
1242
+ rest: deliveryRest,
1243
+ runtime,
1244
+ replyToId,
1245
+ replyToMode,
1246
+ textLimit,
1247
+ maxLinesPerMessage,
1248
+ tableMode,
1249
+ chunkMode,
1250
+ sessionKey: ctxPayload.SessionKey,
1251
+ threadBindings,
1252
+ mediaLocalRoots,
1253
+ kind: info.kind
1254
+ });
1255
+ return true;
1256
+ },
1257
+ onNormalDelivered: () => {
1258
+ markUserFacingFinalDelivered();
1259
+ replyReference.markSent();
1260
+ }
1261
+ })).kind !== "normal-skipped") return { visibleReplySent: true };
1262
+ }
1263
+ if (isProcessAborted(abortSignal)) {
1264
+ logVerbose(formatDiscordReplySkip({
1272
1265
  kind: info.kind,
1273
- err,
1266
+ reason: "aborted before delivery",
1274
1267
  target: deliverTarget,
1275
1268
  sessionKey: ctxPayload.SessionKey
1276
- })));
1277
- },
1278
- onReplyStart: async () => {
1279
- if (isProcessAborted(abortSignal)) return;
1280
- await replyPipeline.typingCallbacks?.onReplyStart();
1281
- await statusReactions.setThinking();
1269
+ }));
1270
+ return { visibleReplySent: false };
1282
1271
  }
1283
- });
1272
+ const replyToId = replyReference.use();
1273
+ if (isFinal) notifyFinalReplyStart();
1274
+ await deliverDiscordReply({
1275
+ cfg,
1276
+ replies: [effectivePayload],
1277
+ target: deliverTarget,
1278
+ token,
1279
+ accountId,
1280
+ rest: deliveryRest,
1281
+ runtime,
1282
+ replyToId,
1283
+ replyToMode,
1284
+ textLimit,
1285
+ maxLinesPerMessage,
1286
+ tableMode,
1287
+ chunkMode,
1288
+ sessionKey: ctxPayload.SessionKey,
1289
+ threadBindings,
1290
+ mediaLocalRoots,
1291
+ kind: info.kind
1292
+ });
1293
+ replyReference.markSent();
1294
+ if (isFinal && payload.isError !== true) markUserFacingFinalDelivered();
1295
+ return { visibleReplySent: true };
1296
+ };
1297
+ const onDiscordDeliveryError = (err, info) => {
1298
+ if (info.kind === "final" && finalReplyStartNotified && !userFacingFinalDelivered) userFacingFinalDeliveryFailed = true;
1299
+ runtime.error(danger(formatDiscordReplyDeliveryFailure({
1300
+ kind: info.kind,
1301
+ err,
1302
+ target: deliverTarget,
1303
+ sessionKey: ctxPayload.SessionKey
1304
+ })));
1305
+ };
1306
+ const onDiscordReplyStart = async () => {
1307
+ if (isProcessAborted(abortSignal)) return;
1308
+ await replyPipeline.typingCallbacks?.onReplyStart();
1309
+ await statusReactions.setThinking();
1310
+ };
1284
1311
  const resolvedBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(discordConfig);
1285
1312
  let dispatchResult = null;
1286
1313
  let dispatchError = false;
1287
1314
  let dispatchAborted = false;
1288
- let dispatchSettledBeforeStart = false;
1289
- const settleDispatchBeforeStart = async () => {
1290
- dispatchSettledBeforeStart = true;
1291
- await settleReplyDispatcher({
1292
- dispatcher,
1293
- onSettled: () => {
1294
- markRunComplete();
1295
- markDispatchIdle();
1296
- }
1297
- });
1315
+ const deliverPendingToolWarningFinalIfNeeded = async () => {
1316
+ if (!pendingToolWarningFinal || userFacingFinalDelivered || isProcessAborted(abortSignal)) return;
1317
+ const pending = pendingToolWarningFinal;
1318
+ pendingToolWarningFinal = void 0;
1319
+ try {
1320
+ return await deliverDiscordPayload(pending.payload, pending.info, { allowFallbackOnlyToolWarning: true });
1321
+ } catch (err) {
1322
+ dispatchError = true;
1323
+ onDiscordDeliveryError(err, pending.info);
1324
+ return { visibleReplySent: false };
1325
+ }
1298
1326
  };
1299
1327
  try {
1300
1328
  if (isProcessAborted(abortSignal)) {
1301
1329
  dispatchAborted = true;
1302
- await settleDispatchBeforeStart();
1303
1330
  return;
1304
1331
  }
1305
- const preparedResult = await runPreparedInboundReplyTurn({
1332
+ const preparedResult = await dispatchChannelInboundReply({
1333
+ cfg,
1306
1334
  channel: "discord",
1307
1335
  accountId: route.accountId,
1336
+ agentId: route.agentId,
1308
1337
  routeSessionKey: persistedSessionKey,
1309
1338
  storePath: turn.storePath,
1310
1339
  ctxPayload,
1311
1340
  recordInboundSession,
1341
+ dispatchReplyWithBufferedBlockDispatcher,
1342
+ dispatcherOptions: {
1343
+ ...replyPipeline,
1344
+ humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
1345
+ beforeDeliver: beforeDiscordPayloadDelivery,
1346
+ onReplyStart: onDiscordReplyStart,
1347
+ onFreshSettledDelivery: deliverPendingToolWarningFinalIfNeeded
1348
+ },
1349
+ delivery: {
1350
+ deliver: deliverDiscordPayload,
1351
+ onError: onDiscordDeliveryError
1352
+ },
1312
1353
  record: turn.record,
1313
1354
  history: isRoomEvent ? void 0 : {
1314
1355
  isGroup: isGuildMessage,
@@ -1316,111 +1357,104 @@ async function processDiscordMessage(ctx, observer) {
1316
1357
  historyMap: guildHistories,
1317
1358
  limit: historyLimit
1318
1359
  },
1319
- onPreDispatchFailure: settleDispatchBeforeStart,
1320
- runDispatch: async () => await dispatchInboundMessage({
1321
- ctx: ctxPayload,
1322
- cfg,
1323
- dispatcher,
1324
- replyOptions: {
1325
- ...replyOptions,
1326
- abortSignal,
1327
- skillFilter: channelConfig?.skills,
1328
- sourceReplyDeliveryMode,
1329
- queuedDeliveryCorrelations: isRoomEvent ? [{ begin: beginDeliveryCorrelation }] : void 0,
1330
- suppressTyping: isRoomEvent ? true : void 0,
1331
- allowProgressCallbacksWhenSourceDeliverySuppressed: sourceRepliesAreToolOnly && draftPreview.draftStream && draftPreview.isProgressMode ? true : void 0,
1332
- disableBlockStreaming: sourceRepliesAreToolOnly ? true : draftPreview.disableBlockStreamingForDraft ?? (typeof resolvedBlockStreamingEnabled === "boolean" ? !resolvedBlockStreamingEnabled : void 0),
1333
- onPartialReply: draftPreview.draftStream && !draftPreview.isProgressMode ? (payload) => draftPreview.updateFromPartial(payload.text) : void 0,
1334
- onAssistantMessageStart: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
1335
- onReasoningEnd: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
1336
- onModelSelected,
1337
- suppressDefaultToolProgressMessages: draftPreview.suppressDefaultToolProgressMessages ? true : void 0,
1338
- onReasoningStream: async (payload) => {
1339
- await statusReactions.setThinking();
1340
- const formattedText = payload?.text ? formatReasoningMessage(payload.text) : void 0;
1341
- await draftPreview.pushReasoningProgress(formattedText);
1342
- },
1343
- onToolStart: async (payload) => {
1344
- if (isProcessAborted(abortSignal)) return;
1345
- await maybeBindStatusReactionsToToolReaction(payload);
1346
- await statusReactions.setTool(payload.name);
1347
- await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
1348
- event: "tool",
1349
- name: payload.name,
1350
- phase: payload.phase,
1351
- args: payload.args
1352
- }, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
1353
- },
1354
- onItemEvent: async (payload) => {
1355
- await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
1356
- event: "item",
1357
- itemId: payload.itemId,
1358
- itemKind: payload.kind,
1359
- title: payload.title,
1360
- name: payload.name,
1361
- phase: payload.phase,
1362
- status: payload.status,
1363
- summary: payload.summary,
1364
- progressText: payload.progressText,
1365
- meta: payload.meta
1366
- }));
1367
- },
1368
- onPlanUpdate: async (payload) => {
1369
- if (payload.phase !== "update") return;
1370
- await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1371
- event: "plan",
1372
- phase: payload.phase,
1373
- title: payload.title,
1374
- explanation: payload.explanation,
1375
- steps: payload.steps
1376
- }));
1377
- },
1378
- onApprovalEvent: async (payload) => {
1379
- if (payload.phase !== "requested") return;
1380
- await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1381
- event: "approval",
1382
- phase: payload.phase,
1383
- title: payload.title,
1384
- command: payload.command,
1385
- reason: payload.reason,
1386
- message: payload.message
1387
- }));
1388
- },
1389
- onCommandOutput: async (payload) => {
1390
- if (payload.phase !== "end") return;
1391
- await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1392
- event: "command-output",
1393
- phase: payload.phase,
1394
- title: payload.title,
1395
- name: payload.name,
1396
- status: payload.status,
1397
- exitCode: payload.exitCode
1398
- }));
1399
- },
1400
- onPatchSummary: async (payload) => {
1401
- if (payload.phase !== "end") return;
1402
- await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1403
- event: "patch",
1404
- phase: payload.phase,
1405
- title: payload.title,
1406
- name: payload.name,
1407
- added: payload.added,
1408
- modified: payload.modified,
1409
- deleted: payload.deleted,
1410
- summary: payload.summary
1411
- }));
1412
- },
1413
- onCompactionStart: async () => {
1414
- if (isProcessAborted(abortSignal)) return;
1415
- await statusReactions.setCompacting();
1416
- },
1417
- onCompactionEnd: async () => {
1418
- if (isProcessAborted(abortSignal)) return;
1419
- statusReactions.cancelPending();
1420
- await statusReactions.setThinking();
1421
- }
1360
+ replyOptions: {
1361
+ abortSignal,
1362
+ skillFilter: channelConfig?.skills,
1363
+ sourceReplyDeliveryMode,
1364
+ queuedDeliveryCorrelations: isRoomEvent ? [{ begin: beginDeliveryCorrelation }] : void 0,
1365
+ suppressTyping: isRoomEvent ? true : void 0,
1366
+ allowProgressCallbacksWhenSourceDeliverySuppressed: sourceRepliesAreToolOnly && draftPreview.draftStream && draftPreview.isProgressMode ? true : void 0,
1367
+ disableBlockStreaming: sourceRepliesAreToolOnly ? true : draftPreview.disableBlockStreamingForDraft ?? (typeof resolvedBlockStreamingEnabled === "boolean" ? !resolvedBlockStreamingEnabled : void 0),
1368
+ onPartialReply: draftPreview.draftStream && !draftPreview.isProgressMode ? (payload) => draftPreview.updateFromPartial(payload.text) : void 0,
1369
+ onAssistantMessageStart: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
1370
+ onReasoningEnd: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
1371
+ onModelSelected,
1372
+ suppressDefaultToolProgressMessages: draftPreview.suppressDefaultToolProgressMessages ? true : void 0,
1373
+ onReasoningStream: async (payload) => {
1374
+ await statusReactions.setThinking();
1375
+ const formattedText = payload?.text ? formatReasoningMessage(payload.text) : void 0;
1376
+ await draftPreview.pushReasoningProgress(formattedText);
1377
+ },
1378
+ onToolStart: async (payload) => {
1379
+ if (isProcessAborted(abortSignal)) return;
1380
+ await maybeBindStatusReactionsToToolReaction(payload);
1381
+ await statusReactions.setTool(payload.name);
1382
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
1383
+ event: "tool",
1384
+ name: payload.name,
1385
+ phase: payload.phase,
1386
+ args: payload.args
1387
+ }, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
1388
+ },
1389
+ onItemEvent: async (payload) => {
1390
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
1391
+ event: "item",
1392
+ itemId: payload.itemId,
1393
+ itemKind: payload.kind,
1394
+ title: payload.title,
1395
+ name: payload.name,
1396
+ phase: payload.phase,
1397
+ status: payload.status,
1398
+ summary: payload.summary,
1399
+ progressText: payload.progressText,
1400
+ meta: payload.meta
1401
+ }));
1402
+ },
1403
+ onPlanUpdate: async (payload) => {
1404
+ if (payload.phase !== "update") return;
1405
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1406
+ event: "plan",
1407
+ phase: payload.phase,
1408
+ title: payload.title,
1409
+ explanation: payload.explanation,
1410
+ steps: payload.steps
1411
+ }));
1412
+ },
1413
+ onApprovalEvent: async (payload) => {
1414
+ if (payload.phase !== "requested") return;
1415
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1416
+ event: "approval",
1417
+ phase: payload.phase,
1418
+ title: payload.title,
1419
+ command: payload.command,
1420
+ reason: payload.reason,
1421
+ message: payload.message
1422
+ }));
1423
+ },
1424
+ onCommandOutput: async (payload) => {
1425
+ if (payload.phase !== "end") return;
1426
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1427
+ event: "command-output",
1428
+ phase: payload.phase,
1429
+ title: payload.title,
1430
+ name: payload.name,
1431
+ status: payload.status,
1432
+ exitCode: payload.exitCode
1433
+ }));
1434
+ },
1435
+ onPatchSummary: async (payload) => {
1436
+ if (payload.phase !== "end") return;
1437
+ await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
1438
+ event: "patch",
1439
+ phase: payload.phase,
1440
+ title: payload.title,
1441
+ name: payload.name,
1442
+ added: payload.added,
1443
+ modified: payload.modified,
1444
+ deleted: payload.deleted,
1445
+ summary: payload.summary
1446
+ }));
1447
+ },
1448
+ onCompactionStart: async () => {
1449
+ if (isProcessAborted(abortSignal)) return;
1450
+ await statusReactions.setCompacting();
1451
+ },
1452
+ onCompactionEnd: async () => {
1453
+ if (isProcessAborted(abortSignal)) return;
1454
+ statusReactions.cancelPending();
1455
+ await statusReactions.setThinking();
1422
1456
  }
1423
- })
1457
+ }
1424
1458
  });
1425
1459
  if (!preparedResult.dispatched) return;
1426
1460
  dispatchResult = preparedResult.dispatchResult;
@@ -1437,14 +1471,7 @@ async function processDiscordMessage(ctx, observer) {
1437
1471
  throw err;
1438
1472
  } finally {
1439
1473
  endDiscordInboundEventDeliveryCorrelation();
1440
- try {
1441
- await draftPreview.cleanup();
1442
- } finally {
1443
- if (!dispatchSettledBeforeStart) {
1444
- markRunComplete();
1445
- markDispatchIdle();
1446
- }
1447
- }
1474
+ await draftPreview.cleanup();
1448
1475
  const finalDeliveryFailed = (dispatchResult?.failedCounts?.final ?? 0) > 0;
1449
1476
  if (statusReactionsActive) if (dispatchAborted) if (removeAckAfterReply) statusReactions.clear();
1450
1477
  else statusReactions.restoreInitial();