@openclaw/discord 2026.7.2-beta.2 → 2026.7.2-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 (85) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{activities-api-D0D_87n3.js → activities-api-DS1L7KRY.js} +15 -42
  3. package/dist/activities-api.js +1 -1
  4. package/dist/{allow-list-B7qOH5xu.js → allow-list-DzKrtfah.js} +18 -7
  5. package/dist/{api-B88T4rFN.js → api-C43z8vUm.js} +3 -1
  6. package/dist/api.js +25 -16
  7. package/dist/{approval-handler.runtime-B7E1-x5q.js → approval-handler.runtime-BbkfPoGc.js} +5 -6
  8. package/dist/{conversation-identity-BPcbhCAH.js → approval-message-safety-DrLlgIPz.js} +48 -2
  9. package/dist/{audit-CjpnY1yb.js → audit-BC6bgPMX.js} +3 -3
  10. package/dist/{channel-BDfKr14H.js → channel-5MfaC2IE.js} +18 -18
  11. package/dist/{channel-actions-BQct-o4T.js → channel-actions-DicWTbYH.js} +2 -2
  12. package/dist/{channel-actions.runtime-CC4QwYVO.js → channel-actions.runtime-D9Qadn-l.js} +8 -8
  13. package/dist/channel-config-api.js +1 -1
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/{channel.setup-BS4gd8sc.js → channel.setup-CJXfFclP.js} +3 -3
  16. package/dist/{chunk-CHpY_lsO.js → chunk-CWzmeWf_.js} +9 -49
  17. package/dist/{config-schema-CoDck1GW.js → config-schema-BxrgfSMD.js} +4 -0
  18. package/dist/{directory-config-G1QwBIdi.js → directory-config-CmukHSms.js} +1 -1
  19. package/dist/directory-contract-api.js +1 -1
  20. package/dist/{directory-live-nfvekMil.js → directory-live-BYlCRO__.js} +3 -3
  21. package/dist/{doctor-Bq4pJAQk.js → doctor-Ci7USQAW.js} +1 -1
  22. package/dist/{handle-action.guild-admin-LYY75_Oa.js → handle-action.guild-admin-Bsk2YBPs.js} +2 -2
  23. package/dist/index.js +2 -2
  24. package/dist/{manager.runtime-w6VVYQMl.js → manager.runtime-Cc8vS-M7.js} +98 -145
  25. package/dist/message-handler-CWJ-mQvH.js +590 -0
  26. package/dist/{message-handler.preflight-DnAH04Ss.js → message-handler.preflight-DK46_qbS.js} +16 -15
  27. package/dist/{message-handler.process-ClgKz8I5.js → message-handler.process-BK0mjFJH.js} +37 -61
  28. package/dist/{message-utils-BU3gc01v.js → message-utils-Cfe6CZ6N.js} +1 -1
  29. package/dist/{outbound-adapter-ByoXsR-V.js → outbound-adapter-B2_oa-zV.js} +36 -5
  30. package/dist/{outbound-components-7wAolLuS.js → outbound-components-BGhk6HI3.js} +3 -3
  31. package/dist/{outbound-session-route-D4QqePZN.js → outbound-session-route-M2bEY9tI.js} +36 -6
  32. package/dist/{pluralkit-r4wiI2yy.js → pluralkit-CT0VRsZd.js} +1 -1
  33. package/dist/{probe-WKeTYyJX.js → probe-C3SCKyYU.js} +1 -1
  34. package/dist/{probe.runtime-CRY-qUke.js → probe.runtime-D55QqC9u.js} +1 -1
  35. package/dist/{provider-PRlF_lZm.js → provider-HW0G-2l6.js} +868 -794
  36. package/dist/{provider-session.runtime-BZPUZRjn.js → provider-session.runtime-cCfGMsF_.js} +3 -3
  37. package/dist/provider.runtime-BWCRsVoT.js +2 -0
  38. package/dist/{resolve-allowlist-common-QN7VMZaC.js → resolve-allowlist-common-CJlqw_nC.js} +2 -2
  39. package/dist/{resolve-channels-BO9NUPlS.js → resolve-channels-9KcG3mLg.js} +4 -4
  40. package/dist/{resolve-users-CU3oG5-m.js → resolve-users-GCGf3oV0.js} +3 -3
  41. package/dist/{runtime-CCf5nkV3.js → runtime-CD5MRZkD.js} +1 -2
  42. package/dist/{runtime-BsefAQ9q.js → runtime-Z2qkKakn.js} +9 -8
  43. package/dist/runtime-api.actions.js +2 -2
  44. package/dist/runtime-api.js +23 -22
  45. package/dist/runtime-api.lookup.js +5 -5
  46. package/dist/runtime-api.monitor-C3EDYJqk.js +6 -0
  47. package/dist/runtime-api.monitor.js +8 -8
  48. package/dist/runtime-api.send.js +6 -5
  49. package/dist/runtime-api.threads.js +3 -3
  50. package/dist/{secret-config-contract-BDNrRWvd.js → secret-config-contract-B1akrlyY.js} +1 -0
  51. package/dist/secret-contract-api.js +1 -1
  52. package/dist/security-audit-contract-api.js +1 -1
  53. package/dist/{security-audit.runtime-K0nkzjzJ.js → security-audit.runtime-DEEywczv.js} +1 -1
  54. package/dist/{send-JJzVo_sK.js → send-CSRxNNgq.js} +7 -86
  55. package/dist/{send.components-CCRW54gK.js → send.components-DLqzOEPJ.js} +5 -4
  56. package/dist/{send.outbound-Cvulb-k_.js → send.outbound-C1SfX20w.js} +4 -3
  57. package/dist/send.reactions-Cj4ugX3F.js +85 -0
  58. package/dist/send.receipt-CuwRrpFz.js +48 -0
  59. package/dist/{send.shared-XuOTO1dW.js → send.shared-Dber6X57.js} +2 -2
  60. package/dist/{sender-identity-C62lBe18.js → sender-identity-mG_zsdox.js} +1 -1
  61. package/dist/session-binding-contract-api.js +1 -1
  62. package/dist/setup-plugin-api.js +1 -1
  63. package/dist/{shared-Bngr0I-v.js → shared-BEhmAdMY.js} +5 -5
  64. package/dist/{subagent-hooks-mSxRv4AE.js → subagent-hooks-Wrk61DwY.js} +2 -2
  65. package/dist/subagent-hooks-api.js +10 -1
  66. package/dist/subagent-progress-B2fpHe3m.js +662 -0
  67. package/dist/{system-events-BANBRAe3.js → system-events-AhfcKRRb.js} +2 -2
  68. package/dist/{target-resolver-BaEmTxeK.js → target-resolver-vqLA450R.js} +2 -2
  69. package/dist/targets-BjEMIe8t.js +3 -0
  70. package/dist/{thread-bindings-4MV-u8vU.js → thread-bindings-CJQoXuWY.js} +4 -4
  71. package/dist/{thread-bindings.discord-api-DXfXsHGZ.js → thread-bindings.discord-api-mNCWlgvM.js} +4 -4
  72. package/dist/{thread-bindings.manager-DJ4cO5Eu.js → thread-bindings.manager-D1z8mK4b.js} +3 -3
  73. package/dist/{timeouts-D5lWili0.js → timeouts-B9cy6RvQ.js} +1 -6
  74. package/dist/{transcripts-source-TDZcMriu.js → transcripts-source-DjDbt14x.js} +1 -1
  75. package/dist/transcripts-source-api.js +1 -1
  76. package/dist/{typing-CKd2NMWG.js → typing-wxtm1NlF.js} +2 -2
  77. package/npm-shrinkwrap.json +3 -3
  78. package/openclaw.plugin.json +10 -0
  79. package/package.json +4 -4
  80. package/dist/approval-message-safety-C-8BL6Kv.js +0 -47
  81. package/dist/message-handler-DExaghWK.js +0 -331
  82. package/dist/provider.runtime-BX8GcCYx.js +0 -2
  83. package/dist/runtime-api.monitor-DCS_ETft.js +0 -6
  84. package/dist/targets-fjJt3PYG.js +0 -3
  85. package/dist/{security-audit-aLbWmUjl.js → security-audit-DtT0SIns.js} +1 -1
