From dfa14001a443507183333fae20020ffbe3dd43fc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 6 Apr 2026 18:42:07 +0100 Subject: [PATCH] fix: harden discord voice receive recovery (#41536) (thanks @wit-oc) --- CHANGELOG.md | 2 ++ extensions/discord/src/voice/manager.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ba375e58b..4c0e142733e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai - Gateway/containers: auto-bind to `0.0.0.0` during container startup for Docker and Podman compatibility, while keeping host-side status and doctor checks on the hardened loopback default when `gateway.bind` is unset. (#61818) Thanks @openperf. - Gateway/status: probe local TLS gateways over `wss://`, forward the local cert fingerprint for self-signed loopback probes, and warn when the local TLS runtime cannot load the configured cert. (#61935) Thanks @ThanhNguyxn07. - Slack/threading: keep legacy thread stickiness for real replies when older callers omit `isThreadReply`, while still honoring `replyToMode` for Slack's auto-created top-level `thread_ts`. (#61835) Thanks @kaonash. +- Discord/voice: re-arm DAVE receive passthrough without suppressing decrypt-failure rejoin recovery, and clear capture state before finalize teardown so rapid speaker restarts keep their next utterance. (#41536) Thanks @wit-oc. - Providers/Google: recognize Gemma model ids in native Google forward-compat resolution, keep the requested provider when cloning fallback templates, and force Gemma reasoning off so Gemma 4 routes stop failing through the Google catalog fallback. (#61507) Thanks @eyjohn. - Providers/Anthropic: skip `service_tier` injection for OAuth-authenticated stream wrapper requests so Claude OAuth requests stop failing with HTTP 401. (#60356) thanks @openperf. - Providers/OpenAI: keep WebSocket text buffered until a real assistant phase arrives, even when text deltas land before a phaseless `output_item.added` announcement. (#61954) Thanks @100yenadmin. @@ -59,6 +60,7 @@ Docs: https://docs.openclaw.ai - Tools/web_fetch and web_search: fix `TypeError: fetch failed` caused by undici 8.0 enabling HTTP/2 by default; pinned SSRF-guard dispatchers now explicitly set `allowH2: false` to restore HTTP/1.1 behavior and keep the custom DNS-pinning lookup compatible. (#61738, #61777) Thanks @zozo123. - Agents/session keys: backfill `sessionKey` from `sessionId` in the embedded PI runner when callers omit it, so hooks, LCM, and compaction receive a valid key; also normalize whitespace-only session keys to `undefined` before downstream consumers see them. (#60555) Thanks @100yenadmin. - Plugins/provider hooks: stop recursive provider snapshot loads from overflowing the stack during plugin initialization, while still preserving cached nested provider-hook results. (#61922, #61938, #61946, #61951) + ## 2026.4.5 ### Breaking diff --git a/extensions/discord/src/voice/manager.ts b/extensions/discord/src/voice/manager.ts index 24f6e1ef5d8..6c5366fb74e 100644 --- a/extensions/discord/src/voice/manager.ts +++ b/extensions/discord/src/voice/manager.ts @@ -176,8 +176,14 @@ function isAbortLikeError(err: unknown): boolean { if (!err || typeof err !== "object") { return false; } - const name = "name" in err ? String((err as { name?: unknown }).name ?? "") : ""; - const message = "message" in err ? String((err as { message?: unknown }).message ?? "") : ""; + const name = + "name" in err && typeof (err as { name?: unknown }).name === "string" + ? (err as { name: string }).name + : ""; + const message = + "message" in err && typeof (err as { message?: unknown }).message === "string" + ? (err as { message: string }).message + : ""; return ( name === "AbortError" || message.includes("The operation was aborted") || @@ -653,11 +659,7 @@ export class DiscordVoiceManager { .catch((err) => logger.warn(`discord voice: playback failed: ${formatErrorMessage(err)}`)); } - private clearCaptureFinalizeTimer( - entry: VoiceSessionEntry, - userId: string, - generation?: number, - ) { + private clearCaptureFinalizeTimer(entry: VoiceSessionEntry, userId: string, generation?: number) { const scheduled = entry.captureFinalizeTimers.get(userId); if (!scheduled || (generation !== undefined && scheduled.generation !== generation)) { return false;