From 4f00e0728449281603e974c919f80bf91ba35b28 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 26 Nov 2025 19:02:47 +0100 Subject: [PATCH] Eliminate hidden host defaults --- .../app/src/components/create-agent-modal.tsx | 39 ++++++++++++------- .../app/src/hooks/use-aggregated-agents.ts | 16 +------- plan.md | 9 ++++- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index cdae03882..bbd57bc56 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -218,10 +218,11 @@ function AgentFlowModal({ const { connectionStates } = useDaemonConnections(); const daemonEntries = useMemo(() => Array.from(connectionStates.values()), [connectionStates]); const initialServerId = useMemo(() => { - if (serverId) { - return serverId; + if (!serverId) { + return null; } - return daemonEntries[0]?.daemon.id ?? null; + const exists = daemonEntries.some((entry) => entry.daemon.id === serverId); + return exists ? serverId : null; }, [serverId, daemonEntries]); const [selectedServerId, setSelectedServerId] = useState(initialServerId); const selectedSession = useSessionForServer(selectedServerId); @@ -229,21 +230,26 @@ function AgentFlowModal({ const sessionDirectory = useSessionDirectory(); useEffect(() => { - if (!selectedServerId) { - setSelectedServerId(initialServerId); + if (selectedServerId) { + const exists = daemonEntries.some((entry) => entry.daemon.id === selectedServerId); + if (!exists) { + setSelectedServerId(initialServerId); + } return; } - const exists = daemonEntries.some((entry) => entry.daemon.id === selectedServerId); - if (!exists) { + if (initialServerId && selectedServerId !== initialServerId) { setSelectedServerId(initialServerId); } }, [daemonEntries, selectedServerId, initialServerId]); useEffect(() => { - if (isVisible && initialServerId && selectedServerId !== initialServerId) { + if (!isVisible) { + return; + } + if (initialServerId && selectedServerId !== initialServerId) { setSelectedServerId(initialServerId); } - }, [isVisible, initialServerId]); + }, [isVisible, initialServerId, selectedServerId]); const ws = session?.ws ?? null; const inertWebSocket = useMemo( @@ -323,15 +329,18 @@ function AgentFlowModal({ selectedDaemonId ?? "Selected host"; const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus); + const hasSelectedDaemon = Boolean(selectedServerId); const selectedDaemonIsUnavailable = !session || selectedDaemonStatus !== "online" || !ws?.isConnected; const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim(); - const daemonAvailabilityError = selectedDaemonIsUnavailable - ? `${selectedDaemonLabel} is ${selectedDaemonStatusLabel}. Connect to it before creating or importing agents.${ - selectedDaemonLastError ? ` ${selectedDaemonLastError}` : "" - }` - : null; - const isTargetDaemonReady = Boolean(session && !selectedDaemonIsUnavailable); + const daemonAvailabilityError = !hasSelectedDaemon + ? "Select a host before creating or importing agents." + : selectedDaemonIsUnavailable + ? `${selectedDaemonLabel} is ${selectedDaemonStatusLabel}. Connect to it before creating or importing agents.${ + selectedDaemonLastError ? ` ${selectedDaemonLastError}` : "" + }` + : null; + const isTargetDaemonReady = Boolean(hasSelectedDaemon && session && !selectedDaemonIsUnavailable); const [isMounted, setIsMounted] = useState(isVisible); const [workingDir, setWorkingDir] = useState(""); diff --git a/packages/app/src/hooks/use-aggregated-agents.ts b/packages/app/src/hooks/use-aggregated-agents.ts index 117eeb26a..1aab15b39 100644 --- a/packages/app/src/hooks/use-aggregated-agents.ts +++ b/packages/app/src/hooks/use-aggregated-agents.ts @@ -9,8 +9,6 @@ export interface AggregatedAgentGroup { agents: Agent[]; } -const EMPTY_AGENT_MAP: Map = new Map(); - const sortAgents = (agents: Agent[]): Agent[] => { return [...agents].sort((left, right) => { const leftRunning = left.status === "running"; @@ -27,10 +25,9 @@ const sortAgents = (agents: Agent[]): Agent[] => { }); }; -export function useAggregatedAgents(fallbackAgents?: Map): AggregatedAgentGroup[] { +export function useAggregatedAgents(): AggregatedAgentGroup[] { const { connectionStates } = useDaemonConnections(); const sessionDirectory = useSessionDirectory(); - const fallback = fallbackAgents ?? EMPTY_AGENT_MAP; return useMemo(() => { const groups = new Map(); @@ -58,15 +55,6 @@ export function useAggregatedAgents(fallbackAgents?: Map): Aggreg }); } - if (groups.size === 0 && fallback.size > 0) { - const fallbackServerId = connectionStates.keys().next().value ?? "default"; - const label = connectionStates.get(fallbackServerId)?.daemon.label ?? fallbackServerId; - const fallbackList = Array.from(fallback.values()); - if (fallbackList.length > 0) { - groups.set(fallbackServerId, { serverLabel: label, agents: fallbackList }); - } - } - const aggregatedGroups = Array.from(groups.entries()).map(([serverId, { serverLabel, agents }]) => ({ serverId, serverLabel, @@ -89,5 +77,5 @@ export function useAggregatedAgents(fallbackAgents?: Map): Aggreg }); return aggregatedGroups; - }, [sessionDirectory, fallback, connectionStates]); + }, [sessionDirectory, connectionStates]); } diff --git a/plan.md b/plan.md index 8ae3dee8d..18ce76079 100644 --- a/plan.md +++ b/plan.md @@ -35,7 +35,8 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope - Removed the legacy autoConnect state from daemon profiles and session hosts so every host hydrates automatically, refreshed the host-unavailable messaging/docs, and verified via `npm run typecheck --workspace=@paseo/app`. ### Review: No Silent Defaults -- [ ] Review the changes to ensure we didn't just replace "active daemon" with "first host" (e.g., `hosts[0]}`. The goal is explicit user choice, not a hidden default. +- [x] Review the changes to ensure we didn't just replace "active daemon" with "first host" (e.g., `hosts[0]}`). The goal is explicit user choice, not a hidden default. + - Create/import modal now requires explicit host selection (no first entry default) and `useAggregatedAgents` no longer fabricates a host id via `connectionStates.keys().next()`; verified with `npm run typecheck --workspace=@paseo/app`. ### 3. Simplify Settings Screen - [ ] Remove the standalone "Test Connection" form/URL input at the top of settings. @@ -50,6 +51,12 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope - [ ] A stopped/disconnected host is NOT an error—don't show error states on home screen just because a host is offline. - [ ] Only show errors when the user tries to interact with an agent whose host is stopped. - [ ] Update connection banners/indicators to show neutral "offline" state instead of error styling. +- [ ] Make the Git Diff offline/unavailable state neutral and stop instructing users to "connect" manually. + - Context: `packages/app/src/app/git-diff.tsx:233-241` still renders the destructive error container and tells users "Connect this host or switch to another one to continue," violating the auto-connect guidance. +- [ ] Update the File Explorer offline state to match the new philosophy (neutral messaging, no manual connect CTA). + - Context: `packages/app/src/app/file-explorer.tsx:690-698` presents the offline host as an error and asks users to "Connect this host and try again." +- [ ] Audit the agent detail flows for the same issue (offline agent screen + delete sheet) and replace the "connect this host" requirement with passive/offline messaging. + - Context: `packages/app/src/app/agent/[serverId]/[agentId].tsx:651-660` and `packages/app/src/components/agent-list.tsx:149` continue to show destructive states directing users to manually connect before managing agents. ### 6. Agent Creation Flow - [ ] Remove reliance on "primary" or "active" daemon for agent creation.