docs: add TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP.md
Task for implementing real-time presence indicators for all agents. Shows Matrix presence (online/unavailable/offline) and DAGI router health. Integrates into agent lists, cabinets, node dashboards, microdao dashboards.
This commit is contained in:
286
docs/tasks/TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP.md
Normal file
286
docs/tasks/TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP.md
Normal file
@@ -0,0 +1,286 @@
|
||||
# TASK PHASE — AGENT PRESENCE INDICATORS (MVP)
|
||||
|
||||
Version: 1.0
|
||||
Status: ACTIVE
|
||||
Target: DAARION.space (Next.js frontend + city-service)
|
||||
|
||||
## 1. Мета
|
||||
|
||||
Реалізувати **Presence Indicators** для всіх агентів у МВП:
|
||||
- Online/Offline статус з Matrix presence
|
||||
- DAGI Router heartbeat індикатори
|
||||
- Візуальні бейджі у всіх місцях де відображаються агенти
|
||||
|
||||
Правило онтології:
|
||||
> Агенти мають бути **"живими"** — їх статус має бути видимим завжди
|
||||
|
||||
---
|
||||
|
||||
## 2. Область робіт
|
||||
|
||||
### 2.1. Сторінки де потрібні Presence Indicators
|
||||
|
||||
1. **Agent List** (`/agents`) — кожен агент у списку
|
||||
2. **Agent Cabinet** (`/agents/:agentId`) — основний статус агента
|
||||
3. **Node Dashboard** (`/nodes/:nodeId`) — Node Core Agents (Guardian/Steward)
|
||||
4. **MicroDAO Dashboard** (`/microdao/:slug`) — Orchestrator + team agents
|
||||
5. **City Dashboard** (`/city`) — civic agents (DAARWIZZ, DARIO, DARIA)
|
||||
|
||||
### 2.2. Типи Presence
|
||||
|
||||
#### Matrix Presence
|
||||
- `online` — користувач активний (зелений)
|
||||
- `unavailable` — користувач неактивний >5 хв (жовтий)
|
||||
- `offline` — користувач офлайн (сірий)
|
||||
|
||||
#### DAGI Router Presence
|
||||
- `healthy` — DAGI router відповідає (зелений)
|
||||
- `degraded` — проблеми з router (жовтий)
|
||||
- `offline` — router недоступний (червоний)
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Implementation
|
||||
|
||||
### 3.1. Додати Presence API
|
||||
|
||||
Додати до `services/city-service/routes_city.py`:
|
||||
|
||||
```python
|
||||
@api_router.get("/agents/presence")
|
||||
async def get_agents_presence():
|
||||
"""
|
||||
Отримати presence статус всіх активних агентів.
|
||||
"""
|
||||
try:
|
||||
# Get agents from DB
|
||||
agents = await repo_city.list_agents_summaries(limit=1000)
|
||||
|
||||
# Get Matrix presence from matrix-presence-aggregator
|
||||
matrix_presence = await get_matrix_presence_status()
|
||||
|
||||
# Get DAGI router health
|
||||
dagi_health = await get_dagi_router_health()
|
||||
|
||||
# Combine data
|
||||
presence_data = []
|
||||
for agent in agents:
|
||||
matrix_status = matrix_presence.get(agent["id"], "offline")
|
||||
dagi_status = dagi_health.get(agent["node_id"], {}).get("router_status", "unknown")
|
||||
|
||||
presence_data.append({
|
||||
"agent_id": agent["id"],
|
||||
"matrix_presence": matrix_status,
|
||||
"dagi_router_presence": dagi_status,
|
||||
"last_seen": matrix_presence.get(f"{agent['id']}_last_seen"),
|
||||
"node_id": agent.get("node_id")
|
||||
})
|
||||
|
||||
return {"presence": presence_data}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get agents presence: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get agents presence")
|
||||
```
|
||||
|
||||
### 3.2. Інтегрувати з існуючими Presence системами
|
||||
|
||||
Використати:
|
||||
- `matrix-presence-aggregator` для Matrix presence
|
||||
- `city-service/presence_gateway.py` для forwarding
|
||||
- `usePresenceHeartbeat` hook для client-side heartbeats
|
||||
|
||||
---
|
||||
|
||||
## 4. Frontend Implementation
|
||||
|
||||
### 4.1. Presence Hook
|
||||
|
||||
Створити `apps/web/src/hooks/useAgentPresence.ts`:
|
||||
|
||||
```typescript
|
||||
interface AgentPresence {
|
||||
agent_id: string;
|
||||
matrix_presence: 'online' | 'unavailable' | 'offline';
|
||||
dagi_router_presence: 'healthy' | 'degraded' | 'offline' | 'unknown';
|
||||
last_seen?: string;
|
||||
node_id?: string;
|
||||
}
|
||||
|
||||
export function useAgentPresence(agentIds?: string[]) {
|
||||
const [presenceData, setPresenceData] = useState<Record<string, AgentPresence>>({});
|
||||
|
||||
// Fetch presence data
|
||||
const fetchPresence = useCallback(async () => {
|
||||
try {
|
||||
const params = agentIds ? `?agent_ids=${agentIds.join(',')}` : '';
|
||||
const res = await fetch(`/api/v1/agents/presence${params}`);
|
||||
const data = await res.json();
|
||||
setPresenceData(data.presence);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch agent presence:', error);
|
||||
}
|
||||
}, [agentIds]);
|
||||
|
||||
// Auto-refresh every 30 seconds
|
||||
useEffect(() => {
|
||||
fetchPresence();
|
||||
const interval = setInterval(fetchPresence, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [fetchPresence]);
|
||||
|
||||
return presenceData;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2. Presence Badge Component
|
||||
|
||||
Створити `apps/web/src/components/ui/AgentPresenceBadge.tsx`:
|
||||
|
||||
```typescript
|
||||
interface AgentPresenceBadgeProps {
|
||||
agentId: string;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
showLabel?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function AgentPresenceBadge({
|
||||
agentId,
|
||||
size = 'sm',
|
||||
showLabel = false,
|
||||
className
|
||||
}: AgentPresenceBadgeProps) {
|
||||
const presenceData = useAgentPresence([agentId]);
|
||||
const presence = presenceData[agentId];
|
||||
|
||||
if (!presence) return null;
|
||||
|
||||
const getStatusInfo = () => {
|
||||
const matrixStatus = presence.matrix_presence;
|
||||
const dagiStatus = presence.dagi_router_presence;
|
||||
|
||||
// Priority: Matrix status, then DAGI status
|
||||
if (matrixStatus === 'online') {
|
||||
return { color: 'bg-emerald-500', label: 'Online' };
|
||||
} else if (matrixStatus === 'unavailable') {
|
||||
return { color: 'bg-amber-500', label: 'Away' };
|
||||
} else if (dagiStatus === 'healthy') {
|
||||
return { color: 'bg-blue-500', label: 'Healthy' };
|
||||
} else if (dagiStatus === 'degraded') {
|
||||
return { color: 'bg-orange-500', label: 'Degraded' };
|
||||
} else if (dagiStatus === 'offline') {
|
||||
return { color: 'bg-red-500', label: 'Offline' };
|
||||
} else {
|
||||
return { color: 'bg-gray-500', label: 'Unknown' };
|
||||
}
|
||||
};
|
||||
|
||||
const statusInfo = getStatusInfo();
|
||||
const sizeClasses = {
|
||||
sm: 'w-2 h-2',
|
||||
md: 'w-3 h-3',
|
||||
lg: 'w-4 h-4'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-1.5', className)}>
|
||||
<div className={cn(
|
||||
'rounded-full border border-white/20',
|
||||
sizeClasses[size],
|
||||
statusInfo.color
|
||||
)} />
|
||||
{showLabel && (
|
||||
<span className="text-xs text-white/70">{statusInfo.label}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3. Інтеграція в Agent Cards
|
||||
|
||||
Додати Presence Badge до `apps/web/src/components/agent/AgentCard.tsx`:
|
||||
|
||||
```typescript
|
||||
// Inside AgentCard component
|
||||
<AgentPresenceBadge
|
||||
agentId={agent.id}
|
||||
size="sm"
|
||||
showLabel={false}
|
||||
className="absolute top-2 right-2"
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Acceptance Criteria
|
||||
|
||||
1. **Agent List** (`/agents`)
|
||||
- Кожен агент має presence indicator у правому верхньому куті аватара
|
||||
- Зеленый = online, Жовтий = away, Сірий = offline
|
||||
- Індикатор оновлюється в реальному часі
|
||||
|
||||
2. **Agent Cabinet** (`/agents/:agentId`)
|
||||
- Великий presence badge у заголовку
|
||||
- Показує обидва статуси: Matrix + DAGI Router
|
||||
- Індикатор "Last seen: 5 min ago"
|
||||
|
||||
3. **Node Dashboard** (`/nodes/:nodeId`)
|
||||
- Guardian/Steward агенти мають presence indicators
|
||||
- Показується DAGI router статус ноди
|
||||
|
||||
4. **MicroDAO Dashboard** (`/microdao/:slug`)
|
||||
- Orchestrator агент має presence indicator
|
||||
- Team agents мають presence indicators
|
||||
|
||||
5. **Real-time Updates**
|
||||
- Presence оновлюється автоматично кожні 30 секунд
|
||||
- Не блокує рендеринг якщо API недоступне
|
||||
|
||||
---
|
||||
|
||||
## 6. Файли до створення
|
||||
|
||||
### Backend
|
||||
- `services/city-service/routes_presence.py` — presence API endpoints
|
||||
- Інтеграція з `matrix-presence-aggregator`
|
||||
- Інтеграція з DAGI router health checks
|
||||
|
||||
### Frontend
|
||||
- `apps/web/src/hooks/useAgentPresence.ts` — presence hook
|
||||
- `apps/web/src/components/ui/AgentPresenceBadge.tsx` — presence badge component
|
||||
- Інтеграція в усі agent-related компоненти
|
||||
|
||||
### Конфігурація
|
||||
- Додати presence API до Next.js rewrites
|
||||
- Оновити presence gateway для forwarding agent presence
|
||||
|
||||
---
|
||||
|
||||
## 7. Тестування
|
||||
|
||||
```bash
|
||||
# Перевірити API
|
||||
curl https://daarion.space/api/v1/agents/presence
|
||||
|
||||
# Перевірити UI
|
||||
# Відкрити /agents - подивитися на presence indicators
|
||||
# Відкрити /agents/daarwizz - подивитися на великий badge
|
||||
# Відкрити /nodes/node-1-hetzner-gex44 - presence для Guardian
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Подальше розширення (не в цьому MVP)
|
||||
|
||||
- Agent typing indicators у кімнатах
|
||||
- Agent "working on task" статус
|
||||
- Presence history & analytics
|
||||
- Push notifications для presence changes
|
||||
- Agent availability scheduling
|
||||
|
||||
---
|
||||
|
||||
**Target Date**: Today
|
||||
**Priority**: High (core UX improvement)
|
||||
**Dependencies**: Matrix presence aggregator, DAGI router health API
|
||||
Reference in New Issue
Block a user