@openclaw/discord 2026.9.8 → 2026.10.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 (147) hide show
  1. package/README.md +7 -0
  2. package/dist/.setup/{account-inspect-De8hOUxG.mjs → account-inspect-CjKqkDnH.mjs} +2 -2
  3. package/dist/.setup/{accounts-ChaMcixG.mjs → accounts-DXHCL8Zm.mjs} +9 -32
  4. package/dist/.setup/{active-turn-thread-route-fmHl1HNd.mjs → active-turn-thread-route-C4tp9noA.mjs} +5 -9
  5. package/dist/.setup/{activities-api-tvbsU2e3.mjs → activities-api-BL_hJ0JQ.mjs} +15 -18
  6. package/dist/.setup/{api-DVbJwZVt.mjs → api-_oznjffB.mjs} +1 -1
  7. package/dist/.setup/{approval-handler.runtime-DxCK2Rux.mjs → approval-handler.runtime-BilYSrRF.mjs} +22 -61
  8. package/dist/.setup/{approval-message-updates-BKFqavHZ.mjs → approval-message-updates-CgRwc2_Q.mjs} +2 -2
  9. package/dist/.setup/{audit-CLg9I71N.mjs → audit-DtvzxONM.mjs} +4 -4
  10. package/dist/.setup/{channel-access-C12aDZ0p.mjs → channel-access-BRp3Y-rF.mjs} +4 -17
  11. package/dist/.setup/{channel-type-DRCrEMss.mjs → channel-type-D1_b5UhU.mjs} +12 -16
  12. package/dist/.setup/{channel-DxlUVU9t.mjs → channel-z5lQMS_9.mjs} +79 -75
  13. package/dist/.setup/{channel.setup-CRo1GjV7.mjs → channel.setup-DBJtugJO.mjs} +14 -34
  14. package/dist/.setup/{components-Cq1PvIpN.mjs → components-CzXLR3QH.mjs} +56 -72
  15. package/dist/.setup/{config-schema-oxELUSDx.mjs → config-schema-CsemBDpS.mjs} +6 -17
  16. package/dist/.setup/{conversation-binding-route-Caz_XuFH.mjs → conversation-binding-route-Bi62ApSt.mjs} +1 -1
  17. package/dist/.setup/{directory-config-esqNn0Eo.mjs → directory-config-BH1LA-gi.mjs} +1 -1
  18. package/dist/.setup/{directory-live-DtKRHuGV.mjs → directory-live-C9xz7IPm.mjs} +10 -13
  19. package/dist/.setup/{discord-0xJYfdDD.mjs → discord-J7aRzq2Q.mjs} +101 -381
  20. package/dist/.setup/{doctor-BCl1H5tl.mjs → doctor-DxSAngJq.mjs} +31 -70
  21. package/dist/.setup/doctor-contract-Bg3NPAMw.mjs +137 -0
  22. package/dist/.setup/{handle-action-CUvQWnE7.mjs → handle-action-DlmFX-_G.mjs} +10 -11
  23. package/dist/.setup/{handle-action.guild-admin-Df-8gVle.mjs → handle-action.guild-admin-BnyqTczR.mjs} +67 -129
  24. package/dist/.setup/{live-policy-config-8Pv20CHc.mjs → live-policy-config-emmPNyh4.mjs} +1 -1
  25. package/dist/.setup/{message-handler-B-Jk4oDk.mjs → message-handler-BNToPZrx.mjs} +7 -6
  26. package/dist/.setup/{message-handler.preflight-v-__NSDq.mjs → message-handler.preflight-D0BcCyI7.mjs} +13 -23
  27. package/dist/.setup/{message-handler.preflight-pluralkit-CWLUBVcx.mjs → message-handler.preflight-pluralkit-D1g3eq0B.mjs} +5 -8
  28. package/dist/.setup/{message-handler.process-BNFFuChn.mjs → message-handler.process-CThRmCWK.mjs} +57 -96
  29. package/dist/.setup/{outbound-adapter-BobT-kse.mjs → outbound-adapter-BPq0c2wY.mjs} +11 -17
  30. package/dist/.setup/{outbound-session-route-zVGxxceg.mjs → outbound-session-route-DGPPKA0z.mjs} +21 -46
  31. package/dist/.setup/{presence-cache-Di3H3DIU.mjs → presence-cache-Dg3_dADn.mjs} +2 -4
  32. package/dist/.setup/{probe-LYrIHlBF.mjs → probe-DIRY8vh7.mjs} +17 -27
  33. package/dist/.setup/{provider-B_G5hASF.mjs → provider-D2OKMo0f.mjs} +273 -446
  34. package/dist/.setup/{provider-session.runtime-BYlTBFD2.mjs → provider-session.runtime-BupCTZQv.mjs} +3 -3
  35. package/dist/.setup/provider.runtime-BaW_JnfU.mjs +2 -0
  36. package/dist/.setup/{realtime-session.runtime-oUd9DOcz.mjs → realtime-session.runtime-D5RSC8Mm.mjs} +6 -8
  37. package/dist/.setup/{receive-recovery-CHPOsnLc.mjs → receive-recovery-GqF2PUN_.mjs} +1 -4
  38. package/dist/.setup/{resolve-allowlist-common-DPdRggu5.mjs → resolve-allowlist-common-DUYRrrVB.mjs} +5 -9
  39. package/dist/.setup/{resolve-channels-BFPNRmVv.mjs → resolve-channels--MUatisx.mjs} +3 -3
  40. package/dist/.setup/{resolve-users-0-aBuCy3.mjs → resolve-users-CxLcDmM0.mjs} +6 -9
  41. package/dist/.setup/{retry-pz_cSha2.mjs → retry-BKzO25k4.mjs} +27 -51
  42. package/dist/.setup/{runtime-sUksZ--Q.mjs → runtime-CerRBfaa.mjs} +2 -2
  43. package/dist/.setup/{runtime-FN000lxA.mjs → runtime-CwUA0RLr.mjs} +228 -255
  44. package/dist/.setup/{runtime-doctor-migrations-CshUGl36.mjs → runtime-doctor-migrations-BPV-R4G9.mjs} +6 -0
  45. package/dist/.setup/{security-audit-Cdaegq7C.mjs → security-audit-D42cp5N7.mjs} +21 -48
  46. package/dist/.setup/{send-CrfEI6Bs.mjs → send-2X7m2Zmw.mjs} +45 -58
  47. package/dist/.setup/{send.components-DxQAMfES.mjs → send.components-RqvgmCs3.mjs} +44 -90
  48. package/dist/.setup/{send.outbound-BISKnb-a.mjs → send.outbound-B4QHSEzm.mjs} +60 -98
  49. package/dist/.setup/{send.shared-DGFkbkQR.mjs → send.shared-BQIUh6Vn.mjs} +13 -20
  50. package/dist/.setup/{sender-identity-B9X5Vb9R.mjs → sender-identity-CooeYfAn.mjs} +1 -1
  51. package/dist/.setup/{session-key-normalization-wJgsKPNF.mjs → session-key-normalization-CCHGNSQw.mjs} +3 -9
  52. package/dist/.setup/setup-plugin-api-nn_giXvZ.mjs +3 -0
  53. package/dist/.setup/{shared-B3wZ4hN-.mjs → shared-CIJn6AEC.mjs} +7 -7
  54. package/dist/.setup/{subagent-hooks-nlUE9ow7.mjs → subagent-hooks-BYlVnxzy.mjs} +4 -9
  55. package/dist/.setup/{system-events-CdeZnvPV.mjs → system-events-CQ3if_pS.mjs} +2 -2
  56. package/dist/.setup/{target-resolver-BuLapkhy.mjs → target-resolver-Dzxf80Y3.mjs} +5 -13
  57. package/dist/.setup/targets-Lf8D55w-.mjs +3 -0
  58. package/dist/.setup/{thread-bindings-D7TfgjR_.mjs → thread-bindings-yNNlcUWE.mjs} +5 -6
  59. package/dist/.setup/{thread-bindings.manager-DN-NissX.mjs → thread-bindings.manager-plk0zJAW.mjs} +14 -20
  60. package/dist/.setup/{thread-bindings.session-updates-CshfKghV.mjs → thread-bindings.session-updates-C9aKtVs6.mjs} +295 -5
  61. package/dist/.setup/{timeouts-D86uWLt_.mjs → timeouts-pun7VJ02.mjs} +2 -15
  62. package/dist/.setup/{transcripts-source-p8EV179h.mjs → transcripts-source-CNWswry-.mjs} +31 -79
  63. package/dist/.setup/typing-CNlSKY5S.mjs +11 -0
  64. package/dist/.setup/{voice-runtime-B46rcwrG.mjs → voice-runtime-Bc75quiH.mjs} +62 -110
  65. package/dist/account-inspect-api.js +1 -1
  66. package/dist/action-runtime-api.js +1 -1
  67. package/dist/activities-api.js +1 -1
  68. package/dist/api.js +18 -18
  69. package/dist/channel-config-api.js +1 -1
  70. package/dist/channel-plugin-api.js +1 -1
  71. package/dist/control-ui/2afbdab1d3aa65a789d8a3eba666f4b1ec5df07073538fe30d6cf32093a1fb2d/index.js +1 -0
  72. package/dist/directory-contract-api.js +1 -1
  73. package/dist/doctor-contract-api.js +19 -128
  74. package/dist/index.js +1 -1
  75. package/dist/runtime-api.js +20 -21
  76. package/dist/security-audit-contract-api.js +1 -1
  77. package/dist/session-key-api.js +1 -1
  78. package/dist/setup-entry.js +1 -1
  79. package/dist/setup-plugin-api.js +1 -1
  80. package/dist/src/voice/audio-worker.runtime.js +6 -8
  81. package/dist/subagent-hooks-api.js +1 -1
  82. package/dist/transcripts-source-api.js +1 -1
  83. package/node_modules/libopus-wasm/CHANGELOG.md +8 -0
  84. package/node_modules/libopus-wasm/dist/discordjs.d.ts.map +1 -1
  85. package/node_modules/libopus-wasm/dist/discordjs.js +14 -2
  86. package/node_modules/libopus-wasm/dist/generated/libopus.generated.mjs +0 -0
  87. package/node_modules/libopus-wasm/dist/index.d.ts.map +1 -1
  88. package/node_modules/libopus-wasm/dist/index.js +55 -100
  89. package/node_modules/libopus-wasm/package.json +5 -5
  90. package/node_modules/undici/README.md +10 -1
  91. package/node_modules/undici/docs/docs/api/Agent.md +8 -8
  92. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +9 -6
  93. package/node_modules/undici/docs/docs/api/Dispatcher.md +9 -0
  94. package/node_modules/undici/docs/docs/api/Errors.md +25 -0
  95. package/node_modules/undici/docs/docs/api/Fetch.md +10 -0
  96. package/node_modules/undici/docs/docs/api/Interceptors.md +4 -3
  97. package/node_modules/undici/docs/docs/api/Pool.md +5 -6
  98. package/node_modules/undici/docs/docs/index.md +10 -1
  99. package/node_modules/undici/lib/api/readable.js +1 -2
  100. package/node_modules/undici/lib/core/request.js +23 -2
  101. package/node_modules/undici/lib/core/util.js +0 -39
  102. package/node_modules/undici/lib/dispatcher/client-h1.js +11 -4
  103. package/node_modules/undici/lib/dispatcher/client-h2.js +52 -8
  104. package/node_modules/undici/lib/dispatcher/client.js +6 -1
  105. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +21 -6
  106. package/node_modules/undici/lib/dispatcher/pool-base.js +63 -2
  107. package/node_modules/undici/lib/dispatcher/pool.js +90 -7
  108. package/node_modules/undici/lib/dispatcher/proxy-agent.js +16 -3
  109. package/node_modules/undici/lib/handler/deduplication-handler.js +24 -11
  110. package/node_modules/undici/lib/handler/redirect-handler.js +5 -0
  111. package/node_modules/undici/lib/handler/retry-handler.js +72 -13
  112. package/node_modules/undici/lib/interceptor/cache.js +4 -1
  113. package/node_modules/undici/lib/interceptor/decompress.js +236 -69
  114. package/node_modules/undici/lib/web/cache/cache.js +17 -8
  115. package/node_modules/undici/lib/web/cache/cachestorage.js +11 -5
  116. package/node_modules/undici/lib/web/cookies/index.js +19 -7
  117. package/node_modules/undici/lib/web/cookies/util.js +3 -1
  118. package/node_modules/undici/lib/web/eventsource/eventsource.js +25 -1
  119. package/node_modules/undici/lib/web/fetch/body.js +22 -15
  120. package/node_modules/undici/lib/web/fetch/data-url.js +6 -6
  121. package/node_modules/undici/lib/web/fetch/formdata.js +30 -36
  122. package/node_modules/undici/lib/web/fetch/headers.js +34 -31
  123. package/node_modules/undici/lib/web/fetch/index.js +46 -25
  124. package/node_modules/undici/lib/web/fetch/request.js +74 -79
  125. package/node_modules/undici/lib/web/fetch/response.js +43 -44
  126. package/node_modules/undici/lib/web/fetch/util.js +7 -7
  127. package/node_modules/undici/lib/web/infra/index.js +16 -1
  128. package/node_modules/undici/lib/web/webidl/index.js +2 -14
  129. package/node_modules/undici/lib/web/websocket/connection.js +33 -12
  130. package/node_modules/undici/lib/web/websocket/events.js +43 -26
  131. package/node_modules/undici/lib/web/websocket/receiver.js +2 -0
  132. package/node_modules/undici/lib/web/websocket/stream/websocketerror.js +13 -10
  133. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +19 -4
  134. package/node_modules/undici/lib/web/websocket/websocket.js +44 -41
  135. package/node_modules/undici/package.json +1 -1
  136. package/node_modules/undici/types/diagnostics-channel.d.ts +2 -1
  137. package/node_modules/undici/types/interceptors.d.ts +1 -1
  138. package/node_modules/undici/types/webidl.d.ts +13 -7
  139. package/openclaw.plugin.json +6 -0
  140. package/package.json +12 -7
  141. package/dist/.setup/doctor-contract-CvvgwrrG.mjs +0 -409
  142. package/dist/.setup/model-picker-preference-primitives-BHT1LkR_.mjs +0 -37
  143. package/dist/.setup/provider.runtime-CUE3UxER.mjs +0 -2
  144. package/dist/.setup/setup-plugin-api-BQUBxbhe.mjs +0 -3
  145. package/dist/.setup/targets-0KUcEIYx.mjs +0 -3
  146. package/dist/.setup/thread-bindings.state-g6v9zAym.mjs +0 -331
  147. package/dist/.setup/typing-BiHCzBRu.mjs +0 -15
