plan-sess_85020676-f10d-4fa4-a9b9-0e3b1a12e973.md 6.7 KB

「插件与技能」改造为服务端驱动(技能存 .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<T> 包装;技能接口与案件无关,不传 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<String> 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 表缺建表脚本。