feat: implement Agent Presence Indicators (MVP)

Backend:
- Add /api/v1/agents/presence endpoint
- Integrate with matrix-presence-aggregator
- Add DAGI router health checks

Frontend:
- Create useAgentPresence hook
- Create AgentPresenceBadge component
- Integrate into /agents list page
- Integrate into /agents/:agentId cabinet

Shows real-time online/offline status for all agents.
This commit is contained in:
Apple
2025-11-30 09:41:57 -08:00
parent 111d62a62a
commit fcdac0f33c
5 changed files with 341 additions and 14 deletions

View File

@@ -1407,6 +1407,115 @@ async def get_microdao_chat_room(slug: str):
raise HTTPException(status_code=500, detail="Failed to get microdao chat room")
# =============================================================================
# Presence API (TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP)
# =============================================================================
@api_router.get("/agents/presence")
async def get_agents_presence():
"""
Отримати presence статус всіх активних агентів.
Повертає Matrix presence + DAGI router health.
"""
try:
# Get all 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 (simplified for MVP)
dagi_health = await get_dagi_router_health()
# Combine presence data
presence_data = []
for agent in agents:
agent_id = agent["id"]
node_id = agent.get("node_id")
# Matrix presence
matrix_status = matrix_presence.get(agent_id, {}).get("status", "offline")
last_seen = matrix_presence.get(agent_id, {}).get("last_seen")
# DAGI router presence (node-level)
dagi_status = "unknown"
if node_id and node_id in dagi_health:
dagi_status = dagi_health[node_id].get("router_status", "unknown")
presence_data.append({
"agent_id": agent_id,
"display_name": agent.get("display_name", agent_id),
"matrix_presence": matrix_status,
"dagi_router_presence": dagi_status,
"last_seen": last_seen,
"node_id": 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")
async def get_matrix_presence_status():
"""
Get Matrix presence from matrix-presence-aggregator.
Returns dict: agent_id -> {status: 'online'|'unavailable'|'offline', last_seen: timestamp}
"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get("http://matrix-presence-aggregator:8080/api/presence/agents")
if response.status_code == 200:
data = response.json()
return data.get("agents", {})
else:
logger.warning(f"Matrix presence aggregator returned {response.status_code}")
return {}
except Exception as e:
logger.warning(f"Failed to get Matrix presence: {e}")
return {}
async def get_dagi_router_health():
"""
Get DAGI router health from node-registry or direct ping.
Returns dict: node_id -> {router_status: 'healthy'|'degraded'|'offline'}
"""
try:
# Try to get from node-registry first
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get("http://dagi-node-registry:9205/api/v1/health")
if response.status_code == 200:
data = response.json()
return data.get("nodes", {})
else:
logger.warning(f"Node registry returned {response.status_code}")
except Exception as e:
logger.warning(f"Failed to get DAGI router health from node-registry: {e}")
# Fallback: try direct ping to known nodes
try:
known_nodes = ["node-1-hetzner-gex44", "node-2-macbook-m4max"]
health_data = {}
for node_id in known_nodes:
try:
async with httpx.AsyncClient(timeout=2.0) as client:
response = await client.get(f"http://dagi-router-{node_id}:8080/health")
if response.status_code == 200:
health_data[node_id] = {"router_status": "healthy"}
else:
health_data[node_id] = {"router_status": "degraded"}
except Exception:
health_data[node_id] = {"router_status": "offline"}
return health_data
except Exception as e:
logger.warning(f"Failed to get DAGI router health: {e}")
return {}
# =============================================================================
# City Feed API
# =============================================================================