@openclaw/discord 2026.5.28 → 2026.5.31-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/{account-inspect-C4sJZNbI.js → account-inspect-DDZtT5C8.js} +9 -7
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/{accounts-dXTfmnSZ.js → accounts-P19jm1lf.js} +1 -1
  4. package/dist/action-runtime-api.js +1 -1
  5. package/dist/api.js +16 -16
  6. package/dist/{approval-handler.runtime-CN_E7nkQ.js → approval-handler.runtime-DSFfzXpo.js} +4 -4
  7. package/dist/{audit-BFCBp74H.js → audit-kgIklCyI.js} +4 -4
  8. package/dist/{channel-COXb22wL.js → channel-D0gX0yj6.js} +44 -31
  9. package/dist/{channel-actions-C2g9OmnG.js → channel-actions-DDoc4V7x.js} +4 -4
  10. package/dist/{channel-actions.runtime-drnbrgpt.js → channel-actions.runtime-CqZfnMaA.js} +5 -5
  11. package/dist/channel-config-api.js +1 -1
  12. package/dist/channel-plugin-api.js +1 -1
  13. package/dist/{channel.setup-BsrP5-hv.js → channel.setup-BNZm3eed.js} +8 -23
  14. package/dist/{components-CjQujWa1.js → components-DcLQ2JkV.js} +1 -1
  15. package/dist/{config-schema-3tZYNVTM.js → config-schema-spuxW477.js} +17 -1
  16. package/dist/contract-api.js +3 -3
  17. package/dist/{conversation-identity-CsPV1Gjx.js → conversation-identity-BURFgcfI.js} +7 -5
  18. package/dist/{directory-config-BqPvRoDv.js → directory-config-B9Jw1Q2R.js} +2 -2
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-CCI0IMZq.js → directory-live-CDWUg-nA.js} +2 -2
  21. package/dist/{doctor-Dy7sI1sL.js → doctor-BUhqxLb9.js} +4 -5
  22. package/dist/{doctor-contract-8-Ia3d_X.js → doctor-contract-BAu3WLvd.js} +5 -5
  23. package/dist/doctor-contract-api.js +1 -1
  24. package/dist/{handle-action.guild-admin-BmjclQbR.js → handle-action.guild-admin-Dpa-kUzu.js} +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/legacy-state-migrations-api.js +3 -1
  27. package/dist/{manager.runtime-ClhnLsZf.js → manager.runtime-BWBENHRA.js} +22 -19
  28. package/dist/{message-handler-BnhAHqem.js → message-handler-HO8BbZmQ.js} +182 -29
  29. package/dist/{message-handler.preflight-B1RTQ6ZO.js → message-handler.preflight-DxHBPQvD.js} +24 -17
  30. package/dist/{message-handler.process-B1ODKav3.js → message-handler.process-Cq3j7A9k.js} +41 -29
  31. package/dist/{message-utils-CmB03FoF.js → message-utils-D_3KTxh_.js} +19 -15
  32. package/dist/{outbound-adapter-DfY2Dtf3.js → outbound-adapter-BkPkILpq.js} +52 -87
  33. package/dist/{pluralkit-DAZN3ZMK.js → pluralkit-Ds9aZAM4.js} +1 -1
  34. package/dist/{probe-BiEg-_i1.js → probe-2MdGjUAf.js} +1 -1
  35. package/dist/{probe.runtime-Dja7CbJT.js → probe.runtime-J927koKG.js} +1 -1
  36. package/dist/{provider-DCcqVFp-.js → provider-BK8hjk_y.js} +224 -150
  37. package/dist/{provider-session.runtime-mKSG07RE.js → provider-session.runtime-CYMFlLg9.js} +3 -3
  38. package/dist/provider.runtime-RfQSSM_v.js +2 -0
  39. package/dist/{resolve-allowlist-common-DAIbZobr.js → resolve-allowlist-common-BIf3417N.js} +1 -1
  40. package/dist/{resolve-channels-D1zgoxnw.js → resolve-channels-DK-3f_aA.js} +2 -2
  41. package/dist/{resolve-users-wPAvLk_8.js → resolve-users-DUDPsfWt.js} +2 -2
  42. package/dist/{runtime-gaB5-Wsj.js → runtime-Blf_d9E7.js} +8 -8
  43. package/dist/runtime-api.actions.js +2 -2
  44. package/dist/runtime-api.js +22 -22
  45. package/dist/runtime-api.lookup.js +5 -5
  46. package/dist/runtime-api.monitor-LVbgPRHA.js +6 -0
  47. package/dist/runtime-api.monitor.js +5 -5
  48. package/dist/runtime-api.send.js +5 -5
  49. package/dist/runtime-api.threads.js +5 -5
  50. package/dist/{send-CBxhnbKU.js → send-BHkA8a-k.js} +7 -7
  51. package/dist/{send.components-uuQuHL3t.js → send.components-CUHzDapa.js} +26 -12
  52. package/dist/{send.outbound-DZHJYmNF.js → send.outbound-DSKZbjML.js} +15 -5
  53. package/dist/{send.receipt-3y2lVFRf.js → send.receipt-CjsWpitW.js} +25 -13
  54. package/dist/{send.shared-Ddl21-Gt.js → send.shared-Cluujl6W.js} +3 -3
  55. package/dist/setup-plugin-api.js +1 -1
  56. package/dist/{shared-D3NkInmq.js → shared-BMGLQw74.js} +5 -5
  57. package/dist/{subagent-hooks-BacIbIBD.js → subagent-hooks-7jAn9HT5.js} +3 -3
  58. package/dist/subagent-hooks-api.js +1 -5
  59. package/dist/{system-events-Cw6Hp8RF.js → system-events-BjuNA9Zs.js} +1 -1
  60. package/dist/{target-resolver-jjQzAS6T.js → target-resolver-BYuWNVQp.js} +3 -3
  61. package/dist/targets-BNDUOdvI.js +3 -0
  62. package/dist/{thread-bindings-BBq-yzq2.js → thread-bindings-CKruJE91.js} +6 -6
  63. package/dist/{thread-bindings.discord-api-5QgEd_dW.js → thread-bindings.discord-api-BjLUwwSP.js} +5 -5
  64. package/dist/{thread-bindings.manager-COkxjwR9.js → thread-bindings.manager-Dji7SZHG.js} +4 -4
  65. package/dist/{thread-bindings.session-updates-D5gY2ZTE.js → thread-bindings.session-updates-DoDFvU_h.js} +1 -1
  66. package/dist/{thread-bindings.state-BsOnj5NX.js → thread-bindings.state-DYp4FVEB.js} +8 -2
  67. package/dist/{timeouts-l_PsHQvX.js → timeouts-CEJ3--Uw.js} +5 -2
  68. package/dist/timeouts.js +1 -1
  69. package/dist/{transcripts-source-DDrS8HrV.js → transcripts-source-O73_YIDs.js} +1 -1
  70. package/dist/transcripts-source-api.js +1 -1
  71. package/dist/{typing-B7bCMPJJ.js → typing-CKW0i8du.js} +2 -2
  72. package/node_modules/typebox/build/type/script/mapping.d.mts +5 -2
  73. package/node_modules/typebox/build/type/script/mapping.mjs +15 -8
  74. package/node_modules/typebox/build/type/script/parser.d.mts +3 -1
  75. package/node_modules/typebox/build/type/script/parser.mjs +2 -1
  76. package/node_modules/typebox/package.json +29 -29
  77. package/npm-shrinkwrap.json +7 -38
  78. package/openclaw.plugin.json +77 -1
  79. package/package.json +5 -5
  80. package/dist/provider.runtime-BmpunJHX.js +0 -2
  81. package/dist/runtime-api.monitor-B38upMUK.js +0 -5
  82. package/dist/targets-BVKF5wbg.js +0 -3
