mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(skill,tool,plugin): gate skill/tool/plugin endpoints by workspace role
This commit is contained in:
parent
f4964bc6d8
commit
41f3408209
@ -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<PluginInfo>> list() {
|
||||
return R.ok(pluginManager.listPlugins());
|
||||
}
|
||||
|
||||
@Operation(summary = "Get plugin detail")
|
||||
@GetMapping("/{name}")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<PluginInfo> get(@PathVariable String name) {
|
||||
return R.ok(pluginManager.getPlugin(name));
|
||||
}
|
||||
|
||||
@Operation(summary = "Disable a plugin")
|
||||
@PostMapping("/{name}/disable")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<Void> 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<Void> 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<Void> updateConfig(@PathVariable String name,
|
||||
@RequestBody Map<String, Object> config) {
|
||||
pluginManager.updateConfig(name, config);
|
||||
|
||||
@ -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<IPage<SkillEntity>> 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<Map<String, Long>> counts() {
|
||||
Map<String, Long> result = skillService.countByType();
|
||||
Set<String> realNames = realSkillNames();
|
||||
@ -250,6 +253,7 @@ public class SkillController {
|
||||
|
||||
@Operation(summary = "重新扫描单个技能(RFC-042 §2.3.4)")
|
||||
@PostMapping("/{id}/rescan")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<SkillEntity> 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<Map<String, Object>> 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<Map<String, Object>> syncAllFiles() {
|
||||
var report = skillFileSyncer.syncAll();
|
||||
Map<String, Object> body = new LinkedHashMap<>();
|
||||
@ -310,6 +316,7 @@ public class SkillController {
|
||||
|
||||
@Operation(summary = "获取已启用技能列表")
|
||||
@GetMapping("/enabled")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<List<SkillEntity>> 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<List<SkillEntity>> listByType(@PathVariable String skillType) {
|
||||
return R.ok(skillService.listSkillsByType(skillType));
|
||||
}
|
||||
|
||||
@Operation(summary = "获取已启用技能摘要(按类型分组)")
|
||||
@GetMapping("/summary")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<Map<String, List<String>>> summary() {
|
||||
return R.ok(skillService.getEnabledSkillSummary());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取技能详情")
|
||||
@GetMapping("/{id}")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<SkillEntity> 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<SkillEntity> create(@RequestBody SkillEntity skill) {
|
||||
return R.ok(skillService.createSkill(skill));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新技能")
|
||||
@PutMapping("/{id}")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<SkillEntity> 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<Void> 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<SkillEntity> 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<Map<String, Object>> 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<Map<String, Object>> getActiveSkills() {
|
||||
List<ResolvedSkill> 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<List<ResolvedSkill>> getRuntimeStatus() {
|
||||
return R.ok(skillRuntimeService.resolveAllSkillsStatus());
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新 active skills 缓存,resync=true 时同步内置技能到 workspace")
|
||||
@PostMapping("/runtime/refresh")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<Map<String, Object>> refreshRuntime(
|
||||
@RequestParam(defaultValue = "false") boolean resync) {
|
||||
List<String> 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<Map<String, Object>> 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<List<Map<String, Object>>> employees(@PathVariable Long id) {
|
||||
// Explicit bindings: agent_skill rows pointing to this skill.
|
||||
List<AgentSkillBinding> 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<Map<String, Object>> 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<Map<String, Object>> 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<Map<String, Object>> synthesizeFromConversation(@RequestBody Map<String, Object> 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<Map<String, Object>> 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<Map<String, Object>> getWorkspaceInfo(@PathVariable Long id) {
|
||||
SkillEntity skill = skillService.getSkill(id);
|
||||
return R.ok(workspaceManager.getWorkspaceInfo(skill.getName()));
|
||||
|
||||
@ -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<List<HubSkillInfo>> 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<InstallTask> 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<InstallTask> 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<Void> 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<Map<String, Object>> 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<Map<String, String>> uninstall(@PathVariable String skillName) {
|
||||
skillInstaller.uninstall(skillName);
|
||||
return R.ok(Map.of("message", "Skill '" + skillName + "' uninstalled"));
|
||||
|
||||
@ -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<SkillSecretService.SecretSummary>> 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<Void> put(@PathVariable Long skillId, @RequestBody Map<String, String> 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<Void> remove(@PathVariable Long skillId, @PathVariable String key) {
|
||||
skillSecretService.remove(skillId, key);
|
||||
return R.ok();
|
||||
|
||||
@ -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<SkillTemplate>> list() {
|
||||
return R.ok(registry.all());
|
||||
}
|
||||
|
||||
@Operation(summary = "Get a single skill template")
|
||||
@GetMapping("/{id}")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<SkillTemplate> 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<SkillEntity> instantiate(
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> values) {
|
||||
|
||||
@ -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<ToolEntity>> list() {
|
||||
return R.ok(toolService.listTools());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取已启用工具列表")
|
||||
@GetMapping("/enabled")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<List<ToolEntity>> listEnabled() {
|
||||
return R.ok(toolService.listEnabledTools());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取员工可绑定的全部原子工具(含 MCP)")
|
||||
@GetMapping("/available")
|
||||
@RequireWorkspaceRole("member")
|
||||
public R<List<AvailableToolDTO>> listAvailable() {
|
||||
return R.ok(availableToolService.listAvailable());
|
||||
}
|
||||
|
||||
@Operation(summary = "获取工具详情")
|
||||
@GetMapping("/{id}")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<ToolEntity> get(@PathVariable Long id) {
|
||||
return R.ok(toolService.getTool(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "创建工具(MCP)")
|
||||
@PostMapping
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<ToolEntity> create(@RequestBody ToolEntity tool) {
|
||||
return R.ok(toolService.createTool(tool));
|
||||
}
|
||||
|
||||
@Operation(summary = "更新工具")
|
||||
@PutMapping("/{id}")
|
||||
@RequireWorkspaceRole("admin")
|
||||
public R<ToolEntity> 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<Void> 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<ToolEntity> toggle(@PathVariable Long id, @RequestParam boolean enabled) {
|
||||
return R.ok(toolService.toggleTool(id, enabled));
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user