fix(voice-call): auto-respond on the webhook event transcript path#79336
fix(voice-call): auto-respond on the webhook event transcript path#79336dvy wants to merge 1 commit into
Conversation
|
Codex review: needs changes before merge. Reviewed July 3, 2026, 5:04 PM ET / 21:04 UTC. Summary PR surface: Source +57, Tests +202, Docs +1. Total +260 across 4 files. Reproducibility: yes. by source inspection: Telnyx and Plivo provider callbacks can emit final Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Refresh this PR or a narrow replacement on current main, preserve the webhook auto-response handoff with waiter/replay guards, drop the changelog hunk, update the Telnyx comment, and rerun focused voice-call validation. Do we have a high-confidence way to reproduce the issue? Yes, by source inspection: Telnyx and Plivo provider callbacks can emit final Is this the best way to solve the issue? Yes, with cleanup: adding the missing webhook-server handoff after final speech is the narrow owner-boundary fix, while moving response generation into manager events would couple manager state handling to runtime response side effects. The branch still needs refresh and small cleanup before it is the best merge artifact. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 010b61746379. Label changesLabel justifications:
Evidence reviewedPR surface: Source +57, Tests +202, Docs +1. Total +260 across 4 files. View PR surface stats
Acceptance criteria:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
bbf7101 to
50e320a
Compare
50e320a to
96c1520
Compare
b1de31b to
e1d519f
Compare
e1d519f to
0e9ba4a
Compare
|
This pull request has been automatically marked as stale due to inactivity. |
|
Landed the maintained replacement in #100255, preserving your contribution credit and consolidating webhook/media transcript auto-response into one guarded handoff path. Canonical merge: The original fork history was no longer practical to refresh safely across rewritten |
Closes #79118.
Summary
processParsedEvents(the generic webhook event path used by providers without OpenClaw streaming integration — Telnyx and Plivo today) to firehandleInboundResponseafter a finalcall.speechevent for inbound or conversation-mode calls, mirroring the path that already runs for Twilio Media Streams in theonTranscriptcallback. Gated onmanager.processEventactually appending a user transcript entry so replays and turn-token mismatches stay idempotent.CallManager.hasTranscriptWaiter(callId)so the webhook layer can avoid double-firing when a programmatic turn-aware flow (e.g.continueCall) is already lined up to consume the transcript.event.callIdshapes: the manager UUID (outbound calls carry it viaclient_state) and the providerCallId fallback (inbound Telnyx calls don't echoclient_state, so the normalized event'scallIdis the call control ID).Why
Reported in #79118: on Telnyx (and any provider whose final user transcripts arrive through OpenClaw's generic webhook event path rather than a media-stream
onTranscriptcallback), inbound calls answer, the greeting plays, the user transcript is captured to the call record, and then nothing — the bot never speaks again. The call sits inlisteninguntil the caller hangs up. Note that Telnyx and Plivo are streaming-capable carriers; OpenClaw's plugin just doesn't wire up streaming integration for them today (see #79324 for the in-flight Telnyx streaming work).Root cause is a single missing call-site.
handleInboundResponsehad exactly one invocation, gated behind the Twilio Media StreamsonTranscriptcallback (webhook.ts:391). The generic webhookcall.speechhandler inmanager/events.ts:238records the transcript and transitions tolisteningbut never invokes the auto-response. Provider webhook events flow throughprocessParsedEvents→manager.processEvent, missing the auto-response handoff entirely.The fix is the smallest viable change: a peek at waiter state before
manager.processEvent(which clears the waiter), then firehandleInboundResponseafterward when no programmatic flow is waiting and the call is inbound or in conversation mode. Path-1 (Twilio Media Streams) is intentionally untouched — no behavior change for streaming providers.Real Behavior Proof
Behavior or issue addressed: Telnyx inbound calls (and any provider whose final user transcripts arrive through OpenClaw's generic webhook event path rather than a media-stream
onTranscriptcallback) answer and play the configured greeting, capture the user's final speech transcript to the call record, but the bot never replies —processParsedEventsonly forwards the event to the manager and never invokeshandleInboundResponse. This PR mirrors the Twilio media-stream auto-response handoff into the webhook event path, gated on actual transcript persistence to preserve manager replay/dedupe semantics. Telnyx and Plivo are streaming-capable carriers; OpenClaw's plugin just doesn't wire up streaming integration for them today (feat(voice-call): add Telnyx realtime media streams #79324 is the in-flight Telnyx streaming work).Real environment tested: Linux home server running OpenClaw
2026.5.7gateway with@openclaw/[email protected]plugin, configured for provider Telnyx (Call Control v2). Regulated DE DID, BNetzA-cleared, Telnyx number statusactive. Inbound webhook served at127.0.0.1:3334/voice/webhookand exposed publicly via Cloudflare Tunnel with full Telnyx signature verification active (TELNYX_PUBLIC_KEY). Inbound policyallowlist, GermaninboundGreeting, per-numberresponseSystemPrompt,streaming.enabled: false,realtime.enabled: false. Because npm only ships the bundled plugin, the source-equivalent fix was applied as a small local patch to thedist/runtime-entry-*.jschunk file carrying the exact sameprocessParsedEventslogic this PR adds.Exact steps or command run after this patch:
~/.openclaw/npm/node_modules/@openclaw/voice-call/dist/runtime-entry-*.js(idempotent shell tool, refuses to apply if upstream pattern shifts).systemctl --user restart openclaw-gateway.service.Evidence after fix: redacted runtime logs from
journalctl --user -u openclaw-gateway.serviceafter the test call. The[voice-call] Auto-responding to inbound call …line is the exact log statement that never appears on current main for the generic webhook event path; with the patch it appears once per final user transcript and the bot's reply is rendered through Telnyx-hosted TTS and audible to the caller.Persisted multi-turn call record copied live from
~/.openclaw/voice-calls/calls.jsonl(redacted, four user turns each followed by a bot reply):{ "callId": "9df40ff1…", "provider": "telnyx", "direction": "inbound", "state": "completed", "from": "+49***REDACTED***", "to": "+49***REDACTED***", "endReason": "completed", "transcript": [ { "speaker": "bot", "text": "Hallo, was kann ich für dich tun?", "isFinal": true }, { "speaker": "user", "text": "hello how are you", "isFinal": true }, { "speaker": "bot", "text": "I'm good, thanks. How can I help you today?", "isFinal": true }, { "speaker": "user", "text": " right now", "isFinal": true }, { "speaker": "bot", "text": "What would you like me to do right now?", "isFinal": true }, { "speaker": "user", "text": " I want you to tell me which large language model are you","isFinal": true }, { "speaker": "bot", "text": "I'm running on OpenAI GPT 5.5 right now, inside OpenClaw.","isFinal": true }, { "speaker": "user", "text": " okay thank you bye bye", "isFinal": true }, { "speaker": "bot", "text": "You're welcome. Bye!", "isFinal": true } ] }Before the fix, only the first two transcript entries existed (the bot greeting and one user turn) and
endReasoncame from the caller hanging up after silence rather than from the bot saying goodbye.Observed result after fix: caller heard each bot reply audibly through the call. Multi-turn conversation worked end-to-end. Conversation ended naturally (caller said "okay thank you bye bye" → bot replied → caller hung up). No regressions observed in outbound calling or any other voice-call flow on the same gateway during the test session.
What was not tested:
processParsedEventscode path covers it and the fix is provider-agnostic, but I don't have a Plivo number to dial.webhook.test.ts > VoiceCallWebhookServer media stream auto-responsecontinues to pass.transcriptWaiterand a webhook transcript event. Covered by the unit test "does not fire handleInboundResponse when a transcript waiter is pending" but not exercised live.Verification
pnpm test extensions/voice-call/src/webhook.test.ts— 38/38 pass (34 pre-existing + 4 new under thewebhook event path auto-response (#79118)describe block)pnpm test extensions/voice-call/src/manager/events.test.ts extensions/voice-call/src/manager.closed-loop.test.ts extensions/voice-call/src/providers/telnyx.test.ts— all passpnpm check:changed— exit 0 (typecheck extensions, typecheck extension tests, lint extensions via oxlint, runtime sidecar loader guard, runtime import cycles, duplicate scan target coverage)Notes
call.speechpath #79118; the live call setup that produced the proof above is the same setup described in the issue body.