From f88e1f4c1c165af2797dfe6890d5b69390d62153 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 3 May 2026 16:58:29 -0700 Subject: [PATCH] fix(openai): fail realtime voice pre-ready closes --- CHANGELOG.md | 1 + .../openai/realtime-voice-provider.test.ts | 21 +++++++++++++++++++ extensions/openai/realtime-voice-provider.ts | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3a7ebba88..7b897786055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai - Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc. - Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys. +- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc. - QA/cache: require the full `CACHE-OK ` marker before live cache probes stop retrying, so suffix-only prose cannot hide a broken probe response. Thanks @vincentkoc. - Slack/Matrix: avoid creating blank progress-draft messages when `streaming.progress.label=false` and progress tool lines are disabled. Thanks @vincentkoc. - QA/Matrix: keep the mock OpenAI tool-progress provider aligned with exact-marker Matrix prompts so the hardened live preview scenario still forces a deterministic read before final delivery. Thanks @vincentkoc. diff --git a/extensions/openai/realtime-voice-provider.test.ts b/extensions/openai/realtime-voice-provider.test.ts index 96224206501..04a08cd2ccf 100644 --- a/extensions/openai/realtime-voice-provider.test.ts +++ b/extensions/openai/realtime-voice-provider.test.ts @@ -394,6 +394,27 @@ describe("buildOpenAIRealtimeVoiceProvider", () => { expect(bridge.isConnected()).toBe(false); }); + it("rejects connection when the socket closes before session readiness", async () => { + const provider = buildOpenAIRealtimeVoiceProvider(); + const bridge = provider.createBridge({ + providerConfig: { apiKey: "sk-test" }, // pragma: allowlist secret + onAudio: vi.fn(), + onClearAudio: vi.fn(), + }); + const connecting = bridge.connect(); + const socket = FakeWebSocket.instances[0]; + if (!socket) { + throw new Error("expected bridge to create a websocket"); + } + + socket.readyState = FakeWebSocket.OPEN; + socket.emit("open"); + socket.close(1006, "session closed"); + + await expect(connecting).rejects.toThrow("OpenAI realtime connection closed before ready"); + expect(bridge.isConnected()).toBe(false); + }); + it("can request PCM16 24 kHz realtime audio for Chrome command-pair bridges", async () => { const provider = buildOpenAIRealtimeVoiceProvider(); const bridge = provider.createBridge({ diff --git a/extensions/openai/realtime-voice-provider.ts b/extensions/openai/realtime-voice-provider.ts index a28590c0737..8ba56d613c2 100644 --- a/extensions/openai/realtime-voice-provider.ts +++ b/extensions/openai/realtime-voice-provider.ts @@ -425,6 +425,10 @@ class OpenAIRealtimeVoiceBridge implements RealtimeVoiceBridge { this.config.onClose?.("completed"); return; } + if (!this.sessionConfigured && !settled) { + settleReject(new Error("OpenAI realtime connection closed before ready")); + return; + } void this.attemptReconnect(); }); });