fix: ensure agents are removed from Mission Control UI on completion

Previously, agents that completed their work would remain visible in the
Mission Control UI until a manual page refresh. This occurred because
the AgentTracker._handle_agent_complete method silently dropped completion
messages when an agent wasn't tracked (e.g., due to missed start messages
from WebSocket connection issues).

Backend changes:
- Modified _handle_agent_complete in server/websocket.py to always emit
  completion messages, even for untracked agents
- Synthetic completions use agentIndex=-1 and agentName='Unknown' as
  sentinel values to indicate untracked agents

Frontend changes:
- Updated useWebSocket.ts to handle synthetic completions by removing
  agents by featureId when agentIndex is -1
- Added 30-minute stale agent cleanup as defense-in-depth for users who
  leave the UI open for extended periods
- Updated TypeScript types to allow 'Unknown' as valid agent name

Component updates:
- AgentAvatar.tsx: Added UNKNOWN_COLORS and UnknownSVG fallback for
  rendering unknown agents with a neutral gray question mark icon
- CelebrationOverlay.tsx, DependencyGraph.tsx: Updated interfaces to
  accept 'Unknown' agent names

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-23 13:19:45 +02:00
parent a03d945fcd
commit 1be42cc734
6 changed files with 94 additions and 35 deletions

View File

@@ -24,7 +24,7 @@ interface ActivityItem {
// Celebration trigger for overlay
interface CelebrationTrigger {
agentName: AgentMascot
agentName: AgentMascot | 'Unknown'
featureName: string
featureId: number
}
@@ -190,9 +190,18 @@ export function useProjectWebSocket(projectName: string | null) {
if (message.state === 'success' || message.state === 'error') {
// Remove agent from active list on completion (success or failure)
// But keep the logs in agentLogs map for debugging
newAgents = prev.activeAgents.filter(
a => a.agentIndex !== message.agentIndex
)
if (message.agentIndex === -1) {
// Synthetic completion: remove by featureId
// This handles agents that weren't tracked but still completed
newAgents = prev.activeAgents.filter(
a => a.featureId !== message.featureId
)
} else {
// Normal completion: remove by agentIndex
newAgents = prev.activeAgents.filter(
a => a.agentIndex !== message.agentIndex
)
}
} else if (existingAgentIdx >= 0) {
// Update existing agent
newAgents = [...prev.activeAgents]
@@ -411,6 +420,27 @@ export function useProjectWebSocket(projectName: string | null) {
}
}, [projectName, connect, sendPing])
// Defense-in-depth: cleanup stale agents for users who leave UI open for hours
// This catches edge cases where completion messages are missed
useEffect(() => {
const STALE_THRESHOLD_MS = 30 * 60 * 1000 // 30 minutes
const cleanup = setInterval(() => {
setState(prev => {
const now = Date.now()
const fresh = prev.activeAgents.filter(a =>
now - new Date(a.timestamp).getTime() < STALE_THRESHOLD_MS
)
if (fresh.length !== prev.activeAgents.length) {
return { ...prev, activeAgents: fresh }
}
return prev
})
}, 60000) // Check every minute
return () => clearInterval(cleanup)
}, [])
// Clear logs function
const clearLogs = useCallback(() => {
setState(prev => ({ ...prev, logs: [] }))