UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

175 lines (174 loc) 7.06 kB
import { y as uniqueStrings } from "./string-normalization-DsCfAx8q.js"; import { i as resolveOsHomeDir } from "./home-dir-BPhrG-aM.js"; import { l as readFileDescriptorBoundedSync, r as isRootFileMissingFailure, s as openRootFileSync } from "./boundary-file-read-uaJcf6X6.js"; import { r as isPathInside } from "./path-guards-Cp-mGr3-.js"; import { d as resolveConfigDir } from "./utils-P__uGsPB.js"; import { n as createSyntheticSourceInfo } from "./source-info-CcFiWAof.js"; import { n as resolveSkillInvocationPolicy, t as parseSkillFrontmatter } from "./frontmatter-CrPBFeiv.js"; import { i as resolveSkillDisplayName } from "./skill-contract-CcQSbxgS.js"; import { r as tryRealpath } from "./symlink-targets-JaagYXQi.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; //#region src/skills/loading/skill-paths.ts /** Workspace sync excludes repository internals and installed dependencies at every depth. */ function shouldSyncSkillPath(filePath) { const name = path.basename(filePath); return name !== ".git" && name !== "node_modules"; } /** Resolve the effective user home used by skill discovery. */ function resolveSkillsUserHomeDir() { return resolveOsHomeDir(process.env, os.homedir); } function resolveNativeUserHomeDir() { try { return path.resolve(os.homedir()); } catch { return; } } function resolveCompactHomePrefixes() { const resolvedHomes = [resolveSkillsUserHomeDir(), resolveNativeUserHomeDir()].filter((home) => Boolean(home)).map((home) => path.resolve(home)); const realHomes = resolvedHomes.map((home) => tryRealpath(home)).filter((home) => Boolean(home)); return uniqueStrings([...resolvedHomes, ...realHomes]).toSorted((a, b) => b.length - a.length).flatMap(compactHomePrefixesForHome); } /** Compact prompt-facing skill paths while preserving managed paths that `~` cannot reach. */ function compactPromptSkills(skills) { const prefixes = resolveCompactHomePrefixes(); if (prefixes.length === 0) return skills; const preservedRoots = resolvePreservedPromptSkillPathRoots(); const tildeRoots = resolvePromptTildeRoots(); return skills.map((skill) => ({ ...skill, filePath: shouldPreservePromptSkillPath(skill.filePath, preservedRoots, tildeRoots) ? skill.filePath : compactHomePath(skill.filePath, prefixes) })); } function resolvePreservedPromptSkillPathRoots() { const configDir = resolveConfigDir(); const promptSkillDirs = [path.resolve(configDir, "skills"), path.resolve(configDir, "plugin-skills")]; const realPromptSkillDirs = promptSkillDirs.map((dir) => tryRealpath(dir)).filter((dir) => Boolean(dir)); return uniqueStrings([...promptSkillDirs, ...realPromptSkillDirs]); } function resolvePromptTildeRoots() { const nativeHome = resolveNativeUserHomeDir(); if (!nativeHome) return []; const resolvedNativeHome = path.resolve(nativeHome); if (isContainerStateHomeWherePromptTildeEscapes(resolvedNativeHome)) return []; const realNativeHome = tryRealpath(resolvedNativeHome); return uniqueStrings([resolvedNativeHome, ...realNativeHome ? [realNativeHome] : []]); } function isContainerStateHomeWherePromptTildeEscapes(home) { const configDir = path.resolve(resolveConfigDir()); return home === "/data" && (configDir === "/data/.openclaw" || isPathInside("/data/.openclaw", configDir)); } function shouldPreservePromptSkillPath(filePath, roots, tildeRoots) { const resolvedFilePath = path.resolve(filePath); if (!roots.some((root) => resolvedFilePath === root || isPathInside(root, resolvedFilePath))) return false; return !tildeRoots.some((root) => resolvedFilePath === root || isPathInside(root, resolvedFilePath)); } function compactHomePath(filePath, prefixes) { for (const prefix of prefixes) if (filePath.startsWith(prefix)) return "~/" + normalizeCompactedSkillPath(filePath.slice(prefix.length), prefix); return filePath; } function compactHomePrefixesForHome(home) { const prefixes = [home.endsWith(path.sep) ? home : home + path.sep]; if (home.includes("\\") && !home.endsWith("\\")) prefixes.push(home + "\\"); return prefixes; } function normalizeCompactedSkillPath(filePath, matchedHomePrefix) { return matchedHomePrefix.includes("\\") ? filePath.replace(/\\/g, "/") : filePath; } /** Compact a skill path for console diagnostics. */ function compactSkillPath(filePath) { return compactHomePath(filePath, resolveCompactHomePrefixes()); } //#endregion //#region src/skills/loading/local-loader.ts function readSkillFileSync(params) { const opened = openRootFileSync({ absolutePath: params.filePath, rootPath: params.rootRealPath, rootRealPath: params.rootRealPath, boundaryLabel: "skill root", rejectSymlinks: false, rejectHardlinks: params.rejectHardlinks !== false }); if (!opened.ok) { if (!isRootFileMissingFailure(opened)) { const message = opened.error instanceof Error ? opened.error.message : `failed to open skill file (${opened.reason})`; params.onDiagnostic?.({ path: params.filePath, message }); } return null; } try { return params.maxBytes === void 0 ? fs.readFileSync(opened.fd, "utf8") : readFileDescriptorBoundedSync(opened.fd, params.maxBytes).toString("utf8"); } catch (error) { const message = error instanceof Error ? error.message : "failed to read skill file"; params.onDiagnostic?.({ path: params.filePath, message }); return null; } finally { fs.closeSync(opened.fd); } } function loadSingleSkillDirectory(params) { const skillFilePath = path.join(params.skillDir, "SKILL.md"); const raw = readSkillFileSync({ rootRealPath: params.rootRealPath, filePath: skillFilePath, maxBytes: params.maxBytes, rejectHardlinks: params.rejectHardlinks, onDiagnostic: params.onDiagnostic }); if (raw === null) return null; let frontmatter; try { frontmatter = parseSkillFrontmatter(raw); } catch (error) { const message = error instanceof Error ? error.message : "failed to parse skill frontmatter"; params.onDiagnostic?.({ path: skillFilePath, message }); return null; } const fallbackName = path.basename(params.skillDir).trim(); const name = frontmatter.name?.trim() || fallbackName; const description = frontmatter.description?.trim(); if (!name || !description) { params.onDiagnostic?.({ path: skillFilePath, message: !name ? "name is required" : "description is required" }); return null; } const invocation = resolveSkillInvocationPolicy(frontmatter); const filePath = path.resolve(skillFilePath); const baseDir = path.resolve(params.skillDir); return { skill: { name, displayName: resolveSkillDisplayName(raw, name), description, filePath, baseDir, source: params.source, sourceInfo: createSyntheticSourceInfo(filePath, { source: params.source, baseDir, scope: "project", origin: "top-level" }), disableModelInvocation: invocation.disableModelInvocation }, frontmatter, content: raw }; } //#endregion export { shouldSyncSkillPath as a, resolveSkillsUserHomeDir as i, compactPromptSkills as n, compactSkillPath as r, loadSingleSkillDirectory as t };