refactor(skills): add serialization to add/remove/install operations
Apply async serialization to prevent concurrent operations: - addSkill: serialized by target skill name - removeSkill: serialized by skill name - installSkill: serialized by skill ID Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac7c124109
commit
7ddf4f76a3
2 changed files with 41 additions and 0 deletions
|
|
@ -17,6 +17,7 @@ import { existsSync } from "node:fs";
|
|||
import { DATA_DIR } from "../../shared/index.js";
|
||||
import { binaryExists } from "./eligibility.js";
|
||||
import { bumpSkillsVersion } from "./watcher.js";
|
||||
import { serialize, SerializeKeys } from "./serialize.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
|
|
@ -356,8 +357,23 @@ async function isSkillDirectory(dir: string): Promise<boolean> {
|
|||
|
||||
/**
|
||||
* Add a skill from a GitHub repository
|
||||
*
|
||||
* Operations are serialized to prevent concurrent modifications
|
||||
* to the same skill directory.
|
||||
*/
|
||||
export async function addSkill(request: SkillAddRequest): Promise<SkillAddResult> {
|
||||
// Parse source to determine the target name for serialization key
|
||||
const parsed = parseSource(request.source);
|
||||
const targetName = request.name ?? (parsed?.skillPath ? basename(parsed.skillPath) : parsed?.repo ?? "default");
|
||||
|
||||
// Serialize operations for the same target
|
||||
return serialize(SerializeKeys.skillAdd(targetName), () => addSkillInternal(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of addSkill (serialized)
|
||||
*/
|
||||
async function addSkillInternal(request: SkillAddRequest): Promise<SkillAddResult> {
|
||||
const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
||||
|
||||
// Check git is available
|
||||
|
|
@ -489,8 +505,17 @@ export async function addSkill(request: SkillAddRequest): Promise<SkillAddResult
|
|||
|
||||
/**
|
||||
* Remove an installed skill
|
||||
*
|
||||
* Operations are serialized to prevent concurrent modifications.
|
||||
*/
|
||||
export async function removeSkill(name: string): Promise<SkillAddResult> {
|
||||
return serialize(SerializeKeys.skillRemove(name), () => removeSkillInternal(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of removeSkill (serialized)
|
||||
*/
|
||||
async function removeSkillInternal(name: string): Promise<SkillAddResult> {
|
||||
const targetDir = join(SKILLS_DIR, name);
|
||||
|
||||
if (!existsSync(targetDir)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { DATA_DIR } from "../../shared/index.js";
|
|||
import type { Skill, SkillInstallSpec, SkillsInstallConfig } from "./types.js";
|
||||
import { getSkillKey } from "./types.js";
|
||||
import { binaryExists } from "./eligibility.js";
|
||||
import { serialize, SerializeKeys } from "./serialize.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
|
|
@ -481,11 +482,26 @@ function checkInstallPrerequisites(
|
|||
/**
|
||||
* Install skill dependencies
|
||||
*
|
||||
* Operations are serialized to prevent concurrent installations
|
||||
* of the same skill from interfering with each other.
|
||||
*
|
||||
* @param request - Install request
|
||||
* @returns Install result
|
||||
*/
|
||||
export async function installSkill(
|
||||
request: SkillInstallRequest,
|
||||
): Promise<SkillInstallResult> {
|
||||
// Serialize operations for the same skill
|
||||
return serialize(SerializeKeys.skillInstall(request.skill.id), () =>
|
||||
installSkillInternal(request),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation of installSkill (serialized)
|
||||
*/
|
||||
async function installSkillInternal(
|
||||
request: SkillInstallRequest,
|
||||
): Promise<SkillInstallResult> {
|
||||
const { skill, installId, prefs } = request;
|
||||
const timeoutMs = Math.min(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue