diff --git a/mateclaw-server/src/main/java/vip/mate/plugin/controller/PluginController.java b/mateclaw-server/src/main/java/vip/mate/plugin/controller/PluginController.java index dd24ca49..3ccdfb46 100644 --- a/mateclaw-server/src/main/java/vip/mate/plugin/controller/PluginController.java +++ b/mateclaw-server/src/main/java/vip/mate/plugin/controller/PluginController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.*; import vip.mate.common.result.R; import vip.mate.plugin.PluginManager; import vip.mate.plugin.model.PluginInfo; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import java.util.List; import java.util.Map; @@ -26,18 +27,21 @@ public class PluginController { @Operation(summary = "List all plugins") @GetMapping + @RequireWorkspaceRole("admin") public R> list() { return R.ok(pluginManager.listPlugins()); } @Operation(summary = "Get plugin detail") @GetMapping("/{name}") + @RequireWorkspaceRole("admin") public R get(@PathVariable String name) { return R.ok(pluginManager.getPlugin(name)); } @Operation(summary = "Disable a plugin") @PostMapping("/{name}/disable") + @RequireWorkspaceRole("admin") public R disable(@PathVariable String name) { pluginManager.disablePlugin(name); return R.ok(); @@ -45,6 +49,7 @@ public class PluginController { @Operation(summary = "Enable a plugin") @PostMapping("/{name}/enable") + @RequireWorkspaceRole("admin") public R enable(@PathVariable String name) { pluginManager.enablePlugin(name); return R.ok(); @@ -52,6 +57,7 @@ public class PluginController { @Operation(summary = "Update plugin configuration") @PutMapping("/{name}/config") + @RequireWorkspaceRole("admin") public R updateConfig(@PathVariable String name, @RequestBody Map config) { pluginManager.updateConfig(name, config); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java index d40d957d..9250c409 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*; import vip.mate.common.result.R; import vip.mate.agent.AgentService; import vip.mate.agent.binding.model.AgentSkillBinding; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import vip.mate.agent.binding.repository.AgentSkillBindingMapper; import vip.mate.agent.binding.service.AgentBindingService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -62,6 +63,7 @@ public class SkillController { @Operation(summary = "获取技能分页列表(RFC-042 §2.1)") @GetMapping + @RequireWorkspaceRole("member") public R> list( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "20") int size, @@ -90,6 +92,7 @@ public class SkillController { @Operation(summary = "获取各类型技能计数(tab 徽章用)") @GetMapping("/counts") + @RequireWorkspaceRole("member") public R> counts() { Map result = skillService.countByType(); Set realNames = realSkillNames(); @@ -250,6 +253,7 @@ public class SkillController { @Operation(summary = "重新扫描单个技能(RFC-042 §2.3.4)") @PostMapping("/{id}/rescan") + @RequireWorkspaceRole("admin") public R rescan(@PathVariable Long id) { rejectVirtualSkillMutation(id); return R.ok(skillService.rescanSecurity(id)); @@ -260,6 +264,7 @@ public class SkillController { "in a multi-instance deployment. Pulls every mate_skill_file row owned by the skill " + "down to disk; if no rows exist yet but local files do, ingests them into the canonical store.") @PostMapping("/{id}/sync-files") + @RequireWorkspaceRole("admin") public R> syncFiles(@PathVariable Long id) { rejectVirtualSkillMutation(id); SkillEntity skill = skillService.getSkill(id); @@ -278,6 +283,7 @@ public class SkillController { description = "Bulk variant of /sync-files; primarily for ops debugging when you suspect " + "the local workspace is out of sync with the canonical store.") @PostMapping("/sync-files") + @RequireWorkspaceRole("admin") public R> syncAllFiles() { var report = skillFileSyncer.syncAll(); Map body = new LinkedHashMap<>(); @@ -310,6 +316,7 @@ public class SkillController { @Operation(summary = "获取已启用技能列表") @GetMapping("/enabled") + @RequireWorkspaceRole("member") public R> listEnabled() { // Mirror the merging the paginated /skills endpoint does so the agent // edit picker (which calls this endpoint) sees MCP- and ACP-derived @@ -337,18 +344,21 @@ public class SkillController { @Operation(summary = "按类型获取技能列表") @GetMapping("/type/{skillType}") + @RequireWorkspaceRole("member") public R> listByType(@PathVariable String skillType) { return R.ok(skillService.listSkillsByType(skillType)); } @Operation(summary = "获取已启用技能摘要(按类型分组)") @GetMapping("/summary") + @RequireWorkspaceRole("member") public R>> summary() { return R.ok(skillService.getEnabledSkillSummary()); } @Operation(summary = "获取技能详情") @GetMapping("/{id}") + @RequireWorkspaceRole("member") public R get(@PathVariable Long id) { // RFC-090 §3.2 — virtual MCP-derived skills synthesize a row // on demand from the live MCP server entity. @@ -370,12 +380,14 @@ public class SkillController { @Operation(summary = "创建技能") @PostMapping + @RequireWorkspaceRole("admin") public R create(@RequestBody SkillEntity skill) { return R.ok(skillService.createSkill(skill)); } @Operation(summary = "更新技能") @PutMapping("/{id}") + @RequireWorkspaceRole("admin") public R update(@PathVariable Long id, @RequestBody SkillEntity skill) { rejectVirtualSkillMutation(id); skill.setId(id); @@ -393,6 +405,7 @@ public class SkillController { */ @Operation(summary = "硬删除技能 (admin only — 物理删除 + 工作区清空)") @DeleteMapping("/{id}") + @RequireWorkspaceRole("admin") public R delete(@PathVariable Long id) { rejectVirtualSkillMutation(id); skillService.hardDeleteSkill(id); @@ -401,6 +414,7 @@ public class SkillController { @Operation(summary = "启用/禁用技能") @PutMapping("/{id}/toggle") + @RequireWorkspaceRole("admin") public R toggle(@PathVariable Long id, @RequestParam boolean enabled) { rejectVirtualSkillMutation(id); return R.ok(skillService.toggleSkill(id, enabled)); @@ -408,6 +422,7 @@ public class SkillController { @Operation(summary = "预览技能 Prompt 增强效果(调试用,与 Agent 真实运行时一致)") @GetMapping("/prompt-preview") + @RequireWorkspaceRole("admin") public R> promptPreview() { String prompt = skillRuntimeService.buildSkillPromptEnhancement(); return R.ok(Map.of( @@ -421,6 +436,7 @@ public class SkillController { @Operation(summary = "获取 active skills 运行时视图") @GetMapping("/runtime/active") + @RequireWorkspaceRole("admin") public R> getActiveSkills() { List skills = skillRuntimeService.getActiveSkills(); return R.ok(Map.of("count", skills.size(), "skills", skills)); @@ -428,12 +444,14 @@ public class SkillController { @Operation(summary = "获取所有技能的运行时解析状态(管理页面使用)") @GetMapping("/runtime/status") + @RequireWorkspaceRole("admin") public R> getRuntimeStatus() { return R.ok(skillRuntimeService.resolveAllSkillsStatus()); } @Operation(summary = "刷新 active skills 缓存,resync=true 时同步内置技能到 workspace") @PostMapping("/runtime/refresh") + @RequireWorkspaceRole("admin") public R> refreshRuntime( @RequestParam(defaultValue = "false") boolean resync) { List resynced = List.of(); @@ -459,6 +477,7 @@ public class SkillController { */ @Operation(summary = "Pre-flight requirement statuses for a skill (RFC-090)") @GetMapping("/{id}/requirements") + @RequireWorkspaceRole("member") public R> requirements(@PathVariable Long id) { ResolvedSkill resolved = skillRuntimeService.resolveAllSkillsStatus().stream() .filter(r -> r != null && id.equals(r.getId())) @@ -524,6 +543,7 @@ public class SkillController { */ @Operation(summary = "List agents that can use this skill (RFC-090 §14.2)") @GetMapping("/{id}/employees") + @RequireWorkspaceRole("member") public R>> employees(@PathVariable Long id) { // Explicit bindings: agent_skill rows pointing to this skill. List explicitBindings = agentSkillBindingMapper.selectList( @@ -590,6 +610,7 @@ public class SkillController { */ @Operation(summary = "Read per-skill LESSONS.md (RFC-090 §11.4)") @GetMapping("/{id}/lessons") + @RequireWorkspaceRole("member") public R> getLessons(@PathVariable Long id) { ResolvedSkill resolved = skillRuntimeService.resolveAllSkillsStatus().stream() .filter(r -> r != null && id.equals(r.getId())) @@ -620,6 +641,7 @@ public class SkillController { @Operation(summary = "Clear all lessons for a skill (RFC-090 §11.4)") @PostMapping("/{id}/lessons/clear") + @RequireWorkspaceRole("admin") public R> clearLessons(@PathVariable Long id) { ResolvedSkill resolved = skillRuntimeService.resolveAllSkillsStatus().stream() .filter(r -> r != null && id.equals(r.getId())) @@ -634,6 +656,7 @@ public class SkillController { @Operation(summary = "从对话历史合成 Skill(RFC-023)") @PostMapping("/synthesize-from-conversation") + @RequireWorkspaceRole("admin") public R> synthesizeFromConversation(@RequestBody Map body) { String conversationId = (String) body.get("conversationId"); Long agentId = body.get("agentId") != null ? Long.valueOf(body.get("agentId").toString()) : null; @@ -666,6 +689,7 @@ public class SkillController { @Operation(summary = "将 skill 导出到工作区目录") @PostMapping("/{id}/export-workspace") + @RequireWorkspaceRole("admin") public R> exportToWorkspace(@PathVariable Long id) { SkillEntity skill = skillService.getSkill(id); var path = workspaceManager.exportToWorkspace(skill.getName(), skill.getSkillContent()); @@ -677,6 +701,7 @@ public class SkillController { @Operation(summary = "获取 skill 工作区信息") @GetMapping("/{id}/workspace") + @RequireWorkspaceRole("admin") public R> getWorkspaceInfo(@PathVariable Long id) { SkillEntity skill = skillService.getSkill(id); return R.ok(workspaceManager.getWorkspaceInfo(skill.getName())); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillInstallController.java b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillInstallController.java index 91dea8a2..4cdca62b 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillInstallController.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/controller/SkillInstallController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import vip.mate.common.result.R; import vip.mate.skill.installer.SkillInstaller; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import vip.mate.skill.installer.ZipSkillFetcher; import vip.mate.skill.installer.model.*; import vip.mate.skill.runtime.SkillFrontmatterParser; @@ -34,6 +35,7 @@ public class SkillInstallController { @Operation(summary = "搜索 ClawHub 市场") @GetMapping("/hub/search") + @RequireWorkspaceRole("admin") public R> searchHub( @RequestParam String q, @RequestParam(defaultValue = "20") int limit) { @@ -42,6 +44,7 @@ public class SkillInstallController { @Operation(summary = "开始异步安装 skill") @PostMapping("/start") + @RequireWorkspaceRole("admin") public R startInstall(@RequestBody InstallRequest request) { if (request.getBundleUrl() == null || request.getBundleUrl().isBlank()) { return R.fail("bundleUrl is required"); @@ -51,6 +54,7 @@ public class SkillInstallController { @Operation(summary = "查询安装任务状态") @GetMapping("/status/{taskId}") + @RequireWorkspaceRole("admin") public R getStatus(@PathVariable String taskId) { InstallTask task = skillInstaller.getTaskStatus(taskId); if (task == null) { @@ -61,6 +65,7 @@ public class SkillInstallController { @Operation(summary = "取消安装任务") @PostMapping("/cancel/{taskId}") + @RequireWorkspaceRole("admin") public R cancel(@PathVariable String taskId) { skillInstaller.cancelTask(taskId); return R.ok(); @@ -68,6 +73,7 @@ public class SkillInstallController { @Operation(summary = "上传 ZIP 安装 skill") @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @RequireWorkspaceRole("admin") public R> uploadZip( @RequestPart("file") MultipartFile zipFile, @RequestParam(defaultValue = "true") Boolean enable, @@ -91,6 +97,7 @@ public class SkillInstallController { @Operation(summary = "卸载 skill") @DeleteMapping("/{skillName}") + @RequireWorkspaceRole("admin") public R> uninstall(@PathVariable String skillName) { skillInstaller.uninstall(skillName); return R.ok(Map.of("message", "Skill '" + skillName + "' uninstalled")); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/secret/SkillSecretController.java b/mateclaw-server/src/main/java/vip/mate/skill/secret/SkillSecretController.java index 3d4761d3..9335f643 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/secret/SkillSecretController.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/secret/SkillSecretController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import vip.mate.common.result.R; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import java.util.List; import java.util.Map; @@ -36,12 +37,14 @@ public class SkillSecretController { @Operation(summary = "List secret keys + masked previews for a skill") @GetMapping + @RequireWorkspaceRole("admin") public R> list(@PathVariable Long skillId) { return R.ok(skillSecretService.listSummaries(skillId)); } @Operation(summary = "Upsert a secret value (empty value deletes it)") @PostMapping + @RequireWorkspaceRole("admin") public R put(@PathVariable Long skillId, @RequestBody Map body) { skillSecretService.put(skillId, body.get("key"), body.get("value")); return R.ok(); @@ -49,6 +52,7 @@ public class SkillSecretController { @Operation(summary = "Delete a single secret by key") @DeleteMapping("/{key}") + @RequireWorkspaceRole("admin") public R remove(@PathVariable Long skillId, @PathVariable String key) { skillSecretService.remove(skillId, key); return R.ok(); diff --git a/mateclaw-server/src/main/java/vip/mate/skill/template/SkillTemplateController.java b/mateclaw-server/src/main/java/vip/mate/skill/template/SkillTemplateController.java index 5ed11ac0..57e1933d 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/template/SkillTemplateController.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/template/SkillTemplateController.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import vip.mate.common.result.R; import vip.mate.skill.model.SkillEntity; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import java.util.List; import java.util.Map; @@ -33,12 +34,14 @@ public class SkillTemplateController { @Operation(summary = "List skill templates (RFC-091)") @GetMapping + @RequireWorkspaceRole("member") public R> list() { return R.ok(registry.all()); } @Operation(summary = "Get a single skill template") @GetMapping("/{id}") + @RequireWorkspaceRole("member") public R get(@PathVariable String id) { SkillTemplate t = registry.find(id); if (t == null) return R.fail("Template not found: " + id); @@ -47,6 +50,7 @@ public class SkillTemplateController { @Operation(summary = "Instantiate a template into a skill") @PostMapping("/{id}/instantiate") + @RequireWorkspaceRole("admin") public R instantiate( @PathVariable String id, @RequestBody Map values) { diff --git a/mateclaw-server/src/main/java/vip/mate/tool/controller/ToolController.java b/mateclaw-server/src/main/java/vip/mate/tool/controller/ToolController.java index e2074d3f..b96b2d4d 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/controller/ToolController.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/controller/ToolController.java @@ -9,6 +9,7 @@ import vip.mate.tool.model.AvailableToolDTO; import vip.mate.tool.model.ToolEntity; import vip.mate.tool.service.AvailableToolService; import vip.mate.tool.service.ToolService; +import vip.mate.workspace.core.annotation.RequireWorkspaceRole; import java.util.List; @@ -28,36 +29,42 @@ public class ToolController { @Operation(summary = "获取工具列表") @GetMapping + @RequireWorkspaceRole("member") public R> list() { return R.ok(toolService.listTools()); } @Operation(summary = "获取已启用工具列表") @GetMapping("/enabled") + @RequireWorkspaceRole("member") public R> listEnabled() { return R.ok(toolService.listEnabledTools()); } @Operation(summary = "获取员工可绑定的全部原子工具(含 MCP)") @GetMapping("/available") + @RequireWorkspaceRole("member") public R> listAvailable() { return R.ok(availableToolService.listAvailable()); } @Operation(summary = "获取工具详情") @GetMapping("/{id}") + @RequireWorkspaceRole("admin") public R get(@PathVariable Long id) { return R.ok(toolService.getTool(id)); } @Operation(summary = "创建工具(MCP)") @PostMapping + @RequireWorkspaceRole("admin") public R create(@RequestBody ToolEntity tool) { return R.ok(toolService.createTool(tool)); } @Operation(summary = "更新工具") @PutMapping("/{id}") + @RequireWorkspaceRole("admin") public R update(@PathVariable Long id, @RequestBody ToolEntity tool) { tool.setId(id); return R.ok(toolService.updateTool(tool)); @@ -65,6 +72,7 @@ public class ToolController { @Operation(summary = "删除工具") @DeleteMapping("/{id}") + @RequireWorkspaceRole("admin") public R delete(@PathVariable Long id) { toolService.deleteTool(id); return R.ok(); @@ -72,6 +80,7 @@ public class ToolController { @Operation(summary = "启用/禁用工具") @PutMapping("/{id}/toggle") + @RequireWorkspaceRole("admin") public R toggle(@PathVariable Long id, @RequestParam boolean enabled) { return R.ok(toolService.toggleTool(id, enabled)); }