@@ -99,6 +99,12 @@ function createLegacyWebhookListenerDoctorContract(params) {
99
99
  };
100
100
  const prefix = `channels.${params.channelKey}`;
101
101
  return {
102
+ historicalWebhookListener: {
103
+ channelId: params.channelKey,
104
+ port: params.defaultPort,
105
+ preserveAuthoredActivation: params.preserveAuthoredActivation,
106
+ ...params.defaultHost === void 0 ? {} : { host: params.defaultHost }
107
+ },
102
108
  legacyConfigRules: [{
103
109
  path: ["channels", params.channelKey],
104
110
  message: `${prefix} webhook listeners moved to Gateway routes. Run "openclaw doctor --fix" to preserve explicitly configured listener settings as legacyWebhook.`,
@@ -32,11 +32,6 @@ function listBroadMemberTargetPaths(params) {
32
32
  }
33
33
  return paths.toSorted();
34
34
  }
35
- function addDiscordNameBasedEntries(params) {
36
- if (!Array.isArray(params.values)) return;
37
- const entries = new Set(params.values.map((value) => String(value).trim()).filter(isDiscordMutableAllowEntry));
38
- if (entries.size > 0) params.target.set(params.source, entries.size);
39
- }
40
35
  async function collectDiscordSecurityAuditFindings(params) {
41
36
  const findings = [];
42
37
  const discordCfg = params.account.config ?? {};
@@ -44,8 +39,14 @@ async function collectDiscordSecurityAuditFindings(params) {
44
39
  const dangerousNameMatchingEnabled = isDangerousNameMatchingEnabled(discordCfg);
45
40
  const storeAllowFrom = await readChannelAllowFromStore("discord", process.env, accountId).catch(() => []);
46
41
  const discordNameBasedAllowEntries = /* @__PURE__ */ new Map();
42
+ const addNameBasedEntries = (values, source) => {
43
+ if (!Array.isArray(values)) return;
44
+ const entries = new Set(values.map((value) => String(value).trim()).filter(isDiscordMutableAllowEntry));
45
+ if (entries.size > 0) discordNameBasedAllowEntries.set(source, entries.size);
46
+ };
47
47
  const discordPathPrefix = params.orderedAccountIds.length > 1 || params.hasExplicitAccountPath ? `channels.discord.accounts.${accountId}` : "channels.discord";
48
- if ((discordCfg.groupPolicy ?? params.cfg.channels?.defaults?.groupPolicy ?? "allowlist") === "allowlist") {
48
+ const effectiveGroupPolicy = discordCfg.groupPolicy ?? params.cfg.channels?.defaults?.groupPolicy ?? "allowlist";
49
+ if (effectiveGroupPolicy === "allowlist") {
49
50
  const broadMemberPaths = listBroadMemberTargetPaths({
50
51
  discordCfg,
51
52
  pathPrefix: discordPathPrefix
@@ -58,39 +59,25 @@ async function collectDiscordSecurityAuditFindings(params) {
58
59
  remediation: "Add users or roles restrictions at each listed guild/channel when only specific members should trigger the agent."
59
60
  });
60
61
  }
61
- addDiscordNameBasedEntries({
62
- target: discordNameBasedAllowEntries,
63
- values: discordCfg.allowFrom,
64
- source: `${discordPathPrefix}.allowFrom`
65
- });
66
- addDiscordNameBasedEntries({
67
- target: discordNameBasedAllowEntries,
68
- values: discordCfg.dm?.allowFrom,
69
- source: `${discordPathPrefix}.dm.allowFrom`
70
- });
71
- addDiscordNameBasedEntries({
72
- target: discordNameBasedAllowEntries,
73
- values: storeAllowFrom,
74
- source: "Discord pairing store"
75
- });
62
+ addNameBasedEntries(discordCfg.allowFrom, `${discordPathPrefix}.allowFrom`);
63
+ const dmAllowFromRaw = discordCfg.dm?.allowFrom;
64
+ addNameBasedEntries(dmAllowFromRaw, `${discordPathPrefix}.dm.allowFrom`);
65
+ addNameBasedEntries(storeAllowFrom, "Discord pairing store");
76
66
  const guildEntries = discordCfg.guilds ?? {};
67
+ let hasAnyUserAllowlist = false;
68
+ const addUserEntries = (values, source) => {
69
+ hasAnyUserAllowlist ||= Array.isArray(values) && values.length > 0;
70
+ addNameBasedEntries(values, source);
71
+ };
77
72
  for (const [guildKey, guildValue] of Object.entries(guildEntries)) {
78
73
  if (!guildValue || typeof guildValue !== "object") continue;
79
74
  const guild = guildValue;
80
- addDiscordNameBasedEntries({
81
- target: discordNameBasedAllowEntries,
82
- values: guild.users,
83
- source: `${discordPathPrefix}.guilds.${guildKey}.users`
84
- });
75
+ addUserEntries(guild.users, `${discordPathPrefix}.guilds.${guildKey}.users`);
85
76
  const channels = guild.channels;
86
77
  if (!channels || typeof channels !== "object") continue;
87
78
  for (const [channelKey, channelValue] of Object.entries(channels)) {
88
79
  if (!channelValue || typeof channelValue !== "object") continue;
89
- addDiscordNameBasedEntries({
90
- target: discordNameBasedAllowEntries,
91
- values: channelValue.users,
92
- source: `${discordPathPrefix}.guilds.${guildKey}.channels.${channelKey}.users`
93
- });
80
+ addUserEntries(channelValue.users, `${discordPathPrefix}.guilds.${guildKey}.channels.${channelKey}.users`);
94
81
  }
95
82
  }
96
83
  if (discordNameBasedAllowEntries.size > 0) {
@@ -118,24 +105,10 @@ async function collectDiscordSecurityAuditFindings(params) {
118
105
  globalSetting: params.cfg.commands?.nativeSkills
119
106
  });
120
107
  if (!nativeEnabled && !nativeSkillsEnabled) return findings;
121
- const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
122
- const groupPolicy = discordCfg.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
123
108
  const guildsConfigured = Object.keys(guildEntries).length > 0;
124
- const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => {
125
- if (!guild || typeof guild !== "object") return false;
126
- const record = guild;
127
- if (Array.isArray(record.users) && record.users.length > 0) return true;
128
- const channels = record.channels;
129
- if (!channels || typeof channels !== "object") return false;
130
- return Object.values(channels).some((channel) => {
131
- if (!channel || typeof channel !== "object") return false;
132
- const channelRecord = channel;
133
- return Array.isArray(channelRecord.users) && channelRecord.users.length > 0;
134
- });
135
- });
136
- const dmAllowFromRaw = discordCfg.dm?.allowFrom;
137
- const ownerAllowFromConfigured = normalizeAllowFromList([...Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [], ...storeAllowFrom]).length > 0;
138
- if (groupPolicy !== "disabled" && guildsConfigured && !ownerAllowFromConfigured && !hasAnyUserAllowlist) findings.push({
109
+ const dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
110
+ const ownerAllowFromConfigured = normalizeAllowFromList([...discordCfg.allowFrom ?? dmAllowFrom, ...storeAllowFrom]).length > 0;
111
+ if (effectiveGroupPolicy !== "disabled" && guildsConfigured && !ownerAllowFromConfigured && !hasAnyUserAllowlist) findings.push({
139
112
  checkId: "channels.discord.commands.native.no_allowlists",
140
113
  severity: "warn",
141
114
  title: "Discord slash commands have no allowlists",
@@ -1,26 +1,26 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
2
- import { At as deleteChannelPermission, Bt as putChannelPermission, C as withDiscordRequestAuthority, Ct as unpinChannelMessage, Dt as createGuildEmoji, Et as createGuildChannel, Ft as listGuildChannels, Ht as removeGuildMemberRole, It as listGuildEmojis, Lt as listGuildRoles, Mt as getGuildMember, Nt as getGuildVoiceState, Ot as createGuildScheduledEvent, Pt as listGuildActiveThreads, Rt as listGuildScheduledEvents, S as readRetryAfter, St as sendChannelTyping, Tt as createGuildBan, Ut as timeoutGuildMember, Vt as removeGuildMember, _ as DiscordError, _t as listChannelArchivedThreads, at as createOwnMessageReaction, b as readDiscordCode, bt as pinChannelMessage, dt as deleteChannelMessage, ft as editChannel, ht as getChannelMessage, jt as getGuild, kt as createGuildSticker, lt as createThread, mt as getChannel, ot as deleteOwnMessageReaction, pt as editChannelMessage, st as listMessageReactionUsers, tt as stripUndefinedFields, ut as deleteChannel, v as RateLimitError, vt as listChannelMessages, wt as addGuildMemberRole, x as readDiscordMessage, xt as searchGuildMessages, y as isUnknownDiscordVoiceStateError, yt as listChannelPins, zt as moveGuildChannels } from "./discord-0xJYfdDD.mjs";
3
- import { D as getDiscordEndpointRuntime, O as resolveDiscordEndpointAttachmentGuard, T as parseDiscordRetryAfterBodySeconds } from "./channel-type-DRCrEMss.mjs";
4
- import { o as parseDiscordHttpErrorBody } from "./api-DVbJwZVt.mjs";
5
- import { a as recordDiscordMessageCreateAmbiguity, f as createReusableDiscordReplyReference, l as createDiscordSendReceiptFromResults, n as classifyDiscordDeliveryFailure, o as chunkDiscordTextWithMode, p as resolveDiscordReplyMessageId, r as createDiscordRetryRunner, u as createDiscordSendResult } from "./retry-pz_cSha2.mjs";
6
- import { C as hasAnyChannelPermissionDiscord, D as createDiscordMessageNonce, F as resolveDiscordClientAccountContext, G as parseAndResolveChannelRecipient, I as resolveDiscordRest, L as DISCORD_REST_TIMEOUT_MS, M as createDiscordClient, O as resolveDiscordMessageFlags, S as hasAllGuildPermissionsDiscord, _ as canManageGuildMemberRoleDiscord, a as normalizeEmojiName, b as fetchChannelPermissionsDiscord, c as resolveChannelId, f as sendDiscordText, g as DiscordSendError, h as DISCORD_MAX_STICKER_BYTES, j as resolveDiscordSuppressEmbeds, m as DISCORD_MAX_EVENT_COVER_BYTES, n as buildDiscordTextChunks, o as normalizeReactionEmoji, p as DISCORD_MAX_EMOJI_BYTES, r as buildReactionIdentifier, t as buildDiscordSendError, v as canManageGuildRoleDiscord, w as hasAnyGuildPermissionDiscord, x as fetchMemberGuildPermissionsDiscord, y as canViewDiscordGuildChannel } from "./send.shared-DGFkbkQR.mjs";
7
- import { i as prepareDiscordOutboundText, n as sendPollDiscord, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-BISKnb-a.mjs";
8
- import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS } from "./timeouts-D86uWLt_.mjs";
2
+ import { C as withDiscordRequestAuthority, S as readRetryAfter, _ as DiscordError, _t as getGuildMember, at as createOwnMessageReaction, b as readDiscordCode, ct as createThread, dt as getChannel, ft as getChannelMessage, gt as getGuild, ht as unpinChannelMessage, lt as deleteChannelMessage, mt as pinChannelMessage, ot as deleteOwnMessageReaction, st as listMessageReactionUsers, tt as stripUndefinedFields, ut as editChannelMessage, v as RateLimitError, vt as getGuildVoiceState, x as readDiscordMessage, y as isUnknownDiscordVoiceStateError, yt as listGuildEmojis } from "./discord-J7aRzq2Q.mjs";
3
+ import { E as parseDiscordRetryAfterBodySeconds, O as getDiscordEndpointRuntime, k as resolveDiscordEndpointAttachmentGuard } from "./channel-type-D1_b5UhU.mjs";
4
+ import { o as parseDiscordHttpErrorBody } from "./api-_oznjffB.mjs";
5
+ import { a as recordDiscordMessageCreateAmbiguity, f as createReusableDiscordReplyReference, l as createDiscordSendReceiptFromResults, n as classifyDiscordDeliveryFailure, o as chunkDiscordTextWithMode, p as resolveDiscordReplyMessageId, r as createDiscordRetryRunner, u as createDiscordSendResult } from "./retry-BKzO25k4.mjs";
6
+ import { C as hasAnyChannelPermissionDiscord, D as createDiscordMessageNonce, F as resolveDiscordClientAccountContext, G as parseAndResolveChannelRecipient, I as resolveDiscordRest, L as DISCORD_REST_TIMEOUT_MS, M as createDiscordClient, O as resolveDiscordMessageFlags, S as hasAllGuildPermissionsDiscord, _ as canManageGuildMemberRoleDiscord, a as normalizeEmojiName, b as fetchChannelPermissionsDiscord, c as resolveChannelId, f as sendDiscordText, g as DiscordSendError, h as DISCORD_MAX_STICKER_BYTES, j as resolveDiscordSuppressEmbeds, m as DISCORD_MAX_EVENT_COVER_BYTES, n as buildDiscordTextChunks, o as normalizeReactionEmoji, p as DISCORD_MAX_EMOJI_BYTES, r as buildReactionIdentifier, t as buildDiscordSendError, v as canManageGuildRoleDiscord, w as hasAnyGuildPermissionDiscord, x as fetchMemberGuildPermissionsDiscord, y as canViewDiscordGuildChannel } from "./send.shared-BQIUh6Vn.mjs";
7
+ import { i as prepareDiscordOutboundText, n as sendPollDiscord, r as sendStickerDiscord, t as sendMessageDiscord } from "./send.outbound-B4QHSEzm.mjs";
8
+ import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS } from "./timeouts-pun7VJ02.mjs";
9
9
  import fs from "node:fs/promises";
10
- import { ChannelType } from "discord-api-types/v10";
10
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { ChannelType, Routes } from "discord-api-types/v10";
11
12
  import crypto from "node:crypto";
12
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
13
13
  import { parseStrictFiniteNumber, resolveExpiresAtMsFromDurationMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
14
14
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
15
15
  import { formatErrorMessage as formatErrorMessage$1 } from "openclaw/plugin-sdk/error-runtime";
16
16
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
17
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
18
17
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
19
18
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
19
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
20
20
  import { recordOutboundMessageIdentity } from "openclaw/plugin-sdk/channel-outbound";
21
21
  import { MEDIA_FFMPEG_MAX_AUDIO_DURATION_SECS, buildOutboundMediaLoadOptions, extensionForMime, maxBytesForKind, parseFfprobeCodecAndSampleRate, runFfmpeg, runFfprobe, unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
22
- import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
23
22
  import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
23
+ import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
24
24
  import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
25
25
  import path from "node:path";
26
26
  import { writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
@@ -35,7 +35,7 @@ async function createChannelDiscord(payload, opts) {
35
35
  position: payload.position,
36
36
  nsfw: payload.nsfw
37
37
  });
38
- return await createGuildChannel(rest, payload.guildId, { body });
38
+ return await rest.post(Routes.guildChannels(payload.guildId), { body });
39
39
  }
40
40
  async function editChannelDiscord(payload, opts) {
41
41
  const rest = resolveDiscordRest(opts);
@@ -57,11 +57,10 @@ async function editChannelDiscord(payload, opts) {
57
57
  emoji_name: tag.emoji_name
58
58
  }))
59
59
  });
60
- return await editChannel(rest, payload.channelId, { body });
60
+ return await rest.patch(Routes.channel(payload.channelId), { body });
61
61
  }
62
62
  async function deleteChannelDiscord(channelId, opts) {
63
- const rest = resolveDiscordRest(opts);
64
- await deleteChannel(rest, channelId);
63
+ await resolveDiscordRest(opts).delete(Routes.channel(channelId));
65
64
  return {
66
65
  ok: true,
67
66
  channelId
@@ -74,7 +73,7 @@ async function moveChannelDiscord(payload, opts) {
74
73
  ...payload.parentId !== void 0 && { parent_id: payload.parentId },
75
74
  ...payload.position !== void 0 && { position: payload.position }
76
75
  }];
77
- await moveGuildChannels(rest, payload.guildId, { body });
76
+ await rest.patch(Routes.guildChannels(payload.guildId), { body });
78
77
  return { ok: true };
79
78
  }
80
79
  async function setChannelPermissionDiscord(payload, opts) {
@@ -84,12 +83,11 @@ async function setChannelPermissionDiscord(payload, opts) {
84
83
  allow: payload.allow,
85
84
  deny: payload.deny
86
85
  });
87
- await putChannelPermission(rest, payload.channelId, payload.targetId, { body });
86
+ await rest.put(Routes.channelPermission(payload.channelId, payload.targetId), { body });
88
87
  return { ok: true };
89
88
  }
90
89
  async function removeChannelPermissionDiscord(channelId, targetId, opts) {
91
- const rest = resolveDiscordRest(opts);
92
- await deleteChannelPermission(rest, channelId, targetId);
90
+ await resolveDiscordRest(opts).delete(Routes.channelPermission(channelId, targetId));
93
91
  return { ok: true };
94
92
  }
95
93
  //#endregion
@@ -115,7 +113,7 @@ async function uploadEmojiDiscord(payload, opts) {
115
113
  ].includes(contentType)) throw new Error("Discord emoji uploads require a PNG, JPG, or GIF image");
116
114
  const image = `data:${contentType};base64,${media.buffer.toString("base64")}`;
117
115
  const roleIds = normalizeStringEntries(payload.roleIds ?? []);
118
- return await createGuildEmoji(rest, payload.guildId, { body: {
116
+ return await rest.post(Routes.guildEmojis(payload.guildId), { body: {
119
117
  name: normalizeEmojiName(payload.name, "Emoji name"),
120
118
  image,
121
119
  roles: roleIds.length ? roleIds : void 0
@@ -135,7 +133,7 @@ async function uploadStickerDiscord(payload, opts) {
135
133
  "image/apng",
136
134
  "application/json"
137
135
  ].includes(contentType)) throw new Error("Discord sticker uploads require a PNG, APNG, or Lottie JSON file");
138
- return await createGuildSticker(rest, payload.guildId, {
136
+ return await rest.post(Routes.guildStickers(payload.guildId), {
139
137
  multipartStyle: "form",
140
138
  body: {
141
139
  name: normalizeEmojiName(payload.name, "Sticker name"),
@@ -157,17 +155,14 @@ async function fetchMemberInfoDiscord(guildId, userId, opts) {
157
155
  return await getGuildMember(rest, guildId, userId);
158
156
  }
159
157
  async function fetchRoleInfoDiscord(guildId, opts) {
160
- const rest = resolveDiscordRest(opts);
161
- return await listGuildRoles(rest, guildId);
158
+ return await resolveDiscordRest(opts).get(Routes.guildRoles(guildId));
162
159
  }
163
160
  async function addRoleDiscord(payload, opts) {
164
- const rest = resolveDiscordRest(opts);
165
- await addGuildMemberRole(rest, payload.guildId, payload.userId, payload.roleId);
161
+ await resolveDiscordRest(opts).put(Routes.guildMemberRole(payload.guildId, payload.userId, payload.roleId));
166
162
  return { ok: true };
167
163
  }
168
164
  async function removeRoleDiscord(payload, opts) {
169
- const rest = resolveDiscordRest(opts);
170
- await removeGuildMemberRole(rest, payload.guildId, payload.userId, payload.roleId);
165
+ await resolveDiscordRest(opts).delete(Routes.guildMemberRole(payload.guildId, payload.userId, payload.roleId));
171
166
  return { ok: true };
172
167
  }
173
168
  async function fetchChannelInfoDiscord(channelId, opts) {
@@ -179,8 +174,7 @@ async function fetchGuildInfoDiscord(guildId, opts) {
179
174
  return await getGuild(rest, guildId);
180
175
  }
181
176
  async function listGuildChannelsDiscord(guildId, opts) {
182
- const rest = resolveDiscordRest(opts);
183
- return await listGuildChannels(rest, guildId);
177
+ return await resolveDiscordRest(opts).get(Routes.guildChannels(guildId));
184
178
  }
185
179
  async function fetchVoiceStatusDiscord(guildId, userId, opts) {
186
180
  const rest = resolveDiscordRest(opts);
@@ -199,8 +193,7 @@ async function fetchVoiceStatusDiscord(guildId, userId, opts) {
199
193
  }
200
194
  }
201
195
  async function listScheduledEventsDiscord(guildId, opts) {
202
- const rest = resolveDiscordRest(opts);
203
- return await listGuildScheduledEvents(rest, guildId);
196
+ return await resolveDiscordRest(opts).get(Routes.guildScheduledEvents(guildId));
204
197
  }
205
198
  const ALLOWED_EVENT_COVER_TYPES = /* @__PURE__ */ new Set([
206
199
  "image/png",
@@ -220,8 +213,7 @@ async function resolveEventCoverImage(imageUrl, opts) {
220
213
  return `data:${contentType};base64,${media.buffer.toString("base64")}`;
221
214
  }
222
215
  async function createScheduledEventDiscord(guildId, payload, opts) {
223
- const rest = resolveDiscordRest(opts);
224
- return await createGuildScheduledEvent(rest, guildId, payload);
216
+ return await resolveDiscordRest(opts).post(Routes.guildScheduledEvents(guildId), { body: payload });
225
217
  }
226
218
  async function timeoutMemberDiscord(payload, opts) {
227
219
  const rest = resolveDiscordRest(opts);
@@ -231,20 +223,19 @@ async function timeoutMemberDiscord(payload, opts) {
231
223
  until = timestampMsToIsoString(resolveExpiresAtMsFromDurationMs(ms));
232
224
  if (!until) throw new Error("Discord timeout duration is outside the supported Date range");
233
225
  }
234
- return await timeoutGuildMember(rest, payload.guildId, payload.userId, {
226
+ return await rest.patch(Routes.guildMember(payload.guildId, payload.userId), {
235
227
  body: { communication_disabled_until: until ?? null },
236
228
  headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0
237
229
  });
238
230
  }
239
231
  async function kickMemberDiscord(payload, opts) {
240
- const rest = resolveDiscordRest(opts);
241
- await removeGuildMember(rest, payload.guildId, payload.userId, { headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0 });
232
+ await resolveDiscordRest(opts).delete(Routes.guildMember(payload.guildId, payload.userId), { headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0 });
242
233
  return { ok: true };
243
234
  }
244
235
  async function banMemberDiscord(payload, opts) {
245
236
  const rest = resolveDiscordRest(opts);
246
237
  const deleteMessageDays = typeof payload.deleteMessageDays === "number" && Number.isFinite(payload.deleteMessageDays) ? Math.min(Math.max(Math.floor(payload.deleteMessageDays), 0), 7) : void 0;
247
- await createGuildBan(rest, payload.guildId, payload.userId, {
238
+ await rest.put(Routes.guildBan(payload.guildId, payload.userId), {
248
239
  body: deleteMessageDays !== void 0 ? { delete_message_days: deleteMessageDays } : void 0,
249
240
  headers: payload.reason ? { "X-Audit-Log-Reason": encodeURIComponent(payload.reason) } : void 0
250
241
  });
@@ -298,7 +289,7 @@ async function readMessagesDiscord(channelId, query, opts) {
298
289
  if (messageQuery.before) params.before = messageQuery.before;
299
290
  if (messageQuery.after) params.after = messageQuery.after;
300
291
  if (messageQuery.around) params.around = messageQuery.around;
301
- return assertDiscordResponseArray(await listChannelMessages(rest, channelId, params), "message read");
292
+ return assertDiscordResponseArray(await rest.get(Routes.channelMessages(channelId), params), "message read");
302
293
  }
303
294
  async function fetchMessageDiscord(channelId, messageId, opts) {
304
295
  const rest = resolveDiscordRest(opts);
@@ -328,8 +319,7 @@ async function unpinMessageDiscord(channelId, messageId, opts) {
328
319
  return { ok: true };
329
320
  }
330
321
  async function listPinsDiscord(channelId, opts) {
331
- const rest = resolveDiscordRest(opts);
332
- return await listChannelPins(rest, channelId);
322
+ return await resolveDiscordRest(opts).get(Routes.channelPins(channelId));
333
323
  }
334
324
  async function createThreadDiscord(channelId, payload, opts) {
335
325
  const assertCreateAllowed = opts.assertCreateAllowed;
@@ -343,7 +333,7 @@ async function createThreadDiscord(channelId, payload, opts) {
343
333
  const archiveDuration = payload.autoArchiveMinutes ?? resolveDefaultThreadAutoArchiveDuration(channel);
344
334
  if (archiveDuration !== void 0) body.auto_archive_duration = archiveDuration;
345
335
  const isForumLike = channel?.type === ChannelType.GuildForum || channel?.type === ChannelType.GuildMedia;
346
- const initialMessageContent = isForumLike ? payload.content?.trim() ? payload.content : payload.name : payload.content?.trim() ? payload.content : "";
336
+ const initialMessageContent = payload.content?.trim() ? payload.content : isForumLike ? payload.name : "";
347
337
  const initialMessageChunks = buildDiscordTextChunks(initialMessageContent, { maxLinesPerMessage: DISCORD_THREAD_TRANSPORT_ONLY_MAX_LINES });
348
338
  if (isForumLike) {
349
339
  body.message = { content: initialMessageChunks[0] ?? payload.name };
@@ -412,21 +402,21 @@ async function listThreadsDiscord(payload, opts) {
412
402
  const params = {};
413
403
  if (payload.before) params.before = payload.before;
414
404
  if (payload.limit) params.limit = payload.limit;
415
- return await listChannelArchivedThreads(rest, payload.channelId, params);
405
+ return await rest.get(Routes.channelThreads(payload.channelId, "public"), params);
416
406
  }
417
- return await listGuildActiveThreads(rest, payload.guildId);
407
+ return await rest.get(Routes.guildActiveThreads(payload.guildId));
418
408
  }
419
409
  async function searchMessagesDiscord(query, opts) {
420
410
  const rest = resolveDiscordRest(opts);
421
411
  const params = new URLSearchParams();
422
412
  params.set("content", query.content);
423
- if (query.channelIds?.length) for (const channelId of query.channelIds) params.append("channel_id", channelId);
424
- if (query.authorIds?.length) for (const authorId of query.authorIds) params.append("author_id", authorId);
413
+ for (const channelId of query.channelIds ?? []) params.append("channel_id", channelId);
414
+ for (const authorId of query.authorIds ?? []) params.append("author_id", authorId);
425
415
  if (query.limit) {
426
416
  const limit = Math.min(Math.max(Math.floor(query.limit), 1), 25);
427
417
  params.set("limit", String(limit));
428
418
  }
429
- const result = assertDiscordResponseObject(await searchGuildMessages(rest, query.guildId, params), "message search");
419
+ const result = assertDiscordResponseObject(await rest.get(`/guilds/${query.guildId}/messages/search?${params.toString()}`), "message search");
430
420
  if (result.code === 11e4) {
431
421
  const message = typeof result.message === "string" && result.message.trim() ? result.message.trim() : "Discord search index is not yet available";
432
422
  const retryAfter = parseDiscordRetryAfterBodySeconds(result.retry_after);
@@ -638,7 +628,7 @@ async function generateWaveformFromPcm(filePath) {
638
628
  const pcmData = await fs.readFile(tempPcm);
639
629
  const samples = new Int16Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 2);
640
630
  const step = Math.max(1, Math.floor(samples.length / WAVEFORM_SAMPLES));
641
- const waveform = [];
631
+ const waveform = Buffer.alloc(WAVEFORM_SAMPLES);
642
632
  for (let i = 0; i < WAVEFORM_SAMPLES && i * step < samples.length; i++) {
643
633
  let sum = 0;
644
634
  let count = 0;
@@ -648,10 +638,9 @@ async function generateWaveformFromPcm(filePath) {
648
638
  }
649
639
  const avg = count > 0 ? sum / count : 0;
650
640
  const normalized = Math.min(255, Math.round(avg / 32767 * 255));
651
- waveform.push(normalized);
641
+ waveform[i] = normalized;
652
642
  }
653
- while (waveform.length < WAVEFORM_SAMPLES) waveform.push(0);
654
- return Buffer.from(waveform).toString("base64");
643
+ return waveform.toString("base64");
655
644
  } finally {
656
645
  await unlinkIfExists(tempPcm);
657
646
  }
@@ -835,11 +824,11 @@ async function sendDiscordVoiceMessage(rest, channelId, audioBuffer, metadata, r
835
824
  uploaded_filename: upload_filename,
836
825
  duration_secs: metadata.durationSecs,
837
826
  waveform: metadata.waveform
838
- }]
839
- };
840
- if (replyTo) messagePayload.message_reference = {
841
- message_id: replyTo,
842
- fail_if_not_exists: false
827
+ }],
828
+ ...replyTo ? { message_reference: {
829
+ message_id: replyTo,
830
+ fail_if_not_exists: false
831
+ } } : {}
843
832
  };
844
833
  let messageCreateMayHaveCommitted = false;
845
834
  try {
@@ -932,8 +921,7 @@ async function sendVoiceMessageDiscordInternal(to, audioPath, opts) {
932
921
  //#endregion
933
922
  //#region extensions/discord/src/send.typing.ts
934
923
  async function sendTypingDiscord(channelId, opts) {
935
- const rest = resolveDiscordRest(opts);
936
- await sendChannelTyping(rest, channelId);
924
+ await resolveDiscordRest(opts).post(Routes.channelTyping(channelId));
937
925
  return {
938
926
  ok: true,
939
927
  channelId
@@ -943,7 +931,6 @@ async function sendTypingDiscord(channelId, opts) {
943
931
  //#region extensions/discord/src/send.reactions.ts
944
932
  function resolveDiscordReactionClient(opts) {
945
933
  if (opts.rest && opts.cfg && opts.accountId) return createDiscordClient(opts);
946
- if (!opts.cfg) throw new Error("Discord reactions requires a resolved runtime config. Load and resolve config at the command or gateway boundary, then pass cfg through the runtime path.");
947
934
  const cfg = requireRuntimeConfig(opts.cfg, "Discord reactions");
948
935
  return createDiscordClient({
949
936
  ...opts,
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
2
- import { d as buildDiscordComponentMessage, f as buildDiscordComponentMessageFlags, g as resolveDiscordComponentAttachmentName } from "./components-Cq1PvIpN.mjs";
3
- import { A as serializePayload, C as withDiscordRequestAuthority, ct as createChannelMessage, pt as editChannelMessage } from "./discord-0xJYfdDD.mjs";
2
+ import { d as buildDiscordComponentMessage, f as buildDiscordComponentMessageFlags, g as resolveDiscordComponentAttachmentName } from "./components-CzXLR3QH.mjs";
3
+ import { A as serializePayload, C as withDiscordRequestAuthority, ut as editChannelMessage } from "./discord-J7aRzq2Q.mjs";
4
4
  import { n as getOptionalDiscordRuntime } from "./runtime-DgnVQ7zW.mjs";
5
- import { u as createDiscordSendResult } from "./retry-pz_cSha2.mjs";
6
- import { D as createDiscordMessageNonce, G as parseAndResolveChannelRecipient, M as createDiscordClient, T as SUPPRESS_NOTIFICATIONS_FLAG, c as resolveChannelId, l as resolveDiscordChannel, t as buildDiscordSendError } from "./send.shared-DGFkbkQR.mjs";
7
- import { t as sendMessageDiscord } from "./send.outbound-BISKnb-a.mjs";
8
- import { ChannelType } from "discord-api-types/v10";
5
+ import { u as createDiscordSendResult } from "./retry-BKzO25k4.mjs";
6
+ import { D as createDiscordMessageNonce, G as parseAndResolveChannelRecipient, M as createDiscordClient, T as SUPPRESS_NOTIFICATIONS_FLAG, c as resolveChannelId, l as resolveDiscordChannel, t as buildDiscordSendError } from "./send.shared-BQIUh6Vn.mjs";
7
+ import { t as sendMessageDiscord } from "./send.outbound-B4QHSEzm.mjs";
8
+ import { hasNonEmptyString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
9
+ import { ChannelType, Routes } from "discord-api-types/v10";
9
10
  import { asDateTimestampMs, isFutureDateTimestampMs, resolveDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
10
11
  import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
11
- import { hasNonEmptyString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
12
12
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
13
13
  import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
14
14
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
@@ -35,8 +35,7 @@ const discordComponentRegistryState = resolveGlobalSingleton(Symbol.for("opencla
35
35
  const DEFAULT_COMPONENT_TTL_MS = 18e5;
36
36
  const PERSISTENT_COMPONENT_NAMESPACE = "discord.components";
37
37
  const PERSISTENT_MODAL_NAMESPACE = "discord.modals";
38
- const PERSISTENT_COMPONENT_MAX_ENTRIES = 500;
39
- const PERSISTENT_MODAL_MAX_ENTRIES = 500;
38
+ const PERSISTENT_REGISTRY_MAX_ENTRIES = 500;
40
39
  function formatRegistryError(error) {
41
40
  if (!(error instanceof Error)) return { error: formatRegistryErrorValue(error) };
42
41
  const details = {
@@ -58,7 +57,6 @@ const reportPersistentComponentRegistryError = createPluginStateErrorReporter(ge
58
57
  function formatRegistryErrorValue(value) {
59
58
  if (typeof value === "string") return value;
60
59
  if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") return String(value);
61
- if (value === null) return "null";
62
60
  try {
63
61
  return JSON.stringify(value) ?? Object.prototype.toString.call(value);
64
62
  } catch {
@@ -71,52 +69,31 @@ function disablePersistentComponentRegistry(error) {
71
69
  discordComponentRegistryState.persistentModalStore = void 0;
72
70
  reportPersistentComponentRegistryError(error);
73
71
  }
74
- function getPersistentComponentStore() {
72
+ function openPersistentRegistryStore(cached, namespace) {
75
73
  if (discordComponentRegistryState.persistentRegistryDisabled) return;
76
- if (discordComponentRegistryState.persistentComponentStore) return discordComponentRegistryState.persistentComponentStore;
74
+ if (cached) return cached;
77
75
  const runtime = getOptionalDiscordRuntime();
78
76
  if (!runtime) return;
79
77
  try {
80
- discordComponentRegistryState.persistentComponentStore = runtime.state.openKeyedStore({
81
- namespace: PERSISTENT_COMPONENT_NAMESPACE,
82
- maxEntries: PERSISTENT_COMPONENT_MAX_ENTRIES,
78
+ return runtime.state.openKeyedStore({
79
+ namespace,
80
+ maxEntries: PERSISTENT_REGISTRY_MAX_ENTRIES,
83
81
  defaultTtlMs: DEFAULT_COMPONENT_TTL_MS
84
82
  });
85
- return discordComponentRegistryState.persistentComponentStore;
86
83
  } catch (error) {
87
84
  disablePersistentComponentRegistry(error);
88
85
  return;
89
86
  }
90
87
  }
88
+ function getPersistentComponentStore() {
89
+ return discordComponentRegistryState.persistentComponentStore = openPersistentRegistryStore(discordComponentRegistryState.persistentComponentStore, PERSISTENT_COMPONENT_NAMESPACE);
90
+ }
91
91
  function getPersistentModalStore() {
92
- if (discordComponentRegistryState.persistentRegistryDisabled) return;
93
- if (discordComponentRegistryState.persistentModalStore) return discordComponentRegistryState.persistentModalStore;
94
- const runtime = getOptionalDiscordRuntime();
95
- if (!runtime) return;
96
- try {
97
- discordComponentRegistryState.persistentModalStore = runtime.state.openKeyedStore({
98
- namespace: PERSISTENT_MODAL_NAMESPACE,
99
- maxEntries: PERSISTENT_MODAL_MAX_ENTRIES,
100
- defaultTtlMs: DEFAULT_COMPONENT_TTL_MS
101
- });
102
- return discordComponentRegistryState.persistentModalStore;
103
- } catch (error) {
104
- disablePersistentComponentRegistry(error);
105
- return;
106
- }
92
+ return discordComponentRegistryState.persistentModalStore = openPersistentRegistryStore(discordComponentRegistryState.persistentModalStore, PERSISTENT_MODAL_NAMESPACE);
107
93
  }
108
94
  function isExpired(entry, now) {
109
95
  return entry.expiresAt !== void 0 && !isFutureDateTimestampMs(entry.expiresAt, { nowMs: now });
110
96
  }
111
- function normalizeEntryTimestamps(entry, now, ttlMs) {
112
- const createdAt = resolveDateTimestampMs(entry.createdAt, now);
113
- const expiresAt = asDateTimestampMs(entry.expiresAt) ?? resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs: createdAt }) ?? 0;
114
- return {
115
- ...entry,
116
- createdAt,
117
- expiresAt
118
- };
119
- }
120
97
  function pruneUndefinedRegistryValues(value) {
121
98
  if (Array.isArray(value)) return value.filter((entry) => entry !== void 0).map((entry) => pruneUndefinedRegistryValues(entry));
122
99
  if (!value || typeof value !== "object") return value;
@@ -128,10 +105,16 @@ function pruneUndefinedRegistryValues(value) {
128
105
  return result;
129
106
  }
130
107
  function normalizeRegistryEntries(entries, params) {
131
- return entries.map((entry) => normalizeEntryTimestamps({
132
- ...entry,
133
- messageId: params.messageId ?? entry.messageId
134
- }, params.now, params.ttlMs));
108
+ return entries.map((entry) => {
109
+ const createdAt = resolveDateTimestampMs(entry.createdAt, params.now);
110
+ const expiresAt = asDateTimestampMs(entry.expiresAt) ?? resolveExpiresAtMsFromDurationMs(params.ttlMs, { nowMs: createdAt }) ?? 0;
111
+ return {
112
+ ...entry,
113
+ messageId: params.messageId ?? entry.messageId,
114
+ createdAt,
115
+ expiresAt
116
+ };
117
+ });
135
118
  }
136
119
  function resolveEntry(store, params) {
137
120
  const entry = store.get(params.id);
@@ -163,17 +146,6 @@ async function registerPersistentRegistryEntries(params) {
163
146
  }
164
147
  }));
165
148
  }
166
- async function registerPersistentEntries(params) {
167
- await Promise.all([registerPersistentRegistryEntries({
168
- entries: params.entries,
169
- ttlMs: params.ttlMs,
170
- openStore: getPersistentComponentStore
171
- }), registerPersistentRegistryEntries({
172
- entries: params.modals,
173
- ttlMs: params.ttlMs,
174
- openStore: getPersistentModalStore
175
- })]);
176
- }
177
149
  async function deletePersistentEntry(params) {
178
150
  const store = params.openStore();
179
151
  if (!store) return;
@@ -188,10 +160,6 @@ function resolveComponentConsumptionIds(entry) {
188
160
  const ids = entry.consumptionGroupEntryIds?.filter((id) => typeof id === "string" && id) ?? [];
189
161
  return ids.length > 0 ? uniqueStrings(ids) : [entry.id];
190
162
  }
191
- function deleteComponentConsumptionGroup(entry) {
192
- const store = discordComponentRegistryState.componentEntries;
193
- for (const id of resolveComponentConsumptionIds(entry)) store.delete(id);
194
- }
195
163
  async function deletePersistentComponentConsumptionGroup(entry) {
196
164
  await Promise.all(resolveComponentConsumptionIds(entry).map((id) => deletePersistentEntry({
197
165
  id,
@@ -224,23 +192,26 @@ function registerDiscordComponentEntries(params) {
224
192
  return discordComponentRegistryState.withRegistryLock(async () => {
225
193
  for (const entry of normalizedEntries) discordComponentRegistryState.componentEntries.set(entry.id, entry);
226
194
  for (const entry of normalizedModals) discordComponentRegistryState.modalEntries.set(entry.id, entry);
227
- await registerPersistentEntries({
195
+ await Promise.all([registerPersistentRegistryEntries({
228
196
  entries: normalizedEntries,
229
- modals: normalizedModals,
230
- ttlMs
231
- });
197
+ ttlMs,
198
+ openStore: getPersistentComponentStore
199
+ }), registerPersistentRegistryEntries({
200
+ entries: normalizedModals,
201
+ ttlMs,
202
+ openStore: getPersistentModalStore
203
+ })]);
232
204
  });
233
205
  }
234
- function resolveDiscordComponentEntry(params) {
235
- const entry = resolveEntry(discordComponentRegistryState.componentEntries, params);
236
- if (entry && params.consume !== false) deleteComponentConsumptionGroup(entry);
237
- return entry;
238
- }
239
206
  async function resolveDiscordComponentEntryWithPersistence(params) {
240
207
  return discordComponentRegistryState.withRegistryLock(async () => {
241
- const inMemory = resolveDiscordComponentEntry(params);
208
+ const store = discordComponentRegistryState.componentEntries;
209
+ const inMemory = resolveEntry(store, params);
242
210
  if (inMemory) {
243
- if (params.consume !== false) await deletePersistentComponentConsumptionGroup(inMemory);
211
+ if (params.consume !== false) {
212
+ for (const id of resolveComponentConsumptionIds(inMemory)) store.delete(id);
213
+ await deletePersistentComponentConsumptionGroup(inMemory);
214
+ }
244
215
  return inMemory;
245
216
  }
246
217
  const persisted = await resolvePersistentRegistryEntry({
@@ -377,25 +348,8 @@ async function sendDiscordComponentMessage(to, spec, opts) {
377
348
  return await withDiscordRequestAuthority(opts.assertPlatformSendAuthorized, async () => {
378
349
  const classicMessage = opts.mediaUrl ? resolveClassicDiscordMessage(spec) : void 0;
379
350
  if (classicMessage) return await sendMessageDiscord(to, classicMessage.text, {
380
- cfg: opts.cfg,
381
- accountId: opts.accountId,
382
- token: opts.token,
383
- rest: opts.rest,
384
- mediaUrl: opts.mediaUrl,
385
- filename: opts.filename?.trim() || classicMessage.filename,
386
- mediaLocalRoots: opts.mediaLocalRoots,
387
- mediaReadFile: opts.mediaReadFile,
388
- mediaAccess: opts.mediaAccess,
389
- reply: opts.reply,
390
- silent: opts.silent,
391
- textLimit: opts.textLimit,
392
- maxLinesPerMessage: opts.maxLinesPerMessage,
393
- tableMode: opts.tableMode,
394
- chunkMode: opts.chunkMode,
395
- onDeliveryResult: opts.onDeliveryResult,
396
- onPlatformSendDispatch: opts.onPlatformSendDispatch,
397
- assertPlatformSendAuthorized: opts.assertPlatformSendAuthorized,
398
- ...opts.suppressEmbeds === void 0 ? {} : { suppressEmbeds: opts.suppressEmbeds }
351
+ ...opts,
352
+ filename: opts.filename?.trim() || classicMessage.filename
399
353
  });
400
354
  return await writeDiscordComponentMessage(to, spec, opts);
401
355
  });
@@ -432,7 +386,7 @@ async function writeDiscordComponentMessage(to, spec, opts, messageId) {
432
386
  if (messageId !== void 0) return editChannelMessage(rest, channelId, messageId, { body });
433
387
  await opts.onPlatformSendDispatch?.();
434
388
  opts.assertPlatformSendAuthorized?.();
435
- return createChannelMessage(rest, channelId, { body });
389
+ return await rest.post(Routes.channelMessages(channelId), { body });
436
390
  }, "components", creating ? { safety: "nonce-protected-create" } : void 0);
437
391
  } catch (err) {
438
392
  throw await buildDiscordSendError(err, {