Skip to content

Commit 22575a9

Browse files
fix(voice-call): track Twilio streams after connect
1 parent 1a3ce7c commit 22575a9

2 files changed

Lines changed: 24 additions & 4 deletions

File tree

extensions/voice-call/src/providers/twilio.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ describe("TwilioProvider", () => {
245245
const secondInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA222");
246246

247247
const firstResult = provider.parseWebhookEvent(firstInbound);
248+
// Simulate the stream actually connecting (the bug: without this, no activeStreamCalls entry exists)
249+
provider.registerCallStream("CA111", "MZ111");
248250
const secondResult = provider.parseWebhookEvent(secondInbound);
249251

250252
expectStreamingTwiml(requireResponseBody(firstResult.providerResponseBody));
@@ -257,6 +259,7 @@ describe("TwilioProvider", () => {
257259
const secondInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA322");
258260

259261
provider.parseWebhookEvent(firstInbound);
262+
provider.registerCallStream("CA311", "MZ311");
260263
provider.unregisterCallStream("CA311");
261264
const secondResult = provider.parseWebhookEvent(secondInbound);
262265

@@ -274,6 +277,7 @@ describe("TwilioProvider", () => {
274277
const nextInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA422");
275278

276279
provider.parseWebhookEvent(firstInbound);
280+
provider.registerCallStream("CA411", "MZ411");
277281
provider.parseWebhookEvent(completed);
278282
const nextResult = provider.parseWebhookEvent(nextInbound);
279283

@@ -291,6 +295,7 @@ describe("TwilioProvider", () => {
291295
const nextInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA522");
292296

293297
provider.parseWebhookEvent(firstInbound);
298+
provider.registerCallStream("CA511", "MZ511");
294299
provider.parseWebhookEvent(canceled);
295300
const nextResult = provider.parseWebhookEvent(nextInbound);
296301

@@ -305,13 +310,31 @@ describe("TwilioProvider", () => {
305310
const secondInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA622");
306311

307312
provider.parseWebhookEvent(firstInbound);
313+
provider.registerCallStream("CA611", "MZ611");
308314
const result = provider.parseWebhookEvent(secondInbound);
309315

310316
expect(requireResponseBody(result.providerResponseBody)).toContain(
311317
'waitUrl="/voice/hold-music"',
312318
);
313319
});
314320

321+
it("does not block subsequent call when first call never opens a media stream", () => {
322+
const provider = createProvider();
323+
const firstInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA711");
324+
const secondInbound = createContext("CallStatus=ringing&Direction=inbound&CallSid=CA722");
325+
326+
// First call gets streaming TwiML but never connects a media stream
327+
// (no registerCallStream ever fires for CA711)
328+
provider.parseWebhookEvent(firstInbound);
329+
330+
// Second inbound call should NOT be queued — no active stream is registered
331+
const secondResult = provider.parseWebhookEvent(secondInbound);
332+
333+
const secondBody = requireResponseBody(secondResult.providerResponseBody);
334+
expectStreamingTwiml(secondBody);
335+
expect(secondBody).not.toContain("hold-queue");
336+
});
337+
315338
it("uses a stable fallback dedupeKey for identical request payloads", () => {
316339
const provider = createProvider();
317340
const rawBody = "CallSid=CA789&Direction=inbound&SpeechResult=hello";

extensions/voice-call/src/providers/twilio.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export class TwilioProvider implements VoiceCallProvider {
170170

171171
registerCallStream(callSid: string, streamSid: string): void {
172172
this.callStreamMap.set(callSid, streamSid);
173+
this.activeStreamCalls.add(callSid);
173174
}
174175

175176
hasRegisteredStream(callSid: string): boolean {
@@ -433,10 +434,6 @@ export class TwilioProvider implements VoiceCallProvider {
433434
if (decision.consumeStoredTwimlCallId) {
434435
this.deleteStoredTwiml(decision.consumeStoredTwimlCallId);
435436
}
436-
if (decision.activateStreamCallSid) {
437-
this.activeStreamCalls.add(decision.activateStreamCallSid);
438-
}
439-
440437
switch (decision.kind) {
441438
case "stored":
442439
return storedTwiml ?? TwilioProvider.EMPTY_TWIML;

0 commit comments

Comments
 (0)