From a7adddb60dda023c90c52578c07a7f82180f2301 Mon Sep 17 00:00:00 2001 From: Apple Date: Sun, 30 Nov 2025 11:48:44 -0800 Subject: [PATCH] feat: MicroDAO Rooms Integration Backend: - GET /city/microdao/{slug}/agents - list agents with roles - Seed: 6 rooms for DAARION, 3+ rooms for each District Task doc: TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md --- ...ASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md | 355 ++++++++++++++++++ services/city-service/routes_city.py | 65 ++++ 2 files changed, 420 insertions(+) create mode 100644 docs/tasks/TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md diff --git a/docs/tasks/TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md b/docs/tasks/TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md new file mode 100644 index 00000000..4fe3c19e --- /dev/null +++ b/docs/tasks/TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md @@ -0,0 +1,355 @@ +# TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1 + +Version: 1.0 +Status: Ready +Priority: High (City → District → MicroDAO контур) + +--- + +# 1. МЕТА + +Повністю інтегрувати Rooms Layer для MicroDAO: +- кожен MicroDAO має стандартний набір кімнат +- кімнати прив'язані до Matrix +- UI відображає кімнати з можливістю переходу та чату + +--- + +# 2. ВИХІДНІ ДАНІ + +Вже є: +- City Rooms (`/city/{slug}`) — 100% +- District Portals (`/districts/{slug}`) — 100% +- Matrix Integration — 100% +- Chat Widget — 100% +- Базова сторінка `/microdao/{slug}` + +Потрібно: +- API для кімнат MicroDAO +- Стандартні room-types +- UI секція кімнат на сторінці MicroDAO +- Seed data для існуючих MicroDAO + +--- + +# 3. SCOPE + +1. Backend API: + - `GET /api/v1/microdao/{slug}/rooms` + - `GET /api/v1/microdao/{slug}/agents` + - `POST /api/v1/microdao/{slug}/rooms` + +2. Стандартні room-types для MicroDAO + +3. Frontend: + - Секція "Кімнати MicroDAO" + - Секція "Агенти MicroDAO" + - Chat Widget для lobby + +4. Seed Data для DAARION та Districts + +5. Matrix auto-create для нових кімнат + +--- + +# 4. МОДУЛЬ 1 — BACKEND API + +## 4.1. GET /api/v1/microdao/{slug}/rooms + +Повертає список кімнат MicroDAO. + +```json +[ + { + "id": "room_microdao_daarion-lobby", + "slug": "daarion-lobby", + "name": "DAARION Lobby", + "description": "Головна кімната DAARION DAO", + "room_role": "lobby", + "is_public": true, + "matrix_room_id": "!abc:daarion.space", + "matrix_room_alias": "#daarion-lobby:daarion.space" + }, + ... +] +``` + +## 4.2. GET /api/v1/microdao/{slug}/agents + +Повертає агентів MicroDAO. + +```json +[ + { + "id": "daarwizz", + "name": "DAARWIZZ", + "kind": "governance", + "role": "orchestrator", + "is_core": true, + "status": "active" + }, + ... +] +``` + +## 4.3. POST /api/v1/microdao/{slug}/rooms + +Створює нову кімнату для MicroDAO. + +Request: +```json +{ + "slug": "daarion-treasury", + "name": "Treasury", + "description": "Фінанси та токеноміка", + "room_role": "treasury", + "is_public": false +} +``` + +Response: створена кімната з matrix_room_id + +--- + +# 5. МОДУЛЬ 2 — СТАНДАРТНІ ROOM-TYPES + +Кожен MicroDAO має мати набір кімнат: + +| room_role | Назва | Опис | is_public | +|-----------|-------|------|-----------| +| lobby | {DAO} Lobby | Головна кімната | true | +| governance | Governance | Голосування, пропозиції | true | +| operations | Operations | Операційна діяльність | false | +| knowledge | Knowledge Base | Документація, FAQ | true | +| treasury | Treasury | Фінанси, токеноміка | false | +| ai-core | AI Core | Агенти, автоматизація | false | + +--- + +# 6. МОДУЛЬ 3 — REPO_CITY МЕТОДИ + +```python +async def get_microdao_rooms(microdao_id: str) -> List[Dict]: + """Отримати кімнати MicroDAO""" + query = """ + SELECT id, slug, name, description, room_role, is_public, + matrix_room_id, matrix_room_alias + FROM city_rooms + WHERE owner_id = $1 AND owner_type = 'microdao' + ORDER BY sort_order, name + """ + rows = await pool.fetch(query, microdao_id) + return [dict(r) for r in rows] + +async def get_microdao_agents(microdao_id: str) -> List[Dict]: + """Отримати агентів MicroDAO""" + query = """ + SELECT a.id, a.display_name as name, a.kind, a.status, + a.avatar_url, ma.role, ma.is_core + FROM agents a + JOIN microdao_agents ma ON ma.agent_id = a.id + WHERE ma.microdao_id = $1 + ORDER BY ma.is_core DESC, a.display_name + """ + rows = await pool.fetch(query, microdao_id) + return [dict(r) for r in rows] + +async def create_microdao_room( + microdao_id: str, + slug: str, + name: str, + description: str, + room_role: str, + is_public: bool = True +) -> Dict: + """Створити кімнату для MicroDAO з Matrix""" + # 1. Create Matrix room + matrix_room_id, matrix_room_alias = await ensure_room_has_matrix( + slug, name, "public" if is_public else "private" + ) + + # 2. Insert into DB + room_id = f"room_microdao_{slug}" + query = """ + INSERT INTO city_rooms ( + id, slug, name, description, created_by, + owner_type, owner_id, room_role, is_public, + matrix_room_id, matrix_room_alias, sort_order + ) + VALUES ($1, $2, $3, $4, 'u_system', 'microdao', $5, $6, $7, $8, $9, $10) + RETURNING * + """ + sort_order = { + 'lobby': 10, 'governance': 20, 'operations': 30, + 'knowledge': 40, 'treasury': 50, 'ai-core': 60 + }.get(room_role, 100) + + row = await pool.fetchrow( + query, room_id, slug, name, description, + microdao_id, room_role, is_public, + matrix_room_id, matrix_room_alias, sort_order + ) + return dict(row) +``` + +--- + +# 7. МОДУЛЬ 4 — FRONTEND + +## 7.1. Оновити /microdao/[slug]/page.tsx + +Додати секції: +- MicroDAO Rooms (список кімнат з картками) +- MicroDAO Agents (список агентів) +- Chat Widget для lobby room + +## 7.2. Компонент MicroDAORoomsSection + +```tsx +function MicroDAORoomsSection({ rooms }: { rooms: Room[] }) { + return ( +
+

