fix(openai): fail realtime voice pre-ready closes

This commit is contained in:
Vincent Koc
2026-05-03 16:58:29 -07:00
parent d057a308f3
commit f88e1f4c1c
3 changed files with 26 additions and 0 deletions

View File

@@ -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 <suffix>` 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.

View File

@@ -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({

View File

@@ -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();
});
});