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
+ );
}