diff --git a/CHANGELOG.md b/CHANGELOG.md index c969315de09..5ba35abd2c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,10 +78,9 @@ Docs: https://docs.openclaw.ai - Discord/ack reactions: keep automatic ACK reaction auth on the active hydrated Discord account so SecretRef-backed and non-default-account reactions stop falling back to stale default config resolution. (#60081) Thanks @FunJim. - Telegram/model switching: render non-default `/model` callback confirmations with HTML formatting so Telegram shows the selected model in bold instead of raw `**...**` markers. (#60042) Thanks @GitZhangChi. - Plugins/update: allow `openclaw plugins update` to use `--dangerously-force-unsafe-install` for built-in dangerous-code false positives during plugin updates. (#60066) Thanks @huntharo. -<<<<<<< HEAD - Gateway/auth: disconnect shared-auth websocket sessions only for effective auth rotations on restart-capable config writes, and keep `config.set` auth edits from dropping still-valid live sessions. (#60387) Thanks @mappel-nv. - Control UI/chat: keep the Stop button visible during tool-only execution so abortable runs do not fall back to Send while tools are still running. (#54528) thanks @chziyue. -- Gateway/auth: disconnect shared-auth websocket sessions only for effective auth rotations on restart-capable config writes, and keep `config.set` auth edits from dropping still-valid live sessions. (#60387) Thanks @mappel-nv. +- Discord/voice: make READY auto-join fire-and-forget while keeping the shorter initial voice-connect timeout separate from the longer playback-start wait. (#60345) Thanks @geekhuashan. ## 2026.4.2 diff --git a/extensions/discord/src/voice/manager.e2e.test.ts b/extensions/discord/src/voice/manager.e2e.test.ts index 6c572148cb6..fa5cb815588 100644 --- a/extensions/discord/src/voice/manager.e2e.test.ts +++ b/extensions/discord/src/voice/manager.e2e.test.ts @@ -292,6 +292,16 @@ describe("DiscordVoiceManager", () => { ); }); + it("keeps the shorter timeout for initial voice connection readiness", async () => { + const connection = createConnectionMock(); + joinVoiceChannelMock.mockReturnValueOnce(connection); + const manager = createManager(); + + await manager.join({ guildId: "g1", channelId: "1001" }); + + expect(entersStateMock).toHaveBeenCalledWith(connection, "ready", 15_000); + }); + it("stores guild metadata on joined voice sessions", async () => { const manager = createManager(); diff --git a/extensions/discord/src/voice/manager.ts b/extensions/discord/src/voice/manager.ts index 2ba10d0246c..5aa365f2b7d 100644 --- a/extensions/discord/src/voice/manager.ts +++ b/extensions/discord/src/voice/manager.ts @@ -32,6 +32,7 @@ const CHANNELS = 2; const BIT_DEPTH = 16; const MIN_SEGMENT_SECONDS = 0.35; const SILENCE_DURATION_MS = 1_000; +const VOICE_CONNECT_READY_TIMEOUT_MS = 15_000; const PLAYBACK_READY_TIMEOUT_MS = 60_000; const SPEAKING_READY_TIMEOUT_MS = 60_000; const DECRYPT_FAILURE_WINDOW_MS = 30_000; @@ -388,7 +389,7 @@ export class DiscordVoiceManager { await voiceSdk.entersState( connection, voiceSdk.VoiceConnectionStatus.Ready, - PLAYBACK_READY_TIMEOUT_MS, + VOICE_CONNECT_READY_TIMEOUT_MS, ); logVoiceVerbose(`join: connected to guild ${guildId} channel ${channelId}`); } catch (err) { diff --git a/src/agents/pi-embedded-runner/extra-params.ts b/src/agents/pi-embedded-runner/extra-params.ts index c8f92fbcb4f..4a8881ba739 100644 --- a/src/agents/pi-embedded-runner/extra-params.ts +++ b/src/agents/pi-embedded-runner/extra-params.ts @@ -206,26 +206,33 @@ function createStreamFnWithExtraParams( if (typeof extraParams.openaiWsWarmup === "boolean") { streamParams.openaiWsWarmup = extraParams.openaiWsWarmup; } - const cacheRetention = resolveCacheRetention( + const initialCacheRetention = resolveCacheRetention( extraParams, provider, typeof model?.api === "string" ? model.api : undefined, typeof model?.id === "string" ? model.id : undefined, ); - if (cacheRetention) { - streamParams.cacheRetention = cacheRetention; + if (Object.keys(streamParams).length > 0 || initialCacheRetention) { + const debugParams = initialCacheRetention + ? { ...streamParams, cacheRetention: initialCacheRetention } + : streamParams; + log.debug(`creating streamFn wrapper with params: ${JSON.stringify(debugParams)}`); } - if (Object.keys(streamParams).length === 0) { - return undefined; - } - - log.debug(`creating streamFn wrapper with params: ${JSON.stringify(streamParams)}`); - const underlying = baseStreamFn ?? streamSimple; - const wrappedStreamFn: StreamFn = (model, context, options) => { - return underlying(model, context, { + const wrappedStreamFn: StreamFn = (callModel, context, options) => { + const cacheRetention = resolveCacheRetention( + extraParams, + provider, + typeof callModel.api === "string" ? callModel.api : undefined, + typeof callModel.id === "string" ? callModel.id : undefined, + ); + if (Object.keys(streamParams).length === 0 && !cacheRetention) { + return underlying(callModel, context, options); + } + return underlying(callModel, context, { ...streamParams, + ...(cacheRetention ? { cacheRetention } : {}), ...options, }); };