openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
249 lines (248 loc) • 8.89 kB
JavaScript
import { n as isErrno } from "./errno-FR4CRyya.mjs";
import "./errors-O33D01XW.mjs";
import { n as sha256Hex } from "./node-crypto-BG4u3UnX.mjs";
import "./crypto-digest-y6oZN-_F.mjs";
import { i as snapshotCommittedSkillArtifactBestEffort, n as hasCommittedSkillChangeHooks, t as dispatchCommittedSkillChangeBestEffort } from "./skill-change-hook-D61wR1rb.mjs";
import { n as resolveWorkspaceSkillInstallDir } from "./install-paths-CdlkBGI2.mjs";
import { n as digestClawHubSkillTree, t as checkClawHubSkillPlanAtPath } from "./skill-tree-digest-D56AKHfj.mjs";
import { _ as untrackClawHubSkill, g as resolveWorkspaceClawHubSkills, n as formatClawHubSkillRef, s as parseRequestedClawHubSkillRef } from "./clawhub-store-mPse6L69.mjs";
import { r as resolveClawHubSkillStatusLinkSync } from "./clawhub-status-d5dDvvjZ.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 };