11// Voice Call plugin module implements stale call reaper behavior.
22import type { CallManager } from "../manager.js" ;
3+ import type { CallState } from "../types.js" ;
34import { TerminalStates } from "../types.js" ;
45
56// Background cleanup loop for calls that never reached answered/terminal state.
67
78const CHECK_INTERVAL_MS = 30_000 ;
89
10+ /** States that indicate a live conversation with speech/transcription.
11+ * Inbound Twilio calls may never fire a call.answered event, so answeredAt
12+ * can be absent even while the call is actively transcribing. These states
13+ * prove the call is live and should not be reaped. */
14+ const LiveConversationStates : ReadonlySet < CallState > = new Set ( [ "speaking" , "listening" ] ) ;
15+
916/** Start a stale-call reaper and return its cleanup callback. */
1017export function startStaleCallReaper ( params : {
1118 manager : CallManager ;
@@ -20,7 +27,16 @@ export function startStaleCallReaper(params: {
2027 const interval = setInterval ( ( ) => {
2128 const now = Date . now ( ) ;
2229 for ( const call of params . manager . getActiveCalls ( ) ) {
23- if ( call . answeredAt || TerminalStates . has ( call . state ) ) {
30+ // Skip calls that have been answered (answeredAt set) or are in a live
31+ // conversation state. Inbound Twilio calls may never fire a call.answered
32+ // event so answeredAt may be absent even when the call is actively
33+ // transcribing/responding. Without this state guard live calls in
34+ // speaking/listening state get reaped as stale.
35+ if (
36+ call . answeredAt ||
37+ TerminalStates . has ( call . state ) ||
38+ LiveConversationStates . has ( call . state )
39+ ) {
2440 continue ;
2541 }
2642
0 commit comments