diff --git a/desktop/src/components/DevQALoop.tsx b/desktop/src/components/DevQALoop.tsx index 509bc8c..2930957 100644 --- a/desktop/src/components/DevQALoop.tsx +++ b/desktop/src/components/DevQALoop.tsx @@ -7,11 +7,11 @@ * @module components/DevQALoop */ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useTeamStore } from '../store/teamStore'; import type { DevQALoop as DevQALoopType, ReviewFeedback, ReviewIssue } from '../types/team'; import { - RefreshCw, CheckCircle, XCircle, AlertTriangle, ArrowRight, + RefreshCw, CheckCircle, XCircle, AlertTriangle, Clock, MessageSquare, FileCode, Bug, Lightbulb, ChevronDown, ChevronUp, Send, ThumbsUp, ThumbsDown, AlertOctagon, } from 'lucide-react'; @@ -126,7 +126,7 @@ interface ReviewFormProps { onCancel: () => void; } -function ReviewForm({ loopId, teamId, onSubmit, onCancel }: ReviewFormProps) { +function ReviewForm({ loopId: _loopId, teamId: _teamId, onSubmit, onCancel }: ReviewFormProps) { const [verdict, setVerdict] = useState('needs_work'); const [comment, setComment] = useState(''); const [issues, setIssues] = useState([]); @@ -316,10 +316,6 @@ export function DevQALoopPanel({ loop, teamId, developerName, reviewerName, task setShowReviewForm(false); }; - const handleStartRevising = async () => { - await updateLoopState(teamId, loop.id, 'revising'); - }; - const handleCompleteRevision = async () => { await updateLoopState(teamId, loop.id, 'reviewing'); }; diff --git a/desktop/src/components/TeamCollaborationView.tsx b/desktop/src/components/TeamCollaborationView.tsx index 8873015..31b6032 100644 --- a/desktop/src/components/TeamCollaborationView.tsx +++ b/desktop/src/components/TeamCollaborationView.tsx @@ -11,7 +11,7 @@ import { useState, useEffect, useRef } from 'react'; import { useTeamStore } from '../store/teamStore'; import type { Team, TeamMember, TeamTask, CollaborationEvent } from '../types/team'; import { - Activity, Users, CheckCircle, Clock, AlertTriangle, Play, Pause, + Activity, Users, CheckCircle, AlertTriangle, Play, ArrowRight, GitBranch, MessageSquare, FileCode, Bot, Zap, TrendingUp, TrendingDown, Minus, Circle, } from 'lucide-react'; @@ -56,7 +56,9 @@ function EventFeedItem({ event, team }: EventFeedItemProps) {

- {event.payload.description || JSON.stringify(event.payload).slice(0, 100)} + {typeof event.payload.description === 'string' + ? event.payload.description + : JSON.stringify(event.payload).slice(0, 100)}

diff --git a/desktop/src/components/TeamOrchestrator.tsx b/desktop/src/components/TeamOrchestrator.tsx index 398ca33..c1e79ef 100644 --- a/desktop/src/components/TeamOrchestrator.tsx +++ b/desktop/src/components/TeamOrchestrator.tsx @@ -7,11 +7,10 @@ * @module components/TeamOrchestrator */ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect } from 'react'; import { useTeamStore } from '../store/teamStore'; import { useGatewayStore } from '../store/gatewayStore'; import type { - Team, TeamMember, TeamTask, TeamMemberRole, @@ -19,9 +18,9 @@ import type { CollaborationPattern, } from '../types/team'; import { - Users, Plus, Trash2, Edit2, Check, X, ChevronDown, ChevronUp, - Bot, GitBranch, ArrowRight, Clock, AlertTriangle, CheckCircle, - Play, Pause, Settings, UserPlus, FileText, Activity, + Users, Plus, Trash2, X, + Bot, Clock, AlertTriangle, CheckCircle, + Play, UserPlus, FileText, } from 'lucide-react'; // === Sub-Components === @@ -115,7 +114,7 @@ interface TaskCardProps { onStatusChange: (status: TeamTask['status']) => void; } -function TaskCard({ task, members, isSelected, onSelect, onAssign, onStatusChange }: TaskCardProps) { +function TaskCard({ task, members, isSelected, onSelect, onAssign, onStatusChange: _onStatusChange }: TaskCardProps) { const [showAssignMenu, setShowAssignMenu] = useState(false); const priorityColors: Record = { @@ -216,7 +215,6 @@ export function TeamOrchestrator({ isOpen, onClose }: TeamOrchestratorProps) { teams, activeTeam, metrics, - isLoading, error, selectedTaskId, selectedMemberId, diff --git a/desktop/src/lib/gateway-client.ts b/desktop/src/lib/gateway-client.ts index 1636f63..cd6388c 100644 --- a/desktop/src/lib/gateway-client.ts +++ b/desktop/src/lib/gateway-client.ts @@ -967,12 +967,42 @@ export class GatewayClient { // === OpenFang Hands API === /** List available Hands */ - async listHands(): Promise<{ hands: { id: string; name: string; description: string; status: string; requirements_met?: boolean; category?: string }[] }> { + async listHands(): Promise<{ + hands: { + id?: string; + name: string; + description?: string; + status?: string; + requirements_met?: boolean; + category?: string; + icon?: string; + tool_count?: number; + tools?: string[]; + metric_count?: number; + metrics?: string[]; + }[] + }> { return this.restGet('/api/hands'); } /** Get Hand details */ - async getHand(name: string): Promise<{ name: string; description: string; config: Record }> { + async getHand(name: string): Promise<{ + id?: string; + name?: string; + description?: string; + status?: string; + requirements_met?: boolean; + category?: string; + icon?: string; + provider?: string; + model?: string; + requirements?: { description?: string; name?: string; met?: boolean; satisfied?: boolean; details?: string; hint?: string }[]; + tools?: string[]; + metrics?: string[]; + config?: Record; + tool_count?: number; + metric_count?: number; + }> { return this.restGet(`/api/hands/${name}`); } @@ -1097,7 +1127,7 @@ export class GatewayClient { created_at: string; updated_at?: string; message_count?: number; - status?: string; + status?: 'active' | 'archived' | 'expired'; }>; }> { const params = new URLSearchParams(); @@ -1113,7 +1143,7 @@ export class GatewayClient { created_at: string; updated_at?: string; message_count?: number; - status?: string; + status?: 'active' | 'archived' | 'expired'; metadata?: Record; }> { return this.restGet(`/api/sessions/${sessionId}`); diff --git a/desktop/src/lib/team-client.ts b/desktop/src/lib/team-client.ts index d58a747..025040b 100644 --- a/desktop/src/lib/team-client.ts +++ b/desktop/src/lib/team-client.ts @@ -23,6 +23,9 @@ import type { TeamMetrics, } from '../types/team'; +// Re-export types for consumers +export type { CollaborationEvent } from '../types/team'; + // === Configuration === const API_BASE = '/api'; // Uses Vite proxy diff --git a/desktop/src/lib/useTeamEvents.ts b/desktop/src/lib/useTeamEvents.ts index 10e880b..0fea232 100644 --- a/desktop/src/lib/useTeamEvents.ts +++ b/desktop/src/lib/useTeamEvents.ts @@ -10,7 +10,7 @@ import { useEffect, useRef, useCallback } from 'react'; import { useTeamStore } from '../store/teamStore'; import { useGatewayStore } from '../store/gatewayStore'; -import type { TeamEventMessage, TeamEventType } from '../lib/team-client'; +import type { TeamEventMessage, TeamEventType, CollaborationEvent } from '../lib/team-client'; interface UseTeamEventsOptions { /** Subscribe to specific team only, or null for all teams */ @@ -25,12 +25,11 @@ interface UseTeamEventsOptions { * Hook for subscribing to real-time team collaboration events */ export function useTeamEvents(options: UseTeamEventsOptions = {}) { - const { teamId = null, eventTypes, maxEvents = 100 } = options; + const { teamId = null, eventTypes } = options; const unsubscribeRef = useRef<(() => void) | null>(null); const { addEvent, - setActiveTeam, updateTaskStatus, updateLoopState, loadTeams, diff --git a/desktop/src/store/gatewayStore.ts b/desktop/src/store/gatewayStore.ts index fb22451..66df0fc 100644 --- a/desktop/src/store/gatewayStore.ts +++ b/desktop/src/store/gatewayStore.ts @@ -1039,17 +1039,23 @@ export const useGatewayStore = create((set, get) => { try { const result = await get().client.listHands(); // Map API response to Hand interface - const hands: Hand[] = (result?.hands || []).map(h => ({ - id: h.id || h.name, - name: h.name, - description: h.description || '', - status: h.status || (h.requirements_met ? 'idle' : 'setup_needed'), - requirements_met: h.requirements_met, - category: h.category, - icon: h.icon, - toolCount: h.tool_count || h.tools?.length, - metricCount: h.metric_count || h.metrics?.length, - })); + const validStatuses = ['idle', 'running', 'needs_approval', 'error', 'unavailable', 'setup_needed'] as const; + const hands: Hand[] = (result?.hands || []).map(h => { + const status = validStatuses.includes(h.status as any) + ? h.status as Hand['status'] + : (h.requirements_met ? 'idle' : 'setup_needed'); + return { + id: h.id || h.name, + name: h.name, + description: h.description || '', + status, + requirements_met: h.requirements_met, + category: h.category, + icon: h.icon, + toolCount: h.tool_count || h.tools?.length, + metricCount: h.metric_count || h.metrics?.length, + }; + }); set({ hands, isLoading: false }); } catch { set({ isLoading: false }); @@ -1062,24 +1068,39 @@ export const useGatewayStore = create((set, get) => { const result = await get().client.getHand(name); if (!result) return undefined; + // Helper to extract string from unknown config + const getStringFromConfig = (key: string): string | undefined => { + const val = result.config?.[key]; + return typeof val === 'string' ? val : undefined; + }; + const getArrayFromConfig = (key: string): string[] | undefined => { + const val = result.config?.[key]; + return Array.isArray(val) ? val : undefined; + }; + + const validStatuses = ['idle', 'running', 'needs_approval', 'error', 'unavailable', 'setup_needed'] as const; + const status = validStatuses.includes(result.status as any) + ? result.status as Hand['status'] + : (result.requirements_met ? 'idle' : 'setup_needed'); + // Map API response to extended Hand interface const hand: Hand = { id: result.id || result.name || name, name: result.name || name, description: result.description || '', - status: result.status || (result.requirements_met ? 'idle' : 'setup_needed'), + status, requirements_met: result.requirements_met, category: result.category, icon: result.icon, - provider: result.provider || result.config?.provider, - model: result.model || result.config?.model, + provider: result.provider || getStringFromConfig('provider'), + model: result.model || getStringFromConfig('model'), requirements: result.requirements?.map((r: any) => ({ description: r.description || r.name || String(r), met: r.met ?? r.satisfied ?? true, details: r.details || r.hint, })), - tools: result.tools || result.config?.tools, - metrics: result.metrics || result.config?.metrics, + tools: result.tools || getArrayFromConfig('tools'), + metrics: result.metrics || getArrayFromConfig('metrics'), toolCount: result.tool_count || result.tools?.length || 0, metricCount: result.metric_count || result.metrics?.length || 0, }; @@ -1119,7 +1140,7 @@ export const useGatewayStore = create((set, get) => { triggerHand: async (name: string, params?: Record) => { try { const result = await get().client.triggerHand(name, params); - return result ? { runId: result.runId, status: result.status } : undefined; + return result ? { runId: result.runId, status: result.status, startedAt: new Date().toISOString() } : undefined; } catch (err: any) { set({ error: err.message }); return undefined; @@ -1446,7 +1467,7 @@ export const useGatewayStore = create((set, get) => { agentId: result.agent_id, createdAt: result.created_at, status: 'active', - metadata: result.metadata, + metadata: metadata, }; set(state => ({ sessions: [...state.sessions, session] })); return session; diff --git a/desktop/src/store/teamStore.ts b/desktop/src/store/teamStore.ts index 86f4c55..4bea348 100644 --- a/desktop/src/store/teamStore.ts +++ b/desktop/src/store/teamStore.ts @@ -16,7 +16,6 @@ import type { TeamMemberRole, DevQALoop, DevQALoopState, - CollaborationPattern, CreateTeamRequest, AddTeamTaskRequest, TeamMetrics, @@ -24,7 +23,6 @@ import type { ReviewFeedback, TaskDeliverable, } from '../types/team'; -import { getGatewayClient } from '../lib/gateway-client'; // === Store State === @@ -136,7 +134,6 @@ export const useTeamStore = create((set, get) => ({ loadTeams: async () => { set({ isLoading: true, error: null }); try { - const client = getGatewayClient(); // For now, load from localStorage until API is available const stored = localStorage.getItem('zclaw-teams'); const teams: Team[] = stored ? JSON.parse(stored) : []; @@ -150,7 +147,7 @@ export const useTeamStore = create((set, get) => ({ set({ isLoading: true, error: null }); try { const now = new Date().toISOString(); - const members: TeamMember[] = request.memberAgents.map((m, index) => ({ + const members: TeamMember[] = request.memberAgents.map((m) => ({ id: generateId(), agentId: m.agentId, name: `Agent-${m.agentId.slice(0, 4)}`, @@ -208,7 +205,7 @@ export const useTeamStore = create((set, get) => ({ }, setActiveTeam: (team: Team | null) => { - set(state => ({ + set(() => ({ activeTeam: team, metrics: team ? calculateMetrics(team) : null, })); diff --git a/desktop/src/types/team.ts b/desktop/src/types/team.ts index da4aacc..bccc69c 100644 --- a/desktop/src/types/team.ts +++ b/desktop/src/types/team.ts @@ -7,7 +7,7 @@ * @module types/team */ -import type { Agent, AgentStatus } from './agent'; +import type { AgentStatus } from './agent'; // === Team Definition ===