/ hooks / useDeferredHookMessages.ts
useDeferredHookMessages.ts
 1  import { useCallback, useEffect, useRef } from 'react'
 2  import type { HookResultMessage, Message } from '../types/message.js'
 3  
 4  /**
 5   * Manages deferred SessionStart hook messages so the REPL can render
 6   * immediately instead of blocking on hook execution (~500ms).
 7   *
 8   * Hook messages are injected asynchronously when the promise resolves.
 9   * Returns a callback that onSubmit should call before the first API
10   * request to ensure the model always sees hook context.
11   */
12  export function useDeferredHookMessages(
13    pendingHookMessages: Promise<HookResultMessage[]> | undefined,
14    setMessages: (action: React.SetStateAction<Message[]>) => void,
15  ): () => Promise<void> {
16    const pendingRef = useRef(pendingHookMessages ?? null)
17    const resolvedRef = useRef(!pendingHookMessages)
18  
19    useEffect(() => {
20      const promise = pendingRef.current
21      if (!promise) return
22      let cancelled = false
23      promise.then(msgs => {
24        if (cancelled) return
25        resolvedRef.current = true
26        pendingRef.current = null
27        if (msgs.length > 0) {
28          setMessages(prev => [...msgs, ...prev])
29        }
30      })
31      return () => {
32        cancelled = true
33      }
34    }, [setMessages])
35  
36    return useCallback(async () => {
37      if (resolvedRef.current || !pendingRef.current) return
38      const msgs = await pendingRef.current
39      if (resolvedRef.current) return
40      resolvedRef.current = true
41      pendingRef.current = null
42      if (msgs.length > 0) {
43        setMessages(prev => [...msgs, ...prev])
44      }
45    }, [setMessages])
46  }