@openclaw/discord 2026.5.28 → 2026.5.30-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-inspect-C4sJZNbI.js → account-inspect-BkL-fm-M.js} +7 -5
- package/dist/account-inspect-api.js +1 -1
- package/dist/action-runtime-api.js +1 -1
- package/dist/api.js +14 -14
- package/dist/{approval-handler.runtime-CN_E7nkQ.js → approval-handler.runtime-BFLqemq4.js} +4 -4
- package/dist/{audit-BFCBp74H.js → audit-CKzCd_nb.js} +4 -4
- package/dist/{channel-COXb22wL.js → channel-DnUKVkVn.js} +16 -16
- package/dist/{channel-actions-C2g9OmnG.js → channel-actions-D60aLg8q.js} +3 -3
- package/dist/{channel-actions.runtime-drnbrgpt.js → channel-actions.runtime-D6awrVWB.js} +5 -5
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-BsrP5-hv.js → channel.setup-Cqfoar5h.js} +6 -20
- package/dist/{components-CjQujWa1.js → components-CWgTTA0J.js} +1 -1
- package/dist/{config-schema-3tZYNVTM.js → config-schema-C1lzB2Ld.js} +1 -1
- package/dist/contract-api.js +2 -2
- package/dist/{conversation-identity-CsPV1Gjx.js → conversation-identity-Dvi-8LyR.js} +2 -2
- package/dist/{directory-config-BqPvRoDv.js → directory-config-B-i4XzWZ.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-CCI0IMZq.js → directory-live-B5E-IbgC.js} +1 -1
- package/dist/{doctor-Dy7sI1sL.js → doctor-DT41iqQA.js} +2 -2
- package/dist/{handle-action.guild-admin-BmjclQbR.js → handle-action.guild-admin-B39nAL5c.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/legacy-state-migrations-api.js +3 -1
- package/dist/{manager.runtime-ClhnLsZf.js → manager.runtime-ClHe315Q.js} +16 -8
- package/dist/{message-handler-BnhAHqem.js → message-handler-Deidymb-.js} +182 -29
- package/dist/{message-handler.preflight-B1RTQ6ZO.js → message-handler.preflight-DeTwc2nk.js} +11 -11
- package/dist/{message-handler.process-B1ODKav3.js → message-handler.process-DJkFWirK.js} +30 -28
- package/dist/{message-utils-CmB03FoF.js → message-utils-C4OlktSy.js} +19 -15
- package/dist/{outbound-adapter-DfY2Dtf3.js → outbound-adapter-DCC3zPuC.js} +51 -86
- package/dist/{pluralkit-DAZN3ZMK.js → pluralkit-Dhad974D.js} +1 -1
- package/dist/{provider-DCcqVFp-.js → provider-SeIwaTTJ.js} +127 -133
- package/dist/{provider-session.runtime-mKSG07RE.js → provider-session.runtime-B5aLy8NA.js} +3 -3
- package/dist/provider.runtime-DVHYE9ZI.js +2 -0
- package/dist/{resolve-channels-D1zgoxnw.js → resolve-channels-LMOlvXSw.js} +1 -1
- package/dist/{resolve-users-wPAvLk_8.js → resolve-users-WNAKeCd_.js} +1 -1
- package/dist/{runtime-gaB5-Wsj.js → runtime-Dg0INaGm.js} +7 -7
- package/dist/runtime-api.actions.js +2 -2
- package/dist/runtime-api.js +21 -21
- package/dist/runtime-api.lookup.js +4 -4
- package/dist/runtime-api.monitor-C56AP2ZG.js +6 -0
- package/dist/runtime-api.monitor.js +5 -5
- package/dist/runtime-api.send.js +5 -5
- package/dist/runtime-api.threads.js +5 -5
- package/dist/{send-CBxhnbKU.js → send-DwUf-fzH.js} +6 -6
- package/dist/{send.components-uuQuHL3t.js → send.components-BVfuI0-G.js} +25 -11
- package/dist/{send.outbound-DZHJYmNF.js → send.outbound-4IJRwYbJ.js} +3 -3
- package/dist/{send.receipt-3y2lVFRf.js → send.receipt-CxFJX9o3.js} +22 -10
- package/dist/{send.shared-Ddl21-Gt.js → send.shared-DQwyGjXe.js} +2 -2
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{shared-D3NkInmq.js → shared-D-ePFtjZ.js} +3 -3
- package/dist/{subagent-hooks-BacIbIBD.js → subagent-hooks-CqhiwYpb.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -5
- package/dist/{system-events-Cw6Hp8RF.js → system-events-DZdHRt2j.js} +1 -1
- package/dist/{target-resolver-jjQzAS6T.js → target-resolver-DTD90GqZ.js} +2 -2
- package/dist/targets-xBATI2q7.js +3 -0
- package/dist/{thread-bindings-BBq-yzq2.js → thread-bindings-VrlFUPDW.js} +6 -6
- package/dist/{thread-bindings.discord-api-5QgEd_dW.js → thread-bindings.discord-api-CItgvv24.js} +5 -5
- package/dist/{thread-bindings.manager-COkxjwR9.js → thread-bindings.manager-hhqHJlGr.js} +4 -4
- package/dist/{thread-bindings.session-updates-D5gY2ZTE.js → thread-bindings.session-updates-0BaqAU8S.js} +1 -1
- package/dist/{thread-bindings.state-BsOnj5NX.js → thread-bindings.state-B-iprgEM.js} +8 -2
- package/dist/{timeouts-l_PsHQvX.js → timeouts-CEJ3--Uw.js} +5 -2
- package/dist/timeouts.js +1 -1
- package/dist/{transcripts-source-DDrS8HrV.js → transcripts-source-CFsqnHc4.js} +1 -1
- package/dist/transcripts-source-api.js +1 -1
- package/dist/{typing-B7bCMPJJ.js → typing-Bfns8H17.js} +2 -2
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
- package/dist/provider.runtime-BmpunJHX.js +0 -2
- package/dist/runtime-api.monitor-B38upMUK.js +0 -5
- package/dist/targets-BVKF5wbg.js +0 -3
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { I as createDiscordRestClient } from "./send.shared-
|
|
2
|
-
import { d as resolveDiscordChannelNameSafe, l as resolveDiscordChannelIdSafe, p as resolveDiscordChannelParentSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-
|
|
3
|
-
import { a as mergeAbortSignals } from "./timeouts-
|
|
4
|
-
import { c as hasDiscordMessageStickers, d as resolveDiscordMessageChannelId, r as resolveDiscordMessageText } from "./message-utils-
|
|
5
|
-
import { t as sendTyping } from "./typing-
|
|
1
|
+
import { I as createDiscordRestClient } from "./send.shared-DQwyGjXe.js";
|
|
2
|
+
import { d as resolveDiscordChannelNameSafe, l as resolveDiscordChannelIdSafe, p as resolveDiscordChannelParentSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.js";
|
|
3
|
+
import { a as mergeAbortSignals } from "./timeouts-CEJ3--Uw.js";
|
|
4
|
+
import { c as hasDiscordMessageStickers, d as resolveDiscordMessageChannelId, r as resolveDiscordMessageText } from "./message-utils-C4OlktSy.js";
|
|
5
|
+
import { t as sendTyping } from "./typing-Bfns8H17.js";
|
|
6
6
|
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
7
|
-
import { createChannelRunQueue } from "openclaw/plugin-sdk/channel-outbound";
|
|
7
|
+
import { createChannelRunQueue, createTypingCallbacks, resolveChannelMessageSourceReplyDeliveryMode } from "openclaw/plugin-sdk/channel-outbound";
|
|
8
8
|
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
9
9
|
import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
|
|
10
10
|
import { createChannelInboundDebouncer, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
|
|
11
11
|
import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
12
|
+
import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
|
|
12
13
|
//#region extensions/discord/src/monitor/inbound-dedupe.ts
|
|
13
14
|
const RECENT_DISCORD_MESSAGE_TTL_MS = 5 * 6e4;
|
|
14
15
|
const RECENT_DISCORD_MESSAGE_MAX = 5e3;
|
|
@@ -59,7 +60,7 @@ function resolveDiscordInboundJobQueueKey(ctx) {
|
|
|
59
60
|
return ctx.messageChannelId;
|
|
60
61
|
}
|
|
61
62
|
function buildDiscordInboundJob(ctx, options) {
|
|
62
|
-
const { runtime, abortSignal, guildHistories, client, threadBindings, discordRestFetch, message, data, threadChannel, ...payload } = ctx;
|
|
63
|
+
const { runtime, abortSignal, guildHistories, client, threadBindings, replyTypingFeedback, discordRestFetch, message, data, threadChannel, ...payload } = ctx;
|
|
63
64
|
const sanitizedMessage = sanitizeDiscordInboundMessage(message);
|
|
64
65
|
return {
|
|
65
66
|
queueKey: resolveDiscordInboundJobQueueKey(ctx),
|
|
@@ -78,6 +79,7 @@ function buildDiscordInboundJob(ctx, options) {
|
|
|
78
79
|
guildHistories,
|
|
79
80
|
client,
|
|
80
81
|
threadBindings,
|
|
82
|
+
replyTypingFeedback,
|
|
81
83
|
discordRestFetch
|
|
82
84
|
},
|
|
83
85
|
replayKeys: options?.replayKeys ? [...options.replayKeys] : void 0
|
|
@@ -118,10 +120,65 @@ function applyImplicitReplyBatchGate(ctx, replyToMode, isBatched) {
|
|
|
118
120
|
ctx.ReplyThreading = replyThreading;
|
|
119
121
|
}
|
|
120
122
|
//#endregion
|
|
123
|
+
//#region extensions/discord/src/monitor/message-handler.reply-typing-policy.ts
|
|
124
|
+
function resolveDiscordSourceReplyDeliveryMode(ctx) {
|
|
125
|
+
return resolveChannelMessageSourceReplyDeliveryMode({
|
|
126
|
+
cfg: ctx.cfg,
|
|
127
|
+
ctx: {
|
|
128
|
+
ChatType: ctx.isDirectMessage ? "direct" : ctx.isGroupDm ? "group" : ctx.isGuildMessage ? "channel" : void 0,
|
|
129
|
+
InboundEventKind: ctx.inboundEventKind
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function resolveDiscordAcceptedTypingPrestart(ctx) {
|
|
134
|
+
const sourceReplyDeliveryMode = resolveDiscordSourceReplyDeliveryMode(ctx);
|
|
135
|
+
if (ctx.abortSignal?.aborted) return {
|
|
136
|
+
sourceReplyDeliveryMode,
|
|
137
|
+
shouldPrestart: false,
|
|
138
|
+
reason: "aborted"
|
|
139
|
+
};
|
|
140
|
+
if (!ctx.messageText.trim()) return {
|
|
141
|
+
sourceReplyDeliveryMode,
|
|
142
|
+
shouldPrestart: false,
|
|
143
|
+
reason: "empty"
|
|
144
|
+
};
|
|
145
|
+
if (ctx.inboundEventKind === "room_event") return {
|
|
146
|
+
sourceReplyDeliveryMode,
|
|
147
|
+
shouldPrestart: false,
|
|
148
|
+
reason: "room-event"
|
|
149
|
+
};
|
|
150
|
+
const configuredTypingMode = ctx.cfg.session?.typingMode ?? ctx.cfg.agents?.defaults?.typingMode;
|
|
151
|
+
if (configuredTypingMode !== void 0) return {
|
|
152
|
+
sourceReplyDeliveryMode,
|
|
153
|
+
shouldPrestart: configuredTypingMode === "instant",
|
|
154
|
+
reason: configuredTypingMode === "instant" ? "configured-instant" : "configured-not-instant"
|
|
155
|
+
};
|
|
156
|
+
if (sourceReplyDeliveryMode === "message_tool_only") return {
|
|
157
|
+
sourceReplyDeliveryMode,
|
|
158
|
+
shouldPrestart: true,
|
|
159
|
+
reason: "tool-only"
|
|
160
|
+
};
|
|
161
|
+
if (!ctx.isGuildMessage && !ctx.isGroupDm) return {
|
|
162
|
+
sourceReplyDeliveryMode,
|
|
163
|
+
shouldPrestart: true,
|
|
164
|
+
reason: "direct"
|
|
165
|
+
};
|
|
166
|
+
if (ctx.effectiveWasMentioned) return {
|
|
167
|
+
sourceReplyDeliveryMode,
|
|
168
|
+
shouldPrestart: true,
|
|
169
|
+
reason: "mentioned-group"
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
sourceReplyDeliveryMode,
|
|
173
|
+
shouldPrestart: false,
|
|
174
|
+
reason: "defer-to-message"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//#endregion
|
|
121
178
|
//#region extensions/discord/src/monitor/message-run-queue.ts
|
|
122
179
|
let messageProcessRuntimePromise;
|
|
123
180
|
async function loadMessageProcessRuntime() {
|
|
124
|
-
messageProcessRuntimePromise ??= import("./message-handler.process-
|
|
181
|
+
messageProcessRuntimePromise ??= import("./message-handler.process-DJkFWirK.js");
|
|
125
182
|
return await messageProcessRuntimePromise;
|
|
126
183
|
}
|
|
127
184
|
async function processDiscordQueuedMessage(params) {
|
|
@@ -146,8 +203,20 @@ async function processDiscordQueuedMessage(params) {
|
|
|
146
203
|
throw error;
|
|
147
204
|
}
|
|
148
205
|
}
|
|
206
|
+
function cleanupSkippedDiscordQueuedMessage(params) {
|
|
207
|
+
try {
|
|
208
|
+
params.job.runtime.replyTypingFeedback?.onCleanup?.();
|
|
209
|
+
} finally {
|
|
210
|
+
releaseDiscordInboundReplay({
|
|
211
|
+
replayKeys: params.job.replayKeys,
|
|
212
|
+
error: new DiscordRetryableInboundError("discord queued run skipped before processing"),
|
|
213
|
+
replayGuard: params.replayGuard
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
149
217
|
function createDiscordMessageRunQueue(params) {
|
|
150
218
|
const replayGuard = params.replayGuard ?? createDiscordInboundReplayGuard();
|
|
219
|
+
const skippedCleanup = /* @__PURE__ */ new Set();
|
|
151
220
|
const runQueue = createChannelRunQueue({
|
|
152
221
|
setStatus: params.setStatus,
|
|
153
222
|
abortSignal: params.abortSignal,
|
|
@@ -155,9 +224,31 @@ function createDiscordMessageRunQueue(params) {
|
|
|
155
224
|
params.runtime.error(danger(`discord message run failed: ${String(error)}`));
|
|
156
225
|
}
|
|
157
226
|
});
|
|
227
|
+
let lifecycleActive = !params.abortSignal?.aborted;
|
|
228
|
+
const cleanupSkippedQueuedMessages = () => {
|
|
229
|
+
if (!lifecycleActive && skippedCleanup.size === 0) return;
|
|
230
|
+
lifecycleActive = false;
|
|
231
|
+
const cleanups = [...skippedCleanup];
|
|
232
|
+
skippedCleanup.clear();
|
|
233
|
+
for (const cleanup of cleanups) cleanup();
|
|
234
|
+
};
|
|
235
|
+
if (params.abortSignal?.aborted) cleanupSkippedQueuedMessages();
|
|
236
|
+
else params.abortSignal?.addEventListener("abort", cleanupSkippedQueuedMessages, { once: true });
|
|
158
237
|
return {
|
|
159
238
|
enqueue(job) {
|
|
239
|
+
const cleanupSkipped = () => {
|
|
240
|
+
cleanupSkippedDiscordQueuedMessage({
|
|
241
|
+
job,
|
|
242
|
+
replayGuard
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
if (!lifecycleActive) {
|
|
246
|
+
cleanupSkipped();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
skippedCleanup.add(cleanupSkipped);
|
|
160
250
|
runQueue.enqueue(job.queueKey, async ({ lifecycleSignal }) => {
|
|
251
|
+
skippedCleanup.delete(cleanupSkipped);
|
|
161
252
|
await processDiscordQueuedMessage({
|
|
162
253
|
job,
|
|
163
254
|
lifecycleSignal,
|
|
@@ -166,39 +257,88 @@ function createDiscordMessageRunQueue(params) {
|
|
|
166
257
|
});
|
|
167
258
|
});
|
|
168
259
|
},
|
|
169
|
-
deactivate
|
|
260
|
+
deactivate() {
|
|
261
|
+
runQueue.deactivate();
|
|
262
|
+
cleanupSkippedQueuedMessages();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createDiscordReplyTypingFeedback(params) {
|
|
267
|
+
let channelId = params.channelId;
|
|
268
|
+
const rest = params.rest ?? createDiscordRestClient({
|
|
269
|
+
cfg: params.cfg,
|
|
270
|
+
token: params.token,
|
|
271
|
+
accountId: params.accountId
|
|
272
|
+
}).rest;
|
|
273
|
+
const createCallbacks = () => createTypingCallbacks({
|
|
274
|
+
start: () => sendTyping({
|
|
275
|
+
rest,
|
|
276
|
+
channelId
|
|
277
|
+
}),
|
|
278
|
+
onStartError: (err) => {
|
|
279
|
+
logTypingFailure({
|
|
280
|
+
log: params.log,
|
|
281
|
+
channel: "discord",
|
|
282
|
+
target: channelId,
|
|
283
|
+
error: err
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
maxDurationMs: params.maxDurationMs ?? 12e5
|
|
287
|
+
});
|
|
288
|
+
const updateChannelId = (nextChannelId) => {
|
|
289
|
+
const trimmed = nextChannelId.trim();
|
|
290
|
+
if (trimmed) channelId = trimmed;
|
|
291
|
+
};
|
|
292
|
+
let callbacks = createCallbacks();
|
|
293
|
+
return {
|
|
294
|
+
onReplyStart: () => callbacks.onReplyStart(),
|
|
295
|
+
onIdle: () => callbacks.onIdle?.(),
|
|
296
|
+
onCleanup: () => callbacks.onCleanup?.(),
|
|
297
|
+
updateChannelId,
|
|
298
|
+
restartForDispatch: (nextChannelId) => {
|
|
299
|
+
updateChannelId(nextChannelId);
|
|
300
|
+
callbacks.onCleanup?.();
|
|
301
|
+
callbacks = createCallbacks();
|
|
302
|
+
},
|
|
303
|
+
getChannelId: () => channelId
|
|
170
304
|
};
|
|
171
305
|
}
|
|
172
306
|
//#endregion
|
|
173
307
|
//#region extensions/discord/src/monitor/message-handler.ts
|
|
174
308
|
let messagePreflightRuntimePromise;
|
|
175
309
|
async function loadMessagePreflightRuntime() {
|
|
176
|
-
messagePreflightRuntimePromise ??= import("./message-handler.preflight-
|
|
310
|
+
messagePreflightRuntimePromise ??= import("./message-handler.preflight-DeTwc2nk.js");
|
|
177
311
|
return await messagePreflightRuntimePromise;
|
|
178
312
|
}
|
|
179
313
|
function isNonEmptyString(value) {
|
|
180
314
|
return typeof value === "string" && value.length > 0;
|
|
181
315
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
if (!ctx.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
function queueAcceptedDiscordTypingCue(ctx) {
|
|
190
|
-
if (!shouldSendAcceptedDiscordTypingCue(ctx)) return;
|
|
191
|
-
const { rest } = createDiscordRestClient({
|
|
316
|
+
function startAcceptedTypingFeedback(params) {
|
|
317
|
+
const { ctx, createFeedback, dedupeKey, activeFeedback } = params;
|
|
318
|
+
if (!resolveDiscordAcceptedTypingPrestart(ctx).shouldPrestart) return;
|
|
319
|
+
const channelId = ctx.messageChannelId.trim();
|
|
320
|
+
if (activeFeedback.get(dedupeKey)) return;
|
|
321
|
+
const replyTypingFeedback = ctx.replyTypingFeedback ?? (createFeedback ?? createDiscordReplyTypingFeedback)({
|
|
192
322
|
cfg: ctx.cfg,
|
|
193
323
|
token: ctx.token,
|
|
194
|
-
accountId: ctx.accountId
|
|
324
|
+
accountId: ctx.accountId,
|
|
325
|
+
channelId: ctx.messageChannelId,
|
|
326
|
+
log: logVerbose
|
|
327
|
+
});
|
|
328
|
+
const cleanup = replyTypingFeedback.onCleanup;
|
|
329
|
+
replyTypingFeedback.onCleanup = () => {
|
|
330
|
+
cleanup?.();
|
|
331
|
+
if (activeFeedback.get(dedupeKey)?.feedback === replyTypingFeedback) activeFeedback.delete(dedupeKey);
|
|
332
|
+
};
|
|
333
|
+
activeFeedback.set(dedupeKey, {
|
|
334
|
+
channelId,
|
|
335
|
+
feedback: replyTypingFeedback
|
|
195
336
|
});
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}).catch((err) => {
|
|
200
|
-
logVerbose(`discord early typing cue failed for channel ${ctx.messageChannelId}: ${String(err)}`);
|
|
337
|
+
ctx.replyTypingFeedback = replyTypingFeedback;
|
|
338
|
+
replyTypingFeedback.onReplyStart().catch((err) => {
|
|
339
|
+
logVerbose(`discord accepted typing feedback failed: ${String(err)}`);
|
|
201
340
|
});
|
|
341
|
+
return replyTypingFeedback;
|
|
202
342
|
}
|
|
203
343
|
function createDiscordMessageHandler(params) {
|
|
204
344
|
const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
|
|
@@ -209,6 +349,7 @@ function createDiscordMessageHandler(params) {
|
|
|
209
349
|
const ackReactionScope = params.discordConfig?.ackReactionScope ?? params.cfg.messages?.ackReactionScope ?? "group-mentions";
|
|
210
350
|
const preflightDiscordMessageImpl = params.testing?.preflightDiscordMessage;
|
|
211
351
|
const replayGuard = createDiscordInboundReplayGuard();
|
|
352
|
+
const prestartedTypingFeedback = /* @__PURE__ */ new Map();
|
|
212
353
|
const messageRunQueue = createDiscordMessageRunQueue({
|
|
213
354
|
runtime: params.runtime,
|
|
214
355
|
setStatus: params.setStatus,
|
|
@@ -269,8 +410,14 @@ function createDiscordMessageHandler(params) {
|
|
|
269
410
|
});
|
|
270
411
|
return;
|
|
271
412
|
}
|
|
413
|
+
const queueKey = resolveDiscordInboundJobQueueKey(ctx);
|
|
414
|
+
startAcceptedTypingFeedback({
|
|
415
|
+
ctx,
|
|
416
|
+
createFeedback: params.testing?.createReplyTypingFeedback,
|
|
417
|
+
dedupeKey: queueKey,
|
|
418
|
+
activeFeedback: prestartedTypingFeedback
|
|
419
|
+
});
|
|
272
420
|
applyImplicitReplyBatchGate(ctx, params.replyToMode, false);
|
|
273
|
-
queueAcceptedDiscordTypingCue(ctx);
|
|
274
421
|
messageRunQueue.enqueue(buildDiscordInboundJob(ctx, { replayKeys }));
|
|
275
422
|
return;
|
|
276
423
|
}
|
|
@@ -322,6 +469,13 @@ function createDiscordMessageHandler(params) {
|
|
|
322
469
|
});
|
|
323
470
|
return;
|
|
324
471
|
}
|
|
472
|
+
const queueKey = resolveDiscordInboundJobQueueKey(ctx);
|
|
473
|
+
startAcceptedTypingFeedback({
|
|
474
|
+
ctx,
|
|
475
|
+
createFeedback: params.testing?.createReplyTypingFeedback,
|
|
476
|
+
dedupeKey: queueKey,
|
|
477
|
+
activeFeedback: prestartedTypingFeedback
|
|
478
|
+
});
|
|
325
479
|
applyImplicitReplyBatchGate(ctx, params.replyToMode, true);
|
|
326
480
|
if (entries.length > 1) {
|
|
327
481
|
const ids = entries.map((entry) => entry.data.message?.id).filter(isNonEmptyString);
|
|
@@ -332,7 +486,6 @@ function createDiscordMessageHandler(params) {
|
|
|
332
486
|
ctxBatch.MessageSidLast = ids[ids.length - 1];
|
|
333
487
|
}
|
|
334
488
|
}
|
|
335
|
-
queueAcceptedDiscordTypingCue(ctx);
|
|
336
489
|
messageRunQueue.enqueue(buildDiscordInboundJob(ctx, { replayKeys }));
|
|
337
490
|
} catch (error) {
|
|
338
491
|
if (error instanceof DiscordRetryableInboundError) releaseDiscordInboundReplay({
|
|
@@ -378,4 +531,4 @@ function createDiscordMessageHandler(params) {
|
|
|
378
531
|
return handler;
|
|
379
532
|
}
|
|
380
533
|
//#endregion
|
|
381
|
-
export { createDiscordMessageHandler as t };
|
|
534
|
+
export { createDiscordReplyTypingFeedback as n, createDiscordMessageHandler as t };
|
package/dist/{message-handler.preflight-B1RTQ6ZO.js → message-handler.preflight-DeTwc2nk.js}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { c as discord_exports, k as Message, mt as getChannelMessage } from "./send.receipt-
|
|
1
|
+
import { c as discord_exports, k as Message, mt as getChannelMessage } from "./send.receipt-CxFJX9o3.js";
|
|
2
2
|
import { o as resolveDefaultDiscordAccountId } from "./accounts-dXTfmnSZ.js";
|
|
3
3
|
import { S as resolveTimestampMs, _ as resolveGroupDmAllow, a as normalizeDiscordSlug, b as formatDiscordUserTag, c as resolveDiscordChannelConfigWithFallback, d as resolveDiscordGuildEntry, f as resolveDiscordMemberAccessState, g as resolveDiscordShouldRequireMention, i as normalizeDiscordDisplaySlug, n as isDiscordGroupAllowedByPolicy, x as resolveDiscordSystemLocation } from "./allow-list-BnkWtVpA.js";
|
|
4
|
-
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-
|
|
5
|
-
import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-
|
|
6
|
-
import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-
|
|
7
|
-
import "./thread-bindings-
|
|
8
|
-
import { D as resolveDiscordTextCommandAccess, E as resolveDiscordDmCommandAccess, _ as resolveDiscordEffectiveRoute, g as resolveDiscordConversationRoute, h as buildDiscordRoutePeer, v as shouldIgnoreStaleDiscordRouteBinding, y as handleDiscordDmCommandDecision } from "./provider-
|
|
9
|
-
import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-
|
|
4
|
+
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-Dvi-8LyR.js";
|
|
5
|
+
import { l as isRecentlyUnboundThreadWebhookMessage } from "./thread-bindings.state-B-iprgEM.js";
|
|
6
|
+
import { d as resolveDiscordChannelNameSafe, u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.js";
|
|
7
|
+
import "./thread-bindings-VrlFUPDW.js";
|
|
8
|
+
import { D as resolveDiscordTextCommandAccess, E as resolveDiscordDmCommandAccess, _ as resolveDiscordEffectiveRoute, g as resolveDiscordConversationRoute, h as buildDiscordRoutePeer, v as shouldIgnoreStaleDiscordRouteBinding, y as handleDiscordDmCommandDecision } from "./provider-SeIwaTTJ.js";
|
|
9
|
+
import { d as resolveDiscordMessageChannelId, l as resolveDiscordMessageStickers, o as resolveMediaList, r as resolveDiscordMessageText, u as resolveDiscordChannelInfo } from "./message-utils-C4OlktSy.js";
|
|
10
10
|
import { n as resolveDiscordWebhookId, t as resolveDiscordSenderIdentity } from "./sender-identity-BFp5w0F8.js";
|
|
11
11
|
import { normalizeOptionalString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
12
12
|
import { getChildLogger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
@@ -28,7 +28,7 @@ async function loadConversationRuntime$1() {
|
|
|
28
28
|
return await conversationRuntimePromise$1;
|
|
29
29
|
}
|
|
30
30
|
async function loadDiscordSendRuntime() {
|
|
31
|
-
discordSendRuntimePromise ??= import("./send-
|
|
31
|
+
discordSendRuntimePromise ??= import("./send-DwUf-fzH.js").then((n) => n.t);
|
|
32
32
|
return await discordSendRuntimePromise;
|
|
33
33
|
}
|
|
34
34
|
async function resolveDiscordDmPreflightAccess(params) {
|
|
@@ -423,7 +423,7 @@ let preflightAudioRuntimePromise;
|
|
|
423
423
|
let systemEventsRuntimePromise;
|
|
424
424
|
let discordThreadingRuntimePromise;
|
|
425
425
|
async function loadPluralKitRuntime() {
|
|
426
|
-
pluralkitRuntimePromise ??= import("./pluralkit-
|
|
426
|
+
pluralkitRuntimePromise ??= import("./pluralkit-Dhad974D.js").then((n) => n.n);
|
|
427
427
|
return await pluralkitRuntimePromise;
|
|
428
428
|
}
|
|
429
429
|
async function loadPreflightAudioRuntime() {
|
|
@@ -431,11 +431,11 @@ async function loadPreflightAudioRuntime() {
|
|
|
431
431
|
return await preflightAudioRuntimePromise;
|
|
432
432
|
}
|
|
433
433
|
async function loadSystemEventsRuntime() {
|
|
434
|
-
systemEventsRuntimePromise ??= import("./system-events-
|
|
434
|
+
systemEventsRuntimePromise ??= import("./system-events-DZdHRt2j.js");
|
|
435
435
|
return await systemEventsRuntimePromise;
|
|
436
436
|
}
|
|
437
437
|
async function loadDiscordThreadingRuntime() {
|
|
438
|
-
discordThreadingRuntimePromise ??= import("./provider-
|
|
438
|
+
discordThreadingRuntimePromise ??= import("./provider-SeIwaTTJ.js").then((n) => n.x);
|
|
439
439
|
return await discordThreadingRuntimePromise;
|
|
440
440
|
}
|
|
441
441
|
function isPreflightAborted(abortSignal) {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-
|
|
1
|
+
import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-CxFJX9o3.js";
|
|
2
2
|
import { f as resolveDiscordMaxLinesPerMessage } from "./accounts-dXTfmnSZ.js";
|
|
3
|
-
import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-
|
|
3
|
+
import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-DQwyGjXe.js";
|
|
4
4
|
import { S as resolveTimestampMs, a as normalizeDiscordSlug, r as normalizeDiscordAllowList } from "./allow-list-BnkWtVpA.js";
|
|
5
|
-
import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-
|
|
6
|
-
import "./targets-
|
|
7
|
-
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-
|
|
5
|
+
import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-DwUf-fzH.js";
|
|
6
|
+
import "./targets-xBATI2q7.js";
|
|
7
|
+
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-Dvi-8LyR.js";
|
|
8
8
|
import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-CEPlt2uz.js";
|
|
9
|
-
import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-
|
|
9
|
+
import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-DCC3zPuC.js";
|
|
10
10
|
import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-DXT8oJdo.js";
|
|
11
|
-
import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-
|
|
12
|
-
import { S as resolveDiscordAutoThreadReplyPlan, a as resolveReplyContext, i as buildGuildLabel, m as createDiscordSupplementalContextAccessChecker, n as deliverDiscordReply, p as buildDiscordInboundAccessContext, r as buildDirectLabel, w as resolveDiscordThreadStarter } from "./provider-
|
|
13
|
-
import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-
|
|
14
|
-
import {
|
|
11
|
+
import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-CEJ3--Uw.js";
|
|
12
|
+
import { S as resolveDiscordAutoThreadReplyPlan, a as resolveReplyContext, i as buildGuildLabel, m as createDiscordSupplementalContextAccessChecker, n as deliverDiscordReply, p as buildDiscordInboundAccessContext, r as buildDirectLabel, w as resolveDiscordThreadStarter } from "./provider-SeIwaTTJ.js";
|
|
13
|
+
import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-C4OlktSy.js";
|
|
14
|
+
import { n as createDiscordReplyTypingFeedback } from "./message-handler-Deidymb-.js";
|
|
15
15
|
import { buildAgentSessionKey, normalizeAccountId, resolveAccountEntry, resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
|
|
16
16
|
import { MessageFlags } from "discord-api-types/v10";
|
|
17
17
|
import path from "node:path";
|
|
@@ -30,7 +30,7 @@ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
|
30
30
|
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
31
31
|
import { loadSessionStore, readLatestAssistantTextFromSessionTranscript, readSessionUpdatedAt, resolveAndPersistSessionFile, resolveSessionStoreEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
|
32
32
|
import { buildChannelInboundEventContext, dispatchChannelInboundReply, formatInboundEnvelope, hasFinalInboundReplyDispatch, recordChannelBotPairLoopAndCheckSuppression, resolveEnvelopeFormatOptions, toHistoryMediaEntries, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
|
|
33
|
-
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure,
|
|
33
|
+
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
|
|
34
34
|
import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
35
35
|
import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
|
|
36
36
|
//#region extensions/discord/src/monitor/ack-reactions.ts
|
|
@@ -848,7 +848,6 @@ function sleep(ms) {
|
|
|
848
848
|
setTimeout(resolve, ms);
|
|
849
849
|
});
|
|
850
850
|
}
|
|
851
|
-
const DISCORD_TYPING_MAX_DURATION_MS = 20 * 6e4;
|
|
852
851
|
let replyRuntimePromise;
|
|
853
852
|
async function loadReplyRuntime() {
|
|
854
853
|
replyRuntimePromise ??= import("openclaw/plugin-sdk/reply-runtime");
|
|
@@ -877,8 +876,15 @@ function readToolBooleanArg(args, key) {
|
|
|
877
876
|
return args[key] === true;
|
|
878
877
|
}
|
|
879
878
|
async function processDiscordMessage(ctx, observer) {
|
|
879
|
+
try {
|
|
880
|
+
await processDiscordMessageInner(ctx, observer);
|
|
881
|
+
} finally {
|
|
882
|
+
ctx.replyTypingFeedback?.onCleanup?.();
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
async function processDiscordMessageInner(ctx, observer) {
|
|
880
886
|
const dispatchStartedAt = Date.now();
|
|
881
|
-
const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, channelConfig, threadBindings, route, discordRestFetch, abortSignal, botLoopProtection } = ctx;
|
|
887
|
+
const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, channelConfig, threadBindings, route, discordRestFetch, abortSignal, botLoopProtection, replyTypingFeedback } = ctx;
|
|
882
888
|
if (isProcessAborted(abortSignal)) return;
|
|
883
889
|
if (botLoopProtection) {
|
|
884
890
|
const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
|
|
@@ -1050,26 +1056,22 @@ async function processDiscordMessage(ctx, observer) {
|
|
|
1050
1056
|
sessionKey: persistedSessionKey
|
|
1051
1057
|
});
|
|
1052
1058
|
const typingChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
|
|
1059
|
+
const typingFeedback = replyTypingFeedback ?? createDiscordReplyTypingFeedback({
|
|
1060
|
+
cfg,
|
|
1061
|
+
token,
|
|
1062
|
+
accountId,
|
|
1063
|
+
channelId: typingChannelId,
|
|
1064
|
+
rest: feedbackRest,
|
|
1065
|
+
log: logVerbose
|
|
1066
|
+
});
|
|
1067
|
+
if (replyTypingFeedback) replyTypingFeedback.restartForDispatch(typingChannelId);
|
|
1068
|
+
else typingFeedback.updateChannelId(typingChannelId);
|
|
1053
1069
|
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
1054
1070
|
cfg,
|
|
1055
1071
|
agentId: route.agentId,
|
|
1056
1072
|
channel: "discord",
|
|
1057
1073
|
accountId: route.accountId,
|
|
1058
|
-
|
|
1059
|
-
start: () => sendTyping({
|
|
1060
|
-
rest: feedbackRest,
|
|
1061
|
-
channelId: typingChannelId
|
|
1062
|
-
}),
|
|
1063
|
-
onStartError: (err) => {
|
|
1064
|
-
logTypingFailure({
|
|
1065
|
-
log: logVerbose,
|
|
1066
|
-
channel: "discord",
|
|
1067
|
-
target: typingChannelId,
|
|
1068
|
-
error: err
|
|
1069
|
-
});
|
|
1070
|
-
},
|
|
1071
|
-
maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS
|
|
1072
|
-
}
|
|
1074
|
+
typingCallbacks: typingFeedback
|
|
1073
1075
|
});
|
|
1074
1076
|
const tableMode = resolveMarkdownTableMode({
|
|
1075
1077
|
cfg,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-
|
|
2
|
-
import { a as mergeAbortSignals } from "./timeouts-
|
|
1
|
+
import { u as resolveDiscordChannelInfoSafe } from "./thread-bindings.discord-api-CItgvv24.js";
|
|
2
|
+
import { a as mergeAbortSignals } from "./timeouts-CEJ3--Uw.js";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeOptionalStringifiedId, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import { ComponentType, StickerFormatType } from "discord-api-types/v10";
|
|
5
|
+
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
5
6
|
import { saveRemoteMedia } from "openclaw/plugin-sdk/media-runtime";
|
|
6
7
|
import { buildMediaPayload } from "openclaw/plugin-sdk/reply-payload";
|
|
7
8
|
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
@@ -10,6 +11,16 @@ import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
|
10
11
|
const DISCORD_CHANNEL_INFO_CACHE_TTL_MS = 300 * 1e3;
|
|
11
12
|
const DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS = 30 * 1e3;
|
|
12
13
|
const DISCORD_CHANNEL_INFO_CACHE = /* @__PURE__ */ new Map();
|
|
14
|
+
function resolveDiscordChannelInfoCacheExpiresAt(ttlMs, nowMs) {
|
|
15
|
+
return resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs });
|
|
16
|
+
}
|
|
17
|
+
function cacheDiscordChannelInfo(channelId, value, ttlMs, nowMs) {
|
|
18
|
+
const expiresAt = resolveDiscordChannelInfoCacheExpiresAt(ttlMs, nowMs);
|
|
19
|
+
if (expiresAt !== void 0) DISCORD_CHANNEL_INFO_CACHE.set(channelId, {
|
|
20
|
+
value,
|
|
21
|
+
expiresAt
|
|
22
|
+
});
|
|
23
|
+
}
|
|
13
24
|
function normalizeDiscordChannelId(value) {
|
|
14
25
|
return normalizeOptionalStringifiedId(value) ?? "";
|
|
15
26
|
}
|
|
@@ -18,18 +29,17 @@ function resolveDiscordMessageChannelId(params) {
|
|
|
18
29
|
return normalizeDiscordChannelId(message.channelId) || normalizeDiscordChannelId(message.channel_id) || normalizeDiscordChannelId(message.rawData?.channel_id) || normalizeDiscordChannelId(params.eventChannelId);
|
|
19
30
|
}
|
|
20
31
|
async function resolveDiscordChannelInfo(client, channelId) {
|
|
32
|
+
const rawNow = Date.now();
|
|
33
|
+
const now = asDateTimestampMs(rawNow);
|
|
21
34
|
const cached = DISCORD_CHANNEL_INFO_CACHE.get(channelId);
|
|
22
35
|
if (cached) {
|
|
23
|
-
if (cached.expiresAt >
|
|
36
|
+
if (now !== void 0 && cached.expiresAt > now) return cached.value;
|
|
24
37
|
DISCORD_CHANNEL_INFO_CACHE.delete(channelId);
|
|
25
38
|
}
|
|
26
39
|
try {
|
|
27
40
|
const channel = await client.fetchChannel(channelId);
|
|
28
41
|
if (!channel) {
|
|
29
|
-
|
|
30
|
-
value: null,
|
|
31
|
-
expiresAt: Date.now() + DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS
|
|
32
|
-
});
|
|
42
|
+
cacheDiscordChannelInfo(channelId, null, DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS, rawNow);
|
|
33
43
|
return null;
|
|
34
44
|
}
|
|
35
45
|
const channelInfo = resolveDiscordChannelInfoSafe(channel);
|
|
@@ -43,17 +53,11 @@ async function resolveDiscordChannelInfo(client, channelId) {
|
|
|
43
53
|
parentId: channelInfo.parentId,
|
|
44
54
|
ownerId: channelInfo.ownerId
|
|
45
55
|
};
|
|
46
|
-
|
|
47
|
-
value: payload,
|
|
48
|
-
expiresAt: Date.now() + DISCORD_CHANNEL_INFO_CACHE_TTL_MS
|
|
49
|
-
});
|
|
56
|
+
cacheDiscordChannelInfo(channelId, payload, DISCORD_CHANNEL_INFO_CACHE_TTL_MS, rawNow);
|
|
50
57
|
return payload;
|
|
51
58
|
} catch (err) {
|
|
52
59
|
logVerbose(`discord: failed to fetch channel ${channelId}: ${String(err)}`);
|
|
53
|
-
|
|
54
|
-
value: null,
|
|
55
|
-
expiresAt: Date.now() + DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS
|
|
56
|
-
});
|
|
60
|
+
cacheDiscordChannelInfo(channelId, null, DISCORD_CHANNEL_INFO_NEGATIVE_CACHE_TTL_MS, rawNow);
|
|
57
61
|
return null;
|
|
58
62
|
}
|
|
59
63
|
}
|