Compare commits

...

2 Commits

Author SHA1 Message Date
Ubuntu
5cb13a3cac Merge branch 'main' of https://gitea.xiaowang.eu.org/wx44wx/ai-video-fullstack 2026-08-06 08:35:25 +08:00
Ubuntu
8bd781242a fix(workflow): prevent untracked BotStoppedSpeakingFrame from consuming end-node tracked-speech counter
The CallEndCoordinator.observe() decremented _tracked_speeches on every
BotStoppedSpeakingFrame, not just the one corresponding to tracked
(end-node) speech.  When a preceding LLM utterance finished between
track_speech() and the actual fixed-speech playback, the counter was
consumed prematurely, ending the call before the end-node message played.

Gate the decrement behind a _current_speech_is_tracked flag that is set
only when BotStartedSpeakingFrame follows a pending tracked-speech start.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-05 22:35:06 +08:00

View File

@@ -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