Files
microdao-daarion/docs/tasks/TASK_PHASE_AGENT_PRESENCE_INDICATORS_MVP.md
Apple 111d62a62a 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.
2025-11-30 09:22:29 -08:00

8.7 KiB
Raw Blame History

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:

@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:

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:

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:

// 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. Тестування

# Перевірити 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