diff --git a/pkg/agent/commands.go b/pkg/agent/commands.go index a7b2f82..f8b40a6 100644 --- a/pkg/agent/commands.go +++ b/pkg/agent/commands.go @@ -131,26 +131,32 @@ func (s *agent) handleCommand(ctx context.Context, input string, handler EventHa }, "/agents": func(args []string) { - entries, err := os.ReadDir(s.agentsDir) - if err != nil { - handler(infoEvent(fmt.Sprintf("agents: %v", err))) - return - } + seen := make(map[string]bool) found := false - for _, e := range entries { - if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") { + for _, dir := range AgentsDirs() { + entries, err := os.ReadDir(dir) + if err != nil { continue } - name := strings.TrimSuffix(e.Name(), ".json") - marker := " " - if name == s.agentName { - marker = "* " + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") { + continue + } + name := strings.TrimSuffix(e.Name(), ".json") + if seen[name] { + continue + } + seen[name] = true + marker := " " + if name == s.agentName { + marker = "* " + } + handler(infoEvent(marker + name)) + found = true } - handler(infoEvent(marker + name)) - found = true } if !found { - handler(infoEvent("no agents found in " + s.agentsDir)) + handler(infoEvent("no agents found")) } }, diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 5268c5e..a34237c 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -137,9 +137,24 @@ func PromptsDirs() []string { return []string{DefaultPromptsDir()} } +// AgentsDirs returns all agent directories from OLLIE_AGENTS_PATH (colon-separated). +func AgentsDirs() []string { + if p := os.Getenv("OLLIE_AGENTS_PATH"); p != "" { + return strings.Split(p, ":") + } + return []string{paths.CfgDir() + "/agents"} +} + // AgentConfigPath resolves the config file path for a named agent. +// It searches all directories in OLLIE_AGENTS_PATH, falling back to agentsDir. func AgentConfigPath(agentsDir, name string) string { + for _, dir := range AgentsDirs() { + p := dir + "/" + name + ".json" + if _, err := os.Stat(p); err == nil { + return p + } + } return agentsDir + "/" + name + ".json" } diff --git a/pkg/agent/core_test.go b/pkg/agent/core_test.go index 3f6e68c..31fc9cc 100644 --- a/pkg/agent/core_test.go +++ b/pkg/agent/core_test.go @@ -1405,6 +1405,7 @@ func TestCommand_SP(t *testing.T) { func TestCommand_Agents_List(t *testing.T) { c := newCore(t, nil, nil) + t.Setenv("OLLIE_AGENTS_PATH", c.agentsDir) if err := os.WriteFile(c.agentsDir+"/myagent.json", []byte(`{}`), 0600); err != nil { t.Fatal(err) } diff --git a/pkg/env/env.go b/pkg/env/env.go index 31579c7..88ad4ab 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -14,6 +14,7 @@ import ( var managed = []string{ "OLLIE", "OLLIE_TOOLS_PATH", + "OLLIE_AGENTS_PATH", "OLLIE_SKILLS_PATH", "OLLIE_PROMPTS_PATH", "OLLIE_MEMORY_PATH", @@ -35,6 +36,7 @@ func EnsureDefaults() { defaults := map[string]string{ "OLLIE": filepath.Join(home, "mnt", "ollie"), "OLLIE_TOOLS_PATH": filepath.Join(home, ".config", "ollie", "tools"), + "OLLIE_AGENTS_PATH": filepath.Join(home, ".config", "ollie", "agents"), "OLLIE_SKILLS_PATH": filepath.Join(home, ".config", "ollie", "skills"), "OLLIE_PROMPTS_PATH": filepath.Join(home, ".config", "ollie", "prompts"), "OLLIE_MEMORY_PATH": filepath.Join(home, ".config", "ollie", "memory"), diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go index 7de6e19..b47f2e6 100644 --- a/pkg/env/env_test.go +++ b/pkg/env/env_test.go @@ -13,7 +13,7 @@ func TestEnsureDefaults(t *testing.T) { os.Unsetenv(k) } EnsureDefaults() - for _, k := range []string{"OLLIE", "OLLIE_TOOLS_PATH", "OLLIE_SKILLS_PATH", "OLLIE_PROMPTS_PATH", "OLLIE_MEMORY_PATH", "OLLIE_TMP_PATH", "OLLIE_TRANSCRIPT_PATH"} { + for _, k := range []string{"OLLIE", "OLLIE_TOOLS_PATH", "OLLIE_AGENTS_PATH", "OLLIE_SKILLS_PATH", "OLLIE_PROMPTS_PATH", "OLLIE_MEMORY_PATH", "OLLIE_TMP_PATH", "OLLIE_TRANSCRIPT_PATH"} { if v := os.Getenv(k); v == "" { t.Errorf("%s not set after EnsureDefaults", k) }