import { useCallback, useEffect, useRef, useState, useSyncExternalStore, type Dispatch, type MutableRefObject, type SetStateAction } from 'react' import type { AiAgentId } from '../lib/aiAgents' import type { AiAgentPermissionMode } from '../lib/aiAgentPermissionMode' import type { AiTarget } from '../lib/aiTargets' import type { AppLocale } from '../lib/i18n' import { getAgentDocsPath } from '../lib/agentDocsPath' import type { NoteReference } from '../utils/ai-context' import { type AgentStatus, type AiAgentMessage, } from '../lib/aiAgentConversation' import type { AgentFileCallbacks } from '../lib/aiAgentFileOperations' import { addAgentLocalMarker, clearAgentConversation, regenerateAgentMessage, sendAgentMessage, stopAgentMessage, type AiAgentSessionRuntime, } from '../lib/aiAgentSession' import type { ToolInvocation } from '../lib/aiAgentMessageState' import { aiWorkspaceSessionDispatchers, aiWorkspaceSessionSnapshot, subscribeAiWorkspaceSession, } from '../lib/aiWorkspaceSessionStore' export type { AgentFileCallbacks } from '../lib/aiAgentFileOperations' export type { AgentStatus } from '../lib/aiAgentConversation' export type { AiAgentMessage } from '../lib/aiAgentConversation' interface UseCliAiAgentOptions { agent: AiAgentId target?: AiTarget locale?: AppLocale agentReady: boolean permissionMode: AiAgentPermissionMode sessionId?: string } interface UseCliAiAgentRuntime extends AiAgentSessionRuntime { messages: AiAgentMessage[] setMessages: Dispatch> status: AgentStatus setStatus: Dispatch> messagesRef: MutableRefObject statusRef: MutableRefObject } function useCliAiAgentRuntime(fileCallbacks: AgentFileCallbacks | undefined): UseCliAiAgentRuntime { const [messages, setMessages] = useState([]) const [status, setStatus] = useState('idle') const abortRef = useRef({ aborted: false }) const responseAccRef = useRef('') const fileCallbacksRef = useRef(fileCallbacks) const toolInputMapRef = useRef>(new Map()) const messagesRef = useRef([]) const statusRef = useRef('idle') useEffect(() => { messagesRef.current = messages }, [messages]) useEffect(() => { statusRef.current = status }, [status]) useEffect(() => { fileCallbacksRef.current = fileCallbacks }, [fileCallbacks]) return { messages, setMessages, status, setStatus, abortRef, responseAccRef, fileCallbacksRef, toolInputMapRef, messagesRef, statusRef, } } function useSharedCliAiAgentRuntime( sessionId: string | undefined, fileCallbacks: AgentFileCallbacks | undefined, ): UseCliAiAgentRuntime { const resolvedSessionId = sessionId ?? '' const subscribe = useCallback((listener: () => void) => ( resolvedSessionId ? subscribeAiWorkspaceSession(resolvedSessionId, listener) : () => {} ), [resolvedSessionId]) const getSnapshot = useCallback(() => aiWorkspaceSessionSnapshot(resolvedSessionId), [resolvedSessionId]) const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot) const dispatchers = resolvedSessionId ? aiWorkspaceSessionDispatchers(resolvedSessionId) : { setMessages: () => {}, setStatus: () => {} } const abortRef = useRef({ aborted: false }) const responseAccRef = useRef('') const fileCallbacksRef = useRef(fileCallbacks) const toolInputMapRef = useRef>(new Map()) const messagesRef = useRef(snapshot.messages) const statusRef = useRef(snapshot.status) useEffect(() => { messagesRef.current = snapshot.messages }, [snapshot.messages]) useEffect(() => { statusRef.current = snapshot.status }, [snapshot.status]) useEffect(() => { fileCallbacksRef.current = fileCallbacks }, [fileCallbacks]) return { messages: snapshot.messages, setMessages: dispatchers.setMessages, status: snapshot.status, setStatus: dispatchers.setStatus, abortRef, responseAccRef, fileCallbacksRef, toolInputMapRef, messagesRef, statusRef, } } export function useCliAiAgent( vaultPath: string, vaultPaths: string[] | undefined, contextPrompt: string | undefined, fileCallbacks: AgentFileCallbacks | undefined, options: UseCliAiAgentOptions, ) { const { agent, agentReady, sessionId, target } = options const locale = options.locale ?? 'en' const { permissionMode } = options const localRuntime = useCliAiAgentRuntime(fileCallbacks) const sharedRuntime = useSharedCliAiAgentRuntime(sessionId, fileCallbacks) const runtime = sessionId ? sharedRuntime : localRuntime const { messages, status } = runtime async function buildAgentContext() { const agentDocsPath = await getAgentDocsPath() return { agent, agentDocsPath, locale, sessionId, target, ready: agentReady, vaultPath, vaultPaths, permissionMode, systemPromptOverride: contextPrompt, } } async function sendPrompt(text: string, references?: NoteReference[]): Promise { await sendAgentMessage({ runtime, context: await buildAgentContext(), prompt: { text, references }, }) } async function sendMessage(text: string, references?: NoteReference[]): Promise { await sendPrompt(text, references) } async function regenerateMessage(messageId: string): Promise { await regenerateAgentMessage({ runtime, context: await buildAgentContext(), messageId, }) } function stopMessage(): void { stopAgentMessage(runtime, { agent, locale }) } function clearConversation(): void { clearAgentConversation(runtime) } function addLocalMarker(text: string): void { addAgentLocalMarker(runtime, text) } return { messages, status, sendMessage, stopMessage, regenerateMessage, clearConversation, addLocalMarker } }