openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
194 lines (193 loc) • 8.58 kB
JavaScript
import { t as CONFIG_DIR } from "./utils-P__uGsPB.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { n as resolveEffectiveAgentSkillFilter } from "./agent-filter-DZ-Tmqcd.js";
import { a as normalizeSkillIndexName, t as buildSkillIndexEntries } from "./skill-index-BgtIQy35.js";
import { n as hasBinary } from "./config-eval-YFuZks3-.js";
import { c as resolveSkillsInstallPreferences, i as isSkillEnvRequirementSatisfied, n as isBundledSkillAllowed, o as resolveBundledAllowlist, r as isSkillConfigPathTruthy, s as resolveSkillConfig } from "./config-Br267cOY.js";
import { t as mergeRemoteNodeSkillEntries } from "./remote-skills-Czcw8kW6.js";
import { a as loadWorkspaceSkills } from "./workspace-skill-loader-_a5Sci8B.js";
import { t as resolveBundledSkillsDir } from "./bundled-dir-DFDIT1MV.js";
import { t as evaluateEntryRequirementsForCurrentPlatform } from "./entry-status-CG367mIi.js";
import { c as resolveLocalSkillCardStatusSync, g as readClawHubSkillsLockfileStatusSync, o as resolveClawHubSkillStatusLinkSync } from "./clawhub-uninstall-Dg6VF3Pf.js";
import "./clawhub-D-1bbvLO.js";
import path from "node:path";
//#region src/skills/discovery/status.ts
const skillsLogger = createSubsystemLogger("skills");
let hasWarnedMissingBundledDir = false;
function resolveSkillStatusEntry(skills, requestedName) {
const raw = requestedName.trim();
if (!raw) return null;
const lower = raw.toLowerCase();
const normalized = normalizeSkillIndexName(raw);
const matchers = [
(skill) => skill.name === raw,
(skill) => skill.skillKey === raw,
(skill) => skill.name.toLowerCase() === lower || skill.skillKey.toLowerCase() === lower,
(skill) => Boolean(normalized) && (normalizeSkillIndexName(skill.name) === normalized || normalizeSkillIndexName(skill.skillKey) === normalized)
];
for (const matches of matchers) {
const candidates = skills.filter(matches);
if (candidates.length > 0) return candidates.length === 1 ? candidates[0] : null;
}
return null;
}
function selectPreferredInstallSpec(install, prefs) {
const findKind = (kind) => install.find((spec) => spec.kind === kind);
const brewSpec = findKind("brew");
const brewAvailable = brewSpec && hasBinary("brew");
return (prefs.preferBrew && brewAvailable ? brewSpec : void 0) ?? findKind("uv") ?? findKind("node") ?? (brewAvailable ? brewSpec : void 0) ?? findKind("go") ?? findKind("download") ?? brewSpec ?? install[0];
}
function normalizeInstallOptions(entry, prefs) {
const requiredOs = entry.metadata?.os ?? [];
if (requiredOs.length > 0 && !requiredOs.includes(process.platform)) return [];
const install = entry.metadata?.install ?? [];
if (install.length === 0) return [];
const platform = process.platform;
const supportsPlatform = (spec) => {
const osList = spec.os ?? [];
return osList.length === 0 || osList.includes(platform);
};
const filtered = install.filter(supportsPlatform);
if (filtered.length === 0) return [];
const toOption = (spec, index) => {
const id = (spec.id ?? `${spec.kind}-${index}`).trim();
const bins = spec.bins ?? [];
let label = (spec.label ?? "").trim();
if (spec.kind === "node" && spec.package) label = `Install ${spec.package} (${prefs.nodeManager})`;
if (!label) {
if (spec.kind === "brew" && spec.formula) label = `Install ${spec.formula} (brew)`;
else if (spec.kind === "node" && spec.package) label = `Install ${spec.package} (${prefs.nodeManager})`;
else if (spec.kind === "go" && spec.module) label = `Install ${spec.module} (go)`;
else if (spec.kind === "uv" && spec.package) label = `Install ${spec.package} (uv)`;
else if (spec.kind === "download" && spec.url) {
const url = spec.url.trim();
const last = url.split("/").pop();
label = `Download ${last && last.length > 0 ? last : url}`;
} else label = "Run installer";
}
return {
id,
kind: spec.kind,
label,
bins
};
};
if (filtered.every((spec) => spec.kind === "download")) {
const options = [];
for (const [index, spec] of install.entries()) if (supportsPlatform(spec)) options.push(toOption(spec, index));
return options;
}
const preferred = selectPreferredInstallSpec(filtered, prefs);
if (!preferred) return [];
return [toOption(preferred, install.indexOf(preferred))];
}
function buildSkillStatus(indexed, context) {
const entry = indexed.entry;
const skillKey = indexed.skillKey;
const { config, prefs, eligibility, allowBundled, agentSkillFilter, workspaceDir } = context;
const skillConfig = resolveSkillConfig(config, skillKey);
const disabled = skillConfig?.enabled === false;
const blockedByAllowlist = !isBundledSkillAllowed(entry, allowBundled);
const blockedByAgentFilter = agentSkillFilter !== void 0 && !indexed.agentAllowed;
const always = entry.metadata?.always === true;
const isEnvSatisfied = (envName) => isSkillEnvRequirementSatisfied({
envName,
skillConfig,
primaryEnv: entry.metadata?.primaryEnv
});
const isConfigSatisfied = (pathStr) => isSkillConfigPathTruthy(config, pathStr);
const skillSource = indexed.source;
const bundled = indexed.bundled;
const { emoji, homepage, required, missing, requirementsSatisfied, configChecks } = evaluateEntryRequirementsForCurrentPlatform({
always,
entry,
hasLocalBin: hasBinary,
remote: eligibility?.remote,
isEnvSatisfied,
isConfigSatisfied
});
const eligible = !disabled && !blockedByAllowlist && requirementsSatisfied;
const platformIncompatible = missing.os.length > 0;
const availableToAgent = eligible && !blockedByAgentFilter;
const userInvocable = indexed.userInvocable;
const isGlobalManagedSkill = !bundled && skillSource === "openclaw-managed";
const clawhub = workspaceDir && !bundled ? resolveClawHubSkillStatusLinkSync({
workspaceDir: isGlobalManagedSkill ? path.dirname(path.resolve(context.managedSkillsDir)) : workspaceDir,
skillDir: entry.skill.baseDir,
skillKey,
lockRead: isGlobalManagedSkill ? context.managedLockRead : context.clawhubLockRead,
lockfileScope: isGlobalManagedSkill ? "managed" : "workspace"
}) : void 0;
const skillCard = resolveLocalSkillCardStatusSync(entry.skill.baseDir);
return {
name: entry.skill.name,
description: entry.skill.description,
source: skillSource,
bundled,
filePath: entry.skill.filePath,
baseDir: entry.skill.baseDir,
skillKey,
primaryEnv: entry.metadata?.primaryEnv,
emoji,
homepage,
always,
disabled,
blockedByAllowlist,
blockedByAgentFilter,
eligible,
platformIncompatible,
modelVisible: availableToAgent && indexed.promptVisible,
userInvocable,
commandVisible: availableToAgent && userInvocable,
requirements: required,
missing,
configChecks,
install: normalizeInstallOptions(entry, prefs),
...clawhub ? { clawhub } : {},
...skillCard ? { skillCard } : {}
};
}
function buildWorkspaceSkillStatus(workspaceDir, opts) {
const managedSkillsDir = opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
const bundledSkillsDir = resolveBundledSkillsDir();
if (!bundledSkillsDir && !hasWarnedMissingBundledDir) {
hasWarnedMissingBundledDir = true;
skillsLogger.warn("Bundled skills directory could not be resolved; built-in skills may be missing.");
}
const agentSkillFilter = opts?.agentId ? resolveEffectiveAgentSkillFilter(opts.config, opts.agentId) : void 0;
const skillEntries = mergeRemoteNodeSkillEntries(opts?.entries ?? loadWorkspaceSkills(workspaceDir, {
config: opts?.config,
agentId: opts?.agentId,
agentSkillFilter: "ignore",
managedSkillsDir,
bundledSkillsDir
}), {
canExec: opts?.eligibility?.nodeSkills?.canExec,
node: opts?.eligibility?.nodeSkills?.node
});
const prefs = resolveSkillsInstallPreferences(opts?.config);
const allowBundled = resolveBundledAllowlist(opts?.config);
const clawhubLockRead = readClawHubSkillsLockfileStatusSync(workspaceDir);
const managedParentDir = path.dirname(path.resolve(managedSkillsDir));
const managedLockRead = managedParentDir === path.resolve(workspaceDir) ? clawhubLockRead : readClawHubSkillsLockfileStatusSync(managedParentDir);
const skillIndexEntries = buildSkillIndexEntries(skillEntries, { agentSkillFilter });
return {
workspaceDir,
managedSkillsDir,
agentId: opts?.agentId,
agentSkillFilter,
skills: skillIndexEntries.map((entry) => buildSkillStatus(entry, {
config: opts?.config,
prefs,
eligibility: opts?.eligibility,
allowBundled,
agentSkillFilter,
workspaceDir,
clawhubLockRead,
managedSkillsDir,
managedLockRead
}))
};
}
//#endregion
export { resolveSkillStatusEntry as n, buildWorkspaceSkillStatus as t };