'use client' import { useState, useEffect, useCallback } from 'react' import { useSmartPoll } from '@/lib/use-smart-poll' import { createClientLogger } from '@/lib/client-logger' import { AgentAvatar } from '@/components/ui/agent-avatar' import { OverviewTab, SoulTab, MemoryTab, TasksTab, ActivityTab, ConfigTab, CreateAgentModal } from './agent-detail-tabs' const log = createClientLogger('AgentSquadPhase3') interface Agent { id: number name: string role: string session_key?: string soul_content?: string working_memory?: string status: 'offline' | 'idle' | 'busy' | 'error' last_seen?: number last_activity?: string created_at: number updated_at: number config?: any taskStats?: { total: number assigned: number in_progress: number completed: number } } interface WorkItem { type: string count: number items: any[] } interface HeartbeatResponse { status: 'HEARTBEAT_OK' | 'WORK_ITEMS_FOUND' agent: string checked_at: number work_items?: WorkItem[] total_items?: number message?: string } interface SoulTemplate { name: string description: string size: number } const statusColors: Record = { offline: 'bg-gray-500', idle: 'bg-green-500', busy: 'bg-yellow-500', error: 'bg-red-500', } const statusIcons: Record = { offline: '-', idle: 'o', busy: '~', error: '!', } export function AgentSquadPanelPhase3() { const [agents, setAgents] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [selectedAgent, setSelectedAgent] = useState(null) const [showCreateModal, setShowCreateModal] = useState(false) const [showQuickSpawnModal, setShowQuickSpawnModal] = useState(false) const [autoRefresh, setAutoRefresh] = useState(true) const [syncing, setSyncing] = useState(false) const [syncToast, setSyncToast] = useState(null) // Sync agents from gateway config const syncFromConfig = async () => { setSyncing(true) setSyncToast(null) try { const response = await fetch('/api/agents/sync', { method: 'POST' }) const data = await response.json() if (!response.ok) throw new Error(data.error || 'Sync failed') setSyncToast(`Synced ${data.synced} agents (${data.created} new, ${data.updated} updated)`) fetchAgents() setTimeout(() => setSyncToast(null), 5000) } catch (err: any) { setSyncToast(`Sync failed: ${err.message}`) setTimeout(() => setSyncToast(null), 5000) } finally { setSyncing(false) } } // Fetch agents const fetchAgents = useCallback(async () => { try { setError(null) if (agents.length === 0) setLoading(true) const response = await fetch('/api/agents') if (!response.ok) throw new Error('Failed to fetch agents') const data = await response.json() setAgents(data.agents || []) } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred') } finally { setLoading(false) } }, [agents.length]) // Smart polling with visibility pause useSmartPoll(fetchAgents, 30000, { enabled: autoRefresh, pauseWhenSseConnected: true }) // Update agent status const updateAgentStatus = async (agentName: string, status: Agent['status'], activity?: string) => { try { const response = await fetch('/api/agents', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: agentName, status, last_activity: activity || `Status changed to ${status}` }) }) if (!response.ok) throw new Error('Failed to update agent status') // Update local state setAgents(prev => prev.map(agent => agent.name === agentName ? { ...agent, status, last_activity: activity || `Status changed to ${status}`, last_seen: Math.floor(Date.now() / 1000), updated_at: Math.floor(Date.now() / 1000) } : agent )) } catch (error) { log.error('Failed to update agent status:', error) setError('Failed to update agent status') } } // Wake agent via session_send const wakeAgent = async (agentName: string, sessionKey: string) => { try { const response = await fetch(`/api/agents/${agentName}/wake`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: `🤖 **Wake Up Call**\n\nAgent ${agentName}, you have been manually woken up.\nCheck Mission Control for any pending tasks or notifications.\n\n⏰ ${new Date().toLocaleString()}` }) }) if (!response.ok) { const data = await response.json().catch(() => ({})) throw new Error(data.error || 'Failed to wake agent') } await updateAgentStatus(agentName, 'idle', 'Manually woken via session') } catch (error) { log.error('Failed to wake agent:', error) setError('Failed to wake agent') } } // Format last seen time const formatLastSeen = (timestamp?: number) => { if (!timestamp) return 'Never' const now = Date.now() const diffMs = now - (timestamp * 1000) const diffMinutes = Math.floor(diffMs / (1000 * 60)) const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)) if (diffMinutes < 1) return 'Just now' if (diffMinutes < 60) return `${diffMinutes}m ago` if (diffHours < 24) return `${diffHours}h ago` if (diffDays < 7) return `${diffDays}d ago` return new Date(timestamp * 1000).toLocaleDateString() } // Check if agent had recent heartbeat (within 30 minutes) const hasRecentHeartbeat = (agent: Agent) => { if (!agent.last_seen) return false const thirtyMinutesAgo = Math.floor(Date.now() / 1000) - (30 * 60) return agent.last_seen > thirtyMinutesAgo } // Get status distribution for summary const statusCounts = agents.reduce((acc, agent) => { acc[agent.status] = (acc[agent.status] || 0) + 1 return acc }, {} as Record) if (loading && agents.length === 0) { return (
Loading agents...
) } return (
{/* Header */}

Agent Squad

{/* Status Summary */}
{Object.entries(statusCounts).map(([status, count]) => (
{count}
))}
{/* Active Heartbeats Indicator */}
{agents.filter(hasRecentHeartbeat).length} active heartbeats
{/* Sync Toast */} {syncToast && (
{syncToast}
)} {/* Error Display */} {error && (
{error}
)} {/* Agent Grid */}
{agents.length === 0 ? (

No agents found

Add your first agent to get started

) : (
{agents.map(agent => (
setSelectedAgent(agent)} > {/* Agent Header */}

{agent.name}

{agent.role}

{/* Heartbeat indicator */} {hasRecentHeartbeat(agent) && (
)}
{agent.status}
{/* Session Info */}
Session: {agent.session_key || 'Not set'} {agent.session_key && (
Active
)}
{/* Task Stats */} {agent.taskStats && (
{agent.taskStats.total}
Total Tasks
{agent.taskStats.in_progress}
In Progress
)} {/* Last Activity */}
Last seen: {formatLastSeen(agent.last_seen)}
{agent.last_activity && (
Activity: {agent.last_activity}
)}
{/* Quick Actions */}
{agent.session_key ? ( ) : ( )}
))}
)}
{/* Agent Detail Modal */} {selectedAgent && ( setSelectedAgent(null)} onUpdate={fetchAgents} onStatusUpdate={updateAgentStatus} onWakeAgent={wakeAgent} /> )} {/* Create Agent Modal */} {showCreateModal && ( setShowCreateModal(false)} onCreated={fetchAgents} /> )} {/* Quick Spawn Modal */} {showQuickSpawnModal && selectedAgent && ( { setShowQuickSpawnModal(false) setSelectedAgent(null) }} onSpawned={fetchAgents} /> )}
) } // Enhanced Agent Detail Modal with Tabs function AgentDetailModalPhase3({ agent, onClose, onUpdate, onStatusUpdate, onWakeAgent }: { agent: Agent onClose: () => void onUpdate: () => void onStatusUpdate: (name: string, status: Agent['status'], activity?: string) => Promise onWakeAgent: (name: string, sessionKey: string) => Promise }) { const [activeTab, setActiveTab] = useState<'overview' | 'soul' | 'memory' | 'config' | 'tasks' | 'activity'>('overview') const [editing, setEditing] = useState(false) const [formData, setFormData] = useState({ role: agent.role, session_key: agent.session_key || '', soul_content: agent.soul_content || '', working_memory: agent.working_memory || '' }) const [soulTemplates, setSoulTemplates] = useState([]) const [heartbeatData, setHeartbeatData] = useState(null) const [loadingHeartbeat, setLoadingHeartbeat] = useState(false) // Load SOUL templates useEffect(() => { const loadTemplates = async () => { try { const response = await fetch(`/api/agents/${agent.name}/soul`, { method: 'PATCH' }) if (response.ok) { const data = await response.json() setSoulTemplates(data.templates || []) } } catch (error) { log.error('Failed to load SOUL templates:', error) } } if (activeTab === 'soul') { loadTemplates() } }, [activeTab, agent.name]) // Perform heartbeat check const performHeartbeat = async () => { setLoadingHeartbeat(true) try { const response = await fetch(`/api/agents/${agent.name}/heartbeat`) if (response.ok) { const data = await response.json() setHeartbeatData(data) } } catch (error) { log.error('Failed to perform heartbeat:', error) } finally { setLoadingHeartbeat(false) } } const handleSave = async () => { try { const response = await fetch('/api/agents', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: agent.name, ...formData }) }) if (!response.ok) throw new Error('Failed to update agent') setEditing(false) onUpdate() } catch (error) { log.error('Failed to update agent:', error) } } const handleSoulSave = async (content: string, templateName?: string) => { try { const response = await fetch(`/api/agents/${agent.name}/soul`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ soul_content: content, template_name: templateName }) }) if (!response.ok) throw new Error('Failed to update SOUL') setFormData(prev => ({ ...prev, soul_content: content })) onUpdate() } catch (error) { log.error('Failed to update SOUL:', error) } } const handleMemorySave = async (content: string, append: boolean = false) => { try { const response = await fetch(`/api/agents/${agent.name}/memory`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ working_memory: content, append }) }) if (!response.ok) throw new Error('Failed to update memory') const data = await response.json() setFormData(prev => ({ ...prev, working_memory: data.working_memory })) onUpdate() } catch (error) { log.error('Failed to update memory:', error) } } const tabs = [ { id: 'overview', label: 'Overview', icon: '#' }, { id: 'soul', label: 'SOUL', icon: '~' }, { id: 'memory', label: 'Memory', icon: '@' }, { id: 'tasks', label: 'Tasks', icon: '+' }, { id: 'config', label: 'Config', icon: '*' }, { id: 'activity', label: 'Activity', icon: '>' } ] return (
{/* Modal Header */}

