feat(matrix): add adapter baseline and platform-aware command hints

This commit is contained in:
Mikhail Putilovskij 2026-04-01 01:04:54 +03:00
parent bcdaea5143
commit 82eb711844
20 changed files with 1127 additions and 3 deletions

View file

@ -0,0 +1,41 @@
from __future__ import annotations
from adapter.matrix.handlers.chat import (
handle_archive,
handle_list_chats,
handle_new_chat,
handle_rename,
)
from adapter.matrix.handlers.confirm import handle_cancel, handle_confirm
from adapter.matrix.handlers.settings import (
handle_settings,
handle_settings_connectors,
handle_settings_plan,
handle_settings_safety,
handle_settings_skills,
handle_settings_soul,
handle_settings_status,
handle_settings_whoami,
handle_toggle_skill,
)
from core.handler import EventDispatcher
from core.protocol import IncomingCallback, IncomingCommand
def register_matrix_handlers(dispatcher: EventDispatcher) -> None:
dispatcher.register(IncomingCommand, "new", handle_new_chat)
dispatcher.register(IncomingCommand, "chats", handle_list_chats)
dispatcher.register(IncomingCommand, "rename", handle_rename)
dispatcher.register(IncomingCommand, "archive", handle_archive)
dispatcher.register(IncomingCommand, "settings", handle_settings)
dispatcher.register(IncomingCommand, "settings_skills", handle_settings_skills)
dispatcher.register(IncomingCommand, "settings_connectors", handle_settings_connectors)
dispatcher.register(IncomingCommand, "settings_soul", handle_settings_soul)
dispatcher.register(IncomingCommand, "settings_safety", handle_settings_safety)
dispatcher.register(IncomingCommand, "settings_plan", handle_settings_plan)
dispatcher.register(IncomingCommand, "settings_status", handle_settings_status)
dispatcher.register(IncomingCommand, "settings_whoami", handle_settings_whoami)
dispatcher.register(IncomingCallback, "confirm", handle_confirm)
dispatcher.register(IncomingCallback, "cancel", handle_cancel)
dispatcher.register(IncomingCallback, "toggle_skill", handle_toggle_skill)

View file

@ -0,0 +1,34 @@
from __future__ import annotations
from typing import Any
from adapter.matrix.store import get_room_meta, set_room_meta
async def handle_invite(client: Any, room: Any, event: Any, platform, store, auth_mgr) -> None:
existing = await get_room_meta(store, room.room_id)
if existing is not None:
return
user = await platform.get_or_create_user(
external_id=getattr(event, "sender", ""),
platform="matrix",
display_name=getattr(room, "display_name", None),
)
await auth_mgr.confirm(getattr(event, "sender", ""))
await client.join(room.room_id)
await set_room_meta(
store,
room.room_id,
{
"room_type": "chat",
"chat_id": "C1",
"display_name": getattr(room, "display_name", room.room_id),
"matrix_user_id": getattr(event, "sender", user.external_id),
},
)
message = (
f"Привет, {user.display_name or user.external_id}! Пиши — я здесь.\n\n"
f"Команды: !new · !chats · !rename · !archive · !skills"
)
await client.room_send(room.room_id, "m.room.message", {"msgtype": "m.text", "body": message})

View file

