diff --git a/internal/handlers/skills_test.go b/internal/handlers/skills_test.go index 7810df91..9baf2c07 100644 --- a/internal/handlers/skills_test.go +++ b/internal/handlers/skills_test.go @@ -39,8 +39,8 @@ import ( func TestListSkillsAPIReportsEffectiveShadowedAndSourceMetadata(t *testing.T) { env := newSkillsTestEnv(t) env.writeSkillFile(t, path.Join(skillset.ManagedDir(), "alpha", "SKILL.md"), managedSkillRaw("alpha", "Managed Alpha")) - env.writeSkillFile(t, path.Join("/data/.openclaw/skills", "alpha", "SKILL.md"), managedSkillRaw("alpha", "Compat Alpha")) - env.writeSkillFile(t, path.Join("/data/.openclaw/skills", "beta", "SKILL.md"), managedSkillRaw("beta", "Compat Beta")) + env.writeSkillFile(t, path.Join("/data/.agents/skills", "alpha", "SKILL.md"), managedSkillRaw("alpha", "Compat Alpha")) + env.writeSkillFile(t, path.Join("/data/.agents/skills", "beta", "SKILL.md"), managedSkillRaw("beta", "Compat Beta")) rec, err := env.callJSON(t, http.MethodGet, "/bots/:bot_id/container/skills", nil, env.handler.ListSkills) if err != nil { @@ -69,7 +69,7 @@ func TestListSkillsAPIReportsEffectiveShadowedAndSourceMetadata(t *testing.T) { t.Fatalf("managed alpha source_kind = %q, want %q", alphaManaged.SourceKind, skillset.SourceKindManaged) } - alphaCompatPath := path.Join("/data/.openclaw/skills", "alpha", "SKILL.md") + alphaCompatPath := path.Join("/data/.agents/skills", "alpha", "SKILL.md") alphaCompat := mustFindSkillByPath(t, resp.Skills, alphaCompatPath) if alphaCompat.Managed { t.Fatalf("compat alpha should not be managed: %+v", alphaCompat) @@ -84,7 +84,7 @@ func TestListSkillsAPIReportsEffectiveShadowedAndSourceMetadata(t *testing.T) { t.Fatalf("compat alpha source_kind = %q, want %q", alphaCompat.SourceKind, skillset.SourceKindCompat) } - betaCompat := mustFindSkillByPath(t, resp.Skills, path.Join("/data/.openclaw/skills", "beta", "SKILL.md")) + betaCompat := mustFindSkillByPath(t, resp.Skills, path.Join("/data/.agents/skills", "beta", "SKILL.md")) if betaCompat.State != skillset.StateEffective { t.Fatalf("beta compat state = %q, want %q", betaCompat.State, skillset.StateEffective) } @@ -96,7 +96,7 @@ func TestListSkillsAPIReportsEffectiveShadowedAndSourceMetadata(t *testing.T) { func TestSkillsActionsAPIAdoptDisableEnableAndDeleteManaged(t *testing.T) { env := newSkillsTestEnv(t) - externalPath := path.Join("/data/.openclaw/skills", "alpha", "SKILL.md") + externalPath := path.Join("/data/.agents/skills", "alpha", "SKILL.md") managedPath := path.Join(skillset.ManagedDir(), "alpha", "SKILL.md") env.writeSkillFile(t, externalPath, managedSkillRaw("alpha", "Compat Alpha")) @@ -188,7 +188,7 @@ func TestSkillsActionsAPIAdoptDisableEnableAndDeleteManaged(t *testing.T) { func TestDeleteSkillsAPIRejectsExternalOnlySkill(t *testing.T) { env := newSkillsTestEnv(t) - env.writeSkillFile(t, path.Join("/data/.openclaw/skills", "alpha", "SKILL.md"), managedSkillRaw("alpha", "Compat Alpha")) + env.writeSkillFile(t, path.Join("/data/.agents/skills", "alpha", "SKILL.md"), managedSkillRaw("alpha", "Compat Alpha")) _, err := env.callJSON(t, http.MethodDelete, "/bots/:bot_id/container/skills", SkillsDeleteRequest{ Names: []string{"alpha"}, @@ -208,10 +208,10 @@ func TestDeleteSkillsAPIRejectsExternalOnlySkill(t *testing.T) { func TestLoadSkillsUsesEffectiveSetAndPromptReflectsOverrideFallback(t *testing.T) { env := newSkillsTestEnv(t) managedPath := path.Join(skillset.ManagedDir(), "alpha", "SKILL.md") - compatPath := path.Join("/data/.openclaw/skills", "alpha", "SKILL.md") + compatPath := path.Join("/data/.agents/skills", "alpha", "SKILL.md") env.writeSkillFile(t, managedPath, managedSkillRaw("alpha", "Managed Alpha")) env.writeSkillFile(t, compatPath, managedSkillRaw("alpha", "Compat Alpha")) - env.writeSkillFile(t, path.Join("/data/.openclaw/skills", "beta", "SKILL.md"), managedSkillRaw("beta", "Compat Beta")) + env.writeSkillFile(t, path.Join("/data/.agents/skills", "beta", "SKILL.md"), managedSkillRaw("beta", "Compat Beta")) loaded, err := env.handler.LoadSkills(context.Background(), env.botID) if err != nil { diff --git a/internal/skills/skills.go b/internal/skills/skills.go index 109a502b..6d7d66bb 100644 --- a/internal/skills/skills.go +++ b/internal/skills/skills.go @@ -115,14 +115,8 @@ func DiscoveryRoots() []Root { return []Root{ {Path: ManagedDirPath, Kind: SourceKindManaged, Managed: true}, {Path: LegacyDirPath, Kind: SourceKindLegacy, Managed: false}, - {Path: path.Join(config.DefaultDataMount, ".agent", "skills"), Kind: SourceKindCompat, Managed: false}, {Path: path.Join(config.DefaultDataMount, ".agents", "skills"), Kind: SourceKindCompat, Managed: false}, - {Path: path.Join(config.DefaultDataMount, ".openclaw", "skills"), Kind: SourceKindCompat, Managed: false}, - {Path: path.Join(config.DefaultDataMount, ".claude", "skills"), Kind: SourceKindCompat, Managed: false}, - {Path: path.Join("/root", ".agent", "skills"), Kind: SourceKindCompat, Managed: false}, {Path: path.Join("/root", ".agents", "skills"), Kind: SourceKindCompat, Managed: false}, - {Path: path.Join("/root", ".openclaw", "skills"), Kind: SourceKindCompat, Managed: false}, - {Path: path.Join("/root", ".claude", "skills"), Kind: SourceKindCompat, Managed: false}, } } diff --git a/internal/skills/skills_test.go b/internal/skills/skills_test.go index 69595965..f1ded6a7 100644 --- a/internal/skills/skills_test.go +++ b/internal/skills/skills_test.go @@ -28,8 +28,8 @@ func TestParseFileFallbacks(t *testing.T) { func TestResolveSupportsDisabledFallbackAndShadowing(t *testing.T) { items := []Entry{ {Name: "alpha", SourcePath: "/data/skills/alpha/SKILL.md", Managed: true, SourceKind: SourceKindManaged}, - {Name: "alpha", SourcePath: "/data/.openclaw/skills/alpha/SKILL.md", SourceKind: SourceKindCompat}, - {Name: "beta", SourcePath: "/data/.openclaw/skills/beta/SKILL.md", SourceKind: SourceKindCompat}, + {Name: "alpha", SourcePath: "/data/.agents/skills/alpha/SKILL.md", SourceKind: SourceKindCompat}, + {Name: "beta", SourcePath: "/data/.agents/skills/beta/SKILL.md", SourceKind: SourceKindCompat}, } resolved := resolve(items, map[string]indexOverride{ @@ -43,14 +43,14 @@ func TestResolveSupportsDisabledFallbackAndShadowing(t *testing.T) { if managedAlpha.State != StateDisabled { t.Fatalf("managed alpha state = %q, want disabled", managedAlpha.State) } - compatAlpha, ok := findBySourcePath(resolved, "/data/.openclaw/skills/alpha/SKILL.md") + compatAlpha, ok := findBySourcePath(resolved, "/data/.agents/skills/alpha/SKILL.md") if !ok { t.Fatalf("compat alpha not found in resolved items") } if compatAlpha.State != StateEffective { t.Fatalf("compat alpha state = %q, want effective", compatAlpha.State) } - beta, ok := findBySourcePath(resolved, "/data/.openclaw/skills/beta/SKILL.md") + beta, ok := findBySourcePath(resolved, "/data/.agents/skills/beta/SKILL.md") if !ok { t.Fatalf("beta not found in resolved items") } @@ -81,8 +81,8 @@ func TestListReadsFullRawContentAndWritesIndex(t *testing.T) { func TestApplyActionAdoptAndDisable(t *testing.T) { client := newFakeClient() - externalPath := pathJoin("/data/.openclaw/skills", "alpha", "SKILL.md") - client.listings["/data/.openclaw/skills"] = []*pb.FileEntry{{Path: "alpha", IsDir: true}} + externalPath := pathJoin("/data/.agents/skills", "alpha", "SKILL.md") + client.listings["/data/.agents/skills"] = []*pb.FileEntry{{Path: "alpha", IsDir: true}} client.files[externalPath] = "---\nname: alpha\ndescription: Alpha\n---\n\n# Alpha" if err := ApplyAction(context.Background(), client, ActionRequest{ @@ -107,16 +107,41 @@ func TestApplyActionAdoptAndDisable(t *testing.T) { } } -func TestDiscoveryRootsStartWithManagedAndLegacy(t *testing.T) { +func TestDiscoveryRootsMatchCurrentPolicy(t *testing.T) { roots := DiscoveryRoots() - if len(roots) < 2 { - t.Fatalf("expected at least 2 discovery roots, got %d", len(roots)) + want := []Root{ + {Path: ManagedDirPath, Kind: SourceKindManaged, Managed: true}, + {Path: LegacyDirPath, Kind: SourceKindLegacy, Managed: false}, + {Path: "/data/.agents/skills", Kind: SourceKindCompat, Managed: false}, + {Path: "/root/.agents/skills", Kind: SourceKindCompat, Managed: false}, } - if roots[0].Path != ManagedDirPath || !roots[0].Managed { - t.Fatalf("first root = %+v, want managed %q", roots[0], ManagedDirPath) + if !slices.Equal(roots, want) { + t.Fatalf("DiscoveryRoots() = %+v, want %+v", roots, want) } - if roots[1].Path != LegacyDirPath || roots[1].Managed { - t.Fatalf("second root = %+v, want legacy %q", roots[1], LegacyDirPath) +} + +func TestListScansConfiguredDiscoveryRootsInOrder(t *testing.T) { + client := newFakeClient() + for _, root := range DiscoveryRoots() { + client.listings[root.Path] = nil + } + client.listings[ManagedDirPath] = []*pb.FileEntry{{Path: "alpha", IsDir: true}} + client.files[pathJoin(ManagedDirPath, "alpha", "SKILL.md")] = "---\nname: alpha\ndescription: Alpha\n---\n\n# Alpha" + + items, err := List(context.Background(), client) + if err != nil { + t.Fatalf("List returned error: %v", err) + } + if len(items) != 1 || items[0].SourceRoot != ManagedDirPath { + t.Fatalf("List() items = %+v, want managed alpha only", items) + } + + wantCalls := make([]string, 0, len(DiscoveryRoots())) + for _, root := range DiscoveryRoots() { + wantCalls = append(wantCalls, root.Path) + } + if !slices.Equal(client.listCalls, wantCalls) { + t.Fatalf("ListDirAll calls = %+v, want %+v", client.listCalls, wantCalls) } } @@ -135,8 +160,9 @@ func TestContainerEnvUsesDataHomeAndXDGDirs(t *testing.T) { } type fakeClient struct { - listings map[string][]*pb.FileEntry - files map[string]string + listings map[string][]*pb.FileEntry + files map[string]string + listCalls []string } func newFakeClient() *fakeClient { @@ -147,6 +173,7 @@ func newFakeClient() *fakeClient { } func (f *fakeClient) ListDirAll(_ context.Context, p string, _ bool) ([]*pb.FileEntry, error) { + f.listCalls = append(f.listCalls, p) items, ok := f.listings[p] if !ok { return nil, io.EOF