mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(voice-call): cancel native consults on teardown
This commit is contained in:
@@ -273,13 +273,25 @@ type ForcedConsultState = {
|
||||
};
|
||||
|
||||
type NativeConsultState = {
|
||||
owner: ActiveRealtimeVoiceBridge;
|
||||
startedAt: number;
|
||||
promise: Promise<unknown>;
|
||||
cancellation: Promise<void>;
|
||||
cancel: () => void;
|
||||
partialUserTranscript?: string;
|
||||
};
|
||||
|
||||
type NativeConsultOutcome = { kind: "completed"; result: unknown } | { kind: "cancelled" };
|
||||
|
||||
type TelephonyCloseReason = "completed" | "error";
|
||||
|
||||
async function waitForNativeConsult(state: NativeConsultState): Promise<NativeConsultOutcome> {
|
||||
return await Promise.race([
|
||||
state.promise.then((result) => ({ kind: "completed", result }) as const),
|
||||
state.cancellation.then(() => ({ kind: "cancelled" }) as const),
|
||||
]);
|
||||
}
|
||||
|
||||
function appendRecentTalkEventMetadata(
|
||||
call: CallRecord | null | undefined,
|
||||
event: TalkEvent,
|
||||
@@ -923,6 +935,7 @@ export class RealtimeCallHandler {
|
||||
this.activeBridgesByCallId.delete(callSid);
|
||||
this.activeTelephonyClosersByCallId.delete(callId);
|
||||
this.activeTelephonyClosersByCallId.delete(callSid);
|
||||
this.cancelNativeConsult(callId, session);
|
||||
this.clearUserTranscriptState(callId);
|
||||
harness.finishOutputAudio(reason);
|
||||
harness.emit({
|
||||
@@ -992,6 +1005,7 @@ export class RealtimeCallHandler {
|
||||
this.activeBridgesByCallId.delete(callSid);
|
||||
this.activeTelephonyClosersByCallId.delete(callId);
|
||||
this.activeTelephonyClosersByCallId.delete(callSid);
|
||||
this.cancelNativeConsult(callId, session);
|
||||
this.clearUserTranscriptState(callId);
|
||||
this.forcedConsultsByCallId.delete(callId);
|
||||
harness.close();
|
||||
@@ -1054,6 +1068,15 @@ export class RealtimeCallHandler {
|
||||
this.clearRecentFinalUserTranscript(callId);
|
||||
}
|
||||
|
||||
private cancelNativeConsult(callId: string, owner: ActiveRealtimeVoiceBridge): void {
|
||||
const state = this.nativeConsultsInFlightByCallId.get(callId);
|
||||
if (!state || state.owner !== owner) {
|
||||
return;
|
||||
}
|
||||
this.nativeConsultsInFlightByCallId.delete(callId);
|
||||
state.cancel();
|
||||
}
|
||||
|
||||
private resolveUserTranscriptContext(callId: string): string | undefined {
|
||||
return (
|
||||
this.partialUserTranscriptsByCallId.get(callId) ??
|
||||
@@ -1412,13 +1435,24 @@ export class RealtimeCallHandler {
|
||||
`[voice-call] realtime tool call sharing in-flight agent consult callId=${callId} ageMs=${Date.now() - existingNativeConsult.startedAt}`,
|
||||
);
|
||||
await submitWorkingResponse();
|
||||
await submitFinalToolResult(await existingNativeConsult.promise);
|
||||
const outcome = await waitForNativeConsult(existingNativeConsult);
|
||||
if (outcome.kind === "cancelled") {
|
||||
return;
|
||||
}
|
||||
await submitFinalToolResult(outcome.result);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancel = () => {};
|
||||
const cancellation = new Promise<void>((resolve) => {
|
||||
cancel = resolve;
|
||||
});
|
||||
const state: NativeConsultState = {
|
||||
owner: bridge,
|
||||
startedAt,
|
||||
promise: Promise.resolve(),
|
||||
cancellation,
|
||||
cancel,
|
||||
};
|
||||
const workingSubmission = submitWorkingResponse();
|
||||
state.promise = workingSubmission.then(async () => {
|
||||
@@ -1443,7 +1477,11 @@ export class RealtimeCallHandler {
|
||||
});
|
||||
this.nativeConsultsInFlightByCallId.set(callId, state);
|
||||
try {
|
||||
const result = await state.promise;
|
||||
const outcome = await waitForNativeConsult(state);
|
||||
if (outcome.kind === "cancelled") {
|
||||
return;
|
||||
}
|
||||
const result = outcome.result;
|
||||
const status =
|
||||
result && typeof result === "object" && !Array.isArray(result) && "error" in result
|
||||
? "error"
|
||||
|
||||
Reference in New Issue
Block a user