package-workshop.mjs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import {execFileSync} from 'node:child_process';
  5. const output=process.argv[2];if(!output||!path.isAbsolute(output))throw Error('请指定交付目录的绝对路径');
  6. const sha=x=>crypto.createHash('sha256').update(x).digest('hex');
  7. const destination=path.resolve(output);fs.mkdirSync(destination,{recursive:true});
  8. const staging=fs.mkdtempSync(path.resolve('work/package-'));
  9. const python=process.env.KSF_PACKAGE_PYTHON||'C:/Users/JSHY/AppData/Local/Python/pythoncore-3.14-64/python.exe';
  10. const results=[];
  11. function write(root,name,content){const file=path.join(root,name);fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,content);}
  12. function copy(root,name){write(root,name,fs.readFileSync(name));}
  13. function pack(name,prepare){
  14. const root=path.join(staging,name);fs.mkdirSync(root);prepare(root);
  15. const files=[];function walk(dir){for(const e of fs.readdirSync(dir,{withFileTypes:true})){const p=path.join(dir,e.name);if(e.isDirectory())walk(p);else files.push(path.relative(root,p).replaceAll('\\','/'));}}walk(root);
  16. files.sort();write(root,'FILES.sha256',files.map(n=>sha(fs.readFileSync(path.join(root,n)))+' '+n).join('\n')+'\n');
  17. const zip=path.join(destination,name+'.zip');
  18. execFileSync(python,['-c','import sys,zipfile,pathlib; root=pathlib.Path(sys.argv[1]); z=zipfile.ZipFile(sys.argv[2],"w",zipfile.ZIP_DEFLATED); [(z.write(p,p.relative_to(root).as_posix())) for p in sorted(root.rglob("*")) if p.is_file()]; z.close(); z=zipfile.ZipFile(sys.argv[2]); assert z.testzip() is None',root,zip],{windowsHide:true});
  19. const bytes=fs.readFileSync(zip);results.push({name,file:zip,sizeBytes:bytes.length,sha256:sha(bytes),manifestEntries:files.length});
  20. }
  21. pack('ksf-workshop-skill-20261101',root=>{
  22. const source='skills/ksf-workshop-runtime';
  23. for(const line of fs.readFileSync(source+'/FILES.sha256','utf8').trim().split('\n'))copy(root,source+'/'+line.slice(66));
  24. copy(root,source+'/FILES.sha256');copy(root,'scripts/install-workshop-skill.mjs');
  25. write(root,'checks/read-only-project-search.json',JSON.stringify({className:'KfProject',filters:{dataMode:'sample'},limit:20},null,2)+'\n');
  26. write(root,'INSTALL.txt',`康师傅工作坊 Skill 升级 · 2026-11-01
  27. 此包仅更新数字人的工作坊执行入口,不包含凭据、私钥、credential-binding.json 或真实研究素材。
  28. 保留已有加密绑定、原 ksf.mjs、runtime-lock、冻结 r7。无需改网关、配置或重启。
  29. 更新 SKILL.md 和加密路由,新增工作坊脚本与任务格式;安装器会备份旧文件,发现未知修改先停,不覆盖。
  30. 1. 先用随包执行单的大小及 SHA-256 校验 ZIP,不符即停,不用旧包代替。
  31. 2. 解压到 /opt/data/ksf-workshop-skill-20261101/(若已存在另建干净目录,不覆盖),cd 到包根。
  32. 3. sha256sum -c FILES.sha256。顶层应 11/11 OK;技能子清单应 7/7。
  33. 4. 阅读 scripts/install-workshop-skill.mjs,执行:
  34. node scripts/install-workshop-skill.mjs
  35. 预期 ok=true,credentialBindingPreserved=true,legacyEntryPreserved=true,gatewayModified=false。
  36. 当前已知基线首次更新四项;已安装同版本则 changed=[],不重复备份。
  37. 5. skills_list 发现,再 skillview 重新读取 ksf-workshop-runtime。文件安装不代表发现或企微调用完成。
  38. 6. cd /opt/data/skills/ksf-workshop-runtime,使用解压包内技能子 FILES.sha256 验证实际安装七文件。
  39. 7. 从新的企微消息按已加载技能运行只读核验:
  40. node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs status --request-id KSF-工作坊升级-20261101-I
  41. node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-search --input /opt/data/ksf-workshop-skill-20261101/checks/read-only-project-search.json --request-id KSF-工作坊只读-20261101-J
  42. 若实际解压目录不同,只改上面 input 目录。两条各自报原始 JSON、两层 invocationId、退出码;下划线原样保留,不输出凭据。
  43. search 应由新 ksf-workshop.mjs 执行、readOnly=true;查询存在的 sample 项目不是新增样例。
  44. 8. 安装和只读核验停在这里。后续另发真实工作坊任务,取真实发送者与当前 Agent,登记会话、保存一份阶段成果,并回读对应编号。
  45. 不把“王刚001(联调模拟)”当实际 Agent;新包不会自动重演管理机的技术样例。
  46. 运行约束:保持 HERMES_WRITE_SAFE_ROOT;只在 /opt/data 下准备任务。无稳定身份时按技能做课堂登记,不伪造账号。
  47. 公开看板不需登录;课堂成果是 workshop,正式报告确认仍保持独立流程。sample 不能升级成真实资料。
  48. 令牌原到期日为北京时间 2026-11-06 18:42,本次没有续期;过期则停止并由管理方更新信封。
  49. `);
  50. });
  51. pack('ksf-board-data-20261101',root=>{
  52. const files=['src/app.config.js','src/services/board-service.js','src/services/workshop-data.js','src/core/kfs/build.js','src/core/library/reports.js','src/core/opportunity/opportunity.js','src/core/taxonomy/taxonomy.js','src/pages/view-members.js','lib/vendor/signal-polyfill/v0.2.2/dist/index.js'];
  53. for(const file of files)copy(root,file);
  54. write(root,'PUBLISH.txt',`康师傅看板数据接入发布 · 2026-11-01
  55. 由王刚001或平台后台按现有站点流程发布,本包尚未推送 Git 或部署前端。
  56. 基于仓库提交 7d01b5cf2249decb63cfbde95e26dfedad86290e。共九个运行文件,无凭据。
  57. 保留公开访问,不增加登录。未修改 CSS、设计令牌、组件样式或布局。
  58. 成员页沿用原表格新增逐项目进展,显示负责人、阶段、成果、方案保存情况与卡住/课堂完成。
  59. 发布前 sha256sum -c FILES.sha256。先备份对应旧文件;如线上已继续更新,先合并差异,不能覆盖其他人的改动。
  60. 按包内相对路径合并到站点源码并发布这九文件,不要直接上传包根说明和清单为站点首页。
  61. 新增 workshop-data.js;务必带上 signal-polyfill 的 dist/index.js(旧仓库 dist 忽略规则曾漏收此运行依赖)。
  62. Git 发布时保留仓库 .gitignore 对该 index.js 的例外,不能只提交文件夹。
  63. index.html 不变;保留现有 base、importmap 和路由回退。更新后按现有发布渠道刷新文件缓存。
  64. 服务端八个工作坊云函数已经上线,不需再次部署、不改 Schema/表权限、不往前端添加服务令牌。
  65. 验收:匿名浏览器打开 /、/members、/pool、/stages、/report,均能读取。
  66. 在 /members 找“工作坊联调(非学员)”、项目“【样例】拌面新品链路技术联调”,应是 S5 / 课堂完成。
  67. 报告 /report/5u4xzJW5DY 应显示样例、Agent“王刚001(联调模拟)”以及未正式确认说明。
  68. 机会 /pool/7TnUg76JOI 与项目 Z-IEjZg0qZ 的关联应可回读。
  69. 浏览器 Network 应看到匿名 POST /api/functions path=/ksfGetPublicBoard,不能发送服务令牌。
  70. 核桌面与390px手机、刷新与深链接;现有评分/决策沿用旧行为。
  71. 以上是管理机明确 sample 的技术联调,不是真实员工研究,也不是企业审批或正式报告发布。
  72. 前端发布和 Hermes 安装后,还需新的企微消息实测员工→当前Agent→阶段成果→看板,才算现场通道完整验收。
  73. `);
  74. });
  75. const receipt={at:new Date().toISOString(),packages:results,containsCredentials:false,hermesInstalled:false,frontendPublished:false};
  76. fs.writeFileSync(path.join(destination,'ksf-workshop-delivery-20261101.json'),JSON.stringify(receipt,null,2));
  77. console.log(JSON.stringify(receipt,null,2));