Pull Request已成功合入, 合并人@openJiuwen-bot
(感谢 wang_cheng_zhao 的贡献)变更摘要
本 PR 补齐了 Web 端两条此前只有库级单测、缺少浏览器旅程覆盖的 ZIP 路径:多份产物打包下载与本地 Skill 文件夹导入。新增一个独立的 ZIP 读取辅助模块 test/helpers/zip-archive.ts,仅依赖 Node 自带 node:zlib 的 inflateRawSync,通过解析中央目录来解包产品生成的归档,从而避免「用打包方同一库解包」带来的自证风险。在此基础上新增两条 Playwright 旅程 test/journey-artifact-archive.spec.ts(J10)与 test/journey-skill-folder-import.spec.ts(J11),分别覆盖 createArtifactArchive 产出的 artifacts.zip 以及 Skill 目录整包上传后在服务端解开并安装的完整用户流程。
主要改动
- 新增独立 ZIP 解析工具:
test/helpers/zip-archive.ts定义ZipEntry接口与readZipEntries/readZipText,基于inflateRawSync按中央目录逐条解析,支持STORED/DEFLATED两种压缩方式,并在签名异常、压缩方式不支持或解压大小与声明不一致时抛错。 - 新增产物打包下载旅程 J10:
test/journey-artifact-archive.spec.ts通过scriptedModel驱动的本地 stub 在results/summary.md与tables/measurements.csv写入并declare_artifact,验证多选状态下「下载所选」按钮的禁用/启用与「已选 2 项」提示,再下载artifacts.zip并用独立解包器比对路径与内容一致。 - 新增 Skill 文件夹导入旅程 J11:
test/journey-skill-folder-import.spec.ts通过input[webkitdirectory]分别上传含SKILL.md的合法目录与仅含readme.md的非法目录,验证非法目录时错误提示包含SKILL.md且不产生安装,合法目录导入后打开「Skill 资源浏览器」核对SKILL.md与reference/notes.md文件树,并确认列表卡片与「已安装」计数增加。 - 导入旅程的清理与状态校验: J11 通过
/api/skills接口按名称解析已导入 Skill 的id,并在finally中回退按名称查找后执行DELETE清理,避免上传成功但 id 查询失败时残留安装记录。 - 旅程隔离与安全约束: 两条旅程均设置
zh-CN本地化、使用旅程独占的 Project/Session 或 Skill 库状态、由本地 stub 提供模型且声明无外部成本与外部凭据访问。


欢迎来到 openJiuwen 社区
Hey @wang_cheng_zhao , 感谢你对社区的贡献.
机器人使用手册
有关指令的使用,可以点击 此处 查看详情。开发人员可以在每个PR或Issue下方评论特定指令来触发机器人任务。


The pipeline(pipeline number:156) is running. Please wait a moment...


AI 自动检视,仅供参考,问题都需要人工评估分析.
本 PR 补了两条此前产品侧已有、但浏览器旅程未覆盖的路径:J10 多选交付物打包下载成 artifacts.zip,以及 J11 用本地 Skill 文件夹(webkitdirectory)导入。相对最新 origin/main 三文件、+398 行、ahead 1 / behind 0,范围干净,没有夹带无关改动。
做法上合理。产物打包侧故意不用产品同款 fflate 去验 ZIP,而是用 Node 自带 inflateRawSync 写了独立的中央目录读取(test/helpers/zip-archive.ts),避免「打包和解压约定一致却对别的解压工具无效」的假绿;旅程再核对路径与正文。Skill 导入先喂无 SKILL.md 的目录断言拒绝文案,再喂合规目录,断言资源浏览器文件树、参考文件正文、列表卡片描述与「已安装」计数,finally 里按名称兜底删除,失败半途也不会留脏数据。两条都挂 @mocked,E2E-META 契约与现有 J 系列一致,check-e2e-meta 对本批无新增报错。
可合


