from __future__ import annotations from typing import Any import structlog from nio.api import RoomVisibility from nio.responses import RoomCreateError from adapter.matrix.agent_registry import AgentRegistry from adapter.matrix.store import ( get_user_meta, next_platform_chat_id, set_room_meta, set_user_meta, ) logger = structlog.get_logger(__name__) def _default_room_name(chat_id: str) -> str: suffix = chat_id[1:] if chat_id.startswith("C") else chat_id return f"Чат {suffix}" def default_agent_notice() -> str: return ( "Внимание: ваш Matrix ID не найден в конфиге агентов. " "Пока используется агент по умолчанию. После добавления вас в конфиг " "бот переключит существующие комнаты на назначенного агента." ) async def _invite_if_possible(client: Any, room_id: str, matrix_user_id: str) -> bool: room_invite = getattr(client, "room_invite", None) if not callable(room_invite): return False try: await room_invite(room_id, matrix_user_id) return True except Exception as exc: logger.warning( "matrix_workspace_reinvite_failed", room_id=room_id, user=matrix_user_id, error=str(exc), ) return False async def provision_workspace_chat( client: Any, matrix_user_id: str, display_name: str, platform, store, auth_mgr, chat_mgr, room_name_override: str | None = None, registry: AgentRegistry | None = None, ) -> dict: user = await platform.get_or_create_user( external_id=matrix_user_id, platform="matrix", display_name=display_name, ) await auth_mgr.confirm(matrix_user_id) homeserver = matrix_user_id.split(":")[-1] user_meta = await get_user_meta(store, matrix_user_id) or {} space_id = user_meta.get("space_id") if not space_id: space_resp = await client.room_create( name=f"Lambda — {display_name}", space=True, visibility=RoomVisibility.private, invite=[matrix_user_id], ) if isinstance(space_resp, RoomCreateError): logger.error( "space creation failed", user=matrix_user_id, error=getattr(space_resp, "status_code", None), ) raise RuntimeError("Не удалось создать Space.") space_id = space_resp.room_id user_meta["space_id"] = space_id await set_user_meta(store, matrix_user_id, user_meta) next_chat_index = int(user_meta.get("next_chat_index", 1)) chat_id = f"C{next_chat_index}" platform_chat_id = await next_platform_chat_id(store) room_name = room_name_override or _default_room_name(chat_id) agent_id = None agent_assignment = "none" if registry is not None: assignment = registry.resolve_agent_for_user(matrix_user_id) agent_id = assignment.agent_id agent_assignment = assignment.source chat_resp = await client.room_create( name=room_name, visibility=RoomVisibility.private, is_direct=False, invite=[matrix_user_id], ) if isinstance(chat_resp, RoomCreateError): logger.error( "chat room creation failed", user=matrix_user_id, error=getattr(chat_resp, "status_code", None), ) raise RuntimeError("Не удалось создать рабочий чат.") chat_room_id = chat_resp.room_id await client.room_put_state( room_id=space_id, event_type="m.space.child", content={"via": [homeserver]}, state_key=chat_room_id, ) user_meta["space_id"] = space_id user_meta["next_chat_index"] = next_chat_index + 1 await set_user_meta(store, matrix_user_id, user_meta) await set_room_meta( store, chat_room_id, { "room_type": "chat", "chat_id": chat_id, "display_name": room_name, "matrix_user_id": matrix_user_id, "space_id": space_id, "platform_chat_id": platform_chat_id, "agent_id": agent_id, "agent_assignment": agent_assignment, }, ) await chat_mgr.get_or_create( user_id=matrix_user_id, chat_id=chat_id, platform="matrix", surface_ref=chat_room_id, name=room_name, ) return { "user": user, "space_id": space_id, "chat_room_id": chat_room_id, "chat_id": chat_id, "room_name": room_name, "agent_assignment": agent_assignment, "agent_id": agent_id, } async def restore_workspace_access( client: Any, matrix_user_id: str, display_name: str, platform, store, auth_mgr, chat_mgr, registry: AgentRegistry | None = None, ) -> dict: user_meta = await get_user_meta(store, matrix_user_id) or {} space_id = user_meta.get("space_id") if not space_id: created = await provision_workspace_chat( client, matrix_user_id, display_name, platform, store, auth_mgr, chat_mgr, room_name_override="Чат 1", registry=registry, ) return {**created, "reinvited_rooms": [], "created_new_chat": True} await auth_mgr.confirm(matrix_user_id) await _invite_if_possible(client, space_id, matrix_user_id) chats = await chat_mgr.list_active(matrix_user_id) if not chats: created = await provision_workspace_chat( client, matrix_user_id, display_name, platform, store, auth_mgr, chat_mgr, registry=registry, ) return {**created, "reinvited_rooms": [], "created_new_chat": True} reinvited_rooms = [] for chat in chats: if chat.surface_ref: if await _invite_if_possible(client, chat.surface_ref, matrix_user_id): reinvited_rooms.append(chat.surface_ref) return { "space_id": space_id, "reinvited_rooms": reinvited_rooms, "created_new_chat": False, } async def handle_invite( client: Any, room: Any, event: Any, platform, store, auth_mgr, chat_mgr, registry: AgentRegistry | None = None, ) -> None: matrix_user_id = getattr(event, "sender", "") display_name = getattr(room, "display_name", None) or matrix_user_id await client.join(room.room_id) existing = await get_user_meta(store, matrix_user_id) if existing and existing.get("space_id"): restored = await restore_workspace_access( client, matrix_user_id, display_name, platform, store, auth_mgr, chat_mgr, registry=registry, ) body = "Я отправил повторные приглашения в пространство Lambda и рабочие чаты." if restored.get("created_new_chat"): body = ( f"Создал новый рабочий чат {restored['room_name']} " f"({restored['chat_id']}) и отправил приглашение." ) if restored.get("agent_assignment") == "default": body = f"{body}\n\n{default_agent_notice()}" await client.room_send( room.room_id, "m.room.message", {"msgtype": "m.text", "body": body}, ) return try: created = await provision_workspace_chat( client, matrix_user_id, display_name, platform, store, auth_mgr, chat_mgr, room_name_override="Чат 1", registry=registry, ) except RuntimeError as exc: logger.error("invite_workspace_provision_failed", user=matrix_user_id, error=str(exc)) return welcome = ( f"Привет, {created['user'].display_name or matrix_user_id}! Пиши — я здесь.\n\n" "Команды: !new · !chats · !rename · !archive · !clear · !help" ) if created.get("agent_assignment") == "default": welcome = f"{welcome}\n\n{default_agent_notice()}" await client.room_send( created["chat_room_id"], "m.room.message", {"msgtype": "m.text", "body": welcome}, )