@openclaw/discord 2026.5.28 → 2026.5.30-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 (71) hide show
  1. package/dist/{account-inspect-C4sJZNbI.js → account-inspect-BkL-fm-M.js} +7 -5
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/action-runtime-api.js +1 -1
  4. package/dist/api.js +14 -14
  5. package/dist/{approval-handler.runtime-CN_E7nkQ.js → approval-handler.runtime-BFLqemq4.js} +4 -4
  6. package/dist/{audit-BFCBp74H.js → audit-CKzCd_nb.js} +4 -4
  7. package/dist/{channel-COXb22wL.js → channel-DnUKVkVn.js} +16 -16
  8. package/dist/{channel-actions-C2g9OmnG.js → channel-actions-D60aLg8q.js} +3 -3
  9. package/dist/{channel-actions.runtime-drnbrgpt.js → channel-actions.runtime-D6awrVWB.js} +5 -5
  10. package/dist/channel-config-api.js +1 -1
  11. package/dist/channel-plugin-api.js +1 -1
  12. package/dist/{channel.setup-BsrP5-hv.js → channel.setup-Cqfoar5h.js} +6 -20
  13. package/dist/{components-CjQujWa1.js → components-CWgTTA0J.js} +1 -1
  14. package/dist/{config-schema-3tZYNVTM.js → config-schema-C1lzB2Ld.js} +1 -1
  15. package/dist/contract-api.js +2 -2
  16. package/dist/{conversation-identity-CsPV1Gjx.js → conversation-identity-Dvi-8LyR.js} +2 -2
  17. package/dist/{directory-config-BqPvRoDv.js → directory-config-B-i4XzWZ.js} +1 -1
  18. package/dist/directory-contract-api.js +1 -1
  19. package/dist/{directory-live-CCI0IMZq.js → directory-live-B5E-IbgC.js} +1 -1
  20. package/dist/{doctor-Dy7sI1sL.js → doctor-DT41iqQA.js} +2 -2
  21. package/dist/{handle-action.guild-admin-BmjclQbR.js → handle-action.guild-admin-B39nAL5c.js} +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/legacy-state-migrations-api.js +3 -1
  24. package/dist/{manager.runtime-ClhnLsZf.js → manager.runtime-ClHe315Q.js} +16 -8
  25. package/dist/{message-handler-BnhAHqem.js → message-handler-Deidymb-.js} +182 -29
  26. package/dist/{message-handler.preflight-B1RTQ6ZO.js → message-handler.preflight-DeTwc2nk.js} +11 -11
  27. package/dist/{message-handler.process-B1ODKav3.js → message-handler.process-DJkFWirK.js} +30 -28
  28. package/dist/{message-utils-CmB03FoF.js → message-utils-C4OlktSy.js} +19 -15
  29. package/dist/{outbound-adapter-DfY2Dtf3.js → outbound-adapter-DCC3zPuC.js} +51 -86
  30. package/dist/{pluralkit-DAZN3ZMK.js → pluralkit-Dhad974D.js} +1 -1
  31. package/dist/{provider-DCcqVFp-.js → provider-SeIwaTTJ.js} +127 -133
  32. package/dist/{provider-session.runtime-mKSG07RE.js → provider-session.runtime-B5aLy8NA.js} +3 -3
  33. package/dist/provider.runtime-DVHYE9ZI.js +2 -0
  34. package/dist/{resolve-channels-D1zgoxnw.js → resolve-channels-LMOlvXSw.js} +1 -1
  35. package/dist/{resolve-users-wPAvLk_8.js → resolve-users-WNAKeCd_.js} +1 -1
  36. package/dist/{runtime-gaB5-Wsj.js → runtime-Dg0INaGm.js} +7 -7
  37. package/dist/runtime-api.actions.js +2 -2
  38. package/dist/runtime-api.js +21 -21
  39. package/dist/runtime-api.lookup.js +4 -4
  40. package/dist/runtime-api.monitor-C56AP2ZG.js +6 -0
  41. package/dist/runtime-api.monitor.js +5 -5
  42. package/dist/runtime-api.send.js +5 -5
  43. package/dist/runtime-api.threads.js +5 -5
  44. package/dist/{send-CBxhnbKU.js → send-DwUf-fzH.js} +6 -6
  45. package/dist/{send.components-uuQuHL3t.js → send.components-BVfuI0-G.js} +25 -11
  46. package/dist/{send.outbound-DZHJYmNF.js → send.outbound-4IJRwYbJ.js} +3 -3
  47. package/dist/{send.receipt-3y2lVFRf.js → send.receipt-CxFJX9o3.js} +22 -10
  48. package/dist/{send.shared-Ddl21-Gt.js → send.shared-DQwyGjXe.js} +2 -2
  49. package/dist/setup-plugin-api.js +1 -1
  50. package/dist/{shared-D3NkInmq.js → shared-D-ePFtjZ.js} +3 -3
  51. package/dist/{subagent-hooks-BacIbIBD.js → subagent-hooks-CqhiwYpb.js} +2 -2
  52. package/dist/subagent-hooks-api.js +1 -5
  53. package/dist/{system-events-Cw6Hp8RF.js → system-events-DZdHRt2j.js} +1 -1
  54. package/dist/{target-resolver-jjQzAS6T.js → target-resolver-DTD90GqZ.js} +2 -2
  55. package/dist/targets-xBATI2q7.js +3 -0
  56. package/dist/{thread-bindings-BBq-yzq2.js → thread-bindings-VrlFUPDW.js} +6 -6
  57. package/dist/{thread-bindings.discord-api-5QgEd_dW.js → thread-bindings.discord-api-CItgvv24.js} +5 -5
  58. package/dist/{thread-bindings.manager-COkxjwR9.js → thread-bindings.manager-hhqHJlGr.js} +4 -4
  59. package/dist/{thread-bindings.session-updates-D5gY2ZTE.js → thread-bindings.session-updates-0BaqAU8S.js} +1 -1
  60. package/dist/{thread-bindings.state-BsOnj5NX.js → thread-bindings.state-B-iprgEM.js} +8 -2
  61. package/dist/{timeouts-l_PsHQvX.js → timeouts-CEJ3--Uw.js} +5 -2
  62. package/dist/timeouts.js +1 -1
  63. package/dist/{transcripts-source-DDrS8HrV.js → transcripts-source-CFsqnHc4.js} +1 -1
  64. package/dist/transcripts-source-api.js +1 -1
  65. package/dist/{typing-B7bCMPJJ.js → typing-Bfns8H17.js} +2 -2
  66. package/npm-shrinkwrap.json +3 -3
  67. package/openclaw.plugin.json +1 -1
  68. package/package.json +4 -4
  69. package/dist/provider.runtime-BmpunJHX.js +0 -2
  70. package/dist/runtime-api.monitor-B38upMUK.js +0 -5
  71. package/dist/targets-BVKF5wbg.js +0 -3