❌ 流水线 ab6e0c538bf54af482814e47c2962dff 执行失败。
可在 https://gitcode.com/openJiuwen/sciencediscovery/pull/156/check 查看完整构建日志。
在 PR 评论中发送 rerun 可重新触发 CI 流水线。
| 任务名称 | 子任务 | 状态 | 详情 |
|---|---|---|---|
| 代码检查 | SCA(开源合规) | FAILED | 点此跳转 |
| Anti-poison(防投毒) | PASSED | 点此跳转 | |
| CodeCheck(静态检查) | PASSED | 点此跳转 | |
| Blacklist(禁用词) | PASSED | 点此跳转 | |
| UT | host tier | PASSED | 公开日志 |
| guest tier (QEMU TCG sandbox) | PASSED | 公开日志 | |
| ST | - | PASSED | 公开日志 |
| Binary | x86_64 debug package | PASSED | 公开日志 |
| aarch64 debug package | PASSED | 公开日志 |


rerun


The pipeline(pipeline number:156) is running. Please wait a moment...


❌ 流水线 15ec6f0586e04feca5cd38ee56c6d863 执行失败。
可在 https://gitcode.com/openJiuwen/sciencediscovery/pull/156/check 查看完整构建日志。
在 PR 评论中发送 rerun 可重新触发 CI 流水线。
| 任务名称 | 子任务 | 状态 | 详情 |
|---|---|---|---|
| 代码检查 | SCA(开源合规) | FAILED | 点此跳转 |
| Anti-poison(防投毒) | PASSED | 点此跳转 | |
| CodeCheck(静态检查) | PASSED | 点此跳转 | |
| Blacklist(禁用词) | PASSED | 点此跳转 | |
| UT | host tier | PASSED | 公开日志 |
| guest tier (QEMU TCG sandbox) | PASSED | 公开日志 | |
| ST | - | PASSED | 公开日志 |
| Binary | x86_64 debug package | PASSED | 公开日志 |
| aarch64 debug package | PASSED | 公开日志 |


The pipeline(pipeline number:156) is running. Please wait a moment...


AI 自动检视,仅供参考,问题都需要人工评估分析.
本轮相对上一轮 HEAD(ee729ef7 → 9dbbc227)主要是把独立 ZIP 校验从自写的 Node 解析器换成了 CPython zipfile:删掉 test/helpers/zip-archive.ts,在 J10 旅程里用 execFileSync("python3", ["-c", ...]) 读下载下来的 artifacts.zip。J11 Skill 文件夹导入旅程本轮无改动。相对最新 origin/main 仍是 ahead=1、behind=0,落点只剩两个规格文件。
做法上仍然坚持「解压侧与打包侧不共用同一套库」——浏览器/前端打的包用系统 Python 打开,比维护一份手写 central-directory 解析更省、也更贴近真实用户用外部工具开包的场景。E2E 环境本身就依赖本机 python3(同旅程里 stub 的 run_shell 也在用),所以这条路径和现有假设一致。
结论:可合


✅ 流水线 a6d41cd939a24c1f9a0207745bf04b6f 执行成功。
可在 https://gitcode.com/openJiuwen/sciencediscovery/pull/156/check 查看完整构建日志。
在 PR 评论中发送 rerun 可重新触发 CI 流水线。
| 任务名称 | 子任务 | 状态 | 详情 |
|---|---|---|---|
| 代码检查 | SCA(开源合规) | PASSED | 点此跳转 |
| Anti-poison(防投毒) | PASSED | 点此跳转 | |
| CodeCheck(静态检查) | PASSED | 点此跳转 | |
| Blacklist(禁用词) | PASSED | 点此跳转 | |
| UT | host tier | PASSED | 公开日志 |
| guest tier (QEMU TCG sandbox) | PASSED | 公开日志 | |
| ST | - | PASSED | 公开日志 |
| Binary | x86_64 debug package | PASSED | binary · SHA256SUMS |
| aarch64 debug package | PASSED | binary · SHA256SUMS |


