fix(agent): track initialization state to prevent duplicate agent initialization
This commit is contained in:
@@ -15,7 +15,14 @@ export default function AgentScreen() {
|
|||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { id } = useLocalSearchParams<{ id: string }>();
|
const { id } = useLocalSearchParams<{ id: string }>();
|
||||||
const { agents, agentStreamState, pendingPermissions, respondToPermission, initializeAgent } = useSession();
|
const {
|
||||||
|
agents,
|
||||||
|
agentStreamState,
|
||||||
|
initializingAgents,
|
||||||
|
pendingPermissions,
|
||||||
|
respondToPermission,
|
||||||
|
initializeAgent,
|
||||||
|
} = useSession();
|
||||||
const { registerFooterControls, unregisterFooterControls } = useFooterControls();
|
const { registerFooterControls, unregisterFooterControls } = useFooterControls();
|
||||||
|
|
||||||
// Keyboard animation
|
// Keyboard animation
|
||||||
@@ -41,15 +48,29 @@ export default function AgentScreen() {
|
|||||||
Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id)
|
Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Agent is initializing if we don't have stream state yet
|
const hasStreamState = id ? agentStreamState.has(id) : false;
|
||||||
const isInitializing = id ? !agentStreamState.has(id) : false;
|
const initializationState = id ? initializingAgents.get(id) : undefined;
|
||||||
|
const isInitializing = id
|
||||||
|
? initializationState !== undefined
|
||||||
|
? initializationState
|
||||||
|
: !hasStreamState
|
||||||
|
: false;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (initializationState !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasStreamState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
initializeAgent({ agentId: id });
|
initializeAgent({ agentId: id });
|
||||||
}, [id, initializeAgent]);
|
}, [id, initializeAgent, initializationState, hasStreamState]);
|
||||||
|
|
||||||
const agentControls = useMemo(() => {
|
const agentControls = useMemo(() => {
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
|
|||||||
@@ -41,14 +41,39 @@ export function AgentStreamView({
|
|||||||
useState<SelectedToolCall | null>(null);
|
useState<SelectedToolCall | null>(null);
|
||||||
const [isNearBottom, setIsNearBottom] = useState(true);
|
const [isNearBottom, setIsNearBottom] = useState(true);
|
||||||
const hasScrolledInitially = useRef(false);
|
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
|
// Scroll to bottom immediately on initial load, then animate for new messages
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isNearBottom) {
|
if (streamItems.length === 0) {
|
||||||
const shouldAnimate = hasScrolledInitially.current;
|
return;
|
||||||
scrollViewRef.current?.scrollToEnd({ animated: shouldAnimate });
|
|
||||||
hasScrolledInitially.current = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]);
|
}, [streamItems, isNearBottom]);
|
||||||
|
|
||||||
function handleOpenToolCallDetails(toolCall: SelectedToolCall) {
|
function handleOpenToolCallDetails(toolCall: SelectedToolCall) {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ interface SessionContextValue {
|
|||||||
setCurrentAssistantMessage: (message: string) => void;
|
setCurrentAssistantMessage: (message: string) => void;
|
||||||
agentStreamState: Map<string, StreamItem[]>;
|
agentStreamState: Map<string, StreamItem[]>;
|
||||||
setAgentStreamState: (state: Map<string, StreamItem[]> | ((prev: Map<string, StreamItem[]>) => Map<string, StreamItem[]>)) => void;
|
setAgentStreamState: (state: Map<string, StreamItem[]> | ((prev: Map<string, StreamItem[]>) => Map<string, StreamItem[]>)) => void;
|
||||||
|
initializingAgents: Map<string, boolean>;
|
||||||
|
|
||||||
// Agents and commands
|
// Agents and commands
|
||||||
agents: Map<string, Agent>;
|
agents: Map<string, Agent>;
|
||||||
@@ -153,6 +154,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
|
|||||||
const [messages, setMessages] = useState<MessageEntry[]>([]);
|
const [messages, setMessages] = useState<MessageEntry[]>([]);
|
||||||
const [currentAssistantMessage, setCurrentAssistantMessage] = useState("");
|
const [currentAssistantMessage, setCurrentAssistantMessage] = useState("");
|
||||||
const [agentStreamState, setAgentStreamState] = useState<Map<string, StreamItem[]>>(new Map());
|
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 [agents, setAgents] = useState<Map<string, Agent>>(new Map());
|
||||||
const [commands, setCommands] = useState<Map<string, Command>>(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])));
|
setCommands(new Map(commandsList.map((c) => [c.id, c as Command])));
|
||||||
setAgentStreamState(new Map());
|
setAgentStreamState(new Map());
|
||||||
setAgentUpdates(new Map());
|
setAgentUpdates(new Map());
|
||||||
|
setInitializingAgents(new Map());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Agent created
|
// Agent created
|
||||||
@@ -287,6 +290,12 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
|
|||||||
);
|
);
|
||||||
return new Map(prev).set(agentId, reconstructedStream);
|
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.)
|
// Agent status update (mode changes, title changes, etc.)
|
||||||
@@ -593,6 +602,12 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
|
|||||||
}, [ws, audioPlayer, setIsPlayingAudio]);
|
}, [ws, audioPlayer, setIsPlayingAudio]);
|
||||||
|
|
||||||
const initializeAgent = useCallback(({ agentId, requestId }: { agentId: string; requestId?: string }) => {
|
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 = {
|
const msg: WSInboundMessage = {
|
||||||
type: "session",
|
type: "session",
|
||||||
message: {
|
message: {
|
||||||
@@ -766,6 +781,7 @@ export function SessionProvider({ children, serverUrl }: SessionProviderProps) {
|
|||||||
setCurrentAssistantMessage,
|
setCurrentAssistantMessage,
|
||||||
agentStreamState,
|
agentStreamState,
|
||||||
setAgentStreamState,
|
setAgentStreamState,
|
||||||
|
initializingAgents,
|
||||||
agents,
|
agents,
|
||||||
setAgents,
|
setAgents,
|
||||||
commands,
|
commands,
|
||||||
|
|||||||
Reference in New Issue
Block a user