fix(agent): track initialization state to prevent duplicate agent initialization

This commit is contained in:
Mohamed Boudra
2025-10-26 16:28:55 +01:00
parent cf7e14e36b
commit 2c1b5b13fe
3 changed files with 70 additions and 8 deletions

View File

@@ -15,7 +15,14 @@ export default function AgentScreen() {
const { theme } = useUnistyles();
const insets = useSafeAreaInsets();
const { id } = useLocalSearchParams<{ id: string }>();
const { agents, agentStreamState, pendingPermissions, respondToPermission, initializeAgent } = useSession();
const {
agents,
agentStreamState,
initializingAgents,
pendingPermissions,
respondToPermission,
initializeAgent,
} = useSession();
const { registerFooterControls, unregisterFooterControls } = useFooterControls();
// Keyboard animation
@@ -41,15 +48,29 @@ export default function AgentScreen() {
Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id)
);
// Agent is initializing if we don't have stream state yet
const isInitializing = id ? !agentStreamState.has(id) : false;
const hasStreamState = id ? agentStreamState.has(id) : false;
const initializationState = id ? initializingAgents.get(id) : undefined;
const isInitializing = id
? initializationState !== undefined
? initializationState
: !hasStreamState
: false;
useEffect(() => {
if (!id) {
return;
}
if (initializationState !== undefined) {
return;
}
if (hasStreamState) {
return;
}
initializeAgent({ agentId: id });
}, [id, initializeAgent]);
}, [id, initializeAgent, initializationState, hasStreamState]);
const agentControls = useMemo(() => {
if (!id) return null;

View File

@@ -41,14 +41,39 @@ export function AgentStreamView({
useState<SelectedToolCall | null>(null);
const [isNearBottom, setIsNearBottom] = useState(true);
const hasScrolledInitially = useRef(false);
const hasAutoScrolledOnce = useRef(false);
useEffect(() => {
hasScrolledInitially.current = false;
hasAutoScrolledOnce.current = false;
}, [agentId]);
// Scroll to bottom immediately on initial load, then animate for new messages
useEffect(() => {
if (isNearBottom) {
const shouldAnimate = hasScrolledInitially.current;
scrollViewRef.current?.scrollToEnd({ animated: shouldAnimate });
hasScrolledInitially.current = true;
if (streamItems.length === 0) {
return;
}
const scrollView = scrollViewRef.current;
if (!scrollView) {
return;
}
if (!hasAutoScrolledOnce.current) {
// First load: jump to bottom without animation and remember we did it
scrollView.scrollToEnd({ animated: false });
hasScrolledInitially.current = true;
hasAutoScrolledOnce.current = true;
return;
}
if (!isNearBottom) {
return;
}
const shouldAnimate = hasScrolledInitially.current;
scrollView.scrollToEnd({ animated: shouldAnimate });
hasScrolledInitially.current = true;
}, [streamItems, isNearBottom]);
function handleOpenToolCallDetails(toolCall: SelectedToolCall) {

View File

@@ -100,6 +100,7 @@ interface SessionContextValue {
setCurrentAssistantMessage: (message: string) => void;
agentStreamState: Map<string, StreamItem[]>;
setAgentStreamState: (state: Map<string, StreamItem[]> | ((prev: Map<string, StreamItem[]>) => Map<string, StreamItem[]>)) => void;
initializingAgents: Map<string, boolean>;
// Agents and commands
agents: Map<string, Agent>;
@@ -153,6 +154,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
const [messages, setMessages] = useState<MessageEntry[]>([]);
const [currentAssistantMessage, setCurrentAssistantMessage] = useState("");
const [agentStreamState, setAgentStreamState] = useState<Map<string, StreamItem[]>>(new Map());
const [initializingAgents, setInitializingAgents] = useState<Map<string, boolean>>(new Map());
const [agents, setAgents] = useState<Map<string, Agent>>(new Map());
const [commands, setCommands] = useState<Map<string, Command>>(new Map());
@@ -198,6 +200,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
setCommands(new Map(commandsList.map((c) => [c.id, c as Command])));
setAgentStreamState(new Map());
setAgentUpdates(new Map());
setInitializingAgents(new Map());
});
// Agent created
@@ -287,6 +290,12 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
);
return new Map(prev).set(agentId, reconstructedStream);
});
setInitializingAgents((prev) => {
const next = new Map(prev);
next.set(agentId, false);
return next;
});
});
// Agent status update (mode changes, title changes, etc.)
@@ -593,6 +602,12 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
}, [ws, audioPlayer, setIsPlayingAudio]);
const initializeAgent = useCallback(({ agentId, requestId }: { agentId: string; requestId?: string }) => {
setInitializingAgents((prev) => {
const next = new Map(prev);
next.set(agentId, true);
return next;
});
const msg: WSInboundMessage = {
type: "session",
message: {
@@ -766,6 +781,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
setCurrentAssistantMessage,
agentStreamState,
setAgentStreamState,
initializingAgents,
agents,
setAgents,
commands,