refactor: split config.py into the beaver_agent package (vault, prompts, skills, policy, hands, agents, frontends, texts, memory, jobs)

This commit is contained in:
hh
2026-09-02 00:24:31 +02:00
parent f4043a7bfd
commit ac4a8d1fae
38 changed files with 1302 additions and 935 deletions
+185
View File
@@ -0,0 +1,185 @@
"""Всё, что 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}",
)