@@ -1,12 +1,13 @@
1
- import { Ht as parseDiscordTarget, _ as VoiceStateUpdateListener, c as discord_exports, h as ResumedListener, jt as getGuildVoiceState, m as ReadyListener } from "./send.receipt-3y2lVFRf.js";
2
- import { c as resolveDiscordAccountAllowFrom } from "./accounts-dXTfmnSZ.js";
1
+ import { Ht as parseDiscordTarget, _ as VoiceStateUpdateListener, c as discord_exports, h as ResumedListener, jt as getGuildVoiceState, m as ReadyListener } from "./send.receipt-CjsWpitW.js";
2
+ import { c as resolveDiscordAccountAllowFrom } from "./accounts-P19jm1lf.js";
3
3
  import { a as normalizeDiscordSlug, b as formatDiscordUserTag, m as resolveDiscordOwnerAccess } from "./allow-list-BnkWtVpA.js";
4
- import { i as formatMention } from "./send.outbound-DZHJYmNF.js";
4
+ import { o as formatMention } from "./send.outbound-DSKZbjML.js";
5
5
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
6
- import { f as buildDiscordGroupSystemPrompt, o as authorizeDiscordVoiceIngress, u as resolveDiscordVoiceEnabled } from "./provider-DCcqVFp-.js";
6
+ import { f as buildDiscordGroupSystemPrompt, o as authorizeDiscordVoiceIngress, u as resolveDiscordVoiceEnabled } from "./provider-BK8hjk_y.js";
7
7
  import { createRequire } from "node:module";
