feat(telegram,core): master topic instead of general in private chats
This commit is contained in:
@@ -345,6 +345,28 @@ class Conversations:
|
||||
)
|
||||
return result.first()
|
||||
|
||||
async def last_binding(
|
||||
self, *, frontend: str, kind: str
|
||||
) -> ConversationBinding | None:
|
||||
"""The window ``frontend`` last used for a conversation of ``kind``.
|
||||
|
||||
A frontend whose window for the master outlives the master itself
|
||||
(the Telegram General topic) finds it here after a rotation.
|
||||
"""
|
||||
async with self._db.session() as session:
|
||||
result = await session.exec(
|
||||
select(ConversationBinding)
|
||||
.join(
|
||||
Conversation,
|
||||
col(Conversation.id) == col(ConversationBinding.conversation_id),
|
||||
)
|
||||
.where(
|
||||
ConversationBinding.frontend == frontend, Conversation.kind == kind
|
||||
)
|
||||
.order_by(col(ConversationBinding.id).desc())
|
||||
)
|
||||
return result.first()
|
||||
|
||||
async def set_flags(
|
||||
self, conv: Conversation, flags: dict[str, Any]
|
||||
) -> Conversation:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""``TelegramFrontend`` - the private chat with the bot as the window (§3.8).
|
||||
|
||||
General is the master, a topic is a branch. The user makes a topic and the
|
||||
A private chat with topics has no General: the gateway makes one topic for
|
||||
the master (``master_topic``) and rebinds it to every new master; any other
|
||||
topic is a branch. The user makes a topic and the
|
||||
first message in it spawns the branch (``seed=morning``); a message into a
|
||||
topic whose branch is merged or closed spawns a new branch on the same
|
||||
topic. Replies stream as drafts and land through the outbox; turns that
|
||||
@@ -112,6 +114,7 @@ class TelegramFrontend(Frontend):
|
||||
chat_id: int | None = None,
|
||||
attachments: Attachments = EPHEMERAL,
|
||||
draft_interval: float = 0.7,
|
||||
master_topic: str = "🦫 General",
|
||||
queued_reaction: str = "👀",
|
||||
poll_timeout: int = 30,
|
||||
outbox_backoff: float = 2.0,
|
||||
@@ -122,6 +125,7 @@ class TelegramFrontend(Frontend):
|
||||
self.master_agent = master_agent
|
||||
self.branch_agent = branch_agent
|
||||
self.attachments = attachments
|
||||
self.master_topic = master_topic
|
||||
self.draft_interval = draft_interval
|
||||
self.queued_reaction = queued_reaction
|
||||
self.poll_timeout = poll_timeout
|
||||
@@ -131,6 +135,7 @@ class TelegramFrontend(Frontend):
|
||||
self._inbox: Inbox | None = None
|
||||
self._outbox: Outbox | None = None
|
||||
self._targets: dict[str, tuple[int, int | None] | None] = {}
|
||||
self._master_window: str | None = None
|
||||
self._topic_names: dict[int, str] = {}
|
||||
self._drafts: dict[str, Draft] = {}
|
||||
self._asks: dict[str, _Ask] = {}
|
||||
@@ -179,7 +184,7 @@ class TelegramFrontend(Frontend):
|
||||
async def materialize(self, conv: Conversation) -> ConversationBinding | None:
|
||||
if conv.kind == "master":
|
||||
return await self.conversations.bind(
|
||||
conv, frontend=FRONTEND, external_id=self._ext(None)
|
||||
conv, frontend=FRONTEND, external_id=await self._master_ext()
|
||||
)
|
||||
if conv.kind != "branch":
|
||||
return None
|
||||
@@ -278,8 +283,30 @@ class TelegramFrontend(Frontend):
|
||||
dedupe_key=key,
|
||||
)
|
||||
|
||||
async def _master_ext(self) -> str:
|
||||
"""The master's window.
|
||||
|
||||
General in a forum group, our own topic in a private chat (Telegram
|
||||
has no General there). Created once, then found through whatever
|
||||
master used it last.
|
||||
"""
|
||||
if self._master_window is not None:
|
||||
return self._master_window
|
||||
last = await self.conversations.last_binding(frontend=FRONTEND, kind="master")
|
||||
if last is not None:
|
||||
self._master_window = last.external_id
|
||||
elif self.chat_id < 0:
|
||||
self._master_window = self._ext(None)
|
||||
else:
|
||||
topic = await self.bot.create_forum_topic(
|
||||
self.chat_id, name=self.master_topic[:128]
|
||||
)
|
||||
self._topic_names[topic.message_thread_id] = topic.name
|
||||
self._master_window = self._ext(topic.message_thread_id)
|
||||
return self._master_window
|
||||
|
||||
async def _master(self) -> Conversation:
|
||||
ext = self._ext(None)
|
||||
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
|
||||
@@ -329,8 +356,11 @@ class TelegramFrontend(Frontend):
|
||||
return
|
||||
in_topic = message.is_topic_message or message.forum_topic_created is not None
|
||||
thread_id = message.message_thread_id if in_topic else None
|
||||
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 await self._live(thread_id) is 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
|
||||
)
|
||||
@@ -351,7 +381,7 @@ class TelegramFrontend(Frontend):
|
||||
if command in _COMMANDS:
|
||||
await self._command(command, args.strip(), message, thread_id)
|
||||
return
|
||||
if thread_id is None:
|
||||
if is_master:
|
||||
conv = await self._master()
|
||||
else:
|
||||
conv = await self._live(thread_id)
|
||||
@@ -489,9 +519,10 @@ class TelegramFrontend(Frontend):
|
||||
async def _run_command(self, command: str, args: str, thread_id: int | None) -> str:
|
||||
if command in ("start", "help"):
|
||||
return _HELP
|
||||
conv = (
|
||||
await self._master() if thread_id is None else await self._live(thread_id)
|
||||
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)
|
||||
if command == "status":
|
||||
return await self._status(conv)
|
||||
if command == "merge":
|
||||
@@ -500,7 +531,7 @@ class TelegramFrontend(Frontend):
|
||||
self._spawn_task(self._merge(conv))
|
||||
return "🔀 сливаю в мастер…"
|
||||
if command == "new":
|
||||
if thread_id is None:
|
||||
if is_master or thread_id is None:
|
||||
child = await self.conversations.spawn(
|
||||
kind="branch",
|
||||
seed="morning",
|
||||
|
||||
Reference in New Issue
Block a user