补充说明一次强制推送的原因,便于评审对照历史。
第一版(ee729ef7)里,J10 用仓内自写的 test/helpers/zip-archive.ts 解压下载到的 artifacts.zip——一个走 ZIP 中央目录、用 Node 自带 inflateRawSync 解压的最小读取器。流水线上SCA(开源合规)连续两次 FAILED(两次是不同的扫描任务 id,其余七项全部 PASSED),扫描详情在 openlibing 上、当前账号打不开。这个 PR 不改任何依赖,唯一可能被开源合规扫到的就是那段自写的 ZIP 解析代码——它和公开实现长得像,很容易被判为片段匹配。
现在(9dbbc227)改为用 CPython 自带的 zipfile 解包:同样不依赖被测的 fflate(这正是这条断言的意义),旅程本来就要用 python3 跑分析那一步,机器上必然有;仓内少了 90 行自写解析器。改完后 SCA 恢复 PASSED,流水线八项全绿。
改动只涉及 J10 读取压缩包的方式,断言本身没有放松:仍然核对两个条目的路径与逐字节内容。改后在同一套隔离栈上重跑了这两条旅程,2 passed。


/merge


🔄 收到 @wang_cheng_zhao 的 /merge,正在校验并把 PR !156 以 rebase 方式合入 main…


✅ 自动合并完成(由 @wang_cheng_zhao 的 /merge 评论触发)。
PR !156 已以 rebase 方式合并到 main:c23f5da..7d02755(1 个提交)
GitCode 的审批规则尚未满足(审批人:hong-ye-zhou,yi-zhang95),已按 CODEOWNERS 授权强制合入。


