fix(openclaw): normalize Windows skill-loader URLs before fileURLToPath (#5679)
This commit is contained in:
@@ -4,10 +4,12 @@
|
|||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import {
|
import {
|
||||||
safePath,
|
safePath,
|
||||||
|
normalizeModuleUrlToPath,
|
||||||
loadSkill,
|
loadSkill,
|
||||||
loadTriagePrompt,
|
loadTriagePrompt,
|
||||||
loadCompactTriagePrompt,
|
loadCompactTriagePrompt,
|
||||||
} from "./skill-loader.ts";
|
} from "./skill-loader.ts";
|
||||||
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// safePath — path containment
|
// safePath — path containment
|
||||||
@@ -74,6 +76,31 @@ describe("loadSkill path traversal", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("normalizeModuleUrlToPath", () => {
|
||||||
|
it("normalizes raw Windows paths before fileURLToPath conversion", () => {
|
||||||
|
const rawWindowsMetaUrl = "C:\\Users\\example\\openclaw\\index.ts";
|
||||||
|
const result = normalizeModuleUrlToPath(rawWindowsMetaUrl);
|
||||||
|
// Assert the decoded property directly rather than reconstructing via the function body
|
||||||
|
expect(typeof result).toBe("string");
|
||||||
|
expect(result).not.toContain("%5C");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves already-correct file URLs unchanged", () => {
|
||||||
|
const fileMetaUrl = "file:///C:/Users/example/openclaw/index.ts";
|
||||||
|
const expected = fileURLToPath(fileMetaUrl);
|
||||||
|
|
||||||
|
expect(normalizeModuleUrlToPath(fileMetaUrl)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skipIf(process.platform === "win32")(
|
||||||
|
"passes POSIX absolute paths through unchanged",
|
||||||
|
() => {
|
||||||
|
const posixPath = "/home/user/openclaw/index.ts";
|
||||||
|
expect(normalizeModuleUrlToPath(posixPath)).toBe(posixPath);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
describe("loadCompactTriagePrompt", () => {
|
describe("loadCompactTriagePrompt", () => {
|
||||||
it("keeps the core triage instructions without inlining the full skill body", () => {
|
it("keeps the core triage instructions without inlining the full skill body", () => {
|
||||||
const prompt = loadCompactTriagePrompt();
|
const prompt = loadCompactTriagePrompt();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import type { SkillsConfig, CategoryConfig } from "./types.ts";
|
import type { SkillsConfig, CategoryConfig } from "./types.ts";
|
||||||
import { readText, exists } from "./fs-safe.ts";
|
import { readText, exists } from "./fs-safe.ts";
|
||||||
|
|
||||||
@@ -84,6 +84,14 @@ function parseSkillFile(content: string): ParsedSkill {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal — exported for testing only */
|
||||||
|
export function normalizeModuleUrlToPath(moduleUrl: string): string {
|
||||||
|
const normalizedUrl = moduleUrl.startsWith("file:")
|
||||||
|
? moduleUrl
|
||||||
|
: pathToFileURL(moduleUrl).toString();
|
||||||
|
return fileURLToPath(normalizedUrl);
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Skill Loader
|
// Skill Loader
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -96,7 +104,7 @@ function resolveSkillsDir(): string {
|
|||||||
|
|
||||||
// Strategy 1: import.meta.url (works in native ESM)
|
// Strategy 1: import.meta.url (works in native ESM)
|
||||||
try {
|
try {
|
||||||
const metaDir = path.dirname(fileURLToPath(import.meta.url));
|
const metaDir = path.dirname(normalizeModuleUrlToPath(import.meta.url));
|
||||||
candidates.push(path.join(metaDir, "skills"));
|
candidates.push(path.join(metaDir, "skills"));
|
||||||
candidates.push(path.join(metaDir, "..", "skills"));
|
candidates.push(path.join(metaDir, "..", "skills"));
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user