@@ -0,0 +1,662 @@
1
+ import { s as resolveDiscordAccount } from "./accounts-CMd4nzLB.js";
2
+ import { a as sendTypingDiscord, i as removeReactionDiscord, n as reactMessageDiscord } from "./send.reactions-Cj4ugX3F.js";
3
+ import { normalizeOptionalLowercaseString, normalizeOptionalStringifiedId } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ import { DEFAULT_EMOJIS } from "openclaw/plugin-sdk/channel-feedback";
5
+ //#region extensions/discord/src/subagent-progress-config.ts
6
+ const RUNNING_EMOJIS = [
7
+ "1️⃣",
8
+ "2️⃣",
9
+ "3️⃣",
10
+ "4️⃣",
11
+ "5️⃣",
12
+ "6️⃣",
13
+ "7️⃣",
14
+ "8️⃣",
15
+ "9️⃣",
16
+ "🔟"
17
+ ];
18
+ function channelIdFromTarget(target) {
19
+ const trimmed = target?.trim();
20
+ if (!trimmed) return;
21
+ if (trimmed.startsWith("channel:")) return trimmed.slice(8).trim() || void 0;
22
+ return /^\d+$/u.test(trimmed) ? trimmed : void 0;
23
+ }
24
+ function resolveDiscordProgressTarget(requester) {
25
+ if (normalizeOptionalLowercaseString(requester?.channel) !== "discord") return;
26
+ const channelId = normalizeOptionalStringifiedId(requester?.channelId) ?? channelIdFromTarget(requester?.to);
27
+ const messageId = normalizeOptionalStringifiedId(requester?.messageId);
28
+ if (!channelId || !messageId) return;
29
+ return {
30
+ channelId,
31
+ messageId
32
+ };
33
+ }
34
+ function reservedReactionEmojis(config, ackReaction) {
35
+ const reserved = new Set(Object.values(DEFAULT_EMOJIS));
36
+ for (const emoji of Object.values(config.messages?.statusReactions?.emojis ?? {})) if (emoji?.trim()) reserved.add(emoji.trim());
37
+ for (const emoji of [config.messages?.ackReaction, ackReaction]) if (emoji?.trim()) reserved.add(emoji.trim());
38
+ for (const agent of config.agents?.list ?? []) {
39
+ const emoji = agent.identity?.emoji?.trim();
40
+ if (emoji) reserved.add(emoji);
41
+ }
42
+ return reserved;
43
+ }
44
+ function reactionsAreAvailable(config, ackReaction) {
45
+ const reserved = reservedReactionEmojis(config, ackReaction);
46
+ return !RUNNING_EMOJIS.some((emoji) => reserved.has(emoji)) && !reserved.has("🔴");
47
+ }
48
+ //#endregion
49
+ //#region extensions/discord/src/subagent-progress-state.ts
50
+ const PROGRESS_STORE_TTL_MS = 10080 * 6e4;
51
+ const MAX_TRACKED_RUNS = 4096;
52
+ const TERMINAL_TOMBSTONE_TTL_MS = 60 * 6e4;
53
+ function persistedTerminalOutcome(persisted) {
54
+ return persisted?.status === "cleanup" ? persisted.outcome : void 0;
55
+ }
56
+ let progressStores = /* @__PURE__ */ new WeakMap();
57
+ const trackerQueues = /* @__PURE__ */ new Map();
58
+ const terminalRuns = /* @__PURE__ */ new Map();
59
+ function markRunTerminal(runId, outcome) {
60
+ const now = Date.now();
61
+ for (const [trackedRunId, terminal] of terminalRuns) if (terminal.expiresAt <= now) terminalRuns.delete(trackedRunId);
62
+ terminalRuns.set(runId, {
63
+ expiresAt: now + TERMINAL_TOMBSTONE_TTL_MS,
64
+ outcome
65
+ });
66
+ if (terminalRuns.size > 4096) {
67
+ const oldest = terminalRuns.keys().next().value;
68
+ if (oldest) terminalRuns.delete(oldest);
69
+ }
70
+ }
71
+ function terminalOutcome(runId) {
72
+ const terminal = terminalRuns.get(runId);
73
+ if (!terminal) return;
74
+ if (terminal.expiresAt <= Date.now()) {
75
+ terminalRuns.delete(runId);
76
+ return;
77
+ }
78
+ return terminal.outcome;
79
+ }
80
+ function logFailure(api, action, error) {
81
+ const message = error instanceof Error ? error.message : String(error);
82
+ api.logger.debug?.(`discord subagent progress ${action} failed: ${message}`);
83
+ }
84
+ function getProgressStore(api) {
85
+ const cached = progressStores.get(api);
86
+ if (cached !== void 0) return cached ?? void 0;
87
+ if (!api.runtime?.state) {
88
+ progressStores.set(api, null);
89
+ return;
90
+ }
91
+ try {
92
+ const store = api.runtime.state.openKeyedStore({
93
+ namespace: "subagent-progress",
94
+ maxEntries: MAX_TRACKED_RUNS,
95
+ overflowPolicy: "reject-new",
96
+ defaultTtlMs: PROGRESS_STORE_TTL_MS
97
+ });
98
+ progressStores.set(api, store);
99
+ return store;
100
+ } catch (error) {
101
+ logFailure(api, "state store open", error);
102
+ return;
103
+ }
104
+ }
105
+ function persistedProgressRunFromTracker(tracker) {
106
+ return {
107
+ key: `${tracker.accountId}:${tracker.channelId}:${tracker.messageId}`,
108
+ accountId: tracker.accountId,
109
+ channelId: tracker.channelId,
110
+ messageId: tracker.messageId,
111
+ status: "active",
112
+ ...tracker.runningEmoji ? { runningEmoji: tracker.runningEmoji } : {}
113
+ };
114
+ }
115
+ async function persistProgressRun(api, runId, tracker) {
116
+ const store = getProgressStore(api);
117
+ if (!store) return "error";
118
+ const value = persistedProgressRunFromTracker(tracker);
119
+ try {
120
+ if (await store.registerIfAbsent(runId, value)) return "persisted";
121
+ const existing = await store.lookup(runId);
122
+ if (!existing) return "error";
123
+ if (existing.status === "cleanup") return "terminal";
124
+ return existing.key === value.key ? "persisted" : "conflict";
125
+ } catch (error) {
126
+ logFailure(api, "state store write", error);
127
+ return "error";
128
+ }
129
+ }
130
+ async function markProgressRunForCleanup(api, runId, persisted, outcome) {
131
+ try {
132
+ await getProgressStore(api)?.register(runId, {
133
+ ...persisted,
134
+ status: "cleanup",
135
+ outcome
136
+ });
137
+ return true;
138
+ } catch (error) {
139
+ logFailure(api, "state store cleanup mark", error);
140
+ return false;
141
+ }
142
+ }
143
+ async function lookupProgressRun(api, runId) {
144
+ const store = getProgressStore(api);
145
+ if (!store) return { status: "error" };
146
+ try {
147
+ const value = await store.lookup(runId);
148
+ return value ? {
149
+ status: "found",
150
+ value
151
+ } : { status: "missing" };
152
+ } catch (error) {
153
+ logFailure(api, "state store read", error);
154
+ return { status: "error" };
155
+ }
156
+ }
157
+ async function consumeProgressRun(api, runId) {
158
+ try {
159
+ return await getProgressStore(api)?.consume(runId);
160
+ } catch (error) {
161
+ logFailure(api, "state store consume", error);
162
+ return;
163
+ }
164
+ }
165
+ async function listProgressStateForKey(api, key) {
166
+ const store = getProgressStore(api);
167
+ if (!store) return { ok: false };
168
+ try {
169
+ const matching = (await store.entries()).filter((entry) => entry.value.key === key);
170
+ const cleanupRuns = matching.flatMap((entry) => entry.value.status === "cleanup" ? [{
171
+ runId: entry.key,
172
+ value: entry.value
173
+ }] : []);
174
+ return {
175
+ ok: true,
176
+ activeRunIds: matching.filter((entry) => entry.value.status === "active").map((entry) => entry.key),
177
+ cleanupRuns,
178
+ ownedEmojis: Array.from(new Set(matching.flatMap((entry) => entry.value.runningEmoji ?? [])))
179
+ };
180
+ } catch (error) {
181
+ logFailure(api, "state store list", error);
182
+ return { ok: false };
183
+ }
184
+ }
185
+ async function runQueued(key, task) {
186
+ const current = (trackerQueues.get(key) ?? Promise.resolve()).catch(() => void 0).then(task);
187
+ trackerQueues.set(key, current);
188
+ try {
189
+ await current;
190
+ } finally {
191
+ if (trackerQueues.get(key) === current) trackerQueues.delete(key);
192
+ }
193
+ }
194
+ function resetDiscordSubagentProgressStateForTest() {
195
+ trackerQueues.clear();
196
+ terminalRuns.clear();
197
+ progressStores = /* @__PURE__ */ new WeakMap();
198
+ }
199
+ //#endregion
200
+ //#region extensions/discord/src/subagent-progress.ts
201
+ const TYPING_INTERVAL_MS = 8500;
202
+ const TYPING_TTL_MS = 60 * 6e4;
203
+ const TERMINAL_LOOKUP_RETRY_MS = 1e3;
204
+ const TERMINAL_RETRY_MAX_DELAY_MS = 60 * 6e4;
205
+ const TERMINAL_RETRY_MAX_ATTEMPTS = 12;
206
+ const STARTUP_RETRY_MAX_ATTEMPTS = 12;
207
+ const trackers = /* @__PURE__ */ new Map();
208
+ const trackerKeyByRunId = /* @__PURE__ */ new Map();
209
+ const terminalRetryTimers = /* @__PURE__ */ new Map();
210
+ const terminalRetryExpiresAt = /* @__PURE__ */ new Map();
211
+ const terminalRetryAttempts = /* @__PURE__ */ new Map();
212
+ const startupRecoveryRetries = /* @__PURE__ */ new Map();
213
+ function clearTerminalRetry(runId) {
214
+ const timer = terminalRetryTimers.get(runId);
215
+ if (timer) clearTimeout(timer);
216
+ terminalRetryTimers.delete(runId);
217
+ terminalRetryExpiresAt.delete(runId);
218
+ terminalRetryAttempts.delete(runId);
219
+ }
220
+ function cancelTerminalRetryTimer(runId) {
221
+ const timer = terminalRetryTimers.get(runId);
222
+ if (timer) clearTimeout(timer);
223
+ terminalRetryTimers.delete(runId);
224
+ }
225
+ async function setReaction(api, tracker, emoji) {
226
+ try {
227
+ return (await reactMessageDiscord(tracker.channelId, tracker.messageId, emoji, {
228
+ cfg: api.config,
229
+ accountId: tracker.accountId
230
+ })).ok;
231
+ } catch (error) {
232
+ logFailure(api, "reaction add", error);
233
+ return false;
234
+ }
235
+ }
236
+ async function clearReaction(api, tracker, emoji) {
237
+ if (!emoji) return true;
238
+ try {
239
+ return (await removeReactionDiscord(tracker.channelId, tracker.messageId, emoji, {
240
+ cfg: api.config,
241
+ accountId: tracker.accountId
242
+ })).ok;
243
+ } catch (error) {
244
+ logFailure(api, "reaction remove", error);
245
+ return false;
246
+ }
247
+ }
248
+ async function clearRunningReactions(api, tracker, emojis) {
249
+ return (await Promise.all(emojis.map((emoji) => clearReaction(api, tracker, emoji)))).every(Boolean);
250
+ }
251
+ async function persistTrackerRunningEmoji(api, tracker) {
252
+ const store = getProgressStore(api);
253
+ if (!store) return false;
254
+ try {
255
+ await Promise.all(Array.from(tracker.persistedRunIds, (runId) => store.register(runId, persistedProgressRunFromTracker(tracker))));
256
+ return true;
257
+ } catch (error) {
258
+ logFailure(api, "reaction ownership write", error);
259
+ return false;
260
+ }
261
+ }
262
+ async function updateRunningReaction(api, tracker) {
263
+ if (!tracker.reactionsEnabled) return true;
264
+ const nextEmoji = tracker.activeRunIds.size > 0 ? RUNNING_EMOJIS[Math.min(tracker.activeRunIds.size, RUNNING_EMOJIS.length) - 1] : void 0;
265
+ if (nextEmoji === tracker.runningEmoji) {
266
+ if (!nextEmoji || tracker.runningEmojiConfirmed) return true;
267
+ if (!await persistTrackerRunningEmoji(api, tracker)) return false;
268
+ tracker.runningEmojiConfirmed = await setReaction(api, tracker, nextEmoji);
269
+ return tracker.runningEmojiConfirmed;
270
+ }
271
+ if (!await clearReaction(api, tracker, tracker.runningEmoji)) return false;
272
+ tracker.runningEmoji = void 0;
273
+ tracker.runningEmojiConfirmed = false;
274
+ if (nextEmoji) {
275
+ tracker.runningEmoji = nextEmoji;
276
+ if (!await persistTrackerRunningEmoji(api, tracker)) {
277
+ tracker.runningEmoji = void 0;
278
+ return false;
279
+ }
280
+ tracker.runningEmojiConfirmed = await setReaction(api, tracker, nextEmoji);
281
+ return tracker.runningEmojiConfirmed;
282
+ }
283
+ await persistTrackerRunningEmoji(api, tracker);
284
+ return true;
285
+ }
286
+ async function disableTrackerReactionsOnCollision(api, tracker, ackReaction) {
287
+ if (!tracker.reactionsEnabled || reactionsAreAvailable(api.config, ackReaction)) return true;
288
+ const reserved = reservedReactionEmojis(api.config, ackReaction);
289
+ if (tracker.runningEmoji && !reserved.has(tracker.runningEmoji)) {
290
+ if (!await clearReaction(api, tracker, tracker.runningEmoji)) {
291
+ tracker.reactionsEnabled = false;
292
+ return false;
293
+ }
294
+ }
295
+ tracker.runningEmoji = void 0;
296
+ tracker.runningEmojiConfirmed = false;
297
+ tracker.reactionsEnabled = false;
298
+ await persistTrackerRunningEmoji(api, tracker);
299
+ return true;
300
+ }
301
+ async function sendTyping(api, tracker) {
302
+ try {
303
+ await sendTypingDiscord(tracker.channelId, {
304
+ cfg: api.config,
305
+ accountId: tracker.accountId
306
+ });
307
+ } catch (error) {
308
+ logFailure(api, "typing", error);
309
+ }
310
+ }
311
+ function startTyping(api, tracker) {
312
+ tracker.typingExpiresAt = Date.now() + TYPING_TTL_MS;
313
+ sendTyping(api, tracker);
314
+ if (tracker.typingTimer) return;
315
+ tracker.typingTimer = setInterval(() => {
316
+ if (tracker.activeRunIds.size === 0 || Date.now() >= tracker.typingExpiresAt) {
317
+ stopTyping(tracker);
318
+ return;
319
+ }
320
+ sendTyping(api, tracker);
321
+ }, TYPING_INTERVAL_MS);
322
+ tracker.typingTimer.unref?.();
323
+ }
324
+ function stopTyping(tracker) {
325
+ if (tracker.typingTimer) {
326
+ clearInterval(tracker.typingTimer);
327
+ tracker.typingTimer = void 0;
328
+ }
329
+ }
330
+ async function handleStarted(api, event) {
331
+ const runId = event.runId.trim();
332
+ const target = resolveDiscordProgressTarget(event.requester);
333
+ if (!runId || !target || terminalOutcome(runId)) return;
334
+ const account = resolveDiscordAccount({
335
+ cfg: api.config,
336
+ accountId: event.requester?.accountId
337
+ });
338
+ const key = `${account.accountId}:${target.channelId}:${target.messageId}`;
339
+ if (!account.enabled || account.config.subagentProgress !== true) {
340
+ await runQueued(key, async () => {
341
+ const tracker = trackers.get(key);
342
+ if (!tracker) return;
343
+ stopTyping(tracker);
344
+ tracker.reactionsEnabled = false;
345
+ if (!account.enabled || !tracker.runningEmoji) return;
346
+ if (!reservedReactionEmojis(api.config, account.config.ackReaction).has(tracker.runningEmoji) && await clearReaction(api, tracker, tracker.runningEmoji)) {
347
+ tracker.runningEmoji = void 0;
348
+ tracker.runningEmojiConfirmed = false;
349
+ await persistTrackerRunningEmoji(api, tracker);
350
+ }
351
+ });
352
+ return;
353
+ }
354
+ await runQueued(key, async () => {
355
+ let tracker = trackers.get(key);
356
+ let restoredCurrentRunWasTerminal = false;
357
+ if (!tracker) {
358
+ const reactionsEnabled = reactionsAreAvailable(api.config, account.config.ackReaction);
359
+ const restored = await listProgressStateForKey(api, key);
360
+ if (!restored.ok) return;
361
+ restoredCurrentRunWasTerminal = restored.cleanupRuns.some((cleanup) => cleanup.runId === runId);
362
+ tracker = {
363
+ accountId: account.accountId,
364
+ channelId: target.channelId,
365
+ messageId: target.messageId,
366
+ activeRunIds: new Set(restored.activeRunIds),
367
+ persistedRunIds: new Set(restored.activeRunIds),
368
+ runningEmojiConfirmed: false,
369
+ reactionsEnabled,
370
+ typingExpiresAt: 0
371
+ };
372
+ if (restored.activeRunIds.length > 0 || restored.cleanupRuns.length > 0) {
373
+ const reserved = reservedReactionEmojis(api.config, account.config.ackReaction);
374
+ const cleanupEmojis = restored.ownedEmojis.filter((emoji) => !reserved.has(emoji));
375
+ const countsCleared = await clearRunningReactions(api, tracker, cleanupEmojis);
376
+ const failurePresented = restored.cleanupRuns.filter((cleanup) => cleanup.value.outcome !== "ok").length === 0 || !reactionsEnabled || countsCleared && await setReaction(api, tracker, "🔴");
377
+ if (countsCleared && failurePresented) {
378
+ for (const cleanup of restored.cleanupRuns) markRunTerminal(cleanup.runId, cleanup.value.outcome);
379
+ await Promise.all(restored.cleanupRuns.map((cleanup) => consumeProgressRun(api, cleanup.runId)));
380
+ } else {
381
+ tracker.reactionsEnabled = false;
382
+ for (const cleanup of restored.cleanupRuns) scheduleTerminalLookupRetry(api, {
383
+ phase: "ended",
384
+ runId: cleanup.runId,
385
+ outcome: cleanup.value.outcome
386
+ }, cleanup.value);
387
+ }
388
+ }
389
+ trackers.set(key, tracker);
390
+ }
391
+ if (!await disableTrackerReactionsOnCollision(api, tracker, account.config.ackReaction)) return;
392
+ if (restoredCurrentRunWasTerminal) {
393
+ if (tracker.activeRunIds.size > 0) {
394
+ await updateRunningReaction(api, tracker);
395
+ startTyping(api, tracker);
396
+ } else trackers.delete(key);
397
+ return;
398
+ }
399
+ if (tracker.activeRunIds.has(runId)) {
400
+ trackerKeyByRunId.set(runId, key);
401
+ await updateRunningReaction(api, tracker);
402
+ startTyping(api, tracker);
403
+ return;
404
+ }
405
+ let persistResult = "error";
406
+ if (tracker.reactionsEnabled) {
407
+ persistResult = await persistProgressRun(api, runId, tracker);
408
+ if (persistResult === "terminal") {
409
+ markRunTerminal(runId, "unknown");
410
+ return;
411
+ }
412
+ if (persistResult === "conflict") {
413
+ api.logger.debug?.(`discord subagent progress ignored conflicting run id: ${runId}`);
414
+ return;
415
+ }
416
+ if (persistResult === "error") {
417
+ await clearReaction(api, tracker, tracker.runningEmoji);
418
+ tracker.runningEmoji = void 0;
419
+ tracker.runningEmojiConfirmed = false;
420
+ tracker.reactionsEnabled = false;
421
+ }
422
+ }
423
+ tracker.activeRunIds.add(runId);
424
+ trackerKeyByRunId.set(runId, key);
425
+ if (persistResult === "persisted") tracker.persistedRunIds.add(runId);
426
+ const endedOutcome = terminalOutcome(runId);
427
+ if (endedOutcome) {
428
+ const owned = persistedProgressRunFromTracker(tracker);
429
+ await markProgressRunForCleanup(api, runId, owned, endedOutcome);
430
+ if (endedOutcome === "ok" || !tracker.reactionsEnabled || await setReaction(api, tracker, "🔴")) await consumeProgressRun(api, runId);
431
+ else scheduleTerminalLookupRetry(api, {
432
+ phase: "ended",
433
+ runId,
434
+ outcome: endedOutcome,
435
+ requester: event.requester
436
+ }, {
437
+ ...owned,
438
+ status: "cleanup",
439
+ outcome: endedOutcome
440
+ });
441
+ tracker.activeRunIds.delete(runId);
442
+ tracker.persistedRunIds.delete(runId);
443
+ trackerKeyByRunId.delete(runId);
444
+ await updateRunningReaction(api, tracker);
445
+ if (tracker.activeRunIds.size === 0) {
446
+ stopTyping(tracker);
447
+ trackers.delete(key);
448
+ }
449
+ return;
450
+ }
451
+ await updateRunningReaction(api, tracker);
452
+ startTyping(api, tracker);
453
+ });
454
+ }
455
+ async function reconcilePersistedTracker(api, persisted, outcome, endingRunId) {
456
+ const store = getProgressStore(api);
457
+ let activeRunIds = [];
458
+ if (store) try {
459
+ activeRunIds = (await store.entries()).filter((entry) => entry.key !== endingRunId && entry.value.key === persisted.key && entry.value.status === "active").map((entry) => entry.key);
460
+ } catch (error) {
461
+ logFailure(api, "state store list", error);
462
+ return { ok: false };
463
+ }
464
+ const tracker = {
465
+ accountId: persisted.accountId,
466
+ channelId: persisted.channelId,
467
+ messageId: persisted.messageId,
468
+ activeRunIds: new Set(activeRunIds),
469
+ persistedRunIds: new Set(activeRunIds),
470
+ runningEmojiConfirmed: false,
471
+ reactionsEnabled: true,
472
+ typingExpiresAt: 0
473
+ };
474
+ const account = resolveDiscordAccount({
475
+ cfg: api.config,
476
+ accountId: persisted.accountId
477
+ });
478
+ const typingEnabled = account.enabled && account.config.subagentProgress === true;
479
+ const reserved = reservedReactionEmojis(api.config, account.config.ackReaction);
480
+ const cleanupEmojis = persisted.runningEmoji && !reserved.has(persisted.runningEmoji) ? [persisted.runningEmoji] : [];
481
+ const reactionsEnabled = typingEnabled && reactionsAreAvailable(api.config, account.config.ackReaction);
482
+ const reactionsCleared = account.enabled && await clearRunningReactions(api, tracker, cleanupEmojis);
483
+ const nextEmoji = RUNNING_EMOJIS[Math.min(activeRunIds.length, RUNNING_EMOJIS.length) - 1];
484
+ let countPresented = true;
485
+ if (reactionsEnabled && reactionsCleared && nextEmoji) {
486
+ tracker.runningEmoji = nextEmoji;
487
+ countPresented = await persistTrackerRunningEmoji(api, tracker) && await setReaction(api, tracker, nextEmoji);
488
+ tracker.runningEmojiConfirmed = countPresented;
489
+ }
490
+ const outcomePresented = outcome === "ok" || !reactionsEnabled || reactionsCleared && countPresented && await setReaction(api, tracker, "🔴");
491
+ if (!reactionsCleared || !countPresented || !outcomePresented) return { ok: false };
492
+ return {
493
+ ok: true,
494
+ activeRunIds,
495
+ reactionsEnabled,
496
+ typingEnabled,
497
+ ...reactionsEnabled && nextEmoji ? { runningEmoji: nextEmoji } : {}
498
+ };
499
+ }
500
+ function scheduleTerminalLookupRetry(api, event, owned) {
501
+ const runId = event.runId.trim();
502
+ if (!runId || terminalRetryTimers.has(runId)) return;
503
+ if (!owned) {
504
+ const target = resolveDiscordProgressTarget(event.requester);
505
+ const account = resolveDiscordAccount({
506
+ cfg: api.config,
507
+ accountId: event.requester?.accountId
508
+ });
509
+ if (!target || !account.enabled || account.config.subagentProgress !== true) return;
510
+ }
511
+ if (terminalRetryTimers.size >= 4096) return;
512
+ const expiresAt = terminalRetryExpiresAt.get(runId) ?? Date.now() + 6048e5;
513
+ const attempts = terminalRetryAttempts.get(runId) ?? 0;
514
+ if (expiresAt <= Date.now() || attempts >= TERMINAL_RETRY_MAX_ATTEMPTS) {
515
+ clearTerminalRetry(runId);
516
+ return;
517
+ }
518
+ terminalRetryExpiresAt.set(runId, expiresAt);
519
+ terminalRetryAttempts.set(runId, attempts + 1);
520
+ const retryDelayMs = Math.min(TERMINAL_LOOKUP_RETRY_MS * 2 ** Math.min(attempts, 12), TERMINAL_RETRY_MAX_DELAY_MS);
521
+ const timer = setTimeout(() => {
522
+ terminalRetryTimers.delete(runId);
523
+ handleEnded(api, event, owned);
524
+ }, retryDelayMs);
525
+ timer.unref?.();
526
+ terminalRetryTimers.set(runId, timer);
527
+ }
528
+ async function handleEnded(api, event, persistedHint) {
529
+ const runId = event.runId.trim();
530
+ if (!runId) return;
531
+ markRunTerminal(runId, event.outcome);
532
+ const lookup = await lookupProgressRun(api, runId);
533
+ const persisted = lookup.status === "found" ? lookup.value : persistedHint;
534
+ const key = trackerKeyByRunId.get(runId) ?? persisted?.key;
535
+ if (!key) {
536
+ if (lookup.status === "error") scheduleTerminalLookupRetry(api, event);
537
+ else clearTerminalRetry(runId);
538
+ return;
539
+ }
540
+ cancelTerminalRetryTimer(runId);
541
+ await runQueued(key, async () => {
542
+ const tracker = trackers.get(key);
543
+ const currentLookup = await lookupProgressRun(api, runId);
544
+ const currentPersisted = currentLookup.status === "found" ? currentLookup.value : currentLookup.status === "error" ? persistedHint ?? persisted : persistedHint ?? (tracker ? void 0 : persisted);
545
+ const outcome = persistedTerminalOutcome(currentPersisted) ?? persistedTerminalOutcome(persisted) ?? event.outcome;
546
+ const retryEvent = outcome === event.outcome ? event : {
547
+ ...event,
548
+ outcome
549
+ };
550
+ trackerKeyByRunId.delete(runId);
551
+ const owned = tracker?.persistedRunIds.has(runId) && currentPersisted?.status !== "cleanup" ? persistedProgressRunFromTracker(tracker) : currentPersisted;
552
+ const cleanupMarked = owned ? owned.status === "cleanup" || await markProgressRunForCleanup(api, runId, owned, outcome) : true;
553
+ if (tracker) {
554
+ const currentAccount = resolveDiscordAccount({
555
+ cfg: api.config,
556
+ accountId: tracker.accountId
557
+ });
558
+ if (!currentAccount.enabled || currentAccount.config.subagentProgress !== true) {
559
+ tracker.reactionsEnabled = false;
560
+ stopTyping(tracker);
561
+ } else await disableTrackerReactionsOnCollision(api, tracker, currentAccount.config.ackReaction);
562
+ }
563
+ if (!tracker) {
564
+ const reconciliation = owned ? await reconcilePersistedTracker(api, owned, outcome, runId) : { ok: false };
565
+ if (reconciliation.ok && owned) {
566
+ if (!await consumeProgressRun(api, runId)) scheduleTerminalLookupRetry(api, retryEvent, owned);
567
+ else clearTerminalRetry(runId);
568
+ if (reconciliation.typingEnabled && reconciliation.activeRunIds.length > 0) {
569
+ const restoredTracker = {
570
+ accountId: owned.accountId,
571
+ channelId: owned.channelId,
572
+ messageId: owned.messageId,
573
+ activeRunIds: new Set(reconciliation.activeRunIds),
574
+ persistedRunIds: new Set(reconciliation.activeRunIds),
575
+ runningEmojiConfirmed: Boolean(reconciliation.runningEmoji),
576
+ reactionsEnabled: reconciliation.reactionsEnabled,
577
+ ...reconciliation.runningEmoji ? { runningEmoji: reconciliation.runningEmoji } : {},
578
+ typingExpiresAt: 0
579
+ };
580
+ trackers.set(key, restoredTracker);
581
+ for (const activeRunId of reconciliation.activeRunIds) trackerKeyByRunId.set(activeRunId, key);
582
+ startTyping(api, restoredTracker);
583
+ }
584
+ } else if (owned) scheduleTerminalLookupRetry(api, retryEvent, owned);
585
+ return;
586
+ }
587
+ tracker.activeRunIds.delete(runId);
588
+ tracker.persistedRunIds.delete(runId);
589
+ const countReconciled = tracker.reactionsEnabled ? await updateRunningReaction(api, tracker) : owned ? (await reconcilePersistedTracker(api, owned, outcome, runId)).ok : true;
590
+ const outcomePresented = outcome === "ok" || !tracker.reactionsEnabled || countReconciled && await setReaction(api, tracker, "🔴");
591
+ if (countReconciled && outcomePresented && owned) {
592
+ const consumed = await consumeProgressRun(api, runId);
593
+ if (!consumed) scheduleTerminalLookupRetry(api, retryEvent, owned);
594
+ else clearTerminalRetry(runId);
595
+ if (!consumed && !cleanupMarked) await markProgressRunForCleanup(api, runId, owned, outcome);
596
+ } else if (owned) scheduleTerminalLookupRetry(api, retryEvent, owned);
597
+ if (tracker.activeRunIds.size === 0) {
598
+ stopTyping(tracker);
599
+ trackers.delete(key);
600
+ }
601
+ });
602
+ }
603
+ async function handleDiscordSubagentProgressImpl(api, event) {
604
+ if (event.phase === "started") {
605
+ await handleStarted(api, event);
606
+ return;
607
+ }
608
+ await handleEnded(api, event);
609
+ }
610
+ function clearStartupRecoveryRetry(api) {
611
+ const retry = startupRecoveryRetries.get(api);
612
+ if (retry?.timer) clearTimeout(retry.timer);
613
+ startupRecoveryRetries.delete(api);
614
+ }
615
+ function scheduleStartupRecoveryRetry(api) {
616
+ const retry = startupRecoveryRetries.get(api) ?? { attempts: 0 };
617
+ if (retry.timer || retry.attempts >= STARTUP_RETRY_MAX_ATTEMPTS) return;
618
+ const delayMs = Math.min(TERMINAL_LOOKUP_RETRY_MS * 2 ** retry.attempts, TERMINAL_RETRY_MAX_DELAY_MS);
619
+ retry.attempts += 1;
620
+ retry.timer = setTimeout(() => {
621
+ retry.timer = void 0;
622
+ recoverDiscordSubagentProgress(api);
623
+ }, delayMs);
624
+ retry.timer.unref?.();
625
+ startupRecoveryRetries.set(api, retry);
626
+ }
627
+ async function recoverDiscordSubagentProgress(api) {
628
+ const store = getProgressStore(api);
629
+ if (!store) {
630
+ if (api.runtime?.state) scheduleStartupRecoveryRetry(api);
631
+ return;
632
+ }
633
+ let persistedRuns;
634
+ try {
635
+ persistedRuns = await store.entries();
636
+ } catch (error) {
637
+ logFailure(api, "startup recovery list", error);
638
+ scheduleStartupRecoveryRetry(api);
639
+ return;
640
+ }
641
+ clearStartupRecoveryRetry(api);
642
+ for (const entry of persistedRuns) await handleEnded(api, {
643
+ phase: "ended",
644
+ runId: entry.key,
645
+ outcome: persistedTerminalOutcome(entry.value) ?? "unknown"
646
+ }, entry.value);
647
+ }
648
+ function resetDiscordSubagentProgressForTest() {
649
+ for (const tracker of trackers.values()) stopTyping(tracker);
650
+ trackers.clear();
651
+ trackerKeyByRunId.clear();
652
+ for (const timer of terminalRetryTimers.values()) clearTimeout(timer);
653
+ terminalRetryTimers.clear();
654
+ terminalRetryExpiresAt.clear();
655
+ terminalRetryAttempts.clear();
656
+ for (const retry of startupRecoveryRetries.values()) if (retry.timer) clearTimeout(retry.timer);
657
+ startupRecoveryRetries.clear();
658
+ resetDiscordSubagentProgressStateForTest();
659
+ }
660
+ const handleDiscordSubagentProgress = Object.assign(handleDiscordSubagentProgressImpl, { resetForTest: resetDiscordSubagentProgressForTest });
661
+ //#endregion
662
+ export { handleDiscordSubagentProgress, recoverDiscordSubagentProgress };
@@ -1,5 +1,5 @@
1
- import { p as discord_exports } from "./chunk-CHpY_lsO.js";
2
- import { x as formatDiscordUserTag } from "./allow-list-B7qOH5xu.js";
1
+ import { d as discord_exports } from "./chunk-CWzmeWf_.js";
2
+ import { x as formatDiscordUserTag } from "./allow-list-DzKrtfah.js";
3
3
  //#region extensions/discord/src/monitor/system-events.ts
