@@ -100,7 +100,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
100100 r .emitAgentWarnings (a , chanSend (events ))
101101 r .configureToolsetHandlers (a , events )
102102
103- agentTools , err := r .getTools (ctx , a , sessionSpan , events )
103+ agentTools , err := r .getTools (ctx , a , sessionSpan , events , true )
104104 if err != nil {
105105 events <- Error (fmt .Sprintf ("failed to get tools: %v" , err ))
106106 return
@@ -163,7 +163,7 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
163163 r .emitAgentWarnings (a , chanSend (events ))
164164 r .configureToolsetHandlers (a , events )
165165
166- agentTools , err := r .getTools (ctx , a , sessionSpan , events )
166+ agentTools , err := r .getTools (ctx , a , sessionSpan , events , true )
167167 if err != nil {
168168 events <- Error (fmt .Sprintf ("failed to get tools: %v" , err ))
169169 return
@@ -382,6 +382,20 @@ func (r *LocalRuntime) RunStream(ctx context.Context, sess *session.Session) <-c
382382
383383 r .processToolCalls (ctx , sess , res .Calls , agentTools , events )
384384
385+ // Re-probe toolsets after tool calls: an install/setup tool call may
386+ // have made a previously-unavailable LSP or MCP connectable. reprobe()
387+ // calls ensureToolSetsAreStarted, emits recovery notices, and updates
388+ // the TUI tool-count immediately.
389+ //
390+ // The new tools are picked up by the next iteration's getTools() call
391+ // at the top of this loop, so the model sees them on its very next
392+ // response — within the same user turn, without requiring a new user
393+ // message. reprobe's return value is intentionally discarded here;
394+ // the top-of-loop getTools() is the authoritative source.
395+ if len (res .Calls ) > 0 {
396+ r .reprobe (ctx , sess , a , agentTools , sessionSpan , events )
397+ }
398+
385399 // Check for degenerate tool call loops
386400 if loopDetector .record (res .Calls ) {
387401 toolName := "unknown"
@@ -575,17 +589,14 @@ func (r *LocalRuntime) compactIfNeeded(
575589 r .Summarize (ctx , sess , "" , events )
576590}
577591
578- // getTools executes tool retrieval with automatic OAuth handling
579- func (r * LocalRuntime ) getTools (ctx context.Context , a * agent.Agent , sessionSpan trace.Span , events chan Event ) ([]tools.Tool , error ) {
580- shouldEmitMCPInit := len (a .ToolSets ()) > 0
581- if shouldEmitMCPInit {
592+ // getTools executes tool retrieval with automatic OAuth handling.
593+ // emitLifecycleEvents controls whether MCPInitStarted/Finished are emitted;
594+ // pass false when calling from reprobe to avoid spurious TUI spinner flicker.
595+ func (r * LocalRuntime ) getTools (ctx context.Context , a * agent.Agent , sessionSpan trace.Span , events chan Event , emitLifecycleEvents bool ) ([]tools.Tool , error ) {
596+ if emitLifecycleEvents && len (a .ToolSets ()) > 0 {
582597 events <- MCPInitStarted (a .Name ())
598+ defer func () { events <- MCPInitFinished (a .Name ()) }()
583599 }
584- defer func () {
585- if shouldEmitMCPInit {
586- events <- MCPInitFinished (a .Name ())
587- }
588- }()
589600
590601 agentTools , err := a .Tools (ctx )
591602 if err != nil {
@@ -616,15 +627,15 @@ func (r *LocalRuntime) configureToolsetHandlers(a *agent.Agent, events chan Even
616627 }
617628}
618629
619- // emitAgentWarnings drains and emits any agent initialization warnings.
630+ // emitAgentWarnings drains and emits any pending toolset warnings as persistent
631+ // TUI notifications. Both start failures and recovery notices are emitted as
632+ // warnings so they remain visible until the user dismisses them.
620633func (r * LocalRuntime ) emitAgentWarnings (a * agent.Agent , send func (Event )) {
621634 warnings := a .DrainWarnings ()
622- if len (warnings ) == 0 {
623- return
635+ if len (warnings ) > 0 {
636+ slog .Warn ("Tool setup partially failed; continuing" , "agent" , a .Name (), "warnings" , warnings )
637+ send (Warning (formatToolWarning (a , warnings ), a .Name ()))
624638 }
625-
626- slog .Warn ("Tool setup partially failed; continuing" , "agent" , a .Name (), "warnings" , warnings )
627- send (Warning (formatToolWarning (a , warnings ), a .Name ()))
628639}
629640
630641func formatToolWarning (a * agent.Agent , warnings []string ) string {
@@ -669,3 +680,52 @@ func chanSend(ch chan Event) func(Event) {
669680 }
670681 }
671682}
683+
684+ // reprobe re-runs ensureToolSetsAreStarted after a batch of tool calls.
685+ // If new tools became available (by name-set diff), it emits recovery notices
686+ // and a ToolsetInfo event to update the TUI immediately. The new tools will be
687+ // picked up by the next iteration's getTools() call at the top of the loop.
688+ //
689+ // reprobe deliberately does NOT return the new tool list: the top-of-loop
690+ // getTools() is the single authoritative source for agentTools each iteration.
691+ func (r * LocalRuntime ) reprobe (
692+ ctx context.Context ,
693+ sess * session.Session ,
694+ a * agent.Agent ,
695+ currentTools []tools.Tool ,
696+ sessionSpan trace.Span ,
697+ events chan Event ,
698+ ) {
699+ updated , err := r .getTools (ctx , a , sessionSpan , events , false )
700+ if err != nil {
701+ slog .Warn ("reprobe: getTools failed" , "agent" , a .Name (), "error" , err )
702+ return
703+ }
704+ updated = filterExcludedTools (updated , sess .ExcludedTools )
705+
706+ // Emit any pending warnings/notices that getTools just generated.
707+ r .emitAgentWarnings (a , chanSend (events ))
708+
709+ // Compute added tools by comparing name-sets (not just counts), so we
710+ // correctly handle a toolset that replaced one tool with another.
711+ prev := make (map [string ]struct {}, len (currentTools ))
712+ for _ , t := range currentTools {
713+ prev [t .Name ] = struct {}{}
714+ }
715+ var added []string
716+ for _ , t := range updated {
717+ if _ , exists := prev [t .Name ]; ! exists {
718+ added = append (added , t .Name )
719+ }
720+ }
721+
722+ if len (added ) == 0 {
723+ return
724+ }
725+
726+ slog .Info ("New tools available after toolset re-probe" ,
727+ "agent" , a .Name (), "added" , added )
728+
729+ // Emit updated tool count to the TUI immediately.
730+ chanSend (events )(ToolsetInfo (len (updated ), false , a .Name ()))
731+ }
0 commit comments