on main help sign in
feat(tools): crush_info tool for readling live config
commit27727343e0ce15d0f29d7d575e88d8566ebaf566 authorChristian Rocha <christian@rocha.is> dateSun 5 Apr 2026, 15:34 (6 months ago) parent18823f3
7 files changed+40−11
internal/agent/coordinator.go+1 internal/config/config.go+1 internal/config/load.go+12−4 internal/config/load_bench_test.go+3−3 internal/config/load_test.go+2−2 internal/config/store.go+16−2 internal/lsp/client.go+5
internal/agent/coordinator.go+1
@@ -447,6 +447,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
447447
448448 allTools = append(allTools,
449449 tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelName),
450+ tools.NewCrushInfoTool(c.cfg, c.lspManager),
450451 tools.NewJobOutputTool(),
451452 tools.NewJobKillTool(),
452453 tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
internal/config/config.go+1
@@ -462,6 +462,7 @@ func allToolNames() []string {
462462 return []string{
463463 "agent",
464464 "bash",
465+ "crush_info",
465466 "job_output",
466467 "job_kill",
467468 "download",
internal/config/load.go+12−4
@@ -33,7 +33,7 @@ const defaultCatwalkURL = "https://catwalk.charm.sh"
3333func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
3434 configPaths := lookupConfigs(workingDir)
3535
36− cfg, err := loadFromConfigPaths(configPaths)
36+ cfg, loadedPaths, err := loadFromConfigPaths(configPaths)
3737 if err != nil {
3838 return nil, fmt.Errorf("failed to load config from paths %v: %w", configPaths, err)
3939 }
@@ -45,6 +45,7 @@ func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
4545 workingDir: workingDir,
4646 globalDataPath: GlobalConfigData(),
4747 workspacePath: filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf("%s.json", appName)),
48+ loadedPaths: loadedPaths,
4849 }
4950
5051 if debug {
@@ -60,6 +61,7 @@ func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
6061 *cfg = *merged
6162 cfg.setDefaults(workingDir, dataDir)
6263 store.config = cfg
64+ store.loadedPaths = append(store.loadedPaths, store.workspacePath)
6365 }
6466 }
6567
@@ -669,8 +671,9 @@ func lookupConfigs(cwd string) []string {
669671 return append(configPaths, foundConfigs...)
670672}
671673
672−func loadFromConfigPaths(configPaths []string) (*Config, error) {
674+func loadFromConfigPaths(configPaths []string) (*Config, []string, error) {
673675 var configs [][]byte
676+ var loaded []string
674677
675678 for _, path := range configPaths {
676679 data, err := os.ReadFile(path)
@@ -678,15 +681,20 @@ func loadFromConfigPaths(configPaths []string) (*Config, error) {
678681 if os.IsNotExist(err) {
679682 continue
680683 }
681− return nil, fmt.Errorf("failed to open config file %s: %w", path, err)
684+ return nil, nil, fmt.Errorf("failed to open config file %s: %w", path, err)
682685 }
683686 if len(data) == 0 {
684687 continue
685688 }
686689 configs = append(configs, data)
690+ loaded = append(loaded, path)
687691 }
688692
689− return loadFromBytes(configs)
693+ cfg, err := loadFromBytes(configs)
694+ if err != nil {
695+ return nil, nil, err
696+ }
697+ return cfg, loaded, nil
690698}
691699
692700func loadFromBytes(configs [][]byte) (*Config, error) {
internal/config/load_bench_test.go+3−3
@@ -53,7 +53,7 @@ func BenchmarkLoadFromConfigPaths(b *testing.B) {
5353
5454 b.ReportAllocs()
5555 for b.Loop() {
56− _, err := loadFromConfigPaths(configPaths)
56+ _, _, err := loadFromConfigPaths(configPaths)
5757 if err != nil {
5858 b.Fatal(err)
5959 }
@@ -78,7 +78,7 @@ func BenchmarkLoadFromConfigPaths_MissingFiles(b *testing.B) {
7878
7979 b.ReportAllocs()
8080 for b.Loop() {
81− _, err := loadFromConfigPaths(configPaths)
81+ _, _, err := loadFromConfigPaths(configPaths)
8282 if err != nil {
8383 b.Fatal(err)
8484 }
@@ -95,7 +95,7 @@ func BenchmarkLoadFromConfigPaths_Empty(b *testing.B) {
9595
9696 b.ReportAllocs()
9797 for b.Loop() {
98− _, err := loadFromConfigPaths(configPaths)
98+ _, _, err := loadFromConfigPaths(configPaths)
9999 if err != nil {
100100 b.Fatal(err)
101101 }
internal/config/load_test.go+2−2
@@ -490,7 +490,7 @@ func TestConfig_setupAgentsWithDisabledTools(t *testing.T) {
490490 coderAgent, ok := cfg.Agents[AgentCoder]
491491 require.True(t, ok)
492492
493− assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "glob", "ls", "sourcegraph", "todos", "view", "write", "list_mcp_resources", "read_mcp_resource"}, coderAgent.AllowedTools)
493+ assert.Equal(t, []string{"agent", "bash", "crush_info", "job_output", "job_kill", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "glob", "ls", "sourcegraph", "todos", "view", "write", "list_mcp_resources", "read_mcp_resource"}, coderAgent.AllowedTools)
494494
495495 taskAgent, ok := cfg.Agents[AgentTask]
496496 require.True(t, ok)
@@ -513,7 +513,7 @@ func TestConfig_setupAgentsWithEveryReadOnlyToolDisabled(t *testing.T) {
513513 cfg.SetupAgents()
514514 coderAgent, ok := cfg.Agents[AgentCoder]
515515 require.True(t, ok)
516− assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "download", "edit", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "todos", "write", "list_mcp_resources", "read_mcp_resource"}, coderAgent.AllowedTools)
516+ assert.Equal(t, []string{"agent", "bash", "crush_info", "job_output", "job_kill", "download", "edit", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "todos", "write", "list_mcp_resources", "read_mcp_resource"}, coderAgent.AllowedTools)
517517
518518 taskAgent, ok := cfg.Agents[AgentTask]
519519 require.True(t, ok)
internal/config/store.go+16−2
@@ -32,8 +32,9 @@ type ConfigStore struct {
3232 config *Config
3333 workingDir string
3434 resolver VariableResolver
35− globalDataPath string // ~/.local/share/crush/crush.json
36− workspacePath string // .crush/crush.json
35+ globalDataPath string // ~/.local/share/crush/crush.json
36+ workspacePath string // .crush/crush.json
37+ loadedPaths []string // config files that were successfully loaded
3738 knownProviders []catwalk.Provider
3839 overrides RuntimeOverrides
3940}
@@ -76,6 +77,11 @@ func (s *ConfigStore) Overrides() *RuntimeOverrides {
7677 return &s.overrides
7778}
7879
80+// LoadedPaths returns the config file paths that were successfully loaded.
81+func (s *ConfigStore) LoadedPaths() []string {
82+ return slices.Clone(s.loadedPaths)
83+}
84+
7985// configPath returns the file path for the given scope.
8086func (s *ConfigStore) configPath(scope Scope) (string, error) {
8187 switch scope {
@@ -337,6 +343,14 @@ func (s *ConfigStore) recordRecentModel(scope Scope, modelType SelectedModelType
337343 return nil
338344}
339345
346+// NewTestStore creates a ConfigStore for testing purposes.
347+func NewTestStore(cfg *Config, loadedPaths ...string) *ConfigStore {
348+ return &ConfigStore{
349+ config: cfg,
350+ loadedPaths: loadedPaths,
351+ }
352+}
353+
340354// ImportCopilot attempts to import a GitHub Copilot token from disk.
341355func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
342356 if s.HasConfigField(ScopeGlobal, "providers.copilot.api_key") || s.HasConfigField(ScopeGlobal, "providers.copilot.oauth") {
internal/lsp/client.go+5
@@ -286,6 +286,11 @@ func (c *Client) GetName() string {
286286 return c.name
287287}
288288
289+// FileTypes returns the file types this LSP client handles
290+func (c *Client) FileTypes() []string {
291+ return c.fileTypes
292+}
293+
289294// SetDiagnosticsCallback sets the callback function for diagnostic changes
290295func (c *Client) SetDiagnosticsCallback(callback func(name string, count int)) {
291296 c.onDiagnosticsChanged = callback