diff --git a/src/auto-reply/skill-commands.test.ts b/src/auto-reply/skill-commands.test.ts index 755eb40f47f..efec8ad262b 100644 --- a/src/auto-reply/skill-commands.test.ts +++ b/src/auto-reply/skill-commands.test.ts @@ -8,6 +8,49 @@ let listSkillCommandsForWorkspace: typeof import("./skill-commands.js").listSkil let resolveSkillCommandInvocation: typeof import("./skill-commands.js").resolveSkillCommandInvocation; let skillCommandsTesting: typeof import("./skill-commands.js").__testing; +const tempDirs: string[] = []; + +async function makeTempDir(prefix: string) { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix)); + tempDirs.push(dir); + return dir; +} + +async function createWorkspace(parentDir: string, name: string) { + const workspace = path.join(parentDir, name); + await fs.mkdir(workspace, { recursive: true }); + return workspace; +} + +async function createMainAndResearchWorkspaces(prefix: string) { + const baseDir = await makeTempDir(prefix); + const mainWorkspace = await createWorkspace(baseDir, "main"); + const researchWorkspace = await createWorkspace(baseDir, "research"); + return { mainWorkspace, researchWorkspace }; +} + +function listMainResearchSkillCommands(params: { + mainWorkspace: string; + researchWorkspace: string; +}) { + return listSkillCommandsForAgents({ + cfg: { + agents: { + list: [ + { id: "main", workspace: params.mainWorkspace, skills: ["demo-skill"] }, + { id: "research", workspace: params.researchWorkspace, skills: ["extra-skill"] }, + ], + }, + }, + agentIds: ["main", "research"], + }); +} + +function expectDemoAndExtraSkillCommands(commands: ReturnType) { + expect(commands.map((entry) => entry.skillName)).toEqual(["demo-skill", "extra-skill"]); + expect(commands.map((entry) => entry.name)).toEqual(["demo_skill", "extra_skill"]); +} + function resolveUniqueSkillCommandName(base: string, used: Set): string { let name = base; let suffix = 2; @@ -101,6 +144,10 @@ beforeAll(async () => { } = await import("./skill-commands.js")); }); +afterAll(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true }))); +}); + beforeEach(() => { vi.clearAllMocks(); }); @@ -143,24 +190,9 @@ describe("resolveSkillCommandInvocation", () => { }); describe("listSkillCommandsForAgents", () => { - const tempDirs: string[] = []; - const makeTempDir = async (prefix: string) => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix)); - tempDirs.push(dir); - return dir; - }; - afterAll(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })), - ); - }); - it("deduplicates by skillName across agents, keeping the first registration", async () => { - const baseDir = await makeTempDir("openclaw-skills-"); - const mainWorkspace = path.join(baseDir, "main"); - const researchWorkspace = path.join(baseDir, "research"); - await fs.mkdir(mainWorkspace, { recursive: true }); - await fs.mkdir(researchWorkspace, { recursive: true }); + const { mainWorkspace, researchWorkspace } = + await createMainAndResearchWorkspaces("openclaw-skills-"); const commands = listSkillCommandsForAgents({ cfg: { @@ -180,8 +212,7 @@ describe("listSkillCommandsForAgents", () => { it("scopes to specific agents when agentIds is provided", async () => { const baseDir = await makeTempDir("openclaw-skills-filter-"); - const researchWorkspace = path.join(baseDir, "research"); - await fs.mkdir(researchWorkspace, { recursive: true }); + const researchWorkspace = await createWorkspace(baseDir, "research"); const commands = listSkillCommandsForAgents({ cfg: { @@ -197,53 +228,29 @@ describe("listSkillCommandsForAgents", () => { }); it("prevents cross-agent skill leakage when each agent has an allowlist", async () => { - const baseDir = await makeTempDir("openclaw-skills-leak-"); - const mainWorkspace = path.join(baseDir, "main"); - const researchWorkspace = path.join(baseDir, "research"); - await fs.mkdir(mainWorkspace, { recursive: true }); - await fs.mkdir(researchWorkspace, { recursive: true }); + const { mainWorkspace, researchWorkspace } = + await createMainAndResearchWorkspaces("openclaw-skills-leak-"); - const commands = listSkillCommandsForAgents({ - cfg: { - agents: { - list: [ - { id: "main", workspace: mainWorkspace, skills: ["demo-skill"] }, - { id: "research", workspace: researchWorkspace, skills: ["extra-skill"] }, - ], - }, - }, - agentIds: ["main", "research"], - }); + const commands = listMainResearchSkillCommands({ mainWorkspace, researchWorkspace }); - expect(commands.map((entry) => entry.skillName)).toEqual(["demo-skill", "extra-skill"]); - expect(commands.map((entry) => entry.name)).toEqual(["demo_skill", "extra_skill"]); + expectDemoAndExtraSkillCommands(commands); }); it("merges allowlists for agents that share one workspace", async () => { const baseDir = await makeTempDir("openclaw-skills-shared-"); - const sharedWorkspace = path.join(baseDir, "research"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "research"); - const commands = listSkillCommandsForAgents({ - cfg: { - agents: { - list: [ - { id: "main", workspace: sharedWorkspace, skills: ["demo-skill"] }, - { id: "research", workspace: sharedWorkspace, skills: ["extra-skill"] }, - ], - }, - }, - agentIds: ["main", "research"], + const commands = listMainResearchSkillCommands({ + mainWorkspace: sharedWorkspace, + researchWorkspace: sharedWorkspace, }); - expect(commands.map((entry) => entry.skillName)).toEqual(["demo-skill", "extra-skill"]); - expect(commands.map((entry) => entry.name)).toEqual(["demo_skill", "extra_skill"]); + expectDemoAndExtraSkillCommands(commands); }); it("deduplicates overlapping allowlists for shared workspace", async () => { const baseDir = await makeTempDir("openclaw-skills-overlap-"); - const sharedWorkspace = path.join(baseDir, "research"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "research"); const commands = listSkillCommandsForAgents({ cfg: { @@ -264,8 +271,7 @@ describe("listSkillCommandsForAgents", () => { it("keeps workspace unrestricted when one co-tenant agent has no skills filter", async () => { const baseDir = await makeTempDir("openclaw-skills-unfiltered-"); - const sharedWorkspace = path.join(baseDir, "research"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "research"); const commands = listSkillCommandsForAgents({ cfg: { @@ -286,8 +292,7 @@ describe("listSkillCommandsForAgents", () => { it("merges empty allowlist with non-empty allowlist for shared workspace", async () => { const baseDir = await makeTempDir("openclaw-skills-empty-"); - const sharedWorkspace = path.join(baseDir, "research"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "research"); const commands = listSkillCommandsForAgents({ cfg: { @@ -306,8 +311,7 @@ describe("listSkillCommandsForAgents", () => { it("uses inherited defaults for agents that share one workspace", async () => { const baseDir = await makeTempDir("openclaw-skills-defaults-"); - const sharedWorkspace = path.join(baseDir, "shared-defaults"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "shared-defaults"); const commands = listSkillCommandsForAgents({ cfg: { @@ -330,8 +334,7 @@ describe("listSkillCommandsForAgents", () => { it("does not inherit defaults when an agent sets an explicit empty skills list", async () => { const baseDir = await makeTempDir("openclaw-skills-defaults-empty-"); - const sharedWorkspace = path.join(baseDir, "shared-defaults"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "shared-defaults"); const commands = listSkillCommandsForAgents({ cfg: { @@ -353,9 +356,8 @@ describe("listSkillCommandsForAgents", () => { it("skips agents with missing workspaces gracefully", async () => { const baseDir = await makeTempDir("openclaw-skills-missing-"); - const validWorkspace = path.join(baseDir, "research"); + const validWorkspace = await createWorkspace(baseDir, "research"); const missingWorkspace = path.join(baseDir, "nonexistent"); - await fs.mkdir(validWorkspace, { recursive: true }); const commands = listSkillCommandsForAgents({ cfg: { @@ -376,22 +378,9 @@ describe("listSkillCommandsForAgents", () => { }); describe("listSkillCommandsForWorkspace", () => { - const tempDirs: string[] = []; - const makeTempDir = async (prefix: string) => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix)); - tempDirs.push(dir); - return dir; - }; - afterAll(async () => { - await Promise.all( - tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })), - ); - }); - it("inherits defaults when agentId is provided without an explicit skill filter", async () => { const baseDir = await makeTempDir("openclaw-skills-workspace-defaults-"); - const sharedWorkspace = path.join(baseDir, "shared-defaults"); - await fs.mkdir(sharedWorkspace, { recursive: true }); + const sharedWorkspace = await createWorkspace(baseDir, "shared-defaults"); const commands = listSkillCommandsForWorkspace({ workspaceDir: sharedWorkspace,