@@ -46,6 +46,7 @@ export type FleetUICtx = {
4646type MainEntry = { kind : "main" } ;
4747type AgentEntry = { kind : "agent" ; record : AgentRecord } ;
4848type FleetEntry = MainEntry | AgentEntry ;
49+ type FleetTUI = { focusedComponent ?: unknown ; requestRender ( force ?: boolean ) : void } ;
4950
5051/** `11s` — integer seconds, no decimal/suffix (matches Claude Code, unlike formatMs). */
5152export function formatFleetElapsed ( ms : number ) : string {
@@ -76,8 +77,9 @@ function rightAlign(left: string, right: string, width: number): string {
7677
7778export class FleetList {
7879 private ui : FleetUICtx | undefined ;
79- private tui : any | undefined ;
80+ private tui : FleetTUI | undefined ;
8081 private inputUnsub : ( ( ) => void ) | undefined ;
82+ private pendingOpen : ReturnType < typeof setImmediate > | undefined ;
8183 private widgetRegistered = false ;
8284 private timer : ReturnType < typeof setInterval > | undefined ;
8385
@@ -89,6 +91,7 @@ export class FleetList {
8991 /** Set while a conversation overlay is open; calling it closes the overlay. */
9092 private viewerClose : ( ( ) => void ) | undefined ;
9193 private viewingAgentId : string | undefined ;
94+ private restoreActiveAfterViewer = false ;
9295
9396 constructor (
9497 private manager : AgentManager ,
@@ -100,13 +103,17 @@ export class FleetList {
100103 setEnabled ( enabled : boolean ) : void {
101104 if ( enabled === this . enabled ) return ;
102105 this . enabled = enabled ;
103- if ( ! enabled ) this . active = false ;
106+ if ( ! enabled ) {
107+ this . active = false ;
108+ this . cancelPendingOpen ( ) ;
109+ }
104110 this . update ( ) ;
105111 }
106112
107113 /** Capture the UI context and (re)register the global input handler. */
108114 setUICtx ( ui : FleetUICtx ) : void {
109115 if ( ui === this . ui ) return ;
116+ this . cancelPendingOpen ( ) ;
110117 this . inputUnsub ?.( ) ;
111118 this . ui = ui ;
112119 this . widgetRegistered = false ;
@@ -128,6 +135,7 @@ export class FleetList {
128135 }
129136
130137 dispose ( ) : void {
138+ this . cancelPendingOpen ( ) ;
131139 if ( this . timer ) { clearInterval ( this . timer ) ; this . timer = undefined ; }
132140 this . inputUnsub ?.( ) ;
133141 this . inputUnsub = undefined ;
@@ -293,30 +301,48 @@ export class FleetList {
293301 }
294302 const session = record . session ;
295303 const activity = this . agentActivity . get ( record . id ) ;
304+ const ui = this . ui ;
305+ this . cancelPendingOpen ( ) ;
306+ this . restoreActiveAfterViewer = this . active ;
296307 this . viewingAgentId = record . id ;
308+ this . deactivate ( ) ;
309+ this . tui ?. requestRender ( true ) ;
310+
311+ this . pendingOpen = setImmediate ( ( ) => {
312+ this . pendingOpen = undefined ;
313+ if ( this . ui !== ui || this . viewerClose || this . viewingAgentId !== record . id ) return ;
314+
315+ void ui . custom < undefined > (
316+ ( tui , theme , keybindings , done ) => {
317+ this . viewerClose = ( ) => done ( undefined ) ;
318+ return new ConversationViewer (
319+ tui ,
320+ session ,
321+ record ,
322+ activity ,
323+ theme ,
324+ done ,
325+ ( ) => {
326+ if ( this . manager . abort ( record . id ) ) this . ui ?. notify ( `Stopped "${ record . description } ".` , "info" ) ;
327+ } ,
328+ keybindings ,
329+ ( message : string ) => this . manager . steer ( record . id , message ) ,
330+ ) ;
331+ } ,
332+ {
333+ overlay : true ,
334+ overlayOptions : { anchor : "center" , width : "90%" , maxHeight : `${ VIEWPORT_HEIGHT_PCT } %` } ,
335+ } ,
336+ ) . then ( ( ) => this . clearViewer ( ) , ( ) => this . clearViewer ( ) ) ;
337+ } ) ;
338+ }
297339
298- void this . ui . custom < undefined > (
299- ( tui , theme , keybindings , done ) => {
300- this . viewerClose = ( ) => done ( undefined ) ;
301- return new ConversationViewer (
302- tui ,
303- session ,
304- record ,
305- activity ,
306- theme ,
307- done ,
308- ( ) => {
309- if ( this . manager . abort ( record . id ) ) this . ui ?. notify ( `Stopped "${ record . description } ".` , "info" ) ;
310- } ,
311- keybindings ,
312- ( message : string ) => this . manager . steer ( record . id , message ) ,
313- ) ;
314- } ,
315- {
316- overlay : true ,
317- overlayOptions : { anchor : "center" , width : "90%" , maxHeight : `${ VIEWPORT_HEIGHT_PCT } %` } ,
318- } ,
319- ) . then ( ( ) => this . clearViewer ( ) , ( ) => this . clearViewer ( ) ) ;
340+ private cancelPendingOpen ( ) : void {
341+ if ( ! this . pendingOpen ) return ;
342+ clearImmediate ( this . pendingOpen ) ;
343+ this . pendingOpen = undefined ;
344+ this . viewingAgentId = undefined ;
345+ this . restoreActiveAfterViewer = false ;
320346 }
321347
322348 /** Reset overlay state and return to the list (on close, auto-close, or error). */
@@ -331,21 +357,22 @@ export class FleetList {
331357 }
332358 this . viewerClose = undefined ;
333359 this . viewingAgentId = undefined ;
360+ this . active = this . restoreActiveAfterViewer ;
361+ this . restoreActiveAfterViewer = false ;
334362 this . update ( ) ;
335363 }
336364
337365 // ---- Rendering ----
338366
339367 private renderBar ( width : number , theme : Theme ) : string [ ] {
368+ if ( ! this . active || this . viewerClose || ! this . editorHasFocus ( ) ) return [ ] ;
340369 const agents = this . roster ( ) . slice ( 1 ) as AgentEntry [ ] ;
341370 if ( agents . length === 0 ) return [ ] ;
342371 // Clamp locally so a render between a roster shrink and the next update()
343372 // (e.g. on terminal resize) never loses the selection marker.
344373 const sel = Math . min ( this . selectedIndex , agents . length ) ;
345374
346- const hint = this . active
347- ? "↑↓ select · enter view · esc back"
348- : "esc to interrupt · ← for agents · ↓ to manage" ;
375+ const hint = "↑↓ select · enter view · esc back" ;
349376 const lines : string [ ] = [ ] ;
350377 lines . push ( truncateToWidth ( " " + theme . fg ( "dim" , hint ) , width ) ) ;
351378 lines . push ( "" ) ;
0 commit comments