What type of PR is this?
/kind task
What does this PR do / why do we need it:
补上两条浏览器旅程。Web 端有两处 ZIP 路径此前只有库级单测,没有任何旅程覆盖用户真正拿到的结果——这个缺口是 #152(依赖 CVE 升级)在描述里记下的,那次升级恰好动了负责打包解包的 fflate:
apps/web/src/artifact-download.ts的createArtifactArchive在浏览器里把选中的产物压成artifacts.zip。apps/web/src/skill-folder-import.ts在浏览器里把整个本地目录压成 zip 上传,服务端packages/specialist/src/skills.ts再流式解开它。原有的库级单测(
apps/web/tests/artifactDownload.test.tsx、apps/web/tests/skill-folder-import.test.tsx、services/api/src/skills.test.ts)保持不动,本 PR 只增加旅程。J10 多份交付物可以打包成一个压缩包带走(
test/journey-artifact-archive.spec.ts)一轮分析在两个不同目录下产出并声明两份交付物,用户进入多选、勾选两份、点「下载所选」。断言落在用户真正拿到的东西上:下载文件名是
artifacts.zip,解开后条目正是results/summary.md与tables/measurements.csv,内容逐字节等于产物本身,界面给出「产物压缩包已下载」。顺带覆盖了一个容易回归的细节:没勾选任何产物时下载按钮是禁用的,勾选后才可用并显示已选数量。解包用的是 CPython 自带的
zipfile(旅程里那一步分析本来就用 python3 跑,机器上必然有)。刻意不复用 fflate 来验证 fflate 写出的文件:那样即使双方约定出一个别的解压工具都打不开的格式,测试也会通过,而用户是拿自己的解压工具打开它的。J11 本地 Skill 文件夹可以直接导入(
test/journey-skill-folder-import.spec.ts)旅程当场在运行输出目录写两个目录:一个只有
readme.md,一个是合规 Skill 目录(SKILL.md+reference/notes.md)。用户先拿错目录:产品拒绝并直接说明原因(提示里点名缺少
SKILL.md),列表没有多出任何东西。再选正确目录:Skill 资源浏览器打开这个 Skill,文件树里展开reference能看到notes.md,点开后编辑器里的内容与本地文件逐字相同——整个目录(而不只是SKILL.md)经过「浏览器打包 → 上传 → 服务端解开」后完整落地。关掉浏览器后,Skill 卡片出现在列表里并带着SKILL.md里的描述,已安装计数加一。旅程结束时按名字反查并删除自己导入的 Skill,失败时也会删。一处顺带发现的问题(本 PR 未改)
skill-folder-import.ts里的几条失败提示是硬编码英文,没有进消息目录,所以中文界面下用户看到的是The selected folder must contain SKILL.md at its root.(截图见 J11 第 2 步的旅程报告)。这与 #149 清理中文界面残留英文的方向一致,建议单独提一个 PR 把这几条文案迁进i18n/messages.ts。J11 的断言只检查提示里是否点名SKILL.md,不绑定语言,所以那次改动不需要动这条旅程。Which issue(s) this PR fixes:
本 PR 只补测试覆盖,不对应 Issue;它跟进的是 #152 在描述里记录的覆盖缺口。
Test Plan and Test result:What scenarios were tested, and what were the verification results(Function, performance, reliability, etc.):
验证提交:
9dbbc227(2 files,+325,基于当前origin/maine79fd693)。三层 CI 全部在本机跑过,E2E 用 4472/4473(evolve 4475、memory-graph 17974)以避开本机常驻实例与其他并行任务的端口。pnpm ci:ut:passed — 静态检查(architecture:check17 passed + 275 个包源文件边界检查、typecheck全仓通过、ci:selftest53 passed、binary:test40 passed / 1 skipped);Node 包测试 2326 passed / 0 failed / 2 skipped(本 PR 点名不动的三份库级单测都在里面:apps/web790 passed 含 artifactDownload 与 skill-folder-import,services/api903 passed / 1 skipped 含 skills 的 ZIP 导入用例);runner 245 passed / 5 skipped;Python paper 2 / gateway 26 / memory-graph 94 passed + 77 skipped / evolve 316 passed。pnpm ci:st:passed —Agent loop smoke PASS。pnpm ci:e2e(mocked 浏览器子集):passed — discovered 43 / passed 41 / failed 0 / skipped 2。43 = 原有 41 + 本 PR 新增 2 条。两条 skipped 与本 PR 无关:journey-prepare-environmentJ3 被该层自身的SCIENTIFIC_ENVS=0判为 BLOCKED;literature-review的「Connector 未启用时的失败反馈」在仓库里就是test.fixme。用户视角 E2E 结论:PASS(浏览器 / Playwright mocked 组)
journey-artifact-archiveresults/summary.md与tables/measurements.csv,多选两份后下载artifacts.zip;解开后条目为这两条路径、内容逐字节一致;界面提示「产物压缩包已下载」journey-skill-folder-importreadme.md的目录,再选合规 Skill 目录SKILL.md,列表不变;后者导入后 Skill 浏览器打开,文件树含SKILL.md与reference/notes.md,打开后者内容与本地文件一致;关掉浏览器后卡片在列表中出现、已安装计数加一两条旅程的
report.md/report.html与逐步骤截图由 journey 固定装置自动产出(journey-reports/,未提交)。上面三层的完整数字取自
ee729ef7。此后只改了 J10 读取压缩包的方式(从仓内自写的 ZIP 读取器换成 CPythonzipfile),得到当前的9dbbc227:ci:ut/ci:st不执行 Playwright 规格,结论不受影响;E2E 侧在同一套隔离栈上重跑了改动涉及的两条旅程,2 passed。一次需要说明的失败与归因:在一个复用过的数据目录上先跑了一次,
journey-compact-process:304、journey-first-runJ1、journey-model-settingsJ6 三条失败,报错分别是Run this journey with fresh isolated Skill library data、找不到「创建项目」空态、找不到「还没有服务商」空态——都是这三条旅程自己要求的干净前置被历史数据破坏,与本 PR 新增的两条旅程无关(它们各自在finally里删掉自己创建的项目/模型/Skill)。把该层的数据目录清空后重跑,43 条中 41 passed / 0 failed / 2 skipped,上面的数字取自这一次。Self-checklist:(请自检,在[ ]内打上x,我们将检视你的完成情况,否则会导致pr无法合入)