fix(skills): prevent duplicate skill installation from different repos (#778)

- Add directory conflict detection before installation
- Fix installed status check to match repo owner and name
- Add i18n translations for conflict error messages
This commit is contained in:
Dex Miller
2026-01-25 23:35:04 +08:00
committed by GitHub
parent 1c6689a0bc
commit 3434dcb87c
6 changed files with 72 additions and 9 deletions

View File

@@ -252,6 +252,50 @@ impl SkillService {
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| skill.directory.clone());
// 检查数据库中是否已有同名 directory 的 skill来自其他仓库
let existing_skills = db.get_all_installed_skills()?;
for existing in existing_skills.values() {
if existing.directory.eq_ignore_ascii_case(&install_name) {
// 检查是否来自同一仓库
let same_repo = existing.repo_owner.as_deref() == Some(&skill.repo_owner)
&& existing.repo_name.as_deref() == Some(&skill.repo_name);
if same_repo {
// 同一仓库的同名 skill返回现有记录可能需要更新启用状态
let mut updated = existing.clone();
updated.apps.set_enabled_for(current_app, true);
db.save_skill(&updated)?;
Self::sync_to_app_dir(&updated.directory, current_app)?;
log::info!(
"Skill {} 已存在,更新 {:?} 启用状态",
updated.name,
current_app
);
return Ok(updated);
} else {
// 不同仓库的同名 skill报错
return Err(anyhow!(format_skill_error(
"SKILL_DIRECTORY_CONFLICT",
&[
("directory", &install_name),
(
"existing_repo",
&format!(
"{}/{}",
existing.repo_owner.as_deref().unwrap_or("unknown"),
existing.repo_name.as_deref().unwrap_or("unknown")
)
),
(
"new_repo",
&format!("{}/{}", skill.repo_owner, skill.repo_name)
),
],
Some("uninstallFirst"),
)));
}
}
}
let dest = ssot_dir.join(&install_name);
// 如果已存在则跳过下载
@@ -933,10 +977,12 @@ impl SkillService {
Ok(meta)
}
/// 去重技能列表
/// 去重技能列表(基于完整 key不同仓库的同名 skill 分开显示)
fn deduplicate_discoverable_skills(skills: &mut Vec<DiscoverableSkill>) {
let mut seen = HashMap::new();
skills.retain(|skill| {
// 使用完整 keyowner/repo:directory作为唯一标识
// 这样不同仓库的同名 skill 会分开显示
let unique_key = skill.key.to_lowercase();
if let std::collections::hash_map::Entry::Vacant(e) = seen.entry(unique_key) {
e.insert(true);