"""Stop the agent from hearing itself. On laptop speakers with no acoustic echo cancellation, the microphone picks up whatever Kokoro just said. The recognizer transcribes it, the aggregator treats it as a user turn, and the agent answers its own greeting — which is exactly what happens without this. Pipecat's ``AlwaysUserMuteStrategy`` mutes while the bot is speaking, but unmutes the instant playback ends, and the tail of that audio is still in the input buffer. This keeps the mute up for a short while afterwards. """ import time from pipecat.frames.frames import BotStartedSpeakingFrame, BotStoppedSpeakingFrame, Frame from pipecat.turns.user_mute.base_user_mute_strategy import BaseUserMuteStrategy class EchoGuardUserMuteStrategy(BaseUserMuteStrategy): """Mute the user while the bot speaks, plus a tail to let echo drain. Args: tail_secs: How long to stay muted after playback ends. """ def __init__(self, *, tail_secs: float = 0.4): super().__init__() self._tail_secs = tail_secs self._bot_speaking = False self._stopped_at = 0.0 async def process_frame(self, frame: Frame) -> bool: await super().process_frame(frame) if isinstance(frame, BotStartedSpeakingFrame): self._bot_speaking = True elif isinstance(frame, BotStoppedSpeakingFrame): self._bot_speaking = False self._stopped_at = time.monotonic() if self._bot_speaking: return True return time.monotonic() - self._stopped_at < self._tail_secs