# 「插件与技能」改造为服务端驱动(技能存 .agentscope 用户级目录) ## 一、存储与隔离口径(按您的两条纠正) - **技能装在用户级目录、与案件无关、不维护数据库**: `{user.dir}/.agentscope/users/{当前登录用户ID}/skills/<技能名>/SKILL.md` —— 与 harness 的工作区同根(工作区是 `{user.dir}/.agentscope/users/{userId}/workspaces/{案件}/agents/{行}`),**一律不碰 `PathConst.SKILLS_PATH`**,agent 相关文件全部留在 `.agentscope` 下。 - 定位口径收在 `WorkspaceManagerFactory`(它本来就是 `.agentscope` 布局的所有者):新增 `resolveUserSkillsPath(Path cwd, Long userId)`,能力服务与 `AgentService` 都调它,避免两处各拼一次路径。 - 同一用户跨案件共享技能;用户之间互不可见。 - **启停状态**:技能目录下的 `.disabled` 标记文件(存在=停用)。用标记文件而不是集中名单,避免「手工往目录里放技能」时名单与实际目录漂移;harness 的技能扫描只认 `SKILL.md`,多一个点文件不影响加载。 - **对话 agent 怎么用上这些技能**(两处改动): 1. `AgentService.buildAgent`:把该用户的技能目录作为技能仓库挂给 agent(`Files.isDirectory` 守卫,与现有 `skill_repositories_json` 处理并列)。 2. `AgentChatServiceImpl.doStream`:该处**已经**每轮对话构造 `RuntimeContext`(:348),加一行 `.put(SkillFilter.class, SkillFilter.disable(已停用技能名…))` → harness 侧 `effectiveFilter = builderFilter.overlay(ctx.get(SkillFilter.class))`(源码已确认),overlay 语义 = 「只停列出的,其余照旧」,**不需要重建 agent 实例**。 ## 二、后端改动(ai-server / module/agent) **新增 `controller/AgentCapabilityController.java`(`@RequestMapping("/capability")`,`Result` 包装;技能接口与案件无关,不传 caseId)** | 接口 | 作用 | |---|---| | `GET /capability/tools` | 6 个业务工具组(组名/组描述/是否默认装备/工具列表=名称+描述+参数 JSON Schema)+ 1 个「基础工具」组(execute_sql、检索表结构、执行 Python、图谱渲染、工作区信息等) | | `GET /capability/mcp` | MCP 接入信息(只读):是否启用/协议/服务名与版本/端点地址/已暴露工具数 | | `GET /capability/skills` | 技能清单:用户目录里的技能(可管理)+ `skill_repositories_json` 里 filesystem 仓库的技能(只读),带来源与启停状态 | | `GET /capability/skills/{name}` | 技能详情:SKILL.md 正文 + 附属文件清单 | | `POST /capability/skills/install` | 上传 zip 安装:路径穿越与技能名合法性校验 → 解压到用户技能目录(同名需显式 `overwrite`) | | `GET /capability/skills/{name}/delete` | 卸载(删目录;沿用本项目「删除用 GET」的既有风格,同 `AgentModelController`) | | `GET /capability/skills/{name}/toggle?enabled=` | 启停:写/删 `.disabled` 标记 | **新增 `service/AgentCapabilityService` + `impl`**(接口/实现分离、`@RequiredArgsConstructor`、`ServerException(404/400, …)`,照 `AgentModelServiceImpl` 的写法): - 工具枚举:`new Toolkit()` + `agentToolRegistry.registerBusinessTools(tk, 全部组)` → `getToolGroup(name)`(组描述/组内工具)+ `getToolSchemas()`(描述+参数);基础工具取自 MCP provider 已缓存的工具定义。 - 技能枚举:用户技能目录(一层一技能,要求 `SKILL.md`;启停名以 frontmatter 的 `name` 为准,缺失时退回目录名)+ 配置仓库(**跳过 git 条目**:会 clone/sync 有副作用,依赖也不在 pom 里)。 - 安装/卸载/启停:纯 `Files` 操作;zip 用项目已在用的 hutool `ZipUtil`(`SystemService.java:265` 有先例)+ 路径穿越校验。 **改动既有文件(小改)** 1. `WorkspaceManagerFactory.java`:新增 `resolveUserSkillsPath(cwd, userId)`。 2. `service/AgentService.java`:挂载用户技能目录 + **实例池 key 加 userId**(现 key 是 `w{案件}-a{行}-m{模型}`,不含用户 → 用户 A 建的 agent 会被 B 复用,技能与工作区就串了,与「用户隔离」直接冲突,必须修)。 3. `service/impl/AgentChatServiceImpl.java`:`RuntimeContext` 上挂 `SkillFilter`(约 5 行)。 4. `mcp/AgentScopeMcpToolProvider.java`:加只读访问器(暴露已缓存 toolkit 的 `ToolSchema`),避免能力服务重抄基础工具注册逻辑。 5. `tools/AgentToolRegistry.java`:加 `public static List allGroupNames()`(`Toolkit` 没有列举组的 API)。 ## 三、前端改动(ai-electron/frontend) 1. **新增 `ai/api/capabilityApi.ts`**(照 `modelApi.ts` 风格,multipart 上传走 `aiHttp.request`)+ `ai/api/types.ts` 补类型。 2. **重写 `ai/views/aiPlugin/index.vue`**,三个页签: - **工具能力**(默认):按组卡片(组名/描述/工具数/「默认装备」徽标)→ 抽屉看工具清单(名称/描述/参数表),带关键字过滤。 - **技能**:卡片列表(名称/描述/来源/启停开关)+「上传 zip 安装」「卸载」;抽屉看 SKILL.md 与附属文件;顶部标明技能所在目录(`.agentscope/users/{用户}/skills`)。 - **MCP 接入**:端点/协议/服务名/版本/暴露工具数 + 外部模型接入说明。 - 删除页面里全部 `codexApi`(IPC)用法与「仅桌面客户端可用」提示(这套现在浏览器里也能用)。 3. 工作台 `aiWorkbench/index.vue` 无需改动;旧工作台 `codex/views/workbench/WbPluginPage.vue` 保持原样。 ## 四、验证 1. 后端:编译通过;给技能「安装/卸载/启停」的文件层与「工具枚举」补 1~2 个单测(临时目录,不依赖 Spring/DB)并跑通。 2. 前端:`vue-tsc` 无新增错误 + `vite build` 通过。 3. 浏览器实测(沿用独立端口 + mock,不碰您的 dev server 与 8980 真实后端):工作台 → 插件与技能 → 三页签渲染;上传一个最小 zip 安装出技能;启停后 `.disabled` 标记与页面状态一致;卸载后消失。 4. **需要真实对话才能确认的一条我会如实说明**:停用后技能是否真的从 agent 可用列表消失(不再进 prompt)——这要靠一次真实对话观察,或您允许时我连真实后端跑一次。 ## 五、明确不做 / 待确认 - **不做**:目录安装(Electron 选目录 + 绝对路径信任问题)、技能市场(Git/Nacos/MySQL 仓库)、MCP 可配置化(服务端目前只「对外暴露 MCP」,没有「消费外部 MCP」的能力)。 - 顺带发现、本轮不修(可另开):会话表没有属主列(同案其他用户能看到此前会话);`agent` 表缺建表脚本。