diff --git a/mateclaw-server/src/main/java/vip/mate/skill/installer/SkillInstaller.java b/mateclaw-server/src/main/java/vip/mate/skill/installer/SkillInstaller.java index 944b6dbd..2a4a6a96 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/installer/SkillInstaller.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/installer/SkillInstaller.java @@ -82,7 +82,10 @@ public class SkillInstaller { * {@code SkillService.hardDeleteSkill} via {@code DELETE /skills/{id}}. */ public void uninstall(String skillName, Long workspaceId) { - List 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 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()); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java b/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java index 6b48796d..49d0b809 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java @@ -274,7 +274,8 @@ public class SkillService { } /** - * 按名称查找技能(RFC-023:SkillManageTool 重名检查用) + * 按名称查找技能(全局,跨所有工作区)。仅用于内置技能同步等全局语义场景; + * 工作区相关的重名检查请用 {@link #findByName(String, Long)}。 */ public SkillEntity findByName(String name) { return skillMapper.selectOne(new LambdaQueryWrapper() @@ -282,6 +283,17 @@ public class SkillService { .last("LIMIT 1")); } + /** + * 按名称在指定工作区内查找技能(含 builtin 全局可见)。用于工作区隔离的 + * 重名/存在性检查,避免跨工作区错误命中别的工作区的同名技能。 + */ + public SkillEntity findByName(String name, Long workspaceId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .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() - .eq(SkillEntity::getName, skill.getName())); + // 名称唯一性按工作区隔离:同名技能只要不在同一工作区(且都不是 builtin)即可共存。 + // 撞 builtin 名仍视为冲突(builtin 全局可见)。 + LambdaQueryWrapper dupCheck = new LambdaQueryWrapper() + .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()); } diff --git a/mateclaw-server/src/main/java/vip/mate/skill/synthesis/SkillSynthesisService.java b/mateclaw-server/src/main/java/vip/mate/skill/synthesis/SkillSynthesisService.java index 230f448c..245570be 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/synthesis/SkillSynthesisService.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/synthesis/SkillSynthesisService.java @@ -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); }