mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Introduce DaemonRegistry and DaemonConnections contexts for managing multiple server connections. Add session directory for aggregating agents across daemons, with automatic routing based on agent/server context. Key changes: - Add daemon profile management (add/edit/remove/set default) - Implement connection state tracking with reconnection and exponential backoff - Create useAggregatedAgents hook for unified agent list across daemons - Add useDaemonSession and useDaemonRequest hooks for daemon-scoped operations - Update agent routes to include serverId for proper session routing - Add connection health indicators on home screen and settings - Persist session snapshots per daemon for faster hydration on reconnect - Guard screens gracefully when target daemon is offline - Add React Query for consistent loading/error state management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
15 KiB
15 KiB
Multi-Daemon Production Rollout Plan
Guiding Principles
- The app always reflects the union of every connected daemon—no manual switching to “see” data.
- Actions (create/resume agent, browse files, realtime tools) automatically route to the correct daemon based on the agent/workspace context.
- Connection health, loading states, and mutations are observable and resilient (React Query or equivalent).
Workstreams & Tasks
1. Session Directory & Data Consistency
- Rebuild
useSessionDirectoryso it stays in sync with realtime session state (agents, permissions, stream updates) instead of caching an accessor forever.- Added session-level subscriptions so the directory re-renders on daemon updates and ran
npm run typecheckto verify.
- Added session-level subscriptions so the directory re-renders on daemon updates and ran
- Expose a lightweight subscription/API for background
SessionProviders so any change invalidates aggregated consumers without forcing rerenders of the main tree.- Added central session-directory listeners in the daemon connections context, had SessionProviders emit invalidations, updated
useSessionDirectoryto use the new API, and rannpm run typecheck.
- Added central session-directory listeners in the daemon connections context, had SessionProviders emit invalidations, updated
- Audit every consumer that still reaches for
useSession()directly (AgentStreamView, AgentInputArea, file explorers, realtime, etc.) and ensure they receive the session instance that corresponds to the agent/daemon they’re operating on.- Agent detail UI now pulls daemon-scoped sessions via
useDaemonSession, so sending messages, toggling modes, and inline file explorers all operate against the route’sserverId(seepackages/app/src/components/agent-input-area.tsx:72,packages/app/src/components/agent-stream-view.tsx:31,packages/app/src/components/agent-status-bar.tsx:6,packages/app/src/app/agent/[serverId]/[agentId].tsx:256) andnpm run typecheckpasses.
- Agent detail UI now pulls daemon-scoped sessions via
2. Aggregated Agent Experience
- Replace the home screen’s
agents.sizegate withuseAggregatedAgents, render the merged list (grouped/sorted by daemon), and remove the daemon picker UI from the header.- Home now builds grouped daemon sections via
useAggregatedAgents,AgentListrenders each section with the correct serverId routing, the header exposes dedicated import/create buttons without manual daemon switching, andnpm run typecheckpasses. - When closing the agent action sheet we now clear the stored
serverIdsouseDaemonSessionfalls back to the active daemon and the Home screen no longer crashes if that background daemon disconnects after a long-press.
- Home now builds grouped daemon sections via
- Ensure all agent rows carry their
serverIdthrough navigation (Agent screen, diff viewer, file explorer, orchestrator) so every deep link includes/agent/[serverId]/[agentId].- Annotated every agent snapshot with its daemon
serverId, updated shared row components (AgentList,AgentSidebar,ActiveProcesses) to read it when navigating/deleting, and re-rannpm run typecheck.
- Annotated every agent snapshot with its daemon
- Update stream detail routes (git diff, file explorer) to validate the daemon session from params and gracefully show status/loading/error states if the background session is unavailable.
- Wrapped both routes in session guards that render connection-aware placeholders when the target daemon is offline/unavailable, moved the existing logic into gated child components, and re-ran
npm run typecheckto verify.
- Wrapped both routes in session guards that render connection-aware placeholders when the target daemon is offline/unavailable, moved the existing logic into gated child components, and re-ran
- Guard the main agent route when the requested daemon session is unavailable so deep links or quick daemon switches don't crash the screen.
- Added a connection-aware guard around
/agent/[serverId]/[agentId]that renders a friendly placeholder when the session is offline/unavailable and re-rannpm run typecheck.
- Added a connection-aware guard around
- Fix the agent screen dropdown positioning so we don't double-apply the safe-area offset and remove the leaked debug logging.
packages/app/src/app/agent/[serverId]/[agentId].tsx:320-339addsinsets.toptomeasureInWindowcoordinates (which already include the status bar) and emits[Menu]console logs on every open, so the action menu renders ~40px too low on notched devices and spams the JS console.- Removed the extra
insets.topoffset, cleaned up the[Menu]debug logs, and re-rannpm run typecheck.
- Make inline file path navigation pick up the correct daemon id even when two daemons generate the same
agentId.- Added
resolvedServerIdto thehandleInlinePathPressdependency list inpackages/app/src/components/agent-stream-view.tsx:83-116, so navigating after switching daemons now routes to the correct file explorer target; re-rannpm run typecheck.
- Added
- Sync the active daemon context with the agent route so realtime/audio flows hit the same websocket as the rendered agent, even when arriving from a deep link.
AgentScreenlooks up the requested session viauseDaemonSessionbut never callssetActiveDaemonId, so opening/agent/[serverB]/[agentId]while daemon A is active leaves the globalSessionProvider/RealtimeProviderpointed at A (seepackages/app/src/app/agent/[serverId]/[agentId].tsx:74-138).AgentInputAreaforwards realtime/voice interactions throughuseRealtime()and the activews(packages/app/src/components/agent-input-area.tsx:656-744), so with the mismatch above those commands get sent to daemon A instead of the daemon hosting the open agent.- Added a route-aware effect in
packages/app/src/app/agent/[serverId]/[agentId].tsxthat synchronizessetActiveDaemonIdwith the screen'sserverIdparam so the root Session/Realtime providers follow deep links, and rannpm run typecheck --workspace=@paseo/app.
- Restore the legacy
/agent/[id]route as a compatibility shim so old deep links (without a daemon id) keep working while we transition the UI.- Added
packages/app/src/app/agent/[id].tsx, which scans the session directory for the requested agent, auto-redirects when there’s a single match, and lets the user choose when multiple daemons share that id; registered the screen in_layout.tsxand re-rannpm run typecheck --workspace=@paseo/app.
- Added
- Keep background agent actions from swapping the active daemon just to open the action sheet or delete.
AgentListstill calledsetActiveDaemonIdon long-press and before deletion (packages/app/src/components/agent-list.tsx:19-63), so managing a background daemon’s agent on Home would tear down the active websocket/realtime session. Removed those calls and rely onuseDaemonSessionso the aggregated view no longer hijacks the global session for contextual actions.
- Allow guarded screens to opt out of the
useDaemonSessionalert so offline placeholders don’t trigger duplicate system popups.- Added a
suppressUnavailableAlertoption touseDaemonSession, had Agent, Git diff, and File Explorer guards opt in so they only render their inline placeholders, and rannpm run typecheck --workspace=@paseo/app.
- Added a
- Force the root
SessionProviderto remount whenever the active daemon changes so session state never leaks between servers.- Added
key={activeDaemon.id}in_layout.tsx, ensuring each daemon gets a fresh session tree so cached agents/permissions don’t show up under the wrong daemon while switching routes.
- Added
3. Agent Creation & Lifecycle Actions
- Remove the server selector from the home header; inside the Create/Import modal replace the current “session swap” approach with an explicit
serverIdprop that simply determines which daemon receives the mutation.- Create/Import modals now accept a
serverIdprop, stop mutating the active daemon, and every caller (home screen, footer, agent view) passes the daemon id they’re operating against; rannpm run typecheck.
- Create/Import modals now accept a
- Prevent
useDaemonSessionfrom throwing during modal render when a daemon is offline—surface that state inside the UI (disabled create button + inline error) and keep the chip selection responsive.- Currently selecting a daemon chip whose session isn’t connected (e.g., auto-connect disabled or still initializing) crashes
CreateAgentModalbecauseuseDaemonSessionrethrows immediately; we need to gate the selection and show an inline “connect first” state instead of exploding. - Confirmed this is still happening:
CreateAgentModalpasses the selectedserverIdstraight intouseDaemonSession(packages/app/src/components/create-agent-modal.tsx:233), so tapping an offline daemon chip kills the modal before we can render error UI. - Updated
CreateAgentModalto read sessions from the directory, block websocket actions while the target daemon is offline, surface a daemon availability warning, and disable create/import flows until the daemon connects; rannpm run typecheck.
- Currently selecting a daemon chip whose session isn’t connected (e.g., auto-connect disabled or still initializing) crashes
- When creating/resuming/cloning agents, route follow-up navigation and queued requests to the daemon returned in the success payload rather than assuming the active daemon changed.
CreateAgentModalnow callssetActiveDaemonIdwith the server from the success payload before pushing the agent route, so the globalSessionProvider/RealtimeProviderswaps to the correct daemon and realtime controls no longer stay bound to the previous server (seepackages/app/src/components/create-agent-modal.tsx:210-220andpackages/app/src/components/create-agent-modal.tsx:845-864); re-rannpm run typecheck.
- Block Create/Import repo + snapshot fetches when the selected daemon is offline so we surface the availability error instead of spinning forever.
- Guarded
requestRepoInfo/requestImportCandidateswith the daemon availability signal so offline sessions now surfacedaemonAvailabilityErrorimmediately instead of issuingws.send, then added the missingsheetDeleteTextDisabledstyle inpackages/app/src/components/agent-list.tsxto clear the lingeringnpm run typecheck --workspace=@paseo/appfailure.
- Guarded
- Restore the Import Agent flow (modal entry, mutation wiring, navigation) without undoing the multi-daemon routing work from previous steps.
- After reworking Home/Header and the modals, the import trigger disappeared and existing deep links no longer reach a functioning flow. Bring the Import CTA back (Home, footer, agent screen), ensure it accepts a daemon id, and verify the import mutation routes to the selected daemon without regressing the new server-aware navigation.
- Rewired the import buttons across Home (header + empty state with deep-link auto open), the global footer, and the agent action menu so every trigger passes the correct daemon id into
ImportAgentModal, and rannpm run typecheck --workspace=@paseo/app.
4. Connection State & Persistence
- Stop the Settings daemon list from firing “Daemon unavailable” alerts when background daemons are offline by reading session snapshots via
useSessionForServerand only performing restart/test flows when a session is actually mounted.- Updated
packages/app/src/app/settings.tsxto rely onuseSessionForServerfor both the active daemon and eachDaemonCard, so offline entries no longer calluseDaemonSession(which showed alerts) andnpm run typecheckstill passes.
- Updated
- Introduce React Query (or a similar observable store) around AsyncStorage-backed registries (
DaemonRegistryProvider,DaemonConnectionsProvider, app settings) so callers get loading/error states without bespoke hooks.- Added
@tanstack/react-querywith a root provider, refactored the daemon registry, connections, and app settings to load/persist via cached queries (surfacing shared loading/error states) and verified everything withnpm run typecheck --workspace=@paseo/app.
- Added
- Add background reconnection + exponential backoff per daemon; surface “connecting/offline/last error” indicators in settings and home.
- WebSocket sessions now retry with exponential backoff and feed precise status/error metadata into the daemon connection store, the home screen shows a connection health banner, settings display colored status badges plus last errors for every daemon, and
npm run typecheck --workspace=@paseo/apppasses.
- WebSocket sessions now retry with exponential backoff and feed precise status/error metadata into the daemon connection store, the home screen shows a connection health banner, settings display colored status badges plus last errors for every daemon, and
- Persist the last successful session snapshot per daemon so the UI can hydrate agent lists immediately while a websocket reconnects.
- SessionProviders now hydrate agents/permissions/commands from the last stored
session_statesnapshot, persist new snapshots to AsyncStorage per daemon, andnpm run typecheck --workspace=@paseo/apppasses.
- SessionProviders now hydrate agents/permissions/commands from the last stored
- Standardize request/response handling behind a shared hook (React-Query style states for idle/loading/success/error, request dedupe, retries, timeouts) and document how daemon-facing components consume it.
- Added
useDaemonRequestwith deduped execution, timeout/retry controls, and React Query-style metadata plus wrotedocs/daemon-request-hook.mddescribing how daemon clients consume it; rannpm run typecheck --workspace=@paseo/app.
- Added
- Replace ad-hoc websocket request flows (git info, permission responses, diff/file fetches, etc.) with the new hook so every async action exposes consistent status + cancellation semantics.
- Adopted
useDaemonRequestfor repo-inspection modals, permission cards, git diff, and file explorer interactions (with inline loading/error states) and re-rannpm run typecheck --workspace=@paseo/app.
- Adopted
5. Performance & UX Polish
- Ensure agent image attachments preserve MIME metadata and are base64 encoded before hitting the daemon.
packages/app/src/contexts/session-context.tsx:1198now accepts{ uri, mimeType }attachments and reads them viaexpo-file-system, andpackages/app/src/components/agent-input-area.tsx:140forwards the stored metadata so queued sends no longer drop screenshots (npm run typecheckpasses).
- Profile the Create Agent modal—debounce expensive effects (e.g., provider model fetches) per server and prefetch metadata when daemons are idle to eliminate the visible lag when switching targets.
- Audit websocket usage so background
SessionProviders never duplicate connections for the active daemon (one live connection per daemon id). - Enforce “impossible states are impossible” across UI/data models (strict typing, discriminated unions, exhaustive switches) so complex flows remain clean without relying on Expo E2E tests.
6. Observability & Tooling
- Add structured logging for daemon connection lifecycle (connect, error, auto-connect skip) so we can diagnose “multi daemon” issues from device logs.
- Emit analytics when users create/resume agents on background daemons, attempt actions while those daemons are offline, or switch default daemons—helps prioritize reconnection UX.
- Document the architecture in
docs/multi-daemon.md(registry, sessions, routing rules) so future contributors understand how to extend it. - Land the accumulated multi-daemon changes in source control with a clean commit (linted, type-checked, plan updated).
Review
- 2025-11-26 Reviewer sanity check for the recent multi-daemon rollout work.
- Confirmed the new
useDaemonSessionhook, guarded Agent/Git Diff/File Explorer screens, and server-aware Create/Import flows align with the documented fixes; no regressions or missing follow-ups spotted, so no additional tasks were opened.
- Confirmed the new
- 2025-11-26 Reviewer follow-up on session isolation across daemons.
- Found that the active
SessionProviderkept its React state when switching daemons, so stale agents/permissions could leak between server contexts; fixed by keying the provider in_layout.tsxso the tree remounts on each daemon change.
- Found that the active