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:
@@ -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
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user