UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

114 lines (113 loc) 4.63 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { a as sha256Hex } from "./crypto-digest-C4hqTb_e.js"; import { t as getGlobalHookRunner } from "./hook-runner-global-0kfmMG4T.js"; import { t as parseSkillFrontmatter } from "./frontmatter-CrPBFeiv.js"; import path from "node:path"; import fs from "node:fs/promises"; //#region src/skills/lifecycle/skill-change-hook.ts const SKILL_FILE_CANDIDATES = [ "SKILL.md", "skill.md", "skills.md", "SKILL.MD" ]; const EXCLUDED_ROOT_DIRS = /* @__PURE__ */ new Set([ ".clawhub", ".clawdhub", ".openclaw" ]); async function collectSkillTreeFiles(skillDir, relativeDir = "") { const entries = await fs.readdir(path.join(skillDir, relativeDir), { withFileTypes: true }); const files = []; for (const entry of entries.toSorted((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)) { if (!relativeDir && EXCLUDED_ROOT_DIRS.has(entry.name)) continue; const relativePath = path.join(relativeDir, entry.name); const portablePath = relativePath.split(path.sep).join("/"); const absolutePath = path.join(skillDir, relativePath); const stat = await fs.lstat(absolutePath); if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile()) throw new Error(`Skill tree contains unsupported entry ${JSON.stringify(portablePath)}.`); if (stat.isDirectory()) { files.push(...await collectSkillTreeFiles(skillDir, relativePath)); continue; } if (stat.nlink > 1) throw new Error(`Skill tree contains hard-linked file ${JSON.stringify(portablePath)}.`); const content = await fs.readFile(absolutePath); files.push({ path: portablePath, sha256: sha256Hex(content), sizeBytes: content.byteLength }); } return files; } function parseSkillArtifactMetadata(content) { const text = content.toString("utf8"); if (text.includes("\0") || !Buffer.from(text, "utf8").equals(content)) return {}; try { const frontmatter = parseSkillFrontmatter(text); return { name: normalizeOptionalString(frontmatter.name), description: normalizeOptionalString(frontmatter.description), declaredVersion: normalizeOptionalString(frontmatter.version) }; } catch { return {}; } } function hasCommittedSkillChangeHooks() { return getGlobalHookRunner()?.hasHooks("skill_changed") ?? false; } function resolveCommittedSkillChangeSource(originType) { if (originType === "clawhub") return "clawhub"; if (originType === "upload") return "upload"; return "source-install"; } async function snapshotCommittedSkillArtifact(params) { const skillDir = path.resolve(params.skillDir); const files = await collectSkillTreeFiles(skillDir); const skillFileEntry = SKILL_FILE_CANDIDATES.map((candidate) => files.find((file) => file.path === candidate)).find((entry) => entry !== void 0); if (!skillFileEntry) throw new Error(`Skill tree is missing SKILL.md: ${skillDir}`); const skillFile = path.join(skillDir, skillFileEntry.path); const frontmatter = parseSkillArtifactMetadata(await fs.readFile(skillFile)); const treeSha256 = sha256Hex(JSON.stringify(files)); return { name: frontmatter.name ?? params.skillKey, skillKey: params.skillKey, ...frontmatter.description ? { description: frontmatter.description } : {}, skillFile, skillDir, source: params.source, revision: { ...frontmatter.declaredVersion ? { declaredVersion: frontmatter.declaredVersion } : {}, contentSha256: `sha256:${skillFileEntry.sha256}`, treeSha256: `sha256:${treeSha256}`, ...params.sourceVersion ? { sourceVersion: params.sourceVersion } : {} } }; } async function snapshotCommittedSkillArtifactBestEffort(params) { try { return await snapshotCommittedSkillArtifact(params); } catch (error) { params.logger?.warn?.(`Could not snapshot committed skill change: ${String(error)}`); return; } } async function dispatchCommittedSkillChangeBestEffort(params) { const runner = getGlobalHookRunner(); if (!runner?.hasHooks("skill_changed")) return; try { await runner.runSkillChanged({ action: params.action, source: params.source, occurredAt: (/* @__PURE__ */ new Date()).toISOString(), ...params.before ? { before: params.before } : {}, ...params.after ? { after: params.after } : {}, ...params.proposal ? { proposal: params.proposal } : {} }, { workspaceDir: params.workspaceDir }); } catch (error) { params.logger?.warn?.(`Committed skill change hook failed: ${String(error)}`); } } //#endregion export { snapshotCommittedSkillArtifactBestEffort as i, hasCommittedSkillChangeHooks as n, resolveCommittedSkillChangeSource as r, dispatchCommittedSkillChangeBestEffort as t };