@@ -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-DQwyGjXe.js";
2
+ import { d as resolveDiscordChannelNameSafe, l as resolveDiscordChannelIdSafe, p as resolveDiscordChannelParentSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.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-C4OlktSy.js";
5
+ import { t as sendTyping } from "./typing-Bfns8H17.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-DJkFWirK.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-DeTwc2nk.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";
1
+ import { c as discord_exports, k as Message, mt as getChannelMessage } from "./send.receipt-CxFJX9o3.js";
2
2
  import { o as resolveDefaultDiscordAccountId } from "./accounts-dXTfmnSZ.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-Dvi-8LyR.js";
5
+ import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-B-iprgEM.js";
6
+ import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.js";
7
+ import "./thread-bindings-VrlFUPDW.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-SeIwaTTJ.js";
9
+ import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-C4OlktSy.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,7 +28,7 @@ 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-DwUf-fzH.js").then((n) => n.t);
32
32
  return await discordSendRuntimePromise;
33
33
  }
34
34
  async function resolveDiscordDmPreflightAccess(params) {
@@ -423,7 +423,7 @@ let preflightAudioRuntimePromise;
423
423
  let systemEventsRuntimePromise;
424
424
  let discordThreadingRuntimePromise;
425
425
  async function loadPluralKitRuntime() {
426
- pluralkitRuntimePromise ??= import("./pluralkit-DAZN3ZMK.js").then((n) => n.n);
426
+ pluralkitRuntimePromise ??= import("./pluralkit-Dhad974D.js").then((n) => n.n);
427
427
  return await pluralkitRuntimePromise;
428
428
  }
429
429
  async function loadPreflightAudioRuntime() {
@@ -431,11 +431,11 @@ async function loadPreflightAudioRuntime() {
431
431
  return await preflightAudioRuntimePromise;
432
432
  }
433
433
  async function loadSystemEventsRuntime() {
434
- systemEventsRuntimePromise ??= import("./system-events-Cw6Hp8RF.js");
434
+ systemEventsRuntimePromise ??= import("./system-events-DZdHRt2j.js");
435
435
  return await systemEventsRuntimePromise;
436
436
  }
437
437
  async function loadDiscordThreadingRuntime() {
438
- discordThreadingRuntimePromise ??= import("./provider-DCcqVFp-.js").then((n) => n.x);
438
+ discordThreadingRuntimePromise ??= import("./provider-SeIwaTTJ.js").then((n) => n.x);
439
439
  return await discordThreadingRuntimePromise;
440
440
  }
441
441
  function isPreflightAborted(abortSignal) {
@@ -1,17 +1,17 @@
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";
1
+ import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-CxFJX9o3.js";
2
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";
3
+ import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-DQwyGjXe.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-DwUf-fzH.js";
6
+ import "./targets-xBATI2q7.js";
7
+ import { t as resolveDiscordConversationIdentity } from "./conversation-identity-Dvi-8LyR.js";
8
8
  import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-CEPlt2uz.js";
9
- import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-DfY2Dtf3.js";
9
+ import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-DCC3zPuC.js";
10
10
  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";
11
+ import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-CEJ3--Uw.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-SeIwaTTJ.js";
13
+ import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-C4OlktSy.js";
14
+ import { n as createDiscordReplyTypingFeedback } from "./message-handler-Deidymb-.js";
15
15
  import { buildAgentSessionKey, normalizeAccountId, resolveAccountEntry, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
16
16
  import { MessageFlags } from "discord-api-types/v10";
17
17
  import path from "node:path";
@@ -30,7 +30,7 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
30
30
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
31
31
  import { loadSessionStore, readLatestAssistantTextFromSessionTranscript, readSessionUpdatedAt, resolveAndPersistSessionFile, resolveSessionStoreEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
32
32
  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";
33
+ import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
34
34
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
35
35
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
36
36
  //#region extensions/discord/src/monitor/ack-reactions.ts
@@ -848,7 +848,6 @@ function sleep(ms) {
848
848
  setTimeout(resolve, ms);
849
849
  });
850
850
  }
851
- const DISCORD_TYPING_MAX_DURATION_MS = 20 * 6e4;
852
851
  let replyRuntimePromise;
853
852
  async function loadReplyRuntime() {
854
853
  replyRuntimePromise ??= import("openclaw/plugin-sdk/reply-runtime");
@@ -877,8 +876,15 @@ function readToolBooleanArg(args, key) {
877
876
  return args[key] === true;
878
877
  }
879
878
  async function processDiscordMessage(ctx, observer) {
879
+ try {
880
+ await processDiscordMessageInner(ctx, observer);
881
+ } finally {
882
+ ctx.replyTypingFeedback?.onCleanup?.();
883
+ }
884
+ }
885
+ async function processDiscordMessageInner(ctx, observer) {
880
886
  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;
887
+ 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
888
  if (isProcessAborted(abortSignal)) return;
883
889
  if (botLoopProtection) {
884
890
  const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
@@ -1050,26 +1056,22 @@ async function processDiscordMessage(ctx, observer) {
1050
1056
  sessionKey: persistedSessionKey
1051
1057
  });
1052
1058
  const typingChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
1059
+ const typingFeedback = replyTypingFeedback ?? createDiscordReplyTypingFeedback({
1060
+ cfg,
1061
+ token,
1062
+ accountId,
1063
+ channelId: typingChannelId,
1064
+ rest: feedbackRest,
1065
+ log: logVerbose
1066
+ });
1067
+ if (replyTypingFeedback) replyTypingFeedback.restartForDispatch(typingChannelId);
1068
+ else typingFeedback.updateChannelId(typingChannelId);
1053
1069
  const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
1054
1070
  cfg,
1055
1071
  agentId: route.agentId,
1056
1072
  channel: "discord",
1057
1073
  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
- }
1074
+ typingCallbacks: typingFeedback
1073
1075
  });
1074
1076
  const tableMode = resolveMarkdownTableMode({
1075
1077
  cfg,
@@ -1,7 +1,8 @@
1
- import { u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-5QgEd_dW.js";
2
- import { a as mergeAbortSignals } from "./timeouts-l_PsHQvX.js";
1
+ import { u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.js";
2
+ import { a as mergeAbortSignals } from "./timeouts-CEJ3--Uw.js";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeOptionalStringifiedId, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { ComponentType, StickerFormatType } from "discord-api-types/v10";
5
+ import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
5
6
  import { saveRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
6
7
  import { buildMediaPayload } from "openclaw/plugin-sdk/reply-payload";
7
8
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
@@ -10,6 +11,16 @@ import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
10
11
  const DISCORD_CHANNEL_INFO_CACHE_TTL_MS = 300 * 1e3;
11
12
  const DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS = 30 * 1e3;
12
13
  const DISCORD_CHANNEL_INFO_CACHE = /* @__PURE__ */ new Map();
14
+ function resolveDiscordChannelInfoCacheExpiresAt(ttlMs, nowMs) {
15
+ return resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs });
16
+ }
17
+ function cacheDiscordChannelInfo(channelId, value, ttlMs, nowMs) {
18
+ const expiresAt = resolveDiscordChannelInfoCacheExpiresAt(ttlMs, nowMs);
19
+ if (expiresAt !== void 0) DISCORD_CHANNEL_INFO_CACHE.set(channelId, {
20
+ value,
21
+ expiresAt
22
+ });
23
+ }
13
24
  function normalizeDiscordChannelId(value) {
14
25
  return normalizeOptionalStringifiedId(value) ?? "";
15
26
  }
@@ -18,18 +29,17 @@ function resolveDiscordMessageChannelId(params) {
18
29
  return normalizeDiscordChannelId(message.channelId) || normalizeDiscordChannelId(message.channel_id) || normalizeDiscordChannelId(message.rawData?.channel_id) || normalizeDiscordChannelId(params.eventChannelId);
19
30
  }
20
31
  async function resolveDiscordChannelInfo(client, channelId) {
32
+ const rawNow = Date.now();
33
+ const now = asDateTimestampMs(rawNow);
21
34
  const cached = DISCORD_CHANNEL_INFO_CACHE.get(channelId);
22
35
  if (cached) {
23
- if (cached.expiresAt > Date.now()) return cached.value;
36
+ if (now !== void 0 && cached.expiresAt > now) return cached.value;
24
37
  DISCORD_CHANNEL_INFO_CACHE.delete(channelId);
25
38
  }
26
39
  try {
27
40
  const channel = await client.fetchChannel(channelId);
28
41
  if (!channel) {
29
- DISCORD_CHANNEL_INFO_CACHE.set(channelId, {
30
- value: null,
31
- expiresAt: Date.now() + DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS
32
- });
42
+ cacheDiscordChannelInfo(channelId, null, DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS, rawNow);
33
43
  return null;
34
44
  }
35
45
  const channelInfo = resolveDiscordChannelInfoSafe(channel);
@@ -43,17 +53,11 @@ async function resolveDiscordChannelInfo(client, channelId) {
43
53
  parentId: channelInfo.parentId,
44
54
  ownerId: channelInfo.ownerId
45
55
  };
46
- DISCORD_CHANNEL_INFO_CACHE.set(channelId, {
47
- value: payload,
48
- expiresAt: Date.now() + DISCORD_CHANNEL_INFO_CACHE_TTL_MS
49
- });
56
+ cacheDiscordChannelInfo(channelId, payload, DISCORD_CHANNEL_INFO_CACHE_TTL_MS, rawNow);
50
57
  return payload;
51
58
  } catch (err) {
52
59
  logVerbose(`discord: failed to fetch channel ${channelId}: ${String(err)}`);
53
- DISCORD_CHANNEL_INFO_CACHE.set(channelId, {
54
- value: null,
55
- expiresAt: Date.now() + DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS
56
- });
60
+ cacheDiscordChannelInfo(channelId, null, DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS, rawNow);
57
61
  return null;
58
62
  }
59
63
  }