"""Всё, что gateway говорит модели и Бобру, по-русски.""" from __future__ import annotations from typing import TYPE_CHECKING from beaver_gateway.conversations.texts import ConversationTexts, EnvelopeTexts from beaver_gateway.frontends.telegram import TelegramTexts from beaver_agent.memory.handouts import ( distill_prompt, handout_prompt, new_day, seed_body, ) from beaver_agent.vault import GRANULES, read if TYPE_CHECKING: from beaver_gateway.conversations.injects import InjectContext BEAVER_ORIGINS = frozenset({"panel", "api"}) """Панель и API - это сам Бобёр; крон, watch, komodo, ротация - нет.""" ORIGINS = { "merge": "слив", "digest": "выжимка", "rotation": "ротация", "cap": "потолок", "system": "система", "agent": "агент", } INTERRUPTED_TURN = ( "[этот инжект прервал предыдущий тёрн: «Request interrupted» выше - " "прерывание, не отказ от тулзы]" ) def inject_header(ctx: InjectContext) -> str: if ctx.origin in BEAVER_ORIGINS: head = ( "[инжект из панели: это Бобёр, ответ он видит в панели; " "в телегу - только через say]" ) else: origin = ORIGINS.get(ctx.origin, ctx.origin) head = ( f"[инжект: {origin} - это не Бобёр, отвечать не нужно, голос не обязателен]" ) return f"{head}\n{INTERRUPTED_TURN}" if ctx.interrupted_turn else head ENVELOPE = EnvelopeTexts( header=( "[конверт - фоновый сигнал, не обращение; " "реагируй, только если относится к вопросу]" ), time="время: {stamp} ({zone})", changed="vault, изменено {since}: {names}", since="с {time}", since_start="со старта", truncated="… (потолок конверта)", file_header="--- {path}, только добавленное ---", more_lines="+ … ещё {count}", ) MERGE_PROMPT = read(GRANULES / "слив.md") or ( "Ветка закрывается. Напиши слив для мастера: что решили, что сделано, " "что не сделано и почему, открытые вопросы. Идентификаторы и ссылки - " "дословно. Коротко, прошедшее время." ) CONVERSATIONS = ConversationTexts( merge_prompt=MERGE_PROMPT, inject_header=inject_header, bundle_header="[инжекты, накопившиеся с {since}; это не Бобёр]", interrupted="прервано", answered="Пользователь ответил: {answer}", unanswered=( "Пользователь не ответил за {minutes} мин. Вопрос ему показан текстом; " "заверши тёрн сейчас, ответ придёт следующим сообщением." ), seed=seed_body, seed_head="[сид: {seed}] {kind}{title}, {stamp}.", seed_copy="История родителя скопирована ({scope}); продолжай в ней.", seed_copy_window="последние {window} тёрнов", seed_copy_all="вся история", seed_morning_missing="Хендаут не приехал.", handout=handout_prompt, new_day=new_day, moved_injects=" Инжектов переехало из старого мастера: {moved}.", distill=distill_prompt, closed="Закрыт глубокий чат [[{chat}]]{digest}.\n{text}", closed_digest=", выжимка [[{digest}]]", digest_missing="файл выжимки не появился", too_long=( "`{name}`: {lines} строк при потолке {max_lines}. Запись отбита, файл " "возвращён к прежней версии. Сократи и перепиши." ), cut_by_restart="тёрн {turn_id} оборван рестартом gateway", repaired_tools=( "; {fixed} незакрытых тул-коллов получили tool_result «{interrupted}»" ), envelope=ENVELOPE, ) TELEGRAM = TelegramTexts( commands=( ("merge", "слить ветку в мастер"), ("new", "новая ветка: в General - новый топик, в топике - заново на нём"), ("chat", "открыть глубокий чат в vault: /chat тема"), ("status", "что с этим разговором"), ("help", "команды"), ), help_head="General - мастер, любой другой топик - ветка.", branch="ветка", waiting="⏳ думаю", thinking="🤔 думаю", writing="✍️ пишу", tool_labels={ "Read": "читаю vault", "Glob": "ищу файлы", "Grep": "ищу в vault", "Edit": "правлю файл", "Write": "пишу файл", "MultiEdit": "правлю файлы", "Bash": "выполняю команду", "WebSearch": "ищу в сети", "WebFetch": "читаю страницу", "Task": "запустил сабагента", "Agent": "запустил сабагента", "AskUserQuestion": "спрашиваю", "TodoWrite": "планирую", "Skill": "открываю скилл", "mcp__gateway__spawn": "открываю разговор", "mcp__gateway__read_conversation": "читаю разговор", "mcp__gateway__say": "говорю", "mcp__gateway__schedule": "ставлю напоминание", "mcp__gateway__inject": "передаю в другой разговор", }, attachment_sticker="[вложение: стикер - не поддерживается]", attachment_animation="[вложение: анимация - не поддерживается]", attachment_kinds={ "photo": "фото", "document": "файл", "voice": "голосовое", "audio": "аудио", "video": "видео", "video_note": "кружок", }, attachment_too_big=( "[вложение: {kind} {name}, {mb} МБ - больше 20 МБ, Telegram не отдаёт ботам]" ), attachment_failed="[вложение: {kind} {name} - не скачалось: {error}]", attachment_size=", {kb} КБ", attachment="[вложение: {kind} {path}{size}]", attachment_kept=( "[вложение: {kind} {path}{size}; хранится {days} дн., " "перенеси в vault, если нужно]" ), merge_nothing="сливать нечего: это не открытая ветка", merging="🔀 сливаю в мастер", merged="✅ слито в мастер", merge_failed="⚠️ слив не удался: {error}", new_branch_topic="🌿 ветка «{title}» - в новом топике", new_branch_here="🌿 новая ветка на этом топике", chat_usage="/chat <тема>", chat_failed="не вышло: {error}", chat_opened="💬 глубокий чат: {where}", status_unbound="этот топик ни к чему не привязан - напиши, и откроется ветка", status_line="сессия: {live} · тёрн: {turn} · в очереди: {queued}", status_live="живая", status_dead="нет", status_running="идёт", status_idle="нет", status_question="❓ ждёт ответа на вопрос", status_pool="пул: {sessions} сессий, rss {mb} МБ", status_outbox="outbox: {pending} в очереди", turn_failed="⚠️ тёрн упал, смотри логи gateway", background_failed="⚠️ фоновый тёрн ({origin}) упал, смотри логи gateway", question_closed="вопрос уже закрыт", question_timeout="⌛ время вышло - ответь текстом", done_button="✅ готово", mirrored="📝 из панели:\n{text}", )