diff --git a/docs/superpowers/specs/2026-03-31-matrix-adapter-design.md b/docs/superpowers/specs/2026-03-31-matrix-adapter-design.md index c7552a1..44ff120 100644 --- a/docs/superpowers/specs/2026-03-31-matrix-adapter-design.md +++ b/docs/superpowers/specs/2026-03-31-matrix-adapter-design.md @@ -20,12 +20,17 @@ Matrix-адаптер — внутренняя поверхность для к **Решение:** DM-комната с ботом = Чат #1. Space создаётся только при первом `!new`. -### Флоу +### Флоу — новый пользователь 1. Пользователь инвайтит бота в личные сообщения -2. Бот принимает инвайт, регистрирует DM-комнату как `chat_room` с `chat_id = C1` -3. Бот пишет приветствие в DM — пользователь сразу пишет -4. При первом `!new` — бот создаёт Space `Lambda — {display_name}`, добавляет DM-комнату в Space через `m.space.child`, создаёт новую комнату-чат +2. Бот принимает инвайт, вызывает `platform.get_or_create_user(matrix_user_id, "matrix", display_name)` +3. Бот регистрирует DM-комнату как `chat_room` с `chat_id = C1` в SQLite +4. Бот пишет приветствие в DM — пользователь сразу пишет +5. При первом `!new` — бот создаёт Space `Lambda — {display_name}`, добавляет DM-комнату в Space через `m.space.child`, создаёт новую комнату-чат + +### Флоу — возвращающийся пользователь + +Если `matrix_user_id` уже есть в БД (бот перезапустился, или пользователь пишет повторно) — `get_or_create_user` возвращает `is_new=False`. Бот не создаёт ничего заново, просто обрабатывает сообщение в контексте существующей комнаты. ### Почему не Space сразу @@ -181,10 +186,25 @@ def from_room_message(event: RoomMessageText, room_id: str, chat_id: str) -> Inc platform="matrix", chat_id=chat_id, # C1, C2... из rooms таблицы text=event.body, - attachments=[], + attachments=extract_attachments(event), reply_to=event.replyto_event_id, ) +def extract_attachments(event: RoomMessageText) -> list[Attachment]: + # m.image → Attachment(type="image", url=mxc_url, mime_type=...) + # m.file → Attachment(type="document", url=mxc_url, filename=..., mime_type=...) + # m.audio → Attachment(type="audio", url=mxc_url, mime_type=...) + # m.text → [] + msgtype = getattr(event, "msgtype", "m.text") + if msgtype == "m.image": + return [Attachment(type="image", url=event.url, mime_type=event.mimetype)] + elif msgtype == "m.file": + return [Attachment(type="document", url=event.url, + filename=event.body, mime_type=event.mimetype)] + elif msgtype == "m.audio": + return [Attachment(type="audio", url=event.url, mime_type=event.mimetype)] + return [] + def from_reaction(event: ReactionEvent, room_id: str) -> IncomingCallback | None: # Парсит m.reaction → IncomingCallback(action="toggle_skill" | "confirm" | "cancel") ...