diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d8b6d14c3d..9e1b63d62de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Google Meet/Twilio: show delegated voice call ID, DTMF, and intro-greeting state in `googlemeet doctor`, and avoid claiming DTMF was sent when no Meet PIN sequence was configured. Refs #72478. Thanks @DougButdorf. - Voice Call/Twilio: send notify-mode initial TwiML directly in the outbound create-call request while keeping conversation and pre-connect DTMF calls webhook-driven, so one-shot notify calls do not depend on a first-answer webhook fetch. Supersedes #72758. Thanks @tyshepps. - Discord/Slack: defer status-reaction cleanup until run finalization so queued, thinking, tool, and terminal reactions no longer flicker during normal progress updates. (#75582) - Discord/voice: leave Discord voice off for text-only configs unless `channels.discord.voice` is explicitly configured, avoiding default `GuildVoiceStates` traffic and idle gateway CPU pressure for bots that do not use `/vc`. Fixes #73753; refs #74044. Thanks @sanchezm86 and @SecureCloudProjO. diff --git a/docs/plugins/google-meet.md b/docs/plugins/google-meet.md index 35f5d267c44..d79084d92c0 100644 --- a/docs/plugins/google-meet.md +++ b/docs/plugins/google-meet.md @@ -1435,6 +1435,8 @@ before entering the PIN. If the phone call is created but the Meet roster never shows the dial-in participant: +- Run `openclaw googlemeet doctor ` to confirm the delegated Twilio + call ID, whether DTMF was queued, and whether the intro greeting was requested. - Run `openclaw voicecall status --call-id ` and confirm the call is still active. - Run `openclaw voicecall tail` and check that Twilio webhooks are arriving at diff --git a/extensions/google-meet/src/cli.test.ts b/extensions/google-meet/src/cli.test.ts index b3ed642d261..98f1c4bee01 100644 --- a/extensions/google-meet/src/cli.test.ts +++ b/extensions/google-meet/src/cli.test.ts @@ -727,6 +727,48 @@ describe("google-meet CLI", () => { } }); + it("prints Twilio session doctor output", async () => { + const stdout = captureStdout(); + try { + await setupCli({ + runtime: { + status: () => ({ + found: true, + session: { + id: "meet_1", + url: "https://meet.google.com/abc-defg-hij", + state: "active", + transport: "twilio", + mode: "realtime", + participantIdentity: "Twilio phone participant", + createdAt: "2026-04-25T00:00:00.000Z", + updatedAt: "2026-04-25T00:00:01.000Z", + realtime: { enabled: true, provider: "openai", toolPolicy: "safe-read-only" }, + twilio: { + dialInNumber: "+15551234567", + pinProvided: true, + dtmfSequence: "ww123456#", + voiceCallId: "call-1", + dtmfSent: true, + introSent: true, + }, + notes: [], + }, + }), + }, + }).parseAsync(["googlemeet", "doctor", "meet_1"], { from: "user" }); + expect(stdout.output()).toContain("session: meet_1"); + expect(stdout.output()).toContain("transport: twilio"); + expect(stdout.output()).toContain("twilio dial-in: +15551234567"); + expect(stdout.output()).toContain("voice call id: call-1"); + expect(stdout.output()).toContain("dtmf sent: yes"); + expect(stdout.output()).toContain("intro sent: yes"); + expect(stdout.output()).not.toContain("audio input active:"); + } finally { + stdout.restore(); + } + }); + it("verifies OAuth refresh without printing secrets", async () => { const fetchMock = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) => jsonResponse({ diff --git a/extensions/google-meet/src/cli.ts b/extensions/google-meet/src/cli.ts index 925fbe18a99..aef8dfa3a9d 100644 --- a/extensions/google-meet/src/cli.ts +++ b/extensions/google-meet/src/cli.ts @@ -256,6 +256,15 @@ function writeDoctorStatus(status: ReturnType): voi writeStdoutLine("state: %s", session.state); writeStdoutLine("transport: %s", session.transport); writeStdoutLine("mode: %s", session.mode); + if (session.twilio) { + writeStdoutLine("twilio dial-in: %s", session.twilio.dialInNumber); + writeStdoutLine("voice call id: %s", formatOptional(session.twilio.voiceCallId)); + writeStdoutLine("dtmf sent: %s", formatBoolean(session.twilio.dtmfSent)); + writeStdoutLine("intro sent: %s", formatBoolean(session.twilio.introSent)); + } + if (!session.chrome) { + continue; + } writeStdoutLine("node: %s", session.chrome?.nodeId ?? "local/none"); writeStdoutLine("audio bridge: %s", session.chrome?.audioBridge?.type ?? "none"); writeStdoutLine( diff --git a/extensions/google-meet/src/runtime.ts b/extensions/google-meet/src/runtime.ts index 0ca69d601e4..1ee625ff665 100644 --- a/extensions/google-meet/src/runtime.ts +++ b/extensions/google-meet/src/runtime.ts @@ -435,7 +435,9 @@ export class GoogleMeetRuntime { } session.notes.push( this.params.config.voiceCall.enabled - ? "Twilio transport delegated the call to the voice-call plugin and sent configured DTMF." + ? dtmfSequence + ? "Twilio transport delegated the call to the voice-call plugin and queued configured DTMF." + : "Twilio transport delegated the call to the voice-call plugin without configured DTMF." : "Twilio transport is an explicit dial plan; voice-call delegation is disabled.", ); }