From 12728b2eb474042d3f2cd9a2bf4135fedec29945 Mon Sep 17 00:00:00 2001 From: h Date: Sat, 29 Aug 2026 02:38:39 +0200 Subject: [PATCH] refactor(envelope): drop the reply counter and the injects line, keep time and vault changes --- src/beaver_gateway/core/conversations.py | 14 +++-------- src/beaver_gateway/core/envelope.py | 32 ++++++------------------ tests/test_envelope.py | 18 ++++--------- 3 files changed, 16 insertions(+), 48 deletions(-) diff --git a/src/beaver_gateway/core/conversations.py b/src/beaver_gateway/core/conversations.py index 506c72d..d2603c3 100644 --- a/src/beaver_gateway/core/conversations.py +++ b/src/beaver_gateway/core/conversations.py @@ -591,8 +591,6 @@ class Conversations: raise ValueError(msg) if agent is None and kind == "branch" and parent is not None: agent = parent.agent_name - if kind == "branch" and parent is not None and parent.kind == "master": - await self.set_flags(parent, {"streak": 0}) agent = agent or self.default_agent(kind) if agent is None: msg = f"no default agent for kind {kind!r}; pass `agent`" @@ -1291,7 +1289,7 @@ class Conversations: if head.priority == "user": origin = "user" prompt = head.text - envelope = await self._envelope_for(conv, injects=len(batch) - 1) + envelope = self._envelope_for(conv) if envelope: prompt += "\n\n" + envelope if len(batch) > 1: @@ -1326,14 +1324,10 @@ class Conversations: text=text, ) - async def _envelope_for(self, conv: Conversation, *, injects: int) -> str | None: - if conv.kind != "master": + def _envelope_for(self, conv: Conversation) -> str | None: + if conv.kind != "master" or self._envelope is None: return None - streak = int(conv.flags.get("streak", 0) or 0) - await self.set_flags(conv, {"streak": streak + 1}) - if self._envelope is None: - return None - return self._envelope.build(streak=streak, injects=injects) + return self._envelope.build() def _observer( self, conv: Conversation, runner: _Runner, turn_id: str, origin: str diff --git a/src/beaver_gateway/core/envelope.py b/src/beaver_gateway/core/envelope.py index 5236e38..bcb57d7 100644 --- a/src/beaver_gateway/core/envelope.py +++ b/src/beaver_gateway/core/envelope.py @@ -1,10 +1,10 @@ """The envelope (§3.3): a background block after the user's text. Assembled when the turn starts, never when the message is queued: the -time, how many replies the master gave in a row without opening a branch, -what changed in the vault since the last envelope (added lines for the -``full`` files, names and counts for the rest) and how many normal -injects ride along below. Ceilings keep it a signal, not a document. +time and what changed in the vault since the last envelope (added lines +for the ``full`` files, names and counts for the rest). Ceilings keep it +a signal, not a document; the injects that ride along are bundled below +it by the queue, with their own header. """ from __future__ import annotations @@ -36,7 +36,7 @@ class Envelope: names_only_within: float = 600.0 last_at: datetime | None = None - def build(self, *, streak: int, injects: int, now: datetime | None = None) -> str: + def build(self, *, now: datetime | None = None) -> str: now = now or datetime.now(UTC) changes = self.watch.take() if self.watch is not None else [] names_only = ( @@ -46,11 +46,9 @@ class Envelope: text = render( now=now, tz=self.tz, - streak=streak, changes=changes, since=self.last_at, names_only=names_only, - injects=injects, max_lines=self.max_lines, per_file=self.per_file, ) @@ -62,30 +60,22 @@ def render( *, now: datetime, tz: str, - streak: int, changes: Sequence[Change], since: datetime | None, names_only: bool, - injects: int, max_lines: int = 120, per_file: int = 30, ) -> str: zone = ZoneInfo(tz) stamp = now.astimezone(zone) - lines = [ - HEADER, - f"время: {stamp:%Y-%m-%d %H:%M} ({_zone_label(tz)})", - f"мастер: {_replies(streak)} подряд без ветки", - ] + lines = [HEADER, f"время: {stamp:%Y-%m-%d %H:%M} ({_zone_label(tz)})"] ordered = sorted(changes, key=lambda c: (not c.full, c.path)) since_label = ( f"с {since.astimezone(zone):%H:%M}" if since is not None else "со старта" # noqa: RUF001 ) if ordered: names = ", ".join(f"{c.path} (+{c.added_count})" for c in ordered) - lines.append(f"vault, изменено {since_label} (mtime): {names}") - if injects: - lines.append(f"инжекты {since_label}: ({injects}) ниже") + lines.append(f"vault, изменено {since_label}: {names}") if not names_only: _append_diffs(lines, ordered, max_lines=max_lines, per_file=per_file) return "\n".join(lines[:max_lines]) @@ -110,13 +100,5 @@ def _append_diffs( budget -= 1 -def _replies(n: int) -> str: - if n % 10 == 1 and n % 100 != 11: - return f"{n} реплика" - if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14: - return f"{n} реплики" - return f"{n} реплик" - - def _zone_label(tz: str) -> str: return tz.rsplit("/", 1)[-1].replace("_", " ") diff --git a/tests/test_envelope.py b/tests/test_envelope.py index decfc81..22c85a9 100644 --- a/tests/test_envelope.py +++ b/tests/test_envelope.py @@ -83,23 +83,21 @@ def test_envelope_respects_ceilings_and_names_only_window() -> None: watch.note(root / "люди" / f"{name}.md") watch.note(diary) envelope = Envelope(watch=watch, tz="Europe/Warsaw") - text = envelope.build(streak=7, injects=2) + text = envelope.build() lines = text.splitlines() assert lines[0] == HEADER assert "(Warsaw)" in lines[1] - assert lines[2] == "мастер: 7 реплик подряд без ветки" - assert f"дни/{today}.md (+200)" in lines[3] - assert "люди/Петя.md (+50)" in lines[3] - assert "инжекты со старта: (2) ниже" in lines[4] + assert lines[2].startswith("vault, изменено со старта: ") + assert f"дни/{today}.md (+200)" in lines[2] + assert "люди/Петя.md (+50)" in lines[2] assert sum(1 for line in lines if line.startswith("+ ")) == 31 assert "+ … ещё 170" in lines assert len(lines) <= 120 append(diary, "- ещё одна\n") watch.note(diary) - second = envelope.build(streak=8, injects=0) + second = envelope.build() assert f"дни/{today}.md (+1)" in second assert "+ - ещё одна" not in second - assert "инжекты" not in second def test_render_hits_total_ceiling() -> None: @@ -117,17 +115,14 @@ def test_render_hits_total_ceiling() -> None: text = render( now=now, tz="Europe/Warsaw", - streak=1, changes=changes, since=now - timedelta(hours=1), names_only=False, - injects=0, ) lines = text.splitlines() assert len(lines) <= 120 assert "… (потолок конверта)" in lines assert lines[1] == "время: 2026-08-26 13:04 (Warsaw)" - assert lines[2] == "мастер: 1 реплика подряд без ветки" async def test_master_turn_gets_envelope_after_text_and_before_injects( @@ -146,13 +141,10 @@ async def test_master_turn_gets_envelope_after_text_and_before_injects( assert head == "hello" assert rest.startswith(HEADER) assert "люди/Прохор.md (+1)" in rest - assert "мастер: 0 реплик подряд без ветки" in rest assert rest.index("[инжекты") > rest.index(HEADER) - assert (await world.conversations.get(master.external_id)).flags["streak"] == 1 branch = await world.conversations.spawn( kind="branch", parent=master, seed="brief", text="do X" ) await world.settle(branch, 1) - assert (await world.conversations.get(master.external_id)).flags["streak"] == 0 assert "[конверт" not in ScriptedClient.instances[-1].prompts[0] await asyncio.sleep(0)