@openclaw/discord 2026.9.1-beta.1 → 2026.9.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-BY-ve3yH.js +45 -0
- package/dist/account-inspect-api.js +1 -1
- package/dist/{accounts-CfQ-vSyf.js → accounts-CwJQoLjM.js} +89 -45
- package/dist/action-runtime-api.js +1 -1
- package/dist/{activities-api-q-9PaaHH.js → activities-api-E-segZn6.js} +30 -17
- package/dist/activities-api.js +1 -1
- package/dist/{allow-list-BDMimASD.js → allow-list-C2JuH-R0.js} +10 -6
- package/dist/{api-DNVbJyEy.js → api-BfmEP6wp.js} +6 -5
- package/dist/api.js +18 -18
- package/dist/{approval-handler.runtime-B7qhK3fR.js → approval-handler.runtime-CW5k3mSa.js} +19 -12
- package/dist/{audit-CBEv_ERu.js → audit-B9rydv5a.js} +8 -7
- package/dist/{channel-Dx1tMTfx.js → channel-D93LnbTE.js} +29 -23
- package/dist/{channel-actions.runtime-CLWWu9nC.js → channel-actions.runtime-DMFv4doE.js} +16 -13
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-DOzXS2Pk.js → channel.setup-DTL7kPOM.js} +18 -18
- package/dist/{components-B9V5o361.js → components-DmXYIQ26.js} +111 -149
- package/dist/{config-schema-BR13IAx6.js → config-schema-B797-Spt.js} +19 -12
- package/dist/{conversation-binding-route-DJYrY2TQ.js → conversation-binding-route-24afmvEC.js} +1 -1
- package/dist/{directory-config-BK6eqekJ.js → directory-config-BD4YL7wT.js} +2 -2
- package/dist/directory-contract-api.js +1 -1
- package/dist/{directory-live-DSAJSrJk.js → directory-live-BrFVc1Ds.js} +4 -4
- package/dist/{doctor-Cm7Y1pJ2.js → doctor-BUB41_lr.js} +5 -5
- package/dist/{doctor-contract-DsWvKIXF.js → doctor-contract-Co5vRCZ6.js} +9 -6
- package/dist/doctor-contract-api.js +3 -2
- package/dist/{handle-action.guild-admin-BdwCRHmm.js → handle-action.guild-admin-B0egstYq.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/ingress-identity-BCzfPPYM.js +60 -0
- package/dist/{message-handler-BXpWZyni.js → message-handler-Qdw9c0j4.js} +14 -10
- package/dist/{message-handler.preflight-Ba5LAY_X.js → message-handler.preflight-Dxi2yBfR.js} +41 -101
- package/dist/{message-handler.process-3HEzmemS.js → message-handler.process-BYDjtxZJ.js} +83 -109
- package/dist/{message-text-aiwO5R2F.js → message-text-BRVqYm-t.js} +11 -6
- package/dist/{outbound-adapter-BE4jSq2d.js → outbound-adapter-DEhthWTk.js} +52 -67
- package/dist/{outbound-components-oLpg8Qzb.js → outbound-components-DehyxvA4.js} +5 -4
- package/dist/{outbound-session-route-Cg15ZFJd.js → outbound-session-route-BFGRF9fE.js} +55 -12
- package/dist/{owner-access-UlYi9t0M.js → owner-access-yc1sX7Gx.js} +7 -6
- package/dist/{pluralkit-Tiyl70dx.js → pluralkit-XmmAHoV6.js} +2 -2
- package/dist/{probe-n6ZXxK8V.js → probe-3ovFAp3b.js} +3 -3
- package/dist/{probe.runtime-CN4bdPfZ.js → probe.runtime-BckHn3cJ.js} +1 -1
- package/dist/{provider-B11xwilq.js → provider-DuDtKbF8.js} +294 -131
- package/dist/{provider-session.runtime-B9xe-Qaf.js → provider-session.runtime-BA2BPyZ4.js} +3 -3
- package/dist/provider.runtime-Db8YwZBv.js +2 -0
- package/dist/{realtime-session.runtime-CU8IQUS-.js → realtime-session.runtime-DgSbX6ay.js} +103 -163
- package/dist/{resolve-allowlist-common-BLoYaxEG.js → resolve-allowlist-common-BJ5UZNNT.js} +3 -3
- package/dist/{resolve-channels-C2sT_n67.js → resolve-channels-CYseD5Uw.js} +4 -4
- package/dist/{resolve-users-CzpjLwjy.js → resolve-users-DrmhDrqm.js} +5 -4
- package/dist/{retry-Cv4jCU5Z.js → retry-BNCxiyyR.js} +120 -32
- package/dist/{retry-after-DdLPmPoa.js → retry-after-BrLjLUJi.js} +2 -1
- package/dist/{runtime-BHrTlFkf.js → runtime-BbWNtfyq.js} +127 -93
- package/dist/{runtime-CY4PRMF-.js → runtime-DB7Fb6AU.js} +2 -2
- package/dist/runtime-api.js +19 -19
- package/dist/{secret-config-contract-B3i6WxV5.js → secret-config-contract-DFu9hfU8.js} +14 -6
- package/dist/secret-contract-api.js +1 -1
- package/dist/{security-audit-BfJhAXZp.js → security-audit-DKfK-iPY.js} +10 -11
- package/dist/security-audit-contract-api.js +1 -1
- package/dist/{security-audit.runtime-BVKF9VtY.js → security-audit.runtime-sKZyKgpx.js} +1 -1
- package/dist/{send-C49MFF9j.js → send-BnTEIeLE.js} +132 -100
- package/dist/{send.components-DKKAw5VX.js → send.components-BWVEcjRP.js} +21 -15
- package/dist/{send.outbound-zam0TrZx.js → send.outbound-b5xwHTlL.js} +35 -22
- package/dist/{send.shared-JjljcV4O.js → send.shared-5vxBTagO.js} +25 -18
- package/dist/{sender-identity-BJtfyise.js → sender-identity-Dt0imZyY.js} +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{shared-_5EcP4jW.js → shared-BSJWHEMA.js} +20 -14
- package/dist/{subagent-hooks-DQzvOptn.js → subagent-hooks-IzQKX4RQ.js} +2 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/{system-events-C7yxaHWP.js → system-events-BOiDS8b3.js} +2 -2
- package/dist/{target-resolver-vxIh6O3-.js → target-resolver-BVI8xSSC.js} +6 -5
- package/dist/targets-DbxVbq8k.js +3 -0
- package/dist/{thread-bindings-DW5F9vat.js → thread-bindings-D7mcaXQC.js} +6 -5
- package/dist/{thread-bindings.manager-ByOtwjo_.js → thread-bindings.manager-B1oySiVc.js} +37 -28
- package/dist/{thread-bindings.session-updates-CJb-BpPL.js → thread-bindings.session-updates-CC7HoQvt.js} +2 -1
- package/dist/{threading-agH1T_6s.js → threading-OQnL-0Zx.js} +8 -6
- package/dist/{timeouts-B9cy6RvQ.js → timeouts-D86uWLt_.js} +1 -1
- package/dist/{transcripts-source-BR11iHA4.js → transcripts-source-D8lH7AkN.js} +9 -4
- package/dist/transcripts-source-api.js +1 -1
- package/dist/{typing-Dc_YMY2e.js → typing-C9q0AQMb.js} +2 -2
- package/dist/{voice-runtime-DIeaa8fJ.js → voice-runtime-Dfhjh1tV.js} +433 -93
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/README.md +1 -1
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/conforms.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/package.json +3 -3
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubArray.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubObject.d.ts +2 -0
- package/node_modules/@discord/embedded-app-sdk/node_modules/@types/lodash/stubString.d.ts +2 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +14 -6
- package/package.json +6 -5
- package/skills/discord/SKILL.md +1 -1
- package/dist/account-inspect-BDfKyuvL.js +0 -89
- package/dist/provider.runtime-DiYaHvOz.js +0 -2
- package/dist/sdk-runtime-4geQHtXc.js +0 -338
- package/dist/targets-CtTKvntJ.js +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as createThreadBindingManager, t as createNoopThreadBindingManager } from "./thread-bindings.manager-
|
|
2
|
-
import { a as reconcileAcpThreadBindingsOnStartup } from "./thread-bindings-
|
|
3
|
-
import { t as createDiscordMessageHandler } from "./message-handler-
|
|
1
|
+
import { n as createThreadBindingManager, t as createNoopThreadBindingManager } from "./thread-bindings.manager-B1oySiVc.js";
|
|
2
|
+
import { a as reconcileAcpThreadBindingsOnStartup } from "./thread-bindings-D7mcaXQC.js";
|
|
3
|
+
import { t as createDiscordMessageHandler } from "./message-handler-Qdw9c0j4.js";
|
|
4
4
|
import { resolveThreadBindingIdleTimeoutMs, resolveThreadBindingMaxAgeMs, resolveThreadBindingsEnabled } from "openclaw/plugin-sdk/conversation-runtime";
|
|
5
5
|
import { getAcpSessionManager, isAcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
|
|
6
6
|
export { createDiscordMessageHandler, createNoopThreadBindingManager, createThreadBindingManager, getAcpSessionManager, isAcpRuntimeError, reconcileAcpThreadBindingsOnStartup, resolveThreadBindingIdleTimeoutMs, resolveThreadBindingMaxAgeMs, resolveThreadBindingsEnabled };
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { n as discordRealtimeVoiceSecretOwnerId } from "./secret-config-contract-
|
|
2
|
-
import {
|
|
1
|
+
import { n as discordRealtimeVoiceSecretOwnerId } from "./secret-config-contract-DFu9hfU8.js";
|
|
2
|
+
import { a as convertRealtimePcm24kMonoToDiscordPcm48kStereo, c as maybeControlDiscordVoiceAgentRun, i as convertDiscordPcm48kStereoToRealtimePcm24kMono, l as formatVoiceLogPreview, n as loadDiscordVoiceSdk, o as createDiscordOpusEncodeStream, r as logVoiceVerbose, s as formatVoiceIngressPrompt } from "./voice-runtime-Dfhjh1tV.js";
|
|
3
3
|
import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
|
|
4
4
|
import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
5
5
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
6
6
|
import { sliceUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
7
|
+
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
7
8
|
import { assertSecretOwnerAvailable, isSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
|
|
8
|
-
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONTROL_TOOL, REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceSessionInstructions, buildRealtimeVoiceSpeakExactMessage, canonicalizeRealtimeVoiceProviderId, classifyRealtimeVoiceConsultToolCall, classifySkippableRealtimeVoiceConsultTranscript, controlRealtimeVoiceAgentRun, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceSessionHarness, createRealtimeVoiceTurnContextTracker, isRealtimeVoiceWakeNameRequired, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, parseRealtimeVoiceAgentControlToolArgs, realtimeVoiceAudioDurationMs, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceBargeIn, resolveRealtimeVoiceInterruptResponseOnInputAudio, resolveRealtimeVoiceMinBargeInAudioEndMs, resolveRealtimeVoiceSessionPolicy } from "openclaw/plugin-sdk/realtime-voice";
|
|
9
|
+
import { REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, REALTIME_VOICE_AGENT_CONTROL_TOOL, REALTIME_VOICE_AGENT_CONTROL_TOOL_NAME, REALTIME_VOICE_AUDIO_FORMAT_PCM16_24KHZ, buildRealtimeVoiceAgentErrorProviderResult, buildRealtimeVoiceSessionInstructions, buildRealtimeVoiceSpeakExactMessage, canonicalizeRealtimeVoiceProviderId, classifyRealtimeVoiceConsultToolCall, classifySkippableRealtimeVoiceConsultTranscript, controlRealtimeVoiceAgentRun, createRealtimeVoiceAgentTalkbackQueue, createRealtimeVoiceSessionHarness, createRealtimeVoiceTurnContextTracker, isRealtimeVoiceWakeNameRequired, matchRealtimeVoiceActivationName, matchRealtimeVoiceConsultQuestions, parseRealtimeVoiceAgentControlToolArgs, realtimeVoiceAudioDurationMs, resolveConfiguredRealtimeVoiceProvider, resolveRealtimeVoiceAgentConsultTools, resolveRealtimeVoiceBargeIn, resolveRealtimeVoiceInterruptResponseOnInputAudio, resolveRealtimeVoiceMinBargeInAudioEndMs, resolveRealtimeVoiceSessionPolicy } from "openclaw/plugin-sdk/realtime-voice";
|
|
9
10
|
import { PassThrough, pipeline } from "node:stream";
|
|
10
11
|
//#region extensions/discord/src/voice/realtime-transcript.ts
|
|
11
12
|
function mergeRealtimePartialTranscript(previous, next) {
|
|
12
13
|
const trimmed = next.trim();
|
|
13
14
|
if (!trimmed) return previous;
|
|
14
|
-
|
|
15
|
+
const merged = trimmed.startsWith(previous) ? trimmed : `${previous}${next}`;
|
|
16
|
+
return sliceUtf16Safe(merged, -240);
|
|
15
17
|
}
|
|
16
18
|
//#endregion
|
|
17
19
|
//#region extensions/discord/src/voice/realtime-turns.ts
|
|
@@ -39,6 +41,7 @@ var DiscordRealtimeTurns = class {
|
|
|
39
41
|
...context,
|
|
40
42
|
userId
|
|
41
43
|
}, {
|
|
44
|
+
transcripts: this.params.entry.transcripts,
|
|
42
45
|
inputDiscordBytes: 0,
|
|
43
46
|
inputRealtimeBytes: 0,
|
|
44
47
|
inputChunks: 0,
|
|
@@ -185,12 +188,13 @@ var DiscordRealtimeTurns = class {
|
|
|
185
188
|
transcriptAttributionFromTurn(turn) {
|
|
186
189
|
return turn ? {
|
|
187
190
|
context: turn.context,
|
|
188
|
-
startedAt: turn.startedAt
|
|
191
|
+
startedAt: turn.startedAt,
|
|
192
|
+
transcripts: turn.transcripts
|
|
189
193
|
} : void 0;
|
|
190
194
|
}
|
|
191
195
|
recordTranscriptUtterance(text, attribution, providerEpoch) {
|
|
192
|
-
const transcripts =
|
|
193
|
-
if (!transcripts || !attribution) return;
|
|
196
|
+
const transcripts = attribution?.transcripts;
|
|
197
|
+
if (!transcripts || !attribution || this.params.entry.transcripts !== transcripts) return;
|
|
194
198
|
const context = attribution.context;
|
|
195
199
|
const utterance = {
|
|
196
200
|
sessionId: transcripts.sessionId,
|
|
@@ -209,7 +213,7 @@ var DiscordRealtimeTurns = class {
|
|
|
209
213
|
}
|
|
210
214
|
};
|
|
211
215
|
Promise.resolve().then(() => {
|
|
212
|
-
if (providerEpoch !== this.params.providerEpoch()) return;
|
|
216
|
+
if (providerEpoch !== this.params.providerEpoch() || this.params.entry.transcripts !== transcripts) return;
|
|
213
217
|
return transcripts.onUtterance(utterance);
|
|
214
218
|
}).catch((error) => {
|
|
215
219
|
logger$3.warn(`discord voice: realtime transcripts utterance failed: ${formatErrorMessage(error)}`);
|
|
@@ -226,6 +230,7 @@ var DiscordRealtimeTurns = class {
|
|
|
226
230
|
if (expiresAt === void 0) return;
|
|
227
231
|
this.pendingWakeNameFollowup = {
|
|
228
232
|
context,
|
|
233
|
+
transcripts: turn?.transcripts,
|
|
229
234
|
startedAt: turn?.startedAt ?? Date.now(),
|
|
230
235
|
expiresAt
|
|
231
236
|
};
|
|
@@ -242,7 +247,8 @@ var DiscordRealtimeTurns = class {
|
|
|
242
247
|
if (currentTurn) this.consumePendingSpeakerContext();
|
|
243
248
|
return {
|
|
244
249
|
context: pending.context,
|
|
245
|
-
startedAt: pending.startedAt
|
|
250
|
+
startedAt: pending.startedAt,
|
|
251
|
+
transcripts: pending.transcripts
|
|
246
252
|
};
|
|
247
253
|
}
|
|
248
254
|
rememberIgnoredWakeNameSpeakerContext(context) {
|
|
@@ -259,9 +265,14 @@ const DISCORD_REALTIME_TALKBACK_DEBOUNCE_MS = 350;
|
|
|
259
265
|
const DISCORD_REALTIME_FALLBACK_TEXT = "I hit an error while checking that. Please try again.";
|
|
260
266
|
const DISCORD_REALTIME_FORCED_CONSULT_FALLBACK_DELAY_MS = 200;
|
|
261
267
|
const DISCORD_REALTIME_FORCED_CONSULT_REASON = "provider_final_transcript_without_openclaw_agent_consult";
|
|
268
|
+
const CANCELLED_CONSULT_RESULT = {
|
|
269
|
+
status: "cancelled",
|
|
270
|
+
message: "OpenClaw cancelled this consult before completion. Do not restart it."
|
|
271
|
+
};
|
|
262
272
|
var DiscordRealtimeConsults = class {
|
|
263
273
|
constructor(params) {
|
|
264
274
|
this.params = params;
|
|
275
|
+
this.providerDeliveries = /* @__PURE__ */ new Map();
|
|
265
276
|
this.talkback = this.createTalkbackQueue();
|
|
266
277
|
}
|
|
267
278
|
close() {
|
|
@@ -298,16 +309,12 @@ var DiscordRealtimeConsults = class {
|
|
|
298
309
|
logger$2.warn(`discord voice: realtime consult rejected malformed args call=${callId || "unknown"}: ${outcome.error}`);
|
|
299
310
|
await session.submitToolResult(callId, { error: outcome.error });
|
|
300
311
|
return;
|
|
301
|
-
case "consult": break;
|
|
302
312
|
}
|
|
303
313
|
const consultMessage = outcome.message;
|
|
304
314
|
logger$2.info(`discord voice: realtime consult requested call=${callId || "unknown"} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} question=${formatVoiceLogPreview(consultMessage)}`);
|
|
305
315
|
const nativeConsult = this.params.harness.forcedConsults.recordNativeConsult(event.args, callId);
|
|
306
316
|
if (nativeConsult.kind === "already_delivered" && this.params.harness.forcedConsults.isCancelled(nativeConsult.handle)) {
|
|
307
|
-
await this.submitTerminalRealtimeToolResult(callId, session,
|
|
308
|
-
status: "cancelled",
|
|
309
|
-
message: "OpenClaw cancelled this consult before completion. Do not restart it."
|
|
310
|
-
});
|
|
317
|
+
await this.submitTerminalRealtimeToolResult(callId, session, CANCELLED_CONSULT_RESULT);
|
|
311
318
|
return;
|
|
312
319
|
}
|
|
313
320
|
const pendingConsult = nativeConsult.kind === "pending" ? nativeConsult.handle : void 0;
|
|
@@ -338,24 +345,14 @@ var DiscordRealtimeConsults = class {
|
|
|
338
345
|
await session.submitToolResult(callId, { error: "No Discord speaker context available" });
|
|
339
346
|
return;
|
|
340
347
|
}
|
|
341
|
-
const
|
|
348
|
+
const result = await this.trackAgentProxyConsult(recent, this.runAgentTurn({
|
|
342
349
|
context,
|
|
343
350
|
message: consultMessage
|
|
344
|
-
});
|
|
345
|
-
if (recent) this.setRecentAgentProxyConsultPromise(recent, promise);
|
|
346
|
-
let text;
|
|
347
|
-
try {
|
|
348
|
-
text = await promise;
|
|
349
|
-
} catch (error) {
|
|
350
|
-
if (providerEpoch !== this.params.providerEpoch()) return;
|
|
351
|
-
const message = formatErrorMessage(error);
|
|
352
|
-
logger$2.warn(`discord voice: realtime consult failed call=${callId || "unknown"}: ${message}`);
|
|
353
|
-
await session.submitToolResult(callId, { error: message });
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
351
|
+
}));
|
|
356
352
|
if (providerEpoch !== this.params.providerEpoch()) return;
|
|
357
|
-
logger$2.info(`discord voice: realtime consult answer (${text.length} chars) voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel} owner=${context.senderIsOwner}: ${formatVoiceLogPreview(text)}`);
|
|
358
|
-
|
|
353
|
+
if ("text" in result) logger$2.info(`discord voice: realtime consult answer (${result.text.length} chars) voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel} owner=${context.senderIsOwner}: ${formatVoiceLogPreview(result.text)}`);
|
|
354
|
+
else if ("error" in result) logger$2.warn(`discord voice: realtime consult failed call=${callId}: ${result.error}`);
|
|
355
|
+
await this.submitAgentProxyConsultResult(callId, session, result);
|
|
359
356
|
}
|
|
360
357
|
async handleAcceptedTranscript(acceptedText, forcedSpeakerContext, providerEpoch) {
|
|
361
358
|
const usesRealtimeAgentHandoff = this.params.usesRealtimeAgentHandoff();
|
|
@@ -483,23 +480,17 @@ var DiscordRealtimeConsults = class {
|
|
|
483
480
|
logger$2.debug(`discord voice: realtime forced agent consult reason=${DISCORD_REALTIME_FORCED_CONSULT_REASON} consultPolicy=${this.params.consultPolicy()} wakeNamePolicy=${this.params.wakeNamePolicy()} requireWakeName=${this.params.isWakeNameRequired()} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId} speaker=${context.speakerLabel}`);
|
|
484
481
|
if (this.params.playback.hasInterruptibleOutputAudio()) logger$2.info(`discord voice: realtime forced agent consult preserving active playback guild=${this.params.entry.guildId} channel=${this.params.entry.channelId} outputAudioMs=${this.params.playback.outputAudioMs()} outputActive=${this.params.playback.isOutputAudioActive()} playbackChunks=${this.params.harness.outputActivity.snapshot().chunks}`);
|
|
485
482
|
state.handledByForcedPlayback = true;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
} catch (error) {
|
|
498
|
-
await state.providerDelivery;
|
|
499
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return;
|
|
500
|
-
logger$2.warn(`discord voice: realtime forced agent consult failed elapsedMs=${Date.now() - startedAt}: ${formatErrorMessage(error)}`);
|
|
501
|
-
if (state.handledByForcedPlayback) this.params.playback.enqueueExactSpeechMessage(DISCORD_REALTIME_FALLBACK_TEXT);
|
|
502
|
-
}
|
|
483
|
+
const result = await this.trackAgentProxyConsult(pending, this.runAgentTurn({
|
|
484
|
+
context,
|
|
485
|
+
message: question
|
|
486
|
+
}));
|
|
487
|
+
const deliveries = this.providerDeliveries.get(state);
|
|
488
|
+
await Promise.all(Array.from(deliveries ?? [], (delivery) => delivery.promise));
|
|
489
|
+
if (state.providerEpoch !== this.params.providerEpoch() || "status" in result) return;
|
|
490
|
+
if ("text" in result) logger$2.info(`discord voice: realtime forced agent consult answer (${result.text.length} chars) elapsedMs=${Date.now() - startedAt} voiceSession=${this.params.entry.voiceSessionKey} supervisorSession=${this.params.entry.route.sessionKey} agent=${this.params.entry.route.agentId}: ${formatVoiceLogPreview(result.text)}`);
|
|
491
|
+
else logger$2.warn(`discord voice: realtime forced agent consult failed elapsedMs=${Date.now() - startedAt}: ${result.error}`);
|
|
492
|
+
const text = "text" in result ? result.text : DISCORD_REALTIME_FALLBACK_TEXT;
|
|
493
|
+
if (text.trim() && state.handledByForcedPlayback) this.params.playback.enqueueExactSpeechMessage(text);
|
|
503
494
|
}
|
|
504
495
|
rememberRecentAgentProxyConsultContext(question, context, options = {}) {
|
|
505
496
|
const handle = this.params.harness.forcedConsults.prepare(question, {
|
|
@@ -513,26 +504,19 @@ var DiscordRealtimeConsults = class {
|
|
|
513
504
|
if (options.started) this.params.harness.forcedConsults.markStarted(handle);
|
|
514
505
|
return handle;
|
|
515
506
|
}
|
|
516
|
-
|
|
517
|
-
const state = recent
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
};
|
|
527
|
-
this.params.harness.forcedConsults.markDelivered(recent);
|
|
528
|
-
}).catch((error) => {
|
|
529
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return;
|
|
530
|
-
state.result = {
|
|
531
|
-
status: "rejected",
|
|
532
|
-
error: formatErrorMessage(error)
|
|
533
|
-
};
|
|
534
|
-
this.params.harness.forcedConsults.markDelivered(recent);
|
|
507
|
+
trackAgentProxyConsult(recent, promise) {
|
|
508
|
+
const state = recent?.context;
|
|
509
|
+
if (recent) this.params.harness.forcedConsults.markStarted(recent);
|
|
510
|
+
const tracked = promise.then((text) => ({ text }), buildRealtimeVoiceAgentErrorProviderResult).then((result) => {
|
|
511
|
+
if (recent && state && state.providerEpoch === this.params.providerEpoch()) {
|
|
512
|
+
state.result = result;
|
|
513
|
+
if ("status" in result) this.params.harness.forcedConsults.markCancelled(recent);
|
|
514
|
+
else this.params.harness.forcedConsults.markDelivered(recent);
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
535
517
|
});
|
|
518
|
+
if (state) state.promise = tracked;
|
|
519
|
+
return tracked;
|
|
536
520
|
}
|
|
537
521
|
findRecentAgentProxyConsultContext(consultMessage) {
|
|
538
522
|
return this.params.harness.forcedConsults.findRecent(consultMessage);
|
|
@@ -548,80 +532,44 @@ var DiscordRealtimeConsults = class {
|
|
|
548
532
|
const state = recent.context;
|
|
549
533
|
if (!state) return false;
|
|
550
534
|
if (state.providerEpoch !== this.params.providerEpoch()) return true;
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return;
|
|
559
|
-
await this.submitTerminalRealtimeToolResult(callId, session, {
|
|
560
|
-
status: "already_delivered",
|
|
561
|
-
message: "OpenClaw already delivered this answer to Discord voice. Do not repeat it."
|
|
562
|
-
});
|
|
563
|
-
};
|
|
564
|
-
const submitResult = async (result) => {
|
|
565
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return;
|
|
566
|
-
if (state.handledByForcedPlayback && !providerOwnsDelivery) {
|
|
567
|
-
await submitAlreadyDelivered();
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
if (result.status === "fulfilled") {
|
|
571
|
-
await session.submitToolResult(callId, { text: result.text });
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
await session.submitToolResult(callId, { error: result.error });
|
|
575
|
-
};
|
|
576
|
-
if (state.result) {
|
|
577
|
-
logger$2.info(`discord voice: realtime consult reused recent agent result call=${callId || "unknown"} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
|
|
578
|
-
await submitResult(state.result);
|
|
579
|
-
return true;
|
|
535
|
+
const pendingResult = state.result ?? state.promise;
|
|
536
|
+
if (!pendingResult) return false;
|
|
537
|
+
const providerDelivery = state.handledByForcedPlayback && !state.result && session.bridge.supportsToolResultSuppression === false ? createDeferred() : void 0;
|
|
538
|
+
if (providerDelivery) {
|
|
539
|
+
const deliveries = this.providerDeliveries.get(state) ?? /* @__PURE__ */ new Set();
|
|
540
|
+
deliveries.add(providerDelivery);
|
|
541
|
+
this.providerDeliveries.set(state, deliveries);
|
|
580
542
|
}
|
|
581
|
-
|
|
582
|
-
logger$2.info(`discord voice: realtime consult joined in-flight agent result call=${callId || "unknown"} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
|
|
583
|
-
if (state.handledByForcedPlayback && !providerOwnsDelivery) {
|
|
584
|
-
await state.promise.catch(() => void 0);
|
|
585
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return true;
|
|
586
|
-
await submitAlreadyDelivered();
|
|
587
|
-
return true;
|
|
588
|
-
}
|
|
589
|
-
let result;
|
|
590
|
-
try {
|
|
591
|
-
result = {
|
|
592
|
-
status: "fulfilled",
|
|
593
|
-
text: await state.promise
|
|
594
|
-
};
|
|
595
|
-
} catch (error) {
|
|
596
|
-
result = {
|
|
597
|
-
status: "rejected",
|
|
598
|
-
error: formatErrorMessage(error)
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
if (state.providerEpoch !== this.params.providerEpoch()) return true;
|
|
543
|
+
logger$2.info(`discord voice: realtime consult ${state.result ? "reused recent" : "joined in-flight"} agent result call=${callId} speaker=${state.speaker.speakerLabel} owner=${state.speaker.senderIsOwner}`);
|
|
602
544
|
try {
|
|
603
|
-
await
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
545
|
+
const result = await pendingResult;
|
|
546
|
+
if (state.providerEpoch !== this.params.providerEpoch()) return true;
|
|
547
|
+
await this.submitAgentProxyConsultResult(callId, session, result, Boolean(state.handledByForcedPlayback && !providerDelivery));
|
|
548
|
+
if (providerDelivery && state.providerEpoch === this.params.providerEpoch()) state.handledByForcedPlayback = false;
|
|
549
|
+
} finally {
|
|
550
|
+
if (providerDelivery) {
|
|
551
|
+
providerDelivery.resolve();
|
|
552
|
+
const deliveries = this.providerDeliveries.get(state);
|
|
553
|
+
deliveries?.delete(providerDelivery);
|
|
554
|
+
if (deliveries?.size === 0) this.providerDeliveries.delete(state);
|
|
608
555
|
}
|
|
609
|
-
} catch (error) {
|
|
610
|
-
state.settleProviderDelivery = void 0;
|
|
611
|
-
resolveProviderDelivery?.(false);
|
|
612
|
-
throw error;
|
|
613
556
|
}
|
|
614
557
|
return true;
|
|
615
558
|
}
|
|
559
|
+
async submitAgentProxyConsultResult(callId, session, result, alreadyDelivered = false) {
|
|
560
|
+
if ("status" in result) await this.submitTerminalRealtimeToolResult(callId, session, CANCELLED_CONSULT_RESULT);
|
|
561
|
+
else if (alreadyDelivered) await this.submitTerminalRealtimeToolResult(callId, session, {
|
|
562
|
+
status: "already_delivered",
|
|
563
|
+
message: "OpenClaw already delivered this answer to Discord voice. Do not repeat it."
|
|
564
|
+
});
|
|
565
|
+
else await session.submitToolResult(callId, result);
|
|
566
|
+
}
|
|
616
567
|
clearProviderConsultState() {
|
|
617
|
-
for (const
|
|
618
|
-
const state = handle.context;
|
|
619
|
-
if (!state) continue;
|
|
568
|
+
for (const [state, deliveries] of this.providerDeliveries) {
|
|
620
569
|
state.handledByForcedPlayback = false;
|
|
621
|
-
|
|
622
|
-
state.settleProviderDelivery = void 0;
|
|
623
|
-
state.providerDelivery = void 0;
|
|
570
|
+
for (const delivery of deliveries) delivery.resolve();
|
|
624
571
|
}
|
|
572
|
+
this.providerDeliveries.clear();
|
|
625
573
|
this.params.harness.forcedConsults.clear();
|
|
626
574
|
}
|
|
627
575
|
};
|
|
@@ -631,7 +579,7 @@ const logger$1 = createSubsystemLogger("discord/voice");
|
|
|
631
579
|
const DISCORD_REALTIME_CONTROL_SPEECH_DEDUPE_MS = 5e3;
|
|
632
580
|
const DISCORD_REALTIME_OUTPUT_PLAYBACK_WATCHDOG_MARGIN_MS = 3e3;
|
|
633
581
|
const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_MESSAGES = 32;
|
|
634
|
-
const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_BYTES =
|
|
582
|
+
const DISCORD_REALTIME_MAX_RETAINED_EXACT_SPEECH_BYTES = 32768;
|
|
635
583
|
const DISCORD_REALTIME_WAKE_ACKS = [
|
|
636
584
|
"Yeah.",
|
|
637
585
|
"Mm-hmm.",
|
|
@@ -829,7 +777,7 @@ var DiscordRealtimePlayback = class {
|
|
|
829
777
|
return this.outputStream;
|
|
830
778
|
}
|
|
831
779
|
stopAfterOverflow(reason, error) {
|
|
832
|
-
this.params.stopTerminally(
|
|
780
|
+
this.params.stopTerminally();
|
|
833
781
|
this.queuedExactSpeechMessages = [];
|
|
834
782
|
this.exactSpeechState = { status: "idle" };
|
|
835
783
|
this.clearOutputAudio(reason);
|
|
@@ -1018,7 +966,6 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1018
966
|
status: "inactive",
|
|
1019
967
|
generation: 0
|
|
1020
968
|
};
|
|
1021
|
-
this.nextLifecycleGeneration = 0;
|
|
1022
969
|
this.consultToolPolicy = "safe-read-only";
|
|
1023
970
|
this.consultPolicy = "auto";
|
|
1024
971
|
this.wakeNamePolicy = "never";
|
|
@@ -1060,8 +1007,8 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1060
1007
|
onTerminalError: this.params.onTerminalError,
|
|
1061
1008
|
providerId: () => this.realtimeProviderId,
|
|
1062
1009
|
realtimeConfig: () => this.realtimeConfig,
|
|
1063
|
-
stopTerminally: (
|
|
1064
|
-
this.
|
|
1010
|
+
stopTerminally: () => {
|
|
1011
|
+
this.lifecycle.status = "stopped";
|
|
1065
1012
|
this.consults.close();
|
|
1066
1013
|
},
|
|
1067
1014
|
stopped: () => this.isStopped(),
|
|
@@ -1100,11 +1047,10 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1100
1047
|
});
|
|
1101
1048
|
}
|
|
1102
1049
|
async connect() {
|
|
1103
|
-
const lifecycleGeneration =
|
|
1050
|
+
const lifecycleGeneration = this.lifecycle.generation + 1;
|
|
1104
1051
|
this.lifecycle = {
|
|
1105
1052
|
status: "starting",
|
|
1106
|
-
generation: lifecycleGeneration
|
|
1107
|
-
instance: this
|
|
1053
|
+
generation: lifecycleGeneration
|
|
1108
1054
|
};
|
|
1109
1055
|
const configuredProviderId = this.realtimeConfig?.provider?.trim();
|
|
1110
1056
|
if (configuredProviderId) {
|
|
@@ -1157,6 +1103,10 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1157
1103
|
toolPolicy,
|
|
1158
1104
|
consultPolicy
|
|
1159
1105
|
});
|
|
1106
|
+
const onReady = () => {
|
|
1107
|
+
this.markProviderGenerationObserved();
|
|
1108
|
+
if (this.markLifecycleReady(lifecycleGeneration)) this.playback.drainQueuedExactSpeechMessages("provider-ready");
|
|
1109
|
+
};
|
|
1160
1110
|
this.bridge = this.harness.createBridge({
|
|
1161
1111
|
provider: resolved.provider,
|
|
1162
1112
|
cfg: this.params.cfg,
|
|
@@ -1191,11 +1141,11 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1191
1141
|
this.markProviderGenerationObserved();
|
|
1192
1142
|
return this.consults.handleToolCall(event, session);
|
|
1193
1143
|
},
|
|
1194
|
-
onReady
|
|
1195
|
-
|
|
1196
|
-
|
|
1144
|
+
onReady,
|
|
1145
|
+
onEvent: (event) => {
|
|
1146
|
+
this.handleBridgeEvent(event);
|
|
1147
|
+
if (event.direction === "client" && event.type === "session.reconnect.ready") onReady();
|
|
1197
1148
|
},
|
|
1198
|
-
onEvent: (event) => this.handleBridgeEvent(event),
|
|
1199
1149
|
onResponseDone: (outcome) => {
|
|
1200
1150
|
this.markProviderGenerationObserved();
|
|
1201
1151
|
this.playback.handleResponseDone(outcome);
|
|
@@ -1204,10 +1154,16 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1204
1154
|
},
|
|
1205
1155
|
onError: (error) => this.logRealtimeError(formatErrorMessage(error)),
|
|
1206
1156
|
onClose: (reason) => {
|
|
1207
|
-
this.
|
|
1208
|
-
|
|
1157
|
+
if (!this.isStopped()) {
|
|
1158
|
+
this.lifecycle.status = "stopped";
|
|
1159
|
+
this.params.onTerminalError(/* @__PURE__ */ new Error(`Realtime provider closed unexpectedly: ${reason}`));
|
|
1160
|
+
}
|
|
1209
1161
|
}
|
|
1210
1162
|
});
|
|
1163
|
+
if (this.isStopped()) {
|
|
1164
|
+
this.close();
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1211
1167
|
const resolvedModel = readProviderConfigString(resolved.providerConfig, "model") ?? resolved.provider.defaultModel;
|
|
1212
1168
|
const resolvedVoice = readProviderConfigString(resolved.providerConfig, "voice");
|
|
1213
1169
|
const humanParticipantCount = this.humanParticipantCount();
|
|
@@ -1223,7 +1179,7 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1223
1179
|
logger.info(`discord voice: realtime bridge ready mode=${this.params.mode} provider=${resolved.provider.id} model=${resolvedModel ?? "default"} voice=${resolvedVoice ?? "default"}`);
|
|
1224
1180
|
}
|
|
1225
1181
|
close() {
|
|
1226
|
-
this.
|
|
1182
|
+
this.lifecycle.status = "stopped";
|
|
1227
1183
|
this.providerContinuityEpoch += 1;
|
|
1228
1184
|
this.flushSuppressedRealtimeErrors();
|
|
1229
1185
|
this.consults.close();
|
|
@@ -1255,21 +1211,9 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1255
1211
|
}
|
|
1256
1212
|
markLifecycleReady(generation) {
|
|
1257
1213
|
if (this.lifecycle.status !== "starting" && this.lifecycle.status !== "active" || this.lifecycle.generation !== generation) return false;
|
|
1258
|
-
this.lifecycle =
|
|
1259
|
-
status: "active",
|
|
1260
|
-
generation,
|
|
1261
|
-
instance: this
|
|
1262
|
-
};
|
|
1214
|
+
this.lifecycle.status = "active";
|
|
1263
1215
|
return true;
|
|
1264
1216
|
}
|
|
1265
|
-
stopLifecycle(reason) {
|
|
1266
|
-
const generation = this.lifecycle.generation;
|
|
1267
|
-
this.lifecycle = {
|
|
1268
|
-
status: "stopped",
|
|
1269
|
-
generation,
|
|
1270
|
-
reason
|
|
1271
|
-
};
|
|
1272
|
-
}
|
|
1273
1217
|
humanParticipantCount() {
|
|
1274
1218
|
return this.params.getHumanParticipantCount?.() ?? 0;
|
|
1275
1219
|
}
|
|
@@ -1293,11 +1237,7 @@ var DiscordRealtimeVoiceSession = class {
|
|
|
1293
1237
|
resetProviderContinuity(reason) {
|
|
1294
1238
|
if (!this.providerGenerationObserved) return;
|
|
1295
1239
|
this.providerGenerationObserved = false;
|
|
1296
|
-
if (this.lifecycle.status === "active") this.lifecycle =
|
|
1297
|
-
status: "starting",
|
|
1298
|
-
generation: this.lifecycle.generation,
|
|
1299
|
-
instance: this
|
|
1300
|
-
};
|
|
1240
|
+
if (this.lifecycle.status === "active") this.lifecycle.status = "starting";
|
|
1301
1241
|
this.providerContinuityEpoch += 1;
|
|
1302
1242
|
this.consults.resetProviderContinuity();
|
|
1303
1243
|
this.turns.resetProviderContinuity();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { m as normalizeDiscordToken } from "./accounts-
|
|
2
|
-
import { r as fetchDiscord } from "./api-
|
|
3
|
-
import { a as normalizeDiscordSlug } from "./allow-list-
|
|
1
|
+
import { m as normalizeDiscordToken } from "./accounts-CwJQoLjM.js";
|
|
2
|
+
import { r as fetchDiscord } from "./api-BfmEP6wp.js";
|
|
3
|
+
import { a as normalizeDiscordSlug } from "./allow-list-C2JuH-R0.js";
|
|
4
4
|
//#region extensions/discord/src/guilds.ts
|
|
5
5
|
async function listGuilds(token, fetcher, options) {
|
|
6
6
|
return (await fetchDiscord("/users/@me/guilds", token, fetcher, options)).filter((guild) => typeof guild.id === "string" && typeof guild.name === "string").map((guild) => ({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as DiscordApiError, r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-
|
|
3
|
-
import { a as normalizeDiscordSlug } from "./allow-list-
|
|
1
|
+
import { sn as __exportAll } from "./retry-BNCxiyyR.js";
|
|
2
|
+
import { n as DiscordApiError, r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-BfmEP6wp.js";
|
|
3
|
+
import { a as normalizeDiscordSlug } from "./allow-list-C2JuH-R0.js";
|
|
4
4
|
import { t as isDiscordThreadChannelType } from "./channel-type-CVLZyGYS.js";
|
|
5
|
-
import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-
|
|
5
|
+
import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BJ5UZNNT.js";
|
|
6
6
|
//#region extensions/discord/src/resolve-channels.ts
|
|
7
7
|
var resolve_channels_exports = /* @__PURE__ */ __exportAll({ resolveDiscordChannelAllowlist: () => resolveDiscordChannelAllowlist });
|
|
8
8
|
function parseDiscordChannelInput(raw) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-
|
|
3
|
-
import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-
|
|
1
|
+
import { sn as __exportAll } from "./retry-BNCxiyyR.js";
|
|
2
|
+
import { r as fetchDiscord, t as DISCORD_DIRECTORY_LOOKUP_TIMEOUT_MS } from "./api-BfmEP6wp.js";
|
|
3
|
+
import { i as listGuilds, n as filterDiscordGuilds, r as resolveDiscordAllowlistToken, t as buildDiscordUnresolvedResults } from "./resolve-allowlist-common-BJ5UZNNT.js";
|
|
4
4
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
5
|
//#region extensions/discord/src/resolve-users.ts
|
|
6
6
|
var resolve_users_exports = /* @__PURE__ */ __exportAll({ resolveDiscordUserAllowlist: () => resolveDiscordUserAllowlist });
|
|
@@ -74,7 +74,8 @@ async function resolveDiscordUserAllowlist(params) {
|
|
|
74
74
|
});
|
|
75
75
|
continue;
|
|
76
76
|
}
|
|
77
|
-
const
|
|
77
|
+
const allGuilds = await getGuilds();
|
|
78
|
+
const guildList = filterDiscordGuilds(allGuilds, {
|
|
78
79
|
guildId: parsed.guildId,
|
|
79
80
|
guildName: parsed.guildName?.trim()
|
|
80
81
|
});
|