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

@@ -238,31 +238,41 @@ class AgentTracker:
}
async def _handle_agent_complete(self, feature_id: int, is_success: bool) -> dict | None:
"""Handle agent completion message from orchestrator."""
"""Handle agent completion - ALWAYS emits a message, even if agent wasn't tracked."""
async with self._lock:
if feature_id not in self.active_agents:
return None
agent = self.active_agents[feature_id]
state = 'success' if is_success else 'error'
agent_type = agent.get('agent_type', 'coding')
result = {
'type': 'agent_update',
'agentIndex': agent['agent_index'],
'agentName': agent['name'],
'agentType': agent_type,
'featureId': feature_id,
'featureName': agent['feature_name'],
'state': state,
'thought': 'Completed successfully!' if is_success else 'Failed to complete',
'timestamp': datetime.now().isoformat(),
}
# Remove from active agents
del self.active_agents[feature_id]
return result
if feature_id in self.active_agents:
# Normal case: agent was tracked
agent = self.active_agents[feature_id]
result = {
'type': 'agent_update',
'agentIndex': agent['agent_index'],
'agentName': agent['name'],
'agentType': agent.get('agent_type', 'coding'),
'featureId': feature_id,
'featureName': agent['feature_name'],
'state': state,
'thought': 'Completed successfully!' if is_success else 'Failed to complete',
'timestamp': datetime.now().isoformat(),
}
del self.active_agents[feature_id]
return result
else:
# Synthetic completion for untracked agent
# This ensures UI always receives completion messages
return {
'type': 'agent_update',
'agentIndex': -1, # Sentinel for untracked
'agentName': 'Unknown',
'agentType': 'coding',
'featureId': feature_id,
'featureName': f'Feature #{feature_id}',
'state': state,
'thought': 'Completed successfully!' if is_success else 'Failed to complete',
'timestamp': datetime.now().isoformat(),
'synthetic': True,
}
class OrchestratorTracker: