11package commands
22
33import (
4+ "cmp"
45 "context"
56 "fmt"
67 "slices"
@@ -11,6 +12,7 @@ import (
1112 "github.com/docker/docker-agent/pkg/app"
1213 "github.com/docker/docker-agent/pkg/config/types"
1314 "github.com/docker/docker-agent/pkg/feedback"
15+ "github.com/docker/docker-agent/pkg/tools"
1416 mcptools "github.com/docker/docker-agent/pkg/tools/mcp"
1517 "github.com/docker/docker-agent/pkg/tui/components/toolcommon"
1618 "github.com/docker/docker-agent/pkg/tui/core"
@@ -26,6 +28,18 @@ type Category struct {
2628 Commands []Item
2729}
2830
31+ // ArgumentCandidate is one completable value for a command's argument,
32+ // e.g. a toolset name for /toolset-restart. It is intentionally free of any
33+ // completion-UI or app-domain types so pkg/tui/commands stays decoupled from
34+ // both pkg/tui/components/completion and pkg/app.
35+ type ArgumentCandidate struct {
36+ Label string
37+ Description string
38+ // Disabled marks a candidate that is shown for context but cannot be
39+ // submitted (e.g. a non-restartable toolset for /toolset-restart).
40+ Disabled bool
41+ }
42+
2943// Item represents a single command in the palette
3044type Item struct {
3145 ID string
@@ -38,6 +52,11 @@ type Item struct {
3852 // Immediate marks commands that should run as soon as they are submitted
3953 // instead of being treated as ordinary queued chat input.
4054 Immediate bool
55+ // CompleteArgument, when set, returns the candidates for this command's
56+ // argument. Called lazily at completion-popup-open time so results
57+ // reflect current runtime state (e.g. toolset lifecycle). Nil for
58+ // commands with no argument completion.
59+ CompleteArgument func () []ArgumentCandidate
4160}
4261
4362func builtInSessionCommands () []Item {
@@ -577,13 +596,72 @@ func newMCPPromptItem(promptName string, promptInfo mcptools.PromptInfo) Item {
577596 }
578597}
579598
599+ // toolsetStatusSource is the minimal surface toolsetRestartCandidates needs.
600+ // *app.App satisfies it; tests can supply a stub instead of constructing a
601+ // full application.
602+ type toolsetStatusSource interface {
603+ CurrentAgentToolsetStatuses () []tools.ToolsetStatus
604+ }
605+
606+ // toolsetRestartCandidates returns one ArgumentCandidate per toolset of the
607+ // current agent, in declaration order, deduplicated by name (preferring the
608+ // restartable entry when duplicate names disagree — display names are not
609+ // guaranteed unique, mirroring runtime.RestartToolset's own matching).
610+ // Non-restartable toolsets are marked Disabled rather than omitted, so the
611+ // popup teaches users the full toolset inventory and why a restart isn't
612+ // offered for some of them.
613+ func toolsetRestartCandidates (source toolsetStatusSource ) []ArgumentCandidate {
614+ statuses := source .CurrentAgentToolsetStatuses ()
615+ byName := make (map [string ]tools.ToolsetStatus , len (statuses ))
616+ order := make ([]string , 0 , len (statuses ))
617+ for _ , s := range statuses {
618+ if s .Name == "" {
619+ continue
620+ }
621+ if existing , ok := byName [s .Name ]; ! ok {
622+ byName [s .Name ] = s
623+ order = append (order , s .Name )
624+ } else if ! existing .Restartable && s .Restartable {
625+ byName [s .Name ] = s
626+ }
627+ }
628+
629+ candidates := make ([]ArgumentCandidate , 0 , len (order ))
630+ for _ , name := range order {
631+ s := byName [name ]
632+ candidates = append (candidates , ArgumentCandidate {
633+ Label : s .Name ,
634+ Description : cmp .Or (s .Kind , "Built-in" ) + " · " + s .State .String (),
635+ Disabled : ! s .Restartable ,
636+ })
637+ }
638+ return candidates
639+ }
640+
641+ // attachToolsetRestartCompletion wires the toolset-name argument completer
642+ // onto the /toolset-restart item. Attached post-hoc (rather than inline in
643+ // builtInSessionCommands) so that function stays free of any status-source
644+ // dependency.
645+ func attachToolsetRestartCompletion (items []Item , source toolsetStatusSource ) {
646+ for i := range items {
647+ if items [i ].ID != "session.toolset.restart" {
648+ continue
649+ }
650+ items [i ].CompleteArgument = func () []ArgumentCandidate {
651+ return toolsetRestartCandidates (source )
652+ }
653+ return
654+ }
655+ }
656+
580657// BuildCommandCategories builds the list of command categories for the command palette
581658func BuildCommandCategories (ctx context.Context , application * app.App ) []Category {
582659 // Get session commands and filter based on model capabilities
583660 sessionCommands := builtInSessionCommands ()
584661 if ! application .SnapshotsEnabled () {
585662 sessionCommands = removeByIDs (sessionCommands , snapshotCommandIDs )
586663 }
664+ attachToolsetRestartCompletion (sessionCommands , application )
587665
588666 categories := []Category {
589667 {
0 commit comments