mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 16:10:24 +00:00
fix(cron): normalize skill-filter snapshots and split isolated run helpers
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
parseAgentSessionKey,
|
||||
} from "../routing/session-key.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { normalizeSkillFilter } from "./skills/filter.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "./workspace.js";
|
||||
|
||||
export { resolveAgentIdFromSessionKey } from "../routing/session-key.js";
|
||||
@@ -128,12 +129,7 @@ export function resolveAgentSkillsFilter(
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
): string[] | undefined {
|
||||
const raw = resolveAgentConfig(cfg, agentId)?.skills;
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = raw.map((entry) => String(entry).trim()).filter(Boolean);
|
||||
return normalized.length > 0 ? normalized : [];
|
||||
return normalizeSkillFilter(resolveAgentConfig(cfg, agentId)?.skills);
|
||||
}
|
||||
|
||||
export function resolveAgentModelPrimary(cfg: OpenClawConfig, agentId: string): string | undefined {
|
||||
|
||||
35
src/agents/skills/filter.test.ts
Normal file
35
src/agents/skills/filter.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
matchesSkillFilter,
|
||||
normalizeSkillFilter,
|
||||
normalizeSkillFilterForComparison,
|
||||
} from "./filter.js";
|
||||
|
||||
describe("skills/filter", () => {
|
||||
it("normalizes configured filters with trimming", () => {
|
||||
expect(normalizeSkillFilter([" weather ", "", "meme-factory"])).toEqual([
|
||||
"weather",
|
||||
"meme-factory",
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves explicit empty list as []", () => {
|
||||
expect(normalizeSkillFilter([])).toEqual([]);
|
||||
expect(normalizeSkillFilter(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes for comparison with dedupe + ordering", () => {
|
||||
expect(normalizeSkillFilterForComparison(["weather", "meme-factory", "weather"])).toEqual([
|
||||
"meme-factory",
|
||||
"weather",
|
||||
]);
|
||||
});
|
||||
|
||||
it("matches equivalent filters after normalization", () => {
|
||||
expect(matchesSkillFilter(["weather", "meme-factory"], [" meme-factory ", "weather"])).toBe(
|
||||
true,
|
||||
);
|
||||
expect(matchesSkillFilter(undefined, undefined)).toBe(true);
|
||||
expect(matchesSkillFilter([], undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
31
src/agents/skills/filter.ts
Normal file
31
src/agents/skills/filter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export function normalizeSkillFilter(skillFilter?: ReadonlyArray<unknown>): string[] | undefined {
|
||||
if (skillFilter === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return skillFilter.map((entry) => String(entry).trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
export function normalizeSkillFilterForComparison(
|
||||
skillFilter?: ReadonlyArray<unknown>,
|
||||
): string[] | undefined {
|
||||
const normalized = normalizeSkillFilter(skillFilter);
|
||||
if (normalized === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return Array.from(new Set(normalized)).toSorted();
|
||||
}
|
||||
|
||||
export function matchesSkillFilter(
|
||||
cached?: ReadonlyArray<unknown>,
|
||||
next?: ReadonlyArray<unknown>,
|
||||
): boolean {
|
||||
const cachedNormalized = normalizeSkillFilterForComparison(cached);
|
||||
const nextNormalized = normalizeSkillFilterForComparison(next);
|
||||
if (cachedNormalized === undefined || nextNormalized === undefined) {
|
||||
return cachedNormalized === nextNormalized;
|
||||
}
|
||||
if (cachedNormalized.length !== nextNormalized.length) {
|
||||
return false;
|
||||
}
|
||||
return cachedNormalized.every((entry, index) => entry === nextNormalized[index]);
|
||||
}
|
||||
@@ -82,6 +82,8 @@ export type SkillEligibilityContext = {
|
||||
export type SkillSnapshot = {
|
||||
prompt: string;
|
||||
skills: Array<{ name: string; primaryEnv?: string }>;
|
||||
/** Normalized agent-level filter used to build this snapshot; undefined means unrestricted. */
|
||||
skillFilter?: string[];
|
||||
resolvedSkills?: Skill[];
|
||||
version?: number;
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
|
||||
import { resolveSandboxPath } from "../sandbox-paths.js";
|
||||
import { resolveBundledSkillsDir } from "./bundled-dir.js";
|
||||
import { shouldIncludeSkill } from "./config.js";
|
||||
import { normalizeSkillFilter } from "./filter.js";
|
||||
import {
|
||||
parseFrontmatter,
|
||||
resolveOpenClawMetadata,
|
||||
@@ -52,14 +53,16 @@ function filterSkillEntries(
|
||||
let filtered = entries.filter((entry) => shouldIncludeSkill({ entry, config, eligibility }));
|
||||
// If skillFilter is provided, only include skills in the filter list.
|
||||
if (skillFilter !== undefined) {
|
||||
const normalized = skillFilter.map((entry) => String(entry).trim()).filter(Boolean);
|
||||
const normalized = normalizeSkillFilter(skillFilter) ?? [];
|
||||
const label = normalized.length > 0 ? normalized.join(", ") : "(none)";
|
||||
console.log(`[skills] Applying skill filter: ${label}`);
|
||||
skillsLogger.debug(`Applying skill filter: ${label}`);
|
||||
filtered =
|
||||
normalized.length > 0
|
||||
? filtered.filter((entry) => normalized.includes(entry.skill.name))
|
||||
: [];
|
||||
console.log(`[skills] After filter: ${filtered.map((entry) => entry.skill.name).join(", ")}`);
|
||||
skillsLogger.debug(
|
||||
`After skill filter: ${filtered.map((entry) => entry.skill.name).join(", ") || "(none)"}`,
|
||||
);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
@@ -232,12 +235,14 @@ export function buildWorkspaceSkillSnapshot(
|
||||
const resolvedSkills = promptEntries.map((entry) => entry.skill);
|
||||
const remoteNote = opts?.eligibility?.remote?.note?.trim();
|
||||
const prompt = [remoteNote, formatSkillsForPrompt(resolvedSkills)].filter(Boolean).join("\n");
|
||||
const skillFilter = normalizeSkillFilter(opts?.skillFilter);
|
||||
return {
|
||||
prompt,
|
||||
skills: eligible.map((entry) => ({
|
||||
name: entry.skill.name,
|
||||
primaryEnv: entry.metadata?.primaryEnv,
|
||||
})),
|
||||
...(skillFilter === undefined ? {} : { skillFilter }),
|
||||
resolvedSkills,
|
||||
version: opts?.snapshotVersion,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user