285 lines
8.5 KiB
Python
285 lines
8.5 KiB
Python
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},
|
||
)
|