fix(voice-call): accept externally-initiated Twilio outbound-api calls#31181
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6adf336370
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const isUnregisteredWebhookCall = | ||
| !call && | ||
| event.providerCallId && | ||
| (event.direction === "inbound" || event.direction === "outbound"); |
There was a problem hiding this comment.
Preserve outbound direction for auto-registered calls
Including event.direction === "outbound" in this auto-registration branch routes externally initiated outbound-api events through createInboundCall, which hardcodes direction: "inbound" and inbound metadata. That misclassifies these calls and changes downstream behavior that depends on direction (for example, the webhook auto-response gate in extensions/voice-call/src/webhook.ts treats inbound calls as always conversational), so outbound-api calls can be handled as inbound conversations unexpectedly. Register outbound-api calls with outbound direction (or a dedicated helper) instead of the inbound constructor path.
Useful? React with 👍 / 👎.
Greptile SummaryCorrectly expands auto-registration to cover externally-initiated Twilio Critical Issue:
Other Issues:
Confidence Score: 2/5
Last reviewed commit: 6adf336 |
| // Call should be registered in activeCalls and providerCallIdMap | ||
| expect(ctx.activeCalls.size).toBe(1); | ||
| expect(ctx.providerCallIdMap.get("CA-external-123")).toBeDefined(); | ||
| const call = [...ctx.activeCalls.values()][0]; | ||
| expect(call?.providerCallId).toBe("CA-external-123"); | ||
| expect(call?.from).toBe("+15550000000"); | ||
| expect(call?.to).toBe("+15559876543"); |
There was a problem hiding this comment.
Test doesn't verify that call.direction matches the event's direction. Add:
| // Call should be registered in activeCalls and providerCallIdMap | |
| expect(ctx.activeCalls.size).toBe(1); | |
| expect(ctx.providerCallIdMap.get("CA-external-123")).toBeDefined(); | |
| const call = [...ctx.activeCalls.values()][0]; | |
| expect(call?.providerCallId).toBe("CA-external-123"); | |
| expect(call?.from).toBe("+15550000000"); | |
| expect(call?.to).toBe("+15559876543"); | |
| // Call should be registered in activeCalls and providerCallIdMap | |
| expect(ctx.activeCalls.size).toBe(1); | |
| expect(ctx.providerCallIdMap.get("CA-external-123")).toBeDefined(); | |
| const call = [...ctx.activeCalls.values()][0]; | |
| expect(call?.direction).toBe("outbound"); | |
| expect(call?.providerCallId).toBe("CA-external-123"); | |
| expect(call?.from).toBe("+15550000000"); | |
| expect(call?.to).toBe("+15559876543"); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/voice-call/src/manager/events.test.ts
Line: 253-259
Comment:
Test doesn't verify that `call.direction` matches the event's direction. Add:
```suggestion
// Call should be registered in activeCalls and providerCallIdMap
expect(ctx.activeCalls.size).toBe(1);
expect(ctx.providerCallIdMap.get("CA-external-123")).toBeDefined();
const call = [...ctx.activeCalls.values()][0];
expect(call?.direction).toBe("outbound");
expect(call?.providerCallId).toBe("CA-external-123");
expect(call?.from).toBe("+15550000000");
expect(call?.to).toBe("+15559876543");
```
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
Consider passing the event's direction to Prompt To Fix With AIThis is a comment left during a code review.
Path: extensions/voice-call/src/manager/events.ts
Line: 145
Comment:
`createInboundCall()` hardcodes `direction: "inbound"` (line 74), but is now used to create externally-initiated outbound calls. The created `CallRecord` will have `direction: "inbound"` even though `event.direction === "outbound"`. This creates data inconsistency.
Consider passing the event's direction to `createInboundCall()` or renaming it to a more general `createWebhookCall(direction)` function.
How can I resolve this? If you propose a fix, please make it concise. |
9552ee0 to
502b1aa
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 502b1aa3a4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
CHANGELOG.md
Outdated
| @@ -2,338 +2,97 @@ | |||
|
|
|||
| Docs: https://docs.openclaw.ai | |||
|
|
|||
| ## 2026.3.2 (Unreleased) | |||
| ## 2026.2.27 | |||
There was a problem hiding this comment.
Restore current changelog head before adding new entry
This commit replaces the existing 2026.3.2 (Unreleased) head with an older 2026.2.27 snapshot, effectively dropping a large set of already-recorded entries and PR references from CHANGELOG.md; that is a regression in release metadata and will trip the repo’s changelog merge-hygiene check in scripts/pr (it rejects dropped PR refs that are not re-added). Please rebase this changelog edit onto the current head and add only the new voice-call fix line.
Useful? React with 👍 / 👎.
502b1aa to
76f8a06
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76f8a06d57
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| initialMessage: | ||
| params.direction === "inbound" | ||
| ? params.ctx.config.inboundGreeting || "Hello! How can I help you today?" | ||
| : undefined, |
There was a problem hiding this comment.
Populate mode for auto-registered outbound webhook calls
When an untracked outbound webhook call is auto-registered here, the new CallRecord does not set metadata.mode, so downstream transcript handling treats it as non-conversational. In extensions/voice-call/src/webhook.ts:127-129, auto-replies only run for inbound calls or mode === "conversation"; externally initiated outbound-api calls created by this path therefore accept the stream but never produce bot responses, leaving the call silent after user speech.
Useful? React with 👍 / 👎.
Fixes openclaw#30900 — Calls initiated directly via the Twilio REST API (Direction=outbound-api) were rejected as "unknown call" because processEvent only auto-registered calls with direction=inbound. External outbound-api calls now get registered in the CallManager so the media stream is accepted. Inbound policy checks still only apply to true inbound calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ection Address Greptile review: externally-initiated outbound-api calls were stored with hardcoded direction: "inbound". Now createWebhookCall accepts a direction parameter so the CallRecord accurately reflects the event's actual direction. Also skip inboundGreeting for outbound calls and add a test asserting inbound direction is preserved. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76f8a06 to
6565e9f
Compare
|
Landed via temp rebase onto main. Thanks @scoootscooob! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6565e9fa96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| metadata: { | ||
| initialMessage: params.ctx.config.inboundGreeting || "Hello! How can I help you today?", | ||
| initialMessage: | ||
| params.direction === "inbound" | ||
| ? params.ctx.config.inboundGreeting || "Hello! How can I help you today?" | ||
| : undefined, |
There was a problem hiding this comment.
Set mode for auto-registered outbound webhook calls
Auto-registering an untracked outbound webhook call here leaves metadata.mode unset, which breaks conversational handling for the very outbound-api flow this commit adds. In extensions/voice-call/src/webhook.ts:127-129, auto-response only runs when call.direction === "inbound" or call.metadata.mode === "conversation"; these outbound calls therefore accept media/transcripts but never trigger handleInboundResponse, resulting in silent calls after user speech unless another path manually drives responses.
Useful? React with 👍 / 👎.
Problem
When calls are initiated directly via the Twilio REST API (e.g.
curl -X POST .../Calls.json -d "Url=https://example.com/voice/webhook"), the voice-call plugin rejects the MediaStream connection with:The call is immediately terminated after the recipient answers — no audio is played.
Fixes #30900
Root Cause
processEvent()inmanager/events.tsonly auto-registers untracked calls whenevent.direction === "inbound". Calls initiated via Twilio REST API arrive withDirection=outbound-api, whichparseDirection()normalizes to"outbound". Since"outbound" !== "inbound", the call is never registered inCallManager, andshouldAcceptStream()rejects the media stream.Fix
Expand the auto-registration condition to also cover
direction === "outbound"— these are externally-initiated outbound-api calls arriving at our webhook without a pre-existing tracked call. Inbound policy checks (disabled/allowlist/pairing) still only apply to true inbound calls; external outbound-api calls are implicitly trusted because the caller controls the webhook URL.Test
events.test.ts:activeCallsandproviderCallIdMap🤖 Generated with Claude Code