import { useCallback, useEffect, useRef } from 'react'; import type { FitAddon } from '@xterm/addon-fit'; import type { Terminal } from '@xterm/xterm'; import type { UseShellRuntimeOptions, UseShellRuntimeResult } from '../types/types'; import { useShellConnection } from './useShellConnection'; import { useShellTerminal } from './useShellTerminal'; export function useShellRuntime({ selectedProject, selectedSession, initialCommand, isPlainShell, minimal, autoConnect, isRestarting, onProcessComplete, onOutputRef, }: UseShellRuntimeOptions): UseShellRuntimeResult { const terminalContainerRef = useRef(null); const terminalRef = useRef(null); const fitAddonRef = useRef(null); const wsRef = useRef(null); const selectedProjectRef = useRef(selectedProject); const selectedSessionRef = useRef(selectedSession); const initialCommandRef = useRef(initialCommand); const isPlainShellRef = useRef(isPlainShell); const onProcessCompleteRef = useRef(onProcessComplete); const lastSessionIdRef = useRef(selectedSession?.id ?? null); // Keep mutable values in refs so websocket handlers always read current data. useEffect(() => { selectedProjectRef.current = selectedProject; selectedSessionRef.current = selectedSession; initialCommandRef.current = initialCommand; isPlainShellRef.current = isPlainShell; onProcessCompleteRef.current = onProcessComplete; }, [selectedProject, selectedSession, initialCommand, isPlainShell, onProcessComplete]); const closeSocket = useCallback(() => { const activeSocket = wsRef.current; if (!activeSocket) { return; } if ( activeSocket.readyState === WebSocket.OPEN || activeSocket.readyState === WebSocket.CONNECTING ) { activeSocket.close(); } wsRef.current = null; }, []); const { isInitialized, clearTerminalScreen, disposeTerminal } = useShellTerminal({ terminalContainerRef, terminalRef, fitAddonRef, wsRef, selectedProject, minimal, isRestarting, closeSocket, }); const { isConnected, isConnecting, connectToShell, disconnectFromShell } = useShellConnection({ wsRef, terminalRef, fitAddonRef, selectedProjectRef, selectedSessionRef, initialCommandRef, isPlainShellRef, onProcessCompleteRef, isInitialized, autoConnect, closeSocket, clearTerminalScreen, onOutputRef, }); useEffect(() => { if (!isRestarting) { return; } disconnectFromShell(); disposeTerminal(); }, [disconnectFromShell, disposeTerminal, isRestarting]); useEffect(() => { if (selectedProject) { return; } disconnectFromShell(); disposeTerminal(); }, [disconnectFromShell, disposeTerminal, selectedProject]); useEffect(() => { const currentSessionId = selectedSession?.id ?? null; if (lastSessionIdRef.current !== currentSessionId && isInitialized) { disconnectFromShell(); } lastSessionIdRef.current = currentSessionId; }, [disconnectFromShell, isInitialized, selectedSession?.id]); return { terminalContainerRef, terminalRef, wsRef, isConnected, isInitialized, isConnecting, connectToShell, disconnectFromShell, }; }