import { useCallback, useRef } from "react"; import type { AgentGUINodeData } from "../../../types"; import type { AgentGUIConversationSummary } from "../model/agentGuiConversationModel"; import type { AgentGUIConversationRailRevealReason } from "../model/agentGuiConversationRailViewState"; import { rememberAgentGUIActiveConversation } from "../model/agentGuiSessionNavigationMemory"; export type ConversationIntent = | { tag: "home" } | { tag: "requested"; id: string } | { tag: "active"; id: string; source?: "activation" | "user-selection"; }; export interface AgentGUIConversationSelectionOptions { reloadConversations?: boolean; reveal?: Extract; } export function resolveConversationSummaryById( conversations: readonly AgentGUIConversationSummary[], conversationId: string | null | undefined, transientConversation: AgentGUIConversationSummary | null = null ): AgentGUIConversationSummary | null { const normalized = conversationId?.trim() ?? ""; if (!normalized) return null; return ( conversations.find((conversation) => conversation.id === normalized) ?? (transientConversation?.id === normalized ? transientConversation : null) ); } interface AgentConversationSelectionInput { activation: { canReload(agentSessionId: string): boolean; forget(agentSessionId: string): void; isPending(agentSessionId: string): boolean; }; conversations: { agentTargetIdFor(agentSessionId: string): string | null; contains(agentSessionId: string): boolean; }; detail: { ensureHydrated(agentSessionId: string): void; ensureStateHydrated(agentSessionId: string): void; isHydrated(agentSessionId: string): boolean; isStateHydrated(agentSessionId: string): boolean; markPending(agentSessionId: string): void; setLoading(loading: boolean): void; }; hasConversationListQuery(): boolean; isMounted(): boolean; onMissingConversationListQuery(previousAgentSessionId: string | null): void; persistence: { update(updater: (current: AgentGUINodeData) => AgentGUINodeData): void; }; rail: { clearRevealRequest(): void; requestReveal( agentSessionId: string, reason: AgentGUIConversationRailRevealReason ): void; }; selection: { clearDetailError(): void; getActiveSessionId(): string | null; setActiveSessionId(agentSessionId: string | null): void; setComposerHome(home: boolean): void; setIntent(intent: ConversationIntent): void; }; } export function useAgentConversationSelection( input: AgentConversationSelectionInput ) { const inputRef = useRef(input); inputRef.current = input; const persistActiveConversation = useCallback( (agentSessionId: string | null, agentTargetId?: string | null) => { inputRef.current.persistence.update((current) => { const resolvedAgentTargetId = agentSessionId ? agentTargetId?.trim() || inputRef.current.conversations.agentTargetIdFor(agentSessionId) : null; return rememberAgentGUIActiveConversation( current, agentSessionId, resolvedAgentTargetId ); }); }, [] ); const syncConversationListProjection = useCallback( async (_preferredSessionId?: string | null) => { const current = inputRef.current; if (current.hasConversationListQuery()) return; const previous = current.selection.getActiveSessionId(); current.onMissingConversationListQuery(previous); current.selection.setIntent({ tag: "home" }); current.selection.setComposerHome(true); current.selection.setActiveSessionId(null); current.detail.setLoading(false); current.selection.clearDetailError(); current.rail.clearRevealRequest(); persistActiveConversation(null); }, [persistActiveConversation] ); const selectConversation = useCallback( ( agentSessionId: string, options?: AgentGUIConversationSelectionOptions ) => { const normalized = agentSessionId.trim(); if (!normalized) return; const current = inputRef.current; const previous = current.selection.getActiveSessionId(); const detailHydrated = current.detail.isHydrated(normalized); const stateHydrated = current.detail.isStateHydrated(normalized); const activationPending = current.activation.isPending(normalized); current.selection.setComposerHome(false); if (previous && previous !== normalized) current.activation.forget(previous); if (previous !== normalized) { if (detailHydrated || activationPending) { current.detail.setLoading(false); } else { current.detail.markPending(normalized); } } const reloadConversations = options?.reloadConversations !== false && current.conversations.contains(normalized); current.selection.setIntent({ id: normalized, source: "user-selection", tag: "active" }); current.selection.setActiveSessionId(normalized); current.selection.clearDetailError(); if (options?.reveal) { current.rail.requestReveal(normalized, options.reveal); } else { current.rail.clearRevealRequest(); } if (!activationPending && current.activation.canReload(normalized)) { if (reloadConversations) { void syncConversationListProjection(normalized); } if (!detailHydrated) { current.detail.ensureHydrated(normalized); } else if (!stateHydrated) { current.detail.ensureStateHydrated(normalized); } } persistActiveConversation(normalized); }, [persistActiveConversation, syncConversationListProjection] ); const isCurrentConversation = useCallback( (agentSessionId: string) => inputRef.current.isMounted() && inputRef.current.selection.getActiveSessionId() === agentSessionId.trim(), [] ); const isConversationStale = useCallback( (agentSessionId: string) => { const normalized = agentSessionId.trim(); return Boolean(normalized && !isCurrentConversation(normalized)); }, [isCurrentConversation] ); return { isCurrentConversation, isConversationStale, persistActiveConversation, selectConversation, syncConversationListProjection }; }