Refine agent list attention handling

This commit is contained in:
Mohamed Boudra
2026-03-08 15:39:40 +07:00
parent 03e1915316
commit faa5aaab6f
21 changed files with 1178 additions and 390 deletions
@@ -6,7 +6,7 @@ import {
Platform,
BackHandler,
} from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import { useFocusEffect, useIsFocused } from "@react-navigation/native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useQueryClient } from "@tanstack/react-query";
import ReanimatedAnimated from "react-native-reanimated";
@@ -55,7 +55,7 @@ import {
} from "@/utils/agent-snapshots";
import { mergePendingCreateImages } from "@/utils/pending-create-images";
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
import { shouldClearAgentAttention } from "@/utils/agent-attention";
import { useAgentAttentionClear } from "@/hooks/use-agent-attention-clear";
import type { DaemonClient } from "@server/client/daemon-client";
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
import type { ExplorerCheckoutContext } from "@/stores/panel-store";
@@ -431,9 +431,7 @@ function AgentScreenContent({
const hasSession = useSessionStore(
(state) => Boolean(state.sessions[serverId])
);
const focusedAgentId = useSessionStore(
(state) => state.sessions[serverId]?.focusedAgentId ?? null
);
const isScreenFocused = useIsFocused();
const { ensureAgentIsInitialized } = useAgentInitialization({
serverId,
client: hasSession ? client : null,
@@ -447,8 +445,6 @@ function AgentScreenContent({
routeKey: string;
reason: "initial-entry" | "resume";
} | null>(null);
const attentionClientRef = useRef(client);
const attentionConnectedRef = useRef(isConnected);
const setFocusedAgentId = useCallback(
(agentId: string | null) => {
useSessionStore.getState().setFocusedAgentId(serverId, agentId);
@@ -456,10 +452,14 @@ function AgentScreenContent({
[serverId]
);
useEffect(() => {
attentionClientRef.current = client;
attentionConnectedRef.current = isConnected;
}, [client, isConnected]);
const attentionController = useAgentAttentionClear({
agentId: resolvedAgentId,
client,
isConnected,
requiresAttention: agent?.requiresAttention,
attentionReason: agent?.attentionReason,
isScreenFocused,
});
const { style: animatedKeyboardStyle } = useKeyboardShiftStyle({
mode: "translate",
@@ -534,33 +534,20 @@ function AgentScreenContent({
isArchivingAgent({ serverId, agentId: resolvedAgentId })
);
useEffect(() => {
if (!resolvedAgentId) {
setFocusedAgentId(null);
return;
}
setFocusedAgentId(resolvedAgentId);
return () => {
const latestClient = attentionClientRef.current;
const latestAgent = useSessionStore
.getState()
.sessions[serverId]
?.agents.get(resolvedAgentId);
if (
latestClient &&
shouldClearAgentAttention({
agentId: resolvedAgentId,
isConnected: attentionConnectedRef.current,
requiresAttention: latestAgent?.requiresAttention,
attentionReason: latestAgent?.attentionReason,
})
) {
latestClient.clearAgentAttention(resolvedAgentId);
useFocusEffect(
useCallback(() => {
if (!resolvedAgentId) {
setFocusedAgentId(null);
return;
}
setFocusedAgentId(null);
};
}, [resolvedAgentId, serverId, setFocusedAgentId]);
setFocusedAgentId(resolvedAgentId);
return () => {
attentionController.clearOnAgentBlur();
setFocusedAgentId(null);
};
}, [attentionController, resolvedAgentId, setFocusedAgentId])
);
const isInitializing = resolvedAgentId ? isInitializingFromMap !== false : false;
const isHistorySyncing = useMemo(() => {
@@ -956,6 +943,8 @@ function AgentScreenContent({
serverId={serverId}
autoFocus
isSubmitLoading={showPendingCreateSubmitLoading}
onAttentionInputFocus={attentionController.clearOnInputFocus}
onAttentionPromptSend={attentionController.clearOnPromptSend}
onAddImages={handleAddImagesCallback}
onComposerHeightChange={() =>
streamViewRef.current?.prepareForViewportChange()
+2 -1
View File
@@ -10,6 +10,7 @@ import { buildHostRootRoute } from "@/utils/host-routes";
export function AgentsScreen({ serverId }: { serverId: string }) {
const { agents, isRevalidating, refreshAll } = useAllAgentsList({
serverId,
includeArchived: true,
});
// Track user-initiated refresh to avoid showing spinner on background revalidation
@@ -43,7 +44,7 @@ export function AgentsScreen({ serverId }: { serverId: string }) {
return (
<View style={styles.container}>
<BackHeader
title="All agents"
title="Sessions"
onBack={() => router.replace(buildHostRootRoute(serverId) as any)}
/>
<AgentList
@@ -195,7 +195,7 @@ export function WorkspaceDesktopTabsRow({
maxWidth: resolvedTabWidth,
},
isActive && styles.tabActive,
(hovered || pressed || isCloseHovered) && styles.tabHovered,
!isActive && (hovered || pressed || isCloseHovered) && styles.tabHovered,
]}
onHoverIn={() => {
setHoveredTabKey(tab.key);
@@ -273,11 +273,19 @@ export function WorkspaceDesktopTabsRow({
(hovered || pressed) && styles.tabCloseButtonActive,
]}
>
{isClosingTab ? (
<ActivityIndicator size={12} color={theme.colors.foregroundMuted} />
) : (
<X size={12} color={theme.colors.foregroundMuted} />
)}
{({ hovered, pressed }) =>
isClosingTab ? (
<ActivityIndicator
size={12}
color={hovered || pressed ? theme.colors.foreground : theme.colors.foregroundMuted}
/>
) : (
<X
size={12}
color={hovered || pressed ? theme.colors.foreground : theme.colors.foregroundMuted}
/>
)
}
</Pressable>
) : null}
</ContextMenuTrigger>
@@ -459,7 +467,7 @@ const styles = StyleSheet.create((theme) => ({
flexShrink: 0,
},
tabActive: {
backgroundColor: theme.colors.surface2,
backgroundColor: theme.colors.surface3,
},
tabHovered: {
backgroundColor: theme.colors.surface2,