8
8
  import { asBoolean, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
9
9
  import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
10
+ import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
10
11
  import path from "node:path";
11
12
  import { resolveFfmpegBin } from "openclaw/plugin-sdk/media-runtime";
12
13
  import { createSubsystemLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
@@ -1425,17 +1426,21 @@ var DiscordRealtimeVoiceSession = class {
1425
1426
  logger$2.warn(`discord voice: realtime wake-name follow-up has no speaker context voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId}`);
1426
1427
  return;
1427
1428
  }
1429
+ const expiresAt = resolveExpiresAtMsFromDurationMs(DISCORD_REALTIME_WAKE_NAME_FOLLOWUP_TTL_MS);
1430
+ if (expiresAt === void 0) return;
1428
1431
  this.pendingWakeNameFollowup = {
1429
1432
  context,
1430
1433
  startedAt: turn?.startedAt ?? Date.now(),
1431
- expiresAt: Date.now() + DISCORD_REALTIME_WAKE_NAME_FOLLOWUP_TTL_MS
1434
+ expiresAt
1432
1435
  };
1433
1436
  logger$2.info(`discord voice: realtime wake-name follow-up armed speaker=${context.speakerLabel} voiceSession=${this.params.entry.voiceSessionKey} agent=${this.params.entry.route.agentId}`);
1434
1437
  }
1435
1438
  consumePendingWakeNameFollowup() {
1436
1439
  const pending = this.pendingWakeNameFollowup;
1437
1440
  this.pendingWakeNameFollowup = void 0;
1438
- if (!pending || Date.now() > pending.expiresAt) return;
1441
+ const now = asDateTimestampMs(Date.now());
1442
+ const expiresAt = pending ? asDateTimestampMs(pending.expiresAt) : void 0;
1443
+ if (!pending || now === void 0 || expiresAt === void 0 || now > expiresAt) return;
1439
1444
  const currentTurn = this.peekPendingSpeakerTurn();
1440
1445
  if (currentTurn && currentTurn.context.userId !== pending.context.userId) return;
1441
1446
  if (currentTurn) this.consumePendingSpeakerContext();
@@ -1949,7 +1954,9 @@ var DiscordVoiceSpeakerContextResolver = class {
1949
1954
  const key = this.resolveCacheKey(guildId, userId);
1950
1955
  const cached = this.cache.get(key);
1951
1956
  if (!cached) return;
1952
- if (cached.expiresAt <= Date.now()) {
1957
+ const now = asDateTimestampMs(Date.now());
1958
+ const expiresAt = asDateTimestampMs(cached.expiresAt);
1959
+ if (now === void 0 || expiresAt === void 0 || expiresAt <= now) {
1953
1960
  this.cache.delete(key);
1954
1961
  return;
1955
1962
  }
@@ -1963,9 +1970,10 @@ var DiscordVoiceSpeakerContextResolver = class {
1963
1970
  }
1964
1971
  setCachedContext(guildId, userId, context) {
1965
1972
  const key = this.resolveCacheKey(guildId, userId);
1966
- this.cache.set(key, {
1973
+ const expiresAt = resolveExpiresAtMsFromDurationMs(SPEAKER_CONTEXT_CACHE_TTL_MS);
1974
+ if (expiresAt !== void 0) this.cache.set(key, {
1967
1975
  ...context,
1968
- expiresAt: Date.now() + SPEAKER_CONTEXT_CACHE_TTL_MS
1976
+ expiresAt
1969
1977
  });
1970
1978
  }
1971
1979
  };
@@ -2394,11 +2402,6 @@ var DiscordVoiceManager$1 = class {
2394
2402
  logger.info(`discord voice: joining guild=${guildId} channel=${channelId} mode=${voiceMode} agent=${route.agentId} voiceSession=${voiceRoute.sessionKey} supervisorSession=${route.sessionKey} agentSessionMode=${agentSessionMode}${agentSessionTarget ? ` agentSessionTarget=${agentSessionTarget}` : ""} voiceModel=${voiceConfig?.model ?? "route-default"} realtimeProvider=${voiceConfig?.realtime?.provider ?? "auto"} realtimeModel=${voiceConfig?.realtime?.model ?? "provider-default"} realtimeVoice=${voiceConfig?.realtime?.voice ?? "provider-default"}`);
2395
2403
  const player = voiceSdk.createAudioPlayer();
2396
2404
  connection.subscribe(player);
2397
- let speakingHandler;
2398
- let speakingEndHandler;
2399
- let disconnectedHandler;
2400
- let destroyedHandler;
2401
- let playerErrorHandler;
2402
2405
  let stopped = false;
2403
2406
  const clearSessionIfCurrent = () => {
2404
2407
  if (this.sessions.get(guildId)?.connection === connection) this.sessions.delete(guildId);
@@ -2474,15 +2477,15 @@ var DiscordVoiceManager$1 = class {
2474
2477
  channelId
2475
2478
  };
2476
2479
  }
2477
- speakingHandler = (userId) => {
2480
+ const speakingHandler = (userId) => {
2478
2481
  this.handleSpeakingStart(entry, userId).catch((err) => {
2479
2482
  logger.warn(`discord voice: capture failed: ${formatErrorMessage(err)}`);
2480
2483
  });
2481
2484
  };
2482
- speakingEndHandler = (userId) => {
2485
+ const speakingEndHandler = (userId) => {
2483
2486
  this.scheduleCaptureFinalize(entry, userId, "speaker end");
2484
2487
  };
2485
- disconnectedHandler = async () => {
2488
+ const disconnectedHandler = async () => {
2486
2489
  try {
2487
2490
  logVoiceVerbose(`disconnected: attempting recovery guild ${guildId} channel ${channelId} grace=${reconnectGraceMs}ms`);
2488
2491
  await Promise.race([voiceSdk.entersState(connection, voiceSdk.VoiceConnectionStatus.Signalling, reconnectGraceMs), voiceSdk.entersState(connection, voiceSdk.VoiceConnectionStatus.Connecting, reconnectGraceMs)]);
@@ -2496,14 +2499,14 @@ var DiscordVoiceManager$1 = class {
2496
2499
  });
2497
2500
  }
2498
2501
  };
2499
- destroyedHandler = () => {
2502
+ const destroyedHandler = () => {
2500
2503
  clearSessionIfCurrent();
2501
2504
  stopEntry(entry, {
2502
2505
  destroyConnection: false,
2503
2506
  reason: `destroyed guild ${guildId} channel ${channelId}`
2504
2507
  });
2505
2508
  };
2506
- playerErrorHandler = (err) => {
2509
+ const playerErrorHandler = (err) => {
2507
2510
  logger.warn(`discord voice: playback error: ${formatErrorMessage(err)}`);
2508
2511
  };
2509
2512
  this.enableDaveReceivePassthrough(entry, "post-join warmup", 30);
@@ -1,14 +1,15 @@
1
- import { I as createDiscordRestClient } from "./send.shared-Ddl21-Gt.js";
2
- import { d as resolveDiscordChannelNameSafe, l as resolveDiscordChannelIdSafe, p as resolveDiscordChannelParentSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-5QgEd_dW.js";
3
- import { a as mergeAbortSignals } from "./timeouts-l_PsHQvX.js";
4
- import { c as hasDiscordMessageStickers, d as resolveDiscordMessageChannelId, r as resolveDiscordMessageText } from "./message-utils-CmB03FoF.js";
5
- import { t as sendTyping } from "./typing-B7bCMPJJ.js";
1
+ import { I as createDiscordRestClient } from "./send.shared-Cluujl6W.js";
2
+ import { d as resolveDiscordChannelNameSafe, l as resolveDiscordChannelIdSafe, p as resolveDiscordChannelParentSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-BjLUwwSP.js";
3
+ import { a as mergeAbortSignals } from "./timeouts-CEJ3--Uw.js";
4
+ import { c as hasDiscordMessageStickers, d as resolveDiscordMessageChannelId, r as resolveDiscordMessageText } from "./message-utils-D_3KTxh_.js";
5
+ import { t as sendTyping } from "./typing-CKW0i8du.js";
6
6
  import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
7
- import { createChannelRunQueue } from "openclaw/plugin-sdk/channel-outbound";
7
+ import { createChannelRunQueue, createTypingCallbacks, resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-outbound";
8
8
  import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
9
9
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
10
10
  import { createChannelInboundDebouncer, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
11
11
  import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
12
+ import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
12
13
  //#region extensions/discord/src/monitor/inbound-dedupe.ts
13
14
  const RECENT_DISCORD_MESSAGE_TTL_MS = 5 * 6e4;
14
15
  const RECENT_DISCORD_MESSAGE_MAX = 5e3;
@@ -59,7 +60,7 @@ function resolveDiscordInboundJobQueueKey(ctx) {
59
60
  return ctx.messageChannelId;
60
61
  }
61
62
  function buildDiscordInboundJob(ctx, options) {
62
- const { runtime, abortSignal, guildHistories, client, threadBindings, discordRestFetch, message, data, threadChannel, ...payload } = ctx;
63
+ const { runtime, abortSignal, guildHistories, client, threadBindings, replyTypingFeedback, discordRestFetch, message, data, threadChannel, ...payload } = ctx;
63
64
  const sanitizedMessage = sanitizeDiscordInboundMessage(message);
64
65
  return {
65
66
  queueKey: resolveDiscordInboundJobQueueKey(ctx),
@@ -78,6 +79,7 @@ function buildDiscordInboundJob(ctx, options) {
78
79
  guildHistories,
79
80
  client,
80
81
  threadBindings,
82
+ replyTypingFeedback,
81
83
  discordRestFetch
82
84
  },
83
85
  replayKeys: options?.replayKeys ? [...options.replayKeys] : void 0
@@ -118,10 +120,65 @@ function applyImplicitReplyBatchGate(ctx, replyToMode, isBatched) {
118
120
  ctx.ReplyThreading = replyThreading;
119
121
  }
120
122
  //#endregion
123
+ //#region extensions/discord/src/monitor/message-handler.reply-typing-policy.ts
124
+ function resolveDiscordSourceReplyDeliveryMode(ctx) {
125
+ return resolveChannelMessageSourceReplyDeliveryMode({
126
+ cfg: ctx.cfg,
127
+ ctx: {
128
+ ChatType: ctx.isDirectMessage ? "direct" : ctx.isGroupDm ? "group" : ctx.isGuildMessage ? "channel" : void 0,
129
+ InboundEventKind: ctx.inboundEventKind
130
+ }
131
+ });
132
+ }
133
+ function resolveDiscordAcceptedTypingPrestart(ctx) {
134
+ const sourceReplyDeliveryMode = resolveDiscordSourceReplyDeliveryMode(ctx);
135
+ if (ctx.abortSignal?.aborted) return {
136
+ sourceReplyDeliveryMode,
137
+ shouldPrestart: false,
138
+ reason: "aborted"
139
+ };
140
+ if (!ctx.messageText.trim()) return {
141
+ sourceReplyDeliveryMode,
142
+ shouldPrestart: false,
143
+ reason: "empty"
144
+ };
145
+ if (ctx.inboundEventKind === "room_event") return {
146
+ sourceReplyDeliveryMode,
147
+ shouldPrestart: false,
148
+ reason: "room-event"
149
+ };
150
+ const configuredTypingMode = ctx.cfg.session?.typingMode ?? ctx.cfg.agents?.defaults?.typingMode;
151
+ if (configuredTypingMode !== void 0) return {
152
+ sourceReplyDeliveryMode,
153
+ shouldPrestart: configuredTypingMode === "instant",
154
+ reason: configuredTypingMode === "instant" ? "configured-instant" : "configured-not-instant"
155
+ };
156
+ if (sourceReplyDeliveryMode === "message_tool_only") return {
157
+ sourceReplyDeliveryMode,
158
+ shouldPrestart: true,
159
+ reason: "tool-only"
160
+ };
161
+ if (!ctx.isGuildMessage && !ctx.isGroupDm) return {
162
+ sourceReplyDeliveryMode,
163
+ shouldPrestart: true,
164
+ reason: "direct"
165
+ };
166
+ if (ctx.effectiveWasMentioned) return {
167
+ sourceReplyDeliveryMode,
168
+ shouldPrestart: true,
169
+ reason: "mentioned-group"
170
+ };
171
+ return {
172
+ sourceReplyDeliveryMode,
173
+ shouldPrestart: false,
174
+ reason: "defer-to-message"
175
+ };
176
+ }
177
+ //#endregion
121
178
  //#region extensions/discord/src/monitor/message-run-queue.ts
122
179
  let messageProcessRuntimePromise;
123
180
  async function loadMessageProcessRuntime() {
124
- messageProcessRuntimePromise ??= import("./message-handler.process-B1ODKav3.js");
181
+ messageProcessRuntimePromise ??= import("./message-handler.process-Cq3j7A9k.js");
125
182
  return await messageProcessRuntimePromise;
126
183
  }
127
184
  async function processDiscordQueuedMessage(params) {
@@ -146,8 +203,20 @@ async function processDiscordQueuedMessage(params) {
146
203
  throw error;
147
204
  }
148
205
  }
206
+ function cleanupSkippedDiscordQueuedMessage(params) {
207
+ try {
208
+ params.job.runtime.replyTypingFeedback?.onCleanup?.();
209
+ } finally {
210
+ releaseDiscordInboundReplay({
211
+ replayKeys: params.job.replayKeys,
212
+ error: new DiscordRetryableInboundError("discord queued run skipped before processing"),
213
+ replayGuard: params.replayGuard
214
+ });
215
+ }
216
+ }
149
217
  function createDiscordMessageRunQueue(params) {
150
218
  const replayGuard = params.replayGuard ?? createDiscordInboundReplayGuard();
219
+ const skippedCleanup = /* @__PURE__ */ new Set();
151
220
  const runQueue = createChannelRunQueue({
152
221
  setStatus: params.setStatus,
153
222
  abortSignal: params.abortSignal,
@@ -155,9 +224,31 @@ function createDiscordMessageRunQueue(params) {
155
224
  params.runtime.error(danger(`discord message run failed: ${String(error)}`));
156
225
  }
157
226
  });
227
+ let lifecycleActive = !params.abortSignal?.aborted;
228
+ const cleanupSkippedQueuedMessages = () => {
229
+ if (!lifecycleActive && skippedCleanup.size === 0) return;
230
+ lifecycleActive = false;
231
+ const cleanups = [...skippedCleanup];
232
+ skippedCleanup.clear();
233
+ for (const cleanup of cleanups) cleanup();
234
+ };
235
+ if (params.abortSignal?.aborted) cleanupSkippedQueuedMessages();
236
+ else params.abortSignal?.addEventListener("abort", cleanupSkippedQueuedMessages, { once: true });
158
237
  return {
159
238
  enqueue(job) {
239
+ const cleanupSkipped = () => {
240
+ cleanupSkippedDiscordQueuedMessage({
241
+ job,
242
+ replayGuard
243
+ });
244
+ };
245
+ if (!lifecycleActive) {
246
+ cleanupSkipped();
247
+ return;
248
+ }
249
+ skippedCleanup.add(cleanupSkipped);
160
250
  runQueue.enqueue(job.queueKey, async ({ lifecycleSignal }) => {
251
+ skippedCleanup.delete(cleanupSkipped);
161
252
  await processDiscordQueuedMessage({
162
253
  job,
163
254
  lifecycleSignal,
@@ -166,39 +257,88 @@ function createDiscordMessageRunQueue(params) {
166
257
  });
167
258
  });
168
259
  },
169
- deactivate: runQueue.deactivate
260
+ deactivate() {
261
+ runQueue.deactivate();
262
+ cleanupSkippedQueuedMessages();
263
+ }
264
+ };
265
+ }
266
+ function createDiscordReplyTypingFeedback(params) {
267
+ let channelId = params.channelId;
268
+ const rest = params.rest ?? createDiscordRestClient({
269
+ cfg: params.cfg,
270
+ token: params.token,
271
+ accountId: params.accountId
272
+ }).rest;
273
+ const createCallbacks = () => createTypingCallbacks({
274
+ start: () => sendTyping({
275
+ rest,
276
+ channelId
277
+ }),
278
+ onStartError: (err) => {
279
+ logTypingFailure({
280
+ log: params.log,
281
+ channel: "discord",
282
+ target: channelId,
283
+ error: err
284
+ });
285
+ },
286
+ maxDurationMs: params.maxDurationMs ?? 12e5
287
+ });
288
+ const updateChannelId = (nextChannelId) => {
289
+ const trimmed = nextChannelId.trim();
290
+ if (trimmed) channelId = trimmed;
291
+ };
292
+ let callbacks = createCallbacks();
293
+ return {
294
+ onReplyStart: () => callbacks.onReplyStart(),
295
+ onIdle: () => callbacks.onIdle?.(),
296
+ onCleanup: () => callbacks.onCleanup?.(),
297
+ updateChannelId,
298
+ restartForDispatch: (nextChannelId) => {
299
+ updateChannelId(nextChannelId);
300
+ callbacks.onCleanup?.();
301
+ callbacks = createCallbacks();
302
+ },
303
+ getChannelId: () => channelId
170
304
  };
171
305
  }
172
306
  //#endregion
173
307
  //#region extensions/discord/src/monitor/message-handler.ts
174
308
  let messagePreflightRuntimePromise;
175
309
  async function loadMessagePreflightRuntime() {
176
- messagePreflightRuntimePromise ??= import("./message-handler.preflight-B1RTQ6ZO.js");
310
+ messagePreflightRuntimePromise ??= import("./message-handler.preflight-DxHBPQvD.js");
177
311
  return await messagePreflightRuntimePromise;
178
312
  }
179
313
  function isNonEmptyString(value) {
180
314
  return typeof value === "string" && value.length > 0;
181
315
  }
182
- function shouldSendAcceptedDiscordTypingCue(ctx) {
183
- if (ctx.abortSignal?.aborted) return false;
184
- if (!ctx.isDirectMessage || ctx.isGuildMessage || ctx.isGroupDm) return false;
185
- if (!ctx.messageText.trim()) return false;
186
- const configuredTypingMode = ctx.cfg.session?.typingMode ?? ctx.cfg.agents?.defaults?.typingMode;
187
- return configuredTypingMode === void 0 || configuredTypingMode === "instant";
188
- }
189
- function queueAcceptedDiscordTypingCue(ctx) {
190
- if (!shouldSendAcceptedDiscordTypingCue(ctx)) return;
191
- const { rest } = createDiscordRestClient({
316
+ function startAcceptedTypingFeedback(params) {
317
+ const { ctx, createFeedback, dedupeKey, activeFeedback } = params;
318
+ if (!resolveDiscordAcceptedTypingPrestart(ctx).shouldPrestart) return;
319
+ const channelId = ctx.messageChannelId.trim();
320
+ if (activeFeedback.get(dedupeKey)) return;
321
+ const replyTypingFeedback = ctx.replyTypingFeedback ?? (createFeedback ?? createDiscordReplyTypingFeedback)({
192
322
  cfg: ctx.cfg,
193
323
  token: ctx.token,
194
- accountId: ctx.accountId
324
+ accountId: ctx.accountId,
325
+ channelId: ctx.messageChannelId,
326
+ log: logVerbose
327
+ });
328
+ const cleanup = replyTypingFeedback.onCleanup;
329
+ replyTypingFeedback.onCleanup = () => {
330
+ cleanup?.();
331
+ if (activeFeedback.get(dedupeKey)?.feedback === replyTypingFeedback) activeFeedback.delete(dedupeKey);
332
+ };
333
+ activeFeedback.set(dedupeKey, {
334
+ channelId,
335
+ feedback: replyTypingFeedback
195
336
  });
196
- sendTyping({
197
- rest,
198
- channelId: ctx.messageChannelId
199
- }).catch((err) => {
200
- logVerbose(`discord early typing cue failed for channel ${ctx.messageChannelId}: ${String(err)}`);
337
+ ctx.replyTypingFeedback = replyTypingFeedback;
338
+ replyTypingFeedback.onReplyStart().catch((err) => {
339
+ logVerbose(`discord accepted typing feedback failed: ${String(err)}`);
201
340
  });
341
+ return replyTypingFeedback;
202
342
  }
203
343
  function createDiscordMessageHandler(params) {
204
344
  const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
@@ -209,6 +349,7 @@ function createDiscordMessageHandler(params) {
209
349
  const ackReactionScope = params.discordConfig?.ackReactionScope ?? params.cfg.messages?.ackReactionScope ?? "group-mentions";
210
350
  const preflightDiscordMessageImpl = params.testing?.preflightDiscordMessage;
211
351
  const replayGuard = createDiscordInboundReplayGuard();
352
+ const prestartedTypingFeedback = /* @__PURE__ */ new Map();
212
353
  const messageRunQueue = createDiscordMessageRunQueue({
213
354
  runtime: params.runtime,
214
355
  setStatus: params.setStatus,
@@ -269,8 +410,14 @@ function createDiscordMessageHandler(params) {
269
410
  });
270
411
  return;
271
412
  }
413
+ const queueKey = resolveDiscordInboundJobQueueKey(ctx);
414
+ startAcceptedTypingFeedback({
415
+ ctx,
416
+ createFeedback: params.testing?.createReplyTypingFeedback,
417
+ dedupeKey: queueKey,
418
+ activeFeedback: prestartedTypingFeedback
419
+ });
272
420
  applyImplicitReplyBatchGate(ctx, params.replyToMode, false);
273
- queueAcceptedDiscordTypingCue(ctx);
274
421
  messageRunQueue.enqueue(buildDiscordInboundJob(ctx, { replayKeys }));
275
422
  return;
276
423
  }
@@ -322,6 +469,13 @@ function createDiscordMessageHandler(params) {
322
469
  });
323
470
  return;
324
471
  }
472
+ const queueKey = resolveDiscordInboundJobQueueKey(ctx);
473
+ startAcceptedTypingFeedback({
474
+ ctx,
475
+ createFeedback: params.testing?.createReplyTypingFeedback,
476
+ dedupeKey: queueKey,
477
+ activeFeedback: prestartedTypingFeedback
478
+ });
325
479
  applyImplicitReplyBatchGate(ctx, params.replyToMode, true);
326
480
  if (entries.length > 1) {
327
481
  const ids = entries.map((entry) => entry.data.message?.id).filter(isNonEmptyString);
@@ -332,7 +486,6 @@ function createDiscordMessageHandler(params) {
332
486
  ctxBatch.MessageSidLast = ids[ids.length - 1];
333
487
  }
334
488
  }
335
- queueAcceptedDiscordTypingCue(ctx);
336
489
  messageRunQueue.enqueue(buildDiscordInboundJob(ctx, { replayKeys }));
337
490
  } catch (error) {
338
491
  if (error instanceof DiscordRetryableInboundError) releaseDiscordInboundReplay({
@@ -378,4 +531,4 @@ function createDiscordMessageHandler(params) {
378
531
  return handler;
379
532
  }
380
533
  //#endregion
381
- export { createDiscordMessageHandler as t };
534
+ export { createDiscordReplyTypingFeedback as n, createDiscordMessageHandler as t };
@@ -1,12 +1,12 @@
1
- import { c as discord_exports, k as Message, mt as getChannelMessage } from "./send.receipt-3y2lVFRf.js";
2
- import { o as resolveDefaultDiscordAccountId } from "./accounts-dXTfmnSZ.js";
1
+ import { c as discord_exports, k as Message, mt as getChannelMessage } from "./send.receipt-CjsWpitW.js";
2
+ import { o as resolveDefaultDiscordAccountId } from "./accounts-P19jm1lf.js";
3
3
  import { S as resolveTimestampMs, _ as resolveGroupDmAllow, a as normalizeDiscordSlug, b as formatDiscordUserTag, c as resolveDiscordChannelConfigWithFallback, d as resolveDiscordGuildEntry, f as resolveDiscordMemberAccessState, g as resolveDiscordShouldRequireMention, i as normalizeDiscordDisplaySlug, n as isDiscordGroupAllowedByPolicy, x as resolveDiscordSystemLocation } from "./allow-list-BnkWtVpA.js";
4
- import { t as resolveDiscordConversationIdentity } from "./conversation-identity-CsPV1Gjx.js";
5
- import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-BsOnj5NX.js";
6
- import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-5QgEd_dW.js";
7
- import "./thread-bindings-BBq-yzq2.js";
8
- import { D as resolveDiscordTextCommandAccess, E as resolveDiscordDmCommandAccess, _ as resolveDiscordEffectiveRoute, g as resolveDiscordConversationRoute, h as buildDiscordRoutePeer, v as shouldIgnoreStaleDiscordRouteBinding, y as handleDiscordDmCommandDecision } from "./provider-DCcqVFp-.js";
9
- import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-CmB03FoF.js";
4
+ import { t as resolveDiscordConversationIdentity } from "./conversation-identity-BURFgcfI.js";
5
+ import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-DYp4FVEB.js";
6
+ import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-BjLUwwSP.js";
7
+ import "./thread-bindings-CKruJE91.js";
8
+ import { D as resolveDiscordTextCommandAccess, E as resolveDiscordDmCommandAccess, _ as resolveDiscordEffectiveRoute, g as resolveDiscordConversationRoute, h as buildDiscordRoutePeer, v as shouldIgnoreStaleDiscordRouteBinding, y as handleDiscordDmCommandDecision } from "./provider-BK8hjk_y.js";
9
+ import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-D_3KTxh_.js";
10
10
  import { n as resolveDiscordWebhookId, t as resolveDiscordSenderIdentity } from "./sender-identity-BFp5w0F8.js";
11
11
  import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
12
12
  import { getChildLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
@@ -28,9 +28,12 @@ async function loadConversationRuntime$1() {
28
28
  return await conversationRuntimePromise$1;
29
29
  }
30
30
  async function loadDiscordSendRuntime() {
31
- discordSendRuntimePromise ??= import("./send-CBxhnbKU.js").then((n) => n.t);
31
+ discordSendRuntimePromise ??= import("./send-BHkA8a-k.js").then((n) => n.t);
32
32
  return await discordSendRuntimePromise;
33
33
  }
34
+ function resolveDiscordDmPairingSenderId(sender) {
35
+ return sender.isPluralKit ? `pk:${sender.id}` : sender.id;
36
+ }
34
37
  async function resolveDiscordDmPreflightAccess(params) {
35
38
  if (params.dmPolicy === "disabled") {
36
39
  logVerbose("discord: drop dm (dmPolicy: disabled)");
@@ -69,9 +72,9 @@ async function resolveDiscordDmPreflightAccess(params) {
69
72
  senderAccess: dmAccess.senderAccess,
70
73
  accountId: params.resolvedAccountId,
71
74
  sender: {
72
- id: params.author.id,
73
- tag: formatDiscordUserTag(params.author),
74
- name: params.author.username ?? void 0
75
+ id: resolveDiscordDmPairingSenderId(params.sender),
76
+ tag: params.sender.tag ?? formatDiscordUserTag(params.author),
77
+ name: params.sender.name ?? params.author.username ?? void 0
75
78
  },
76
79
  onPairingCreated: async (code) => {
77
80
  logVerbose(`discord pairing request sender=${params.author.id} tag=${formatDiscordUserTag(params.author)} reason=${dmAccess.senderAccess.reasonCode}`);
@@ -423,7 +426,7 @@ let preflightAudioRuntimePromise;
423
426
  let systemEventsRuntimePromise;
424
427
  let discordThreadingRuntimePromise;
425
428
  async function loadPluralKitRuntime() {
426
- pluralkitRuntimePromise ??= import("./pluralkit-DAZN3ZMK.js").then((n) => n.n);
429
+ pluralkitRuntimePromise ??= import("./pluralkit-Ds9aZAM4.js").then((n) => n.n);
427
430
  return await pluralkitRuntimePromise;
428
431
  }
429
432
  async function loadPreflightAudioRuntime() {
@@ -431,11 +434,11 @@ async function loadPreflightAudioRuntime() {
431
434
  return await preflightAudioRuntimePromise;
432
435
  }
433
436
  async function loadSystemEventsRuntime() {
434
- systemEventsRuntimePromise ??= import("./system-events-Cw6Hp8RF.js");
437
+ systemEventsRuntimePromise ??= import("./system-events-BjuNA9Zs.js");
435
438
  return await systemEventsRuntimePromise;
436
439
  }
437
440
  async function loadDiscordThreadingRuntime() {
438
- discordThreadingRuntimePromise ??= import("./provider-DCcqVFp-.js").then((n) => n.x);
441
+ discordThreadingRuntimePromise ??= import("./provider-BK8hjk_y.js").then((n) => n.x);
439
442
  return await discordThreadingRuntimePromise;
440
443
  }
441
444
  function isPreflightAborted(abortSignal) {
@@ -796,7 +799,11 @@ async function preflightDiscordMessage(params) {
796
799
  logVerbose(`discord: drop bound-thread bot system message ${message.id}`);
797
800
  return null;
798
801
  }
799
- const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId);
802
+ const mentionRegexes = buildMentionRegexes(params.cfg, effectiveRoute.agentId, {
803
+ provider: "discord",
804
+ conversationId: messageChannelId,
805
+ providerPolicy: params.discordConfig?.mentionPatterns
806
+ });
800
807
  const explicitlyMentioned = Boolean(botId && message.mentionedUsers?.some((user) => user.id === botId));
801
808
  const hasAnyMention = !isDirectMessage && ((message.mentionedUsers?.length ?? 0) > 0 || (message.mentionedRoles?.length ?? 0) > 0 || message.mentionedEveryone && (!author.bot || sender.isPluralKit));
802
809
  const hasUserOrRoleMention = !isDirectMessage && ((message.mentionedUsers?.length ?? 0) > 0 || (message.mentionedRoles?.length ?? 0) > 0);
@@ -809,7 +816,7 @@ async function preflightDiscordMessage(params) {
809
816
  guildId: params.data.guild_id ?? void 0,
810
817
  guildEntries: params.guildEntries
811
818
  }) : null;
812
- logDebug(`[discord-preflight] guild_id=${params.data.guild_id} guild_obj=${!!params.data.guild} guild_obj_id=${params.data.guild?.id} guildInfo=${!!guildInfo} guildEntries=${params.guildEntries ? Object.keys(params.guildEntries).join(",") : "none"}`);
819
+ logDebug(`[discord-preflight] guild_id=${params.data.guild_id} guild_obj=${Boolean(params.data.guild)} guild_obj_id=${params.data.guild?.id} guildInfo=${Boolean(guildInfo)} guildEntries=${params.guildEntries ? Object.keys(params.guildEntries).join(",") : "none"}`);
813
820
  if (isGuildMessage && params.guildEntries && Object.keys(params.guildEntries).length > 0 && !guildInfo) {
814
821
  logDebug(`[discord-preflight] guild blocked: guild_id=${params.data.guild_id} guildEntries keys=${Object.keys(params.guildEntries).join(",")}`);
815
822
  logVerbose(`Blocked discord guild ${params.data.guild_id ?? "unknown"} (not in discord.guilds)`);
@@ -1,17 +1,18 @@
1
- import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-3y2lVFRf.js";
2
- import { f as resolveDiscordMaxLinesPerMessage } from "./accounts-dXTfmnSZ.js";
3
- import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-Ddl21-Gt.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-CjsWpitW.js";
2
+ import { f as resolveDiscordMaxLinesPerMessage, s as resolveDiscordAccount } from "./accounts-P19jm1lf.js";
3
+ import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-Cluujl6W.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-CBxhnbKU.js";
6
- import "./targets-BVKF5wbg.js";
7
- import { t as resolveDiscordConversationIdentity } from "./conversation-identity-CsPV1Gjx.js";
5
+ import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-BHkA8a-k.js";
6
+ import { a as discordTextHasTargetedMention, i as discordTextHasBroadcastMention, s as rewriteDiscordKnownMentions } from "./send.outbound-DSKZbjML.js";
7
+ import "./targets-BNDUOdvI.js";
8
+ import { t as resolveDiscordConversationIdentity } from "./conversation-identity-BURFgcfI.js";
8
9
  import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-CEPlt2uz.js";
9
- import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-DfY2Dtf3.js";
10
+ import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-BkPkILpq.js";
10
11
  import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-DXT8oJdo.js";
11
- import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-l_PsHQvX.js";
12
- import { S as resolveDiscordAutoThreadReplyPlan, a as resolveReplyContext, i as buildGuildLabel, m as createDiscordSupplementalContextAccessChecker, n as deliverDiscordReply, p as buildDiscordInboundAccessContext, r as buildDirectLabel, w as resolveDiscordThreadStarter } from "./provider-DCcqVFp-.js";
13
- import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-CmB03FoF.js";
14
- import { t as sendTyping } from "./typing-B7bCMPJJ.js";
12
+ import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-CEJ3--Uw.js";
13
+ import { S as resolveDiscordAutoThreadReplyPlan, a as resolveReplyContext, i as buildGuildLabel, m as createDiscordSupplementalContextAccessChecker, n as deliverDiscordReply, p as buildDiscordInboundAccessContext, r as buildDirectLabel, w as resolveDiscordThreadStarter } from "./provider-BK8hjk_y.js";
14
+ import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-D_3KTxh_.js";
15
+ import { n as createDiscordReplyTypingFeedback } from "./message-handler-HO8BbZmQ.js";
15
16
  import { buildAgentSessionKey, normalizeAccountId, resolveAccountEntry, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
16
17
  import { MessageFlags } from "discord-api-types/v10";
17
18
  import path from "node:path";
@@ -30,7 +31,7 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
30
31
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
31
32
  import { loadSessionStore, readLatestAssistantTextFromSessionTranscript, readSessionUpdatedAt, resolveAndPersistSessionFile, resolveSessionStoreEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
32
33
  import { buildChannelInboundEventContext, dispatchChannelInboundReply, formatInboundEnvelope, hasFinalInboundReplyDispatch, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toHistoryMediaEntries, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
33
- import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
34
+ import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
34
35
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
35
36
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
36
37
  //#region extensions/discord/src/monitor/ack-reactions.ts
@@ -286,6 +287,7 @@ async function buildDiscordMessageProcessContext(params) {
286
287
  id: messageChannelId,
287
288
  label: fromLabel,
288
289
  spaceId: isGuildMessage ? (guildInfo?.id ?? guildSlug) || void 0 : void 0,
290
+ parentId: threadChannel ? threadParentId : void 0,
289
291
  threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? void 0
290
292
  },
291
293
  route: {
@@ -848,7 +850,6 @@ function sleep(ms) {
848
850
  setTimeout(resolve, ms);
849
851
  });
850
852
  }
851
- const DISCORD_TYPING_MAX_DURATION_MS = 20 * 6e4;
852
853
  let replyRuntimePromise;
853
854
  async function loadReplyRuntime() {
854
855
  replyRuntimePromise ??= import("openclaw/plugin-sdk/reply-runtime");
@@ -877,8 +878,15 @@ function readToolBooleanArg(args, key) {
877
878
  return args[key] === true;
878
879
  }
879
880
  async function processDiscordMessage(ctx, observer) {
881
+ try {
882
+ await processDiscordMessageInner(ctx, observer);
883
+ } finally {
884
+ ctx.replyTypingFeedback?.onCleanup?.();
885
+ }
886
+ }
887
+ async function processDiscordMessageInner(ctx, observer) {
880
888
  const dispatchStartedAt = Date.now();
881
- const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, channelConfig, threadBindings, route, discordRestFetch, abortSignal, botLoopProtection } = ctx;
889
+ const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, channelConfig, threadBindings, route, discordRestFetch, abortSignal, botLoopProtection, replyTypingFeedback } = ctx;
882
890
  if (isProcessAborted(abortSignal)) return;
883
891
  if (botLoopProtection) {
884
892
  const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
@@ -1050,26 +1058,22 @@ async function processDiscordMessage(ctx, observer) {
1050
1058
  sessionKey: persistedSessionKey
1051
1059
  });
1052
1060
  const typingChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
1061
+ const typingFeedback = replyTypingFeedback ?? createDiscordReplyTypingFeedback({
1062
+ cfg,
1063
+ token,
1064
+ accountId,
1065
+ channelId: typingChannelId,
1066
+ rest: feedbackRest,
1067
+ log: logVerbose
1068
+ });
1069
+ if (replyTypingFeedback) replyTypingFeedback.restartForDispatch(typingChannelId);
1070
+ else typingFeedback.updateChannelId(typingChannelId);
1053
1071
  const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
1054
1072
  cfg,
1055
1073
  agentId: route.agentId,
1056
1074
  channel: "discord",
1057
1075
  accountId: route.accountId,
1058
- typing: {
1059
- start: () => sendTyping({
1060
- rest: feedbackRest,
1061
- channelId: typingChannelId
1062
- }),
1063
- onStartError: (err) => {
1064
- logTypingFailure({
1065
- log: logVerbose,
1066
- channel: "discord",
1067
- target: typingChannelId,
1068
- error: err
1069
- });
1070
- },
1071
- maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS
1072
- }
1076
+ typingCallbacks: typingFeedback
1073
1077
  });
1074
1078
  const tableMode = resolveMarkdownTableMode({
1075
1079
  cfg,
@@ -1241,6 +1245,14 @@ async function processDiscordMessage(ctx, observer) {
1241
1245
  },
1242
1246
  buildFinalEdit: () => {
1243
1247
  if (draftPreview.finalizedViaPreviewMessage || hasMedia && !ttsSupplement || typeof previewFinalText !== "string" || hasExplicitReplyDirective || payload.isError) return;
1248
+ const rewrittenFinal = rewriteDiscordKnownMentions(previewFinalText, {
1249
+ accountId,
1250
+ mentionAliases: resolveDiscordAccount({
1251
+ cfg,
1252
+ accountId
1253
+ }).config.mentionAliases
1254
+ });
1255
+ if (discordTextHasTargetedMention(rewrittenFinal) && !discordTextHasBroadcastMention(rewrittenFinal)) return;
1244
1256
  return {
1245
1257
  content: previewFinalText,
1246
1258
  ...finalPreviewFlags ? { flags: finalPreviewFlags } : {}