1- import { useEffect } from 'react' ;
1+ import { useEffect , useRef } from 'react' ;
22import { listen } from '@tauri-apps/api/event' ;
33import {
44 useCodexStore ,
@@ -50,6 +50,18 @@ export function useCodexEvents(enabled = true) {
5050 const taskCompleteBeepMode = useSettingsStore ( ( state ) => state . enableTaskCompleteBeep ) ;
5151 const isChatInterfaceActive = useLayoutStore ( ( state ) => state . view === 'agent' ) ;
5252
53+ // Use refs for values that change but are only read inside callbacks.
54+ // This avoids re-registering all Tauri listeners whenever the user switches
55+ // views or settings change — listener accumulation was the cause of 100% CPU.
56+ const isChatInterfaceActiveRef = useRef ( isChatInterfaceActive ) ;
57+ isChatInterfaceActiveRef . current = isChatInterfaceActive ;
58+ const taskCompleteBeepModeRef = useRef ( taskCompleteBeepMode ) ;
59+ taskCompleteBeepModeRef . current = taskCompleteBeepMode ;
60+ const preventSleepDuringTasksRef = useRef ( preventSleepDuringTasks ) ;
61+ preventSleepDuringTasksRef . current = preventSleepDuringTasks ;
62+ const showReasoningRef = useRef ( showReasoning ) ;
63+ showReasoningRef . current = showReasoning ;
64+
5365 useEffect ( ( ) => {
5466 if ( ! enabled ) {
5567 return ;
@@ -105,7 +117,7 @@ export function useCodexEvents(enabled = true) {
105117 payload . method === 'item/reasoning/summaryTextDelta' ||
106118 payload . method === 'item/reasoning/summaryPartAdded' ||
107119 ( payload . method === 'item/completed' && payload . params . item . type === 'reasoning' ) ;
108- if ( ! showReasoning && isReasoningEvent ) {
120+ if ( ! showReasoningRef . current && isReasoningEvent ) {
109121 return ;
110122 }
111123
@@ -122,7 +134,7 @@ export function useCodexEvents(enabled = true) {
122134 }
123135 }
124136
125- if ( preventSleepDuringTasks && payload . method === 'turn/started' ) {
137+ if ( preventSleepDuringTasksRef . current && payload . method === 'turn/started' ) {
126138 void preventSleep ( threadId ) . catch ( ( error ) => {
127139 console . warn ( '[useCodexEvents] preventSleep failed:' , error ) ;
128140 } ) ;
@@ -136,7 +148,7 @@ export function useCodexEvents(enabled = true) {
136148 const turnStatus = payload . params . turn . status ;
137149 if (
138150 turnStatus === 'completed' &&
139- shouldPlayCompletionBeep ( taskCompleteBeepMode , isChatInterfaceActive )
151+ shouldPlayCompletionBeep ( taskCompleteBeepModeRef . current , isChatInterfaceActiveRef . current )
140152 ) {
141153 playBeep ( ) ;
142154 }
@@ -162,43 +174,48 @@ export function useCodexEvents(enabled = true) {
162174 } ;
163175
164176 if ( isDesktopTauri ( ) ) {
165- const unlistenPromises : Promise < ( ) => void > [ ] = [ ] ;
166177 console . log ( '[useCodexEvents] Setting up Tauri event listeners...' ) ;
167178
168- unlistenPromises . push (
169- listen < ApprovalRequest > ( 'codex/approval-request' , ( event ) => {
170- addApproval ( event . payload ) ;
171- } )
172- ) ;
173-
174- unlistenPromises . push (
175- listen < RequestUserInputRequest > ( 'codex/request-user-input' , ( event ) => {
176- addRequest ( event . payload ) ;
177- } )
178- ) ;
179-
180- unlistenPromises . push (
181- listen < ServerNotification > ( 'codex:notification' , ( event ) => {
182- handleServerNotification ( event . payload ) ;
183- } )
184- ) ;
185-
186- unlistenPromises . push (
187- listen < CodexStderrEvent > ( 'codex:stderr' , ( event ) => {
188- console . error ( '[useCodexEvents] codex stderr:' , event . payload . message ) ;
189- } )
190- ) ;
191-
192- unlistenPromises . push (
193- listen < CodexParseErrorEvent > ( 'codex:parseError' , ( event ) => {
194- console . error ( '[useCodexEvents] codex parseError:' , event . payload . error , event . payload . raw ) ;
195- } )
196- ) ;
179+ // Collect resolved unlisten functions synchronously as promises settle.
180+ // Using a cancelled flag ensures we don't register listeners after cleanup.
181+ let cancelled = false ;
182+ const unlisteners : ( ( ) => void ) [ ] = [ ] ;
183+
184+ const registerListener = async < T > (
185+ event : string ,
186+ handler : ( event : { payload : T } ) => void
187+ ) => {
188+ const unlisten = await listen < T > ( event , handler ) ;
189+ if ( cancelled ) {
190+ unlisten ( ) ;
191+ } else {
192+ unlisteners . push ( unlisten ) ;
193+ }
194+ } ;
195+
196+ void registerListener < ApprovalRequest > ( 'codex/approval-request' , ( event ) => {
197+ addApproval ( event . payload ) ;
198+ } ) ;
199+
200+ void registerListener < RequestUserInputRequest > ( 'codex/request-user-input' , ( event ) => {
201+ addRequest ( event . payload ) ;
202+ } ) ;
203+
204+ void registerListener < ServerNotification > ( 'codex:notification' , ( event ) => {
205+ handleServerNotification ( event . payload ) ;
206+ } ) ;
207+
208+ void registerListener < CodexStderrEvent > ( 'codex:stderr' , ( event ) => {
209+ console . error ( '[useCodexEvents] codex stderr:' , event . payload . message ) ;
210+ } ) ;
211+
212+ void registerListener < CodexParseErrorEvent > ( 'codex:parseError' , ( event ) => {
213+ console . error ( '[useCodexEvents] codex parseError:' , event . payload . error , event . payload . raw ) ;
214+ } ) ;
197215
198216 return ( ) => {
199- Promise . all ( unlistenPromises ) . then ( ( unlisteners ) => {
200- unlisteners . forEach ( ( unlisten ) => unlisten ( ) ) ;
201- } ) ;
217+ cancelled = true ;
218+ unlisteners . forEach ( ( unlisten ) => unlisten ( ) ) ;
202219 } ;
203220 }
204221
@@ -245,15 +262,5 @@ export function useCodexEvents(enabled = true) {
245262 return ( ) => {
246263 es . close ( ) ;
247264 } ;
248- } , [
249- addEvent ,
250- addApproval ,
251- addRequest ,
252- setHasAccount ,
253- enabled ,
254- taskCompleteBeepMode ,
255- preventSleepDuringTasks ,
256- showReasoning ,
257- isChatInterfaceActive ,
258- ] ) ;
265+ } , [ addEvent , addApproval , addRequest , setHasAccount , enabled ] ) ;
259266}
0 commit comments