fix(telegram,core): first message seeds the branch, registered commands, no ellipses, html drafts
This commit is contained in:
@@ -26,6 +26,7 @@ from aiogram import Bot
|
||||
from aiogram.client.default import DefaultBotProperties
|
||||
from aiogram.exceptions import TelegramAPIError
|
||||
from aiogram.types import (
|
||||
BotCommand,
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
@@ -53,13 +54,16 @@ _log = logging.getLogger("beaver_gateway.frontends.telegram")
|
||||
|
||||
FRONTEND = "telegram"
|
||||
_DONE = "done"
|
||||
_COMMANDS = ("merge", "new", "chat", "status", "start", "help")
|
||||
_HELP = (
|
||||
"General - мастер, топик - ветка. Создай топик и пиши в него.\n"
|
||||
"/merge - слить ветку в мастер\n"
|
||||
"/new [название] - новая ветка (в General - новый топик)\n"
|
||||
"/chat <тема> - открыть глубокий чат в vault\n"
|
||||
"/status - что с этим разговором" # noqa: RUF001
|
||||
_COMMAND_HELP = (
|
||||
("merge", "слить ветку в мастер"),
|
||||
("new", "новая ветка: в General - новый топик, в топике - заново на нём"),
|
||||
("chat", "открыть глубокий чат в vault: /chat тема"),
|
||||
("status", "что с этим разговором"), # noqa: RUF001
|
||||
("help", "команды"),
|
||||
)
|
||||
_COMMANDS = (*(c for c, _ in _COMMAND_HELP), "start")
|
||||
_HELP = "General - мастер, любой другой топик - ветка.\n" + "\n".join(
|
||||
f"/{c} - {d}" for c, d in _COMMAND_HELP
|
||||
)
|
||||
|
||||
|
||||
@@ -171,6 +175,9 @@ class TelegramFrontend(Frontend):
|
||||
getattr(me, "has_topics_enabled", None),
|
||||
)
|
||||
self._sweep_attachments()
|
||||
await self.bot.set_my_commands(
|
||||
[BotCommand(command=c, description=d) for c, d in _COMMAND_HELP]
|
||||
)
|
||||
try:
|
||||
async with asyncio.TaskGroup() as tg:
|
||||
tg.create_task(self.inbox.run())
|
||||
@@ -305,25 +312,35 @@ class TelegramFrontend(Frontend):
|
||||
self._master_window = self._ext(topic.message_thread_id)
|
||||
return self._master_window
|
||||
|
||||
async def _master(self) -> Conversation:
|
||||
async def _master(self, text: str | None = None) -> tuple[Conversation, bool]:
|
||||
"""The open master behind its window, spawning one when there is none.
|
||||
|
||||
``text`` rides with the seed of a fresh master; the second value says
|
||||
whether it was consumed that way.
|
||||
"""
|
||||
ext = await self._master_ext()
|
||||
conv = await self.conversations.find_bound(frontend=FRONTEND, external_id=ext)
|
||||
if conv is not None and conv.status == "open":
|
||||
return conv
|
||||
return conv, False
|
||||
masters = await self.conversations.find(kind="master", status="open", limit=1)
|
||||
if masters:
|
||||
await self.conversations.bind(
|
||||
masters[0], frontend=FRONTEND, external_id=ext
|
||||
)
|
||||
return masters[0]
|
||||
return await self.conversations.spawn(
|
||||
kind="master", seed="clean", origin=FRONTEND, binding=(FRONTEND, ext)
|
||||
return masters[0], False
|
||||
conv = await self.conversations.spawn(
|
||||
kind="master",
|
||||
seed="clean",
|
||||
text=text,
|
||||
origin=FRONTEND,
|
||||
binding=(FRONTEND, ext),
|
||||
)
|
||||
return conv, text is not None
|
||||
|
||||
async def _branch(
|
||||
self, thread_id: int, *, title: str | None, text: str | None
|
||||
) -> Conversation:
|
||||
master = await self._master()
|
||||
master, _ = await self._master()
|
||||
return await self.conversations.spawn(
|
||||
kind="branch",
|
||||
seed="morning",
|
||||
@@ -343,10 +360,13 @@ class TelegramFrontend(Frontend):
|
||||
await self._on_callback(update.callback_query)
|
||||
|
||||
async def _on_message(self, message: Message) -> None:
|
||||
if message.forum_topic_created is not None and message.message_thread_id:
|
||||
self._topic_names[message.message_thread_id] = (
|
||||
message.forum_topic_created.name
|
||||
)
|
||||
created = message.forum_topic_created
|
||||
if (
|
||||
created is not None
|
||||
and message.message_thread_id
|
||||
and not created.is_name_implicit
|
||||
):
|
||||
self._topic_names[message.message_thread_id] = created.name
|
||||
if message.from_user is None or message.from_user.id != self.user_id:
|
||||
_log.warning(
|
||||
"ignoring message from %s", getattr(message.from_user, "id", None)
|
||||
@@ -359,11 +379,7 @@ class TelegramFrontend(Frontend):
|
||||
is_master = (
|
||||
thread_id is None or self._ext(thread_id) == await self._master_ext()
|
||||
)
|
||||
if message.forum_topic_created is not None and thread_id is not None:
|
||||
if not is_master and await self._live(thread_id) is None:
|
||||
await self._branch(
|
||||
thread_id, title=message.forum_topic_created.name, text=None
|
||||
)
|
||||
if message.forum_topic_created is not None:
|
||||
return
|
||||
if message.forum_topic_edited is not None and thread_id is not None:
|
||||
if message.forum_topic_edited.name:
|
||||
@@ -382,11 +398,14 @@ class TelegramFrontend(Frontend):
|
||||
await self._command(command, args.strip(), message, thread_id)
|
||||
return
|
||||
if is_master:
|
||||
conv = await self._master()
|
||||
conv, consumed = await self._master(text)
|
||||
if consumed:
|
||||
return
|
||||
else:
|
||||
conv = await self._live(thread_id)
|
||||
if conv is None:
|
||||
await self._branch(thread_id, title=self._title_from(text), text=text)
|
||||
title = self._topic_names.get(thread_id) or self._title_from(text)
|
||||
await self._branch(thread_id, title=title, text=text)
|
||||
return
|
||||
pending = self.conversations.pending_question(conv.external_id)
|
||||
if pending is not None and self.conversations.answer(pending[0], text):
|
||||
@@ -522,14 +541,14 @@ class TelegramFrontend(Frontend):
|
||||
is_master = (
|
||||
thread_id is None or self._ext(thread_id) == await self._master_ext()
|
||||
)
|
||||
conv = await self._master() if is_master else await self._live(thread_id)
|
||||
conv = (await self._master())[0] if is_master else await self._live(thread_id)
|
||||
if command == "status":
|
||||
return await self._status(conv)
|
||||
if command == "merge":
|
||||
if conv is None or conv.kind != "branch":
|
||||
return "сливать нечего: это не открытая ветка"
|
||||
self._spawn_task(self._merge(conv))
|
||||
return "🔀 сливаю в мастер…"
|
||||
return "🔀 сливаю в мастер"
|
||||
if command == "new":
|
||||
if is_master or thread_id is None:
|
||||
child = await self.conversations.spawn(
|
||||
@@ -649,6 +668,10 @@ class TelegramFrontend(Frontend):
|
||||
key=f"{event.get('turn_id')}:error",
|
||||
)
|
||||
case "reply":
|
||||
if conv.kind == "master" and str(
|
||||
event.get("item_origin") or ""
|
||||
).startswith("сид"):
|
||||
return
|
||||
await self._on_reply(conv, event)
|
||||
case "say":
|
||||
await self._deliver(
|
||||
@@ -711,10 +734,10 @@ class TelegramFrontend(Frontend):
|
||||
if kind == "content_block_delta":
|
||||
delta = raw.get("delta") or {}
|
||||
if delta.get("type") == "text_delta":
|
||||
draft.set_status("✍️ пишу…")
|
||||
draft.set_status("✍️ пишу")
|
||||
draft.append(str(delta.get("text") or ""))
|
||||
elif delta.get("type") == "thinking_delta":
|
||||
draft.set_status("🤔 думаю…")
|
||||
draft.set_status("🤔 думаю")
|
||||
elif kind == "content_block_start":
|
||||
block = raw.get("content_block") or {}
|
||||
if block.get("type") == "tool_use":
|
||||
|
||||
Reference in New Issue
Block a user