( sendMessage: (message: QueuedMessage) => Promise<void>, isChainInProgressRef: React.MutableRefObject<boolean>, activeAgentStreamsRef: React.MutableRefObject<number>, )
| 15 | const QUEUE_WATCHDOG_TIMEOUT_MS = 60 * 1000 |
| 16 | |
| 17 | export const useMessageQueue = ( |
| 18 | sendMessage: (message: QueuedMessage) => Promise<void>, |
| 19 | isChainInProgressRef: React.MutableRefObject<boolean>, |
| 20 | activeAgentStreamsRef: React.MutableRefObject<number>, |
| 21 | ) => { |
| 22 | const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]) |
| 23 | const [streamStatus, setStreamStatus] = useState<StreamStatus>('idle') |
| 24 | const [canProcessQueue, setCanProcessQueue] = useState<boolean>(true) |
| 25 | // Separate state for user-initiated pause to ensure re-renders when pause status changes |
| 26 | const [queuePausedState, setQueuePausedState] = useState<boolean>(false) |
| 27 | |
| 28 | // Keep a ref so clearQueue can return the current queue synchronously. |
| 29 | const queuedMessagesRef = useRef<QueuedMessage[]>([]) |
| 30 | const streamTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
| 31 | const streamIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null) |
| 32 | const streamMessageIdRef = useRef<string | null>(null) |
| 33 | const isProcessingQueueRef = useRef<boolean>(false) |
| 34 | // User-initiated pause state (separate from system-busy state) |
| 35 | const isQueuePausedRef = useRef<boolean>(false) |
| 36 | // Watchdog timer to recover from stuck queue processing lock |
| 37 | const watchdogTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null) |
| 38 | |
| 39 | // queuePaused reflects whether the user has explicitly paused the queue |
| 40 | // (not whether the system is temporarily busy processing) |
| 41 | // Use state instead of ref to ensure components re-render when pause status changes |
| 42 | const queuePaused = queuePausedState |
| 43 | |
| 44 | const clearStreaming = useCallback(() => { |
| 45 | if (streamTimeoutRef.current) { |
| 46 | clearTimeout(streamTimeoutRef.current) |
| 47 | streamTimeoutRef.current = null |
| 48 | } |
| 49 | if (streamIntervalRef.current) { |
| 50 | clearInterval(streamIntervalRef.current) |
| 51 | streamIntervalRef.current = null |
| 52 | } |
| 53 | streamMessageIdRef.current = null |
| 54 | activeAgentStreamsRef.current = 0 |
| 55 | setStreamStatus('idle') |
| 56 | }, [activeAgentStreamsRef]) |
| 57 | |
| 58 | useEffect(() => { |
| 59 | return () => { |
| 60 | clearStreaming() |
| 61 | // Clean up watchdog timer on unmount |
| 62 | if (watchdogTimeoutRef.current) { |
| 63 | clearTimeout(watchdogTimeoutRef.current) |
| 64 | watchdogTimeoutRef.current = null |
| 65 | } |
| 66 | } |
| 67 | }, [clearStreaming]) |
| 68 | |
| 69 | const processNextMessage = useCallback(() => { |
| 70 | const queuedList = queuedMessagesRef.current |
| 71 | const queueLength = queuedList.length |
| 72 | |
| 73 | if (queueLength === 0) { |
| 74 | return |
no test coverage detected