feat: add concurrent agents with dependency system and delightful UI

Major feature implementation for parallel agent execution with dependency-aware
scheduling and an engaging multi-agent UI experience.

Backend Changes:
- Add parallel_orchestrator.py for concurrent feature processing
- Add api/dependency_resolver.py with cycle detection (Kahn's algorithm + DFS)
- Add atomic feature_claim_next() with retry limit and exponential backoff
- Fix circular dependency check arguments in 4 locations
- Add AgentTracker class for parsing agent output and emitting updates
- Add browser isolation with --isolated flag for Playwright MCP
- Extend WebSocket protocol with agent_update messages and log attribution
- Add WSAgentUpdateMessage schema with agent states and mascot names
- Fix WSProgressMessage to include in_progress field

New UI Components:
- AgentMissionControl: Dashboard showing active agents with collapsible activity
- AgentCard: Individual agent status with avatar and thought bubble
- AgentAvatar: SVG mascots (Spark, Fizz, Octo, Hoot, Buzz) with animations
- ActivityFeed: Recent activity stream with stable keys (no flickering)
- CelebrationOverlay: Confetti animation with click/Escape dismiss
- DependencyGraph: Interactive node graph visualization with dagre layout
- DependencyBadge: Visual indicator for feature dependencies
- ViewToggle: Switch between Kanban and Graph views
- KeyboardShortcutsHelp: Help overlay accessible via ? key

UI/UX Improvements:
- Celebration queue system to handle rapid success messages
- Accessibility attributes on AgentAvatar (role, aria-label, aria-live)
- Collapsible Recent Activity section with persisted preference
- Agent count display in header
- Keyboard shortcut G to toggle Kanban/Graph view
- Real-time thought bubbles and state animations

Bug Fixes:
- Fix circular dependency validation (swapped source/target arguments)
- Add MAX_CLAIM_RETRIES=10 to prevent stack overflow under contention
- Fix THOUGHT_PATTERNS to match actual [Tool: name] format
- Fix ActivityFeed key prop to prevent re-renders on new items
- Add featureId/agentIndex to log messages for proper attribution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-17 12:59:42 +02:00
parent 91cc00a9d0
commit 85f6940a54
39 changed files with 4532 additions and 157 deletions

View File

@@ -12,6 +12,7 @@ import type {
FeatureUpdate,
FeatureBulkCreate,
FeatureBulkCreateResponse,
DependencyGraph,
AgentStatusResponse,
AgentActionResponse,
SetupStatus,
@@ -141,6 +142,50 @@ export async function createFeaturesBulk(
})
}
// ============================================================================
// Dependency Graph API
// ============================================================================
export async function getDependencyGraph(projectName: string): Promise<DependencyGraph> {
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/graph`)
}
export async function addDependency(
projectName: string,
featureId: number,
dependencyId: number
): Promise<{ success: boolean; feature_id: number; dependencies: number[] }> {
return fetchJSON(
`/projects/${encodeURIComponent(projectName)}/features/${featureId}/dependencies/${dependencyId}`,
{ method: 'POST' }
)
}
export async function removeDependency(
projectName: string,
featureId: number,
dependencyId: number
): Promise<{ success: boolean; feature_id: number; dependencies: number[] }> {
return fetchJSON(
`/projects/${encodeURIComponent(projectName)}/features/${featureId}/dependencies/${dependencyId}`,
{ method: 'DELETE' }
)
}
export async function setDependencies(
projectName: string,
featureId: number,
dependencyIds: number[]
): Promise<{ success: boolean; feature_id: number; dependencies: number[] }> {
return fetchJSON(
`/projects/${encodeURIComponent(projectName)}/features/${featureId}/dependencies`,
{
method: 'PUT',
body: JSON.stringify({ dependency_ids: dependencyIds }),
}
)
}
// ============================================================================
// Agent API
// ============================================================================
@@ -151,11 +196,19 @@ export async function getAgentStatus(projectName: string): Promise<AgentStatusRe
export async function startAgent(
projectName: string,
yoloMode: boolean = false
options: {
yoloMode?: boolean
parallelMode?: boolean
maxConcurrency?: number
} = {}
): Promise<AgentActionResponse> {
return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/start`, {
method: 'POST',
body: JSON.stringify({ yolo_mode: yoloMode }),
body: JSON.stringify({
yolo_mode: options.yoloMode ?? false,
parallel_mode: options.parallelMode ?? false,
max_concurrency: options.maxConcurrency,
}),
})
}

View File

@@ -66,6 +66,32 @@ export interface Feature {
steps: string[]
passes: boolean
in_progress: boolean
dependencies?: number[] // Optional for backwards compat
blocked?: boolean // Computed by API
blocking_dependencies?: number[] // Computed by API
}
// Status type for graph nodes
export type FeatureStatus = 'pending' | 'in_progress' | 'done' | 'blocked'
// Graph visualization types
export interface GraphNode {
id: number
name: string
category: string
status: FeatureStatus
priority: number
dependencies: number[]
}
export interface GraphEdge {
source: number
target: number
}
export interface DependencyGraph {
nodes: GraphNode[]
edges: GraphEdge[]
}
export interface FeatureListResponse {
@@ -80,6 +106,7 @@ export interface FeatureCreate {
description: string
steps: string[]
priority?: number
dependencies?: number[]
}
export interface FeatureUpdate {
@@ -88,6 +115,7 @@ export interface FeatureUpdate {
description?: string
steps?: string[]
priority?: number
dependencies?: number[]
}
// Agent types
@@ -99,6 +127,8 @@ export interface AgentStatusResponse {
started_at: string | null
yolo_mode: boolean
model: string | null // Model being used by running agent
parallel_mode: boolean
max_concurrency: number | null
}
export interface AgentActionResponse {
@@ -140,8 +170,26 @@ export interface TerminalInfo {
created_at: string
}
// Agent mascot names for multi-agent UI
export const AGENT_MASCOTS = ['Spark', 'Fizz', 'Octo', 'Hoot', 'Buzz'] as const
export type AgentMascot = typeof AGENT_MASCOTS[number]
// Agent state for Mission Control
export type AgentState = 'idle' | 'thinking' | 'working' | 'testing' | 'success' | 'error' | 'struggling'
// Agent update from backend
export interface ActiveAgent {
agentIndex: number
agentName: AgentMascot
featureId: number
featureName: string
state: AgentState
thought?: string
timestamp: string
}
// WebSocket message types
export type WSMessageType = 'progress' | 'feature_update' | 'log' | 'agent_status' | 'pong' | 'dev_log' | 'dev_server_status'
export type WSMessageType = 'progress' | 'feature_update' | 'log' | 'agent_status' | 'pong' | 'dev_log' | 'dev_server_status' | 'agent_update'
export interface WSProgressMessage {
type: 'progress'
@@ -161,6 +209,20 @@ export interface WSLogMessage {
type: 'log'
line: string
timestamp: string
featureId?: number
agentIndex?: number
agentName?: AgentMascot
}
export interface WSAgentUpdateMessage {
type: 'agent_update'
agentIndex: number
agentName: AgentMascot
featureId: number
featureName: string
state: AgentState
thought?: string
timestamp: string
}
export interface WSAgentStatusMessage {
@@ -189,6 +251,7 @@ export type WSMessage =
| WSFeatureUpdateMessage
| WSLogMessage
| WSAgentStatusMessage
| WSAgentUpdateMessage
| WSPongMessage
| WSDevLogMessage
| WSDevServerStatusMessage