mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix: lint centralized skills subsystem
This commit is contained in:
@@ -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/",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -224,7 +224,7 @@ describe("SkillsService", () => {
|
||||
return workspaceDir;
|
||||
}),
|
||||
);
|
||||
const firstWorkspace = workspaces[0]!;
|
||||
const firstWorkspace = workspaces[0];
|
||||
const first = service.getIndex({
|
||||
workspaceDir: firstWorkspace,
|
||||
...isolatedSkillRoots(firstWorkspace),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user