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>
Finalizes the pure Zustand refactor by ensuring all components properly access both session state and imperative methods through useDaemonSession. Moves CLAUDE.md to root and ignores local overrides.
Changes:
- Fix useDaemonSession to return stable combined state + methods object
- Update components to use useDaemonSession instead of direct context
- Fix realtime context to work with session state only
- Move CLAUDE.md to root, ignore CLAUDE.local.md for local config
- Add proper null checks and type safety throughout
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Agent directory was still being synced from SessionProvider to
Zustand store, violating the "SessionProvider is just a message handler"
principle and creating unnecessary state duplication.
Solution: Make agent directory pure derived state:
- Removed agentDirectory from SessionStoreState
- Removed setAgentDirectory/clearAgentDirectory actions
- Changed getAgentDirectory to compute on-demand from session.agents
- Removed buildAgentDirectoryEntries and syncing from SessionProvider
- Updated useAggregatedAgents to derive from sessions directly
How it works now:
- Agent directory is computed on-demand from session.agents Map
- lastActivityAt automatically updates when agents update via WebSocket
- No syncing, no stale state, no overhead
- Single source of truth: session.agents
Benefits:
- Eliminated redundant state (agentDirectory was duplicate of agents)
- No sync overhead or complexity
- Always fresh data (derived on read)
- Simpler mental model (agents is the only source)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: SessionContext used useSyncedSessionState which synced every state
update from React useState to Zustand, creating double overhead and complexity.
Solution: Complete migration to pure Zustand architecture:
- session-store.ts is now the single source of truth
- Moved ALL state directly into Zustand (no React useState)
- Added per-session state: messages, agents, commands, permissions, etc.
- Added Zustand actions for all state updates (setMessages, setAgents, etc.)
- Exported all types (MessageEntry, Agent, Command, etc.)
- session-context.tsx is now a pure WebSocket message handler
- Removed useSyncedSessionState hook entirely
- Removed all useState calls for session state
- Removed sync machinery (syncSessionField, syncSessionPartial)
- WebSocket handlers now call Zustand setters directly
- Context only provides imperative APIs (ws, audioPlayer, actions)
- No state values in context
Architecture change:
Before: WebSocket → React useState → sync → Zustand (double overhead)
After: WebSocket → Zustand directly (single source of truth)
Benefits:
- Eliminated double overhead from React → Zustand syncing
- Single state update per WebSocket message
- Better re-render optimization via Zustand selectors
- Cleaner separation: SessionProvider = message handler, SessionStore = state
- Reduced complexity: removed 400+ lines of hybrid sync code
Backward compatibility:
- useDaemonSession() already reads from Zustand, no changes needed
- Type exports re-exported from session-store for compatibility
- Components work exactly the same
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed a state management bug where clicking the checkmark in create agent dictation mode would not trigger processing. The issue was that setIsDictationProcessing(true) was called after stopping the recorder, causing a race condition where the UI never showed the processing state.
Changes:
- Move setIsDictationProcessing(true) to execute immediately at the start of the confirm handler
- Add proper cleanup when audioData is null
- Ensure UI shows loading spinner when checkmark is clicked
This aligns the create agent dictation behavior with the working agent chat dictation implementation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>