Fix loading state bugs and server crash

Backend fixes:
- Fix server crash when logging large session_state messages (186 agents)
- Use safe logging that extracts only message/stack properties
- Prevents TypeError in util.inspect on circular refs or deep objects

Frontend loading state fixes:
- Add reactive WebSocket update to prevent stale session.ws references
- Add timeout/retry wrapper for session_state loading (3 retries × 15s)
- Add hasEverReceivedSessionState flag to track cached data across reconnects
- Implement data-aware loading (isInitialLoad vs isRevalidating)
- Show reconnecting badge instead of hiding agents on reconnect

Debug improvements:
- Add detailed connection state logging in useAggregatedAgents
- Add connection status logging in AgentInputArea
- Track connecting reasons for easier debugging

Fixes:
- Agent screen no longer shows false "Offline" toast on reconnect
- Home screen shows loading immediately (no empty state flash)
- Home screen keeps agents visible during reconnect with badge
- Stuck loading has timeout protection with graceful degradation
- Server no longer crashes when sending session_state with many agents

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-12-03 02:45:16 +00:00
co-authored by Claude
parent 5ddaea7352
commit 348cb9d637
7 changed files with 244 additions and 30 deletions
@@ -11,6 +11,8 @@ export interface AggregatedAgent extends AgentDirectoryEntry {
export interface AggregatedAgentsResult {
agents: AggregatedAgent[];
isLoading: boolean;
isInitialLoad: boolean;
isRevalidating: boolean;
}
export function useAggregatedAgents(): AggregatedAgentsResult {
@@ -55,17 +57,71 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
return rightTime - leftTime;
});
const isLoading = Array.from(connectionStates.values()).some(c => {
// Only these states count as "loading":
// 1. Actively connecting
// 2. Online but session not yet received
if (c.status === 'connecting') return true;
if (c.status === 'online' && !c.sessionReady) return true;
// Check if we have any cached data
const hasAnyData = allAgents.length > 0;
// Check if any connection is currently loading
const connectingReasons: string[] = [];
const isConnecting = Array.from(connectionStates.entries()).some(([id, c]) => {
const shortId = id.substring(0, 20);
// First-time connection (never received session state)
if (c.status === 'connecting' && !c.hasEverReceivedSessionState) {
connectingReasons.push(`${shortId}: first-time connecting`);
return true;
}
if (c.status === 'online' && !c.hasEverReceivedSessionState) {
connectingReasons.push(`${shortId}: online but no session_state yet`);
return true;
}
// Reconnecting (have received session state before)
if (c.status === 'connecting' && c.hasEverReceivedSessionState) {
connectingReasons.push(`${shortId}: reconnecting`);
return true;
}
if (c.status === 'online' && !c.sessionReady && c.hasEverReceivedSessionState) {
connectingReasons.push(`${shortId}: online but sessionReady=false (waiting for session_state)`);
return true;
}
// Offline/error = not loading, just unavailable (normal state)
return false;
});
return { agents: allAgents, isLoading };
// isInitialLoad: Loading for the first time (no cached data)
const isInitialLoad = isConnecting && !hasAnyData;
// isRevalidating: Loading but we have cached data (reconnecting)
const isRevalidating = isConnecting && hasAnyData;
// isLoading: Generic loading flag (either initial or revalidating)
const isLoading = isConnecting;
const connectionStatesArray = Array.from(connectionStates.entries()).map(([id, state]) => ({
id: id.substring(0, 20) + (id.length > 20 ? '...' : ''),
status: state.status,
sessionReady: state.sessionReady,
hasEverReceivedSessionState: state.hasEverReceivedSessionState,
}));
console.log('[useAggregatedAgents] States:', {
hasAnyData,
isConnecting,
isInitialLoad,
isRevalidating,
totalConnectionStates: connectionStates.size,
connectingReasons: connectingReasons.length > 0 ? connectingReasons : 'none',
});
console.log('[useAggregatedAgents] Connection States Detail:',
JSON.stringify(connectionStatesArray, null, 2)
);
return {
agents: allAgents,
isLoading,
isInitialLoad,
isRevalidating,
};
}, [sessions, connectionStates]);
}