surfaces/adapter/matrix/handlers/auth.py
Mikhail Putilovskij b1aaa210a1 feat(deploy): platform handoff — agent routing, persistence, docs cleanup
Agent routing:
- Remove !agent command and manual agent selection flow
- Registry auto-assigns agent from user_agents mapping (fallback: agents[0])
- provision_workspace_chat and !new both write agent_id to room_meta
- Reconciliation backfills agent_id from registry on cold start
- Fix duplicate agent_id block in auth.py

Deployment stability:
- Add bot-state named volume to persist lambda_matrix.db and matrix_store
- Fix docker-compose.prod.yml duplicate environment: key (was silently losing all Matrix credentials)
- Fix MATRIX_AGENT_REGISTRY_PATH to use absolute container path /app/config/...
- Add bot-state volume declaration to docker-compose.fullstack.yml

Docs and config:
- Rewrite README.md for platform handoff (deploy table, working commands only)
- Rewrite docs/matrix-prototype.md (remove stale commands and mock descriptions)
- Remove !save/!load/!context/!agent from help text and welcome message
- Add !clear, !list, !remove, !yes/!no to help text
- Clean up .env.example (remove Telegram token, internal vars, real URLs)
- Update config/matrix-agents.example.yaml with user_agents section and comments
- Add explanatory comment to Dockerfile for --ignore-requires-python
- Remove silent uv sync fallbacks in Dockerfile
2026-04-28 03:05:11 +03:00

175 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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}"
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
if registry is not None:
agent_id = registry.get_agent_id_for_user(matrix_user_id)
if agent_id is None and registry.agents:
agent_id = registry.agents[0].agent_id
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,
},
)
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,
}
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"):
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"
)
await client.room_send(
created["chat_room_id"],
"m.room.message",
{"msgtype": "m.text", "body": welcome},
)