diff --git a/src/agents/skills.loadworkspaceskillentries.test.ts b/src/agents/skills.loadworkspaceskillentries.test.ts index a946b77c25c..799df0a4078 100644 --- a/src/agents/skills.loadworkspaceskillentries.test.ts +++ b/src/agents/skills.loadworkspaceskillentries.test.ts @@ -4,7 +4,6 @@ import path from "node:path"; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { resetLogger, setLoggerOverride } from "../logging/logger.js"; import { loggingState } from "../logging/state.js"; -import { withPathResolutionEnv } from "../test-utils/env.js"; import { writeSkill, writeWorkspaceSkills } from "./skills.e2e-test-helpers.js"; import { restoreMockSkillsHomeEnv, @@ -26,10 +25,6 @@ async function createTempWorkspaceDir() { return workspaceDir; } -function withWorkspaceHome(workspaceDir: string, cb: () => T): T { - return withPathResolutionEnv(workspaceDir, { PATH: "" }, () => cb()); -} - function captureWarningLogger() { setLoggerOverride({ level: "silent", consoleLevel: "warn" }); const warn = vi.fn(); @@ -42,6 +37,17 @@ function captureWarningLogger() { return warn; } +function loadTestWorkspaceSkillEntries( + workspaceDir: string, + opts?: Parameters[1], +) { + return loadWorkspaceSkillEntries(workspaceDir, { + managedSkillsDir: path.join(workspaceDir, ".managed"), + bundledSkillsDir: "", + ...opts, + }); +} + beforeAll(async () => { tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-skills-workspace-")); fakeHome = path.join(tempRoot, "home"); @@ -80,44 +86,27 @@ async function setupWorkspaceWithProsePlugin() { } describe("loadWorkspaceSkillEntries", () => { - it("handles an empty managed skills dir without throwing", async () => { - const workspaceDir = await createTempWorkspaceDir(); - const managedDir = path.join(workspaceDir, ".managed"); - await fs.mkdir(managedDir, { recursive: true }); - - const entries = withWorkspaceHome(workspaceDir, () => - loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: managedDir, - bundledSkillsDir: path.join(workspaceDir, ".bundled"), - }), - ); - - expect(entries).toEqual([]); - }); - it("filters plugin-shipped skills through plugin config", async () => { - const { workspaceDir, managedDir, bundledDir } = await setupWorkspaceWithProsePlugin(); + const { workspaceDir, managedDir } = await setupWorkspaceWithProsePlugin(); - const enabledEntries = loadWorkspaceSkillEntries(workspaceDir, { + const enabledEntries = loadTestWorkspaceSkillEntries(workspaceDir, { config: { plugins: { entries: { "open-prose": { enabled: true } }, }, }, managedSkillsDir: managedDir, - bundledSkillsDir: bundledDir, }); expect(enabledEntries.map((entry) => entry.skill.name)).toContain("prose"); - const blockedEntries = loadWorkspaceSkillEntries(workspaceDir, { + const blockedEntries = loadTestWorkspaceSkillEntries(workspaceDir, { config: { plugins: { allow: ["something-else"], }, }, managedSkillsDir: managedDir, - bundledSkillsDir: bundledDir, }); expect(blockedEntries.map((entry) => entry.skill.name)).not.toContain("prose"); @@ -139,10 +128,7 @@ describe("loadWorkspaceSkillEntries", () => { frontmatterExtra: "disable-model-invocation: true", }); - const entries = loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), - }); + const entries = loadTestWorkspaceSkillEntries(workspaceDir); expect(entries.map((entry) => entry.skill.name)).toContain("fallback-name"); const hiddenEntry = entries.find((entry) => entry.skill.name === "hidden-skill"); @@ -159,7 +145,7 @@ describe("loadWorkspaceSkillEntries", () => { { name: "docs-search", description: "Docs" }, ]); - const defaultEntries = loadWorkspaceSkillEntries(workspaceDir, { + const defaultEntries = loadTestWorkspaceSkillEntries(workspaceDir, { config: { agents: { defaults: { @@ -169,13 +155,11 @@ describe("loadWorkspaceSkillEntries", () => { }, }, agentId: "writer", - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), }); expect(defaultEntries.map((entry) => entry.skill.name)).toEqual(["github"]); - const replacementEntries = loadWorkspaceSkillEntries(workspaceDir, { + const replacementEntries = loadTestWorkspaceSkillEntries(workspaceDir, { config: { agents: { defaults: { @@ -185,8 +169,6 @@ describe("loadWorkspaceSkillEntries", () => { }, }, agentId: "writer", - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), }); expect(replacementEntries.map((entry) => entry.skill.name)).toEqual(["docs-search"]); @@ -201,7 +183,7 @@ describe("loadWorkspaceSkillEntries", () => { metadata: '{"openclaw":{"requires":{"anyBins":["missingbin","sandboxbin"]}}}', }); - const entries = loadWorkspaceSkillEntries(workspaceDir, { + const entries = loadTestWorkspaceSkillEntries(workspaceDir, { config: { agents: { defaults: { @@ -219,8 +201,6 @@ describe("loadWorkspaceSkillEntries", () => { note: "sandbox", }, }, - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), }); expect(entries.map((entry) => entry.skill.name)).toEqual(["remote-only"]); @@ -242,10 +222,7 @@ describe("loadWorkspaceSkillEntries", () => { await fs.symlink(escapedSkillDir, requestedPath, "dir"); const warn = captureWarningLogger(); - const entries = loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), - }); + const entries = loadTestWorkspaceSkillEntries(workspaceDir); expect(entries.map((entry) => entry.skill.name)).not.toContain("outside-skill"); const [line] = warn.mock.calls[0] ?? []; @@ -276,8 +253,7 @@ describe("loadWorkspaceSkillEntries", () => { await fs.symlink(escapedSkillDir, requestedPath, "dir"); const warn = captureWarningLogger(); - const entries = loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: path.join(workspaceDir, ".managed"), + const entries = loadTestWorkspaceSkillEntries(workspaceDir, { bundledSkillsDir: bundledDir, }); @@ -310,8 +286,7 @@ describe("loadWorkspaceSkillEntries", () => { await fs.symlink(escapedSkillDir, requestedPath, "dir"); const warn = captureWarningLogger(); - loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: path.join(workspaceDir, ".managed"), + loadTestWorkspaceSkillEntries(workspaceDir, { bundledSkillsDir: bundledDir, }); @@ -347,10 +322,7 @@ describe("loadWorkspaceSkillEntries", () => { await fs.mkdir(symlinkedSkillDir, { recursive: true }); await fs.symlink(path.join(targetDir, "SKILL.md"), path.join(symlinkedSkillDir, "SKILL.md")); - const entries = loadWorkspaceSkillEntries(workspaceDir, { - managedSkillsDir: path.join(workspaceDir, ".managed"), - bundledSkillsDir: path.join(workspaceDir, ".bundled"), - }); + const entries = loadTestWorkspaceSkillEntries(workspaceDir); expect(entries.map((entry) => entry.skill.name)).not.toContain("outside-file-skill"); expect(entries.map((entry) => entry.skill.name)).not.toContain("symlink-target"); diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index cb488daa407..7f06b99026a 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -365,6 +365,9 @@ function loadSkillEntries( const loadSkills = (params: { dir: string; source: string }): Skill[] => { const rootDir = path.resolve(params.dir); + if (!fs.existsSync(rootDir)) { + return []; + } const rootRealPath = tryRealpath(rootDir) ?? rootDir; const resolved = resolveNestedSkillsRoot(params.dir, { maxEntriesToScan: limits.maxCandidatesPerRoot,