+ Кімнати MicroDAO +

+
+ {rooms.map(room => ( + +
+

{room.name}

+

{room.room_role}

+ {room.matrix_room_id && ( + + )} +
+ + ))} +
+
+ ) +} +``` + +## 7.3. Компонент MicroDAOAgentsSection + +```tsx +function MicroDAOAgentsSection({ agents }: { agents: Agent[] }) { + return ( +
+

+ Агенти MicroDAO +

+
+ {agents.map(agent => ( + +
+
+
+

{agent.name}

+

{agent.role}

+
+ +
+ + ))} +
+
+ ) +} +``` + +--- + +# 8. МОДУЛЬ 5 — SEED DATA + +## 8.1. DAARION DAO (dao_daarion) + +```sql +INSERT INTO city_rooms (id, slug, name, description, created_by, owner_type, owner_id, room_role, is_public, sort_order) +VALUES + ('room_microdao_daarion-lobby', 'daarion-lobby', 'DAARION Lobby', 'Головна кімната DAARION DAO', 'u_system', 'microdao', 'dao_daarion', 'lobby', true, 10), + ('room_microdao_daarion-governance', 'daarion-governance', 'Governance', 'Голосування та пропозиції', 'u_system', 'microdao', 'dao_daarion', 'governance', true, 20), + ('room_microdao_daarion-operations', 'daarion-operations', 'Operations', 'Операційна діяльність', 'u_system', 'microdao', 'dao_daarion', 'operations', false, 30), + ('room_microdao_daarion-knowledge', 'daarion-knowledge', 'Knowledge Base', 'Документація та FAQ', 'u_system', 'microdao', 'dao_daarion', 'knowledge', true, 40), + ('room_microdao_daarion-treasury', 'daarion-treasury', 'Treasury', 'Фінанси та токеноміка', 'u_system', 'microdao', 'dao_daarion', 'treasury', false, 50), + ('room_microdao_daarion-ai-core', 'daarion-ai-core', 'AI Core', 'Агенти та автоматизація', 'u_system', 'microdao', 'dao_daarion', 'ai-core', false, 60) +ON CONFLICT (slug) DO UPDATE SET + name = EXCLUDED.name, + room_role = EXCLUDED.room_role, + owner_id = EXCLUDED.owner_id; +``` + +## 8.2. Districts (SOUL, GREENFOOD, ENERGYUNION) + +Аналогічно для кожного District-а. + +--- + +# 9. МОДУЛЬ 6 — MATRIX AUTO-CREATE + +При створенні кімнати: +1. Викликати `ensure_room_has_matrix(slug, name, visibility)` +2. Отримати matrix_room_id +3. Додати orchestrator агента до кімнати через `join_user_to_room()` +4. Зберегти matrix_room_id в БД + +--- + +# 10. SMOKE-ТЕСТИ + +1. `/microdao/daarion`: + - Показує 6 кімнат + - Кожна кімната має Matrix статус (зелена точка) + - Клік → відкриває `/city/daarion-lobby` + +2. API: + - `GET /api/v1/microdao/daarion/rooms` → 6 кімнат + - `GET /api/v1/microdao/daarion/agents` → агенти DAO + +3. Chat Widget: + - На сторінці MicroDAO працює чат для lobby + +4. Districts: + - `/microdao/soul`, `/microdao/greenfood`, `/microdao/energy-union` + - Мають свої кімнати + +--- + +# 11. ФІНАЛЬНИЙ ЗВІТ + +Після виконання створити: + +`docs/debug/microdao_rooms_integration_report_.md` + +Зміст: +- Список кімнат для кожного MicroDAO +- API responses +- Скріншоти/описи UI +- Matrix статус кімнат + +--- + +# 12. PROMPT ДЛЯ CURSOR + +```text +Виконай TASK_PHASE_MICRODAO_ROOMS_INTEGRATION_v1.md. + +Фокус: +1) Backend: GET /api/v1/microdao/{slug}/rooms, /agents +2) repo_city: get_microdao_rooms, get_microdao_agents, create_microdao_room +3) Seed: 6 кімнат для DAARION, по 6 для кожного District +4) Frontend: MicroDAORoomsSection, MicroDAOAgentsSection +5) Matrix: auto-create для нових кімнат + +Після завершення створи: +docs/debug/microdao_rooms_integration_report_.md +``` + +--- + +**Target Date**: Immediate +**Priority**: High +**Dependencies**: District Portals complete, Matrix integration working + diff --git a/services/city-service/routes_city.py b/services/city-service/routes_city.py index 1b94765b..3a9ef80f 100644 --- a/services/city-service/routes_city.py +++ b/services/city-service/routes_city.py @@ -2777,6 +2777,71 @@ async def get_microdao_rooms_endpoint(slug: str): raise HTTPException(status_code=500, detail="Failed to get microdao rooms") +@router.get("/microdao/{slug}/agents") +async def get_microdao_agents_endpoint(slug: str): + """ + Отримати всіх агентів MicroDAO. + Повертає список агентів з їх ролями та статусами. + """ + try: + # Get microdao by slug + dao = await repo_city.get_microdao_by_slug(slug) + if not dao: + raise HTTPException(status_code=404, detail=f"MicroDAO not found: {slug}") + + # Get agents from microdao_agents + pool = await repo_city.get_pool() + query = """ + SELECT + a.id, + a.display_name as name, + a.kind, + a.status, + a.avatar_url, + a.gov_level, + ma.role, + ma.is_core + FROM agents a + JOIN microdao_agents ma ON ma.agent_id = a.id + WHERE ma.microdao_id = $1 + AND COALESCE(a.is_archived, false) = false + AND a.deleted_at IS NULL + ORDER BY + ma.is_core DESC, + CASE ma.role + WHEN 'orchestrator' THEN 0 + WHEN 'district_lead' THEN 1 + WHEN 'core_team' THEN 2 + ELSE 3 + END, + a.display_name + """ + rows = await pool.fetch(query, dao["id"]) + + return { + "microdao_id": dao["id"], + "microdao_slug": dao["slug"], + "agents": [ + { + "id": r["id"], + "name": r["name"], + "kind": r["kind"], + "status": r["status"], + "avatar_url": r.get("avatar_url"), + "gov_level": r.get("gov_level"), + "role": r["role"], + "is_core": r["is_core"] + } + for r in rows + ] + } + except HTTPException: + raise + except Exception as e: + logger.error(f"Failed to get microdao agents for {slug}: {e}") + raise HTTPException(status_code=500, detail="Failed to get microdao agents") + + @router.post("/microdao/{slug}/rooms/attach-existing", response_model=CityRoomSummary) async def attach_existing_room_endpoint( slug: str,