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

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 (114) hide show
  1. package/dist/{account-inspect-SXiCBCgP.js → account-inspect-D5xmUpnj.js} +49 -43
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/accounts-DNl-oCEw.js +371 -0
  4. package/dist/action-runtime-api.js +1 -1
  5. package/dist/{active-turn-thread-route-MlKWp8RD.js → active-turn-thread-route-fmHl1HNd.js} +9 -11
  6. package/dist/{activities-api-DTKsuDxf.js → activities-api-KCBAmWxD.js} +133 -175
  7. package/dist/activities-api.js +1 -1
  8. package/dist/{allow-list-DzKrtfah.js → allow-list-BDMimASD.js} +2 -3
  9. package/dist/{api-DnrK9gML.js → api-DNVbJyEy.js} +7 -2
  10. package/dist/api.js +18 -18
  11. package/dist/{approval-handler.runtime-HymvFTYn.js → approval-handler.runtime-B12RCYr8.js} +6 -6
  12. package/dist/{approval-message-safety-DEyKVsAw.js → approval-message-safety-XGFPbDoI.js} +89 -3
  13. package/dist/{audit-CjsrabP8.js → audit-Yc7eZDd6.js} +5 -5
  14. package/dist/{channel-actions-uXFTJX3o.js → channel-actions-Dr2QI8Bf.js} +11 -7
  15. package/dist/{channel-actions.runtime-CIYU90sl.js → channel-actions.runtime-CODTvo7c.js} +99 -97
  16. package/dist/{channel-vKa-76xY.js → channel-cXYzGc4L.js} +102 -104
  17. package/dist/channel-config-api.js +1 -1
  18. package/dist/channel-plugin-api.js +1 -1
  19. package/dist/{channel.setup-CmcwZ_qd.js → channel.setup-CSiwnMgs.js} +16 -54
  20. package/dist/{outbound-session-route-H-HoyH5U.js → components-ezGKo_Yh.js} +69 -142
  21. package/dist/{config-schema-WHRQcY19.js → config-schema-DC5kDtQ5.js} +42 -11
  22. package/dist/conversation-binding-route-D6rCQ5S0.js +43 -0
  23. package/dist/{directory-config-D_LojtCe.js → directory-config-XyCfoqLg.js} +2 -2
  24. package/dist/directory-contract-api.js +1 -1
  25. package/dist/{directory-live-Bw5Ephzv.js → directory-live-Df0NbFJB.js} +7 -4
  26. package/dist/{doctor-Bc4S1qyd.js → doctor-DYY31GrO.js} +34 -13
  27. package/dist/{doctor-contract-CCFMIDk9.js → doctor-contract-DsWvKIXF.js} +2 -3
  28. package/dist/doctor-contract-api.js +147 -2
  29. package/dist/{handle-action.guild-admin-D6VRRW-C.js → handle-action.guild-admin-6Tvvinsa.js} +4 -7
  30. package/dist/{inbound-event-delivery-CEPlt2uz.js → inbound-event-delivery-De2qxTrP.js} +4 -25
  31. package/dist/index.js +5 -4
  32. package/dist/{message-handler-CeiSulIw.js → message-handler-B61q5MOF.js} +118 -33
  33. package/dist/{message-handler.preflight-DibgQFf2.js → message-handler.preflight-Bzg_Hawz.js} +75 -61
  34. package/dist/{message-handler.process-Dnc_oz9y.js → message-handler.process--4_712Ec.js} +236 -296
  35. package/dist/{message-utils-BgTwT_q7.js → message-utils-BXLLnFl6.js} +34 -35
  36. package/dist/{model-picker-preference-primitives-D25pnCOu.js → model-picker-preference-primitives-BHT1LkR_.js} +3 -3
  37. package/dist/{outbound-adapter-DtE-i2OJ.js → outbound-adapter-DxHTOqiN.js} +157 -68
  38. package/dist/{outbound-components-C7GVyfPh.js → outbound-components-B2PL0ooH.js} +3 -3
  39. package/dist/owner-access-Bg41VzAf.js +107 -0
  40. package/dist/{pluralkit-CD4B11JB.js → pluralkit-dUl4v1X3.js} +1 -1
  41. package/dist/{preflight-audio-BjnQZ5yS.js → preflight-audio-DrnqFA8U.js} +22 -28
  42. package/dist/{probe-BeWKhjqX.js → probe-BTdtVRwU.js} +37 -13
  43. package/dist/probe.runtime-Dq5-PRKT.js +2 -0
  44. package/dist/{provider-DfQJX3z5.js → provider-DlK_ynqt.js} +1131 -2021
  45. package/dist/{provider-session.runtime-Bo88k0pV.js → provider-session.runtime-CvvphHvs.js} +3 -3
  46. package/dist/provider.runtime-CzKGRiAF.js +2 -0
  47. package/dist/realtime-session.runtime-ysbMG8v7.js +1351 -0
  48. package/dist/{resolve-allowlist-common-DHERApxi.js → resolve-allowlist-common-DIVXbD1P.js} +3 -3
  49. package/dist/{resolve-channels-BjepAj8b.js → resolve-channels-CGeUoIqb.js} +4 -4
  50. package/dist/{resolve-users-Bc28htgt.js → resolve-users-BtKxLUZv.js} +3 -3
  51. package/dist/{chunk-DX4jpFQP.js → retry-DNsVTeTo.js} +308 -131
  52. package/dist/{runtime-BmW_j_hl.js → runtime-CQovMsH3.js} +12 -15
  53. package/dist/{runtime-_aRC6V2s.js → runtime-D71_VFLr.js} +49 -58
  54. package/dist/runtime-api.actions.js +2 -2
  55. package/dist/runtime-api.js +23 -23
  56. package/dist/runtime-api.lookup.js +5 -5
  57. package/dist/runtime-api.monitor-CzeTYF32.js +6 -0
  58. package/dist/runtime-api.monitor.js +6 -5
  59. package/dist/runtime-api.send.js +6 -6
  60. package/dist/runtime-api.threads.js +5 -6
  61. package/dist/sdk-runtime-37OU6_Yh.js +369 -0
  62. package/dist/{secret-config-contract-B1akrlyY.js → secret-config-contract-B3i6WxV5.js} +53 -25
  63. package/dist/secret-contract-api.js +1 -1
  64. package/dist/security-audit-contract-api.js +1 -1
  65. package/dist/{security-audit.runtime-CPN0xFYz.js → security-audit.runtime-BVKF9VtY.js} +1 -1
  66. package/dist/{send-CdkZ6zeW.js → send-rQIffSYu.js} +180 -91
  67. package/dist/{send.components-5uqwkjgE.js → send.components-ChK1wS7w.js} +32 -46
  68. package/dist/{send.outbound-D-80Ej-t.js → send.outbound-HIkgji_7.js} +57 -34
  69. package/dist/{send.reactions-CtlmGd_o.js → send.reactions-gmJ6yFPo.js} +14 -21
  70. package/dist/{send.receipt-CuwRrpFz.js → send.receipt-Cnhoautb.js} +29 -1
  71. package/dist/{send.shared-jpvAG_u6.js → send.shared-B3fpaLWJ.js} +112 -181
  72. package/dist/{sender-identity-mG_zsdox.js → sender-identity-BJtfyise.js} +1 -1
  73. package/dist/setup-entry.js +0 -5
  74. package/dist/setup-plugin-api.js +1 -1
  75. package/dist/{shared-CXF1tup1.js → shared-CaAaoksy.js} +27 -23
  76. package/dist/subagent-hooks-CNT2ZJTJ.js +49 -0
  77. package/dist/subagent-hooks-api.js +2 -6
  78. package/dist/subagent-progress-FPLqBY1s.js +110 -0
  79. package/dist/{system-events-DdUqf91B.js → system-events-Nuwoq_au.js} +2 -2
  80. package/dist/{target-resolver-C3s_2m0t.js → target-resolver-DIXShHaZ.js} +3 -3
  81. package/dist/targets-B2pf49zc.js +3 -0
  82. package/dist/{thread-bindings-C-M-ly7P.js → thread-bindings-VlymUnbz.js} +29 -28
  83. package/dist/{thread-bindings.manager-DYvSlrva.js → thread-bindings.manager-CngsgErq.js} +258 -29
  84. package/dist/{thread-bindings.session-updates-DAIlp7Px.js → thread-bindings.session-updates-CJb-BpPL.js} +1 -1
  85. package/dist/{thread-bindings.state-Cgi5uRn-.js → thread-bindings.state-D8OMdk4O.js} +3 -16
  86. package/dist/threading-DU6pgeGm.js +456 -0
  87. package/dist/transcripts-source-DeIMNbRh.js +265 -0
  88. package/dist/transcripts-source-api.js +7 -2
  89. package/dist/{typing--Mzo2kM7.js → typing-oLiS5Z13.js} +1 -1
  90. package/dist/voice-runtime-CjNFaynA.js +2549 -0
  91. package/openclaw.plugin.json +215 -25
  92. package/package.json +15 -11
  93. package/skills/discord/SKILL.md +22 -115
  94. package/dist/accounts-DJTf1LtA.js +0 -219
  95. package/dist/channel-api-CAJ0wMoV.js +0 -21
  96. package/dist/legacy-state-migrations-api.js +0 -138
  97. package/dist/manager.runtime-Bjav3pfC.js +0 -3655
  98. package/dist/preflight-audio.runtime-CoCXMM8r.js +0 -7
  99. package/dist/probe.runtime-DqFGFMbZ.js +0 -2
  100. package/dist/provider.runtime-Cy7XZmyh.js +0 -2
  101. package/dist/runtime-api.monitor-CCRubd3-.js +0 -5
  102. package/dist/session-binding-contract-api.js +0 -2
  103. package/dist/subagent-hooks-Dxy7Fmnq.js +0 -120
  104. package/dist/subagent-progress-DI3sYLLo.js +0 -664
  105. package/dist/targets-Dv_RN7vT.js +0 -3
  106. package/dist/thread-bindings.discord-api-BoDNqamb.js +0 -246
  107. package/dist/transcripts-source-DIKGsTGT.js +0 -123
  108. package/node_modules/p-map/index.d.ts +0 -155
  109. package/node_modules/p-map/index.js +0 -285
  110. package/node_modules/p-map/license +0 -9
  111. package/node_modules/p-map/package.json +0 -57
  112. package/node_modules/p-map/readme.md +0 -190
  113. package/dist/{audit-core-xwjIczO0.js → audit-core-BCX8UeFH.js} +1 -1
  114. package/dist/{security-audit-jGnJF__m.js → security-audit-BfJhAXZp.js} +1 -1
