// useAgentStatus Composable // Manages agent lifecycle operations and status tracking import { ref, computed, onMounted, onUnmounted, type Ref } from 'vue' import { useWebSocket } from './useWebSocket' import type { Agent, AgentStatus, WebSocketEvent } from '@/types' interface AgentStatusComposable { // State isStarting: Ref isStopping: Ref isRestarting: Ref isLoading: Ref error: Ref // Computed canStart: Ref canStop: Ref canRestart: Ref // Actions startAgent: () => Promise stopAgent: () => Promise restartAgent: () => Promise refreshStatus: () => Promise clearError: () => void } export function useAgentStatus(agent: Agent): AgentStatusComposable { // State const isStarting = ref(false) const isStopping = ref(false) const isRestarting = ref(false) const error = ref(null) // WebSocket for real-time updates const { connected, on, off } = useWebSocket() // Computed const isLoading = computed(() => isStarting.value || isStopping.value || isRestarting.value ) const canStart = computed(() => agent.status === 'inactive' && !isLoading.value ) const canStop = computed(() => ['active', 'processing', 'idle'].includes(agent.status) && !isLoading.value ) const canRestart = computed(() => agent.status === 'error' && !isLoading.value ) // API client async function apiCall(method: string, endpoint: string, data?: any) { const response = await fetch(`/api/v1${endpoint}`, { method, headers: { 'Content-Type': 'application/json', }, body: data ? JSON.stringify(data) : undefined, }) if (!response.ok) { const errorData = await response.json().catch(() => ({})) throw new Error(errorData.message || `HTTP ${response.status}`) } return response.json() } // Actions async function startAgent(): Promise { if (!canStart.value) return isStarting.value = true error.value = null try { await apiCall('POST', `/agents/${agent.id}/start`) // Status will be updated via WebSocket } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to start agent' throw err } finally { isStarting.value = false } } async function stopAgent(): Promise { if (!canStop.value) return isStopping.value = true error.value = null try { await apiCall('POST', `/agents/${agent.id}/stop`) // Status will be updated via WebSocket } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to stop agent' throw err } finally { isStopping.value = false } } async function restartAgent(): Promise { if (!canRestart.value) return isRestarting.value = true error.value = null try { await apiCall('POST', `/agents/${agent.id}/restart`) // Status will be updated via WebSocket } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to restart agent' throw err } finally { isRestarting.value = false } } async function refreshStatus(): Promise { try { const response = await apiCall('GET', `/agents/${agent.id}`) // Update agent status through parent component or store // This assumes the parent handles the agent object updates Object.assign(agent, response.data) } catch (err) { error.value = err instanceof Error ? err.message : 'Failed to refresh status' throw err } } function clearError(): void { error.value = null } // WebSocket event handlers function handleAgentStatusChanged(event: WebSocketEvent) { if (event.data.agentId === agent.id) { // Update agent status Object.assign(agent, event.data) // Clear loading states if status changed if (event.data.status !== agent.status) { isStarting.value = false isStopping.value = false isRestarting.value = false } } } // Lifecycle onMounted(() => { // Subscribe to WebSocket events on('agent_status_changed', handleAgentStatusChanged) // Initial status refresh if needed if (connected.value) { refreshStatus().catch(() => { // Silently handle initial refresh errors }) } }) onUnmounted(() => { // Unsubscribe from WebSocket events off('agent_status_changed', handleAgentStatusChanged) }) return { // State isStarting, isStopping, isRestarting, isLoading, error, // Computed canStart, canStop, canRestart, // Actions startAgent, stopAgent, restartAgent, refreshStatus, clearError } } // Agent Status Store (optional Pinia store) export interface AgentStatusState { agents: Record loading: boolean error: string | null } // Helper functions export function getAgentStatusColor(status: AgentStatus): string { const colorMap = { active: 'var(--saap-success)', inactive: 'var(--saap-neutral-400)', processing: 'var(--saap-warning)', error: 'var(--saap-error)', idle: 'var(--saap-info)' } return colorMap[status] || colorMap.inactive } export function getAgentTypeIcon(type: string): string { const iconMap = { generalist: 'brain', specialist: 'target', coordinator: 'network' } return iconMap[type as keyof typeof iconMap] || 'help-circle' } export function formatAgentUptime(timestamp: string): string { const start = new Date(timestamp) const now = new Date() const diff = now.getTime() - start.getTime() const days = Math.floor(diff / (1000 * 60 * 60 * 24)) const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)) if (days > 0) { return `${days}d ${hours}h` } if (hours > 0) { return `${hours}h ${minutes}m` } return `${minutes}m` }