"""Смена мастера и закрытие чата: утренний сид, хендаут, «новый день», дистиллятор.""" from __future__ import annotations from typing import TYPE_CHECKING from beaver_gateway.frontends.turn_record import slugify from beaver_agent.memory.recall import notes_head from beaver_agent.vault import ( DAYS, DIARY, DIGESTS, GRANULES, PORTRAIT, STATE, VAULT, read, ) if TYPE_CHECKING: from pathlib import Path from beaver_gateway.conversations.distill import DistillContext from beaver_gateway.conversations.rotation import HandoutContext from beaver_gateway.conversations.texts import NewDayContext, SeedContext def latest_handout() -> Path | None: """Последний хендаут, а не «вчерашний по календарю».""" files = sorted(DAYS.glob("????-??-??.md")) if DAYS.exists() else [] return files[-1] if files else None def seed_body(ctx: SeedContext) -> str | None: """Утренний сид: последний хендаут, шапка состояния, портрет Бобра.""" if ctx.seed != "morning": return None handout = latest_handout() if handout is None: return "Хендаут не приехал." parts = [ f"Хендаут ({handout.stem}):\n\n{handout.read_text(encoding='utf-8').strip()}" ] signal = "\n".join((read(STATE) or "").splitlines()[:5]) if signal: parts.append( f"Состояние (первые строки, полностью в {STATE.relative_to(VAULT)}):\n" f"{signal}" ) portrait = notes_head(PORTRAIT, ("сейчас", "паттерны"), cap=40) if portrait: parts.append( "Портрет Бобра (шапка " f"`{PORTRAIT.relative_to(VAULT)}`, лента - там же):\n{portrait}" ) return "\n\n".join(parts) DEFAULT_HANDOUT = ( "Этот мастер закрывается ({reason}). Напиши хендаут за {day} в " "`мета/бобер/дни/{day}.md`: справку на утро, не задание." ) def handout_prompt(ctx: HandoutContext) -> str: day = ctx.day.isoformat() body = ( (read(GRANULES / "хендаут.md") or DEFAULT_HANDOUT) .replace("{day}", day) .replace("{reason}", ctx.reason) ) if (DIARY / f"{day}.md").exists(): return f"{body}\n\nДневник за {day} приехал: `📅 дни/{day}.md`, возьми из него." return f"{body}\n\nДневник за {day} не приехал - так и напиши в разделе «дневник»." NEW_DAY = ( "Новый день ({day}). Ты - новый мастер того же треда: хендаут и сигнал состояния " "в сиде выше. Скажи Бобру что-то через say, только если есть что сказать." ) SAME_DAY = ( "Мастер пересоздан ({why}), день тот же - {day}. Хендаут в сиде выше - справка от " "предыдущего мастера за сегодня, не утро: продолжай с того места, ничего не " "приветствуй. Скажи Бобру что-то через say, только если есть что сказать." ) ROTATION_WHY = { "age": "прошло больше 36 часов", "context": "контекст стал слишком большим", } def new_day(ctx: NewDayContext) -> str: """«Новый день» - только ночная ротация; остальные причины - тот же день.""" day = ctx.day.isoformat() if ctx.reason == "night": return NEW_DAY.format(day=day) return SAME_DAY.format(day=day, why=ROTATION_WHY.get(ctx.reason, ctx.reason)) DISTILL = ( "Глубокий чат [[{chat}]] закрыт ({reason}), сегодня {day}. Выжимка - `Write` в " '`{path}`, фронтматтер `source: "[[{chat}]]"`, `date: {day}`; слив - текст ' "ответа, до 5 строк, третье лицо." ) DISTILL_NO_MEMORY = ( "Глубокий чат [[{chat}]] закрыт ({reason}), сегодня {day}. Память для него " "выключена: файл не пиши, только слив текстом ответа - до 5 строк, третье лицо." ) def distill_prompt(ctx: DistillContext) -> str: day = ctx.day.isoformat() if not ctx.memory: return DISTILL_NO_MEMORY.format(chat=ctx.chat_name, reason=ctx.reason, day=day) path = DIGESTS / f"{day} - {slugify(ctx.chat_name, maxlen=60)}.md" return DISTILL.format(chat=ctx.chat_name, reason=ctx.reason, day=day, path=path)