diff --git a/extensions/voice-call/src/manager/events.test.ts b/extensions/voice-call/src/manager/events.test.ts index f378e43ef14..23409d79a05 100644 --- a/extensions/voice-call/src/manager/events.test.ts +++ b/extensions/voice-call/src/manager/events.test.ts @@ -199,6 +199,48 @@ describe("processEvent (functional)", () => { expect(ctx.providerCallIdMap.has("request-uuid")).toBe(false); }); + it("does not burn replay keys for unknown calls before a later replay can resolve them", () => { + const now = Date.now(); + const ctx = createContext(); + const event: NormalizedEvent = { + id: "evt-late-call", + dedupeKey: "stable-late-call", + type: "call.answered", + callId: "call-late", + providerCallId: "provider-late", + timestamp: now + 1, + }; + + processEvent(ctx, event); + + expect(ctx.processedEventIds.size).toBe(0); + + ctx.activeCalls.set("call-late", { + callId: "call-late", + providerCallId: "provider-late", + provider: "plivo", + direction: "inbound", + state: "ringing", + from: "+15550000002", + to: "+15550000000", + startedAt: now, + transcript: [], + processedEventIds: [], + metadata: {}, + }); + ctx.providerCallIdMap.set("provider-late", "call-late"); + + processEvent(ctx, event); + + const call = ctx.activeCalls.get("call-late"); + if (!call) { + throw new Error("expected replayed event to resolve after call registration"); + } + expect(call.state).toBe("answered"); + expect(call.answeredAt).toBe(now + 1); + expect(Array.from(ctx.processedEventIds)).toEqual(["stable-late-call"]); + }); + it("invokes onCallAnswered hook for answered events", () => { const now = Date.now(); let answeredCallId: string | null = null; diff --git a/extensions/voice-call/src/manager/events.ts b/extensions/voice-call/src/manager/events.ts index ad9c7c37ff4..fba7ec2675a 100644 --- a/extensions/voice-call/src/manager/events.ts +++ b/extensions/voice-call/src/manager/events.ts @@ -99,7 +99,6 @@ export function processEvent(ctx: EventContext, event: NormalizedEvent): void { if (ctx.processedEventIds.has(dedupeKey)) { return; } - ctx.processedEventIds.add(dedupeKey); let call = findCall({ activeCalls: ctx.activeCalls, @@ -125,6 +124,7 @@ export function processEvent(ctx: EventContext, event: NormalizedEvent): void { ); return; } + ctx.processedEventIds.add(dedupeKey); if (ctx.rejectedProviderCallIds.has(pid)) { return; } @@ -161,6 +161,8 @@ export function processEvent(ctx: EventContext, event: NormalizedEvent): void { return; } + ctx.processedEventIds.add(dedupeKey); + if (event.providerCallId && event.providerCallId !== call.providerCallId) { const previousProviderCallId = call.providerCallId; call.providerCallId = event.providerCallId;