diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx index 2d66212a9..611fac200 100644 --- a/packages/app/src/components/agent-list.tsx +++ b/packages/app/src/components/agent-list.tsx @@ -9,7 +9,7 @@ import { type SectionListRenderItem, } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { useCallback, useMemo, useRef, useState, type ReactElement } from "react"; +import { useCallback, useMemo, useState, type ReactElement } from "react"; import { router, usePathname } from "expo-router"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useQueryClient } from "@tanstack/react-query"; @@ -81,6 +81,71 @@ function deriveDateSectionLabel(lastActivityAt: Date): string { return "Older"; } +interface AgentListRowProps { + agent: AggregatedAgent; + selectedAgentId?: string; + showCheckoutInfo: boolean; + onPress: (serverId: string, agentId: string) => void; + onLongPress: (agent: AggregatedAgent) => void; +} + +function AgentListRow({ + agent, + selectedAgentId, + showCheckoutInfo, + onPress, + onLongPress, +}: AgentListRowProps) { + const timeAgo = formatTimeAgo(agent.lastActivityAt); + const agentKey = `${agent.serverId}:${agent.id}`; + const isSelected = selectedAgentId === agentKey; + const checkoutQuery = useCheckoutStatusCacheOnly({ + serverId: agent.serverId, + cwd: agent.cwd, + }); + const checkout = checkoutQuery.data ?? null; + const projectPath = showCheckoutInfo + ? deriveProjectPath(agent.cwd, checkout) + : agent.cwd; + const branchLabel = showCheckoutInfo ? deriveBranchLabel(checkout) : null; + + return ( + [ + styles.agentItem, + isSelected && styles.agentItemSelected, + hovered && styles.agentItemHovered, + pressed && styles.agentItemPressed, + ]} + onPress={() => onPress(agent.serverId, agent.id)} + onLongPress={() => onLongPress(agent)} + testID={`agent-row-${agent.serverId}-${agent.id}`} + > + {({ hovered }) => ( + + + + + {agent.title || "New agent"} + + + + + {shortenPath(projectPath)} + {branchLabel ? ` · ${branchLabel}` : ""} · {timeAgo} + + + )} + + ); +} + export function AgentList({ agents, showCheckoutInfo = true, @@ -187,66 +252,6 @@ export function AgentList({ [queryClient, showCheckoutInfo] ); - const AgentListRow = useCallback( - ({ agent }: { agent: AggregatedAgent }) => { - const timeAgo = formatTimeAgo(agent.lastActivityAt); - const agentKey = `${agent.serverId}:${agent.id}`; - const isSelected = selectedAgentId === agentKey; - - const checkoutQuery = useCheckoutStatusCacheOnly({ - serverId: agent.serverId, - cwd: agent.cwd, - }); - const checkout = checkoutQuery.data ?? null; - const projectPath = showCheckoutInfo - ? deriveProjectPath(agent.cwd, checkout) - : agent.cwd; - const branchLabel = showCheckoutInfo ? deriveBranchLabel(checkout) : null; - - return ( - [ - styles.agentItem, - isSelected && styles.agentItemSelected, - hovered && styles.agentItemHovered, - pressed && styles.agentItemPressed, - ]} - onPress={() => handleAgentPress(agent.serverId, agent.id)} - onLongPress={() => handleAgentLongPress(agent)} - testID={`agent-row-${agent.serverId}-${agent.id}`} - > - {({ hovered }) => ( - - - - - {agent.title || "New agent"} - - - - - {shortenPath(projectPath)} - {branchLabel ? ` · ${branchLabel}` : ""} · {timeAgo} - - - )} - - ); - }, - [ - handleAgentLongPress, - handleAgentPress, - selectedAgentId, - showCheckoutInfo, - ] - ); - const sections = useMemo((): AgentListSection[] => { const order = ["Today", "Yesterday", "This week", "This month", "Older"] as const; const buckets = new Map(); @@ -269,7 +274,18 @@ export function AgentList({ }, [agents]); const renderAgentItem: SectionListRenderItem = - useCallback(({ item: agent }) => , [AgentListRow]); + useCallback( + ({ item: agent }) => ( + + ), + [handleAgentLongPress, handleAgentPress, selectedAgentId, showCheckoutInfo] + ); const renderSectionHeader = useCallback( ({ section }: { section: AgentListSection }) => ( diff --git a/packages/app/src/components/agent-status-bar.tsx b/packages/app/src/components/agent-status-bar.tsx index fb836c689..1da91015f 100644 --- a/packages/app/src/components/agent-status-bar.tsx +++ b/packages/app/src/components/agent-status-bar.tsx @@ -38,17 +38,19 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null); - if (!agent) { - return null; - } - - const canFetchModels = Boolean(client) && Boolean(agent.provider) && (IS_WEB || prefsOpen); + const canFetchModels = + Boolean(client) && Boolean(agent?.provider) && (IS_WEB || prefsOpen); const modelsQuery = useQuery({ - queryKey: ["providerModels", serverId, agent.provider, agent.cwd], + queryKey: [ + "providerModels", + serverId, + agent?.provider ?? "__missing_provider__", + agent?.cwd ?? "__missing_cwd__", + ], enabled: canFetchModels, staleTime: 5 * 60 * 1000, queryFn: async () => { - if (!client) { + if (!client || !agent) { throw new Error("Daemon client unavailable"); } const payload = await client.listProviderModels(agent.provider, { cwd: agent.cwd }); @@ -61,7 +63,7 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { const models = modelsQuery.data ?? null; function handleModeChange(modeId: string) { - if (!client) { + if (!client || !agent) { return; } void client.setAgentMode(agentId, modeId).catch((error) => { @@ -69,8 +71,8 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { }); } - const normalizedRuntimeModelId = normalizeModelId(agent.runtimeInfo?.model); - const normalizedConfiguredModelId = normalizeModelId(agent.model); + const normalizedRuntimeModelId = normalizeModelId(agent?.runtimeInfo?.model); + const normalizedConfiguredModelId = normalizeModelId(agent?.model); const preferredModelId = normalizedRuntimeModelId ?? normalizedConfiguredModelId; const selectedModel = useMemo(() => { if (!models || !preferredModelId) return null; @@ -82,7 +84,7 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { const thinkingOptions = selectedModel?.thinkingOptions ?? null; const explicitThinkingId = - agent.thinkingOptionId && agent.thinkingOptionId !== "default" + agent?.thinkingOptionId && agent.thinkingOptionId !== "default" ? agent.thinkingOptionId : null; const selectedThinkingId = @@ -93,10 +95,14 @@ export function AgentStatusBar({ agentId, serverId }: AgentStatusBarProps) { (selectedThinkingId === "default" ? "Model default" : selectedThinkingId ?? "auto"); const displayMode = - agent.availableModes?.find((m) => m.id === agent.currentModeId)?.label || - agent.currentModeId || + agent?.availableModes?.find((m) => m.id === agent.currentModeId)?.label || + agent?.currentModeId || "default"; + if (!agent) { + return null; + } + return ( {/* Agent Mode Badge (desktop only — on mobile, mode is in the preferences sheet) */} diff --git a/packages/app/src/components/file-explorer-pane.tsx b/packages/app/src/components/file-explorer-pane.tsx index 86fb80602..7a707c049 100644 --- a/packages/app/src/components/file-explorer-pane.tsx +++ b/packages/app/src/components/file-explorer-pane.tsx @@ -597,14 +597,6 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { }); }, [agentId, errorRecoveryPath, requestDirectoryListing, selectExplorerEntry]); - if (!agentExists) { - return ( - - Agent not found - - ); - } - const handleTreeListScroll = useCallback( (event: NativeSyntheticEvent) => { if (showDesktopWebScrollbar) { @@ -623,6 +615,14 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) { [showDesktopWebScrollbar, treeScrollbarMetrics] ); + if (!agentExists) { + return ( + + Agent not found + + ); + } + return ( ( selector?: SessionSelector, equalityFn?: EqualityFn ): SessionState | null | T { - const baseSelector = useCallback( - (state: ReturnType) => - (serverId ? state.sessions[serverId] ?? null : null), - [serverId] + const storeSelector = useCallback( + (state: ReturnType): SessionState | null | T => { + const session = serverId ? state.sessions[serverId] ?? null : null; + return selector ? selector(session) : session; + }, + [selector, serverId] ); - if (selector) { - const derivedSelector = useCallback( - (state: ReturnType) => selector(baseSelector(state)), - [baseSelector, selector] - ); - return useStoreWithEqualityFn(useSessionStore, derivedSelector, equalityFn); - } - - return useSessionStore(baseSelector); + return useStoreWithEqualityFn( + useSessionStore, + storeSelector, + equalityFn as EqualityFn + ); }