@@ -30,7 +30,7 @@ describe('HudFileBridge', () => {
3030
3131 it ( 'emits MODE_CHANGED when currentMode changes in file' , async ( ) => {
3232 writeHudState ( hudFile , { currentMode : 'PLAN' } ) ;
33- bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 } ) ;
33+ bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 , pollIntervalMs : 50 } ) ;
3434 bridge . start ( ) ;
3535
3636 const modeChanges : Array < { from : string | null ; to : string } > = [ ] ;
@@ -61,7 +61,7 @@ describe('HudFileBridge', () => {
6161
6262 it ( 'emits AGENT_ACTIVATED when activeAgent changes' , async ( ) => {
6363 writeHudState ( hudFile , { currentMode : 'PLAN' , activeAgent : null } ) ;
64- bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 } ) ;
64+ bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 , pollIntervalMs : 50 } ) ;
6565 bridge . start ( ) ;
6666
6767 const activations : unknown [ ] = [ ] ;
@@ -107,13 +107,16 @@ describe('HudFileBridge', () => {
107107 const modeChanges : Array < { from : string | null ; to : string } > = [ ] ;
108108 eventBus . on ( TUI_EVENTS . MODE_CHANGED , p => modeChanges . push ( p ) ) ;
109109
110- // Simulate watcher error to trigger polling fallback
111- // Access internal watcher and emit error
110+ // Simulate watcher error — polling is already running (hybrid mode)
112111 const watcher = ( bridge as unknown as { watcher : fs . FSWatcher | null } ) . watcher ;
113112 expect ( watcher ) . not . toBeNull ( ) ;
114113 watcher ! . emit ( 'error' , new Error ( 'EPERM' ) ) ;
115114
116- // Write new state — polling should detect the change
115+ // Watcher should be cleaned up after error
116+ const bridgeInternal = bridge as unknown as { watcher : fs . FSWatcher | null } ;
117+ expect ( bridgeInternal . watcher ) . toBeNull ( ) ;
118+
119+ // Write new state — polling safety net should detect the change
117120 await sleep ( 100 ) ;
118121 writeHudState ( hudFile , { currentMode : 'ACT' } ) ;
119122 await sleep ( 300 ) ;
@@ -122,6 +125,31 @@ describe('HudFileBridge', () => {
122125 expect ( modeChanges [ modeChanges . length - 1 ] ) . toEqual ( { from : 'PLAN' , to : 'ACT' } ) ;
123126 } ) ;
124127
128+ it ( 'runs hybrid mode — both watch and poll active simultaneously' , async ( ) => {
129+ writeHudState ( hudFile , { currentMode : 'PLAN' } ) ;
130+ bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 , pollIntervalMs : 50 } ) ;
131+ bridge . start ( ) ;
132+
133+ const bridgeInternal = bridge as unknown as {
134+ watcher : fs . FSWatcher | null ;
135+ pollInterval : ReturnType < typeof setInterval > | null ;
136+ } ;
137+
138+ // Both watcher and polling should be active (hybrid mode)
139+ expect ( bridgeInternal . watcher ) . not . toBeNull ( ) ;
140+ expect ( bridgeInternal . pollInterval ) . not . toBeNull ( ) ;
141+
142+ // Polling alone should detect changes even if fs.watch is silent
143+ const modeChanges : Array < { from : string | null ; to : string } > = [ ] ;
144+ eventBus . on ( TUI_EVENTS . MODE_CHANGED , p => modeChanges . push ( p ) ) ;
145+
146+ writeHudState ( hudFile , { currentMode : 'EVAL' } ) ;
147+ await sleep ( 300 ) ;
148+
149+ expect ( modeChanges . length ) . toBeGreaterThanOrEqual ( 1 ) ;
150+ expect ( modeChanges [ modeChanges . length - 1 ] ) . toEqual ( { from : 'PLAN' , to : 'EVAL' } ) ;
151+ } ) ;
152+
125153 it ( 'handles malformed JSON gracefully' , async ( ) => {
126154 writeHudState ( hudFile , { currentMode : 'PLAN' } ) ;
127155 bridge = new HudFileBridge ( eventBus , hudFile , { debounceMs : 10 } ) ;
0 commit comments