@@ -165,6 +165,7 @@ export class AudioRecognition {
165165 private isInterruptionEnabled : boolean ;
166166 private isAgentSpeaking : boolean ;
167167 private interruptionStreamChannel ?: StreamChannel < InterruptionSentinel | AudioFrame > ;
168+ private closed = false ;
168169
169170 constructor ( opts : AudioRecognitionOptions ) {
170171 this . hooks = opts . recognitionHooks ;
@@ -696,6 +697,15 @@ export class AudioRecognition {
696697 } ) ;
697698 } ) ;
698699 }
700+ // STT EOT changes user state from speaking to listening without updating VAD internal states.
701+ // VAD EOS will also skip updating user state from listening (STT enforced) to listening (VAD detected)
702+ // and user state won't be updated until a new VAD SOS is received.
703+ // Reset VAD so that incorrect end of turn from STT can be corrected by VAD interruption.
704+ // If user is still speaking (an immediate VAD SOS will interrupt the agent).
705+ if ( this . vad && this . speaking ) {
706+ this . logger . warn ( 'stt end of speech received while user is speaking, resetting vad' ) ;
707+ this . resetVad ( ) ;
708+ }
699709 this . speaking = false ;
700710 this . userTurnCommitted = true ;
701711 this . lastSpeakingTime = Date . now ( ) ;
@@ -1159,6 +1169,23 @@ export class AudioRecognition {
11591169 } ) ;
11601170 }
11611171
1172+ /**
1173+ * Reset the VAD by restarting its task. This is needed when STT sends a premature
1174+ * end-of-turn signal while the user is still speaking, so VAD can detect new speech
1175+ * and trigger interruptions correctly.
1176+ */
1177+ private resetVad ( ) {
1178+ if ( ! this . vad ) return ;
1179+
1180+ this . vadTask ?. cancelAndWait ( ) . finally ( ( ) => {
1181+ if ( this . closed ) return ;
1182+ this . vadTask = Task . from ( ( { signal } ) => this . createVadTask ( this . vad , signal ) ) ;
1183+ this . vadTask . result . catch ( ( err ) => {
1184+ this . logger . error ( `Error running VAD task: ${ err } ` ) ;
1185+ } ) ;
1186+ } ) ;
1187+ }
1188+
11621189 commitUserTurn ( audioDetached : boolean ) {
11631190 const commitUserTurnTask =
11641191 ( delayDuration : number = 500 ) =>
@@ -1206,6 +1233,7 @@ export class AudioRecognition {
12061233 }
12071234
12081235 async close ( ) {
1236+ this . closed = true ;
12091237 this . detachInputAudioStream ( ) ;
12101238 this . silenceAudioWriter . releaseLock ( ) ;
12111239 await this . commitUserTurnTask ?. cancelAndWait ( ) ;
0 commit comments