diff --git a/backend/services/pipecat/call_lifecycle.py b/backend/services/pipecat/call_lifecycle.py index 4daf1c2..9238548 100644 --- a/backend/services/pipecat/call_lifecycle.py +++ b/backend/services/pipecat/call_lifecycle.py @@ -27,6 +27,12 @@ class CallEndCoordinator: self._finish_after_tracked_speech = False self._finished = False self._reason = "completed" + # Only BotStoppedSpeakingFrame that follows a tracked BotStartedSpeakingFrame + # decrements the tracked-speech counter. This prevents a stale or cross-talk + # stop frame (e.g. from a preceding LLM utterance) from consuming the counter + # meant for a fixed end-node message. + self._pending_tracked_starts = 0 + self._current_speech_is_tracked = False @property def ending(self) -> bool: @@ -58,6 +64,7 @@ class CallEndCoordinator: completion = asyncio.get_running_loop().create_future() self._tracked_speech_completions.append(completion) self._tracked_speeches += 1 + self._pending_tracked_starts += 1 return completion async def arm_after_tracked_speech(self) -> None: @@ -87,14 +94,19 @@ class CallEndCoordinator: self._speaking = True self._speech_stopped.clear() self._response_speech_started = True + if self._pending_tracked_starts > 0: + self._pending_tracked_starts -= 1 + self._current_speech_is_tracked = True elif isinstance(frame, BotStoppedSpeakingFrame) and self._speaking: self._speaking = False self._speech_stopped.set() - if self._tracked_speeches > 0: - self._tracked_speeches -= 1 - completion = self._tracked_speech_completions.popleft() - if not completion.done(): - completion.set_result(None) + if self._current_speech_is_tracked: + self._current_speech_is_tracked = False + if self._tracked_speeches > 0: + self._tracked_speeches -= 1 + completion = self._tracked_speech_completions.popleft() + if not completion.done(): + completion.set_result(None) if ( self._finish_after_tracked_speech and self._tracked_speeches == 0