@openclaw/discord 2026.7.2-beta.7 → 2026.8.1-beta.2

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 (95) hide show
  1. package/dist/{account-inspect-SXiCBCgP.js → account-inspect-CqzrH14k.js} +48 -42
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/action-runtime-api.js +1 -1
  4. package/dist/{active-turn-thread-route-MlKWp8RD.js → active-turn-thread-route-fmHl1HNd.js} +9 -11
  5. package/dist/{activities-api-DTKsuDxf.js → activities-api-DS7vpdtp.js} +4 -4
  6. package/dist/activities-api.js +1 -1
  7. package/dist/{allow-list-DzKrtfah.js → allow-list-DnxIlPP2.js} +2 -3
  8. package/dist/{api-DnrK9gML.js → api-DNVbJyEy.js} +7 -2
  9. package/dist/api.js +15 -15
  10. package/dist/{approval-handler.runtime-HymvFTYn.js → approval-handler.runtime-B6yEV79q.js} +5 -5
  11. package/dist/{approval-message-safety-DEyKVsAw.js → approval-message-safety-AcIxvrf3.js} +1 -1
  12. package/dist/{audit-CjsrabP8.js → audit-Db-rlntl.js} +4 -4
  13. package/dist/{channel-vKa-76xY.js → channel-CnnbJSt5.js} +46 -72
  14. package/dist/{channel-actions-uXFTJX3o.js → channel-actions-5s_hjYKd.js} +4 -4
  15. package/dist/{channel-actions.runtime-CIYU90sl.js → channel-actions.runtime-CLKrx6tr.js} +53 -36
  16. package/dist/channel-config-api.js +1 -1
  17. package/dist/channel-plugin-api.js +1 -1
  18. package/dist/{channel.setup-CmcwZ_qd.js → channel.setup-BFAO-V1P.js} +15 -53
  19. package/dist/{config-schema-WHRQcY19.js → config-schema-BgEskFAs.js} +9 -4
  20. package/dist/{directory-config-D_LojtCe.js → directory-config-BVX7Cflc.js} +1 -1
  21. package/dist/directory-contract-api.js +1 -1
  22. package/dist/{directory-live-Bw5Ephzv.js → directory-live-DGIGxpIo.js} +6 -3
  23. package/dist/{doctor-Bc4S1qyd.js → doctor-BzC-TLnB.js} +4 -4
  24. package/dist/{doctor-contract-CCFMIDk9.js → doctor-contract-DLa-BTiU.js} +2 -3
  25. package/dist/doctor-contract-api.js +147 -2
  26. package/dist/{handle-action.guild-admin-D6VRRW-C.js → handle-action.guild-admin-BNVTUISF.js} +4 -7
  27. package/dist/{inbound-event-delivery-CEPlt2uz.js → inbound-event-delivery-De2qxTrP.js} +4 -25
  28. package/dist/index.js +4 -2
  29. package/dist/{message-handler-CeiSulIw.js → message-handler-Unk_f10F.js} +41 -32
  30. package/dist/{message-handler.preflight-DibgQFf2.js → message-handler.preflight-C9zecxR9.js} +50 -26
  31. package/dist/{message-handler.process-Dnc_oz9y.js → message-handler.process-DWQ6UHHu.js} +130 -166
  32. package/dist/{message-utils-BgTwT_q7.js → message-utils-BSCPVgy0.js} +4 -4
  33. package/dist/{model-picker-preference-primitives-D25pnCOu.js → model-picker-preference-primitives-BHT1LkR_.js} +3 -3
  34. package/dist/{outbound-adapter-DtE-i2OJ.js → outbound-adapter-D9nRivT1.js} +91 -32
  35. package/dist/{outbound-components-C7GVyfPh.js → outbound-components-cTzwBJ60.js} +3 -3
  36. package/dist/{outbound-session-route-H-HoyH5U.js → outbound-session-route-DxLkrgKQ.js} +68 -95
  37. package/dist/{pluralkit-CD4B11JB.js → pluralkit-DdiJ9KMK.js} +1 -1
  38. package/dist/{preflight-audio-BjnQZ5yS.js → preflight-audio-DrnqFA8U.js} +22 -28
  39. package/dist/{probe-BeWKhjqX.js → probe-Bl-k5_Ku.js} +36 -12
  40. package/dist/probe.runtime-M9fREKvp.js +2 -0
  41. package/dist/{provider-DfQJX3z5.js → provider-DvSNb-2v.js} +969 -1248
  42. package/dist/{provider-session.runtime-Bo88k0pV.js → provider-session.runtime-BUEqD0rA.js} +3 -3
  43. package/dist/provider.runtime-C9PnJZbZ.js +2 -0
  44. package/dist/realtime-session.runtime-C7wen-aF.js +1340 -0
  45. package/dist/{resolve-allowlist-common-DHERApxi.js → resolve-allowlist-common-D04lpEzb.js} +2 -2
  46. package/dist/{resolve-channels-BjepAj8b.js → resolve-channels-Dm7jEeg6.js} +4 -4
  47. package/dist/{resolve-users-Bc28htgt.js → resolve-users-bktrCUUU.js} +3 -3
  48. package/dist/{chunk-DX4jpFQP.js → retry-CLpJcXXf.js} +291 -68
  49. package/dist/{runtime-_aRC6V2s.js → runtime-DGsSyDTS.js} +30 -25
  50. package/dist/{runtime-BmW_j_hl.js → runtime-DoHjckYm.js} +4 -6
  51. package/dist/runtime-api.actions.js +2 -2
  52. package/dist/runtime-api.js +22 -23
  53. package/dist/runtime-api.lookup.js +5 -5
  54. package/dist/runtime-api.monitor-CHxrFPnW.js +5 -0
  55. package/dist/runtime-api.monitor.js +5 -5
  56. package/dist/runtime-api.send.js +6 -6
  57. package/dist/runtime-api.threads.js +5 -6
  58. package/dist/sdk-runtime-QpY8PvCh.js +370 -0
  59. package/dist/{send-CdkZ6zeW.js → send-B7Q6Oc1I.js} +175 -86
  60. package/dist/{send.components-5uqwkjgE.js → send.components-iJyFXw9L.js} +24 -28
  61. package/dist/{send.outbound-D-80Ej-t.js → send.outbound-DS5tHwfR.js} +37 -24
  62. package/dist/{send.reactions-CtlmGd_o.js → send.reactions-BAn0iiVQ.js} +14 -21
  63. package/dist/{send.receipt-CuwRrpFz.js → send.receipt-IPI1p-SP.js} +19 -1
  64. package/dist/{send.shared-jpvAG_u6.js → send.shared-BmwFjW6T.js} +16 -85
  65. package/dist/{sender-identity-mG_zsdox.js → sender-identity-QtalnCYv.js} +1 -1
  66. package/dist/setup-entry.js +0 -5
  67. package/dist/setup-plugin-api.js +1 -1
  68. package/dist/{shared-CXF1tup1.js → shared-BIXIX6uJ.js} +16 -17
  69. package/dist/{subagent-hooks-Dxy7Fmnq.js → subagent-hooks-CTt5KCJr.js} +2 -2
  70. package/dist/subagent-hooks-api.js +2 -6
  71. package/dist/subagent-progress-DhaE_h8a.js +109 -0
  72. package/dist/{system-events-DdUqf91B.js → system-events-CTqDf6_s.js} +2 -2
  73. package/dist/{target-resolver-C3s_2m0t.js → target-resolver-maOKSqO0.js} +2 -2
  74. package/dist/targets-yUFTHoAx.js +3 -0
  75. package/dist/{thread-bindings-C-M-ly7P.js → thread-bindings-3L8p82GV.js} +5 -7
  76. package/dist/{thread-bindings.manager-DYvSlrva.js → thread-bindings.manager-DCIH6WDC.js} +257 -29
  77. package/dist/{thread-bindings.session-updates-DAIlp7Px.js → thread-bindings.session-updates-bJ2KQFY6.js} +1 -1
  78. package/dist/{thread-bindings.state-Cgi5uRn-.js → thread-bindings.state-aeV8HNgV.js} +2 -15
  79. package/dist/{transcripts-source-DIKGsTGT.js → transcripts-source-CfFMexGt.js} +1 -1
  80. package/dist/transcripts-source-api.js +1 -1
  81. package/dist/{typing--Mzo2kM7.js → typing-CS-nNVoe.js} +1 -1
  82. package/dist/voice-runtime-BEdqNcST.js +2459 -0
  83. package/openclaw.plugin.json +203 -19
  84. package/package.json +15 -9
  85. package/skills/discord/SKILL.md +22 -115
  86. package/dist/legacy-state-migrations-api.js +0 -138
  87. package/dist/manager.runtime-Bjav3pfC.js +0 -3655
  88. package/dist/preflight-audio.runtime-CoCXMM8r.js +0 -7
  89. package/dist/probe.runtime-DqFGFMbZ.js +0 -2
  90. package/dist/provider.runtime-Cy7XZmyh.js +0 -2
  91. package/dist/runtime-api.monitor-CCRubd3-.js +0 -5
  92. package/dist/session-binding-contract-api.js +0 -2
  93. package/dist/subagent-progress-DI3sYLLo.js +0 -664
  94. package/dist/targets-Dv_RN7vT.js +0 -3
  95. package/dist/thread-bindings.discord-api-BoDNqamb.js +0 -246
