- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
449 lines
12 KiB
Markdown
449 lines
12 KiB
Markdown
# TASK PHASE 9 — LIVING MAP (FULL STACK SERVICE)
|
||
|
||
Version: 1.0
|
||
Status: READY FOR IMPLEMENTATION
|
||
Scope: Backend + WebSocket + NATS + Minimal Frontend Hook
|
||
|
||
## 1. Context
|
||
|
||
DAARION уже має:
|
||
|
||
- core сервіси:
|
||
- `messaging-service` (Matrix-aware Messenger)
|
||
- `agent-runtime`, `agent-filter`, `dagi-router`
|
||
- `llm-proxy`, `memory-orchestrator`, `toolcore`
|
||
- `auth-service`, `pdp-service`, `usage-engine`
|
||
- `agents-service` (Agent Hub + lifecycle)
|
||
- `microdao-service` (microDAO Console)
|
||
- `dao-service` (DAO Dashboard)
|
||
- `city-service`, `space-service`
|
||
- інфраструктуру:
|
||
- PostgreSQL
|
||
- NATS JetStream
|
||
- docker-compose для різних фаз
|
||
- WebSocket-потоки для окремих модулів
|
||
|
||
Потрібен **єдиний "Living Map" шар**, який агрегує стан всієї мережі:
|
||
|
||
- City (microDAO, метрики)
|
||
- Space (DAO-планети, ноди)
|
||
- Nodes (ресурси, алерти)
|
||
- Agents (статус, використання)
|
||
- DAO (голосування, proposals)
|
||
- Messaging (активність каналів)
|
||
|
||
і видає це:
|
||
|
||
- через `GET /living-map/snapshot` (full-state)
|
||
- через `GET /living-map/history` (event log)
|
||
- через `GET /living-map/entities` (каталог сутностей)
|
||
- через `WS /living-map/stream` (живий потік подій).
|
||
|
||
Цей таск — **backend + API + NATS + WS + базовий frontend hook**.
|
||
|
||
---
|
||
|
||
## 2. Goals
|
||
|
||
1. Створити сервіс `living-map-service` (FastAPI), порт `7017`.
|
||
2. Зібрати стан мережі з існуючих сервісів **в один snapshot**.
|
||
3. Підписатися на ключові NATS-сабджекти й зберігати історію в `living_map_history`.
|
||
4. Віддавати:
|
||
- `snapshot` (HTTP)
|
||
- `history` (HTTP)
|
||
- `real-time stream` (WS).
|
||
5. Додати мінімальну фронтенд-обгортку `useLivingMapFull` (React hook) для інтеграції з 2D/3D UI.
|
||
|
||
---
|
||
|
||
## 3. Architecture Overview
|
||
|
||
### 3.1. New service: `living-map-service`
|
||
|
||
- Stack: Python 3 + FastAPI + uvicorn
|
||
- Port: `7017`
|
||
- Responsibilities:
|
||
- Агрегувати дані з:
|
||
- `city-service` (city snapshot)
|
||
- `space-service` (planets/nodes/events)
|
||
- `agents-service` (agents, metrics, events)
|
||
- `microdao-service` (microDAOs)
|
||
- `dao-service` (dao, proposals, votes)
|
||
- `usage-engine` (LLM/tool usage summary)
|
||
- Нормалізувати в **єдину структуру сцени**:
|
||
- `scene.layers.city`
|
||
- `scene.layers.space`
|
||
- `scene.layers.nodes`
|
||
- `scene.layers.agents`
|
||
- `scene.meta` (timestamps, version)
|
||
- Підписуватись на NATS-subject'и й створювати event log.
|
||
- Видавати snapshot/history + WebSocket stream.
|
||
|
||
### 3.2. Data sources (HTTP)
|
||
|
||
Очікувані існуючі/доступні ендпоінти (можна створити прості адаптери, якщо їх ще нема):
|
||
|
||
- `city-service`:
|
||
- `GET http://city-service:7001/api/city/snapshot`
|
||
- `space-service`:
|
||
- `GET http://space-service:7002/api/space/scene` або окремо planets/nodes/events
|
||
- `agents-service`:
|
||
- `GET http://agents-service:7014/agents` (list)
|
||
- `GET http://agents-service:7014/agents/metrics` (summary)
|
||
- `microdao-service`:
|
||
- `GET http://microdao-service:7015/microdaos` (list)
|
||
- `dao-service`:
|
||
- `GET http://dao-service:7016/dao` (list)
|
||
- `GET http://dao-service:7016/dao/proposals/summary`
|
||
- `usage-engine`:
|
||
- `GET http://usage-engine:7013/internal/usage/summary?period_hours=24`
|
||
|
||
Якщо чогось немає — зробити простий adapter/placeholder з mock даними всередині `living-map-service`.
|
||
|
||
### 3.3. NATS Subjects
|
||
|
||
Потрібно підписатися на:
|
||
|
||
- `city.event.*`
|
||
- `dao.event.*`
|
||
- `microdao.event.*`
|
||
- `node.metrics.*`
|
||
- `agent.event.*`
|
||
- `usage.llm.*`
|
||
- `usage.agent.*`
|
||
- `messaging.message.created`
|
||
|
||
(Якщо частина сабджектів ще не існує — підготувати consumer з graceful handling та вимкненими/placeholder subscriptions.)
|
||
|
||
---
|
||
|
||
## 4. API Specification
|
||
|
||
### 4.1. `GET /living-map/health`
|
||
|
||
Простий health-check.
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"service": "living-map-service",
|
||
"version": "1.0.0",
|
||
"time": "2025-11-24T12:34:56Z"
|
||
}
|
||
```
|
||
|
||
### 4.2. `GET /living-map/snapshot`
|
||
|
||
Агрегований стан усієї мережі.
|
||
|
||
Response (спрощений приклад):
|
||
|
||
```json
|
||
{
|
||
"generated_at": "2025-11-24T12:34:56Z",
|
||
"layers": {
|
||
"city": {
|
||
"microdaos_total": 12,
|
||
"active_users": 57,
|
||
"active_agents": 34,
|
||
"health": "green",
|
||
"items": [
|
||
{
|
||
"id": "microdao:7",
|
||
"slug": "daarion-city",
|
||
"name": "DAARION City",
|
||
"status": "active",
|
||
"agents": 9,
|
||
"nodes": 3
|
||
}
|
||
]
|
||
},
|
||
"space": {
|
||
"planets": [
|
||
{
|
||
"id": "dao:daarion-core",
|
||
"name": "DAARION CORE",
|
||
"type": "dao",
|
||
"orbits": ["node:gpu-1", "node:gpu-2"],
|
||
"status": "active"
|
||
}
|
||
],
|
||
"nodes": [
|
||
{
|
||
"id": "node:gpu-1",
|
||
"name": "NODE1",
|
||
"cpu": 0.42,
|
||
"gpu": 0.77,
|
||
"memory": 0.63,
|
||
"alerts": []
|
||
}
|
||
]
|
||
},
|
||
"nodes": {
|
||
"items": [
|
||
{
|
||
"id": "node:gpu-1",
|
||
"microdao_id": "microdao:7",
|
||
"status": "online",
|
||
"metrics": {
|
||
"cpu": 0.42,
|
||
"gpu": 0.77,
|
||
"ram": 0.63,
|
||
"net_in": 12345,
|
||
"net_out": 9876
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"agents": {
|
||
"items": [
|
||
{
|
||
"id": "agent:sofia",
|
||
"name": "Sofia",
|
||
"kind": "system",
|
||
"microdao_id": "microdao:7",
|
||
"status": "online",
|
||
"usage": {
|
||
"llm_calls_24h": 123,
|
||
"tokens_24h": 45678
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"meta": {
|
||
"source_services": [
|
||
"city-service",
|
||
"space-service",
|
||
"agents-service",
|
||
"microdao-service",
|
||
"dao-service",
|
||
"usage-engine"
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3. `GET /living-map/entities`
|
||
|
||
Плоский список сутностей з мінімальними даними для побудови legend/списків.
|
||
|
||
Query params:
|
||
|
||
* `type` (optional): `city|space|node|agent|dao|microdao|channel`
|
||
* `limit` (optional, default 100)
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "microdao:7",
|
||
"type": "microdao",
|
||
"label": "DAARION City",
|
||
"status": "active",
|
||
"layer": "city"
|
||
},
|
||
{
|
||
"id": "dao:daarion-core",
|
||
"type": "dao",
|
||
"label": "DAARION CORE DAO",
|
||
"status": "active",
|
||
"layer": "space"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 4.4. `GET /living-map/entities/{id}`
|
||
|
||
Детальний опис сутності (проксі до відповідного сервісу з нормалізацією).
|
||
|
||
Response (узагальнений):
|
||
|
||
```json
|
||
{
|
||
"id": "agent:sofia",
|
||
"type": "agent",
|
||
"layer": "agents",
|
||
"data": {
|
||
"...": "raw or normalized fields"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.5. `GET /living-map/history`
|
||
|
||
Історія подій Living Map.
|
||
|
||
Query params:
|
||
|
||
* `since` (optional, ISO datetime)
|
||
* `limit` (optional, default 200)
|
||
|
||
Response:
|
||
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": "evt-uuid",
|
||
"timestamp": "2025-11-24T12:30:00Z",
|
||
"event_type": "node.metrics.update",
|
||
"payload": {
|
||
"node_id": "node:gpu-1",
|
||
"cpu": 0.8,
|
||
"gpu": 0.95
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 4.6. `WS /living-map/stream`
|
||
|
||
WebSocket-потік:
|
||
|
||
* Типи повідомлень:
|
||
|
||
* `snapshot` — повний стан (на підключення та за потреби)
|
||
* `event` — одинична подія
|
||
* Формат:
|
||
|
||
```json
|
||
{
|
||
"kind": "event",
|
||
"event_type": "agent.event.status",
|
||
"timestamp": "2025-11-24T12:34:56Z",
|
||
"payload": { ... }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Database Schema
|
||
|
||
Створити міграцію, наприклад: `migrations/010_create_living_map_tables.sql`.
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS living_map_history (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
timestamp timestamptz NOT NULL DEFAULT now(),
|
||
event_type TEXT NOT NULL,
|
||
payload JSONB NOT NULL
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_living_map_history_timestamp
|
||
ON living_map_history (timestamp DESC);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_living_map_history_event_type
|
||
ON living_map_history (event_type);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Implementation Plan
|
||
|
||
### 6.1. Files to create
|
||
|
||
* `services/living-map-service/main.py`
|
||
* `services/living-map-service/models.py`
|
||
* `services/living-map-service/repository_history.py`
|
||
* `services/living-map-service/adapters/city_client.py`
|
||
* `services/living-map-service/adapters/space_client.py`
|
||
* `services/living-map-service/adapters/agents_client.py`
|
||
* `services/living-map-service/adapters/microdao_client.py`
|
||
* `services/living-map-service/adapters/dao_client.py`
|
||
* `services/living-map-service/adapters/usage_client.py`
|
||
* `services/living-map-service/nats_subscriber.py`
|
||
* `services/living-map-service/ws_stream.py`
|
||
* `services/living-map-service/Dockerfile`
|
||
* `services/living-map-service/requirements.txt`
|
||
* `docker-compose.phase9.yml` (або оновити загальний)
|
||
* `scripts/start-phase9.sh`
|
||
* `scripts/stop-phase9.sh`
|
||
* `migrations/010_create_living_map_tables.sql`
|
||
* Frontend:
|
||
|
||
* `src/features/livingMap/hooks/useLivingMapFull.ts`
|
||
|
||
### 6.2. Minimal frontend hook
|
||
|
||
`useLivingMapFull.ts`:
|
||
|
||
* `GET /living-map/snapshot` → зберігає state
|
||
* Підключає WS `/living-map/stream`
|
||
* При `kind=event` оновлює локальний state (immutable update)
|
||
* Повертає:
|
||
|
||
* `snapshot`
|
||
* `events`
|
||
* `isLoading`
|
||
* `error`
|
||
* `connectionStatus`
|
||
|
||
---
|
||
|
||
## 7. Security / Auth
|
||
|
||
* Всі HTTP-ендпоінти:
|
||
|
||
* Перевірка JWT/Session через `auth-service` (ActorContext).
|
||
* Опціонально, `GET /living-map/snapshot` може мати:
|
||
|
||
* `public_mode` (спрощений, анонімний)
|
||
* або вимагати auth (рекомендовано).
|
||
* WS:
|
||
|
||
* `Authorization: Bearer <token>` у заголовках.
|
||
* NATS:
|
||
|
||
* лише internal subjects, використовувати існуючий NATS connection з параметрами як в інших сервісах.
|
||
|
||
---
|
||
|
||
## 8. TODO Checklist
|
||
|
||
Backend:
|
||
|
||
* [ ] Створити міграцію `010_create_living_map_tables.sql`
|
||
* [ ] Створити `living-map-service` структуру
|
||
* [ ] Реалізувати адаптери до інших сервісів (з timeout/retry)
|
||
* [ ] Реалізувати `GET /living-map/health`
|
||
* [ ] Реалізувати `GET /living-map/snapshot`
|
||
* [ ] Реалізувати `GET /living-map/entities`
|
||
* [ ] Реалізувати `GET /living-map/entities/{id}`
|
||
* [ ] Реалізувати `GET /living-map/history`
|
||
* [ ] Реалізувати `WS /living-map/stream`
|
||
* [ ] Реалізувати `nats_subscriber.py` для key subjects
|
||
* [ ] Інтегрувати Auth/PDP (як у інших сервісах)
|
||
* [ ] Додати Dockerfile + requirements.txt
|
||
* [ ] Оновити docker-compose/script'и
|
||
* [ ] Додати базові unit tests (snapshot builder)
|
||
|
||
Frontend:
|
||
|
||
* [ ] Створити `useLivingMapFull.ts`
|
||
* [ ] Протестувати запит snapshot + WS stream (можна через тимчасовий debug-компонент)
|
||
|
||
---
|
||
|
||
## 9. Acceptance Criteria
|
||
|
||
1. `docker-compose -f docker-compose.phase9.yml up -d` піднімає `living-map-service` без помилок.
|
||
2. `GET /living-map/health` повертає `status=ok`.
|
||
3. `GET /living-map/snapshot` повертає валідний JSON з `layers.city`, `layers.space`, `layers.nodes`, `layers.agents`.
|
||
4. `GET /living-map/history` повертає список подій, які приходять з NATS.
|
||
5. `WS /living-map/stream` надсилає:
|
||
|
||
* при підключенні: `kind="snapshot"`
|
||
* далі: `kind="event"` при нових подіях.
|
||
6. `useLivingMapFull` успішно підключається до API+WS і оновлює локальний state без TypeScript помилок.
|
||
7. Уся нова логіка проходить лінтер та тести.
|
||
|
||
---
|
||
|
||
END OF TASK
|
||
|