(messages: Message[], setMessages: (action: React.SetStateAction<Message[]>) => void, abortControllerRef: React.RefObject<AbortController | null>, commands: readonly Command[], mainLoopModel: string)
| 51 | * Inbound messages from claude.ai are injected into the REPL via queuedCommands. |
| 52 | */ |
| 53 | export function useReplBridge(messages: Message[], setMessages: (action: React.SetStateAction<Message[]>) => void, abortControllerRef: React.RefObject<AbortController | null>, commands: readonly Command[], mainLoopModel: string): { |
| 54 | sendBridgeResult: () => void; |
| 55 | } { |
| 56 | const handleRef = useRef<ReplBridgeHandle | null>(null); |
| 57 | const teardownPromiseRef = useRef<Promise<void> | undefined>(undefined); |
| 58 | const lastWrittenIndexRef = useRef(0); |
| 59 | // Tracks UUIDs already flushed as initial messages. Persists across |
| 60 | // bridge reconnections so Bridge #2+ only sends new messages — sending |
| 61 | // duplicate UUIDs causes the server to kill the WebSocket. |
| 62 | const flushedUUIDsRef = useRef(new Set<string>()); |
| 63 | const failureTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined); |
| 64 | // Persists across effect re-runs (unlike the effect's local state). Reset |
| 65 | // only on successful init. Hits MAX_CONSECUTIVE_INIT_FAILURES → fuse blown |
| 66 | // for the session, regardless of replBridgeEnabled re-toggling. |
| 67 | const consecutiveFailuresRef = useRef(0); |
| 68 | const setAppState = useSetAppState(); |
| 69 | const commandsRef = useRef(commands); |
| 70 | commandsRef.current = commands; |
| 71 | const mainLoopModelRef = useRef(mainLoopModel); |
| 72 | mainLoopModelRef.current = mainLoopModel; |
| 73 | const messagesRef = useRef(messages); |
| 74 | messagesRef.current = messages; |
| 75 | const store = useAppStateStore(); |
| 76 | const { |
| 77 | addNotification |
| 78 | } = useNotifications(); |
| 79 | const replBridgeEnabled = feature('BRIDGE_MODE') ? |
| 80 | // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant |
| 81 | useAppState(s => s.replBridgeEnabled) : false; |
| 82 | const replBridgeConnected = feature('BRIDGE_MODE') ? |
| 83 | // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant |
| 84 | useAppState(s_0 => s_0.replBridgeConnected) : false; |
| 85 | const replBridgeOutboundOnly = feature('BRIDGE_MODE') ? |
| 86 | // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant |
| 87 | useAppState(s_1 => s_1.replBridgeOutboundOnly) : false; |
| 88 | const replBridgeInitialName = feature('BRIDGE_MODE') ? |
| 89 | // biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant |
| 90 | useAppState(s_2 => s_2.replBridgeInitialName) : undefined; |
| 91 | |
| 92 | // Initialize/teardown bridge when enabled state changes. |
| 93 | // Passes current messages as initialMessages so the remote session |
| 94 | // starts with the existing conversation context (e.g. from /bridge). |
| 95 | useEffect(() => { |
| 96 | // feature() check must use positive pattern for dead code elimination — |
| 97 | // negative pattern (if (!feature(...)) return) does NOT eliminate |
| 98 | // dynamic imports below. |
| 99 | if (feature('BRIDGE_MODE')) { |
| 100 | if (!replBridgeEnabled) return; |
| 101 | const outboundOnly = replBridgeOutboundOnly; |
| 102 | function notifyBridgeFailed(detail?: string): void { |
| 103 | if (outboundOnly) return; |
| 104 | addNotification({ |
| 105 | key: 'bridge-failed', |
| 106 | jsx: <> |
| 107 | <Text color="error">Remote Control failed</Text> |
| 108 | {detail && <Text dimColor> · {detail}</Text>} |
| 109 | </>, |
| 110 | priority: 'immediate' |
no test coverage detected