openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
175 lines (174 loc) • 7.06 kB
JavaScript
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 };