fix: lint centralized skills subsystem

This commit is contained in:
Shakker
2026-05-29 14:20:06 +01:00
committed by Shakker
parent d9278c8efd
commit de83e9eb87
8 changed files with 56 additions and 32 deletions

View File

@@ -186,7 +186,7 @@
"node_modules/",
"patches/",
"pnpm-lock.yaml",
"skills/",
"skills/**",
"src/auto-reply/reply/export-html/template.js",
"src/canvas-host/a2ui/a2ui.bundle.js",
"vendor/",

View File

@@ -42,7 +42,6 @@ import { normalizeOptionalString } from "../shared/string-coerce.js";
import { resolveEffectiveAgentSkillFilter } from "../skills/discovery/agent-filter.js";
import type { getRemoteSkillEligibility } from "../skills/runtime/remote.js";
import type { resolveReusableWorkspaceSkillSnapshot } from "../skills/runtime/session-snapshot.js";
import type { SkillSnapshot } from "../skills/types.js";
import { sanitizeForLog } from "../terminal/ansi.js";
import { createTrajectoryRuntimeRecorder } from "../trajectory/runtime.js";
import { resolveUserPath } from "../utils.js";

View File

@@ -224,7 +224,7 @@ describe("SkillsService", () => {
return workspaceDir;
}),
);
const firstWorkspace = workspaces[0]!;
const firstWorkspace = workspaces[0];
const first = service.getIndex({
workspaceDir: firstWorkspace,
...isolatedSkillRoots(firstWorkspace),

View File

@@ -46,13 +46,27 @@ export function resolveSkillOwner(params: {
sourceLabel: string;
skillPath: string;
}): string {
if (params.sourceKind === "workspace") return "workspace";
if (params.sourceKind === "generated") return "workspace";
if (params.sourceKind === "bundled") return "openclaw-release";
if (params.sourceKind === "clawhub") return "clawhub";
if (params.sourceKind === "plugin") return "plugin";
if (params.sourceKind === "system") return "openclaw-system";
if (params.sourceLabel === "agents-skills-personal") return "user";
if (params.sourceKind === "workspace") {
return "workspace";
}
if (params.sourceKind === "generated") {
return "workspace";
}
if (params.sourceKind === "bundled") {
return "openclaw-release";
}
if (params.sourceKind === "clawhub") {
return "clawhub";
}
if (params.sourceKind === "plugin") {
return "plugin";
}
if (params.sourceKind === "system") {
return "openclaw-system";
}
if (params.sourceLabel === "agents-skills-personal") {
return "user";
}
return path.basename(path.dirname(params.skillPath)) || "extra";
}
@@ -72,6 +86,7 @@ export function resolveSkillWritablePolicy(sourceKind: SkillSourceKind): SkillWr
case "extra":
return { writable: false, reason: "extra-root-load-only" };
}
return { writable: false, reason: "unknown-source-load-only" };
}
export function resolveSkillTrustInfo(entry: SkillEntry): SkillTrustInfo {

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { ArchiveLogger } from "../../infra/archive.js";
import { formatErrorMessage } from "../../infra/errors.js";
import {
installSkillArchiveFromPath,
@@ -52,7 +53,7 @@ export async function installUploadedSkillArchive(params: {
timeoutMs?: number;
workspaceDir: string;
config: OpenClawConfig;
log?: (message: string) => void;
log?: ArchiveLogger;
store?: SkillUploadStore;
}): Promise<UploadedSkillInstallResult> {
const store = params.store ?? defaultSkillUploadStore;

View File

@@ -93,7 +93,7 @@ function listCandidateSkillDirs(dir: string): string[] {
entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules",
)
.map((entry) => path.join(dir, entry.name))
.sort((left, right) => left.localeCompare(right));
.toSorted((left, right) => left.localeCompare(right));
} catch {
return [];
}

View File

@@ -151,7 +151,9 @@ function collectSkillTargets(dir: string, targets: Map<string, string>): void {
}).entries;
for (const entry of entries) {
const childPath = entry.path;
if (!hasPublishableSkillFile({ skillDir: childPath, rootDir: dir })) continue;
if (!hasPublishableSkillFile({ skillDir: childPath, rootDir: dir })) {
continue;
}
const basename = entry.name;
const existing = targets.get(basename);
if (existing) {

View File

@@ -65,12 +65,14 @@ function resolveCompactHomePrefixes(): string[] {
const realHomes = resolvedHomes
.map((home) => tryRealpath(home))
.filter((home): home is string => !!home);
return uniqueStrings([...resolvedHomes, ...realHomes]).sort((a, b) => b.length - a.length);
return uniqueStrings([...resolvedHomes, ...realHomes]).toSorted((a, b) => b.length - a.length);
}
function compactSkillPaths(skills: Skill[]): Skill[] {
const homes = resolveCompactHomePrefixes();
if (homes.length === 0) return skills;
if (homes.length === 0) {
return skills;
}
return skills.map((s) => ({
...s,
filePath: compactHomePath(s.filePath, homes),
@@ -106,12 +108,12 @@ function compactPathForConsoleMessage(filePath: string): string {
function isSkillVisibleInAvailableSkillsPrompt(entry: SkillEntry): boolean {
if (entry.exposure) {
return entry.exposure.includeInAvailableSkillsPrompt !== false;
return entry.exposure.includeInAvailableSkillsPrompt ?? true;
}
if (entry.invocation) {
return entry.invocation.disableModelInvocation !== true;
return !entry.invocation.disableModelInvocation;
}
return entry.skill.disableModelInvocation !== true;
return !entry.skill.disableModelInvocation;
}
function filterSkillEntries(
@@ -839,8 +841,7 @@ function loadGeneratedPluginSkillRecords(params: {
if (loadedSkills.length > maxSkillsLoadedPerSource) {
return loadedSkills
.slice()
.sort((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.toSorted((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.slice(0, maxSkillsLoadedPerSource);
}
return loadedSkills;
@@ -1107,8 +1108,7 @@ function loadSkillEntries(
if (loadedSkills.length > maxSkillsLoadedPerSource) {
return loadedSkills
.slice()
.sort((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.toSorted((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.slice(0, maxSkillsLoadedPerSource);
}
@@ -1193,7 +1193,7 @@ function loadSkillEntries(
}
const skillEntries: SkillEntry[] = Array.from(merged.values())
.sort((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.toSorted((a, b) => a.skill.name.localeCompare(b.skill.name, "en"))
.map((record) => {
const skill = record.skill;
const frontmatter =
@@ -1205,22 +1205,27 @@ function loadSkillEntries(
}) ??
({} as ParsedSkillFrontmatter);
const invocation = resolveSkillInvocationPolicy(frontmatter);
return {
const entry: SkillEntry = {
skill,
frontmatter,
metadata: resolveSkillEntryMetadata({ frontmatter, skillDir: skill.baseDir }),
invocation,
...(record.syncSourceDir !== undefined ? { syncSourceDir: record.syncSourceDir } : {}),
...(record.syncDirName !== undefined ? { syncDirName: record.syncDirName } : {}),
exposure: {
includeInRuntimeRegistry: true,
// Freshly loaded entries preserve the documented disable-model-invocation
// contract, while legacy entries without exposure metadata still use the
// fallback in isSkillVisibleInAvailableSkillsPrompt().
includeInAvailableSkillsPrompt: invocation.disableModelInvocation !== true,
userInvocable: invocation.userInvocable !== false,
includeInAvailableSkillsPrompt: !invocation.disableModelInvocation,
userInvocable: invocation.userInvocable ?? true,
},
};
if (record.syncSourceDir !== undefined) {
entry.syncSourceDir = record.syncSourceDir;
}
if (record.syncDirName !== undefined) {
entry.syncDirName = record.syncDirName;
}
return entry;
});
return skillEntries;
}
@@ -1240,7 +1245,9 @@ function escapeXml(str: string): string {
* preserving awareness of all skills before resorting to dropping.
*/
export function formatSkillsCompact(skills: Skill[]): string {
if (skills.length === 0) return "";
if (skills.length === 0) {
return "";
}
const lines = [
"\n\nThe following skills provide specialized instructions for specific tasks.",
"Use the read tool to load a skill's file when the task matches its name.",
@@ -1392,9 +1399,9 @@ function resolveWorkspaceSkillPromptState(
// Budget checks and final render both use this same representation so the
// tier decision is based on the exact strings that end up in the prompt.
// resolvedSkills keeps canonical paths for snapshot / runtime consumers.
const promptSkills = compactSkillPaths(resolvedSkills)
.slice()
.sort((a, b) => a.name.localeCompare(b.name, "en"));
const promptSkills = compactSkillPaths(resolvedSkills).toSorted((a, b) =>
a.name.localeCompare(b.name, "en"),
);
const { skillsForPrompt, truncated, compact } = applySkillsPromptLimits({
skills: promptSkills,
config: opts?.config,