Detailed changes
@@ -447,6 +447,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
allTools = append(allTools,
tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelName),
+ tools.NewCrushInfoTool(c.cfg, c.lspManager),
tools.NewJobOutputTool(),
tools.NewJobKillTool(),
tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
@@ -462,6 +462,7 @@ func allToolNames() []string {
return []string{
"agent",
"bash",
+ "crush_info",
"job_output",
"job_kill",
"download",
@@ -33,7 +33,7 @@ const defaultCatwalkURL = "https://catwalk.charm.sh"
func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
configPaths := lookupConfigs(workingDir)
- cfg, err := loadFromConfigPaths(configPaths)
+ cfg, loadedPaths, err := loadFromConfigPaths(configPaths)
if err != nil {
return nil, fmt.Errorf("failed to load config from paths %v: %w", configPaths, err)
}
@@ -45,6 +45,7 @@ func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
workingDir: workingDir,
globalDataPath: GlobalConfigData(),
workspacePath: filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf("%s.json", appName)),
+ loadedPaths: loadedPaths,
}
if debug {
@@ -60,6 +61,7 @@ func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
*cfg = *merged
cfg.setDefaults(workingDir, dataDir)
store.config = cfg
+ store.loadedPaths = append(store.loadedPaths, store.workspacePath)
}
}
@@ -669,8 +671,9 @@ func lookupConfigs(cwd string) []string {
return append(configPaths, foundConfigs...)
}
-func loadFromConfigPaths(configPaths []string) (*Config, error) {
+func loadFromConfigPaths(configPaths []string) (*Config, []string, error) {
var configs [][]byte
+ var loaded []string
for _, path := range configPaths {
data, err := os.ReadFile(path)
@@ -678,15 +681,20 @@ func loadFromConfigPaths(configPaths []string) (*Config, error) {
if os.IsNotExist(err) {
continue
}
- return nil, fmt.Errorf("failed to open config file %s: %w", path, err)
+ return nil, nil, fmt.Errorf("failed to open config file %s: %w", path, err)
}
if len(data) == 0 {
continue
}
configs = append(configs, data)
+ loaded = append(loaded, path)
}
- return loadFromBytes(configs)
+ cfg, err := loadFromBytes(configs)
+ if err != nil {
+ return nil, nil, err
+ }
+ return cfg, loaded, nil
}
func loadFromBytes(configs [][]byte) (*Config, error) {
@@ -53,7 +53,7 @@ func BenchmarkLoadFromConfigPaths(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
- _, err := loadFromConfigPaths(configPaths)
+ _, _, err := loadFromConfigPaths(configPaths)
if err != nil {
b.Fatal(err)
}
@@ -78,7 +78,7 @@ func BenchmarkLoadFromConfigPaths_MissingFiles(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
- _, err := loadFromConfigPaths(configPaths)
+ _, _, err := loadFromConfigPaths(configPaths)
if err != nil {
b.Fatal(err)
}
@@ -95,7 +95,7 @@ func BenchmarkLoadFromConfigPaths_Empty(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
- _, err := loadFromConfigPaths(configPaths)
+ _, _, err := loadFromConfigPaths(configPaths)
if err != nil {
b.Fatal(err)
}
@@ -490,7 +490,7 @@ func TestConfig_setupAgentsWithDisabledTools(t *testing.T) {
coderAgent, ok := cfg.Agents[AgentCoder]
require.True(t, ok)
- 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)
+ 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)
taskAgent, ok := cfg.Agents[AgentTask]
require.True(t, ok)
@@ -513,7 +513,7 @@ func TestConfig_setupAgentsWithEveryReadOnlyToolDisabled(t *testing.T) {
cfg.SetupAgents()
coderAgent, ok := cfg.Agents[AgentCoder]
require.True(t, ok)
- 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)
+ 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)
taskAgent, ok := cfg.Agents[AgentTask]
require.True(t, ok)
@@ -32,8 +32,9 @@ type ConfigStore struct {
config *Config
workingDir string
resolver VariableResolver
- globalDataPath string // ~/.local/share/crush/crush.json
- workspacePath string // .crush/crush.json
+ globalDataPath string // ~/.local/share/crush/crush.json
+ workspacePath string // .crush/crush.json
+ loadedPaths []string // config files that were successfully loaded
knownProviders []catwalk.Provider
overrides RuntimeOverrides
}
@@ -76,6 +77,11 @@ func (s *ConfigStore) Overrides() *RuntimeOverrides {
return &s.overrides
}
+// LoadedPaths returns the config file paths that were successfully loaded.
+func (s *ConfigStore) LoadedPaths() []string {
+ return slices.Clone(s.loadedPaths)
+}
+
// configPath returns the file path for the given scope.
func (s *ConfigStore) configPath(scope Scope) (string, error) {
switch scope {
@@ -337,6 +343,14 @@ func (s *ConfigStore) recordRecentModel(scope Scope, modelType SelectedModelType
return nil
}
+// NewTestStore creates a ConfigStore for testing purposes.
+func NewTestStore(cfg *Config, loadedPaths ...string) *ConfigStore {
+ return &ConfigStore{
+ config: cfg,
+ loadedPaths: loadedPaths,
+ }
+}
+
// ImportCopilot attempts to import a GitHub Copilot token from disk.
func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
if s.HasConfigField(ScopeGlobal, "providers.copilot.api_key") || s.HasConfigField(ScopeGlobal, "providers.copilot.oauth") {
@@ -286,6 +286,11 @@ func (c *Client) GetName() string {
return c.name
}
+// FileTypes returns the file types this LSP client handles
+func (c *Client) FileTypes() []string {
+ return c.fileTypes
+}
+
// SetDiagnosticsCallback sets the callback function for diagnostic changes
func (c *Client) SetDiagnosticsCallback(callback func(name string, count int)) {
c.onDiagnosticsChanged = callback