fix(voice-call): accept externally-initiated Twilio outbound-api calls

Fixes #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>
This commit is contained in:
scoootscooob
2026-03-01 18:24:31 -08:00
committed by Peter Steinberger
parent a5b81d1c13
commit a1b4a0066b
2 changed files with 56 additions and 2 deletions

View File

@@ -235,6 +235,50 @@ describe("processEvent (functional)", () => {
expect(ctx.activeCalls.size).toBe(0);
});
it("auto-registers externally-initiated outbound-api calls", () => {
const ctx = createContext();
const event: NormalizedEvent = {
id: "evt-external-1",
type: "call.initiated",
callId: "CA-external-123",
providerCallId: "CA-external-123",
timestamp: Date.now(),
direction: "outbound",
from: "+15550000000",
to: "+15559876543",
};
processEvent(ctx, event);
// 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");
});
it("does not reject externally-initiated outbound calls even with disabled inbound policy", () => {
const { ctx, hangupCalls } = createRejectingInboundContext();
const event: NormalizedEvent = {
id: "evt-external-2",
type: "call.initiated",
callId: "CA-external-456",
providerCallId: "CA-external-456",
timestamp: Date.now(),
direction: "outbound",
from: "+15550000000",
to: "+15559876543",
};
processEvent(ctx, event);
// External outbound calls bypass inbound policy — they should be accepted
expect(ctx.activeCalls.size).toBe(1);
expect(hangupCalls).toHaveLength(0);
});
it("deduplicates by dedupeKey even when event IDs differ", () => {
const now = Date.now();
const ctx = createContext();

View File

@@ -104,8 +104,18 @@ export function processEvent(ctx: EventContext, event: NormalizedEvent): void {
callIdOrProviderCallId: event.callId,
});
if (!call && event.direction === "inbound" && event.providerCallId) {
if (!shouldAcceptInbound(ctx.config, event.from)) {
// Auto-register untracked calls arriving via webhook. This covers both
// true inbound calls and externally-initiated outbound-api calls (e.g. calls
// placed directly via the Twilio REST API pointing at our webhook URL).
const isUnregisteredWebhookCall =
!call &&
event.providerCallId &&
(event.direction === "inbound" || event.direction === "outbound");
if (isUnregisteredWebhookCall) {
// Apply inbound policy for true inbound calls; external outbound-api calls
// are implicitly trusted because the caller controls the webhook URL.
if (event.direction === "inbound" && !shouldAcceptInbound(ctx.config, event.from)) {
const pid = event.providerCallId;
if (!ctx.provider) {
console.warn(