fix(agents): respect overridden home for personal skills

This commit is contained in:
Vincent Koc
2026-04-11 02:47:23 +01:00
parent 1e4036a2f1
commit 8ae6d42faa
4 changed files with 34 additions and 14 deletions

View File

@@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { withEnv } from "../test-utils/env.js";
import { withPathResolutionEnv } from "../test-utils/env.js";
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
import { writeSkill } from "./skills.e2e-test-helpers.js";
import { buildWorkspaceSkillSnapshot, buildWorkspaceSkillsPrompt } from "./skills.js";
@@ -40,7 +40,7 @@ afterAll(async () => {
});
function withWorkspaceHome<T>(workspaceDir: string, cb: () => T): T {
return withEnv({ HOME: workspaceDir, PATH: "" }, cb);
return withPathResolutionEnv(workspaceDir, { PATH: "" }, () => cb());
}
function buildSnapshot(

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { withPathResolutionEnv } from "../test-utils/env.js";
import { writeSkill } from "./skills.e2e-test-helpers.js";
import { loadWorkspaceSkillEntries } from "./skills.js";
import { readSkillFrontmatterSafe } from "./skills/local-loader.js";
@@ -15,6 +16,10 @@ async function createTempWorkspaceDir() {
return workspaceDir;
}
function withWorkspaceHome<T>(workspaceDir: string, cb: () => T): T {
return withPathResolutionEnv(workspaceDir, { PATH: "" }, () => cb());
}
afterEach(async () => {
await Promise.all(
tempDirs.splice(0, tempDirs.length).map((dir) => fs.rm(dir, { recursive: true, force: true })),
@@ -59,10 +64,12 @@ describe("loadWorkspaceSkillEntries", () => {
const managedDir = path.join(workspaceDir, ".managed");
await fs.mkdir(managedDir, { recursive: true });
const entries = loadWorkspaceSkillEntries(workspaceDir, {
managedSkillsDir: managedDir,
bundledSkillsDir: path.join(workspaceDir, ".bundled"),
});
const entries = withWorkspaceHome(workspaceDir, () =>
loadWorkspaceSkillEntries(workspaceDir, {
managedSkillsDir: managedDir,
bundledSkillsDir: path.join(workspaceDir, ".bundled"),
}),
);
expect(entries).toEqual([]);
});

View File

@@ -6,6 +6,7 @@ import {
setRuntimeConfigSnapshot,
type OpenClawConfig,
} from "../config/config.js";
import { withPathResolutionEnv } from "../test-utils/env.js";
import { createFixtureSuite } from "../test-utils/fixture-suite.js";
import { createTempHomeEnv, type TempHomeEnv } from "../test-utils/temp-home.js";
import { writeSkill } from "./skills.e2e-test-helpers.js";
@@ -30,6 +31,10 @@ const resolveTestSkillDirs = (workspaceDir: string) => ({
const makeWorkspace = async () => await fixtureSuite.createCaseDir("workspace");
const apiKeyField = ["api", "Key"].join("");
function withWorkspaceHome<T>(workspaceDir: string, cb: () => T): T {
return withPathResolutionEnv(workspaceDir, { PATH: "" }, () => cb());
}
const withClearedEnv = <T>(
keys: string[],
run: (original: Record<string, string | undefined>) => T,
@@ -109,10 +114,12 @@ describe("buildWorkspaceSkillCommandSpecs", () => {
frontmatterExtra: "user-invocable: false",
});
const commands = buildWorkspaceSkillCommandSpecs(workspaceDir, {
...resolveTestSkillDirs(workspaceDir),
reservedNames: new Set(["help"]),
});
const commands = withWorkspaceHome(workspaceDir, () =>
buildWorkspaceSkillCommandSpecs(workspaceDir, {
...resolveTestSkillDirs(workspaceDir),
reservedNames: new Set(["help"]),
}),
);
const names = commands.map((entry) => entry.name).toSorted();
expect(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
@@ -247,7 +254,9 @@ describe("buildWorkspaceSkillsPrompt", () => {
it("returns empty prompt when skills dirs are missing", async () => {
const workspaceDir = await makeWorkspace();
const prompt = buildWorkspaceSkillsPrompt(workspaceDir, resolveTestSkillDirs(workspaceDir));
const prompt = withWorkspaceHome(workspaceDir, () =>
buildWorkspaceSkillsPrompt(workspaceDir, resolveTestSkillDirs(workspaceDir)),
);
expect(prompt).toBe("");
});

View File

@@ -5,7 +5,7 @@ import type { OpenClawConfig } from "../../config/config.js";
import { isPathInside } from "../../infra/path-guards.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { normalizeOptionalString } from "../../shared/string-coerce.js";
import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
import { CONFIG_DIR, resolveHomeDir, resolveUserPath } from "../../utils.js";
import { resolveSandboxPath } from "../sandbox-paths.js";
import { resolveEffectiveAgentSkillFilter } from "./agent-filter.js";
import { resolveBundledSkillsDir } from "./bundled-dir.js";
@@ -37,7 +37,7 @@ const skillsLogger = createSubsystemLogger("skills");
* Saves ~56 tokens per skill path × N skills ≈ 400600 tokens total.
*/
function compactSkillPaths(skills: Skill[]): Skill[] {
const home = os.homedir();
const home = resolveHomeDir() ?? os.homedir();
if (!home) return skills;
const prefix = home.endsWith(path.sep) ? home : home + path.sep;
return skills.map((s) => ({
@@ -435,7 +435,11 @@ function loadSkillEntries(
dir: managedSkillsDir,
source: "openclaw-managed",
});
const personalAgentsSkillsDir = path.resolve(os.homedir(), ".agents", "skills");
const personalAgentsSkillsDir = path.resolve(
resolveHomeDir() ?? os.homedir(),
".agents",
"skills",
);
const personalAgentsSkills = loadSkills({
dir: personalAgentsSkillsDir,
source: "agents-skills-personal",