UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

197 lines (196 loc) • 8.08 kB
import { d as pathExists } from "./fs-safe-BUXBSN-4.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { i as getAgentWorkspaceAccess, t as WorkspaceAccessUnavailableError } from "./workspace-access-BnPMMciK.mjs"; import { r as installPackageDir } from "./install-package-dir-Bloi0nzp.mjs"; import { t as evaluateSkillInstallPolicy } from "./install-security-scan-BzbKd4mU.mjs"; import { i as snapshotCommittedSkillArtifactBestEffort, n as hasCommittedSkillChangeHooks, r as resolveCommittedSkillChangeSource, t as dispatchCommittedSkillChangeBestEffort } from "./skill-change-hook-D61wR1rb.mjs"; import { n as resolveWorkspaceSkillInstallDir } from "./install-paths-CdlkBGI2.mjs"; import { t as checkClawHubSkillPlanAtPath } from "./skill-tree-digest-D56AKHfj.mjs"; import { n as withExtractedArchiveRoot } from "./install-flow-BLL1bcaF.mjs"; import path from "node:path"; //#region src/skills/lifecycle/archive-install.ts const DEFAULT_SKILL_ARCHIVE_ROOT_MARKERS = ["SKILL.md"]; /** Accepted root marker names for ClawHub skill archive uploads. */ const CLAWHUB_SKILL_ARCHIVE_ROOT_MARKERS = [ "SKILL.md", "skill.md", "skills.md", "SKILL.MD" ]; function installFailure(error, failureKind) { return { ok: false, error, failureKind }; } async function hasSkillArchiveRoot(rootDir, rootMarkers) { for (const candidate of rootMarkers) if (await pathExists(path.join(rootDir, candidate))) return true; return false; } const TRANSIENT_ARCHIVE_ERROR_PATTERNS = [ "enoent", "enospc", "eio", "eacces", "eperm", "ebusy", "emfile", "enfile", "timeout", "timed out" ]; function archiveFailureKind(error) { const lower = error.toLowerCase(); return lower.startsWith("failed to install skill:") || TRANSIENT_ARCHIVE_ERROR_PATTERNS.some((pattern) => lower.includes(pattern)) ? "unavailable" : "invalid-request"; } async function installExtractedSkillRoot(params) { try { const changeSource = resolveCommittedSkillChangeSource(params.policy?.origin.type); const sourceVersionValue = params.policy?.origin.version ?? params.policy?.origin.commit; const sourceVersion = typeof sourceVersionValue === "string" || typeof sourceVersionValue === "number" ? String(sourceVersionValue) : void 0; const captureChanges = hasCommittedSkillChangeHooks(); const workspaceAccess = getAgentWorkspaceAccess(params.workspaceDir, "loadSkills"); const access = workspaceAccess?.loadSkills ? workspaceAccess : void 0; if (access && !access.applySkillRoot) throw new WorkspaceAccessUnavailableError("Remote workspace skill installation is unavailable"); const applyRoot = access?.applySkillRoot ?? applyExtractedSkillRoot; const { policy: _policy, ...files } = params; params.beforePersistentApply?.(); const result = await applyRoot({ ...files, ...captureChanges ? { changes: { source: changeSource, sourceVersion } } : {}, beforeInstall: async (mode) => { if (!params.policy) return; const scanResult = await evaluateSkillInstallPolicy({ config: params.policy.config, onInstallPolicyWarning: params.policy.onInstallPolicyWarning, installId: params.policy.installId ?? "archive", logger: params.logger ?? {}, origin: params.policy.origin, requestedSpecifier: params.policy.requestedSpecifier, source: params.policy.source, mode, skillName: params.slug, sourceDir: params.extractedRoot }); params.beforePersistentApply?.(); return scanResult?.blocked ? { error: scanResult.blocked.reason, failureKind: scanResult.blocked.code === "security_scan_failed" ? "unavailable" : "invalid-request" } : void 0; } }); if (!result.ok) return result; if (captureChanges) await dispatchCommittedSkillChangeBestEffort({ action: result.mode === "update" ? "updated" : "created", source: changeSource, workspaceDir: params.workspaceDir, before: result.before, after: result.after, logger: params.logger }); return { ok: true, targetDir: result.targetDir }; } catch (err) { return installFailure(formatErrorMessage(err), "unavailable"); } } /** Native file replacement on the workspace host; policy and hook dispatch stay with the caller. */ async function applyExtractedSkillRoot(params) { try { if (!await hasSkillArchiveRoot(params.extractedRoot, params.rootMarkers ?? DEFAULT_SKILL_ARCHIVE_ROOT_MARKERS)) return installFailure("archive is missing SKILL.md", "invalid-request"); let targetDir; try { targetDir = resolveWorkspaceSkillInstallDir(params.workspaceDir, params.slug); } catch (err) { return installFailure(formatErrorMessage(err), "invalid-request"); } const targetExists = await pathExists(targetDir); const effectiveMode = params.mode === "update" && targetExists ? "update" : "install"; if (params.mode === "install" && targetExists) return installFailure(`Skill already exists at ${targetDir}. Re-run with force/update.`, "invalid-request"); const before = params.changes && effectiveMode === "update" ? await snapshotCommittedSkillArtifactBestEffort({ skillDir: targetDir, skillKey: params.slug, source: params.changes.source, logger: params.logger }) : void 0; const policyFailure = await params.beforeInstall?.(effectiveMode); if (policyFailure) return installFailure(policyFailure.error, policyFailure.failureKind); const expectedClawHubState = params.expectedClawHubState; let replacementBlocked; const install = await installPackageDir({ sourceDir: params.extractedRoot, targetDir, mode: effectiveMode, timeoutMs: params.timeoutMs ?? 12e4, logger: params.logger, copyErrorPrefix: "failed to install skill", beforePersistentApply: params.beforePersistentApply, authorizeMutation: params.authorizeMutation, hasDeps: false, depsLogMessage: "", ...expectedClawHubState !== void 0 ? { afterBackup: async (backupDir) => { const current = expectedClawHubState ? await checkClawHubSkillPlanAtPath(expectedClawHubState, backupDir) : { ok: false, error: `Skill ${JSON.stringify(params.slug)} appeared during update.` }; replacementBlocked = current.ok ? void 0 : `${current.error} Updating replaces the installed skill directory.`; return replacementBlocked ? { ok: false, error: replacementBlocked } : { ok: true }; } } : {} }); if (!install.ok) return { ...installFailure(install.error, replacementBlocked ? "invalid-request" : "unavailable"), ...replacementBlocked ? { replacementBlocked } : {} }; const after = params.changes ? await snapshotCommittedSkillArtifactBestEffort({ skillDir: targetDir, skillKey: params.slug, source: params.changes.source, sourceVersion: params.changes.sourceVersion, logger: params.logger }) : void 0; return { ok: true, targetDir, mode: effectiveMode, before, after }; } catch (err) { return installFailure(formatErrorMessage(err), "unavailable"); } } async function installSkillArchiveFromPath(params) { const result = await withExtractedArchiveRoot({ archivePath: params.archivePath, tempDirPrefix: "openclaw-skill-archive-", timeoutMs: params.timeoutMs ?? 12e4, logger: params.logger, rootMarkers: ["SKILL.md"], onExtracted: async (rootDir) => await installExtractedSkillRoot({ workspaceDir: params.workspaceDir, slug: params.slug, extractedRoot: rootDir, mode: params.force ? "update" : "install", timeoutMs: params.timeoutMs, logger: params.logger, policy: params.policy, beforePersistentApply: params.beforePersistentApply }) }); if (!result.ok) { const error = result.error.includes("unexpected archive layout") ? "archive is missing SKILL.md" : result.error; return installFailure(error, "failureKind" in result && (result.failureKind === "invalid-request" || result.failureKind === "unavailable") ? result.failureKind : archiveFailureKind(error)); } return result; } //#endregion export { installSkillArchiveFromPath as i, applyExtractedSkillRoot as n, installExtractedSkillRoot as r, CLAWHUB_SKILL_ARCHIVE_ROOT_MARKERS as t };