@@ -1,10 +1,11 @@
1
- import { A as readDiscordMessage, At as createGuildBan, Bt as listGuildChannels, Ct as listChannelMessages, D as RateLimitError, E as DiscordError, Et as searchGuildMessages, Ft as deleteChannelPermission, Gt as putChannelPermission, Ht as listGuildRoles, It as getGuild, Jt as timeoutGuildMember, Kt as removeGuildMember, Lt as getGuildMember, Mt as createGuildEmoji, Nt as createGuildScheduledEvent, O as isUnknownDiscordVoiceStateError, Ot as unpinChannelMessage, Pt as createGuildSticker, Rt as getGuildVoiceState, St as listChannelArchivedThreads, Tt as pinChannelMessage, Ut as listGuildScheduledEvents, Vt as listGuildEmojis, Wt as moveGuildChannels, Yt as __exportAll, _t as editChannel, bt as getChannelMessage, gt as deleteChannelMessage, ht as deleteChannel, j as readRetryAfter, jt as createGuildChannel, k as readDiscordCode, kt as addGuildMemberRole, mt as createThread, pt as createChannelMessage, qt as removeGuildMemberRole, vt as editChannelMessage, wt as listChannelPins, yt as getChannel, zt as listGuildActiveThreads } from "./chunk-DX4jpFQP.js";
1
+ import { $t as removeGuildMemberRole, At as listChannelPins, Bt as createGuildSticker, Ct as editChannel, Et as getChannelMessage, F as readDiscordMessage, Ft as addGuildMemberRole, Gt as listGuildActiveThreads, Ht as getGuild, I as readRetryAfter, It as createGuildBan, Jt as listGuildRoles, Kt as listGuildChannels, Lt as createGuildChannel, M as RateLimitError, Mt as searchGuildMessages, N as isUnknownDiscordVoiceStateError, Nt as sendChannelTyping, Ot as listChannelArchivedThreads, P as readDiscordCode, Pt as unpinChannelMessage, Qt as removeGuildMember, Rt as createGuildEmoji, St as deleteChannelMessage, Tt as getChannel, Ut as getGuildMember, Vt as deleteChannelPermission, Wt as getGuildVoiceState, Xt as moveGuildChannels, Yt as listGuildScheduledEvents, Zt as putChannelPermission, bt as createThread, en as timeoutGuildMember, i as recordDiscordMessageCreateAmbiguity, j as DiscordError, jt as pinChannelMessage, kt as listChannelMessages, n as createDiscordRetryRunner, qt as listGuildEmojis, t as classifyDiscordDeliveryFailure, tn as __exportAll, wt as editChannelMessage, xt as deleteChannel, zt as createGuildScheduledEvent } from "./retry-CLpJcXXf.js";
2
2
  import { s as resolveDiscordAccount } from "./accounts-DJTf1LtA.js";
