mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(voice-call): keep active call when replacement setup fails (#117960)
* commit 'b65d010408f23da0247a0f91d8459a6549dab759': fix(voice-call): avoid retaining replaced bridges fix(voice-call): preserve predecessor on provisional close test(voice-call): cover provisional replacement close
This commit is contained in:
@@ -1993,17 +1993,20 @@ describe("RealtimeCallHandler path routing", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("restores the prior transcript owner when replacement bridge creation fails", async () => {
|
||||
it("preserves the predecessor when replacement closes with error during creation", async () => {
|
||||
const callbacks: RealtimeBridgeRequest[] = [];
|
||||
const oldTriggerGreeting = vi.fn();
|
||||
const createBridge = vi.fn((request: RealtimeBridgeRequest) => {
|
||||
callbacks.push(request);
|
||||
if (callbacks.length === 1) {
|
||||
return makeBridge();
|
||||
return makeBridge({ triggerGreeting: oldTriggerGreeting });
|
||||
}
|
||||
request.onTranscript?.("user", "Failed ", false);
|
||||
request.onClose?.("error");
|
||||
throw new Error("replacement bridge failed");
|
||||
});
|
||||
const processEvent = vi.fn();
|
||||
const hangupCall = vi.fn(async () => {});
|
||||
const sharedCallSid = "CA-transcript-rollback";
|
||||
const call = makeCallRecord(sharedCallSid);
|
||||
const handler = makeHandler(undefined, {
|
||||
@@ -2011,6 +2014,7 @@ describe("RealtimeCallHandler path routing", () => {
|
||||
getCallByProviderCallId: vi.fn(() => call),
|
||||
processEvent,
|
||||
},
|
||||
provider: { hangupCall },
|
||||
realtimeProvider: makeRealtimeProvider(createBridge),
|
||||
});
|
||||
const oldServer = await startRealtimeServer(handler);
|
||||
@@ -2043,6 +2047,17 @@ describe("RealtimeCallHandler path routing", () => {
|
||||
expect(createBridge).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
expect(handler.speak(call.callId, "Continue the existing call.")).toEqual({
|
||||
success: true,
|
||||
});
|
||||
expect(oldTriggerGreeting).toHaveBeenCalledWith("Continue the existing call.");
|
||||
expect(hangupCall).not.toHaveBeenCalled();
|
||||
expect(
|
||||
processEvent.mock.calls
|
||||
.map(([event]) => event as NormalizedEvent)
|
||||
.filter((event) => event.type === "call.ended"),
|
||||
).toHaveLength(0);
|
||||
|
||||
callbacks[0]?.onTranscript?.("user", "caller", true);
|
||||
await waitForRealtimeTest(() => {
|
||||
expect(
|
||||
|
||||
@@ -771,6 +771,7 @@ export class RealtimeCallHandler {
|
||||
typeof this.providerConfig.interruptResponseOnInputAudio === "boolean"
|
||||
? this.providerConfig.interruptResponseOnInputAudio
|
||||
: undefined;
|
||||
const hadPredecessorOnAdmission = this.activeBridgesByCallId.has(callId);
|
||||
// Providers may close synchronously before createBridge returns; no consult can exist yet.
|
||||
const nativeConsultOwner: { current?: ActiveRealtimeVoiceBridge } = {};
|
||||
// Provisional ownership accepts callbacks fired during createBridge. Commit
|
||||
@@ -1014,7 +1015,12 @@ export class RealtimeCallHandler {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.close(1011, "Bridge disconnected");
|
||||
}
|
||||
if (owner && !ownsCallState) {
|
||||
// A provisional replacement may fail before its bridge owner is assigned.
|
||||
// The active predecessor still owns call termination until creation succeeds.
|
||||
if (
|
||||
(owner && !ownsCallState) ||
|
||||
(!owner && hadPredecessorOnAdmission && this.activeBridgesByCallId.has(callId))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
emitCallEnd("error");
|
||||
|
||||
Reference in New Issue
Block a user