fix(skill): 优化远程技能安装服务,优化临时工作目录管理

This commit is contained in:
Wenjie Zhang 2026-04-01 15:07:07 +08:00
parent a828d7154b
commit 82d4add70b

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
import os import os
import re import re
import shutil
import tempfile import tempfile
from pathlib import Path from pathlib import Path
@ -12,7 +13,6 @@ from yuxi.services.skill_service import import_skill_dir, is_valid_skill_slug
ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]") ANSI_ESCAPE_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
CONTROL_SEQUENCE_RE = re.compile(r"\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[\(\)][A-Za-z0-9]") CONTROL_SEQUENCE_RE = re.compile(r"\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[\(\)][A-Za-z0-9]")
SKILL_LINE_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
CLI_TIMEOUT_SECONDS = 300 CLI_TIMEOUT_SECONDS = 300
@ -60,7 +60,7 @@ def _parse_available_skills(output: str) -> list[dict[str, str]]:
continue continue
if "Use --skill " in line: if "Use --skill " in line:
break break
if not SKILL_LINE_RE.fullmatch(line): if not is_valid_skill_slug(line):
continue continue
if line in seen: if line in seen:
continue continue
@ -74,10 +74,12 @@ def _parse_available_skills(output: str) -> list[dict[str, str]]:
continue continue
if "Use --skill " in next_line: if "Use --skill " in next_line:
break break
if SKILL_LINE_RE.fullmatch(next_line): if is_valid_skill_slug(next_line):
break break
if next_line and next_line[0].isalpha(): if next_line and next_line[0].isalpha():
description = next_line description = next_line
else:
continue
break break
seen.add(line) seen.add(line)
@ -114,20 +116,27 @@ async def _run_skills_cli(
return combined return combined
def _create_isolated_workdir() -> tuple[str, dict[str, str], str]:
temp_home = tempfile.mkdtemp(prefix=".remote-skills-")
env = os.environ.copy()
env["HOME"] = temp_home
workdir = str(Path(temp_home) / "workspace")
Path(workdir).mkdir(parents=True, exist_ok=True)
return temp_home, env, workdir
async def list_remote_skills(source: str) -> list[dict[str, str]]: async def list_remote_skills(source: str) -> list[dict[str, str]]:
normalized_source = _normalize_source(source) normalized_source = _normalize_source(source)
with tempfile.TemporaryDirectory(prefix=".remote-skills-") as temp_home: temp_home, env, workdir = _create_isolated_workdir()
env = os.environ.copy() try:
env["HOME"] = temp_home
workdir = str(Path(temp_home) / "workspace")
Path(workdir).mkdir(parents=True, exist_ok=True)
output = await _run_skills_cli( output = await _run_skills_cli(
["npx", "-y", "skills", "add", normalized_source, "--list"], ["npx", "-y", "skills", "add", normalized_source, "--list"],
env=env, env=env,
cwd=workdir, cwd=workdir,
) )
finally:
shutil.rmtree(temp_home, ignore_errors=True)
skills = _parse_available_skills(output) skills = _parse_available_skills(output)
if not skills: if not skills:
@ -145,12 +154,8 @@ async def install_remote_skill(
normalized_source = _normalize_source(source) normalized_source = _normalize_source(source)
normalized_skill = _normalize_skill_name(skill) normalized_skill = _normalize_skill_name(skill)
with tempfile.TemporaryDirectory(prefix=".remote-skills-") as temp_home: temp_home, env, workdir = _create_isolated_workdir()
env = os.environ.copy() try:
env["HOME"] = temp_home
workdir = str(Path(temp_home) / "workspace")
Path(workdir).mkdir(parents=True, exist_ok=True)
available_skills = _parse_available_skills( available_skills = _parse_available_skills(
await _run_skills_cli( await _run_skills_cli(
["npx", "-y", "skills", "add", normalized_source, "--list"], ["npx", "-y", "skills", "add", normalized_source, "--list"],
@ -188,3 +193,5 @@ async def install_remote_skill(
source_dir=installed_dir, source_dir=installed_dir,
created_by=created_by, created_by=created_by,
) )
finally:
shutil.rmtree(temp_home, ignore_errors=True)