Skill 完整内容提交与编辑状态
状态:implemented 类型:simplification Owner:backend/yuxi/modules/extensions/skills/content.py
问题
文件编辑、ZIP 替换与历史恢复各自修改有效目录并协调数据库回滚。专属 Skill 内容保存还同步撤下所有已有用户投影;前端分别维护文件、依赖和发版状态,使“保存并发版”只能保存当前局部草稿。
决策
实现方案
受管理 Skill 使用固定 slug 和不可变内容目录。内容提交入口先取得 Agent → Skill 资源锁,校验预期修订,再准备完整包并校验路径、根说明及依赖,最后提交当前目录引用与索引。内容提交可按请求同事务新增历史记录,记录引用该完整目录。恢复校验历史哈希与当前依赖权限后通过相同入口更新当前引用。普通 Skill 复用内容提交,历史入口只向专属 Skill 开放。内置与自建 Agent 共用绑定能力;内置 Agent 不可删除,其专属绑定持续保留,不新增解绑或重置状态。依赖索引使用用例的 owning session 读取,清理的新事务沿用调用方数据库引擎;内容用例不额外创建全局连接池。
现有 OpenAPI 的单文件、依赖和目录读写 HTTP 契约保留;本次不把前端 consumer 的迁移当作删除公开接口的授权。edit.py 仅适配文件级修订与单项变更,全部写入同一个 content.py,不保留原地写入或异常补偿机制。前端只使用整包 content 接口;文件读取仍服务预览,导出与 ZIP 导入保持原入口。这是对既有 HTTP 契约的保留,与旧数据库兼容无关。
前端为完整 Skill 保存一份编辑状态;切换文件或页签保留草稿,文件和依赖表单编辑同一根说明。文件区域或依赖区域保存全部修改,历史页发版保留已保存内容;绑定、历史与交互取舍见专属 Skill 决定。ZIP 导入与导出保留,专属编辑保留所属 Agent 导航上下文。
内容保存由运行准备物化当前内容,权限撤销继续在授权提交前撤下旧投影。当前与历史记录引用的目录不能清理,无引用目录在提交后的新资源锁内清理;文件清理在线程中执行,避免阻塞请求事件循环;清理失败只记录日志,下次提交或历史删除再次清理。执行中根说明沿用已经准备的文本,其他文件与能力授权保持现有机制。
替代方案
| 方案 | 取舍 |
|---|---|
| keep:保留分散写入 | 保留多套补偿流程、全用户同步与局部保存语义 |
| narrow:只整理函数和按钮 | 局部改善阅读,仍需协调正在使用的文件与数据库 |
| replace:资源锁内准备完整内容、数据库提交引用 | 统一失败边界,代价是内容目录存储与引用清理 |
| remove:取消 ZIP、依赖或历史 | 破坏当前已确认的用户能力,保留这些入口并复用提交 |
后果
完整保存使用整包修订,两个修改不同文件的编辑者仍可能发生冲突;冲突保留本地草稿并要求重新加载。每次内容变更复制完整包,当前包预算保持 50 MiB 和 2000 条目。未提交或未引用目录不会影响当前内容;不承诺文件系统损坏自动恢复。版本标识保持北京时间年月日与 UUID8,历史删除不能删除仍作为当前内容的目录。
验证
验证普通与专属 Skill 的原子完整保存、409 保留、发布引用、恢复、删除当前历史后的内容保留、并发写入与引用清理、数据库提交失败仍读取旧内容,以及内容保存不遍历用户投影。真实 HTTP、PostgreSQL 和 worker 回读当前内容、历史索引、投影与模型输入;前端覆盖跨文件草稿、依赖与根说明同步和统一保存。owning 文档与真实页面共同核对行为,界面证据保存在仓库外;独立 Reviewer 检查完整差异与旧补偿路径的删除。
旧能力不存在:搜索确认在线编辑、专属替换和历史恢复不再原地替换当前内容,不再通过 ORM 临时字段传递提交结果,普通内容保存不再调用全用户投影失效用例。
重新引入条件:只有新的已确认 consumer 要求独立文件级发布或全用户内容即时同步时,重新评估并记录取舍。
| 验收主张 | 失败面 | 语义 Owner | 直接证据 / 命令 | 负向案例 | 当前结果 |
|---|---|---|---|---|---|
| 完整保存与历史引用 | 数据库与内容目录 | content.py | unit、真实 HTTP integration、worker E2E | 提交失败、过期基准、并发 prune | Passed |
| 一份编辑草稿 | 多文件与依赖 | SkillDetailView.vue | 组件测试与真实页面 | 409 保留跨文件修改 | Passed |