feat(skill): scope skill dedup, name uniqueness and reinstall/uninstall lookup by workspace

This commit is contained in:
matevip 2026-07-24 10:27:32 +08:00
parent c18ff31ae8
commit 67301c5f7c
3 changed files with 34 additions and 11 deletions

View File

@ -82,7 +82,10 @@ public class SkillInstaller {
* {@code SkillService.hardDeleteSkill} via {@code DELETE /skills/{id}}.
*/
public void uninstall(String skillName, Long workspaceId) {
List<SkillEntity> skills = skillService.listSkills();
// Scope the lookup to this workspace (+ builtin) so a same-named skill in
// another workspace is never picked up (which would wrongly 403 below even
// though the current workspace has its own skill to uninstall).
List<SkillEntity> skills = skillService.listSkills(workspaceId);
SkillEntity target = skills.stream()
.filter(s -> s.getName().equals(skillName))
.findFirst()
@ -147,8 +150,9 @@ public class SkillInstaller {
return CompletableFuture.completedFuture(null);
}
// 3. 检查是否已存在
boolean exists = skillService.listSkills().stream()
// 3. 检查是否已存在按工作区隔离只在本工作区 + builtin 范围内查重
// 不同工作区的同名技能可以各自独立安装
boolean exists = skillService.listSkills(request.getWorkspaceId()).stream()
.anyMatch(s -> s.getName().equals(skillName));
if (exists && !Boolean.TRUE.equals(request.getOverwrite())) {
task.markFailed("Skill '" + skillName + "' already exists. Set overwrite=true to replace.");
@ -218,7 +222,8 @@ public class SkillInstaller {
throw new vip.mate.exception.MateClawException("err.skill.name_required", "Cannot determine skill name from bundle");
}
boolean exists = skillService.listSkills().stream()
// Workspace-scoped dedup: same-named skills in different workspaces coexist.
boolean exists = skillService.listSkills(workspaceId).stream()
.anyMatch(s -> s.getName().equals(skillName));
if (exists && !overwrite) {
throw new vip.mate.exception.MateClawException("err.skill.name_exists",
@ -263,7 +268,9 @@ public class SkillInstaller {
boolean enable, Long workspaceId) {
SkillEntity skillEntity;
if (exists) {
skillEntity = skillService.listSkills().stream()
// Locate the row to update within THIS workspace (+ builtin), so a
// reinstall never grabs a same-named skill owned by another workspace.
skillEntity = skillService.listSkills(workspaceId).stream()
.filter(s -> s.getName().equals(skillName))
.findFirst().orElseThrow();
skillEntity.setSkillContent(bundle.content());

View File

@ -274,7 +274,8 @@ public class SkillService {
}
/**
* 按名称查找技能RFC-023SkillManageTool 重名检查用
* 按名称查找技能全局跨所有工作区仅用于内置技能同步等全局语义场景
* 工作区相关的重名检查请用 {@link #findByName(String, Long)}
*/
public SkillEntity findByName(String name) {
return skillMapper.selectOne(new LambdaQueryWrapper<SkillEntity>()
@ -282,6 +283,17 @@ public class SkillService {
.last("LIMIT 1"));
}
/**
* 按名称在指定工作区内查找技能 builtin 全局可见用于工作区隔离的
* 重名/存在性检查避免跨工作区错误命中别的工作区的同名技能
*/
public SkillEntity findByName(String name, Long workspaceId) {
LambdaQueryWrapper<SkillEntity> wrapper = new LambdaQueryWrapper<SkillEntity>()
.eq(SkillEntity::getName, name);
applyWorkspaceScope(wrapper, workspaceId);
return skillMapper.selectOne(wrapper.last("LIMIT 1"));
}
/**
* 按类型获取技能列表
*/
@ -323,9 +335,12 @@ public class SkillService {
throw new MateClawException("err.skill.name_required", "技能名称不能为空");
}
// 检查名称唯一性
Long count = skillMapper.selectCount(new LambdaQueryWrapper<SkillEntity>()
.eq(SkillEntity::getName, skill.getName()));
// 名称唯一性按工作区隔离同名技能只要不在同一工作区且都不是 builtin即可共存
// builtin 名仍视为冲突builtin 全局可见
LambdaQueryWrapper<SkillEntity> dupCheck = new LambdaQueryWrapper<SkillEntity>()
.eq(SkillEntity::getName, skill.getName());
applyWorkspaceScope(dupCheck, skill.getWorkspaceId());
Long count = skillMapper.selectCount(dupCheck);
if (count > 0) {
throw new MateClawException("err.skill.name_exists", "技能名称已存在: " + skill.getName());
}

View File

@ -95,8 +95,9 @@ public class SkillSynthesisService {
}
name = name.strip().toLowerCase().replaceAll("[^a-z0-9._-]", "-");
// 去重
SkillEntity existing = skillService.findByName(name);
// 去重按工作区隔离只与本工作区 + builtin 的同名技能避让
// 不同工作区的同名技能不应互相影响命名
SkillEntity existing = skillService.findByName(name, workspaceId);
if (existing != null) {
name = name + "-" + (System.currentTimeMillis() % 10000);
}