skills: normalize capability aliases and shape variants

This commit is contained in:
Vincent Koc
2026-02-27 09:13:02 -08:00
parent 482bd91dfd
commit 269fe0f624

View File

@@ -104,14 +104,137 @@ export function resolveOpenClawMetadata(
}
function parseCapabilities(raw: unknown): SkillCapability[] | undefined {
const list = normalizeStringList(raw);
if (list.length === 0) {
const canonical = new Set<SkillCapability>();
const names = extractCapabilityNames(raw);
for (const name of names) {
const normalized = normalizeCapabilityName(name);
if (normalized) {
canonical.add(normalized);
}
}
return canonical.size > 0 ? [...canonical] : undefined;
}
const CAPABILITY_SET = new Set<string>(SKILL_CAPABILITIES as readonly string[]);
// Accept common naming used across Codex/Claude/Cursor and map to canonical OpenClaw capabilities.
const CAPABILITY_ALIASES: Record<string, SkillCapability> = {
// shell
bash: "shell",
command: "shell",
commands: "shell",
exec: "shell",
process: "shell",
shell: "shell",
terminal: "shell",
"shell.exec": "shell",
"shell.execute": "shell",
shell_exec: "shell",
// filesystem
"apply-patch": "filesystem",
apply_patch: "filesystem",
edit: "filesystem",
file: "filesystem",
files: "filesystem",
filesystem: "filesystem",
fs: "filesystem",
write: "filesystem",
// network
fetch: "network",
http: "network",
mcp: "network",
network: "network",
web: "network",
webfetch: "network",
"web-fetch": "network",
web_fetch: "network",
web_search: "network",
"web.search": "network",
"network.fetch": "network",
"network.search": "network",
// browser / computer-use style
browser: "browser",
"computer-use": "browser",
computer_use: "browser",
gui: "browser",
screen: "browser",
ui: "browser",
// sessions / orchestration
delegate: "sessions",
orchestration: "sessions",
sessions: "sessions",
sessions_send: "sessions",
sessions_spawn: "sessions",
subagent: "sessions",
subagents: "sessions",
// messaging
chat: "messaging",
message: "messaging",
messages: "messaging",
messaging: "messaging",
// scheduling
cron: "scheduling",
schedule: "scheduling",
scheduler: "scheduling",
scheduling: "scheduling",
timer: "scheduling",
};
function normalizeCapabilityName(raw: string): SkillCapability | undefined {
const key = raw.trim().toLowerCase();
if (!key) {
return undefined;
}
const valid = list.filter((v): v is SkillCapability =>
(SKILL_CAPABILITIES as readonly string[]).includes(v),
);
return valid.length > 0 ? valid : undefined;
if (CAPABILITY_SET.has(key)) {
return key as SkillCapability;
}
const alias = CAPABILITY_ALIASES[key];
if (alias) {
return alias;
}
const firstSegment = key.split(/[._:-]/)[0];
if (CAPABILITY_SET.has(firstSegment)) {
return firstSegment as SkillCapability;
}
return undefined;
}
function extractCapabilityNames(raw: unknown): string[] {
if (!raw) {
return [];
}
if (typeof raw === "string") {
return normalizeStringList(raw);
}
if (Array.isArray(raw)) {
const names: string[] = [];
for (const entry of raw) {
if (typeof entry === "string") {
names.push(entry);
continue;
}
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
const obj = entry as Record<string, unknown>;
const candidate = [obj.name, obj.type, obj.id, obj.capability].find(
(value) => typeof value === "string",
);
if (typeof candidate === "string") {
names.push(candidate);
}
}
}
return names;
}
if (typeof raw === "object") {
return Object.keys(raw as Record<string, unknown>);
}
return [];
}
export function resolveSkillInvocationPolicy(