{agent.name}

{agent.role}

{agent.status}
{/* Tab Navigation */}
{tabs.map(tab => ( ))}
{/* Tab Content */}
{activeTab === 'overview' && ( setEditing(true)} onCancel={() => setEditing(false)} heartbeatData={heartbeatData} loadingHeartbeat={loadingHeartbeat} onPerformHeartbeat={performHeartbeat} /> )} {activeTab === 'soul' && ( )} {activeTab === 'memory' && ( )} {activeTab === 'tasks' && ( )} {activeTab === 'config' && ( )} {activeTab === 'activity' && ( )}
) } // Quick Spawn Modal Component function QuickSpawnModal({ agent, onClose, onSpawned }: { agent: Agent onClose: () => void onSpawned: () => void }) { const [spawnData, setSpawnData] = useState({ task: '', model: 'sonnet', label: `${agent.name}-subtask-${Date.now()}`, timeoutSeconds: 300 }) const [isSpawning, setIsSpawning] = useState(false) const [spawnResult, setSpawnResult] = useState(null) const models = [ { id: 'haiku', name: 'Claude Haiku', cost: '$0.25/1K', speed: 'Ultra Fast' }, { id: 'sonnet', name: 'Claude Sonnet', cost: '$3.00/1K', speed: 'Fast' }, { id: 'opus', name: 'Claude Opus', cost: '$15.00/1K', speed: 'Slow' }, { id: 'groq-fast', name: 'Groq Llama 8B', cost: '$0.05/1K', speed: '840 tok/s' }, { id: 'groq', name: 'Groq Llama 70B', cost: '$0.59/1K', speed: '150 tok/s' }, { id: 'deepseek', name: 'DeepSeek R1', cost: 'FREE', speed: 'Local' }, ] const handleSpawn = async () => { if (!spawnData.task.trim()) { alert('Please enter a task description') return } setIsSpawning(true) try { const response = await fetch('/api/spawn', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...spawnData, parentAgent: agent.name, sessionKey: agent.session_key }) }) const result = await response.json() if (response.ok) { setSpawnResult(result) onSpawned() // Auto-close after 2 seconds if successful setTimeout(() => { onClose() }, 2000) } else { alert(result.error || 'Failed to spawn agent') } } catch (error) { log.error('Spawn failed:', error) alert('Network error occurred') } finally { setIsSpawning(false) } } return (

Quick Spawn for {agent.name}

{spawnResult ? (
Agent spawned successfully!

Agent ID: {spawnResult.agentId}

Session: {spawnResult.sessionId}

Model: {spawnResult.model}

) : (
{/* Task Description */}