mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(google-meet): keep realtime Twilio joins alive
This commit is contained in:
committed by
Peter Steinberger
parent
0c1df35315
commit
b2f2185348
@@ -28,7 +28,7 @@ describe("Google Meet voice-call gateway", () => {
|
||||
gatewayMocks.startGatewayClientWhenEventLoopReady.mockClear();
|
||||
});
|
||||
|
||||
it("starts Twilio Meet calls, sends delayed DTMF, then speaks the intro", async () => {
|
||||
it("starts Twilio Meet calls, sends delayed DTMF, then speaks the intro without TwiML fallback", async () => {
|
||||
const config = resolveGoogleMeetConfig({
|
||||
voiceCall: {
|
||||
gatewayUrl: "ws://127.0.0.1:18789",
|
||||
@@ -70,10 +70,39 @@ describe("Google Meet voice-call gateway", () => {
|
||||
"voicecall.speak",
|
||||
{
|
||||
callId: "call-1",
|
||||
allowTwimlFallback: false,
|
||||
message: "Say exactly: I'm here and listening.",
|
||||
},
|
||||
{ timeoutMs: 30_000 },
|
||||
);
|
||||
expect(gatewayMocks.request).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("skips the intro without failing when the realtime bridge is not ready", async () => {
|
||||
gatewayMocks.request
|
||||
.mockResolvedValueOnce({ callId: "call-1" })
|
||||
.mockResolvedValueOnce({ success: true })
|
||||
.mockResolvedValueOnce({ success: false, error: "No active realtime bridge for call" });
|
||||
const config = resolveGoogleMeetConfig({
|
||||
voiceCall: {
|
||||
gatewayUrl: "ws://127.0.0.1:18789",
|
||||
dtmfDelayMs: 1,
|
||||
postDtmfSpeechDelayMs: 1,
|
||||
},
|
||||
});
|
||||
const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
|
||||
|
||||
const result = await joinMeetViaVoiceCallGateway({
|
||||
config,
|
||||
dialInNumber: "+15551234567",
|
||||
dtmfSequence: "123456#",
|
||||
logger,
|
||||
message: "Say exactly: I'm here and listening.",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({ callId: "call-1", dtmfSent: true, introSent: false });
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Skipped intro speech because realtime bridge was not ready"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -145,17 +145,23 @@ export async function joinMeetViaVoiceCallGateway(params: {
|
||||
"voicecall.speak",
|
||||
{
|
||||
callId: start.callId,
|
||||
allowTwimlFallback: false,
|
||||
message: params.message,
|
||||
},
|
||||
{ timeoutMs: params.config.voiceCall.requestTimeoutMs },
|
||||
)) as VoiceCallSpeakResult;
|
||||
if (spoken.success === false) {
|
||||
throw new Error(spoken.error || "voicecall.speak failed");
|
||||
params.logger?.warn?.(
|
||||
`[google-meet] Skipped intro speech because realtime bridge was not ready: ${
|
||||
spoken.error || "voicecall.speak failed"
|
||||
}`,
|
||||
);
|
||||
} else {
|
||||
introSent = true;
|
||||
params.logger?.info(
|
||||
`[google-meet] Intro speech requested after Meet dial sequence: callId=${start.callId}`,
|
||||
);
|
||||
}
|
||||
introSent = true;
|
||||
params.logger?.info(
|
||||
`[google-meet] Intro speech requested after Meet dial sequence: callId=${start.callId}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
callId: start.callId,
|
||||
|
||||
@@ -466,6 +466,30 @@ describe("voice-call plugin", () => {
|
||||
expect(respond.mock.calls[0]).toEqual([true, { success: true }]);
|
||||
});
|
||||
|
||||
it("does not fall back to one-shot TwiML speak when realtime-only speech is requested", async () => {
|
||||
runtimeStub.config.realtime.enabled = true;
|
||||
const { methods } = setup({ provider: "mock" });
|
||||
const handler = methods.get("voicecall.speak") as
|
||||
| ((ctx: {
|
||||
params: Record<string, unknown>;
|
||||
respond: ReturnType<typeof vi.fn>;
|
||||
}) => Promise<void>)
|
||||
| undefined;
|
||||
const respond = vi.fn();
|
||||
|
||||
await handler?.({
|
||||
params: { allowTwimlFallback: false, callId: "call-1", message: "hello" },
|
||||
respond,
|
||||
});
|
||||
|
||||
expect(runtimeStub.webhookServer.speakRealtime).toHaveBeenCalledWith("call-1", "hello");
|
||||
expect(runtimeStub.manager.speak).not.toHaveBeenCalled();
|
||||
expect(respond.mock.calls[0]).toEqual([
|
||||
true,
|
||||
{ success: false, error: "No active realtime bridge for call" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports ended call history when speaking to a stale call", async () => {
|
||||
runtimeStub.manager.getCall = vi.fn(() => undefined);
|
||||
runtimeStub.manager.getCallByProviderCallId = vi.fn(() => undefined);
|
||||
|
||||
@@ -504,6 +504,13 @@ export default definePluginEntry({
|
||||
respond(true, { success: true });
|
||||
return;
|
||||
}
|
||||
if (params?.allowTwimlFallback === false) {
|
||||
respond(true, {
|
||||
success: false,
|
||||
error: realtimeResult.error ?? "Realtime bridge is not active",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const result = await request.rt.manager.speak(request.callId, request.message);
|
||||
if (!result.success) {
|
||||
|
||||
Reference in New Issue
Block a user