@ -0,0 +1,50 @@
from __future__ import annotations
from core.protocol import IncomingCommand, OutgoingMessage
async def handle_new_chat(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
if not await auth_mgr.is_authenticated(event.user_id):
return [OutgoingMessage(chat_id=event.chat_id, text="Введите !start чтобы начать.")]
name = " ".join(event.args).strip() if event.args else ""
chats = await chat_mgr.list_active(event.user_id)
chat_id = f"C{len(chats) + 1}"
ctx = await chat_mgr.get_or_create(
user_id=event.user_id,
chat_id=chat_id,
platform=event.platform,
surface_ref=event.chat_id,
name=name or None,
)
return [
OutgoingMessage(
chat_id=event.chat_id, text=f"Создан чат: {ctx.display_name} ({ctx.chat_id})"
)
]
async def handle_list_chats(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
chats = await chat_mgr.list_active(event.user_id)
if not chats:
return [OutgoingMessage(chat_id=event.chat_id, text="Нет активных чатов.")]
lines = [f"{c.display_name} ({c.chat_id})" for c in chats]
return [OutgoingMessage(chat_id=event.chat_id, text="\n".join(lines))]
async def handle_rename(event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr) -> list:
if not event.args:
return [OutgoingMessage(chat_id=event.chat_id, text="Укажите название: !rename Название")]
ctx = await chat_mgr.rename(event.chat_id, " ".join(event.args), user_id=event.user_id)
return [OutgoingMessage(chat_id=event.chat_id, text=f"Переименован в: {ctx.display_name}")]
async def handle_archive(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
await chat_mgr.archive(event.chat_id, user_id=event.user_id)
return [OutgoingMessage(chat_id=event.chat_id, text="Чат архивирован.")]

View file

@ -0,0 +1,19 @@
from __future__ import annotations
from core.protocol import IncomingCallback, OutgoingMessage
async def handle_confirm(
event: IncomingCallback, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
action_id = event.payload.get("action_id", "unknown")
return [
OutgoingMessage(chat_id=event.chat_id, text=f"Действие подтверждено (id: {action_id}).")
]
async def handle_cancel(
event: IncomingCallback, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
action_id = event.payload.get("action_id", "unknown")
return [OutgoingMessage(chat_id=event.chat_id, text=f"Действие отменено (id: {action_id}).")]

View file

@ -0,0 +1,145 @@
from __future__ import annotations
from adapter.matrix.reactions import build_skills_text
from core.protocol import IncomingCommand, OutgoingMessage, SettingsAction
def _render_mapping(title: str, data: dict | None) -> str:
data = data or {}
lines = [title]
if not data:
lines.append("Нет данных.")
else:
for key, value in data.items():
lines.append(f"{key}: {value}")
return "\n".join(lines)
def _parse_bool(value: str) -> bool:
return value.lower() in {"1", "true", "yes", "on", "enable", "enabled"}
async def handle_settings(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
return [
OutgoingMessage(
chat_id=event.chat_id,
text=(
"⚙️ Настройки Matrix\n"
"!skills\n"
"!connectors\n"
"!soul [field value]\n"
"!safety [trigger on|off]\n"
"!plan\n"
"!status\n"
"!whoami"
),
)
]
async def handle_settings_skills(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
settings = await settings_mgr.get(event.user_id)
return [OutgoingMessage(chat_id=event.chat_id, text=build_skills_text(settings))]
async def handle_settings_connectors(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
settings = await settings_mgr.get(event.user_id)
return [
OutgoingMessage(
chat_id=event.chat_id, text=_render_mapping("🔗 Коннекторы", settings.connectors)
)
]
async def handle_settings_soul(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
if len(event.args) >= 2:
field = event.args[0]
value = " ".join(event.args[1:])
await settings_mgr.apply(
event.user_id,
SettingsAction(action="set_soul", payload={"field": field, "value": value}),
)
return [
OutgoingMessage(chat_id=event.chat_id, text=f"Личность обновлена: {field} = {value}")
]
settings = await settings_mgr.get(event.user_id)
return [
OutgoingMessage(chat_id=event.chat_id, text=_render_mapping("🧠 Личность", settings.soul))
]
async def handle_settings_safety(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
if len(event.args) >= 2:
trigger = event.args[0]
enabled = _parse_bool(event.args[1])
await settings_mgr.apply(
event.user_id,
SettingsAction(action="set_safety", payload={"trigger": trigger, "enabled": enabled}),
)
state = "включена" if enabled else "выключена"
return [OutgoingMessage(chat_id=event.chat_id, text=f"Безопасность {trigger} {state}")]
settings = await settings_mgr.get(event.user_id)
return [
OutgoingMessage(
chat_id=event.chat_id, text=_render_mapping("🔒 Безопасность", settings.safety)
)
]
async def handle_settings_plan(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
settings = await settings_mgr.get(event.user_id)
return [OutgoingMessage(chat_id=event.chat_id, text=_render_mapping("💳 План", settings.plan))]
async def handle_settings_status(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
chats = await chat_mgr.list_active(event.user_id)
settings = await settings_mgr.get(event.user_id)
text = "\n".join(
[
"📊 Статус",
f"Активных чатов: {len(chats)}",
f"Скиллов: {len(settings.skills)}",
f"Коннекторов: {len(settings.connectors)}",
]
)
return [OutgoingMessage(chat_id=event.chat_id, text=text)]
async def handle_settings_whoami(
event: IncomingCommand, auth_mgr, platform, chat_mgr, settings_mgr
) -> list:
return [OutgoingMessage(chat_id=event.chat_id, text=f"👤 {event.platform}:{event.user_id}")]
async def handle_toggle_skill(event, auth_mgr, platform, chat_mgr, settings_mgr) -> list:
settings = await settings_mgr.get(event.user_id)
keys = list(settings.skills.keys())
skill = event.payload.get("skill")
if not skill:
idx = event.payload.get("skill_index")
if isinstance(idx, int) and 1 <= idx <= len(keys):
skill = keys[idx - 1]
if not skill:
return [OutgoingMessage(chat_id=event.chat_id, text="Ошибка: не удалось определить навык.")]
enabled = not bool(settings.skills.get(skill, False))
await settings_mgr.apply(
event.user_id,
SettingsAction(action="toggle_skill", payload={"skill": skill, "enabled": enabled}),
)
state = "включён" if enabled else "выключен"
return [OutgoingMessage(chat_id=event.chat_id, text=f"Навык {skill} {state}.")]