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