From ec57f7a59619c85b293fd987a7d2a75ba9631770 Mon Sep 17 00:00:00 2001 From: Apple Date: Sun, 30 Nov 2025 10:34:27 -0800 Subject: [PATCH] feat: Add Chat API endpoints for Matrix integration City Service: - GET /api/v1/chat/rooms/{room_id}/messages - get message history - POST /api/v1/chat/rooms/{room_id}/messages - send message - Add get_room_by_id to repo_city Synapse: - Increase rate limits for room creation - Add rc_joins, rc_invites configuration All 27 rooms now have matrix_room_id --- docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md | 338 ++++++++++++++++++++ services/city-service/repo_city.py | 18 ++ services/city-service/routes_city.py | 96 ++++++ 3 files changed, 452 insertions(+) create mode 100644 docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md diff --git a/docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md b/docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md new file mode 100644 index 00000000..a740bf2c --- /dev/null +++ b/docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md @@ -0,0 +1,338 @@ +# TASK_PHASE_MATRIX_FINALIZE_v2 — Matrix / Chat / Presence + +Version: 2.0 +Status: Ready +Priority: Critical (Live Chat & Presence) + +--- + +## 1. Поточний стан (вихідні дані) + +Вже виконано у попередніх фазах: + +- Matrix Gateway: + - ✅ `POST /internal/matrix/room/join` + - ✅ `POST /internal/matrix/message/send` + - ✅ `GET /internal/matrix/rooms/{id}/messages` +- City Service: + - ✅ Matrix-клієнт (join, send, get messages) + - ✅ `ensure_room_has_matrix()` + - ✅ `POST /city/rooms/sync/matrix` — bulk sync +- Auto-create Matrix Rooms: + - ✅ agent chat rooms (частково) + - ✅ node support rooms (NODE1, NODE2) + - ✅ microDAO lobby rooms (DAARION) +- Frontend: + - ✅ AgentChatWidget на `/agents/:agentId`, `/nodes/:nodeId`, `/microdao/:slug` + - ✅ Відображає "Увійти щоб почати спілкування" для неавторизованих + +Обмеження зараз: + +- частина кімнат має `matrix_room_id = NULL` через rate limiting Synapse +- не всі агенти додані в кімнати +- чат-виджет працює лише як оболонка (немає повного send/receive циклу) +- presence (online/offline) не реалізовано + +--- + +## 2. Мета TASK_PHASE_MATRIX_FINALIZE_v2 + +Завершити інтеграцію Matrix так, щоб: + +1. **Усі кімнати** (City, MicroDAO, Node, Agent) мали валідний `matrix_room_id`. +2. **Усі потрібні агенти** були учасниками своїх кімнат. +3. **Message flow** працював end-to-end: + - frontend → city-service → gateway → Matrix → gateway → city-service → frontend. +4. **Chat widget** показував реальні повідомлення (історія + нові). +5. **Presence (online/offline)** відображався для агентів. +6. **chat_available = true** там, де Matrix-кімната та gateway працюють. + +--- + +## 3. Scope (модулі та компоненти) + +Цей таск включає: + +1. Synapse (Matrix Homeserver) — налаштування rate limits. +2. Matrix Gateway — доопрацювання endpoint'ів та presence. +3. City Service — покращення room sync + чат API. +4. Frontend (Next.js) — повна інтеграція чат-виджета з Matrix. +5. Smoke-тести та короткий звіт. + +--- + +## 4. Модуль 1 — Synapse (Matrix HS) Rate Limits + +### 4.1. Завдання + +- Відкрити конфігурацію Synapse (homeserver.yaml або еквівалент). +- Підняти або вимкнути rate limiting для: + - room creation (createRoom) + - room join + - message send +- Увімкнути/перевірити: + - `/versions` + - `/presence` (якщо потрібно окремо) + +### 4.2. Орієнтовні параметри + +(Конкретні значення — на розсуд Cursor, але ціль — щоб sync 20–30 кімнат проходив без помилок.) + +```yaml +# homeserver.yaml +rc_message: + per_second: 100 + burst_count: 500 + +rc_joins: + local: + per_second: 50 + burst_count: 100 + remote: + per_second: 10 + burst_count: 20 + +rc_login: + address: + per_second: 10 + burst_count: 50 + account: + per_second: 10 + burst_count: 50 + +rc_admin_redaction: + per_second: 100 + burst_count: 500 +``` + +### 4.3. Acceptance + +- Повторний виклик `POST /city/rooms/sync/matrix` не падає по rate limit. +- Всі кімнати з `rooms` отримали `matrix_room_id`. + +--- + +## 5. Модуль 2 — Повна синхронізація кімнат + +### 5.1. City Rooms + +- Для кожної city-кімнати (`rooms.scope = 'city'`): + - якщо `matrix_room_id IS NULL` → створити через gateway (`room/create` або існуючу логіку). + - оновити `rooms.matrix_room_id`. + - додати системних агентів: + - DARIO, DARIA, DAARWIZZ + - інші public-агенти за потреби. + +### 5.2. MicroDAO Rooms + +- Для кожного microDAO: + - lobby, governance, news, builders, help (згідно Rooms_Layer_Architecture_v1). + - створити Matrix-кімнати, якщо ще не створені. + - orchestrator-агент має бути учасником lobby + governance. + - core-team агенти — учасники builders/news (якщо такі задані). + +### 5.3. Node Support Rooms + +- Для кожної ноди: + - кімната `node-{slug}-support`. + - додати: + - Node Guardian + - Node Steward + - (опційно) технічні агенти ноди (Pulse, Atomis, GuardianOS тощо). + +### 5.4. Agent Direct Rooms + +- Для кожного агента: + - створити або пересвідчитись в існуванні агенто-специфічної кімнати. + - агент повинен бути учасником. + - ця кімната використовується для direct-chat з агентом. + +### 5.5. Acceptance + +- Усі записи `rooms` мають заповнений `matrix_room_id` для: + - scope in ('city', 'microdao', 'node', 'agent'). +- Для кожного public-агента існує принаймні одна кімната, де він учасник. + +--- + +## 6. Модуль 3 — Message Flow (send/receive) + +### 6.1. Backend (city-service) + +Переконатись/додати: + +- API для надсилання повідомлень від імені користувача/агента: + - `POST /api/v1/chat/rooms/{room_id}/messages` + - вхід: `{ "body": "text" }` + - діє: + - перевіряє, які Matrix credentials використовувати (service user або impersonation) + - викликає `matrix-gateway /internal/matrix/message/send`. + +- API для отримання історії: + - `GET /api/v1/chat/rooms/{room_id}/messages?limit=50` + - повертає список повідомлень (час, автор, текст). + +За потреби використати існуючий `get_room_messages()` у city-service. + +### 6.2. Gateway + +- Гарантувати: + - правильне формування body для Matrix `send` (m.room.message, msgtype: m.text). + - розбір `GET /internal/matrix/rooms/{id}/messages` для history. + +### 6.3. Acceptance + +- Через API (curl/Postman) можливо: + - надіслати повідомлення у кімнату. + - прочитати історію. +- Це працює хоча б для: + - agent room (DAARWIZZ), + - node support room (NODE1), + - DAARION microDAO lobby. + +--- + +## 7. Модуль 4 — Presence (online/offline) + +### 7.1. Gateway + +- Реалізувати простий Presence Poller: + - періодично опитувати Matrix API (presence endpoints або sync). + - зберігати стан `online/away/offline` для Matrix user IDs агентів. + +- Надати internal endpoint: + - `GET /internal/matrix/presence/{matrix_user_id}` → `{ "state": "online" | "offline" | "unavailable" }`. + +### 7.2. City-Service + +- Додати endpoint: + - `GET /api/v1/agents/{agent_id}/presence` + - мапить agent_id → matrix_user_id → викликає gateway → повертає presence. + +- (Опційно) кешувати presence у пам'яті на 15–60 секунд. + +### 7.3. Frontend + +- На сторінках: + - `/agents` + - `/agents/:agentId` + - `/nodes/:nodeId` (для Guardian/Steward) + - `/microdao/:slug` (для orchestrator) + +Показати: + +- зелена точка — online +- жовта — away/degraded +- сіра — offline/unknown + +### 7.4. Acceptance + +- Presence-індикатори відображаються в UI. +- Відповідають реальному status (принаймні для test users/agents). + +--- + +## 8. Модуль 5 — Chat Widget FULL Integration + +### 8.1. AgentChatWidget (Next.js) + +Розширити: + +- Додавання станів: + - `loading` + - `no_room` + - `chat_available` + - `auth_required` + +- Для `chat_available = true`: + - при відкритті: + - `GET /api/v1/chat/rooms/{room_id}/messages?limit=N` — завантажити історію. + - при відправці: + - `POST /api/v1/chat/rooms/{room_id}/messages` — надіслати. + - відображати список повідомлень (без fancy UI, MVP-стиль). + +- Для неавторизованих: + - показати CTA "Увійти, щоб продовжити спілкування". + +### 8.2. Інтеграція за контекстами + +- Для `/agents/:agentId`: + - кімната агента (direct) +- Для `/nodes/:nodeId`: + - node support room +- Для `/microdao/:slug`: + - microDAO lobby room + +### 8.3. Acceptance + +- На кожній із трьох сторінок можливий діалог: + - відправити повідомлення, + - бачити історію, + - не отримувати помилки 4xx/5xx (за нормальних умов). + +--- + +## 9. Smoke-тести (обов'язкові) + +1. **DAARWIZZ Chat** + - Відкрити `/agents/daarwizz`. + - Відкрити чат. + - Написати повідомлення. + - Переконатися, що воно з'являється в історії. + +2. **NODE1 Guardian Chat** + - Відкрити `/nodes/node-1-hetzner-gex44`. + - Відкрити чат. + - Написати повідомлення. + - Переконатися, що воно доходить до Matrix. + +3. **DAARION MicroDAO Chat** + - Відкрити `/microdao/daarion`. + - Переконатися, що кімната існує, є історія, можна написати повідомлення. + +4. **Presence** + - На `/agents` бачити індикатор присутності (хоча б у базовій формі). + - Змінити стан в Matrix (якщо можливо) та перевірити оновлення. + +--- + +## 10. Вихідні артефакти + +Після виконання таска Cursor має створити: + +- `docs/debug/matrix_finalize_v2_report_.md` + Зміст: + - перелік створених Matrix кімнат; + - список agent rooms, node rooms, microDAO rooms; + - приклади presence-станів; + - приклад message flow (request/response); + - скріншоти або опис перевірених UI-сторінок. + +--- + +## 11. PROMPT ДЛЯ CURSOR + +Рекомендований текст: + +``` +Виконай, будь ласка, +docs/tasks/TASK_PHASE_MATRIX_FINALIZE_v2.md +у повному обсязі. +Зосередься на: +– Synapse rate limits, +– повній синхронізації Matrix rooms, +– message send/receive, +– presence, +– інтеграції AgentChatWidget з реальними повідомленнями. + +Після завершення створи файл +docs/debug/matrix_finalize_v2_report_.md +з результатами smoke-тестів та фактами реалізації. +``` + +--- + +**Target Date**: Immediate +**Priority**: Critical +**Dependencies**: Matrix Gateway running, Synapse accessible + diff --git a/services/city-service/repo_city.py b/services/city-service/repo_city.py index a2682b13..41f00bc5 100644 --- a/services/city-service/repo_city.py +++ b/services/city-service/repo_city.py @@ -142,6 +142,24 @@ async def get_room_by_slug(slug: str) -> Optional[dict]: return dict(row) if row else None +async def get_room_by_id(room_id: str) -> Optional[dict]: + """Отримати кімнату по ID (UUID)""" + pool = await get_pool() + + query = """ + SELECT + cr.id, cr.slug, cr.name, cr.description, cr.is_default, cr.created_at, cr.created_by, + cr.matrix_room_id, cr.matrix_room_alias, cr.logo_url, cr.banner_url, + cr.microdao_id, m.name AS microdao_name, m.slug AS microdao_slug, m.logo_url AS microdao_logo_url + FROM city_rooms cr + LEFT JOIN microdaos m ON cr.microdao_id = m.id + WHERE cr.id = $1 + """ + + row = await pool.fetchrow(query, room_id) + return dict(row) if row else None + + async def create_room( slug: str, name: str, diff --git a/services/city-service/routes_city.py b/services/city-service/routes_city.py index 05b83a54..d84e54dd 100644 --- a/services/city-service/routes_city.py +++ b/services/city-service/routes_city.py @@ -1589,6 +1589,102 @@ async def get_microdao_chat_room(slug: str): raise HTTPException(status_code=500, detail="Failed to get microdao chat room") +# ============================================================================= +# Chat API (TASK_PHASE_MATRIX_FINALIZE_v2) +# ============================================================================= + +class SendMessageRequest(BaseModel): + body: str + sender: Optional[str] = None + + +class ChatMessage(BaseModel): + event_id: str + sender: str + body: str + timestamp: int + + +@api_router.get("/chat/rooms/{room_id}/messages") +async def get_chat_messages(room_id: str, limit: int = 50): + """ + Отримати історію повідомлень з Matrix кімнати. + room_id може бути slug або UUID кімнати. + """ + try: + # Get room from DB + room = await repo_city.get_room_by_id(room_id) + if not room: + # Try by slug + room = await repo_city.get_room_by_slug(room_id) + + if not room: + raise HTTPException(status_code=404, detail=f"Room not found: {room_id}") + + matrix_room_id = room.get("matrix_room_id") + if not matrix_room_id: + raise HTTPException(status_code=400, detail="Room has no Matrix integration") + + # Get messages from Matrix via gateway + messages = await get_room_messages(matrix_room_id, limit=limit) + + return { + "room_id": room.get("id"), + "room_slug": room.get("slug"), + "matrix_room_id": matrix_room_id, + "messages": messages, + "count": len(messages) + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to get chat messages for {room_id}: {e}") + raise HTTPException(status_code=500, detail="Failed to get chat messages") + + +@api_router.post("/chat/rooms/{room_id}/messages") +async def send_chat_message(room_id: str, payload: SendMessageRequest): + """ + Надіслати повідомлення в Matrix кімнату. + room_id може бути slug або UUID кімнати. + """ + try: + # Get room from DB + room = await repo_city.get_room_by_id(room_id) + if not room: + # Try by slug + room = await repo_city.get_room_by_slug(room_id) + + if not room: + raise HTTPException(status_code=404, detail=f"Room not found: {room_id}") + + matrix_room_id = room.get("matrix_room_id") + if not matrix_room_id: + raise HTTPException(status_code=400, detail="Room has no Matrix integration") + + # Send message via gateway + event_id = await send_message_to_room( + room_id=matrix_room_id, + body=payload.body, + sender=payload.sender + ) + + if not event_id: + raise HTTPException(status_code=500, detail="Failed to send message to Matrix") + + return { + "ok": True, + "event_id": event_id, + "room_id": room.get("id"), + "matrix_room_id": matrix_room_id + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to send chat message to {room_id}: {e}") + raise HTTPException(status_code=500, detail="Failed to send chat message") + + # ============================================================================= # Presence API (TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP) # =============================================================================