import { useCallback, useEffect, type Dispatch, type RefObject, type SetStateAction } from "react"; import type { AgentGUIRuntime } from "../../../agentActivityRuntime"; import type { PendingActivationIntentRecord } from "@tutti-os/agent-activity-core"; import type { AgentGUIConversationFilter } from "../model/agentGuiConversationFilter"; import type { AgentComposerDraft } from "../model/agentGuiNodeTypes"; import { buildAgentComposerDraft } from "../model/agentComposerDraft"; import { normalizeAgentComposerDraftProjectPath, resolveAgentComposerDraftScopeKey } from "../model/agentComposerDraftScope"; import type { AgentGUINodeData, AgentGUIProvider, AgentGUIAgentTarget } from "../../../types"; import { resolveAgentGUIAgentTarget } from "../../../agentTargets"; import type { AgentGUIComposerTargetData } from "./agentGuiController.composerPresentation"; import { isPendingNewConversationActivationForSession } from "./useAgentGUIActivation"; import { resolveAgentGUIComposerAppendRequest, type AgentGUIComposerAppendRequest } from "./useAgentGUIComposerAppendRequest"; import { buildNodeDefaultComposerSettings, nodeDataFromComposerSettings } from "./agentGuiController.composerHelpers"; export interface AgentGUIPrefillPromptRequest { agentTargetId?: string | null; autoSubmit?: boolean; draftPrompt: string; model?: string | null; modelPlanId?: string | null; provider?: AgentGUIProvider; sequence: number; userProjectPath?: string | null; } export interface UseAgentGUIConversationHomeInput { activeConversationId: string | null; activeConversationIdRef: RefObject; activePendingActivation: PendingActivationIntentRecord | null; agentActivityRuntime: AgentGUIRuntime; currentProvider: AgentGUIProvider; composerTargetDataFromProviderTarget: (input: { current: AgentGUINodeData; isExplicit: boolean; target: AgentGUIAgentTarget; }) => AgentGUIComposerTargetData; composerAppendRequest: AgentGUIComposerAppendRequest | null; conversationFilterRef: RefObject; dataRef: RefObject; defaultAgentTargetId: string | null; handledPrefillPromptSequenceRef: RefObject; handledComposerAppendSequenceRef: RefObject; isComposerHomeRef: RefObject; isExplicitAgentGUIAgentTarget: ( target: AgentGUIAgentTarget, explicitTargets: readonly AgentGUIAgentTarget[] ) => boolean; loadDraftComposerOptions: () => void; normalizedExplicitProviderTargets: readonly AgentGUIAgentTarget[]; normalizedProviderTargets: readonly AgentGUIAgentTarget[]; onDataChangeRef: RefObject< (updater: (current: AgentGUINodeData) => AgentGUINodeData) => void >; onComposerAppendHandled?: (sequence: number) => void; submitPrefillPrompt: (prompt: string) => void; persistActiveConversation: (agentSessionId: string | null) => void; prefillPromptRequest: AgentGUIPrefillPromptRequest | null; reportActiveConversationCleared: (input: { details: Record; previousAgentSessionId: string | null; reason: "create_conversation" | "prefill_prompt"; runtime: AgentGUIRuntime; workspaceId: string; }) => void; selectedComposerTargetDataRef: RefObject; selectedProjectPathRef: RefObject; draftByScopeKeyRef: RefObject>; setActiveConversationId: Dispatch>; setConversationFilter: Dispatch>; setDetailError: Dispatch>; setDraftByScopeKey: Dispatch< SetStateAction> >; setHomeComposerTargetOverride: Dispatch< SetStateAction >; setIntent: (intent: { tag: "home" }) => void; setIsComposerHome: Dispatch>; setIsLoadingMessages: Dispatch>; setSelectedProjectPath: Dispatch>; shouldUseStaticProviderTargets: boolean; unactivate: (agentSessionId: string) => Promise; workspaceId: string; } /** Owns transitions from an active conversation back to the home composer. */ export function useAgentGUIConversationHome({ activeConversationId, activeConversationIdRef, activePendingActivation, agentActivityRuntime, composerTargetDataFromProviderTarget, composerAppendRequest, conversationFilterRef, currentProvider, dataRef, defaultAgentTargetId, handledPrefillPromptSequenceRef, handledComposerAppendSequenceRef, isComposerHomeRef, isExplicitAgentGUIAgentTarget, loadDraftComposerOptions, normalizedExplicitProviderTargets, normalizedProviderTargets, onDataChangeRef, onComposerAppendHandled, submitPrefillPrompt, persistActiveConversation, prefillPromptRequest, reportActiveConversationCleared, selectedComposerTargetDataRef, selectedProjectPathRef, draftByScopeKeyRef, setActiveConversationId, setConversationFilter, setDetailError, setDraftByScopeKey, setHomeComposerTargetOverride, setIntent, setIsComposerHome, setIsLoadingMessages, setSelectedProjectPath, shouldUseStaticProviderTargets, unactivate, workspaceId }: UseAgentGUIConversationHomeInput) { const enterHome = useCallback( ( reason: "create_conversation" | "prefill_prompt", details: Record ) => { const previous = activeConversationIdRef.current; reportActiveConversationCleared({ details, previousAgentSessionId: previous, reason, runtime: agentActivityRuntime, workspaceId }); if ( previous && !isPendingNewConversationActivationForSession( activePendingActivation, previous ) ) { void unactivate(previous); } setIntent({ tag: "home" }); isComposerHomeRef.current = true; setIsComposerHome(true); activeConversationIdRef.current = null; setActiveConversationId(null); setIsLoadingMessages(false); setDetailError(null); }, [ activeConversationIdRef, activePendingActivation, agentActivityRuntime, isComposerHomeRef, reportActiveConversationCleared, setActiveConversationId, setDetailError, setIntent, setIsComposerHome, setIsLoadingMessages, unactivate, workspaceId ] ); const createConversation = useCallback( (options?: { projectPath?: string | null; source?: string }) => { if (options && "projectPath" in options) { const projectPath = normalizeAgentComposerDraftProjectPath( options.projectPath ); selectedProjectPathRef.current = projectPath; setSelectedProjectPath(projectPath); } enterHome("create_conversation", { hasProjectPathOption: Boolean(options && "projectPath" in options), isComposerHome: isComposerHomeRef.current, projectPathPresent: Boolean( options && "projectPath" in options && normalizeAgentComposerDraftProjectPath(options.projectPath) ), source: options?.source ?? "controller" }); persistActiveConversation(null); const filter = conversationFilterRef.current; if (filter.kind === "agentTarget") { const target = resolveAgentGUIAgentTarget({ agentTargetId: filter.agentTargetId, defaultAgentTargetId, provider: currentProvider, agentTargets: normalizedProviderTargets, useStaticCatalog: false }); if ( target && target.disabled !== true && (target.agentTargetId?.trim() ?? "") === filter.agentTargetId ) { setHomeComposerTargetOverride(target); } } loadDraftComposerOptions(); }, [ currentProvider, conversationFilterRef, dataRef, defaultAgentTargetId, enterHome, isComposerHomeRef, loadDraftComposerOptions, normalizedProviderTargets, persistActiveConversation, selectedProjectPathRef, setHomeComposerTargetOverride, setSelectedProjectPath ] ); useEffect(() => { const resolvedAppendRequest = resolveAgentGUIComposerAppendRequest({ activeConversationId, draftByScopeKey: draftByScopeKeyRef.current, handledSequence: handledComposerAppendSequenceRef.current, request: composerAppendRequest }); if (resolvedAppendRequest) { handledComposerAppendSequenceRef.current = resolvedAppendRequest.sequence; draftByScopeKeyRef.current = { ...draftByScopeKeyRef.current, [resolvedAppendRequest.draftKey]: resolvedAppendRequest.nextDraft }; setDraftByScopeKey((current) => ({ ...current, [resolvedAppendRequest.draftKey]: resolvedAppendRequest.nextDraft })); onComposerAppendHandled?.(resolvedAppendRequest.sequence); } if ( !prefillPromptRequest || handledPrefillPromptSequenceRef.current === prefillPromptRequest.sequence ) { return; } handledPrefillPromptSequenceRef.current = prefillPromptRequest.sequence; const draftPrompt = prefillPromptRequest.draftPrompt.trim(); if (!draftPrompt) return; const projectPath = normalizeAgentComposerDraftProjectPath( prefillPromptRequest.userProjectPath ); selectedProjectPathRef.current = projectPath; setSelectedProjectPath(projectPath); enterHome("prefill_prompt", { autoSubmit: prefillPromptRequest.autoSubmit === true, sequence: prefillPromptRequest.sequence }); const selectedTargetData = selectedComposerTargetDataRef.current; const target = prefillPromptRequest.provider || prefillPromptRequest.agentTargetId ? resolveAgentGUIAgentTarget({ agentTargetId: prefillPromptRequest.agentTargetId, defaultAgentTargetId, provider: prefillPromptRequest.provider ?? selectedTargetData.provider, agentTargets: normalizedProviderTargets, useStaticCatalog: shouldUseStaticProviderTargets }) : null; const targetData = target ? composerTargetDataFromProviderTarget({ current: dataRef.current, isExplicit: isExplicitAgentGUIAgentTarget( target, normalizedExplicitProviderTargets ), target }) : selectedTargetData; if (target) { setHomeComposerTargetOverride(target); setConversationFilter( targetData.agentTargetId ? { kind: "agentTarget", agentTargetId: targetData.agentTargetId } : { kind: "all" } ); onDataChangeRef.current((current) => { const nextTargetData = composerTargetDataFromProviderTarget({ current, isExplicit: isExplicitAgentGUIAgentTarget( target, normalizedExplicitProviderTargets ), target }); const nextTargetNodeData = { ...nextTargetData.data, lastActiveAgentSessionId: null }; const nextData = applyAgentGUIPrefillComposerAssignment( nextTargetNodeData, prefillPromptRequest ); dataRef.current = nextData; return nextData; }); } else if (prefillPromptRequest.model || prefillPromptRequest.modelPlanId) { onDataChangeRef.current((current) => { const nextData = applyAgentGUIPrefillComposerAssignment( { ...current, lastActiveAgentSessionId: null }, prefillPromptRequest ); dataRef.current = nextData; return nextData; }); } const sourceScopeKey = resolveAgentComposerDraftScopeKey({}); const prefilledDraft = buildAgentComposerDraft({ prompt: draftPrompt }); draftByScopeKeyRef.current = { ...draftByScopeKeyRef.current, [sourceScopeKey]: prefilledDraft }; setDraftByScopeKey((current) => ({ ...current, [sourceScopeKey]: prefilledDraft })); if (prefillPromptRequest.autoSubmit) { submitPrefillPrompt(draftPrompt); } persistActiveConversation(null); loadDraftComposerOptions(); }, [ activeConversationId, dataRef, draftByScopeKeyRef, composerAppendRequest, defaultAgentTargetId, enterHome, handledPrefillPromptSequenceRef, handledComposerAppendSequenceRef, loadDraftComposerOptions, normalizedExplicitProviderTargets, normalizedProviderTargets, onComposerAppendHandled, onDataChangeRef, persistActiveConversation, prefillPromptRequest, selectedComposerTargetDataRef, selectedProjectPathRef, setConversationFilter, setDraftByScopeKey, setHomeComposerTargetOverride, setSelectedProjectPath, submitPrefillPrompt, shouldUseStaticProviderTargets ]); return { createConversation, enterHome }; } export function applyAgentGUIPrefillComposerAssignment( data: AgentGUINodeData, request: Pick ): AgentGUINodeData { const model = request.model?.trim() || null; const modelPlanId = request.modelPlanId?.trim() || null; if (!model && !modelPlanId) { return data; } return nodeDataFromComposerSettings(data, { ...buildNodeDefaultComposerSettings(data), model, modelPlanId }); }