UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

249 lines (248 loc) • 8.89 kB
import { n as isErrno } from "./errno-FR4CRyya.mjs"; import "./errors-BSdPMFao.mjs"; import { n as sha256Hex } from "./node-crypto-BInnzoVt.mjs"; import "./crypto-digest-y6oZN-_F.mjs"; import { i as snapshotCommittedSkillArtifactBestEffort, n as hasCommittedSkillChangeHooks, t as dispatchCommittedSkillChangeBestEffort } from "./skill-change-hook-nCwgL8Am.mjs"; import { n as resolveWorkspaceSkillInstallDir } from "./install-paths-CdlkBGI2.mjs"; import { n as digestClawHubSkillTree, t as checkClawHubSkillPlanAtPath } from "./skill-tree-digest-BcJY8MWQ.mjs"; import { _ as untrackClawHubSkill, g as resolveWorkspaceClawHubSkills, n as formatClawHubSkillRef, s as parseRequestedClawHubSkillRef } from "./clawhub-store-D8JOkjxF.mjs"; import { r as resolveClawHubSkillStatusLinkSync } from "./clawhub-status-CkZTI2Pa.mjs"; import { lstatSync } from "node:fs"; import path from "node:path"; import fs$1 from "node:fs/promises"; import { randomUUID } from "node:crypto"; //#region src/skills/lifecycle/clawhub-uninstall.ts async function planClawHubSkillUninstall(params) { const tracking = resolveWorkspaceClawHubSkills(params.workspaceDir); if (tracking) return await tracking.planClawHubSkillUninstall(params); let requestedRef; try { requestedRef = parseRequestedClawHubSkillRef(params.slug); } catch (error) { return { ok: false, code: "ambiguous", error: String(error) }; } return await planTrackedClawHubSkillState({ workspaceDir: params.workspaceDir, requestedRef, expectedVersion: params.expectedVersion }); } async function planTrackedClawHubSkillState(params) { const requestedRef = params.requestedRef; const slug = requestedRef.slug; const targetDir = resolveWorkspaceSkillInstallDir(params.workspaceDir, slug); const link = resolveClawHubSkillStatusLinkSync({ workspaceDir: params.workspaceDir, skillDir: targetDir, skillKey: slug }); if (!link) return { ok: false, code: "missing", error: `Skill ${JSON.stringify(slug)} is not a tracked ClawHub install.` }; if (!link.valid || !link.skillFile || !link.fileTreeSha256) return { ok: false, code: "ambiguous", error: link.valid ? `Skill ${JSON.stringify(slug)} was installed before OpenClaw recorded file fingerprints, so local changes cannot be detected.` : link.reason }; if (requestedRef.ownerHandle && link.ownerHandle !== requestedRef.ownerHandle) { const trackedRef = link.ownerHandle ? `@${link.ownerHandle}/${slug}` : slug; return { ok: false, code: "ambiguous", error: `Skill ${JSON.stringify(slug)} is tracked as ${trackedRef}, not @${requestedRef.ownerHandle}/${slug}.` }; } if (requestedRef.requestedReference && link.requestedReference !== requestedRef.requestedReference) return { ok: false, code: "ambiguous", error: `Skill ${JSON.stringify(slug)} is not tracked from ${requestedRef.requestedReference}.` }; if (link.installedVersion !== params.expectedVersion) return { ok: false, code: "modified", error: `Skill ${JSON.stringify(slug)} is at ${link.installedVersion}, expected ${params.expectedVersion}.` }; const skillFilePath = path.join(targetDir, link.skillFile.path); let content; try { const stat = await fs$1.lstat(targetDir); if (!stat.isDirectory() || stat.isSymbolicLink()) return { ok: false, code: "ambiguous", error: `Skill ${JSON.stringify(slug)} is not a regular managed directory.` }; content = await fs$1.readFile(skillFilePath); } catch (error) { return { ok: false, code: "missing", error: String(error) }; } if (sha256Hex(content) !== link.skillFile.sha256) return { ok: false, code: "modified", error: `Skill ${JSON.stringify(slug)} has local SKILL.md changes.` }; let fileTreeSha256; try { fileTreeSha256 = await digestClawHubSkillTree(targetDir); } catch (error) { return { ok: false, code: "ambiguous", error: String(error) }; } if (fileTreeSha256 !== link.fileTreeSha256) return { ok: false, code: "modified", error: `Skill ${JSON.stringify(slug)} has local file changes.` }; return { ok: true, plan: { workspaceDir: params.workspaceDir, requestedRef: requestedRef.requestedReference ?? formatClawHubSkillRef(requestedRef), slug, version: link.installedVersion, installedAt: link.installedAt, targetDir, skillFilePath: link.skillFile.path, skillFileSha256: link.skillFile.sha256, fileTreeSha256 } }; } async function applyClawHubSkillUninstall(plan, deps = {}) { const tracking = resolveWorkspaceClawHubSkills(plan.workspaceDir); if (tracking) return await tracking.applyClawHubSkillUninstall(plan, { beforePersistentApply: deps.beforePersistentApply, beforeRollback: deps.beforeRollback, onCommittedChange: deps.onCommittedChange ?? (hasCommittedSkillChangeHooks() ? dispatchCommittedSkillChangeBestEffort : void 0) }); const current = await planClawHubSkillUninstall({ workspaceDir: plan.workspaceDir, slug: plan.requestedRef, expectedVersion: plan.version }); if (!current.ok) return { ok: false, error: current.error }; const shouldDispatchChange = Boolean(deps.onCommittedChange) || hasCommittedSkillChangeHooks(); const before = shouldDispatchChange ? await snapshotCommittedSkillArtifactBestEffort({ skillDir: plan.targetDir, skillKey: plan.slug, source: "clawhub", sourceVersion: plan.version }) : void 0; const stagedDir = `${plan.targetDir}.openclaw-skill-remove-${randomUUID()}`; let staged = false; let removed = false; let restoreTracking; const rename = deps.rename ?? fs$1.rename; const sourceIdentity = await fs$1.lstat(plan.targetDir, { bigint: true }); const assertRollbackCurrent = () => { const stagedIdentity = lstatSync(stagedDir, { bigint: true }); if (sourceIdentity.dev === 0n || sourceIdentity.ino === 0n || stagedIdentity.dev !== sourceIdentity.dev || stagedIdentity.ino !== sourceIdentity.ino || lstatSync(plan.targetDir, { throwIfNoEntry: false }) !== void 0) throw new Error(`Skill ${JSON.stringify(plan.slug)} staging or destination changed during rollback.`); deps.beforeRollback?.(); }; const restoreStaged = async () => { if (deps.authorizeMutation) await deps.authorizeMutation("rollback"); assertRollbackCurrent(); await rename(stagedDir, plan.targetDir); staged = false; }; try { if (deps.authorizeMutation) await deps.authorizeMutation("apply"); deps.beforePersistentApply?.(); await rename(plan.targetDir, stagedDir); staged = true; if (!(await checkClawHubSkillPlanAtPath(plan, stagedDir, deps.readFile ?? fs$1.readFile)).ok) { await restoreStaged(); return { ok: false, error: `Skill ${JSON.stringify(plan.slug)} changed during removal.` }; } if (deps.authorizeMutation) await deps.authorizeMutation("apply"); deps.beforePersistentApply?.(); restoreTracking = await (deps.untrack ?? untrackClawHubSkill)(plan.workspaceDir, plan.slug, deps.beforePersistentApply, assertRollbackCurrent, deps.authorizeMutation); if (deps.authorizeMutation) await deps.authorizeMutation("apply"); deps.beforePersistentApply?.(); await (deps.removeDir ?? fs$1.rm)(stagedDir, { recursive: true, force: false }); removed = true; if (shouldDispatchChange) { if (deps.authorizeMutation) await deps.authorizeMutation("apply"); deps.beforePersistentApply?.(); try { await (deps.onCommittedChange ?? dispatchCommittedSkillChangeBestEffort)({ action: "removed", source: "clawhub", workspaceDir: plan.workspaceDir, before }); } catch {} } return { ok: true }; } catch (error) { const rollbackErrors = []; if (!removed) { if (restoreTracking) try { assertRollbackCurrent(); await restoreTracking(); } catch (rollbackError) { rollbackErrors.push(`could not restore lockfile: ${String(rollbackError)}`); } if (staged) try { await restoreStaged(); } catch (rollbackError) { rollbackErrors.push(`could not restore skill directory from ${stagedDir}: ${String(rollbackError)}`); } } return { ok: false, error: `${String(error)}${rollbackErrors.length > 0 ? `; rollback incomplete: ${rollbackErrors.join("; ")}` : ""}` }; } } async function guardTrackedSkillLocalState(params) { const targetDir = resolveWorkspaceSkillInstallDir(params.workspaceDir, params.slug); try { await fs$1.lstat(targetDir); } catch (error) { if (isErrno(error) && error.code === "ENOENT") return { ok: true, plan: void 0 }; return { ok: false, error: String(error) }; } const local = await planTrackedClawHubSkillState({ workspaceDir: params.workspaceDir, requestedRef: { slug: params.slug }, expectedVersion: params.previousVersion ?? "" }); if (local.ok) return { ok: true, plan: local.plan }; return { ok: false, error: local.error }; } //#endregion export { guardTrackedSkillLocalState as n, planClawHubSkillUninstall as r, applyClawHubSkillUninstall as t };