4
4
  function resolveDiscordSystemEvent(message, location) {
5
5
  switch (message.type) {
@@ -1,6 +1,6 @@
1
- import { Zt as __exportAll, d as parseDiscordTarget, s as allowFromContainsDiscordUserId } from "./chunk-CHpY_lsO.js";
1
+ import { Yt as __exportAll, a as allowFromContainsDiscordUserId, l as parseDiscordTarget } from "./chunk-CWzmeWf_.js";
2
2
  import { c as resolveDiscordAccountAllowFrom, s as resolveDiscordAccount } from "./accounts-CMd4nzLB.js";
3
- import { i as rememberDiscordDirectoryUser, r as listDiscordDirectoryPeersLive } from "./directory-live-nfvekMil.js";
3
+ import { i as rememberDiscordDirectoryUser, r as listDiscordDirectoryPeersLive } from "./directory-live-BYlCRO__.js";
4
4
  import { buildMessagingTarget } from "openclaw/plugin-sdk/channel-targets";
5
5
  //#region extensions/discord/src/send-target-parsing.ts
6
6
  const parseDiscordSendTarget = (raw, options = {}) => parseDiscordTarget(raw, options);
@@ -0,0 +1,3 @@
1
+ import "./chunk-CWzmeWf_.js";
2
+ import "./target-resolver-vqLA450R.js";
3
+ export {};