3
- import { A as hasAllGuildPermissionsDiscord, C as DISCORD_MAX_STICKER_BYTES, D as canViewDiscordGuildChannel, E as canManageGuildRoleDiscord, L as resolveDiscordClientAccountContext, M as hasAnyGuildPermissionDiscord, O as fetchChannelPermissionsDiscord, P as createDiscordClient, R as resolveDiscordRest, S as DISCORD_MAX_EVENT_COVER_BYTES, T as canManageGuildMemberRoleDiscord, W as parseAndResolveChannelRecipient, g as createDiscordMessageNonce, j as hasAnyChannelPermissionDiscord, k as fetchMemberGuildPermissionsDiscord, l as resolveChannelId, o as normalizeEmojiName, t as buildDiscordSendError, w as DiscordSendError, x as DISCORD_MAX_EMOJI_BYTES, z as DISCORD_REST_TIMEOUT_MS } from "./send.shared-jpvAG_u6.js";
4
- import { n as sendPollDiscord, o as rewriteDiscordKnownMentions, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-D-80Ej-t.js";
5
- import { n as createDiscordSendResult } from "./send.receipt-CuwRrpFz.js";
3
+ import { t as parseDiscordRetryAfterBodySeconds } from "./retry-after-DdLPmPoa.js";
4
+ import { A as hasAllGuildPermissionsDiscord, C as DISCORD_MAX_STICKER_BYTES, D as canViewDiscordGuildChannel, E as canManageGuildRoleDiscord, L as resolveDiscordClientAccountContext, M as hasAnyGuildPermissionDiscord, O as fetchChannelPermissionsDiscord, P as createDiscordClient, R as resolveDiscordRest, S as DISCORD_MAX_EVENT_COVER_BYTES, T as canManageGuildMemberRoleDiscord, W as parseAndResolveChannelRecipient, _ as resolveDiscordMessageFlags, b as resolveDiscordSuppressEmbeds, g as createDiscordMessageNonce, j as hasAnyChannelPermissionDiscord, k as fetchMemberGuildPermissionsDiscord, l as resolveChannelId, n as buildDiscordTextChunks, o as normalizeEmojiName, p as sendDiscordText, t as buildDiscordSendError, w as DiscordSendError, x as DISCORD_MAX_EMOJI_BYTES, z as DISCORD_REST_TIMEOUT_MS } from "./send.shared-BmwFjW6T.js";
5
+ import { n as sendPollDiscord, o as rewriteDiscordKnownMentions, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-DS5tHwfR.js";
6
+ import { r as createDiscordSendResult } from "./send.receipt-IPI1p-SP.js";
6
7
  import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS } from "./timeouts-B9cy6RvQ.js";
7
- import { a as sendTypingDiscord, i as removeReactionDiscord, n as reactMessageDiscord, r as removeOwnReactionsDiscord, t as fetchReactionsDiscord } from "./send.reactions-CtlmGd_o.js";
8
+ import { i as removeReactionDiscord, n as reactMessageDiscord, r as removeOwnReactionsDiscord, t as fetchReactionsDiscord } from "./send.reactions-BAn0iiVQ.js";
8
9
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
9
10
  import fs from "node:fs/promises";
10
11
  import { ChannelType } from "discord-api-types/v10";
@@ -18,11 +19,11 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
19
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
19
20
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
20
21
  import { recordOutboundMessageIdentity } from "openclaw/plugin-sdk/channel-outbound";
21
- import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, extensionForMime, maxBytesForKind, parseFfprobeCodecAndSampleRate, runFfmpeg, runFfprobe, unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
22
+ import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, buildOutboundMediaLoadOptions, extensionForMime, maxBytesForKind, parseFfprobeCodecAndSampleRate, runFfmpeg, runFfprobe, unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
22
23
  import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
23
24
  import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
24
25
  import path from "node:path";
25
- import { resolvePreferredOpenClawTmpDir, tempWorkspace } from "openclaw/plugin-sdk/temp-path";
26
+ import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
26
27
  import { writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
27
28
  //#region extensions/discord/src/send.channels.ts
28
29
  async function createChannelDiscord(payload, opts) {
@@ -218,8 +219,11 @@ async function banMemberDiscord(payload, opts) {
218
219
  }
219
220
  //#endregion
220
221
  //#region extensions/discord/src/send.messages.ts
221
- function formatDiscordThreadInitialMessageError(error) {
222
- return error instanceof Error ? error.message : String(error);
222
+ const DISCORD_THREAD_TRANSPORT_ONLY_MAX_LINES = Number.MAX_SAFE_INTEGER;
223
+ function resolveDiscordThreadStarterMessageId(thread) {
224
+ const starterMessage = "message" in thread ? thread.message : void 0;
225
+ if (starterMessage && typeof starterMessage === "object" && "id" in starterMessage && typeof starterMessage.id === "string") return starterMessage.id;
226
+ return thread.id;
223
227
  }
224
228
  function assertDiscordResponseArray(value, label) {
225
229
  if (!Array.isArray(value)) throw new Error(`Unexpected Discord response for ${label}: expected array.`);
@@ -233,12 +237,22 @@ function resolveDefaultThreadAutoArchiveDuration(channel) {
233
237
  if (!channel || !("default_auto_archive_duration" in channel)) return;
234
238
  return channel.default_auto_archive_duration;
235
239
  }
240
+ function describeDiscordThreadInitialMessageFailure(delivery) {
241
+ if (delivery?.failedChunkDelivery === "unknown") return delivery.deliveredChunkCount > 0 ? "Discord thread was created, but delivery of the remaining initial content could not be confirmed" : "Discord thread was created, but initial message delivery could not be confirmed";
242
+ return delivery && delivery.deliveredChunkCount > 0 ? "Discord thread was created, but its initial content was only partially delivered" : "Discord thread was created, but sending the initial message failed";
243
+ }
236
244
  var DiscordThreadInitialMessageError = class extends Error {
237
- constructor(thread, error) {
238
- const initialMessageError = formatDiscordThreadInitialMessageError(error);
239
- super(`Discord thread was created, but sending the initial message failed: ${initialMessageError}`);
245
+ constructor(thread, error, initialMessageDelivery) {
246
+ const initialMessageError = formatErrorMessage(error);
247
+ const initialMessageWarning = describeDiscordThreadInitialMessageFailure(initialMessageDelivery);
248
+ super(`${initialMessageWarning}: ${initialMessageError}`, { cause: error });
240
249
  this.name = "DiscordThreadInitialMessageError";
250
+ this.initialMessageDelivery = initialMessageDelivery ? {
251
+ ...initialMessageDelivery,
252
+ deliveredMessageIds: [...initialMessageDelivery.deliveredMessageIds]
253
+ } : void 0;
241
254
  this.initialMessageError = initialMessageError;
255
+ this.initialMessageWarning = initialMessageWarning;
242
256
  this.thread = thread;
243
257
  }
244
258
  };
@@ -278,7 +292,7 @@ async function listPinsDiscord(channelId, opts) {
278
292
  return await listChannelPins(resolveDiscordRest(opts), channelId);
279
293
  }
280
294
  async function createThreadDiscord(channelId, payload, opts) {
281
- const rest = resolveDiscordRest(opts);
295
+ const { rest, request } = createDiscordClient(opts);
282
296
  const body = { name: payload.name };
283
297
  if (!payload.messageId && payload.type !== void 0) body.type = payload.type;
284
298
  let channel;
@@ -288,16 +302,52 @@ async function createThreadDiscord(channelId, payload, opts) {
288
302
  const archiveDuration = payload.autoArchiveMinutes ?? resolveDefaultThreadAutoArchiveDuration(channel);
289
303
  if (archiveDuration !== void 0) body.auto_archive_duration = archiveDuration;
290
304
  const isForumLike = channel?.type === ChannelType.GuildForum || channel?.type === ChannelType.GuildMedia;
305
+ const initialMessageChunks = buildDiscordTextChunks(isForumLike ? payload.content?.trim() ? payload.content : payload.name : payload.content?.trim() ? payload.content : "", { maxLinesPerMessage: DISCORD_THREAD_TRANSPORT_ONLY_MAX_LINES });
291
306
  if (isForumLike) {
292
- body.message = { content: payload.content?.trim() ? payload.content : payload.name };
307
+ body.message = { content: initialMessageChunks[0] ?? payload.name };
293
308
  if (payload.appliedTags?.length) body.applied_tags = payload.appliedTags;
294
309
  }
295
310
  if (!payload.messageId && !isForumLike && body.type === void 0) body.type = ChannelType.PublicThread;
296
311
  const thread = await createThread(rest, channelId, { body }, payload.messageId);
297
- if (!isForumLike && payload.content?.trim() && "id" in thread) try {
298
- await createChannelMessage(rest, thread.id, { body: { content: payload.content } });
299
- } catch (error) {
300
- throw new DiscordThreadInitialMessageError(thread, error);
312
+ const followupChunks = isForumLike ? initialMessageChunks.slice(1) : initialMessageChunks;
313
+ if (followupChunks.length && "id" in thread) {
314
+ const deliveredMessageIds = isForumLike ? [resolveDiscordThreadStarterMessageId(thread)] : [];
315
+ let deliveredChunkCount = isForumLike ? 1 : 0;
316
+ const firstFollowupChunkIndex = isForumLike ? 1 : 0;
317
+ for (const [followupIndex, content] of followupChunks.entries()) {
318
+ let chunkMayHaveDelivered = false;
319
+ const trackedRequest = (fn, label, options) => request(async () => {
320
+ try {
321
+ return await fn();
322
+ } catch (error) {
323
+ chunkMayHaveDelivered ||= classifyDiscordDeliveryFailure(error) === "ambiguous";
324
+ throw error;
325
+ }
326
+ }, label, options);
327
+ try {
328
+ const result = await sendDiscordText({
329
+ rest,
330
+ request: trackedRequest,
331
+ channelId: thread.id,
332
+ text: content,
333
+ maxLinesPerMessage: DISCORD_THREAD_TRANSPORT_ONLY_MAX_LINES
334
+ });
335
+ deliveredMessageIds.push(...result.platformMessageIds);
336
+ deliveredChunkCount += 1;
337
+ } catch (error) {
338
+ const finalFailure = classifyDiscordDeliveryFailure(error);
339
+ const failedChunkDelivery = chunkMayHaveDelivered || finalFailure === "ambiguous" || finalFailure === "unknown" ? "unknown" : "not_delivered";
340
+ if (failedChunkDelivery === "unknown") recordDiscordMessageCreateAmbiguity(error);
341
+ throw new DiscordThreadInitialMessageError(thread, error, {
342
+ starterMessageDelivered: isForumLike,
343
+ deliveredChunkCount,
344
+ deliveredMessageIds,
345
+ failedChunkDelivery,
346
+ failedChunkIndex: firstFollowupChunkIndex + followupIndex,
347
+ totalChunkCount: initialMessageChunks.length
348
+ });
349
+ }
350
+ }
301
351
  }
302
352
  return thread;
303
353
  }
@@ -322,7 +372,15 @@ async function searchMessagesDiscord(query, opts) {
322
372
  const limit = Math.min(Math.max(Math.floor(query.limit), 1), 25);
323
373
  params.set("limit", String(limit));
324
374
  }
325
- return assertDiscordResponseObject(await searchGuildMessages(rest, query.guildId, params), "message search");
375
+ const result = assertDiscordResponseObject(await searchGuildMessages(rest, query.guildId, params), "message search");
376
+ if (result.code === 11e4) {
377
+ const message = typeof result.message === "string" && result.message.trim() ? result.message.trim() : "Discord search index is not yet available";
378
+ const retryAfter = parseDiscordRetryAfterBodySeconds(result.retry_after);
379
+ const retryHint = retryAfter === void 0 ? "" : ` (retry after ${retryAfter}s)`;
380
+ throw new Error(`Discord message search unavailable: ${message}${retryHint}`);
381
+ }
382
+ if (!Array.isArray(result.messages)) throw new Error("Unexpected Discord response for message search: expected messages array.");
383
+ return result;
326
384
  }
327
385
  //#endregion
328
386
  //#region extensions/discord/src/send.webhook.ts
@@ -376,6 +434,7 @@ async function sendWebhookMessageDiscord(text, opts) {
376
434
  accountId: account.accountId,
377
435
  mentionAliases: account.config.mentionAliases
378
436
  });
437
+ const flags = resolveDiscordMessageFlags({ suppressEmbeds: resolveDiscordSuppressEmbeds({ configured: account.config.suppressEmbeds }) });
379
438
  const threadConversationId = opts.threadId == null ? "" : String(opts.threadId).trim();
380
439
  if (threadConversationId) recordOutboundMessageIdentity({
381
440
  channel: "discord",
@@ -393,19 +452,25 @@ async function sendWebhookMessageDiscord(text, opts) {
393
452
  timeoutMs: DISCORD_WEBHOOK_TIMEOUT_MS,
394
453
  operation: "discord.webhook.send"
395
454
  });
455
+ const request = createDiscordRetryRunner({ signal: deadline.signal });
396
456
  try {
397
- const response = await (proxyFetch ?? fetch)(url, {
398
- method: "POST",
399
- headers: { "content-type": "application/json" },
400
- body: JSON.stringify({
401
- content: rewrittenText,
402
- username: normalizeOptionalString(opts.username),
403
- avatar_url: normalizeOptionalString(opts.avatarUrl),
404
- ...messageReference ? { message_reference: messageReference } : {}
405
- }),
406
- signal: deadline.signal
407
- });
408
- if (!response.ok) await throwWebhookResponseError(response, deadline.signal);
457
+ const response = await request(async () => {
458
+ await opts.onPlatformSendDispatch?.();
459
+ const attemptResponse = await (proxyFetch ?? fetch)(url, {
460
+ method: "POST",
461
+ headers: { "content-type": "application/json" },
462
+ body: JSON.stringify({
463
+ content: rewrittenText,
464
+ username: normalizeOptionalString(opts.username),
465
+ avatar_url: normalizeOptionalString(opts.avatarUrl),
466
+ ...flags ? { flags } : {},
467
+ ...messageReference ? { message_reference: messageReference } : {}
468
+ }),
469
+ signal: deadline.signal
470
+ });
471
+ if (!attemptResponse.ok) await throwWebhookResponseError(attemptResponse, deadline.signal);
472
+ return attemptResponse;
473
+ }, "webhook", { safety: "non-idempotent-create" });
409
474
  const payload = response.status === 204 ? {} : await readProviderJsonResponse(response, "Discord webhook send").catch(() => {
410
475
  throwIfWebhookDeadlineExpired(deadline.signal);
411
476
  return {};
@@ -735,7 +800,20 @@ async function sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, r
735
800
  message_id: replyTo,
736
801
  fail_if_not_exists: false
737
802
  };
738
- return await request(() => rest.post(`/channels/${channelId}/messages`, { body: messagePayload }), "voice-message", { safety: "nonce-protected-create" });
803
+ let messageCreateMayHaveCommitted = false;
804
+ try {
805
+ return await request(async () => {
806
+ try {
807
+ return await rest.post(`/channels/${channelId}/messages`, { body: messagePayload });
808
+ } catch (error) {
809
+ messageCreateMayHaveCommitted ||= classifyDiscordDeliveryFailure(error) === "ambiguous";
810
+ throw error;
811
+ }
812
+ }, "voice-message", { safety: "nonce-protected-create" });
813
+ } catch (error) {
814
+ if (messageCreateMayHaveCommitted) recordDiscordMessageCreateAmbiguity(error);
815
+ throw error;
816
+ }
739
817
  }
740
818
  //#endregion
741
819
  //#region extensions/discord/src/send.voice.ts
@@ -747,19 +825,20 @@ function toDiscordSendResult(result, fallbackChannelId, reply) {
747
825
  reply
748
826
  });
749
827
  }
750
- async function materializeVoiceMessageInput(mediaUrl) {
751
- const media = await loadWebMediaRaw(mediaUrl, maxBytesForKind("audio"));
828
+ async function withMaterializedVoiceMessageInput(mediaUrl, opts, run) {
829
+ const media = await loadWebMediaRaw(mediaUrl, buildOutboundMediaLoadOptions({
830
+ maxBytes: maxBytesForKind("audio"),
831
+ mediaAccess: opts.mediaAccess,
832
+ mediaLocalRoots: opts.mediaLocalRoots,
833
+ mediaReadFile: opts.mediaReadFile
834
+ }));
752
835
  const extFromName = media.fileName ? path.extname(media.fileName) : "";
753
836
  const extFromMime = media.contentType ? extensionForMime(media.contentType) : "";
754
837
  const ext = extFromName || extFromMime || ".bin";
755
- const workspace = await tempWorkspace({
838
+ return await withTempWorkspace({
756
839
  rootDir: resolvePreferredOpenClawTmpDir(),
757
840
  prefix: "voice-src-"
758
- });
759
- return {
760
- filePath: await workspace.write(`input${ext}`, media.buffer),
761
- cleanup: () => workspace.cleanup().then(() => void 0)
762
- };
841
+ }, async (workspace) => await run(await workspace.write(`input${ext}`, media.buffer)));
763
842
  }
764
843
  /**
765
844
  * Send a voice message to Discord.
@@ -772,52 +851,62 @@ async function materializeVoiceMessageInput(mediaUrl) {
772
851
  * @param opts - Send options
773
852
  */
774
853
  async function sendVoiceMessageDiscord(to, audioPath, opts) {
775
- const { filePath: localInputPath, cleanup: cleanupLocalInput } = await materializeVoiceMessageInput(audioPath);
776
- let oggPath = null;
777
- let oggCleanup = false;
778
- let token;
779
- let rest;
780
- let channelId;
781
854
  const cfg = requireRuntimeConfig(opts.cfg, "Discord voice send");
782
- try {
783
- const accountInfo = resolveDiscordAccount({
784
- cfg,
785
- accountId: opts.accountId
786
- });
787
- const client = createDiscordClient({
788
- ...opts,
789
- cfg
790
- });
791
- token = client.token;
792
- rest = client.rest;
793
- const request = client.request;
794
- const recipient = await parseAndResolveChannelRecipient(to, cfg, opts.accountId);
795
- channelId = (await resolveChannelId(rest, recipient, request)).channelId;
796
- const ogg = await ensureOggOpus(localInputPath);
797
- oggPath = ogg.path;
798
- oggCleanup = ogg.cleanup;
799
- const metadata = await getVoiceMessageMetadata(oggPath);
800
- const audioBuffer = await fs.readFile(oggPath);
801
- const result = await sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, opts.reply?.messageId, request, opts.silent, token);
802
- recordChannelActivity({
803
- channel: "discord",
804
- accountId: accountInfo.accountId,
805
- direction: "outbound"
806
- });
807
- return toDiscordSendResult(result, channelId, opts.reply);
808
- } catch (err) {
809
- if (channelId && rest && token) throw await buildDiscordSendError(err, {
810
- channelId,
811
- cfg,
812
- rest,
813
- token,
814
- hasMedia: true
815
- });
816
- throw err;
817
- } finally {
818
- await unlinkIfExists(oggCleanup ? oggPath : null);
819
- await cleanupLocalInput();
820
- }
855
+ return await withMaterializedVoiceMessageInput(audioPath, opts, async (localInputPath) => {
856
+ let oggPath = null;
857
+ let oggCleanup = false;
858
+ let token;
859
+ let rest;
860
+ let channelId;
861
+ try {
862
+ const accountInfo = resolveDiscordAccount({
863
+ cfg,
864
+ accountId: opts.accountId
865
+ });
866
+ const client = createDiscordClient({
867
+ ...opts,
868
+ cfg
869
+ });
870
+ token = client.token;
871
+ rest = client.rest;
872
+ const request = client.request;
873
+ const recipient = await parseAndResolveChannelRecipient(to, cfg, opts.accountId);
874
+ channelId = (await resolveChannelId(rest, recipient, request)).channelId;
875
+ const ogg = await ensureOggOpus(localInputPath);
876
+ oggPath = ogg.path;
877
+ oggCleanup = ogg.cleanup;
878
+ const metadata = await getVoiceMessageMetadata(oggPath);
879
+ const audioBuffer = await fs.readFile(oggPath);
880
+ await opts.onPlatformSendDispatch?.();
881
+ const result = await sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, opts.reply?.messageId, request, opts.silent, token);
882
+ recordChannelActivity({
883
+ channel: "discord",
884
+ accountId: accountInfo.accountId,
885
+ direction: "outbound"
886
+ });
887
+ return toDiscordSendResult(result, channelId, opts.reply);
888
+ } catch (err) {
889
+ if (channelId && rest && token) throw await buildDiscordSendError(err, {
890
+ channelId,
891
+ cfg,
892
+ rest,
893
+ token,
894
+ hasMedia: true
895
+ });
896
+ throw err;
897
+ } finally {
898
+ await unlinkIfExists(oggCleanup ? oggPath : null);
899
+ }
900
+ });
901
+ }
902
+ //#endregion
903
+ //#region extensions/discord/src/send.typing.ts
904
+ async function sendTypingDiscord(channelId, opts) {
905
+ await sendChannelTyping(resolveDiscordRest(opts), channelId);
906
+ return {
907
+ ok: true,
908
+ channelId
909
+ };
821
910
  }
822
911
  //#endregion
823
912
  //#region extensions/discord/src/send.ts
@@ -877,4 +966,4 @@ var send_exports = /* @__PURE__ */ __exportAll({
877
966
  uploadStickerDiscord: () => uploadStickerDiscord
878
967
  });
879
968
  //#endregion
880
- export { uploadEmojiDiscord as A, kickMemberDiscord as C, resolveEventCoverImage as D, removeRoleDiscord as E, moveChannelDiscord as F, removeChannelPermissionDiscord as I, setChannelPermissionDiscord as L, createChannelDiscord as M, deleteChannelDiscord as N, timeoutMemberDiscord as O, editChannelDiscord as P, fetchVoiceStatusDiscord as S, listScheduledEventsDiscord as T, createScheduledEventDiscord as _, createThreadDiscord as a, fetchMemberInfoDiscord as b, fetchMessageDiscord as c, pinMessageDiscord as d, readMessagesDiscord as f, banMemberDiscord as g, addRoleDiscord as h, DiscordThreadInitialMessageError as i, uploadStickerDiscord as j, listGuildEmojisDiscord as k, listPinsDiscord as l, unpinMessageDiscord as m, sendVoiceMessageDiscord as n, deleteMessageDiscord as o, searchMessagesDiscord as p, sendWebhookMessageDiscord as r, editMessageDiscord as s, send_exports as t, listThreadsDiscord as u, fetchChannelInfoDiscord as v, listGuildChannelsDiscord as w, fetchRoleInfoDiscord as x, fetchGuildInfoDiscord as y };
969
+ export { listGuildEmojisDiscord as A, fetchVoiceStatusDiscord as C, removeRoleDiscord as D, listScheduledEventsDiscord as E, editChannelDiscord as F, moveChannelDiscord as I, removeChannelPermissionDiscord as L, uploadStickerDiscord as M, createChannelDiscord as N, resolveEventCoverImage as O, deleteChannelDiscord as P, setChannelPermissionDiscord as R, fetchRoleInfoDiscord as S, listGuildChannelsDiscord as T, banMemberDiscord as _, DiscordThreadInitialMessageError as a, fetchGuildInfoDiscord as b, editMessageDiscord as c, listThreadsDiscord as d, pinMessageDiscord as f, addRoleDiscord as g, unpinMessageDiscord as h, sendWebhookMessageDiscord as i, uploadEmojiDiscord as j, timeoutMemberDiscord as k, fetchMessageDiscord as l, searchMessagesDiscord as m, sendTypingDiscord as n, createThreadDiscord as o, readMessagesDiscord as p, sendVoiceMessageDiscord as r, deleteMessageDiscord as s, send_exports as t, listPinsDiscord as u, createScheduledEventDiscord as v, kickMemberDiscord as w, fetchMemberInfoDiscord as x, fetchChannelInfoDiscord as y };
@@ -1,12 +1,12 @@
1
- import { L as serializePayload, Yt as __exportAll, pt as createChannelMessage, vt as editChannelMessage } from "./chunk-DX4jpFQP.js";
1
+ import { H as serializePayload, ft as stripUndefinedFields, tn as __exportAll, wt as editChannelMessage, yt as createChannelMessage } from "./retry-CLpJcXXf.js";
2
2
  import { s as resolveDiscordAccount } from "./accounts-DJTf1LtA.js";
3
- import { f as buildDiscordComponentMessage, g as resolveDiscordComponentAttachmentName, p as buildDiscordComponentMessageFlags } from "./outbound-session-route-H-HoyH5U.js";
4
- import { P as createDiscordClient, W as parseAndResolveChannelRecipient, b as stripUndefinedFields, g as createDiscordMessageNonce, l as resolveChannelId, m as SUPPRESS_NOTIFICATIONS_FLAG, t as buildDiscordSendError, u as resolveDiscordChannel } from "./send.shared-jpvAG_u6.js";
3
+ import { f as buildDiscordComponentMessage, g as resolveDiscordComponentAttachmentName, p as buildDiscordComponentMessageFlags } from "./outbound-session-route-DxLkrgKQ.js";
4
+ import { P as createDiscordClient, W as parseAndResolveChannelRecipient, g as createDiscordMessageNonce, l as resolveChannelId, m as SUPPRESS_NOTIFICATIONS_FLAG, t as buildDiscordSendError, u as resolveDiscordChannel } from "./send.shared-BmwFjW6T.js";
5
5
  import { n as getOptionalDiscordRuntime } from "./runtime-DgnVQ7zW.js";
6
- import "./config-schema-WHRQcY19.js";
6
+ import "./config-schema-BgEskFAs.js";
7
7
  import "./channel-api-CAJ0wMoV.js";
8
- import { t as sendMessageDiscord } from "./send.outbound-D-80Ej-t.js";
9
- import { n as createDiscordSendResult } from "./send.receipt-CuwRrpFz.js";
8
+ import { t as sendMessageDiscord } from "./send.outbound-DS5tHwfR.js";
9
+ import { r as createDiscordSendResult } from "./send.receipt-IPI1p-SP.js";
10
10
  import "openclaw/plugin-sdk/account-helpers";
11
11
  import "openclaw/plugin-sdk/account-id";
12
12
  import "openclaw/plugin-sdk/channel-config-helpers";
@@ -18,27 +18,27 @@ import { asDateTimestampMs, isFutureDateTimestampMs, resolveDateTimestampMs, res
18
18
  import { assertMediaNotDataUrl, jsonResult as jsonResult$1, parseAvailableTags, readNonNegativeIntegerParam, readPositiveIntegerParam, readReactionParams, readStringArrayParam, readStringParam as readStringParam$1, resolvePollMaxSelections, withNormalizedTimestamp } from "openclaw/plugin-sdk/channel-actions";
19
19
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
20
20
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
21
- import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";
21
+ import { createPluginStateErrorReporter } from "openclaw/plugin-sdk/plugin-state-runtime";
22
+ import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
22
23
  import "openclaw/plugin-sdk/channel-status";
23
24
  import "openclaw/plugin-sdk/channel-config-schema";
24
25
  import { readBooleanParam as readBooleanParam$1 } from "openclaw/plugin-sdk/boolean-param";
25
26
  import "openclaw/plugin-sdk/channel-plugin-common";
26
27
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
27
28
  //#region extensions/discord/src/components-registry-state.ts
28
- const discordComponentRegistryState = {
29
- componentEntries: resolveGlobalMap(Symbol.for("openclaw.discord.componentEntries")),
30
- modalEntries: resolveGlobalMap(Symbol.for("openclaw.discord.modalEntries")),
29
+ const discordComponentRegistryState = resolveGlobalSingleton(Symbol.for("openclaw.discord.componentRegistryState"), () => ({
30
+ componentEntries: /* @__PURE__ */ new Map(),
31
+ modalEntries: /* @__PURE__ */ new Map(),
31
32
  persistentComponentStore: void 0,
32
33
  persistentModalStore: void 0,
33
- persistentRegistryDisabled: false,
34
- reset() {
35
- this.componentEntries.clear();
36
- this.modalEntries.clear();
37
- this.persistentComponentStore = void 0;
38
- this.persistentModalStore = void 0;
39
- this.persistentRegistryDisabled = false;
40
- }
41
- };
34
+ persistentRegistryDisabled: false
35
+ }), (state) => {
36
+ state.componentEntries.clear();
37
+ state.modalEntries.clear();
38
+ state.persistentComponentStore = void 0;
39
+ state.persistentModalStore = void 0;
40
+ state.persistentRegistryDisabled = false;
41
+ });
42
42
  //#endregion
43
43
  //#region extensions/discord/src/components-registry.ts
44
44
  const DEFAULT_COMPONENT_TTL_MS = 1800 * 1e3;
@@ -52,14 +52,6 @@ function getComponentEntries() {
52
52
  function getModalEntries() {
53
53
  return discordComponentRegistryState.modalEntries;
54
54
  }
55
- function reportPersistentComponentRegistryError(error) {
56
- try {
57
- getOptionalDiscordRuntime()?.logging.getChildLogger({
58
- plugin: "discord",
59
- feature: "component-registry-state"
60
- }).warn("Discord persistent component registry state failed", formatRegistryError(error));
61
- } catch {}
62
- }
63
55
  function formatRegistryError(error) {
64
56
  if (!(error instanceof Error)) return { error: formatRegistryErrorValue(error) };
65
57
  const details = {
@@ -77,6 +69,7 @@ function formatRegistryError(error) {
77
69
  } else if (cause !== void 0) details.errorCause = formatRegistryErrorValue(cause);
78
70
  return details;
79
71
  }
72
+ const reportPersistentComponentRegistryError = createPluginStateErrorReporter(getOptionalDiscordRuntime, "discord", "component-registry-state", "Discord persistent component registry state failed", formatRegistryError);
80
73
  function formatRegistryErrorValue(value) {
81
74
  if (typeof value === "string") return value;
82
75
  if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") return String(value);
@@ -385,7 +378,8 @@ async function buildDiscordComponentPayload(params) {
385
378
  spec = withImplicitComponentAttachmentBlock(spec, resolvedFileName);
386
379
  files = [{
387
380
  data: media.buffer,
388
- name: resolvedFileName
381
+ name: resolvedFileName,
382
+ contentType: media.contentType
389
383
  }];
390
384
  }
391
385
  const uniqueAttachmentNames = uniqueStrings(extractComponentAttachmentNames(spec));
@@ -433,6 +427,7 @@ async function sendDiscordComponentMessage(to, spec, opts) {
433
427
  tableMode: opts.tableMode,
434
428
  chunkMode: opts.chunkMode,
435
429
  onDeliveryResult: opts.onDeliveryResult,
430
+ onPlatformSendDispatch: opts.onPlatformSendDispatch,
436
431
  ...opts.suppressEmbeds === void 0 ? {} : { suppressEmbeds: opts.suppressEmbeds }
437
432
  });
438
433
  const cfg = requireRuntimeConfig(opts.cfg, "Discord component send");
@@ -459,6 +454,7 @@ async function sendDiscordComponentMessage(to, spec, opts) {
459
454
  };
460
455
  let result;
461
456
  try {
457
+ await opts.onPlatformSendDispatch?.();
462
458
  result = await request(() => createChannelMessage(rest, channelId, { body }), "components", { safety: "nonce-protected-create" });
463
459
  } catch (err) {
464
460
  throw await buildDiscordSendError(err, {
@@ -1,8 +1,8 @@
1
- import { mt as createThread, n as createReusableDiscordReplyReference, pt as createChannelMessage } from "./chunk-DX4jpFQP.js";
1
+ import { bt as createThread, o as createReusableDiscordReplyReference, yt as createChannelMessage } from "./retry-CLpJcXXf.js";
2
2
  import { s as resolveDiscordAccount } from "./accounts-DJTf1LtA.js";
3
- import { P as createDiscordClient, W as parseAndResolveChannelRecipient, _ as resolveDiscordMessageFlags, a as normalizeDiscordPollInput, c as normalizeStickerIds, f as sendDiscordMedia, g as createDiscordMessageNonce, h as buildDiscordMessageRequest, l as resolveChannelId, n as buildDiscordTextChunks, p as sendDiscordText, t as buildDiscordSendError, u as resolveDiscordChannel, v as resolveDiscordSendComponents, y as resolveDiscordSendEmbeds } from "./send.shared-jpvAG_u6.js";
4
- import { i as normalizeDiscordHandleKey, o as resolveDiscordDirectoryUserId } from "./directory-live-Bw5Ephzv.js";
5
- import { n as createDiscordSendResult } from "./send.receipt-CuwRrpFz.js";
3
+ import { P as createDiscordClient, W as parseAndResolveChannelRecipient, _ as resolveDiscordMessageFlags, a as normalizeDiscordPollInput, b as resolveDiscordSuppressEmbeds, c as normalizeStickerIds, f as sendDiscordMedia, g as createDiscordMessageNonce, h as buildDiscordMessageRequest, l as resolveChannelId, n as buildDiscordTextChunks, p as sendDiscordText, t as buildDiscordSendError, u as resolveDiscordChannel, v as resolveDiscordSendComponents, y as resolveDiscordSendEmbeds } from "./send.shared-BmwFjW6T.js";
4
+ import { i as normalizeDiscordHandleKey, o as resolveDiscordDirectoryUserId } from "./directory-live-DGIGxpIo.js";
5
+ import { n as createDiscordSendReceiptFromResults, r as createDiscordSendResult } from "./send.receipt-IPI1p-SP.js";
6
6
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeOptionalStringifiedId } from "openclaw/plugin-sdk/string-coerce-runtime";
7
7
  import { ChannelType } from "discord-api-types/v10";
8
8
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
@@ -416,6 +416,8 @@ function discordTextHasBroadcastMention(text) {
416
416
  //#endregion
417
417
  //#region extensions/discord/src/send.outbound.ts
418
418
  const DEFAULT_DISCORD_MEDIA_MAX_MB = 100;
419
+ /** Discord's ChannelFlags.RequireTag is bit 4 on forum/media parent channels. */
420
+ const DISCORD_FORUM_REQUIRE_TAG_FLAG = 16;
419
421
  async function sendDiscordThreadTextChunks(params) {
420
422
  for (const chunk of params.chunks) await sendDiscordText({
421
423
  rest: params.rest,
@@ -428,12 +430,10 @@ async function sendDiscordThreadTextChunks(params) {
428
430
  suppressEmbeds: params.suppressEmbeds,
429
431
  allowedMentions: params.allowedMentions,
430
432
  maxChars: params.maxChars,
431
- onResult: params.onResult
433
+ onResult: params.onResult,
434
+ onPlatformSendDispatch: params.onPlatformSendDispatch
432
435
  });
433
436
  }
434
- function resolveDiscordSuppressEmbeds(params) {
435
- return params.override ?? params.configured ?? true;
436
- }
437
437
  /** Discord thread names are capped at 100 characters. */
438
438
  const DISCORD_THREAD_NAME_LIMIT = 100;
439
439
  /** Derive a thread title from the first non-empty line of the message text. */
@@ -499,6 +499,7 @@ async function sendMessageDiscord(to, text, opts) {
499
499
  const { channelId } = await resolveChannelId(rest, await parseAndResolveChannelRecipient(to, cfg, opts.accountId), request);
500
500
  const channel = await resolveDiscordChannel(rest, channelId);
501
501
  if (isForumLikeChannel(channel)) {
502
+ if (((channel.flags ?? 0) & DISCORD_FORUM_REQUIRE_TAG_FLAG) !== 0) throw new Error(`Discord forum channel ${channelId} requires an applied tag; use thread-create with appliedTags, then send to the created thread.`);
502
503
  const threadName = deriveForumThreadName(renderedText);
503
504
  const chunks = buildDiscordTextChunks(textWithMentions, {
504
505
  maxLinesPerMessage,
@@ -528,6 +529,7 @@ async function sendMessageDiscord(to, text, opts) {
528
529
  });
529
530
  let threadRes;
530
531
  try {
532
+ await opts.onPlatformSendDispatch?.();
531
533
  threadRes = await request(() => createThread(rest, channelId, { body: {
532
534
  name: threadName,
533
535
  ...channel.default_auto_archive_duration === void 0 ? {} : { auto_archive_duration: channel.default_auto_archive_duration },
@@ -546,18 +548,22 @@ async function sendMessageDiscord(to, text, opts) {
546
548
  const messageId = threadRes.message?.id ?? threadId;
547
549
  const resultChannelId = threadRes.message?.channel_id ?? threadId;
548
550
  const remainingChunks = chunks.slice(1);
549
- await opts.onDeliveryResult?.(toDiscordSendResult({
551
+ const starterResult = toDiscordSendResult({
550
552
  id: messageId,
551
553
  channel_id: resultChannelId
552
554
  }, channelId, {
553
555
  kind: "text",
554
556
  threadId
555
- }));
557
+ });
558
+ const deliveredResults = [starterResult];
559
+ await opts.onDeliveryResult?.(starterResult);
556
560
  const reportThreadResult = async (result, kind) => {
557
- await opts.onDeliveryResult?.(toDiscordSendResult(result, threadId, {
561
+ const deliveredResult = toDiscordSendResult(result, threadId, {
558
562
  kind,
559
563
  threadId
560
- }));
564
+ });
565
+ deliveredResults.push(deliveredResult);
566
+ await opts.onDeliveryResult?.(deliveredResult);
561
567
  };
562
568
  try {
563
569
  if (opts.mediaUrl) {
@@ -579,7 +585,8 @@ async function sendMessageDiscord(to, text, opts) {
579
585
  suppressEmbeds,
580
586
  allowedMentions: opts.allowedMentions,
581
587
  maxChars: textLimit,
582
- onResult: reportThreadResult
588
+ onResult: reportThreadResult,
589
+ onPlatformSendDispatch: opts.onPlatformSendDispatch
583
590
  });
584
591
  await sendDiscordThreadTextChunks({
585
592
  rest,
@@ -592,7 +599,8 @@ async function sendMessageDiscord(to, text, opts) {
592
599
  silent: opts.silent,
593
600
  suppressEmbeds,
594
601
  allowedMentions: opts.allowedMentions,
595
- onResult: reportThreadResult
602
+ onResult: reportThreadResult,
603
+ onPlatformSendDispatch: opts.onPlatformSendDispatch
596
604
  });
597
605
  } else await sendDiscordThreadTextChunks({
598
606
  rest,
@@ -605,7 +613,8 @@ async function sendMessageDiscord(to, text, opts) {
605
613
  silent: opts.silent,
606
614
  suppressEmbeds,
607
615
  allowedMentions: opts.allowedMentions,
608
- onResult: reportThreadResult
616
+ onResult: reportThreadResult,
617
+ onPlatformSendDispatch: opts.onPlatformSendDispatch
609
618
  });
610
619
  } catch (err) {
611
620
  throw await buildDiscordSendError(err, {
@@ -621,13 +630,13 @@ async function sendMessageDiscord(to, text, opts) {
621
630
  accountId: accountInfo.accountId,
622
631
  direction: "outbound"
623
632
  });
624
- return toDiscordSendResult({
625
- id: messageId,
626
- channel_id: resultChannelId
627
- }, channelId, {
628
- kind: opts.mediaUrl ? "media" : "text",
629
- threadId
630
- });
633
+ return {
634
+ ...starterResult,
635
+ receipt: createDiscordSendReceiptFromResults({
636
+ results: deliveredResults,
637
+ threadId
638
+ })
639
+ };
631
640
  }
632
641
  let result;
633
642
  const reportResult = async (progressResult, kind, replyToId) => {
@@ -657,7 +666,8 @@ async function sendMessageDiscord(to, text, opts) {
657
666
  suppressEmbeds,
658
667
  allowedMentions: opts.allowedMentions,
659
668
  maxChars: textLimit,
660
- onResult: reportResult
669
+ onResult: reportResult,
670
+ onPlatformSendDispatch: opts.onPlatformSendDispatch
661
671
  });
662
672
  else result = await sendDiscordText({
663
673
  rest,
@@ -673,7 +683,8 @@ async function sendMessageDiscord(to, text, opts) {
673
683
  suppressEmbeds,
674
684
  allowedMentions: opts.allowedMentions,
675
685
  maxChars: textLimit,
676
- onResult: reportResult
686
+ onResult: reportResult,
687
+ onPlatformSendDispatch: opts.onPlatformSendDispatch
677
688
  });
678
689
  } catch (err) {
679
690
  throw await buildDiscordSendError(err, {
@@ -705,6 +716,7 @@ async function sendStickerDiscord(to, stickerIds, opts) {
705
716
  enforce_nonce: true,
706
717
  ...flags ? { flags } : {}
707
718
  };
719
+ await opts.onPlatformSendDispatch?.();
708
720
  return toDiscordSendResult(await request(() => createChannelMessage(rest, channelId, { body }), "sticker", { safety: "nonce-protected-create" }), channelId, { kind: "card" });
709
721
  }
710
722
  async function sendPollDiscord(to, poll, opts) {
@@ -722,6 +734,7 @@ async function sendPollDiscord(to, poll, opts) {
722
734
  enforce_nonce: true,
723
735
  ...flags ? { flags } : {}
724
736
  };
737
+ await opts.onPlatformSendDispatch?.();
725
738
  return toDiscordSendResult(await request(() => createChannelMessage(rest, channelId, { body }), "poll", { safety: "nonce-protected-create" }), channelId, { kind: "card" });
726
739
  }
727
740
  async function resolveDiscordStructuredSendContext(to, opts) {