diff --git a/packages/app/src/app/agent/[serverId]/[agentId].tsx b/packages/app/src/app/agent/[serverId]/[agentId].tsx index cfa8ad636..05b8fd10a 100644 --- a/packages/app/src/app/agent/[serverId]/[agentId].tsx +++ b/packages/app/src/app/agent/[serverId]/[agentId].tsx @@ -82,21 +82,9 @@ export default function AgentScreen() { const { serverId, agentId } = useLocalSearchParams<{ serverId: string; agentId: string }>(); const resolvedAgentId = typeof agentId === "string" ? agentId : undefined; const resolvedServerId = typeof serverId === "string" ? serverId : undefined; - const { connectionStates, activeDaemonId, setActiveDaemonId } = useDaemonConnections(); + const { connectionStates } = useDaemonConnections(); const hasConnectionEntry = resolvedServerId ? connectionStates.has(resolvedServerId) : false; - useEffect(() => { - if (!resolvedServerId || !hasConnectionEntry) { - return; - } - - if (activeDaemonId === resolvedServerId) { - return; - } - - setActiveDaemonId(resolvedServerId, { source: "agent_route" }); - }, [resolvedServerId, hasConnectionEntry, activeDaemonId, setActiveDaemonId]); - const session = useDaemonSession(resolvedServerId, { suppressUnavailableAlert: true, allowUnavailable: true, @@ -644,7 +632,7 @@ function AgentSessionUnavailableState({ Cannot open this agent because {serverLabel} is not configured on this device. - Add the daemon in Settings or open an agent on a configured server to continue. + Add the host in Settings or open an agent on a configured server to continue. @@ -661,7 +649,7 @@ function AgentSessionUnavailableState({ <> Connecting to {serverLabel}... - We will show this agent once the daemon is online. + We will show this agent once the host is online. ) : ( <> @@ -669,7 +657,7 @@ function AgentSessionUnavailableState({ Cannot open this agent while {serverLabel} is {connectionStatusLabel.toLowerCase()}. - Connect this daemon or switch to another one to continue. + Connect this host or switch to another one to continue. {lastError ? {lastError} : null} diff --git a/packages/app/src/app/index.tsx b/packages/app/src/app/index.tsx index b766d4ab8..d25aeca01 100644 --- a/packages/app/src/app/index.tsx +++ b/packages/app/src/app/index.tsx @@ -18,7 +18,7 @@ export default function HomeScreen() { const { theme } = useUnistyles(); const insets = useSafeAreaInsets(); const { agents } = useSession(); - const { activeDaemonId, connectionStates } = useDaemonConnections(); + const { connectionStates } = useDaemonConnections(); const aggregatedAgents = useAggregatedAgents(agents); const [showCreateModal, setShowCreateModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false); @@ -63,15 +63,11 @@ export default function HomeScreen() { setShowCreateModal(true); }, []); - const openImportModal = useCallback( - (serverIdOverride?: string | null) => { - const resolvedServerId = serverIdOverride ?? activeDaemonId ?? null; - setPendingImportServerId(resolvedServerId); - setImportModalMounted(true); - setShowImportModal(true); - }, - [activeDaemonId] - ); + const openImportModal = useCallback((serverIdOverride?: string | null) => { + setPendingImportServerId(serverIdOverride ?? null); + setImportModalMounted(true); + setShowImportModal(true); + }, []); const handleImportAgent = useCallback(() => { openImportModal(); @@ -166,14 +162,13 @@ export default function HomeScreen() { ) : null} {importModalMounted ? ( ) : null} diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx index 36780dc58..a4adc75b8 100644 --- a/packages/app/src/components/agent-list.tsx +++ b/packages/app/src/components/agent-list.tsx @@ -6,7 +6,6 @@ import type { Agent } from "@/contexts/session-context"; import { formatTimeAgo } from "@/utils/time"; import { getAgentStatusColor, getAgentStatusLabel } from "@/utils/agent-status"; import { getAgentProviderDefinition } from "@server/server/agent/provider-manifest"; -import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { type AggregatedAgentGroup } from "@/hooks/use-aggregated-agents"; import { useDaemonSession } from "@/hooks/use-daemon-session"; @@ -16,7 +15,6 @@ interface AgentListProps { export function AgentList({ agentGroups }: AgentListProps) { const { theme } = useUnistyles(); - const { setActiveDaemonId } = useDaemonConnections(); const [actionAgent, setActionAgent] = useState(null); const [actionAgentServerId, setActionAgentServerId] = useState(null); const actionSession = useDaemonSession(actionAgentServerId ?? undefined, { @@ -28,19 +26,21 @@ export function AgentList({ agentGroups }: AgentListProps) { const isActionSheetVisible = actionAgent !== null; const isActionDaemonUnavailable = Boolean(actionAgentServerId && !actionSession); - const handleAgentPress = useCallback((serverId: string, agentId: string) => { - if (isActionSheetVisible) { - return; - } - setActiveDaemonId(serverId, { source: "agent_row_press" }); - router.push({ - pathname: "/agent/[serverId]/[agentId]", - params: { - serverId, - agentId, - }, - }); - }, [isActionSheetVisible, setActiveDaemonId]); + const handleAgentPress = useCallback( + (serverId: string, agentId: string) => { + if (isActionSheetVisible) { + return; + } + router.push({ + pathname: "/agent/[serverId]/[agentId]", + params: { + serverId, + agentId, + }, + }); + }, + [isActionSheetVisible] + ); const handleAgentLongPress = useCallback((serverId: string, agent: Agent) => { setActionAgentServerId(serverId); diff --git a/packages/app/src/components/create-agent-modal.tsx b/packages/app/src/components/create-agent-modal.tsx index c9a4414e0..a04878ee8 100644 --- a/packages/app/src/components/create-agent-modal.tsx +++ b/packages/app/src/components/create-agent-modal.tsx @@ -215,17 +215,14 @@ function AgentFlowModal({ const isCreateFlow = !isImportFlow; const { recentPaths, addRecentPath } = useRecentPaths(); - const { connectionStates, activeDaemonId, setActiveDaemonId } = useDaemonConnections(); + const { connectionStates } = useDaemonConnections(); const daemonEntries = useMemo(() => Array.from(connectionStates.values()), [connectionStates]); const initialServerId = useMemo(() => { if (serverId) { return serverId; } - if (activeDaemonId) { - return activeDaemonId; - } return daemonEntries[0]?.daemon.id ?? null; - }, [serverId, activeDaemonId, daemonEntries]); + }, [serverId, daemonEntries]); const [selectedServerId, setSelectedServerId] = useState(initialServerId); const activeSession = useSession(); const selectedSession = useSessionForServer(selectedServerId); @@ -308,8 +305,8 @@ function AgentFlowModal({ } = gitRepoInfoRequest; const isWsConnected = ws?.isConnected ?? false; const router = useRouter(); - const activeServerId = session?.serverId ?? null; - const selectedDaemonId = selectedServerId ?? activeServerId; + const sessionServerId = session?.serverId ?? null; + const selectedDaemonId = selectedServerId ?? sessionServerId; const selectedDaemonConnection = selectedDaemonId ? connectionStates.get(selectedDaemonId) : null; @@ -330,8 +327,6 @@ function AgentFlowModal({ const selectedDaemonStatusLabel = formatConnectionStatus(selectedDaemonStatus); const selectedDaemonIsUnavailable = !session || selectedDaemonStatus !== "online" || !ws?.isConnected; - const isBackgroundDaemonTarget = - Boolean(selectedDaemonId && activeDaemonId && selectedDaemonId !== activeDaemonId); const selectedDaemonLastError = selectedDaemonConnection?.lastError?.trim(); const daemonAvailabilityError = selectedDaemonIsUnavailable ? `${selectedDaemonLabel} is ${selectedDaemonStatusLabel}. Connect to it before creating or importing agents.${ @@ -407,6 +402,7 @@ function AgentFlowModal({ typeof InteractionManager.runAfterInteractions > | null>(null); const pendingNavigationAgentIdRef = useRef(null); + const pendingNavigationServerIdRef = useRef(null); const openDropdownSheet = useCallback((key: DropdownKey) => { setOpenDropdown(key); }, []); @@ -862,45 +858,17 @@ function AgentFlowModal({ type: "offline_daemon_action_attempt", action, daemonId: selectedDaemonId, - activeDaemonId, status: selectedDaemonStatus ?? null, - isBackground: isBackgroundDaemonTarget, reason: reason ?? daemonAvailabilityError, }); }, [ - activeDaemonId, daemonAvailabilityError, - isBackgroundDaemonTarget, selectedDaemonId, selectedDaemonStatus, ] ); - const logBackgroundAgentAction = useCallback( - ( - action: "create" | "resume", - payload: { cwd?: string; provider?: string; modeId?: string; model?: string; baseBranch?: string } - ) => { - if (!isBackgroundDaemonTarget || !selectedDaemonId) { - return; - } - trackAnalyticsEvent({ - type: "background_agent_action", - action, - daemonId: selectedDaemonId, - activeDaemonId, - isBackground: true, - cwd: payload.cwd, - provider: payload.provider, - modeId: payload.modeId, - model: payload.model, - baseBranch: payload.baseBranch, - }); - }, - [activeDaemonId, isBackgroundDaemonTarget, selectedDaemonId] - ); - const resetFormState = useCallback(() => { setInitialPrompt(""); promptBaselineHeightRef.current = null; @@ -918,6 +886,7 @@ function AgentFlowModal({ resetRepoInfo(); setOpenDropdown(null); pendingRequestIdRef.current = null; + pendingNavigationServerIdRef.current = null; shouldSyncBaseBranchRef.current = true; dictationRequestIdRef.current = null; setIsDictating(false); @@ -1024,24 +993,23 @@ function AgentFlowModal({ const navigateToAgentIfNeeded = useCallback(() => { const agentId = pendingNavigationAgentIdRef.current; - if (!agentId || !activeServerId) { + const targetServerId = pendingNavigationServerIdRef.current ?? selectedDaemonId; + if (!agentId || !targetServerId) { return; } pendingNavigationAgentIdRef.current = null; - if (activeDaemonId !== activeServerId) { - setActiveDaemonId(activeServerId, { source: "agent_create_resume_nav" }); - } + pendingNavigationServerIdRef.current = null; InteractionManager.runAfterInteractions(() => { router.push({ pathname: "/agent/[serverId]/[agentId]", params: { - serverId: activeServerId, + serverId: targetServerId, agentId, }, }); }); - }, [activeDaemonId, activeServerId, router, setActiveDaemonId]); + }, [router, selectedDaemonId]); const handleSelectServer = useCallback((serverId: string) => { setSelectedServerId(serverId); @@ -1408,6 +1376,7 @@ function AgentFlowModal({ const requestId = generateMessageId(); pendingRequestIdRef.current = requestId; + pendingNavigationServerIdRef.current = selectedDaemonId ?? null; setIsLoading(true); setErrorMessage(""); @@ -1443,14 +1412,6 @@ function AgentFlowModal({ } : undefined; - logBackgroundAgentAction("create", { - cwd: trimmedPath, - provider: selectedProvider, - modeId, - model: trimmedModel, - baseBranch: trimmedBaseBranch || undefined, - }); - try { createAgent({ config, @@ -1463,6 +1424,7 @@ function AgentFlowModal({ setErrorMessage("Failed to create agent. Please try again."); setIsLoading(false); pendingRequestIdRef.current = null; + pendingNavigationServerIdRef.current = null; } }, [ workingDir, @@ -1475,7 +1437,6 @@ function AgentFlowModal({ repoInfo, selectedMode, modeOptions, - logBackgroundAgentAction, logOfflineDaemonAction, selectedProvider, isLoading, @@ -1484,6 +1445,7 @@ function AgentFlowModal({ createAgent, daemonAvailabilityError, isTargetDaemonReady, + selectedDaemonId, ]); const handleImportCandidatePress = useCallback( @@ -1499,13 +1461,10 @@ function AgentFlowModal({ ); return; } - logBackgroundAgentAction("resume", { - cwd: candidate.cwd, - provider: candidate.provider, - }); setErrorMessage(""); const requestId = generateMessageId(); pendingRequestIdRef.current = requestId; + pendingNavigationServerIdRef.current = selectedDaemonId ?? null; setIsLoading(true); resumeAgent({ handle: candidate.persistence, @@ -1515,10 +1474,10 @@ function AgentFlowModal({ [ daemonAvailabilityError, isLoading, - logBackgroundAgentAction, logOfflineDaemonAction, isTargetDaemonReady, resumeAgent, + selectedDaemonId, setImportError, ] ); @@ -1603,6 +1562,7 @@ function AgentFlowModal({ return; } pendingRequestIdRef.current = null; + pendingNavigationServerIdRef.current = null; setIsLoading(false); setErrorMessage( payload.error ?? diff --git a/packages/app/src/components/global-footer.tsx b/packages/app/src/components/global-footer.tsx index e1a60484c..afd48c014 100644 --- a/packages/app/src/components/global-footer.tsx +++ b/packages/app/src/components/global-footer.tsx @@ -7,7 +7,6 @@ import { AudioLines, Users, Plus, Download } from "lucide-react-native"; import { useRealtime } from "@/contexts/realtime-context"; import { useSession } from "@/contexts/session-context"; import { useFooterControls, FOOTER_HEIGHT } from "@/contexts/footer-controls-context"; -import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { RealtimeControls } from "./realtime-controls"; import { CreateAgentModal, ImportAgentModal } from "./create-agent-modal"; import Animated, { @@ -24,7 +23,6 @@ export function GlobalFooter() { const router = useRouter(); const { isRealtimeMode, startRealtime } = useRealtime(); const { ws } = useSession(); - const { activeDaemonId } = useDaemonConnections(); const { controls } = useFooterControls(); const [showCreateModal, setShowCreateModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false); @@ -188,12 +186,10 @@ export function GlobalFooter() { setShowCreateModal(false)} - serverId={activeDaemonId} /> setShowImportModal(false)} - serverId={activeDaemonId} /> ); diff --git a/packages/app/src/hooks/use-aggregated-agents.ts b/packages/app/src/hooks/use-aggregated-agents.ts index 72f2bfb3e..a193a8083 100644 --- a/packages/app/src/hooks/use-aggregated-agents.ts +++ b/packages/app/src/hooks/use-aggregated-agents.ts @@ -26,9 +26,8 @@ const sortAgents = (agents: Agent[]): Agent[] => { }; export function useAggregatedAgents(fallbackAgents: Map): AggregatedAgentGroup[] { - const { activeDaemonId, connectionStates } = useDaemonConnections(); + const { connectionStates } = useDaemonConnections(); const sessionDirectory = useSessionDirectory(); - const activeServerId = activeDaemonId ?? "default"; return useMemo(() => { const groups = new Map(); @@ -57,10 +56,11 @@ export function useAggregatedAgents(fallbackAgents: Map): Aggrega } if (groups.size === 0) { - const label = connectionStates.get(activeServerId)?.daemon.label ?? activeServerId; + const fallbackServerId = connectionStates.keys().next().value ?? "default"; + const label = connectionStates.get(fallbackServerId)?.daemon.label ?? fallbackServerId; const fallbackList = Array.from(fallbackAgents.values()); if (fallbackList.length > 0) { - groups.set(activeServerId, { serverLabel: label, agents: fallbackList }); + groups.set(fallbackServerId, { serverLabel: label, agents: fallbackList }); } } @@ -86,5 +86,5 @@ export function useAggregatedAgents(fallbackAgents: Map): Aggrega }); return aggregatedGroups; - }, [sessionDirectory, fallbackAgents, activeServerId, connectionStates]); + }, [sessionDirectory, fallbackAgents, connectionStates]); } diff --git a/packages/app/src/utils/analytics.ts b/packages/app/src/utils/analytics.ts index 265bf6855..1b0383e1c 100644 --- a/packages/app/src/utils/analytics.ts +++ b/packages/app/src/utils/analytics.ts @@ -1,4 +1,3 @@ -type BackgroundAgentAction = "create" | "resume"; type OfflineAction = | "create" | "resume" @@ -12,25 +11,11 @@ type AnalyticsEvent = previousDaemonId: string | null; source?: string; } - | { - type: "background_agent_action"; - action: BackgroundAgentAction; - daemonId: string; - activeDaemonId: string | null; - isBackground: boolean; - cwd?: string; - provider?: string; - modeId?: string; - model?: string; - baseBranch?: string; - } | { type: "offline_daemon_action_attempt"; action: OfflineAction; daemonId: string | null; - activeDaemonId: string | null; status: string | null; - isBackground: boolean; reason?: string | null; }; diff --git a/plan.md b/plan.md index e05009198..9069a1c23 100644 --- a/plan.md +++ b/plan.md @@ -22,12 +22,16 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope ### 2. Remove Active/Primary/Auto-Connect Concepts - [x] Remove the concept of "active daemon" from the UI and simplify to just "hosts". - - Removed the Settings "Set Active" controls/labels, scrubbed the copy that mentioned an active host (including git diff fallback text), and left the per-host cards focused on status/default/test actions; no automated tests were run. -- [x] Remove the concept of "primary daemon"—no default host for actions. - - Removed the Default host toggle/state in the registry and settings UI, updated host docs, and kept connection fallback logic simple; tests not run (not requested). + - Summary: Home/footer actions, create/import flows, and agent navigation now route directly via explicit host IDs with analytics/docs/aggregated list updates, verified with `npm run typecheck --workspace=@paseo/app`. + - Context: `GlobalFooter`, the create/import modals, the agent screen, and the agent list previously read/write `activeDaemonId`, forcing a global active host before routing; they now rely on explicit host IDs instead. +- [ ] Remove the concept of "primary daemon"—no default host for actions. + - Context: `DaemonConnectionsProvider` still persists `activeDaemonId` in AsyncStorage and `MultiDaemonSessionHost` filters by `primary` roles to decide which hosts stay backgrounded, so the “primary/default” pipeline is intact and needs to be unwound. - [ ] Remove the "auto-connect" toggle from host settings—hosts always auto-connect when added. - [ ] Clean up any related state/UI that exposes these concepts to users. +### 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. + ### 3. Simplify Settings Screen - [ ] Remove the standalone "Test Connection" form/URL input at the top of settings. - [ ] Keep the per-host "Test" button in each host row (already exists). @@ -49,5 +53,5 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope ### 7. Home Screen Agent List - [ ] Remove grouping of agents by host—show a single flat list. -- [ ] Each agent row displays its host name as metadata (badge, subtitle, etc.). -- [ ] Sort agents by recent activity or alphabetically (not by host). + - [ ] Each agent row displays its host name as metadata (badge, subtitle, etc.). + - [ ] Sort agents by recent activity or alphabetically (not by host).