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
@@ -3,11 +3,11 @@ import type { ReactNode } from "react";
import { useDaemonRegistry, type DaemonProfile } from "./daemon-registry-context";
export type ConnectionState =
| { status: "idle"; lastError: null; lastOnlineAt: string | null; sessionReady: false }
| { status: "connecting"; lastError: null; lastOnlineAt: string | null; sessionReady: false }
| { status: "online"; lastError: null; lastOnlineAt: string; sessionReady: boolean }
| { status: "offline"; lastError: string | null; lastOnlineAt: string | null; sessionReady: false }
| { status: "error"; lastError: string; lastOnlineAt: string | null; sessionReady: false };
| { status: "idle"; lastError: null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: false }
| { status: "connecting"; lastError: null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean }
| { status: "online"; lastError: null; lastOnlineAt: string; sessionReady: boolean; hasEverReceivedSessionState: boolean }
| { status: "offline"; lastError: string | null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean }
| { status: "error"; lastError: string; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean };
export type ConnectionStatus = ConnectionState["status"];
@@ -36,6 +36,7 @@ function createDefaultConnectionState(): ConnectionState {
lastError: null,
lastOnlineAt: null,
sessionReady: false,
hasEverReceivedSessionState: false,
};
}
@@ -45,20 +46,31 @@ function resolveNextConnectionState(
): ConnectionState {
switch (update.status) {
case "idle":
return { status: "idle", lastError: null, lastOnlineAt: existing.lastOnlineAt, sessionReady: false };
return {
status: "idle",
lastError: null,
lastOnlineAt: existing.lastOnlineAt,
sessionReady: false,
hasEverReceivedSessionState: false,
};
case "connecting":
return {
status: "connecting",
lastError: null,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
sessionReady: false,
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
};
case "online":
const currentSessionReady = update.sessionReady ?? (existing.status === "online" ? existing.sessionReady : false);
return {
status: "online",
lastError: null,
lastOnlineAt: update.lastOnlineAt,
sessionReady: update.sessionReady ?? (existing.status === "online" ? existing.sessionReady : false),
sessionReady: currentSessionReady,
hasEverReceivedSessionState:
currentSessionReady || existing.hasEverReceivedSessionState || false,
};
case "offline":
return {
@@ -66,6 +78,7 @@ function resolveNextConnectionState(
lastError: update.lastError ?? null,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
sessionReady: false,
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
};
case "error":
return {
@@ -73,6 +86,7 @@ function resolveNextConnectionState(
lastError: update.lastError,
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
sessionReady: false,
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
};
}
}