@@ -1,11 +1,12 @@
1
- import { o as resolveDefaultDiscordAccountId } from "./accounts-DJTf1LtA.js";
2
- import { t as inspectDiscordAccount } from "./account-inspect-SXiCBCgP.js";
3
- import { r as DISCORD_LEGACY_CONFIG_RULES } from "./shared-CXF1tup1.js";
4
- import { n as normalizeCompatibilityConfig } from "./doctor-contract-CCFMIDk9.js";
1
+ import { s as resolveDefaultDiscordAccountId } from "./accounts-DNl-oCEw.js";
2
+ import { t as discordVoiceTranscriptsSourceProvider } from "./transcripts-source-DeIMNbRh.js";
3
+ import { t as inspectDiscordAccount } from "./account-inspect-D5xmUpnj.js";
4
+ import { r as DISCORD_LEGACY_CONFIG_RULES } from "./shared-CaAaoksy.js";
5
+ import { n as normalizeCompatibilityConfig } from "./doctor-contract-DsWvKIXF.js";
5
6
  import { t as isDiscordMutableAllowEntry } from "./security-doctor-nGgPm65L.js";
6
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
7
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
8
- import { asObjectRecord, collectChannelAccountScopes, collectProviderDangerousNameMatchingScopes } from "openclaw/plugin-sdk/runtime-doctor";
8
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
9
+ import { asObjectRecord, collectChannelAccountScopes, collectProviderDangerousNameMatchingScopes } from "openclaw/plugin-sdk/runtime-doctor-migrations";
9
10
  //#region extensions/discord/src/doctor.ts
