@@ -40,22 +40,22 @@ type terminalRefreshResult struct {
4040
4141type terminalRefreshLoader func (context.Context , * terminalRefreshRequest ) terminalRefreshSnapshot
4242
43- func (app * App ) requestTerminalRefresh (ctx context.Context ) {
43+ func (app * App ) requestTerminalRefresh (ctx context.Context ) <- chan struct {} {
4444 // Keep the parent task/workflow summary stable while inspecting a child.
4545 if len (app .agentTaskSessionStack ) > 0 {
46- return
46+ return nil
4747 }
4848
4949 if app .refreshInFlight {
5050 app .refreshPending = true
5151
5252 app .logTerminalRefresh ("coalesce" , 0 , "pending" )
5353
54- return
54+ return nil
5555 }
5656
5757 if app .runtime == nil || app .sessionID == "" {
58- return
58+ return nil
5959 }
6060
6161 app .refreshGeneration ++
@@ -73,8 +73,11 @@ func (app *App) requestTerminalRefresh(ctx context.Context) {
7373
7474 screen := app .screen
7575 diagnostics := app .refreshDiagnostics
76+ done := make (chan struct {})
7677
7778 go func () {
79+ defer close (done )
80+
7881 refreshCtx , cancel := context .WithTimeout (loadCtx , terminalRefreshTimeout )
7982 defer cancel ()
8083
@@ -93,6 +96,8 @@ func (app *App) requestTerminalRefresh(ctx context.Context) {
9396 logTerminalRefreshTimings (diagnostics , result , outcome )
9497 postTerminalRefreshResult (ctx , screen , result )
9598 }()
99+
100+ return done
96101}
97102
98103func refreshOutcome (result * terminalRefreshResult ) string {
@@ -133,10 +138,37 @@ func postTerminalRefreshResult(ctx context.Context, screen terminalScreen, resul
133138 return
134139 }
135140
141+ stop := screenStop (screen )
142+ select {
143+ case <- stop :
144+ return
145+ default :
146+ }
147+
148+ postTerminalRefreshEvent (ctx , screen .EventQ (), stop , tcell .NewEventInterrupt (result ))
149+ }
150+
151+ func postTerminalRefreshEvent (
152+ ctx context.Context ,
153+ events chan <- tcell.Event ,
154+ stop <- chan struct {},
155+ event tcell.Event ,
156+ ) (posted bool ) {
157+ // Test screens may close their event queue during shutdown. Treat that race
158+ // like a stopped screen instead of crashing the refresh worker.
159+ defer func () {
160+ if recover () != nil {
161+ posted = false
162+ }
163+ }()
164+
136165 select {
137- case screen .EventQ () <- tcell .NewEventInterrupt (result ):
166+ case events <- event :
167+ return true
138168 case <- ctx .Done ():
139- case <- screenStop (screen ):
169+ return false
170+ case <- stop :
171+ return false
140172 }
141173}
142174
@@ -290,17 +322,10 @@ func (diagnostics *terminalRefreshDiagnostics) log(
290322 outcome string ,
291323 count int ,
292324) {
293- attributes := []any {
294- slog .String ("operation" , operation ),
295- slog .Duration ("duration" , duration ),
296- slog .String ("outcome" , outcome ),
297- }
298- if count > 0 {
299- attributes = append (attributes , slog .Int ("count" , count ))
300- }
301-
302325 logger := slog .Default ()
303- logger .Debug ("terminal refresh" , attributes ... )
326+ if logger .Enabled (context .Background (), slog .LevelDebug ) {
327+ logger .Debug ("terminal refresh" , terminalRefreshAttributes (operation , duration , outcome , count )... )
328+ }
304329
305330 if duration < terminalSlowOperationThreshold {
306331 return
@@ -324,6 +349,20 @@ func (diagnostics *terminalRefreshDiagnostics) log(
324349 diagnostics .suppressed [key ] = 0
325350 diagnostics .Unlock ()
326351
352+ attributes := terminalRefreshAttributes (operation , duration , outcome , count )
327353 attributes = append (attributes , slog .Int ("suppressed_count" , suppressed ))
328354 logger .Warn ("terminal refresh slow" , attributes ... )
329355}
356+
357+ func terminalRefreshAttributes (operation string , duration time.Duration , outcome string , count int ) []any {
358+ attributes := []any {
359+ slog .String ("operation" , operation ),
360+ slog .Duration ("duration" , duration ),
361+ slog .String ("outcome" , outcome ),
362+ }
363+ if count > 0 {
364+ attributes = append (attributes , slog .Int ("count" , count ))
365+ }
366+
367+ return attributes
368+ }
0 commit comments