diff --git a/desktop/src/components/DevQALoop.tsx b/desktop/src/components/DevQALoop.tsx
new file mode 100644
index 0000000..509bc8c
--- /dev/null
+++ b/desktop/src/components/DevQALoop.tsx
@@ -0,0 +1,468 @@
+/**
+ * DevQALoop - Developer ↔ QA Review Loop Interface
+ *
+ * Visualizes the iterative review cycle between Developer and QA agents,
+ * showing iteration count, feedback history, and current state.
+ *
+ * @module components/DevQALoop
+ */
+
+import { useState, useEffect } from 'react';
+import { useTeamStore } from '../store/teamStore';
+import type { DevQALoop as DevQALoopType, ReviewFeedback, ReviewIssue } from '../types/team';
+import {
+ RefreshCw, CheckCircle, XCircle, AlertTriangle, ArrowRight,
+ Clock, MessageSquare, FileCode, Bug, Lightbulb, ChevronDown, ChevronUp,
+ Send, ThumbsUp, ThumbsDown, AlertOctagon,
+} from 'lucide-react';
+
+// === Sub-Components ===
+
+interface FeedbackItemProps {
+ feedback: ReviewFeedback;
+ iteration: number;
+ isExpanded: boolean;
+ onToggle: () => void;
+}
+
+function FeedbackItem({ feedback, iteration, isExpanded, onToggle }: FeedbackItemProps) {
+ const verdictColors = {
+ approved: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300',
+ needs_work: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
+ rejected: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
+ };
+
+ const verdictIcons = {
+ approved: ,
+ needs_work: ,
+ rejected: ,
+ };
+
+ const severityColors = {
+ critical: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
+ major: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300',
+ minor: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
+ suggestion: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
+ };
+
+ return (
+
+
+
+ {isExpanded && (
+
+ {/* Comments */}
+ {feedback.comments.length > 0 && (
+
+
Comments
+
+ {feedback.comments.map((comment, idx) => (
+ -
+
+ {comment}
+
+ ))}
+
+
+ )}
+
+ {/* Issues */}
+ {feedback.issues.length > 0 && (
+
+
Issues ({feedback.issues.length})
+
+ {feedback.issues.map((issue, idx) => (
+
+
+
+ {issue.severity}
+
+ {issue.file && (
+
+
+ {issue.file}{issue.line ? `:${issue.line}` : ''}
+
+ )}
+
+
{issue.description}
+ {issue.suggestion && (
+
+
+ {issue.suggestion}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ )}
+
+ );
+}
+
+interface ReviewFormProps {
+ loopId: string;
+ teamId: string;
+ onSubmit: (feedback: Omit) => void;
+ onCancel: () => void;
+}
+
+function ReviewForm({ loopId, teamId, onSubmit, onCancel }: ReviewFormProps) {
+ const [verdict, setVerdict] = useState('needs_work');
+ const [comment, setComment] = useState('');
+ const [issues, setIssues] = useState([]);
+ const [newIssue, setNewIssue] = useState>({});
+
+ const handleAddIssue = () => {
+ if (!newIssue.description) return;
+ setIssues([...issues, {
+ severity: newIssue.severity || 'minor',
+ description: newIssue.description,
+ file: newIssue.file,
+ line: newIssue.line,
+ suggestion: newIssue.suggestion,
+ } as ReviewIssue]);
+ setNewIssue({});
+ };
+
+ const handleSubmit = () => {
+ onSubmit({
+ verdict,
+ comments: comment ? [comment] : [],
+ issues,
+ });
+ };
+
+ return (
+
+
Submit Review
+
+ {/* Verdict */}
+
+
+
+ {(['approved', 'needs_work', 'rejected'] as const).map(v => (
+
+ ))}
+
+
+
+ {/* Comment */}
+
+
+
+
+ {/* Add Issue */}
+
+
+ {/* Issues List */}
+ {issues.length > 0 && (
+
+ {issues.map((issue, idx) => (
+
+
+ {issue.severity}
+
+ {issue.description}
+
+
+ ))}
+
+ )}
+
+ {/* Actions */}
+
+
+
+
+
+ );
+}
+
+// === Main Component ===
+
+interface DevQALoopPanelProps {
+ loop: DevQALoopType;
+ teamId: string;
+ developerName: string;
+ reviewerName: string;
+ taskTitle: string;
+}
+
+export function DevQALoopPanel({ loop, teamId, developerName, reviewerName, taskTitle }: DevQALoopPanelProps) {
+ const [expandedFeedback, setExpandedFeedback] = useState(null);
+ const [showReviewForm, setShowReviewForm] = useState(false);
+
+ const { submitReview, updateLoopState } = useTeamStore();
+
+ const stateConfig = {
+ idle: { color: 'gray', icon: , label: 'Idle' },
+ developing: { color: 'blue', icon: , label: 'Developing' },
+ reviewing: { color: 'yellow', icon: , label: 'Reviewing' },
+ revising: { color: 'orange', icon: , label: 'Revising' },
+ approved: { color: 'green', icon: , label: 'Approved' },
+ escalated: { color: 'red', icon: , label: 'Escalated' },
+ };
+
+ const config = stateConfig[loop.state];
+
+ const handleSubmitReview = async (feedback: Omit) => {
+ await submitReview(teamId, loop.id, feedback);
+ setShowReviewForm(false);
+ };
+
+ const handleStartRevising = async () => {
+ await updateLoopState(teamId, loop.id, 'revising');
+ };
+
+ const handleCompleteRevision = async () => {
+ await updateLoopState(teamId, loop.id, 'reviewing');
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
{config.icon}
+
+
Dev↔QA Loop
+
{taskTitle}
+
+
+
+
+ {config.label}
+
+
+ Iteration {loop.iterationCount + 1}/{loop.maxIterations}
+
+
+
+
+
+ {/* Flow Visualization */}
+
+
+
+
+
+
+
+
{developerName}
+
Developer
+
+
+
+
+
+
+
+
{reviewerName}
+
QA Reviewer
+
+
+
+
+
+
+
+
+ {/* Progress Bar */}
+
+
+ {Array.from({ length: loop.maxIterations }).map((_, i) => (
+
+ ))}
+
+
+
+ {/* Feedback History */}
+
+
Feedback History
+ {loop.feedbackHistory.length === 0 ? (
+
+ No feedback yet. First review pending.
+
+ ) : (
+ loop.feedbackHistory.map((feedback, idx) => (
+
setExpandedFeedback(expandedFeedback === idx ? null : idx)}
+ />
+ ))
+ )}
+
+
+ {/* Actions */}
+
+ {loop.state === 'escalated' ? (
+
+
+
+ Maximum iterations reached. Human intervention required.
+
+
+ ) : loop.state === 'approved' ? (
+
+
+
+ Task approved and completed!
+
+
+ ) : loop.state === 'reviewing' && !showReviewForm ? (
+
+ ) : loop.state === 'revising' ? (
+
+ ) : showReviewForm ? (
+
setShowReviewForm(false)}
+ />
+ ) : null}
+
+
+ );
+}
+
+export default DevQALoopPanel;
diff --git a/desktop/src/components/TeamCollaborationView.tsx b/desktop/src/components/TeamCollaborationView.tsx
new file mode 100644
index 0000000..8873015
--- /dev/null
+++ b/desktop/src/components/TeamCollaborationView.tsx
@@ -0,0 +1,399 @@
+/**
+ * TeamCollaborationView - Real-time Team Collaboration Status
+ *
+ * Displays live collaboration events, member status, task progress,
+ * and team metrics in a unified dashboard view.
+ *
+ * @module components/TeamCollaborationView
+ */
+
+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,
+ ArrowRight, GitBranch, MessageSquare, FileCode, Bot, Zap,
+ TrendingUp, TrendingDown, Minus, Circle,
+} from 'lucide-react';
+
+// === Sub-Components ===
+
+interface EventFeedItemProps {
+ event: CollaborationEvent;
+ team: Team;
+}
+
+function EventFeedItem({ event, team }: EventFeedItemProps) {
+ const sourceMember = team.members.find(m => m.agentId === event.sourceAgentId);
+
+ const eventIcons: Record = {
+ task_assigned: ,
+ task_started: ,
+ task_completed: ,
+ review_requested: ,
+ review_submitted: ,
+ loop_state_change: ,
+ member_status_change: ,
+ };
+
+ const formatTime = (timestamp: string) => {
+ const diff = Date.now() - new Date(timestamp).getTime();
+ if (diff < 60000) return 'Just now';
+ if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
+ return new Date(timestamp).toLocaleTimeString();
+ };
+
+ return (
+
+
{eventIcons[event.type]}
+
+
+
+ {sourceMember?.name || 'System'}
+
+
+ {event.type.replace(/_/g, ' ')}
+
+
+
+ {event.payload.description || JSON.stringify(event.payload).slice(0, 100)}
+
+
+
+ {formatTime(event.timestamp)}
+
+
+ );
+}
+
+function RefreshCw({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+interface MemberStatusBadgeProps {
+ member: TeamMember;
+}
+
+function MemberStatusBadge({ member }: MemberStatusBadgeProps) {
+ const statusConfig = {
+ idle: { color: 'bg-gray-400', label: 'Idle' },
+ running: { color: 'bg-green-500 animate-pulse', label: 'Active' },
+ paused: { color: 'bg-yellow-500', label: 'Paused' },
+ error: { color: 'bg-red-500', label: 'Error' },
+ };
+
+ const config = statusConfig[member.status];
+
+ return (
+
+
+
+ {member.currentTasks.length > 0 && (
+
+ {member.currentTasks.length}
+
+ )}
+
+
+
+
+ {member.name}
+
+ {config.label}
+
+
+
+
+ );
+}
+
+interface TaskProgressCardProps {
+ task: TeamTask;
+ assignee?: TeamMember;
+}
+
+function TaskProgressCard({ task, assignee }: TaskProgressCardProps) {
+ const statusConfig: Record = {
+ pending: { color: 'text-gray-400', icon: },
+ assigned: { color: 'text-blue-400', icon: },
+ in_progress: { color: 'text-green-500', icon: },
+ review: { color: 'text-yellow-500', icon: },
+ blocked: { color: 'text-red-500', icon: },
+ completed: { color: 'text-green-600', icon: },
+ failed: { color: 'text-red-600', icon: },
+ };
+
+ const config = statusConfig[task.status];
+
+ return (
+
+
+
{config.icon}
+
+
+
+ {task.title}
+
+
+ {task.priority}
+
+
+
+ {task.type}
+ {assignee && (
+ <>
+ ·
+ {assignee.name}
+ >
+ )}
+
+
+
+
+ );
+}
+
+interface MetricCardProps {
+ label: string;
+ value: number | string;
+ trend?: 'up' | 'down' | 'neutral';
+ format?: 'number' | 'percent' | 'time';
+}
+
+function MetricCard({ label, value, trend, format = 'number' }: MetricCardProps) {
+ const formattedValue = format === 'percent' ? `${value}%` :
+ format === 'time' ? `${Math.floor((value as number) / 60000)}m` :
+ value;
+
+ return (
+
+
+ {label}
+ {trend && (
+ trend === 'up' ? :
+ trend === 'down' ? :
+
+ )}
+
+
+ {formattedValue}
+
+
+ );
+}
+
+// === Main Component ===
+
+interface TeamCollaborationViewProps {
+ teamId: string;
+ compact?: boolean;
+}
+
+export function TeamCollaborationView({ teamId, compact = false }: TeamCollaborationViewProps) {
+ const { teams, recentEvents, metrics, activeTeam } = useTeamStore();
+ const [autoScroll, setAutoScroll] = useState(true);
+ const eventFeedRef = useRef(null);
+
+ const team = teams.find(t => t.id === teamId) || activeTeam;
+
+ useEffect(() => {
+ if (autoScroll && eventFeedRef.current) {
+ eventFeedRef.current.scrollTop = 0;
+ }
+ }, [recentEvents, autoScroll]);
+
+ if (!team) {
+ return (
+
+ );
+ }
+
+ const tasksByStatus = {
+ active: team.tasks.filter(t => ['in_progress', 'review'].includes(t.status)),
+ pending: team.tasks.filter(t => ['pending', 'assigned'].includes(t.status)),
+ completed: team.tasks.filter(t => t.status === 'completed'),
+ blocked: team.tasks.filter(t => ['blocked', 'failed'].includes(t.status)),
+ };
+
+ if (compact) {
+ return (
+
+ {/* Quick Stats */}
+
+
+
{tasksByStatus.active.length}
+
Active
+
+
+
{tasksByStatus.pending.length}
+
Pending
+
+
+
{tasksByStatus.completed.length}
+
Done
+
+
+
{tasksByStatus.blocked.length}
+
Blocked
+
+
+
+ {/* Member Status */}
+
+ {team.members.slice(0, 4).map(member => (
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
{team.name}
+
+ {team.status}
+
+
+
+
+ {team.pattern}
+
+
+
+
+ {/* Metrics */}
+ {metrics && (
+
+
+
+ 80 ? 'up' : 'down'} />
+
+
+ 70 ? 'up' : 'down'} />
+
+
+ )}
+
+ {/* Main Content */}
+
+ {/* Left: Members & Tasks */}
+
+ {/* Members */}
+
+
+
+ Team Members ({team.members.length})
+
+
+ {team.members.map(member => (
+
+ ))}
+
+
+
+ {/* Tasks */}
+
+
+
+ Active Tasks ({tasksByStatus.active.length})
+
+
+ {tasksByStatus.active.map(task => (
+
m.id === task.assigneeId)}
+ />
+ ))}
+ {tasksByStatus.active.length === 0 && (
+
+ No active tasks
+
+ )}
+
+
+ {tasksByStatus.blocked.length > 0 && (
+ <>
+
+
+ Blocked ({tasksByStatus.blocked.length})
+
+
+ {tasksByStatus.blocked.map(task => (
+ m.id === task.assigneeId)}
+ />
+ ))}
+
+ >
+ )}
+
+
+
+ {/* Right: Event Feed */}
+
+
+
+
+ Live Events
+
+
+
+
+ {recentEvents.filter(e => e.teamId === teamId).length === 0 ? (
+
+ ) : (
+ recentEvents
+ .filter(e => e.teamId === teamId)
+ .map((event, idx) => (
+
+ ))
+ )}
+
+
+
+
+ );
+}
+
+export default TeamCollaborationView;
diff --git a/desktop/src/components/TeamOrchestrator.tsx b/desktop/src/components/TeamOrchestrator.tsx
new file mode 100644
index 0000000..398ca33
--- /dev/null
+++ b/desktop/src/components/TeamOrchestrator.tsx
@@ -0,0 +1,508 @@
+/**
+ * TeamOrchestrator - Multi-Agent Team Orchestration UI
+ *
+ * Provides an interface for creating teams, assigning agents,
+ * managing tasks, and monitoring collaboration workflows.
+ *
+ * @module components/TeamOrchestrator
+ */
+
+import { useState, useEffect, useCallback } from 'react';
+import { useTeamStore } from '../store/teamStore';
+import { useGatewayStore } from '../store/gatewayStore';
+import type {
+ Team,
+ TeamMember,
+ TeamTask,
+ TeamMemberRole,
+ TaskPriority,
+ 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,
+} from 'lucide-react';
+
+// === Sub-Components ===
+
+interface MemberCardProps {
+ member: TeamMember;
+ isSelected: boolean;
+ onSelect: () => void;
+ onRoleChange: (role: TeamMemberRole) => void;
+ onRemove: () => void;
+}
+
+function MemberCard({ member, isSelected, onSelect, onRoleChange, onRemove }: MemberCardProps) {
+ const [showRoleMenu, setShowRoleMenu] = useState(false);
+
+ const roleColors: Record = {
+ orchestrator: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300',
+ developer: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
+ reviewer: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300',
+ tester: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
+ architect: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300',
+ specialist: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300',
+ };
+
+ const statusColors = {
+ idle: 'bg-gray-400',
+ running: 'bg-green-500 animate-pulse',
+ paused: 'bg-yellow-500',
+ error: 'bg-red-500',
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ {showRoleMenu && (
+
+ {(['orchestrator', 'developer', 'reviewer', 'tester', 'architect', 'specialist'] as TeamMemberRole[]).map(role => (
+
+ ))}
+
+ )}
+
+
+ Workload: {member.workload}%
+ Tasks: {member.currentTasks.length}
+
+
+ );
+}
+
+interface TaskCardProps {
+ task: TeamTask;
+ members: TeamMember[];
+ isSelected: boolean;
+ onSelect: () => void;
+ onAssign: (memberId: string) => void;
+ onStatusChange: (status: TeamTask['status']) => void;
+}
+
+function TaskCard({ task, members, isSelected, onSelect, onAssign, onStatusChange }: TaskCardProps) {
+ const [showAssignMenu, setShowAssignMenu] = useState(false);
+
+ const priorityColors: Record = {
+ critical: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300',
+ high: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300',
+ medium: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300',
+ low: 'bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-300',
+ };
+
+ const statusIcons: Record = {
+ pending: ,
+ assigned: ,
+ in_progress: ,
+ review: ,
+ blocked: ,
+ completed: ,
+ failed: ,
+ };
+
+ const assignee = members.find(m => m.id === task.assigneeId);
+
+ return (
+
+
+
+
+ {statusIcons[task.status]}
+ {task.title}
+
+ {task.description && (
+
+ {task.description}
+
+ )}
+
+
+ {task.priority}
+
+
+
+
+
+
+ {task.type}
+
+ {task.estimate && (
+
{task.estimate}pts
+ )}
+
+
+ {showAssignMenu && (
+
+ {members.map(member => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+// === Main Component ===
+
+interface TeamOrchestratorProps {
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+export function TeamOrchestrator({ isOpen, onClose }: TeamOrchestratorProps) {
+ const [view, setView] = useState<'teams' | 'tasks' | 'members'>('teams');
+ const [isCreating, setIsCreating] = useState(false);
+ const [newTeamName, setNewTeamName] = useState('');
+ const [newTeamPattern, setNewTeamPattern] = useState('sequential');
+
+ const {
+ teams,
+ activeTeam,
+ metrics,
+ isLoading,
+ error,
+ selectedTaskId,
+ selectedMemberId,
+ loadTeams,
+ createTeam,
+ deleteTeam,
+ setActiveTeam,
+ addTask,
+ updateTaskStatus,
+ assignTask,
+ addMember,
+ removeMember,
+ updateMemberRole,
+ setSelectedTask,
+ setSelectedMember,
+ } = useTeamStore();
+
+ const { clones } = useGatewayStore();
+
+ useEffect(() => {
+ if (isOpen) {
+ loadTeams();
+ }
+ }, [isOpen, loadTeams]);
+
+ const handleCreateTeam = async () => {
+ if (!newTeamName.trim()) return;
+ const team = await createTeam({
+ name: newTeamName.trim(),
+ pattern: newTeamPattern,
+ memberAgents: [],
+ });
+ if (team) {
+ setActiveTeam(team);
+ setNewTeamName('');
+ setIsCreating(false);
+ }
+ };
+
+ const handleAddMember = async (agentId: string) => {
+ if (!activeTeam) return;
+ await addMember(activeTeam.id, agentId, 'developer');
+ };
+
+ const handleAddTask = async () => {
+ if (!activeTeam) return;
+ await addTask({
+ teamId: activeTeam.id,
+ title: `Task ${activeTeam.tasks.length + 1}`,
+ priority: 'medium',
+ type: 'implementation',
+ });
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
Team Orchestrator
+
+
+ {metrics && (
+
+ Completed: {metrics.tasksCompleted}
+ Pass Rate: {metrics.passRate.toFixed(0)}%
+ Efficiency: {metrics.efficiency.toFixed(0)}%
+
+ )}
+
+
+
+
+ {/* Content */}
+
+ {/* Sidebar - Team List */}
+
+
+
Teams
+
+
+
+ {isCreating && (
+
+
setNewTeamName(e.target.value)}
+ placeholder="Team name..."
+ className="w-full px-2 py-1 text-sm border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white"
+ />
+
+
+
+
+
+
+ )}
+
+
+ {teams.map(team => (
+
setActiveTeam(team)}
+ className={`p-3 rounded-lg cursor-pointer transition-all ${
+ activeTeam?.id === team.id
+ ? 'bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800'
+ : 'hover:bg-gray-50 dark:hover:bg-gray-800'
+ }`}
+ >
+
+ {team.name}
+
+
+
+
+ {team.members.length} members
+ ·
+ {team.tasks.length} tasks
+
+
+
+ {team.pattern}
+
+
+
+ ))}
+
+
+
+ {/* Main Content */}
+ {activeTeam ? (
+
+ {/* View Tabs */}
+
+
+
+
+
+ {/* Tasks View */}
+ {view === 'tasks' && (
+
+
+
Tasks
+
+
+
+
+ {activeTeam.tasks.length === 0 ? (
+
+ No tasks yet. Click "Add Task" to create one.
+
+ ) : (
+ activeTeam.tasks.map(task => (
+
setSelectedTask(task.id)}
+ onAssign={(memberId) => assignTask(activeTeam.id, task.id, memberId)}
+ onStatusChange={(status) => updateTaskStatus(activeTeam.id, task.id, status)}
+ />
+ ))
+ )}
+
+
+ )}
+
+ {/* Members View */}
+ {view === 'members' && (
+
+
+
Members
+
+
+
+
+
+
+ {activeTeam.members.length === 0 ? (
+
+ No members yet. Select an agent to add.
+
+ ) : (
+ activeTeam.members.map(member => (
+
setSelectedMember(member.id)}
+ onRoleChange={(role) => updateMemberRole(activeTeam.id, member.id, role)}
+ onRemove={() => removeMember(activeTeam.id, member.id)}
+ />
+ ))
+ )}
+
+
+ )}
+
+ ) : (
+
+
+
+
Select or create a team to get started
+
+
+ )}
+
+
+ {/* Footer */}
+
+ {teams.length} teams total
+ {error && {error}}
+
+
+
+ );
+}
diff --git a/desktop/src/store/teamStore.ts b/desktop/src/store/teamStore.ts
new file mode 100644
index 0000000..86f4c55
--- /dev/null
+++ b/desktop/src/store/teamStore.ts
@@ -0,0 +1,587 @@
+/**
+ * Team Store - Multi-Agent Team Collaboration State Management
+ *
+ * Manages team orchestration, task assignment, Dev↔QA loops,
+ * and real-time collaboration state.
+ *
+ * @module store/teamStore
+ */
+
+import { create } from 'zustand';
+import type {
+ Team,
+ TeamMember,
+ TeamTask,
+ TeamTaskStatus,
+ TeamMemberRole,
+ DevQALoop,
+ DevQALoopState,
+ CollaborationPattern,
+ CreateTeamRequest,
+ AddTeamTaskRequest,
+ TeamMetrics,
+ CollaborationEvent,
+ ReviewFeedback,
+ TaskDeliverable,
+} from '../types/team';
+import { getGatewayClient } from '../lib/gateway-client';
+
+// === Store State ===
+
+interface TeamStoreState {
+ // Data
+ teams: Team[];
+ activeTeam: Team | null;
+ metrics: TeamMetrics | null;
+
+ // UI State
+ isLoading: boolean;
+ error: string | null;
+ selectedTaskId: string | null;
+ selectedMemberId: string | null;
+
+ // Real-time events
+ recentEvents: CollaborationEvent[];
+
+ // Actions - Team Management
+ loadTeams: () => Promise;
+ createTeam: (request: CreateTeamRequest) => Promise;
+ deleteTeam: (teamId: string) => Promise;
+ setActiveTeam: (team: Team | null) => void;
+
+ // Actions - Member Management
+ addMember: (teamId: string, agentId: string, role: TeamMemberRole) => Promise;
+ removeMember: (teamId: string, memberId: string) => Promise;
+ updateMemberRole: (teamId: string, memberId: string, role: TeamMemberRole) => Promise;
+
+ // Actions - Task Management
+ addTask: (request: AddTeamTaskRequest) => Promise;
+ updateTaskStatus: (teamId: string, taskId: string, status: TeamTaskStatus) => Promise;
+ assignTask: (teamId: string, taskId: string, memberId: string) => Promise;
+ submitDeliverable: (teamId: string, taskId: string, deliverable: TaskDeliverable) => Promise;
+
+ // Actions - Dev↔QA Loop
+ startDevQALoop: (teamId: string, taskId: string, developerId: string, reviewerId: string) => Promise;
+ submitReview: (teamId: string, loopId: string, feedback: Omit) => Promise;
+ updateLoopState: (teamId: string, loopId: string, state: DevQALoopState) => Promise;
+
+ // Actions - Events
+ addEvent: (event: CollaborationEvent) => void;
+ clearEvents: () => void;
+
+ // Actions - UI
+ setSelectedTask: (taskId: string | null) => void;
+ setSelectedMember: (memberId: string | null) => void;
+ clearError: () => void;
+}
+
+// === Helper Functions ===
+
+const generateId = () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+
+const calculateMetrics = (team: Team): TeamMetrics => {
+ const completedTasks = team.tasks.filter(t => t.status === 'completed');
+ const totalTasks = team.tasks.length;
+ const reviewedTasks = completedTasks.filter(t => t.reviewFeedback);
+
+ const avgCompletionTime = completedTasks.length > 0
+ ? completedTasks.reduce((sum, t) => {
+ if (t.startedAt && t.completedAt) {
+ return sum + (new Date(t.completedAt).getTime() - new Date(t.startedAt).getTime());
+ }
+ return sum;
+ }, 0) / completedTasks.length
+ : 0;
+
+ const approvedReviews = reviewedTasks.filter(t => t.reviewFeedback?.verdict === 'approved');
+ const passRate = reviewedTasks.length > 0
+ ? (approvedReviews.length / reviewedTasks.length) * 100
+ : 0;
+
+ const totalIterations = team.activeLoops.reduce((sum, loop) => sum + loop.iterationCount, 0);
+ const avgIterations = team.activeLoops.length > 0
+ ? totalIterations / team.activeLoops.length
+ : 0;
+
+ const escalations = team.activeLoops.filter(loop => loop.state === 'escalated').length;
+
+ const efficiency = totalTasks > 0
+ ? Math.min(100, (completedTasks.length / totalTasks) * 100 * (passRate / 100))
+ : 0;
+
+ return {
+ tasksCompleted: completedTasks.length,
+ avgCompletionTime,
+ passRate,
+ avgIterations,
+ escalations,
+ efficiency,
+ };
+};
+
+// === Store Implementation ===
+
+export const useTeamStore = create((set, get) => ({
+ // Initial State
+ teams: [],
+ activeTeam: null,
+ metrics: null,
+ isLoading: false,
+ error: null,
+ selectedTaskId: null,
+ selectedMemberId: null,
+ recentEvents: [],
+
+ // Team Management
+ 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) : [];
+ set({ teams, isLoading: false });
+ } catch (error) {
+ set({ error: (error as Error).message, isLoading: false });
+ }
+ },
+
+ createTeam: async (request: CreateTeamRequest) => {
+ set({ isLoading: true, error: null });
+ try {
+ const now = new Date().toISOString();
+ const members: TeamMember[] = request.memberAgents.map((m, index) => ({
+ id: generateId(),
+ agentId: m.agentId,
+ name: `Agent-${m.agentId.slice(0, 4)}`,
+ role: m.role,
+ skills: [],
+ workload: 0,
+ status: 'idle',
+ maxConcurrentTasks: m.role === 'orchestrator' ? 5 : 2,
+ currentTasks: [],
+ }));
+
+ const team: Team = {
+ id: generateId(),
+ name: request.name,
+ description: request.description,
+ members,
+ tasks: [],
+ pattern: request.pattern,
+ activeLoops: [],
+ status: 'active',
+ createdAt: now,
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = [...state.teams, team];
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return { teams, activeTeam: team, isLoading: false };
+ });
+
+ return team;
+ } catch (error) {
+ set({ error: (error as Error).message, isLoading: false });
+ return null;
+ }
+ },
+
+ deleteTeam: async (teamId: string) => {
+ set({ isLoading: true, error: null });
+ try {
+ set(state => {
+ const teams = state.teams.filter(t => t.id !== teamId);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? null : state.activeTeam,
+ isLoading: false
+ };
+ });
+ return true;
+ } catch (error) {
+ set({ error: (error as Error).message, isLoading: false });
+ return false;
+ }
+ },
+
+ setActiveTeam: (team: Team | null) => {
+ set(state => ({
+ activeTeam: team,
+ metrics: team ? calculateMetrics(team) : null,
+ }));
+ },
+
+ // Member Management
+ addMember: async (teamId: string, agentId: string, role: TeamMemberRole) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return null;
+
+ const member: TeamMember = {
+ id: generateId(),
+ agentId,
+ name: `Agent-${agentId.slice(0, 4)}`,
+ role,
+ skills: [],
+ workload: 0,
+ status: 'idle',
+ maxConcurrentTasks: role === 'orchestrator' ? 5 : 2,
+ currentTasks: [],
+ };
+
+ const updatedTeam = {
+ ...team,
+ members: [...team.members, member],
+ updatedAt: new Date().toISOString(),
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return member;
+ },
+
+ removeMember: async (teamId: string, memberId: string) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const updatedTeam = {
+ ...team,
+ members: team.members.filter(m => m.id !== memberId),
+ updatedAt: new Date().toISOString(),
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return true;
+ },
+
+ updateMemberRole: async (teamId: string, memberId: string, role: TeamMemberRole) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const updatedTeam = {
+ ...team,
+ members: team.members.map(m =>
+ m.id === memberId ? { ...m, role, maxConcurrentTasks: role === 'orchestrator' ? 5 : 2 } : m
+ ),
+ updatedAt: new Date().toISOString(),
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return true;
+ },
+
+ // Task Management
+ addTask: async (request: AddTeamTaskRequest) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === request.teamId);
+ if (!team) return null;
+
+ const now = new Date().toISOString();
+ const task: TeamTask = {
+ id: generateId(),
+ title: request.title,
+ description: request.description,
+ status: request.assigneeId ? 'assigned' : 'pending',
+ priority: request.priority,
+ assigneeId: request.assigneeId,
+ dependencies: request.dependencies || [],
+ type: request.type,
+ estimate: request.estimate,
+ createdAt: now,
+ updatedAt: now,
+ };
+
+ const updatedTeam = {
+ ...team,
+ tasks: [...team.tasks, task],
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === request.teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === request.teamId ? updatedTeam : state.activeTeam,
+ metrics: state.activeTeam?.id === request.teamId ? calculateMetrics(updatedTeam) : state.metrics,
+ };
+ });
+
+ return task;
+ },
+
+ updateTaskStatus: async (teamId: string, taskId: string, status: TeamTaskStatus) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const now = new Date().toISOString();
+ const updatedTeam = {
+ ...team,
+ tasks: team.tasks.map(t => {
+ if (t.id !== taskId) return t;
+ const updates: Partial = { status, updatedAt: now };
+ if (status === 'in_progress' && !t.startedAt) {
+ updates.startedAt = now;
+ }
+ if (status === 'completed') {
+ updates.completedAt = now;
+ }
+ return { ...t, ...updates };
+ }),
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ metrics: state.activeTeam?.id === teamId ? calculateMetrics(updatedTeam) : state.metrics,
+ };
+ });
+
+ return true;
+ },
+
+ assignTask: async (teamId: string, taskId: string, memberId: string) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const now = new Date().toISOString();
+ const updatedTeam = {
+ ...team,
+ tasks: team.tasks.map(t =>
+ t.id === taskId
+ ? { ...t, assigneeId: memberId, status: 'assigned' as TeamTaskStatus, updatedAt: now }
+ : t
+ ),
+ members: team.members.map(m =>
+ m.id === memberId
+ ? { ...m, currentTasks: [...m.currentTasks, taskId], workload: (m.workload + 25) }
+ : m
+ ),
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return true;
+ },
+
+ submitDeliverable: async (teamId: string, taskId: string, deliverable: TaskDeliverable) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const now = new Date().toISOString();
+ const updatedTeam = {
+ ...team,
+ tasks: team.tasks.map(t =>
+ t.id === taskId
+ ? { ...t, deliverable, status: 'review' as TeamTaskStatus, updatedAt: now }
+ : t
+ ),
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return true;
+ },
+
+ // Dev↔QA Loop
+ startDevQALoop: async (teamId: string, taskId: string, developerId: string, reviewerId: string) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return null;
+
+ const now = new Date().toISOString();
+ const loop: DevQALoop = {
+ id: generateId(),
+ developerId,
+ reviewerId,
+ taskId,
+ state: 'developing',
+ iterationCount: 0,
+ maxIterations: 3,
+ feedbackHistory: [],
+ startedAt: now,
+ lastUpdatedAt: now,
+ };
+
+ const updatedTeam = {
+ ...team,
+ activeLoops: [...team.activeLoops, loop],
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return loop;
+ },
+
+ submitReview: async (teamId: string, loopId: string, feedback: Omit) => {
+ const state = get();
+ const team = state.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const loop = team.activeLoops.find(l => l.id === loopId);
+ if (!loop) return false;
+
+ const now = new Date().toISOString();
+ const fullFeedback: ReviewFeedback = {
+ ...feedback,
+ reviewedAt: now,
+ reviewerId: loop.reviewerId,
+ };
+
+ let newState: DevQALoopState;
+ let newIterationCount = loop.iterationCount;
+
+ if (feedback.verdict === 'approved') {
+ newState = 'approved';
+ } else if (newIterationCount >= loop.maxIterations - 1) {
+ newState = 'escalated';
+ } else {
+ newState = 'revising';
+ newIterationCount++;
+ }
+
+ const updatedTeam = {
+ ...team,
+ tasks: team.tasks.map(t =>
+ t.id === loop.taskId
+ ? { ...t, reviewFeedback: fullFeedback, updatedAt: now }
+ : t
+ ),
+ activeLoops: team.activeLoops.map(l =>
+ l.id === loopId
+ ? {
+ ...l,
+ state: newState,
+ iterationCount: newIterationCount,
+ feedbackHistory: [...l.feedbackHistory, fullFeedback],
+ lastUpdatedAt: now,
+ }
+ : l
+ ),
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ metrics: state.activeTeam?.id === teamId ? calculateMetrics(updatedTeam) : state.metrics,
+ };
+ });
+
+ return true;
+ },
+
+ updateLoopState: async (teamId: string, loopId: string, state: DevQALoopState) => {
+ const teamStore = get();
+ const team = teamStore.teams.find(t => t.id === teamId);
+ if (!team) return false;
+
+ const now = new Date().toISOString();
+ const updatedTeam = {
+ ...team,
+ activeLoops: team.activeLoops.map(l =>
+ l.id === loopId
+ ? { ...l, state, lastUpdatedAt: now }
+ : l
+ ),
+ updatedAt: now,
+ };
+
+ set(state => {
+ const teams = state.teams.map(t => t.id === teamId ? updatedTeam : t);
+ localStorage.setItem('zclaw-teams', JSON.stringify(teams));
+ return {
+ teams,
+ activeTeam: state.activeTeam?.id === teamId ? updatedTeam : state.activeTeam,
+ };
+ });
+
+ return true;
+ },
+
+ // Events
+ addEvent: (event: CollaborationEvent) => {
+ set(state => ({
+ recentEvents: [event, ...state.recentEvents].slice(0, 100),
+ }));
+ },
+
+ clearEvents: () => {
+ set({ recentEvents: [] });
+ },
+
+ // UI
+ setSelectedTask: (taskId: string | null) => {
+ set({ selectedTaskId: taskId });
+ },
+
+ setSelectedMember: (memberId: string | null) => {
+ set({ selectedMemberId: memberId });
+ },
+
+ clearError: () => {
+ set({ error: null });
+ },
+}));
diff --git a/desktop/src/types/index.ts b/desktop/src/types/index.ts
index 45e90d3..272877e 100644
--- a/desktop/src/types/index.ts
+++ b/desktop/src/types/index.ts
@@ -56,3 +56,24 @@ export type {
WorkflowControlRequest,
WorkflowControlResponse,
} from './workflow';
+
+// Team Collaboration Types
+export type {
+ TeamMemberRole,
+ TeamTaskStatus,
+ TaskPriority,
+ TeamMember,
+ TeamTask,
+ TaskDeliverable,
+ ReviewFeedback,
+ ReviewIssue,
+ DevQALoopState,
+ DevQALoop,
+ CollaborationPattern,
+ Team,
+ CollaborationEvent,
+ TeamMetrics,
+ CreateTeamRequest,
+ AddTeamTaskRequest,
+ TeamResponse,
+} from './team';
diff --git a/desktop/src/types/team.ts b/desktop/src/types/team.ts
new file mode 100644
index 0000000..da4aacc
--- /dev/null
+++ b/desktop/src/types/team.ts
@@ -0,0 +1,296 @@
+/**
+ * Multi-Agent Team Collaboration Types for OpenFang
+ *
+ * This module defines types for multi-agent team orchestration,
+ * collaboration workflows, and Dev↔QA loops.
+ *
+ * @module types/team
+ */
+
+import type { Agent, AgentStatus } from './agent';
+
+// === Team Definition ===
+
+/**
+ * Role of an agent within a team
+ */
+export type TeamMemberRole =
+ | 'orchestrator' // Team coordinator - assigns tasks, manages flow
+ | 'developer' // Primary implementation agent
+ | 'reviewer' // Code review and quality assurance
+ | 'tester' // Testing and validation
+ | 'architect' // System design and architecture
+ | 'specialist'; // Domain-specific expertise
+
+/**
+ * Possible states for a team task
+ */
+export type TeamTaskStatus =
+ | 'pending' // Waiting to be assigned
+ | 'assigned' // Assigned to an agent but not started
+ | 'in_progress' // Currently being worked on
+ | 'review' // Under review by another agent
+ | 'blocked' // Blocked by dependency or issue
+ | 'completed' // Successfully completed
+ | 'failed'; // Failed with error
+
+/**
+ * Priority levels for team tasks
+ */
+export type TaskPriority = 'critical' | 'high' | 'medium' | 'low';
+
+/**
+ * A member of a team (an agent with a specific role)
+ */
+export interface TeamMember {
+ /** Unique identifier for this team member */
+ id: string;
+ /** ID of the underlying agent */
+ agentId: string;
+ /** Agent display name */
+ name: string;
+ /** Role within the team */
+ role: TeamMemberRole;
+ /** Skills this member contributes */
+ skills: string[];
+ /** Current workload (0-100%) */
+ workload: number;
+ /** Current status */
+ status: AgentStatus;
+ /** Maximum concurrent tasks */
+ maxConcurrentTasks: number;
+ /** Current assigned task IDs */
+ currentTasks: string[];
+}
+
+/**
+ * A task that can be assigned to team members
+ */
+export interface TeamTask {
+ /** Unique task identifier */
+ id: string;
+ /** Task title */
+ title: string;
+ /** Detailed task description */
+ description?: string;
+ /** Current status */
+ status: TeamTaskStatus;
+ /** Priority level */
+ priority: TaskPriority;
+ /** ID of assigned team member */
+ assigneeId?: string;
+ /** IDs of tasks this depends on */
+ dependencies: string[];
+ /** Task type classification */
+ type: 'implementation' | 'review' | 'testing' | 'design' | 'deployment';
+ /** Estimated effort (story points or hours) */
+ estimate?: number;
+ /** ISO timestamp of creation */
+ createdAt: string;
+ /** ISO timestamp of last update */
+ updatedAt?: string;
+ /** ISO timestamp when work started */
+ startedAt?: string;
+ /** ISO timestamp of completion */
+ completedAt?: string;
+ /** Result or deliverable */
+ deliverable?: TaskDeliverable;
+ /** Review feedback if in review */
+ reviewFeedback?: ReviewFeedback;
+}
+
+/**
+ * Deliverable produced by a task
+ */
+export interface TaskDeliverable {
+ /** Type of deliverable */
+ type: 'code' | 'document' | 'test' | 'config' | 'report';
+ /** Description of what was produced */
+ description: string;
+ /** File paths modified/created */
+ files?: string[];
+ /** Key metrics or outcomes */
+ metrics?: Record;
+}
+
+/**
+ * Feedback from a review
+ */
+export interface ReviewFeedback {
+ /** Review verdict */
+ verdict: 'approved' | 'needs_work' | 'rejected';
+ /** Detailed feedback */
+ comments: string[];
+ /** Issues found */
+ issues: ReviewIssue[];
+ /** ISO timestamp of review */
+ reviewedAt: string;
+ /** ID of reviewer */
+ reviewerId: string;
+}
+
+/**
+ * An issue found during review
+ */
+export interface ReviewIssue {
+ /** Issue severity */
+ severity: 'critical' | 'major' | 'minor' | 'suggestion';
+ /** Issue description */
+ description: string;
+ /** File location if applicable */
+ file?: string;
+ /** Line number if applicable */
+ line?: number;
+ /** Suggested fix */
+ suggestion?: string;
+}
+
+// === Dev↔QA Loop ===
+
+/**
+ * States in the Dev↔QA loop
+ */
+export type DevQALoopState =
+ | 'idle' // No active loop
+ | 'developing' // Developer is implementing
+ | 'reviewing' // QA is reviewing
+ | 'revising' // Developer is fixing issues
+ | 'approved' // QA approved the work
+ | 'escalated'; // Too many retries, needs human intervention
+
+/**
+ * A Dev↔QA loop instance
+ */
+export interface DevQALoop {
+ /** Unique loop identifier */
+ id: string;
+ /** ID of the developer agent */
+ developerId: string;
+ /** ID of the QA/reviewer agent */
+ reviewerId: string;
+ /** Task being worked on */
+ taskId: string;
+ /** Current state */
+ state: DevQALoopState;
+ /** Number of revision cycles */
+ iterationCount: number;
+ /** Maximum iterations before escalation */
+ maxIterations: number;
+ /** All feedback history */
+ feedbackHistory: ReviewFeedback[];
+ /** ISO timestamp of loop start */
+ startedAt: string;
+ /** ISO timestamp of last state change */
+ lastUpdatedAt: string;
+}
+
+// === Team Collaboration ===
+
+/**
+ * Possible collaboration patterns
+ */
+export type CollaborationPattern =
+ | 'sequential' // Tasks completed one after another
+ | 'parallel' // Tasks completed simultaneously
+ | 'pipeline' // Output of one feeds into next
+ | 'review_loop'; // Dev↔QA iteration pattern
+
+/**
+ * A team collaboration session
+ */
+export interface Team {
+ /** Unique team identifier */
+ id: string;
+ /** Team name */
+ name: string;
+ /** Team description */
+ description?: string;
+ /** Team members */
+ members: TeamMember[];
+ /** Active tasks */
+ tasks: TeamTask[];
+ /** Collaboration pattern */
+ pattern: CollaborationPattern;
+ /** Active Dev↔QA loops */
+ activeLoops: DevQALoop[];
+ /** Team status */
+ status: 'active' | 'paused' | 'completed' | 'error';
+ /** ISO timestamp of creation */
+ createdAt: string;
+ /** ISO timestamp of last activity */
+ updatedAt?: string;
+}
+
+/**
+ * Real-time collaboration event
+ */
+export interface CollaborationEvent {
+ /** Event type */
+ type: 'task_assigned' | 'task_started' | 'task_completed' |
+ 'review_requested' | 'review_submitted' |
+ 'loop_state_change' | 'member_status_change';
+ /** ID of the team */
+ teamId: string;
+ /** ID of the source agent */
+ sourceAgentId: string;
+ /** Event payload */
+ payload: Record;
+ /** ISO timestamp */
+ timestamp: string;
+}
+
+/**
+ * Team performance metrics
+ */
+export interface TeamMetrics {
+ /** Total tasks completed */
+ tasksCompleted: number;
+ /** Average task completion time (ms) */
+ avgCompletionTime: number;
+ /** Review pass rate (0-100) */
+ passRate: number;
+ /** Average iterations per task */
+ avgIterations: number;
+ /** Escalation count */
+ escalations: number;
+ /** Team efficiency score (0-100) */
+ efficiency: number;
+}
+
+// === API Types ===
+
+/**
+ * Request to create a new team
+ */
+export interface CreateTeamRequest {
+ name: string;
+ description?: string;
+ memberAgents: Array<{
+ agentId: string;
+ role: TeamMemberRole;
+ }>;
+ pattern: CollaborationPattern;
+}
+
+/**
+ * Request to add a task to a team
+ */
+export interface AddTeamTaskRequest {
+ teamId: string;
+ title: string;
+ description?: string;
+ priority: TaskPriority;
+ type: TeamTask['type'];
+ assigneeId?: string;
+ dependencies?: string[];
+ estimate?: number;
+}
+
+/**
+ * Response for team operations
+ */
+export interface TeamResponse {
+ team: Team;
+ success: boolean;
+ error?: string;
+}
diff --git a/docs/SYSTEM_ANALYSIS.md b/docs/SYSTEM_ANALYSIS.md
index 64c6c17..b070334 100644
--- a/docs/SYSTEM_ANALYSIS.md
+++ b/docs/SYSTEM_ANALYSIS.md
@@ -429,9 +429,20 @@ ZCLAW 是基于 **OpenFang** (Rust Agent OS) 的 AI Agent 桌面客户端,核
*Phase 1-4 已完成 ✅*
*Phase 5 已完成 ✅*
* 基础 Skills: 9 个 ✅
- * agency-agents 集成: 60+ Skills ✅
+ * agency-agents 集成: 68 Skills ✅
* 多 Agent 协作框架: 完整 ✅
* 协作协议 (Handoff Templates)
* Agent 激活提示
* 7 阶段 Playbooks (Discovery → Operate)
-*下一步: 多 Agent Team 协作功能实现*
+*Phase 6 进行中 🔄*
+ * 多 Agent Team 协作 UI 实现:
+ * ✅ 类型定义 (`types/team.ts`)
+ * ✅ Team Store (`store/teamStore.ts`)
+ * ✅ Agent 编排器 UI (`components/TeamOrchestrator.tsx`)
+ * ✅ Dev↔QA 循环界面 (`components/DevQALoop.tsx`)
+ * ✅ 实时协作状态 (`components/TeamCollaborationView.tsx`)
+ * 待完成:
+ * 组件集成到主应用
+ * WebSocket 实时事件同步
+ * 与 OpenFang API 对接
+*下一步: 组件集成与 API 对接*