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

@@ -1,6 +1,6 @@
import { useState } from 'react'
import { X, CheckCircle2, Circle, SkipForward, Trash2, Loader2, AlertCircle, Pencil } from 'lucide-react'
import { useSkipFeature, useDeleteFeature } from '../hooks/useProjects'
import { X, CheckCircle2, Circle, SkipForward, Trash2, Loader2, AlertCircle, Pencil, Link2, AlertTriangle } from 'lucide-react'
import { useSkipFeature, useDeleteFeature, useFeatures } from '../hooks/useProjects'
import { EditFeatureForm } from './EditFeatureForm'
import type { Feature } from '../lib/types'
@@ -37,6 +37,25 @@ export function FeatureModal({ feature, projectName, onClose }: FeatureModalProp
const skipFeature = useSkipFeature(projectName)
const deleteFeature = useDeleteFeature(projectName)
const { data: allFeatures } = useFeatures(projectName)
// Build a map of feature ID to feature for looking up dependency names
const featureMap = new Map<number, Feature>()
if (allFeatures) {
;[...allFeatures.pending, ...allFeatures.in_progress, ...allFeatures.done].forEach(f => {
featureMap.set(f.id, f)
})
}
// Get dependency features
const dependencies = (feature.dependencies || [])
.map(id => featureMap.get(id))
.filter((f): f is Feature => f !== undefined)
// Get blocking dependencies (unmet dependencies)
const blockingDeps = (feature.blocking_dependencies || [])
.map(id => featureMap.get(id))
.filter((f): f is Feature => f !== undefined)
const handleSkip = async () => {
setError(null)
@@ -145,6 +164,57 @@ export function FeatureModal({ feature, projectName, onClose }: FeatureModalProp
</p>
</div>
{/* Blocked By Warning */}
{blockingDeps.length > 0 && (
<div className="p-4 bg-[var(--color-neo-warning-bg)] border-3 border-[var(--color-neo-warning-border)]">
<h3 className="font-display font-bold mb-2 uppercase text-sm flex items-center gap-2 text-[var(--color-neo-warning-text)]">
<AlertTriangle size={16} />
Blocked By
</h3>
<p className="text-sm text-[var(--color-neo-warning-text)] mb-2">
This feature cannot start until the following dependencies are complete:
</p>
<ul className="space-y-1">
{blockingDeps.map(dep => (
<li
key={dep.id}
className="flex items-center gap-2 text-sm"
>
<Circle size={14} className="text-[var(--color-neo-warning-text)]" />
<span className="font-mono text-xs text-[var(--color-neo-warning-text)]">#{dep.id}</span>
<span className="text-[var(--color-neo-warning-text)]">{dep.name}</span>
</li>
))}
</ul>
</div>
)}
{/* Dependencies */}
{dependencies.length > 0 && (
<div>
<h3 className="font-display font-bold mb-2 uppercase text-sm flex items-center gap-2">
<Link2 size={16} />
Depends On
</h3>
<ul className="space-y-1">
{dependencies.map(dep => (
<li
key={dep.id}
className="flex items-center gap-2 p-2 bg-[var(--color-neo-bg)] border-2 border-[var(--color-neo-border)]"
>
{dep.passes ? (
<CheckCircle2 size={16} className="text-[var(--color-neo-done)]" />
) : (
<Circle size={16} className="text-[var(--color-neo-text-secondary)]" />
)}
<span className="font-mono text-xs text-[var(--color-neo-text-secondary)]">#{dep.id}</span>
<span className={dep.passes ? 'text-[var(--color-neo-done)]' : ''}>{dep.name}</span>
</li>
))}
</ul>
</div>
)}
{/* Steps */}
{feature.steps.length > 0 && (
<div>