"""Единственная ветка голосовых точек: как её открыть и как сменить за ночь.""" from __future__ import annotations import logging from typing import TYPE_CHECKING from beaver_agent.voice.texts import KEY, ROTATED, TITLE if TYPE_CHECKING: from beaver_gateway.conversations.service import Conversations from beaver_gateway.storage.models import Conversation AGENT = "beaver-voice" """Кто сидит в голосовой ветке; ему не видно ни справки, ни vault.""" FRONTEND = "voice" TELEGRAM = "telegram" _log = logging.getLogger("beaver_agent.voice") async def current(conversations: Conversations) -> Conversation | None: conv = await conversations.find_bound(frontend=FRONTEND, external_id=KEY) return conv if conv is not None and conv.status == "open" else None async def open_thread( conversations: Conversations, *, agent: str, window: str | None = None ) -> Conversation: """Завести ветку; без ``window`` окно ей открывает телеграм - новым топиком.""" conv = await conversations.spawn( kind="branch", agent=agent, title=TITLE, origin=FRONTEND, binding=(TELEGRAM, window) if window else None, ) await conversations.bind(conv, frontend=FRONTEND, external_id=KEY) _log.info("голосовая ветка %s (%s)", conv.external_id, agent) return conv async def window_of(conversations: Conversations, conv: Conversation) -> str | None: for binding in await conversations.bindings(conv): if binding.frontend == TELEGRAM and binding.visible: return binding.external_id return None async def rotate(conversations: Conversations) -> Conversation | None: """Ночью - новая ветка в том же топике; молчавшую за сутки не трогаем.""" old = await current(conversations) if old is None or old.last_user_activity_at is None: return None new = await open_thread( conversations, agent=old.agent_name, window=await window_of(conversations, old) ) await conversations.close(old) await conversations.say(new, ROTATED) _log.info("голосовая ветка: %s -> %s", old.external_id, new.external_id) return new