refactor: split flat core into capability packages, layer the conversations service, English defaults for every model-facing text

This commit is contained in:
hh
2026-09-02 00:13:20 +02:00
parent 253bde1b11
commit 9aaddbed75
77 changed files with 2987 additions and 2944 deletions
+22 -22
View File
@@ -17,24 +17,24 @@ from test_conversations import ScriptedClient, World, world
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeOptions
from beaver_gateway.backends.claude_sdk import ClaudeSdkBackend
from beaver_gateway.core.conversations import ConversationTexts
from beaver_gateway.core.distill import (
from beaver_gateway.conversations.service import ConversationTexts
from beaver_gateway.conversations.distill import (
Distiller,
DistillContext,
LineCap,
check_digest,
trim_summary,
)
from beaver_gateway.core.gateway_tools import _tools, build_tool_server
from beaver_gateway.core.registry import AgentRegistry
from beaver_gateway.core.scheduler import Job, JobRun, Scheduler
from beaver_gateway.core.transcript import build_entries
from beaver_gateway.conversations.tools import _tools, build_tool_server
from beaver_gateway.app import AgentRegistry
from beaver_gateway.jobs.scheduler import Job, JobRun, Scheduler
from beaver_gateway.backends.transcript import build_entries
from beaver_gateway.storage.models import Conversation
__all__ = ["world"]
DIGEST = """---
type: выжимка
type: digest
source: "[[{chat}]]"
date: 2026-08-29
---
@@ -59,7 +59,7 @@ class DistillerClient(ScriptedClient):
async def receive_response(self):
prompt = self.prompts[-1]
written: list[ToolUseBlock] = []
if self.write and self.digest_dir is not None and "файл не пиши" not in prompt:
if self.write and self.digest_dir is not None and "write no file" not in prompt:
chat = prompt.split("«", 1)[1].split("»", 1)[0] if "«" in prompt else "чат"
path = self.digest_dir / "2026-08-29 - тема.md"
path.write_text(self.frontmatter.format(chat=chat), encoding="utf-8")
@@ -226,7 +226,7 @@ async def test_distill_writes_the_digest_indexes_it_and_merges_short(
assert result.digest.path == config.dir / "2026-08-29 - тема.md"
assert result.digest.source == "[[2026-08-20 - тема чата]]"
index = config.index.read_text(encoding="utf-8")
assert index.startswith("# индекс")
assert index.startswith("# index")
assert "- 2026-08-29 [[2026-08-20 - тема чата]] → [[2026-08-29 - тема]]" in index
assert result.text.count("\n") == 2 and not result.trimmed
closed = await world.conversations.get(chat.external_id)
@@ -236,9 +236,9 @@ async def test_distill_writes_the_digest_indexes_it_and_merges_short(
fork = await world.conversations.get(result.fork.external_id)
assert fork.kind == "fork" and fork.agent_name == "x" and fork.status == "closed"
items = await world.conversations.queue.recent(master.id)
assert items[0].origin == "выжимка"
assert items[0].origin == "digest"
assert items[0].text.startswith(
"Закрыт глубокий чат [[2026-08-20 - тема чата]], выжимка [[2026-08-29 - тема]]."
"Deep chat [[2026-08-20 - тема чата]] closed, digest [[2026-08-29 - тема]]."
)
assert items[0].text.endswith(result.text)
forked = ScriptedClient.instances[-1]
@@ -270,8 +270,8 @@ async def test_memory_off_merges_without_a_file(world: World) -> None:
assert (await world.conversations.get(chat.external_id)).status == "closed"
items = await world.conversations.queue.recent(master.id)
assert len(items) == 1 and items[0].text.endswith(result.text)
assert ", выжимка" not in items[0].text
assert "файл не пиши" in ScriptedClient.instances[-1].prompts[0]
assert ", digest" not in items[0].text
assert "write no file" in ScriptedClient.instances[-1].prompts[0]
async def test_bad_frontmatter_and_long_merge_are_reported(world: World) -> None:
@@ -294,13 +294,13 @@ async def test_bad_frontmatter_and_long_merge_are_reported(world: World) -> None
def test_check_digest_rejects_what_is_not_a_digest(tmp_path: Path) -> None:
config = Distiller(agent="x", dir=tmp_path, index=tmp_path / "i.md")
path = tmp_path / "d.md"
path.write_text("---\ntype: выжимка\nsource: ''\ndate: 2026-08-29\n---\nx\n")
assert check_digest(path, config) == "`source` пустой"
path.write_text("---\ntype: выжимка\nsource: '[[a]]'\ndate: вчера\n---\nx\n")
path.write_text("---\ntype: digest\nsource: ''\ndate: 2026-08-29\n---\nx\n")
assert check_digest(path, config) == "`source` is empty"
path.write_text("---\ntype: digest\nsource: '[[a]]'\ndate: вчера\n---\nx\n")
assert "`date`" in check_digest(path, config)
path.write_text("---\ntype: выжимка\nsource: '[[a]]'\ndate: 2026-08-29\n---\n\n")
assert check_digest(path, config) == "тело пустое"
path.write_text("---\ntype: выжимка\nsource: '[[a]]'\ndate: 2026-08-29\n---\nx\n")
path.write_text("---\ntype: digest\nsource: '[[a]]'\ndate: 2026-08-29\n---\n\n")
assert check_digest(path, config) == "empty body"
path.write_text("---\ntype: digest\nsource: '[[a]]'\ndate: 2026-08-29\n---\nx\n")
digest = check_digest(path, config)
assert not isinstance(digest, str) and digest.date.isoformat() == "2026-08-29"
assert trim_summary("a\n\nb\nc\nd\ne\nf") == ("a\nb\nc\nd\ne", True)
@@ -347,7 +347,7 @@ async def test_close_chat_tool_closes_after_the_reply(world: World) -> None:
assert row.flags["closed_reason"] == "close_chat"
assert row.flags["digest"] is not None
items = await world.conversations.queue.recent(master.id)
assert items[0].origin == "выжимка"
assert items[0].origin == "digest"
async def test_idle_picks_quiet_chats_after_launch_at_most_limit(world: World) -> None:
@@ -412,8 +412,8 @@ async def test_line_cap_bounces_a_long_rewrite_and_asks_to_shorten(
client = ScriptedClient.instances[-1]
assert len(client.prompts) == 2
assert "70 строк при потолке 60" in client.prompts[1]
assert "[инжект: потолок" in client.prompts[1]
assert "70 lines against a cap of 60" in client.prompts[1]
assert "[inject: cap" in client.prompts[1]
assert state.read_text(encoding="utf-8").count("\n") == 10 - 1
row = await world.conversations.get(job.external_id)
assert row.flags["line_cap_attempts"] == 1