diff --git a/extensions/voice-call/src/manager.restore.test.ts b/extensions/voice-call/src/manager.restore.test.ts index 61dccf0e095..712eff72dc4 100644 --- a/extensions/voice-call/src/manager.restore.test.ts +++ b/extensions/voice-call/src/manager.restore.test.ts @@ -107,4 +107,37 @@ describe("CallManager verification on restore", () => { expect(activeCall.callId).toBe(call.callId); expect(activeCall.state).toBe(call.state); }); + + it("restores dedupe keys from terminal persisted calls so replayed webhooks stay ignored", async () => { + const storePath = createTestStorePath(); + const persisted = makePersistedCall({ + state: "completed", + endedAt: Date.now() - 5_000, + endReason: "completed", + processedEventIds: ["evt-terminal-init"], + }); + writeCallsToStore(storePath, [persisted]); + + const provider = new FakeProvider(); + const config = VoiceCallConfigSchema.parse({ + enabled: true, + provider: "plivo", + fromNumber: "+15550000000", + }); + const manager = new CallManager(config, storePath); + await manager.initialize(provider, "https://example.com/voice/webhook"); + + manager.processEvent({ + id: "evt-terminal-init", + type: "call.initiated", + callId: String(persisted.providerCallId), + providerCallId: String(persisted.providerCallId), + timestamp: Date.now(), + direction: "outbound", + from: "+15550000000", + to: "+15550000001", + }); + + expect(manager.getActiveCalls()).toHaveLength(0); + }); }); diff --git a/extensions/voice-call/src/manager/store.ts b/extensions/voice-call/src/manager/store.ts index a15edaa8277..f412efd0be0 100644 --- a/extensions/voice-call/src/manager/store.ts +++ b/extensions/voice-call/src/manager/store.ts @@ -50,6 +50,9 @@ export function loadActiveCallsFromStore(storePath: string): { const rejectedProviderCallIds = new Set(); for (const [callId, call] of callMap) { + for (const eventId of call.processedEventIds) { + processedEventIds.add(eventId); + } if (TerminalStates.has(call.state)) { continue; } @@ -57,9 +60,6 @@ export function loadActiveCallsFromStore(storePath: string): { if (call.providerCallId) { providerCallIdMap.set(call.providerCallId, callId); } - for (const eventId of call.processedEventIds) { - processedEventIds.add(eventId); - } } return { activeCalls, providerCallIdMap, processedEventIds, rejectedProviderCallIds };