mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 16:54:31 +00:00
refactor(skills): stabilize watcher targets and include agents skills
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
@@ -22,10 +23,15 @@ describe("ensureSkillsWatcher", () => {
|
|||||||
const opts = watchMock.mock.calls[0]?.[1] as { ignored?: unknown };
|
const opts = watchMock.mock.calls[0]?.[1] as { ignored?: unknown };
|
||||||
|
|
||||||
expect(opts.ignored).toBe(mod.DEFAULT_SKILLS_WATCH_IGNORED);
|
expect(opts.ignored).toBe(mod.DEFAULT_SKILLS_WATCH_IGNORED);
|
||||||
|
const posix = (p: string) => p.replaceAll("\\", "/");
|
||||||
expect(targets).toEqual(
|
expect(targets).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
path.join("/tmp/workspace", "skills", "SKILL.md"),
|
posix(path.join("/tmp/workspace", "skills", "SKILL.md")),
|
||||||
path.join("/tmp/workspace", "skills", "*", "SKILL.md"),
|
posix(path.join("/tmp/workspace", "skills", "*", "SKILL.md")),
|
||||||
|
posix(path.join("/tmp/workspace", ".agents", "skills", "SKILL.md")),
|
||||||
|
posix(path.join("/tmp/workspace", ".agents", "skills", "*", "SKILL.md")),
|
||||||
|
posix(path.join(os.homedir(), ".agents", "skills", "SKILL.md")),
|
||||||
|
posix(path.join(os.homedir(), ".agents", "skills", "*", "SKILL.md")),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
expect(targets.every((target) => target.includes("SKILL.md"))).toBe(true);
|
expect(targets.every((target) => target.includes("SKILL.md"))).toBe(true);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import chokidar, { type FSWatcher } from "chokidar";
|
import chokidar, { type FSWatcher } from "chokidar";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawConfig } from "../../config/config.js";
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||||
@@ -59,8 +60,10 @@ function resolveWatchPaths(workspaceDir: string, config?: OpenClawConfig): strin
|
|||||||
const paths: string[] = [];
|
const paths: string[] = [];
|
||||||
if (workspaceDir.trim()) {
|
if (workspaceDir.trim()) {
|
||||||
paths.push(path.join(workspaceDir, "skills"));
|
paths.push(path.join(workspaceDir, "skills"));
|
||||||
|
paths.push(path.join(workspaceDir, ".agents", "skills"));
|
||||||
}
|
}
|
||||||
paths.push(path.join(CONFIG_DIR, "skills"));
|
paths.push(path.join(CONFIG_DIR, "skills"));
|
||||||
|
paths.push(path.join(os.homedir(), ".agents", "skills"));
|
||||||
const extraDirsRaw = config?.skills?.load?.extraDirs ?? [];
|
const extraDirsRaw = config?.skills?.load?.extraDirs ?? [];
|
||||||
const extraDirs = extraDirsRaw
|
const extraDirs = extraDirsRaw
|
||||||
.map((d) => (typeof d === "string" ? d.trim() : ""))
|
.map((d) => (typeof d === "string" ? d.trim() : ""))
|
||||||
@@ -72,17 +75,24 @@ function resolveWatchPaths(workspaceDir: string, config?: OpenClawConfig): strin
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toWatchGlobRoot(raw: string): string {
|
||||||
|
// Chokidar treats globs as POSIX-ish patterns. Normalize Windows separators
|
||||||
|
// so `*` works consistently across platforms.
|
||||||
|
return raw.replaceAll("\\", "/").replace(/\/+$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
function resolveWatchTargets(workspaceDir: string, config?: OpenClawConfig): string[] {
|
function resolveWatchTargets(workspaceDir: string, config?: OpenClawConfig): string[] {
|
||||||
// Skills are defined by SKILL.md; watch only those files to avoid traversing
|
// Skills are defined by SKILL.md; watch only those files to avoid traversing
|
||||||
// or watching unrelated large trees (e.g. datasets) that can exhaust FDs.
|
// or watching unrelated large trees (e.g. datasets) that can exhaust FDs.
|
||||||
const targets = new Set<string>();
|
const targets = new Set<string>();
|
||||||
for (const root of resolveWatchPaths(workspaceDir, config)) {
|
for (const root of resolveWatchPaths(workspaceDir, config)) {
|
||||||
|
const globRoot = toWatchGlobRoot(root);
|
||||||
// Some configs point directly at a skill folder.
|
// Some configs point directly at a skill folder.
|
||||||
targets.add(path.join(root, "SKILL.md"));
|
targets.add(`${globRoot}/SKILL.md`);
|
||||||
// Standard layout: <skillsRoot>/<skillName>/SKILL.md
|
// Standard layout: <skillsRoot>/<skillName>/SKILL.md
|
||||||
targets.add(path.join(root, "*", "SKILL.md"));
|
targets.add(`${globRoot}/*/SKILL.md`);
|
||||||
}
|
}
|
||||||
return Array.from(targets);
|
return Array.from(targets).toSorted();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerSkillsChangeListener(listener: (event: SkillsChangeEvent) => void) {
|
export function registerSkillsChangeListener(listener: (event: SkillsChangeEvent) => void) {
|
||||||
|
|||||||
Reference in New Issue
Block a user