@@ -15,6 +15,7 @@ import (
1515 "github.com/docker/cagent/pkg/runtime"
1616 "github.com/docker/cagent/pkg/session"
1717 "github.com/docker/cagent/pkg/tools"
18+ chatmsgs "github.com/docker/cagent/pkg/tui/components/messages"
1819 "github.com/docker/cagent/pkg/tui/components/scrollbar"
1920 "github.com/docker/cagent/pkg/tui/components/spinner"
2021 "github.com/docker/cagent/pkg/tui/components/tab"
@@ -89,6 +90,7 @@ type model struct {
8990 scrollbar * scrollbar.Model
9091 workingDirectory string
9192 queuedMessages []string // Truncated preview of queued messages
93+ streamCancelled bool // true after ESC cancel until next StreamStartedEvent
9294}
9395
9496// Option is a functional option for configuring the sidebar.
@@ -305,12 +307,20 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
305307 m .SetTokenUsage (msg )
306308 return m , nil
307309 case * runtime.MCPInitStartedEvent :
310+ // Ignore if stream was cancelled (stale event from before cancellation)
311+ if m .streamCancelled {
312+ return m , nil
313+ }
308314 m .mcpInit = true
309315 return m , m .spinner .Init ()
310316 case * runtime.MCPInitFinishedEvent :
311317 m .mcpInit = false
312318 return m , nil
313319 case * runtime.RAGIndexingStartedEvent :
320+ // Ignore if stream was cancelled (stale event from before cancellation)
321+ if m .streamCancelled {
322+ return m , nil
323+ }
314324 // Use composite key: "ragName/strategyName" to differentiate strategies within same RAG manager
315325 key := msg .RAGName + "/" + msg .StrategyName
316326 slog .Debug ("Sidebar received RAG indexing started event" , "rag" , msg .RAGName , "strategy" , msg .StrategyName , "key" , key )
@@ -336,6 +346,8 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
336346 m .sessionTitle = msg .Title
337347 return m , nil
338348 case * runtime.StreamStartedEvent :
349+ // New stream starting - reset cancelled flag and enable spinner
350+ m .streamCancelled = false
339351 m .workingAgent = msg .AgentName
340352 return m , m .spinner .Init ()
341353 case * runtime.StreamStoppedEvent :
@@ -351,11 +363,26 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
351363 m .SetAgentSwitching (msg .Switching )
352364 return m , nil
353365 case * runtime.ToolsetInfoEvent :
366+ // Ignore loading state if stream was cancelled (stale event from before cancellation)
367+ if m .streamCancelled && msg .Loading {
368+ return m , nil
369+ }
354370 m .SetToolsetInfo (msg .AvailableTools , msg .Loading )
355371 if msg .Loading {
356372 return m , m .spinner .Init ()
357373 }
358374 return m , nil
375+ case chatmsgs.StreamCancelledMsg :
376+ // Clear all spinner-driving state when stream is cancelled via ESC
377+ m .streamCancelled = true
378+ m .workingAgent = ""
379+ m .toolsLoading = false
380+ m .mcpInit = false
381+ // Clear any in-flight RAG indexing state
382+ for k := range m .ragIndexing {
383+ delete (m .ragIndexing , k )
384+ }
385+ return m , nil
359386 default :
360387 var cmds []tea.Cmd
361388
0 commit comments