10
11
  function sanitizeForLog(value) {
11
12
  return value.replace(/\p{Cc}+/gu, " ").trim();
@@ -165,6 +166,22 @@ function collectDiscordMissingEnvTokenWarnings(params) {
165
166
  if (!account.enabled || account.tokenStatus !== "missing" || account.tokenSource !== "none") return [];
166
167
  return ["- channels.discord: default account has no available bot token, and DISCORD_BOT_TOKEN is absent in this doctor environment. After migration, verify DISCORD_BOT_TOKEN is present in the state-dir .env or configure channels.discord.token / channels.discord.accounts.default.token as a SecretRef."];
167
168
  }
169
+ function collectDiscordTranscriptsAutoStartWarnings(cfg) {
170
+ if (cfg.transcripts?.enabled === false || !Array.isArray(cfg.transcripts?.autoStart)) return [];
171
+ const ownership = discordVoiceTranscriptsSourceProvider.accessControl;
172
+ if (!ownership) return [];
173
+ return cfg.transcripts.autoStart.flatMap((entry, index) => {
174
+ const providerId = normalizeOptionalString(entry.providerId)?.toLowerCase();
175
+ if (providerId !== discordVoiceTranscriptsSourceProvider.id && !discordVoiceTranscriptsSourceProvider.aliases?.includes(providerId ?? "") || normalizeOptionalString(entry.accountId)) return [];
176
+ const resolution = ownership.resolveAccountId({
177
+ cfg,
178
+ source: entry
179
+ });
180
+ if (resolution.ok) return [];
181
+ const path = `transcripts.autoStart[${index}]`;
182
+ return [`- ${path} cannot select a Discord voice account: ${resolution.error} Set ${path}.accountId to the intended enabled voice account, or set channels.discord.defaultAccount when one account should be the global default.`];
183
+ });
184
+ }
168
185
  function collectDiscordMutableAllowlistWarnings(cfg) {
169
186
  const hits = [];
170
187
  const addHits = (pathLabel, list) => {
@@ -215,13 +232,17 @@ const discordDoctor = {
215
232
  warnOnEmptyGroupSenderAllowlist: false,
216
233
  legacyConfigRules: DISCORD_LEGACY_CONFIG_RULES,
217
234
  normalizeCompatibilityConfig,
218
- collectPreviewWarnings: ({ cfg, doctorFixCommand, env }) => [...collectDiscordMissingEnvTokenWarnings({
219
- cfg,
220
- env
221
- }), ...collectDiscordNumericIdWarnings({
222
- hits: scanDiscordNumericIdEntries(cfg),
223
- doctorFixCommand
224
- })],
235
+ collectPreviewWarnings: ({ cfg, doctorFixCommand, env }) => [
236
+ ...collectDiscordMissingEnvTokenWarnings({
237
+ cfg,
238
+ env
239
+ }),
240
+ ...collectDiscordNumericIdWarnings({
241
+ hits: scanDiscordNumericIdEntries(cfg),
242
+ doctorFixCommand
243
+ }),
244
+ ...collectDiscordTranscriptsAutoStartWarnings(cfg)
245
+ ],
225
246
  collectMutableAllowlistWarnings: ({ cfg }) => collectDiscordMutableAllowlistWarnings(cfg),
226
247
  repairConfig: ({ cfg, doctorFixCommand }) => maybeRepairDiscordNumericIds(cfg, doctorFixCommand)
227
248
  };
@@ -1,5 +1,5 @@
1
- import { isSupportedRealtimeVoiceActivationName, normalizeRealtimeVoiceActivationNamePrefix } from "openclaw/plugin-sdk/realtime-voice";
2
- import { asObjectRecord, defineChannelAliasMigration, defineKeyMoveMigration, hasLegacyAccountStreamingAliases, normalizeChannelAccounts, stripRetiredChannelKeys } from "openclaw/plugin-sdk/runtime-doctor";
1
+ import { asObjectRecord, defineChannelAliasMigration, defineKeyMoveMigration, hasLegacyAccountStreamingAliases, normalizeChannelAccounts, stripRetiredChannelKeys } from "openclaw/plugin-sdk/runtime-doctor-migrations";
2
+ import { isSupportedRealtimeVoiceActivationName, normalizeRealtimeVoiceActivationNamePrefix } from "openclaw/plugin-sdk/realtime-voice-activation";
3
3
  //#region extensions/discord/src/doctor-contract.ts
4
4
  const LEGACY_TTS_PROVIDER_KEYS = [
5
5
  "openai",
@@ -18,7 +18,6 @@ const streamingAliasMigration = defineChannelAliasMigration({
18
18
  channelId: "discord",
19
19
  streaming: {
20
20
  defaultMode: "off",
21
- absentObjectDefault: "progress",
22
21
  includePreviewChunk: true
23
22
  },
24
23
  accountStreamingReplacesRoot: true,
@@ -1,2 +1,147 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-CCFMIDk9.js";
2
- export { legacyConfigRules, normalizeCompatibilityConfig };
1
+ import { n as DISCORD_COMMAND_DEPLOY_HASH_NAMESPACE, t as DISCORD_COMMAND_DEPLOY_HASH_MAX_ENTRIES } from "./command-deploy-store-DFkBTViB.js";
2
+ import { a as THREAD_BINDINGS_MAX_ENTRIES, d as normalizePersistedBinding, k as toBindingRecordKey, o as THREAD_BINDINGS_NAMESPACE } from "./thread-bindings.state-D8OMdk4O.js";
3
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-DsWvKIXF.js";
4
+ import { i as sanitizeRecentModels, r as preferenceTimestampMs, t as buildPreferenceModelKey } from "./model-picker-preference-primitives-BHT1LkR_.js";
5
+ import { MAX_DATE_TIMESTAMP_MS, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
6
+ import { definePluginDoctorMigrationFromPlans } from "openclaw/plugin-sdk/runtime-doctor-migrations";
7
+ import path from "node:path";
8
+ import fs from "node:fs";
9
+ //#region extensions/discord/src/monitor/model-picker-preferences-migrations.ts
10
+ const PREFERENCE_MAX_ENTRIES = 2e3;
11
+ const MAX_PLUGIN_STATE_KEY_BYTES = 512;
12
+ const textEncoder = new TextEncoder();
13
+ function fileExists(filePath) {
14
+ try {
15
+ return fs.statSync(filePath).isFile();
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+ function readLegacyStore(filePath) {
21
+ try {
22
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
23
+ return parsed && typeof parsed === "object" ? parsed : null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function readLegacyThreadBindingsStore(filePath) {
29
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
30
+ if (!parsed || typeof parsed !== "object") throw new Error("legacy Discord thread bindings store must be an object");
31
+ return parsed;
32
+ }
33
+ function normalizeLegacyPreferenceKey(key) {
34
+ const trimmed = key.trim();
35
+ if (!trimmed || textEncoder.encode(trimmed).length > MAX_PLUGIN_STATE_KEY_BYTES) return;
36
+ return trimmed;
37
+ }
38
+ function legacyUpdatedAtForIndex(updatedAt, index, total) {
39
+ const baseMs = preferenceTimestampMs(updatedAt);
40
+ return timestampMsToIsoString(Math.min(baseMs + Math.max(0, total), MAX_DATE_TIMESTAMP_MS) - Math.max(0, index)) ?? timestampMsToIsoString(baseMs) ?? timestampMsToIsoString(Math.max(0, total - index)) ?? "1970-01-01T00:00:00.000Z";
41
+ }
42
+ function readFiniteNumberField(entry, key) {
43
+ const value = entry[key];
44
+ return typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : void 0;
45
+ }
46
+ function upgradeLegacyThreadBindingShape(rawEntry) {
47
+ if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) return rawEntry;
48
+ const entry = { ...rawEntry };
49
+ if (entry.targetSessionKey === void 0 && typeof entry.sessionKey === "string") entry.targetSessionKey = entry.sessionKey;
50
+ delete entry.sessionKey;
51
+ const expiresAt = readFiniteNumberField(entry, "expiresAt");
52
+ delete entry.expiresAt;
53
+ if (entry.idleTimeoutMs === void 0 && entry.maxAgeMs === void 0 && expiresAt !== void 0) {
54
+ entry.idleTimeoutMs = 0;
55
+ if (expiresAt <= 0) entry.maxAgeMs = 0;
56
+ else {
57
+ const boundAt = readFiniteNumberField(entry, "boundAt") ?? 0;
58
+ const lastActivityAt = readFiniteNumberField(entry, "lastActivityAt") ?? 0;
59
+ entry.maxAgeMs = Math.max(1, expiresAt - Math.max(0, boundAt > 0 ? boundAt : lastActivityAt));
60
+ }
61
+ }
62
+ return entry;
63
+ }
64
+ const detectDiscordLegacyStateMigrations = ({ stateDir }) => {
65
+ const plans = [];
66
+ const commandDeployCacheSourcePath = path.join(stateDir, "discord", "command-deploy-cache.json");
67
+ if (fileExists(commandDeployCacheSourcePath)) plans.push({
68
+ kind: "plugin-state-import",
69
+ label: "Discord command deployment cache",
70
+ sourcePath: commandDeployCacheSourcePath,
71
+ targetPath: `plugin state:${DISCORD_COMMAND_DEPLOY_HASH_NAMESPACE}`,
72
+ pluginId: "discord",
73
+ namespace: DISCORD_COMMAND_DEPLOY_HASH_NAMESPACE,
74
+ maxEntries: DISCORD_COMMAND_DEPLOY_HASH_MAX_ENTRIES,
75
+ scopeKey: "",
76
+ cleanupSource: "remove",
77
+ cleanupWhenEmpty: true,
78
+ readEntries: () => []
79
+ });
80
+ const modelPickerSourcePath = path.join(stateDir, "discord", "model-picker-preferences.json");
81
+ if (fileExists(modelPickerSourcePath)) plans.push({
82
+ kind: "plugin-state-import",
83
+ label: "Discord model picker preferences",
84
+ sourcePath: modelPickerSourcePath,
85
+ targetPath: "plugin state:model-picker-preferences",
86
+ pluginId: "discord",
87
+ namespace: "model-picker-preferences",
88
+ maxEntries: PREFERENCE_MAX_ENTRIES,
89
+ scopeKey: "",
90
+ cleanupSource: "rename",
91
+ readEntries: () => {
92
+ const store = readLegacyStore(modelPickerSourcePath);
93
+ if (!store || !store.entries || typeof store.entries !== "object") return [];
94
+ const out = [];
95
+ for (const [rawKey, rawEntry] of Object.entries(store.entries)) {
96
+ const scopeKey = normalizeLegacyPreferenceKey(rawKey);
97
+ if (!scopeKey || !rawEntry || typeof rawEntry !== "object") continue;
98
+ const recent = sanitizeRecentModels(rawEntry.recent, 10);
99
+ for (const [index, modelRef] of recent.entries()) out.push({
100
+ key: buildPreferenceModelKey(scopeKey, modelRef),
101
+ value: {
102
+ scopeKey,
103
+ modelRef,
104
+ updatedAt: legacyUpdatedAtForIndex(rawEntry.updatedAt, index, recent.length)
105
+ }
106
+ });
107
+ }
108
+ return out;
109
+ }
110
+ });
111
+ const threadBindingsSourcePath = path.join(stateDir, "discord", "thread-bindings.json");
112
+ if (fileExists(threadBindingsSourcePath)) plans.push({
113
+ kind: "plugin-state-import",
114
+ label: "Discord thread bindings",
115
+ sourcePath: threadBindingsSourcePath,
116
+ targetPath: `plugin state:${THREAD_BINDINGS_NAMESPACE}`,
117
+ pluginId: "discord",
118
+ namespace: THREAD_BINDINGS_NAMESPACE,
119
+ maxEntries: THREAD_BINDINGS_MAX_ENTRIES,
120
+ scopeKey: "",
121
+ cleanupSource: "rename",
122
+ cleanupWhenEmpty: true,
123
+ readEntries: () => {
124
+ const store = readLegacyThreadBindingsStore(threadBindingsSourcePath);
125
+ if (store?.version !== 1 || !store.bindings || typeof store.bindings !== "object") throw new Error("legacy Discord thread bindings store must have version 1 bindings");
126
+ const out = [];
127
+ for (const [rawKey, rawEntry] of Object.entries(store.bindings)) {
128
+ const normalized = normalizePersistedBinding(rawKey, upgradeLegacyThreadBindingShape(rawEntry));
129
+ if (normalized) out.push({
130
+ key: toBindingRecordKey(normalized),
131
+ value: normalized
132
+ });
133
+ }
134
+ return out;
135
+ }
136
+ });
137
+ return plans;
138
+ };
139
+ //#endregion
140
+ //#region extensions/discord/doctor-contract-api.ts
141
+ const stateMigrations = [definePluginDoctorMigrationFromPlans({
142
+ id: "discord-legacy-state",
143
+ label: "Discord legacy state",
144
+ resolvePlans: detectDiscordLegacyStateMigrations
145
+ })];
146
+ //#endregion
147
+ export { legacyConfigRules, normalizeCompatibilityConfig, stateMigrations };
@@ -1,6 +1,6 @@
1
- import { c as readDiscordChannelMoveParams, n as isDiscordModerationAction, o as readDiscordChannelCreateParams, r as readDiscordModerationCommand, s as readDiscordChannelEditParams, t as handleDiscordAction } from "./runtime-_aRC6V2s.js";
1
+ import { c as readDiscordChannelMoveParams, n as isDiscordModerationAction, o as readDiscordChannelCreateParams, r as readDiscordModerationCommand, s as readDiscordChannelEditParams, t as handleDiscordAction } from "./runtime-D71_VFLr.js";
2
2
  import "./action-runtime-api.js";
3
- import { n as isTrustedRequesterGuildAdminAction } from "./channel-actions-uXFTJX3o.js";
3
+ import { n as isTrustedRequesterGuildAdminAction } from "./channel-actions-Dr2QI8Bf.js";
4
4
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { readNonNegativeIntegerParam, readPositiveIntegerParam, readStringArrayParam, readStringParam } from "openclaw/plugin-sdk/agent-runtime";
6
6
  //#region extensions/discord/src/actions/handle-action.guild-admin.ts
@@ -13,7 +13,7 @@ function senderParam(senderUserId) {
13
13
  return senderUserId ? { senderUserId } : {};
14
14
  }
15
15
  async function tryHandleDiscordMessageActionGuildAdmin(params) {
16
- const { ctx, resolveChannelId, readPolicyOptions } = params;
16
+ const { ctx, resolveChannelId, readPolicyOptions, actionOptions } = params;
17
17
  const { action, params: actionParams, cfg } = ctx;
18
18
  const accountId = ctx.accountId ?? readStringParam(actionParams, "accountId");
19
19
  const senderUserId = readDiscordRequesterSenderId(ctx);
@@ -284,10 +284,7 @@ async function tryHandleDiscordMessageActionGuildAdmin(params) {
284
284
  content,
285
285
  mediaUrl: mediaUrl ?? void 0,
286
286
  replyTo: replyTo ?? void 0
287
- }, cfg, {
288
- mediaLocalRoots: ctx.mediaLocalRoots,
289
- mediaReadFile: ctx.mediaReadFile
290
- });
287
+ }, cfg, actionOptions);
291
288
  }
292
289
  if (action === "search") {
293
290
  const guildId = readStringParam(actionParams, "guildId");
@@ -1,32 +1,11 @@
1
1
  import { asOptionalRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ import { createInboundEventDeliveryCorrelation } from "openclaw/plugin-sdk/inbound-event-delivery";
2
3
  //#region extensions/discord/src/inbound-event-delivery.ts
3
4
  const DISCORD_INBOUND_EVENT_DELIVERY_KEY = "__openclawInboundEventDelivery";
4
- const registry = /* @__PURE__ */ new Map();
5
5
  function normalizeDiscordDeliveryTarget(value) {
6
6
  return value.trim().replace(/^discord:/iu, "").replace(/^channel:/iu, "").toLowerCase();
7
7
  }
8
- function resolveDiscordInboundEventDeliveryCorrelationKey(sessionKey, inboundEventKind) {
9
- const key = sessionKey?.trim();
10
- if (!key) return;
11
- return inboundEventKind === "room_event" ? `${key}:room_event` : key;
12
- }
13
- function beginDiscordInboundEventDeliveryCorrelation(sessionKey, event, options) {
14
- const key = resolveDiscordInboundEventDeliveryCorrelationKey(sessionKey, options?.inboundEventKind);
15
- if (!key) return () => {};
16
- registry.set(key, event);
17
- return () => {
18
- if (registry.get(key) === event) registry.delete(key);
19
- };
20
- }
21
- function notifyDiscordInboundEventOutboundSuccess(params) {
22
- const key = resolveDiscordInboundEventDeliveryCorrelationKey(params.sessionKey, params.inboundEventKind);
23
- if (!key) return;
24
- const event = registry.get(key);
25
- if (!event || normalizeDiscordDeliveryTarget(event.outboundTo) !== normalizeDiscordDeliveryTarget(params.to)) return;
26
- if (event.outboundAccountId && params.accountId && params.accountId !== event.outboundAccountId) return;
27
- registry.delete(key);
28
- event.markInboundEventDelivered();
29
- }
8
+ const discordInboundEventDelivery = createInboundEventDeliveryCorrelation({ targetsMatch: (expected, actual) => normalizeDiscordDeliveryTarget(expected) === normalizeDiscordDeliveryTarget(actual) });
30
9
  function withDiscordInboundEventDeliveryMetadata(payload, params) {
31
10
  const sessionKey = params.sessionKey?.trim();
32
11
  if (!sessionKey || params.inboundEventKind !== "room_event") return payload;
@@ -49,7 +28,7 @@ function withDiscordInboundEventDeliveryMetadata(payload, params) {
49
28
  function notifyDiscordInboundEventOutboundPayloadSuccess(params) {
50
29
  const metadata = asOptionalRecord(asOptionalRecord(asOptionalRecord(params.payload.channelData)?.discord)?.[DISCORD_INBOUND_EVENT_DELIVERY_KEY]);
51
30
  if (!metadata) return;
52
- notifyDiscordInboundEventOutboundSuccess({
31
+ discordInboundEventDelivery.notify({
53
32
  sessionKey: normalizeOptionalString(metadata.sessionKey),
54
33
  inboundEventKind: normalizeOptionalString(metadata.inboundEventKind),
55
34
  to: params.to,
@@ -57,4 +36,4 @@ function notifyDiscordInboundEventOutboundPayloadSuccess(params) {
57
36
  });
58
37
  }
59
38
  //#endregion
60
- export { withDiscordInboundEventDeliveryMetadata as i, notifyDiscordInboundEventOutboundPayloadSuccess as n, notifyDiscordInboundEventOutboundSuccess as r, beginDiscordInboundEventDeliveryCorrelation as t };
39
+ export { notifyDiscordInboundEventOutboundPayloadSuccess as n, withDiscordInboundEventDeliveryMetadata as r, discordInboundEventDelivery as t };
package/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
- import { t as registerDiscordActivities } from "./activities-api-DTKsuDxf.js";
1
+ import { t as registerDiscordActivities } from "./activities-api-KCBAmWxD.js";
2
2
  import { registerDiscordSubagentHooks } from "./subagent-hooks-api.js";
3
- import { t as discordVoiceTranscriptsSourceProvider } from "./transcripts-source-DIKGsTGT.js";
4
- import "./transcripts-source-api.js";
3
+ import { registerDiscordTranscriptSourceProvider } from "./transcripts-source-api.js";
5
4
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
6
5
  //#region extensions/discord/index.ts
7
6
  var discord_default = defineBundledChannelEntry({
@@ -24,7 +23,9 @@ var discord_default = defineBundledChannelEntry({
24
23
  registerFull(api) {
25
24
  registerDiscordActivities(api);
26
25
  registerDiscordSubagentHooks(api);
27
- api.registerTranscriptSourceProvider(discordVoiceTranscriptsSourceProvider);
26
+ },
27
+ registerCapabilities(api) {
28
+ registerDiscordTranscriptSourceProvider(api);
28
29
  }
29
30
  });
30
31
  //#endregion
@@ -1,12 +1,15 @@
1
1
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
2
- import { d as resolveDiscordChannelIdSafe, f as resolveDiscordChannelInfoSafe, h as resolveDiscordChannelParentSafe, p as resolveDiscordChannelNameSafe } from "./thread-bindings.discord-api-BoDNqamb.js";
3
- import { w as mapGatewayDispatchData } from "./provider-DfQJX3z5.js";
4
- import { f as resolveDiscordMessageChannelId, i as resolveDiscordMessageText, l as hasDiscordMessageStickers } from "./message-utils-BgTwT_q7.js";
5
- import { normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ import { c as resolveDiscordChannelInfoSafe, d as resolveDiscordChannelParentSafe, l as resolveDiscordChannelNameSafe, s as resolveDiscordChannelIdSafe } from "./thread-bindings.manager-CngsgErq.js";
3
+ import { _ as mapGatewayDispatchData } from "./provider-DlK_ynqt.js";
4
+ import { i as resolveDiscordMessageText, l as resolveDiscordCdnPolicy, p as resolveDiscordMessageChannelId, u as hasDiscordMessageStickers } from "./message-utils-BXLLnFl6.js";
5
+ import { logDebug } from "openclaw/plugin-sdk/logging-core";
6
6
  import { GatewayDispatchEvents } from "discord-api-types/v10";
7
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
8
+ import { normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
9
  import { danger } from "openclaw/plugin-sdk/runtime-env";
8
10
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
9
- import { createChannelIngressError, createChannelIngressMonitor, createChannelRunQueue } from "openclaw/plugin-sdk/channel-outbound";
11
+ import { DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, createChannelIngressError, createChannelIngressMonitor, createChannelRunQueue } from "openclaw/plugin-sdk/channel-outbound";
12
+ import { saveRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
10
13
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
11
14
  import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
12
15
  import { fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
@@ -43,9 +46,6 @@ function decodeDiscordIngressPayload(payload, claimedId) {
43
46
  }
44
47
  };
45
48
  }
46
- function errorText(error) {
47
- return error instanceof Error ? error.message : String(error);
48
- }
49
49
  function isDiscordAuthenticationFailure(error) {
50
50
  let current = error;
51
51
  const seen = /* @__PURE__ */ new Set();
@@ -87,6 +87,10 @@ function createDiscordIngressMonitor(params) {
87
87
  },
88
88
  appendRetryDelaysMs: [0],
89
89
  drain: {
90
+ retryPolicy: {
91
+ maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
92
+ deadLetterMinAgeMs: 0
93
+ },
90
94
  resolveNonRetryableFailure: (error) => {
91
95
  if (error instanceof DiscordIngressPayloadError) return {
92
96
  reason: "invalid-event",
@@ -94,13 +98,13 @@ function createDiscordIngressMonitor(params) {
94
98
  };
95
99
  if (isDiscordAuthenticationFailure(error)) return {
96
100
  reason: "authentication-failed",
97
- message: errorText(error)
101
+ message: formatErrorMessage(error)
98
102
  };
99
103
  return null;
100
104
  },
101
105
  onLog: (message) => params.runtime.error?.(danger(`discord ingress: ${message}`))
102
106
  },
103
- onError: (error) => params.runtime.error?.(danger(`discord ingress drain failed: ${errorText(error)}`))
107
+ onError: (error) => params.runtime.error?.(danger(`discord ingress drain failed: ${formatErrorMessage(error)}`))
104
108
  });
105
109
  return {
106
110
  accept: async (rawMessage) => {
@@ -112,18 +116,10 @@ function createDiscordIngressMonitor(params) {
112
116
  }
113
117
  //#endregion
114
118
  //#region extensions/discord/src/monitor/inbound-job.ts
115
- function resolveDiscordInboundJobQueueKey(ctx) {
116
- const sessionKey = ctx.route.sessionKey?.trim();
117
- if (sessionKey) return sessionKey;
118
- const baseSessionKey = ctx.baseSessionKey?.trim();
119
- if (baseSessionKey) return baseSessionKey;
120
- return ctx.messageChannelId;
121
- }
122
119
  function buildDiscordInboundJob(ctx, options) {
123
120
  const { runtime, abortSignal, guildHistories, client, turnAdoptionLifecycle, threadBindings, discordRestFetch, message, data, threadChannel, ...payload } = ctx;
124
121
  const sanitizedMessage = sanitizeDiscordInboundMessage(message);
125
122
  return {
126
- queueKey: resolveDiscordInboundJobQueueKey(ctx),
127
123
  payload: {
128
124
  ...payload,
129
125
  message: sanitizedMessage,
@@ -173,6 +169,77 @@ function normalizeDiscordThreadChannel(threadChannel) {
173
169
  };
174
170
  }
175
171
  //#endregion
172
+ //#region extensions/discord/src/monitor/message-avatar.ts
173
+ const DISCORD_AVATAR_MAX_BYTES = 256 * 1024;
174
+ const DISCORD_AVATAR_CACHE_MAX_ENTRIES = 128;
175
+ const DISCORD_GUILD_ICON_TTL_MS = 5 * 6e4;
176
+ function setBoundedEntry(map, key, value) {
177
+ map.delete(key);
178
+ map.set(key, value);
179
+ while (map.size > DISCORD_AVATAR_CACHE_MAX_ENTRIES) {
180
+ const oldest = map.keys().next().value;
181
+ if (oldest === void 0) break;
182
+ map.delete(oldest);
183
+ }
184
+ }
185
+ function discordAvatarUrl(owner, id, hash) {
186
+ return `https://cdn.discordapp.com/${owner}/${id}/${hash}.png?size=128`;
187
+ }
188
+ /** Dispatcher-lifetime resolver for eventually available Discord conversation images. */
189
+ function createDiscordAvatarResolver() {
190
+ const saved = /* @__PURE__ */ new Map();
191
+ const pending = /* @__PURE__ */ new Set();
192
+ const guildIcons = /* @__PURE__ */ new Map();
193
+ const pendingGuilds = /* @__PURE__ */ new Set();
194
+ const resolveSavedAvatar = (key, url) => {
195
+ const cached = saved.get(key);
196
+ if (cached) {
197
+ saved.delete(key);
198
+ saved.set(key, cached);
199
+ return cached;
200
+ }
201
+ if (pending.has(key) || pending.size >= DISCORD_AVATAR_CACHE_MAX_ENTRIES) return;
202
+ pending.add(key);
203
+ saveRemoteMedia({
204
+ url,
205
+ filePathHint: "conversation-avatar.png",
206
+ maxBytes: DISCORD_AVATAR_MAX_BYTES,
207
+ ssrfPolicy: resolveDiscordCdnPolicy()
208
+ }).then((media) => {
209
+ setBoundedEntry(saved, key, media.path);
210
+ }).catch((error) => {
211
+ logDebug(`discord conversation avatar download failed: ${formatErrorMessage(error)}`);
212
+ }).finally(() => {
213
+ pending.delete(key);
214
+ });
215
+ };
216
+ const refreshGuildIcon = (client, guildId, conversationId) => {
217
+ if (pendingGuilds.has(guildId) || pendingGuilds.size >= DISCORD_AVATAR_CACHE_MAX_ENTRIES) return;
218
+ pendingGuilds.add(guildId);
219
+ client.fetchGuild(guildId).then((guild) => {
220
+ const hash = guild.icon ?? null;
221
+ setBoundedEntry(guildIcons, guildId, {
222
+ expiresAt: Date.now() + DISCORD_GUILD_ICON_TTL_MS,
223
+ hash
224
+ });
225
+ if (hash) resolveSavedAvatar(`${conversationId}\0${hash}`, discordAvatarUrl("icons", guildId, hash));
226
+ }).catch((error) => {
227
+ logDebug(`discord guild icon lookup failed: ${formatErrorMessage(error)}`);
228
+ }).finally(() => {
229
+ pendingGuilds.delete(guildId);
230
+ });
231
+ };
232
+ return { resolve(params) {
233
+ if (!params.guildId) {
234
+ const hash = params.author.avatar;
235
+ return hash ? resolveSavedAvatar(`${params.conversationId}\0${hash}`, discordAvatarUrl("avatars", params.author.id, hash)) : void 0;
236
+ }
237
+ const guildIcon = guildIcons.get(params.guildId);
238
+ if (!guildIcon || guildIcon.expiresAt <= Date.now()) refreshGuildIcon(params.client, params.guildId, params.conversationId);
239
+ return guildIcon?.hash ? resolveSavedAvatar(`${params.conversationId}\0${guildIcon.hash}`, discordAvatarUrl("icons", params.guildId, guildIcon.hash)) : void 0;
240
+ } };
241
+ }
242
+ //#endregion
176
243
  //#region extensions/discord/src/monitor/message-handler.batch-gate.ts
177
244
  function applyImplicitReplyBatchGate(ctx, replyToMode, isBatched) {
178
245
  const replyThreading = resolveBatchedReplyThreadingPolicy(replyToMode, isBatched);
@@ -181,20 +248,21 @@ function applyImplicitReplyBatchGate(ctx, replyToMode, isBatched) {
181
248
  }
182
249
  //#endregion
183
250
  //#region extensions/discord/src/monitor/message-run-queue.ts
184
- const loadMessageProcessRuntime = createLazyRuntimeModule(() => import("./message-handler.process-Dnc_oz9y.js"));
251
+ const loadMessageProcessRuntime = createLazyRuntimeModule(() => import("./message-handler.process--4_712Ec.js"));
185
252
  async function processDiscordQueuedMessage(params) {
186
253
  const abortSignal = params.job.runtime.abortSignal && params.lifecycleSignal ? AbortSignal.any([params.job.runtime.abortSignal, params.lifecycleSignal]) : params.job.runtime.abortSignal ?? params.lifecycleSignal;
187
254
  try {
188
255
  await (params.testing?.processDiscordMessage ?? (await loadMessageProcessRuntime()).processDiscordMessage)(materializeDiscordInboundJob(params.job, abortSignal));
189
- if (abortSignal?.aborted) await params.job.ingressSettlement?.abandon(abortSignal.reason);
256
+ if (abortSignal?.aborted) await params.job.ingressSettlement?.cancel();
190
257
  else await params.job.ingressSettlement?.settle();
191
258
  } catch (error) {
192
- await params.job.ingressSettlement?.abandon(error);
259
+ if (abortSignal?.aborted) await params.job.ingressSettlement?.cancel();
260
+ else await params.job.ingressSettlement?.abandon(error);
193
261
  throw error;
194
262
  }
195
263
  }
196
264
  async function cleanupSkippedDiscordQueuedMessage(params) {
197
- await params.job.ingressSettlement?.abandon(/* @__PURE__ */ new Error("discord queued run skipped before processing"));
265
+ await params.job.ingressSettlement?.cancel();
198
266
  }
199
267
  function createDiscordMessageRunQueue(params) {
200
268
  const skippedCleanup = /* @__PURE__ */ new Set();
@@ -232,6 +300,10 @@ function createDiscordMessageRunQueue(params) {
232
300
  const cleanupSkipped = async () => {
233
301
  try {
234
302
  await cleanupSkippedDiscordQueuedMessage({ job });
303
+ } catch (error) {
304
+ try {
305
+ params.runtime.error(danger(`discord queued message cleanup failed: ${String(error)}`));
306
+ } catch {}
235
307
  } finally {
236
308
  settlePending();
237
309
  }
@@ -241,7 +313,7 @@ function createDiscordMessageRunQueue(params) {
241
313
  return;
242
314
  }
243
315
  skippedCleanup.add(cleanupSkipped);
244
- runQueue.enqueue(job.queueKey, async ({ lifecycleSignal }) => {
316
+ runQueue.enqueue(job.payload.message.id, async ({ lifecycleSignal }) => {
245
317
  skippedCleanup.delete(cleanupSkipped);
246
318
  try {
247
319
  await processDiscordQueuedMessage({
@@ -263,7 +335,7 @@ function createDiscordMessageRunQueue(params) {
263
335
  }
264
336
  //#endregion
265
337
  //#region extensions/discord/src/monitor/message-dispatcher.ts
266
- const loadMessagePreflightRuntime = createLazyRuntimeModule(() => import("./message-handler.preflight-DibgQFf2.js"));
338
+ const loadMessagePreflightRuntime = createLazyRuntimeModule(() => import("./message-handler.preflight-Bzg_Hawz.js"));
267
339
  function isNonEmptyString(value) {
268
340
  return typeof value === "string" && value.length > 0;
269
341
  }
@@ -282,6 +354,7 @@ function createDiscordMessageDispatcher(params) {
282
354
  testing: params.testing
283
355
  });
284
356
  const dispatcherShutdown = new AbortController();
357
+ const avatarResolver = createDiscordAvatarResolver();
285
358
  const pendingDebounceEntries = /* @__PURE__ */ new Set();
286
359
  const pendingCancellationSettlements = /* @__PURE__ */ new Set();
287
360
  const resolveDebounceKey = (entry) => {
@@ -317,13 +390,14 @@ function createDiscordMessageDispatcher(params) {
317
390
  if (!last) return;
318
391
  const abortSignal = last.abortSignal;
319
392
  if (abortSignal?.aborted) {
320
- await admissionLifecycle.onAbandoned();
393
+ await ingress.cancel();
321
394
  return;
322
395
  }
323
396
  try {
324
397
  if (entries.length === 1) {
325
398
  const ctx = await (preflightDiscordMessageImpl ?? (await loadMessagePreflightRuntime()).preflightDiscordMessage)({
326
399
  ...params,
400
+ avatarResolver,
327
401
  ackReactionScope,
328
402
  groupPolicy,
329
403
  abortSignal,
@@ -332,7 +406,7 @@ function createDiscordMessageDispatcher(params) {
332
406
  turnAdoptionLifecycle: admissionLifecycle
333
407
  });
334
408
  if (abortSignal?.aborted) {
335
- await ingress.abandon(abortSignal.reason);
409
+ await ingress.cancel();
336
410
  return;
337
411
  }
338
412
  if (!ctx) {
@@ -378,6 +452,7 @@ function createDiscordMessageDispatcher(params) {
378
452
  };
379
453
  const ctx = await (preflightDiscordMessageImpl ?? (await loadMessagePreflightRuntime()).preflightDiscordMessage)({
380
454
  ...params,
455
+ avatarResolver,
381
456
  ackReactionScope,
382
457
  groupPolicy,
383
458
  abortSignal,
@@ -386,7 +461,7 @@ function createDiscordMessageDispatcher(params) {
386
461
  turnAdoptionLifecycle: admissionLifecycle
387
462
  });
388
463
  if (abortSignal?.aborted) {
389
- await ingress.abandon(abortSignal.reason);
464
+ await ingress.cancel();
390
465
  return;
391
466
  }
392
467
  if (!ctx) {
@@ -403,7 +478,10 @@ function createDiscordMessageDispatcher(params) {
403
478
  }
404
479
  messageRunQueue.enqueue(buildDiscordInboundJob(ctx, { ingressSettlement: ingress }));
405
480
  } catch (error) {
406
- await admissionLifecycle.onAbandoned();
481
+ if (abortSignal?.aborted) {
482
+ await ingress.cancel();
483
+ return;
484
+ }
407
485
  throw error;
408
486
  }
409
487
  }
@@ -415,7 +493,7 @@ function createDiscordMessageDispatcher(params) {
415
493
  onCancel: (entries) => {
416
494
  for (const entry of entries) {
417
495
  pendingDebounceEntries.delete(entry);
418
- const settlement = Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned()).catch((error) => {
496
+ const settlement = fanInChannelIngressLifecycles([entry.turnAdoptionLifecycle]).cancel().catch((error) => {
419
497
  params.runtime.error(danger(`discord ingress cancellation settlement failed: ${String(error)}`));
420
498
  }).finally(() => {
421
499
  pendingCancellationSettlements.delete(settlement);
@@ -426,10 +504,17 @@ function createDiscordMessageDispatcher(params) {
426
504
  });
427
505
  const dispatchMessage = async (data, client, options) => {
428
506
  try {
429
- if (dispatcherShutdown.signal.aborted || options?.abortSignal?.aborted) return {
430
- kind: "failed-retryable",
431
- error: dispatcherShutdown.signal.aborted ? dispatcherShutdown.signal.reason ?? /* @__PURE__ */ new Error("discord dispatcher shut down") : options?.abortSignal?.reason ?? /* @__PURE__ */ new Error("discord dispatch aborted")
432
- };
507
+ if (dispatcherShutdown.signal.aborted || options?.abortSignal?.aborted) {
508
+ const reason = dispatcherShutdown.signal.aborted ? dispatcherShutdown.signal.reason ?? /* @__PURE__ */ new Error("discord dispatcher shut down") : options?.abortSignal?.reason ?? /* @__PURE__ */ new Error("discord dispatch aborted");
509
+ if (options?.turnAdoptionLifecycle) {
510
+ await fanInChannelIngressLifecycles([options.turnAdoptionLifecycle]).cancel();
511
+ return { kind: "deferred" };
512
+ }
513
+ return {
514
+ kind: "failed-retryable",
515
+ error: reason
516
+ };
517
+ }
433
518
  const msgAuthorId = data.message?.author?.id ?? data.author?.id;
434
519
  if (params.botUserId && msgAuthorId === params.botUserId) return { kind: "completed" };
435
520
  const entry = {