@runweave/server-linux-x64 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/manifest.json +1482 -151
  2. package/native.tar.gz +0 -0
  3. package/package.json +1 -1
  4. package/payload/.agents/plugins/marketplace.json +20 -0
  5. package/payload/app-server/build-info.json +3 -3
  6. package/payload/app-server/index.cjs +1 -1
  7. package/payload/backend/build-info.json +4 -4
  8. package/payload/backend/index.cjs +45 -9
  9. package/payload/backend/ios-simulators/pool.py +3 -3
  10. package/payload/frontend/dist/assets/{_baseUniq-vo4uuhMd.js → _baseUniq-qHFga5V8.js} +1 -1
  11. package/payload/frontend/dist/assets/{access-_jCu7Rgu.js → access-DgHvmn15.js} +1 -1
  12. package/payload/frontend/dist/assets/{advanced-CqOhObYj.js → advanced-C1Fh14vw.js} +1 -1
  13. package/payload/frontend/dist/assets/{appearance-CrrD5gzC.js → appearance-Bs7cSXqO.js} +1 -1
  14. package/payload/frontend/dist/assets/{arc-dbfyHSF0.js → arc-E3iYHQG_.js} +1 -1
  15. package/payload/frontend/dist/assets/{architectureDiagram-Q4EWVU46-CntSCAsO.js → architectureDiagram-Q4EWVU46-DgpYpTb9.js} +1 -1
  16. package/payload/frontend/dist/assets/{blockDiagram-DXYQGD6D-Chp0D4qy.js → blockDiagram-DXYQGD6D-CfFOEKZ7.js} +1 -1
  17. package/payload/frontend/dist/assets/{c4Diagram-AHTNJAMY-CF91pOuW.js → c4Diagram-AHTNJAMY-BxlHBUj3.js} +1 -1
  18. package/payload/frontend/dist/assets/channel-amxyNKnk.js +1 -0
  19. package/payload/frontend/dist/assets/{chunk-4BX2VUAB-B1gxlVu2.js → chunk-4BX2VUAB-xWocgDo8.js} +1 -1
  20. package/payload/frontend/dist/assets/{chunk-4TB4RGXK-Cxt3mj9b.js → chunk-4TB4RGXK-Bl1cNICf.js} +1 -1
  21. package/payload/frontend/dist/assets/{chunk-55IACEB6-BJT6qNik.js → chunk-55IACEB6-DVtE836u.js} +1 -1
  22. package/payload/frontend/dist/assets/{chunk-EDXVE4YY-yI59bCa0.js → chunk-EDXVE4YY-Dbd3tDCr.js} +1 -1
  23. package/payload/frontend/dist/assets/{chunk-FMBD7UC4-CDvncKKJ.js → chunk-FMBD7UC4-MPJaTsyN.js} +1 -1
  24. package/payload/frontend/dist/assets/{chunk-OYMX7WX6-BblOIU57.js → chunk-OYMX7WX6-D3LuudlW.js} +1 -1
  25. package/payload/frontend/dist/assets/{chunk-QZHKN3VN-rODzNdO0.js → chunk-QZHKN3VN-Dn1aF6iQ.js} +1 -1
  26. package/payload/frontend/dist/assets/{chunk-YZCP3GAM-B6D4wQwk.js → chunk-YZCP3GAM-jZYbHnCw.js} +1 -1
  27. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-B8cTijjT.js +1 -0
  28. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-B8cTijjT.js +1 -0
  29. package/payload/frontend/dist/assets/clone-xzI2eGRI.js +1 -0
  30. package/payload/frontend/dist/assets/connection-V2iI-6rM.js +12 -0
  31. package/payload/frontend/dist/assets/{connections-DqDmxS-O.js → connections-G_Q7342g.js} +1 -1
  32. package/payload/frontend/dist/assets/{cose-bilkent-S5V4N54A-BvwVWp29.js → cose-bilkent-S5V4N54A-75FLD3RS.js} +1 -1
  33. package/payload/frontend/dist/assets/{dagre-KV5264BT-h_K1TH11.js → dagre-KV5264BT-DEm-DpJL.js} +1 -1
  34. package/payload/frontend/dist/assets/{desktop-BQ3kGtKG.js → desktop-g5JC-CQd.js} +1 -1
  35. package/payload/frontend/dist/assets/{diagram-5BDNPKRD-B8g2SV6L.js → diagram-5BDNPKRD-BV6P2UTy.js} +1 -1
  36. package/payload/frontend/dist/assets/{diagram-G4DWMVQ6-BOEnrqYm.js → diagram-G4DWMVQ6-DuCK-MM2.js} +1 -1
  37. package/payload/frontend/dist/assets/{diagram-MMDJMWI5-DlaN_z71.js → diagram-MMDJMWI5-D5_GlPgO.js} +1 -1
  38. package/payload/frontend/dist/assets/{diagram-TYMM5635-C_pSKftn.js → diagram-TYMM5635-QRbOYR1P.js} +1 -1
  39. package/payload/frontend/dist/assets/{erDiagram-SMLLAGMA-C2dL-h3I.js → erDiagram-SMLLAGMA-DefCheAw.js} +1 -1
  40. package/payload/frontend/dist/assets/{flowDiagram-DWJPFMVM-BmXhKWFj.js → flowDiagram-DWJPFMVM-DVzyWIso.js} +1 -1
  41. package/payload/frontend/dist/assets/{ganttDiagram-T4ZO3ILL-D_Odnssf.js → ganttDiagram-T4ZO3ILL-BJaClUEF.js} +1 -1
  42. package/payload/frontend/dist/assets/{gitGraphDiagram-UUTBAWPF-DcQdcwKZ.js → gitGraphDiagram-UUTBAWPF-BLfbApNz.js} +1 -1
  43. package/payload/frontend/dist/assets/{graph-C_XsxefA.js → graph-C-YqFh68.js} +1 -1
  44. package/payload/frontend/dist/assets/{image-D3hgVr8O.js → image-CZo_2Awc.js} +1 -1
  45. package/payload/frontend/dist/assets/{index-CEUsFFQq.js → index-6q2aeqkt.js} +95 -105
  46. package/payload/frontend/dist/assets/{infoDiagram-42DDH7IO-C6TnRyMi.js → infoDiagram-42DDH7IO-BakVOutT.js} +1 -1
  47. package/payload/frontend/dist/assets/{ishikawaDiagram-UXIWVN3A-BL7rfALJ.js → ishikawaDiagram-UXIWVN3A-D_1Oa5Dc.js} +1 -1
  48. package/payload/frontend/dist/assets/{journeyDiagram-VCZTEJTY-C_6407GW.js → journeyDiagram-VCZTEJTY-Y8TVFv6B.js} +1 -1
  49. package/payload/frontend/dist/assets/{kanban-definition-6JOO6SKY-CK61Fd0p.js → kanban-definition-6JOO6SKY-DxeNhTIB.js} +1 -1
  50. package/payload/frontend/dist/assets/{layout-DVn6JKbl.js → layout-5cMeOTAp.js} +1 -1
  51. package/payload/frontend/dist/assets/{linear-DyWskq8H.js → linear-Bnc3ev5g.js} +1 -1
  52. package/payload/frontend/dist/assets/main-3TPtEl66.js +706 -0
  53. package/payload/frontend/dist/assets/main-D4_uoZQa.css +33 -0
  54. package/payload/frontend/dist/assets/{min-apbLpSJ3.js → min-BRXKkrS8.js} +1 -1
  55. package/payload/frontend/dist/assets/{mindmap-definition-QFDTVHPH-MVHZZbma.js → mindmap-definition-QFDTVHPH-RYZO_BEr.js} +1 -1
  56. package/payload/frontend/dist/assets/{monitoring-BApsVez_.js → monitoring-Fv7kZ4s6.js} +1 -1
  57. package/payload/frontend/dist/assets/{notifications-CjxSNLU1.js → notifications-BzrYqyMH.js} +1 -1
  58. package/payload/frontend/dist/assets/{pieDiagram-DEJITSTG-Dhbb0AYU.js → pieDiagram-DEJITSTG-Bzw0dhjz.js} +1 -1
  59. package/payload/frontend/dist/assets/{quadrantDiagram-34T5L4WZ-Yk6LOF0S.js → quadrantDiagram-34T5L4WZ-CQJY_msa.js} +1 -1
  60. package/payload/frontend/dist/assets/{record-content-BCJ80KS5.js → record-content-B7-6olQt.js} +1 -1
  61. package/payload/frontend/dist/assets/{requirementDiagram-MS252O5E-BUhS0PEq.js → requirementDiagram-MS252O5E-DuP8hAV8.js} +1 -1
  62. package/payload/frontend/dist/assets/{sankeyDiagram-XADWPNL6-D2sdxz14.js → sankeyDiagram-XADWPNL6-D-fQnCZZ.js} +1 -1
  63. package/payload/frontend/dist/assets/{sequenceDiagram-FGHM5R23-CRere4Fy.js → sequenceDiagram-FGHM5R23-DaZNEccV.js} +1 -1
  64. package/payload/frontend/dist/assets/{stateDiagram-FHFEXIEX-_pk-OMlm.js → stateDiagram-FHFEXIEX-CkOLt5R1.js} +1 -1
  65. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-atg1K4nB.js +1 -0
  66. package/payload/frontend/dist/assets/{status-Dk5rRVQF.js → status-CIIPajZp.js} +1 -1
  67. package/payload/frontend/dist/assets/suiji-C453W-Sg.js +1 -0
  68. package/payload/frontend/dist/assets/{svg-X7YkMMvx.js → svg-BUqAWGtg.js} +1 -1
  69. package/payload/frontend/dist/assets/{tabs-BmKbcPVh.js → tabs-Cwlp1Meu.js} +1 -1
  70. package/payload/frontend/dist/assets/{timeline-definition-GMOUNBTQ-BPsI2pMC.js → timeline-definition-GMOUNBTQ-BOKDMwHS.js} +1 -1
  71. package/payload/frontend/dist/assets/{vennDiagram-DHZGUBPP-BNJkZ1Az.js → vennDiagram-DHZGUBPP-fSZjDS41.js} +1 -1
  72. package/payload/frontend/dist/assets/{wardley-RL74JXVD-CVcYeATS.js → wardley-RL74JXVD-D1KIsvoD.js} +1 -1
  73. package/payload/frontend/dist/assets/{wardleyDiagram-NUSXRM2D-BpLJ0alk.js → wardleyDiagram-NUSXRM2D-FMe67dwE.js} +1 -1
  74. package/payload/frontend/dist/assets/{xychartDiagram-5P7HB3ND-BdsYYk4M.js → xychartDiagram-5P7HB3ND-B9AM9_9w.js} +1 -1
  75. package/payload/frontend/dist/build-info.json +4 -4
  76. package/payload/frontend/dist/companion.html +1 -1
  77. package/payload/frontend/dist/index.html +3 -3
  78. package/payload/packages/agent-bridge/src/install-pi.mjs +81 -0
  79. package/payload/plugins/pi/dist/SKILL.md +22 -0
  80. package/payload/plugins/pi/dist/bridge/app-server-client.cjs +82 -0
  81. package/payload/plugins/pi/dist/bridge/feishu_stop_notify.sh +169 -0
  82. package/payload/plugins/pi/dist/bridge/runweave-hook-bridge.cjs +583 -0
  83. package/payload/plugins/pi/dist/bridge/runweave-hook-completion.cjs +42 -0
  84. package/payload/plugins/pi/dist/bridge/runweave-hook-payload.cjs +578 -0
  85. package/payload/plugins/pi/dist/bridge/runweave-task-supervision.cjs +167 -0
  86. package/payload/plugins/pi/dist/runweave.js +316 -0
  87. package/payload/plugins/toolkit/.codex-plugin/plugin.json +31 -0
  88. package/payload/plugins/toolkit/README.md +185 -0
  89. package/payload/plugins/toolkit/hooks/AGENTS.md +6 -0
  90. package/payload/plugins/toolkit/hooks/app-server-client.cjs +82 -0
  91. package/payload/plugins/toolkit/hooks/feishu_stop_notify.sh +169 -0
  92. package/payload/plugins/toolkit/hooks/runweave-hook-bridge.cjs +583 -0
  93. package/payload/plugins/toolkit/hooks/runweave-hook-completion.cjs +42 -0
  94. package/payload/plugins/toolkit/hooks/runweave-hook-dispatch.cjs +123 -0
  95. package/payload/plugins/toolkit/hooks/runweave-hook-payload.cjs +578 -0
  96. package/payload/plugins/toolkit/hooks/runweave-task-supervision.cjs +167 -0
  97. package/payload/plugins/toolkit/hooks.json +108 -0
  98. package/payload/plugins/toolkit/skills/acceptance/SKILL.md +70 -0
  99. package/payload/plugins/toolkit/skills/acceptance/agents/openai.yaml +4 -0
  100. package/payload/plugins/toolkit/skills/acceptance/references/capture.md +38 -0
  101. package/payload/plugins/toolkit/skills/acceptance/references/delivery.md +54 -0
  102. package/payload/plugins/toolkit/skills/acceptance/references/gallery-contract.md +84 -0
  103. package/payload/plugins/toolkit/skills/acceptance/references/gallery-copy.md +34 -0
  104. package/payload/plugins/toolkit/skills/acceptance/references/video.md +52 -0
  105. package/payload/plugins/toolkit/skills/acceptance/scripts/capture.py +137 -0
  106. package/payload/plugins/toolkit/skills/acceptance/scripts/common.py +63 -0
  107. package/payload/plugins/toolkit/skills/acceptance/scripts/gallery.py +353 -0
  108. package/payload/plugins/toolkit/skills/acceptance/scripts/preview.py +109 -0
  109. package/payload/plugins/toolkit/skills/acceptance/scripts/video.py +283 -0
  110. package/payload/plugins/toolkit/skills/acceptance/templates/reader.css +576 -0
  111. package/payload/plugins/toolkit/skills/acceptance/templates/reader.html +63 -0
  112. package/payload/plugins/toolkit/skills/acceptance/templates/reader.js +326 -0
  113. package/payload/plugins/toolkit/skills/acceptance/templates/viewer.css +55 -0
  114. package/payload/plugins/toolkit/skills/acceptance-gallery/SKILL.md +8 -0
  115. package/payload/plugins/toolkit/skills/acceptance-gallery/agents/openai.yaml +6 -0
  116. package/payload/plugins/toolkit/skills/acceptance-gallery/references/capture-and-copy.md +3 -0
  117. package/payload/plugins/toolkit/skills/acceptance-gallery/references/content-contract.md +3 -0
  118. package/payload/plugins/toolkit/skills/acceptance-gallery/scripts/gallery.py +9 -0
  119. package/payload/plugins/toolkit/skills/acceptance-video/SKILL.md +8 -0
  120. package/payload/plugins/toolkit/skills/acceptance-video/agents/openai.yaml +6 -0
  121. package/payload/plugins/toolkit/skills/acceptance-video/references/input-and-capture.md +3 -0
  122. package/payload/plugins/toolkit/skills/acceptance-video/references/mobile-review.md +3 -0
  123. package/payload/plugins/toolkit/skills/acceptance-video/scripts/capture.py +9 -0
  124. package/payload/plugins/toolkit/skills/acceptance-video/scripts/video.py +9 -0
  125. package/payload/plugins/toolkit/skills/agent-device/SKILL.md +86 -0
  126. package/payload/plugins/toolkit/skills/agent-device/agents/openai.yaml +4 -0
  127. package/payload/plugins/toolkit/skills/agent-device/references/ios-setup.md +36 -0
  128. package/payload/plugins/toolkit/skills/agent-device/scripts/device.py +398 -0
  129. package/payload/plugins/toolkit/skills/agent-device/scripts/resources.py +248 -0
  130. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/SKILL.md +128 -0
  131. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/agents/openai.yaml +4 -0
  132. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-checklist.md +59 -0
  133. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-guide.md +135 -0
  134. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-handoff-template.md +57 -0
  135. package/payload/plugins/toolkit/skills/ask-first/SKILL.md +81 -0
  136. package/payload/plugins/toolkit/skills/brainstorming/SKILL.md +41 -0
  137. package/payload/plugins/toolkit/skills/browse-record/SKILL.md +35 -0
  138. package/payload/plugins/toolkit/skills/browse-record/references/app-usage.md +19 -0
  139. package/payload/plugins/toolkit/skills/browse-record/references/daily-reporting.md +115 -0
  140. package/payload/plugins/toolkit/skills/browse-record/references/zhihu.md +33 -0
  141. package/payload/plugins/toolkit/skills/browse-record/scripts/app_usage.py +110 -0
  142. package/payload/plugins/toolkit/skills/browse-record/scripts/browse_job.py +385 -0
  143. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu-read.js +126 -0
  144. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu_http.py +273 -0
  145. package/payload/plugins/toolkit/skills/clear-technical-explanations/LICENSE +21 -0
  146. package/payload/plugins/toolkit/skills/clear-technical-explanations/SKILL.md +103 -0
  147. package/payload/plugins/toolkit/skills/code-grounded-requirements/SKILL.md +54 -0
  148. package/payload/plugins/toolkit/skills/daily-doc-maintenance/SKILL.md +151 -0
  149. package/payload/plugins/toolkit/skills/daily-record/SKILL.md +63 -0
  150. package/payload/plugins/toolkit/skills/daily-record/agents/openai.yaml +4 -0
  151. package/payload/plugins/toolkit/skills/daily-record/references/browser-history.md +6 -0
  152. package/payload/plugins/toolkit/skills/daily-record/references/feishu.md +90 -0
  153. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-reporting.md +144 -0
  154. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-task.template.json +15 -0
  155. package/payload/plugins/toolkit/skills/daily-record/references/suiji-work-records.md +57 -0
  156. package/payload/plugins/toolkit/skills/daily-record/scripts/feishu.py +280 -0
  157. package/payload/plugins/toolkit/skills/daily-record/scripts/job.py +416 -0
  158. package/payload/plugins/toolkit/skills/daily-record/scripts/job_browser.py +77 -0
  159. package/payload/plugins/toolkit/skills/daily-record/scripts/job_facts.py +132 -0
  160. package/payload/plugins/toolkit/skills/daily-record/scripts/job_isolation.py +69 -0
  161. package/payload/plugins/toolkit/skills/daily-record/scripts/job_materials.py +154 -0
  162. package/payload/plugins/toolkit/skills/daily-record/scripts/job_screen.py +77 -0
  163. package/payload/plugins/toolkit/skills/daily-record/scripts/job_state.py +309 -0
  164. package/payload/plugins/toolkit/skills/daily-refactor/SKILL.md +101 -0
  165. package/payload/plugins/toolkit/skills/daily-refactor/scripts/compare-architecture-reports.mjs +168 -0
  166. package/payload/plugins/toolkit/skills/debugging-strategies/SKILL.md +38 -0
  167. package/payload/plugins/toolkit/skills/debugging-strategies/resources/implementation-playbook.md +520 -0
  168. package/payload/plugins/toolkit/skills/diagnostic-log-debugging/SKILL.md +79 -0
  169. package/payload/plugins/toolkit/skills/doc-coauthoring/SKILL.md +64 -0
  170. package/payload/plugins/toolkit/skills/eli5/LICENSE +21 -0
  171. package/payload/plugins/toolkit/skills/eli5/SKILL.md +18 -0
  172. package/payload/plugins/toolkit/skills/experience/SKILL.md +71 -0
  173. package/payload/plugins/toolkit/skills/experience/package.json +13 -0
  174. package/payload/plugins/toolkit/skills/git-advanced-workflows/SKILL.md +457 -0
  175. package/payload/plugins/toolkit/skills/github-pr/SKILL.md +109 -0
  176. package/payload/plugins/toolkit/skills/github-pr/scripts/github-access.mjs +292 -0
  177. package/payload/plugins/toolkit/skills/github-pr/scripts/preflight.mjs +191 -0
  178. package/payload/plugins/toolkit/skills/github-pr/scripts/source-worktree.mjs +354 -0
  179. package/payload/plugins/toolkit/skills/grill-with-docs/SKILL.md +66 -0
  180. package/payload/plugins/toolkit/skills/grill-with-docs/agents/openai.yaml +6 -0
  181. package/payload/plugins/toolkit/skills/grilling/SKILL.md +14 -0
  182. package/payload/plugins/toolkit/skills/handoff/SKILL.md +98 -0
  183. package/payload/plugins/toolkit/skills/karpathy-guidelines/SKILL.md +73 -0
  184. package/payload/plugins/toolkit/skills/playwright-cli/SKILL.md +562 -0
  185. package/payload/plugins/toolkit/skills/playwright-cli/references/element-attributes.md +23 -0
  186. package/payload/plugins/toolkit/skills/playwright-cli/references/playwright-tests.md +39 -0
  187. package/payload/plugins/toolkit/skills/playwright-cli/references/request-mocking.md +87 -0
  188. package/payload/plugins/toolkit/skills/playwright-cli/references/running-code.md +240 -0
  189. package/payload/plugins/toolkit/skills/playwright-cli/references/session-management.md +226 -0
  190. package/payload/plugins/toolkit/skills/playwright-cli/references/spec-driven-testing.md +312 -0
  191. package/payload/plugins/toolkit/skills/playwright-cli/references/storage-state.md +275 -0
  192. package/payload/plugins/toolkit/skills/playwright-cli/references/test-generation.md +138 -0
  193. package/payload/plugins/toolkit/skills/playwright-cli/references/tracing.md +142 -0
  194. package/payload/plugins/toolkit/skills/playwright-cli/references/video-recording.md +157 -0
  195. package/payload/plugins/toolkit/skills/playwright-cli/templates/standalone.config.json +7 -0
  196. package/payload/plugins/toolkit/skills/product-exploration/SKILL.md +64 -0
  197. package/payload/plugins/toolkit/skills/product-exploration/agents/openai.yaml +5 -0
  198. package/payload/plugins/toolkit/skills/prototype-first-development/SKILL.md +134 -0
  199. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/README.md +88 -0
  200. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/app.js +145 -0
  201. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/index.html +229 -0
  202. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/mock-state.json +60 -0
  203. package/payload/plugins/toolkit/skills/prototype-first-development/references/implementation-plan-handoff-template.md +61 -0
  204. package/payload/plugins/toolkit/skills/prototype-first-development/references/prototype-workflow-checklist.md +64 -0
  205. package/payload/plugins/toolkit/skills/publish-runweave-server/SKILL.md +64 -0
  206. package/payload/plugins/toolkit/skills/publish-runweave-server/agents/openai.yaml +4 -0
  207. package/payload/plugins/toolkit/skills/react-best-practices/AGENTS.md +3227 -0
  208. package/payload/plugins/toolkit/skills/react-best-practices/README.md +127 -0
  209. package/payload/plugins/toolkit/skills/react-best-practices/SKILL.md +143 -0
  210. package/payload/plugins/toolkit/skills/react-best-practices/metadata.json +15 -0
  211. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/event-handler-refs.md +55 -0
  212. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/init-once.md +42 -0
  213. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/use-latest.md +39 -0
  214. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/api-routes.md +38 -0
  215. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/defer-await.md +80 -0
  216. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/dependencies.md +52 -0
  217. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/parallel.md +28 -0
  218. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/suspense-boundaries.md +99 -0
  219. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/barrel-imports.md +59 -0
  220. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/conditional.md +37 -0
  221. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/defer-third-party.md +49 -0
  222. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/dynamic-imports.md +35 -0
  223. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/preload.md +46 -0
  224. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/event-listeners.md +78 -0
  225. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/localstorage-schema.md +77 -0
  226. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/passive-event-listeners.md +48 -0
  227. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/swr-dedup.md +56 -0
  228. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/batch-dom-css.md +110 -0
  229. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-function-results.md +80 -0
  230. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-property-access.md +28 -0
  231. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-storage.md +70 -0
  232. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/combine-iterations.md +32 -0
  233. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/early-exit.md +50 -0
  234. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/flatmap-filter.md +55 -0
  235. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/hoist-regexp.md +45 -0
  236. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/index-maps.md +37 -0
  237. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/length-check-first.md +50 -0
  238. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/min-max-loop.md +82 -0
  239. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/set-map-lookups.md +24 -0
  240. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/tosorted-immutable.md +57 -0
  241. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/sections.md +46 -0
  242. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/template.md +28 -0
  243. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/activity.md +26 -0
  244. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/animate-svg-wrapper.md +38 -0
  245. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/conditional-render.md +32 -0
  246. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/content-visibility.md +38 -0
  247. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hoist-jsx.md +36 -0
  248. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-no-flicker.md +72 -0
  249. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-suppress-warning.md +26 -0
  250. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/resource-hints.md +89 -0
  251. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/script-defer-async.md +71 -0
  252. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/svg-precision.md +28 -0
  253. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/usetransition-loading.md +75 -0
  254. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/defer-reads.md +39 -0
  255. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/dependencies.md +45 -0
  256. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state-no-effect.md +40 -0
  257. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state.md +29 -0
  258. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/functional-setstate.md +77 -0
  259. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/lazy-state-init.md +58 -0
  260. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo-with-default-value.md +36 -0
  261. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo.md +44 -0
  262. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/move-effect-to-event.md +45 -0
  263. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/no-inline-components.md +83 -0
  264. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/simple-expression-in-memo.md +35 -0
  265. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/transitions.md +40 -0
  266. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/use-ref-transient-values.md +73 -0
  267. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/after-nonblocking.md +74 -0
  268. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/auth-actions.md +96 -0
  269. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-lru.md +41 -0
  270. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-react.md +76 -0
  271. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/dedup-props.md +65 -0
  272. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/hoist-static-io.md +139 -0
  273. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/parallel-fetching.md +83 -0
  274. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/serialization.md +38 -0
  275. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/SKILL.md +8 -0
  276. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/agents/openai.yaml +6 -0
  277. package/payload/plugins/toolkit/skills/reproduce-before-fix/SKILL.md +93 -0
  278. package/payload/plugins/toolkit/skills/reproduce-before-fix/agents/openai.yaml +7 -0
  279. package/payload/plugins/toolkit/skills/review-only/SKILL.md +72 -0
  280. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/SKILL.md +134 -0
  281. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/agents/openai.yaml +7 -0
  282. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/references/contracts.md +114 -0
  283. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/classify-runtime-verdict.mjs +140 -0
  284. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/run-static-review.mjs +389 -0
  285. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/static-review-contract.mjs +243 -0
  286. package/payload/plugins/toolkit/skills/run-test-cases/SKILL.md +63 -0
  287. package/payload/plugins/toolkit/skills/runweave-change-validation/SKILL.md +196 -0
  288. package/payload/plugins/toolkit/skills/runweave-change-validation/agents/openai.yaml +7 -0
  289. package/payload/plugins/toolkit/skills/runweave-change-validation/scripts/assert-dev-session-status.mjs +181 -0
  290. package/payload/plugins/toolkit/skills/runweave-dev-session/SKILL.md +126 -0
  291. package/payload/plugins/toolkit/skills/runweave-dev-session/agents/openai.yaml +7 -0
  292. package/payload/plugins/toolkit/skills/runweave-management/SKILL.md +47 -0
  293. package/payload/plugins/toolkit/skills/scheduled-task/SKILL.md +35 -0
  294. package/payload/plugins/toolkit/skills/shadcn/SKILL.md +245 -0
  295. package/payload/plugins/toolkit/skills/shadcn/agents/openai.yml +5 -0
  296. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn-small.png +0 -0
  297. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn.png +0 -0
  298. package/payload/plugins/toolkit/skills/shadcn/cli.md +276 -0
  299. package/payload/plugins/toolkit/skills/shadcn/customization.md +209 -0
  300. package/payload/plugins/toolkit/skills/shadcn/evals/evals.json +47 -0
  301. package/payload/plugins/toolkit/skills/shadcn/mcp.md +94 -0
  302. package/payload/plugins/toolkit/skills/shadcn/rules/base-vs-radix.md +310 -0
  303. package/payload/plugins/toolkit/skills/shadcn/rules/composition.md +197 -0
  304. package/payload/plugins/toolkit/skills/shadcn/rules/forms.md +198 -0
  305. package/payload/plugins/toolkit/skills/shadcn/rules/icons.md +101 -0
  306. package/payload/plugins/toolkit/skills/shadcn/rules/styling.md +163 -0
  307. package/payload/plugins/toolkit/skills/suiji/SKILL.md +54 -0
  308. package/payload/plugins/toolkit/skills/suiji/references/api.md +72 -0
  309. package/payload/plugins/toolkit/skills/suiji/references/source-adapter.md +40 -0
  310. package/payload/plugins/toolkit/skills/suiji/references/work-records.md +36 -0
  311. package/payload/plugins/toolkit/skills/suiji/scripts/source_adapter.py +46 -0
  312. package/payload/plugins/toolkit/skills/suiji/scripts/suiji.py +358 -0
  313. package/payload/plugins/toolkit/skills/suiji/scripts/work_records.py +328 -0
  314. package/payload/plugins/toolkit/skills/suiji/scripts/work_reports.py +75 -0
  315. package/payload/plugins/toolkit/skills/sync-branch-hard-reset/SKILL.md +59 -0
  316. package/payload/plugins/toolkit/skills/update-runweave-backend/SKILL.md +90 -0
  317. package/payload/plugins/toolkit/skills/update-runweave-backend/agents/openai.yaml +4 -0
  318. package/payload/plugins/toolkit/skills/update-runweave-backend/references/npm-update.md +68 -0
  319. package/payload/plugins/toolkit/skills/update-runweave-backend/references/systemd.md +34 -0
  320. package/payload/plugins/toolkit/skills/update-runweave-desktop/SKILL.md +102 -0
  321. package/payload/plugins/toolkit/skills/update-runweave-ios/SKILL.md +16 -0
  322. package/payload/plugins/toolkit/skills/update-runweave-ios/references/ios-wireless-recovery.md +54 -0
  323. package/payload/plugins/toolkit/skills/using-rw/SKILL.md +265 -0
  324. package/payload/plugins/toolkit/skills/write-plan/SKILL.md +72 -0
  325. package/payload/plugins/toolkit/skills/write-test-cases/SKILL.md +100 -0
  326. package/payload/plugins/toolkit/skills/write-test-cases/assets/test-plan-template.yaml +15 -0
  327. package/payload/plugins/toolkit/skills/write-test-cases/references/coverage-checklist.md +75 -0
  328. package/payload/plugins/toolkit/skills/write-test-cases/references/design-techniques.md +91 -0
  329. package/payload/plugins/toolkit/skills/youtube-reading/SKILL.md +35 -0
  330. package/payload/plugins/toolkit/skills/youtube-reading/agents/openai.yaml +4 -0
  331. package/payload/plugins/toolkit/skills/youtube-reading/assets/runtime-lock.json +92 -0
  332. package/payload/plugins/toolkit/skills/youtube-reading/references/content-contract.md +52 -0
  333. package/payload/plugins/toolkit/skills/youtube-reading/references/setup.md +70 -0
  334. package/payload/plugins/toolkit/skills/youtube-reading/requirements.lock +9 -0
  335. package/payload/plugins/toolkit/skills/youtube-reading/scripts/artifacts.py +206 -0
  336. package/payload/plugins/toolkit/skills/youtube-reading/scripts/runtime.py +424 -0
  337. package/payload/plugins/toolkit/skills/youtube-reading/scripts/youtube_reading.py +314 -0
  338. package/payload/plugins/toolkit-release.json +1 -0
  339. package/payload/scripts/toolkit/codex-state.mjs +75 -0
  340. package/payload/scripts/toolkit/release.mjs +210 -0
  341. package/payload/scripts/toolkit/sync-runtime.mjs +108 -0
  342. package/payload/scripts/toolkit/sync.mjs +585 -0
  343. package/payload/frontend/dist/assets/channel-Bf3IeONI.js +0 -1
  344. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-CFsG2SvG.js +0 -1
  345. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-CFsG2SvG.js +0 -1
  346. package/payload/frontend/dist/assets/clone-CeQXm2YN.js +0 -1
  347. package/payload/frontend/dist/assets/connection-XriXysSc.js +0 -12
  348. package/payload/frontend/dist/assets/main-Cg6Oo1xK.css +0 -33
  349. package/payload/frontend/dist/assets/main-H5R-3_w1.js +0 -696
  350. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-CzpdUl_0.js +0 -1
  351. package/payload/frontend/dist/assets/suiji-CO_L5W1X.js +0 -1
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env python3
2
+ """Private, frozen browsing batches; never writes work facts or schedules tasks."""
3
+ import argparse
4
+ import contextlib
5
+ import datetime as dt
6
+ import fcntl
7
+ import hashlib
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ import socket
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ import uuid
16
+ from zoneinfo import ZoneInfo
17
+ sys.dont_write_bytecode = True
18
+ sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "suiji" / "scripts"))
19
+ import suiji
20
+ import zhihu_http
21
+ import app_usage
22
+ import source_adapter
23
+
24
+
25
+ def require(ok, code):
26
+ if not ok:
27
+ raise suiji.Failure(code)
28
+
29
+
30
+ def read(path):
31
+ return json.loads(suiji.private_read(path))
32
+
33
+
34
+ def write(path, value):
35
+ path = Path(path)
36
+ fd, temporary = tempfile.mkstemp(dir=path.parent, prefix=".browse-")
37
+ try:
38
+ with os.fdopen(fd, "w") as stream:
39
+ json.dump(value, stream, ensure_ascii=False, indent=2)
40
+ stream.flush()
41
+ os.fsync(stream.fileno())
42
+ os.replace(temporary, path)
43
+ directory = os.open(path.parent, os.O_RDONLY)
44
+ try:
45
+ os.fsync(directory)
46
+ finally:
47
+ os.close(directory)
48
+ finally:
49
+ if os.path.exists(temporary):
50
+ os.unlink(temporary)
51
+
52
+
53
+ def digest(value):
54
+ return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()).hexdigest()
55
+
56
+
57
+ def directory(path):
58
+ p = Path(path)
59
+ require(not p.is_symlink(), "SYMLINK_REJECTED")
60
+ p.mkdir(mode=0o700, parents=True, exist_ok=True)
61
+ require(not p.stat().st_mode & 0o077, "PRIVATE_DIRECTORY_REQUIRED")
62
+ return p
63
+
64
+
65
+ def config(path):
66
+ c = read(path)
67
+ require(set(c) - {"scheduledGroup", "transport", "sessionFile", "effectiveFrom", "archivePath"} == {"schemaVersion", "timeZone", "serverId", "ownerId", "connectionId", "externalAccountId", "machine", "profile", "group", "stateDir", "suijiConfig", "tokenFile"}, "INVALID_CONFIG_FIELDS")
68
+ require(type(c.get("scheduledGroup", False)) is bool, "INVALID_SCHEDULED_GROUP_POLICY")
69
+ require(c.get("schemaVersion") == 1 and c.get("timeZone") == "Asia/Shanghai", "INVALID_CONFIG")
70
+ for key in ("serverId", "ownerId", "connectionId"):
71
+ uuid.UUID(c[key])
72
+ require(c.get("externalAccountId") and c.get("machine") == socket.gethostname(), "MACHINE_OR_ACCOUNT_BINDING_MISMATCH")
73
+ for key in ("stateDir", "suijiConfig", "tokenFile"):
74
+ require(Path(c[key]).is_absolute(), "ABSOLUTE_PRIVATE_PATH_REQUIRED")
75
+ connection = read(c["suijiConfig"])
76
+ require(connection.get("mcpPath") == "/mcp/browse" and all(connection.get(k) == c[k] for k in ("serverId", "ownerId")), "BROWSE_CREDENTIAL_IDENTITY_REQUIRED")
77
+ require(c.get("transport", "browser") in ("browser", "http", "app-usage"), "INVALID_TRANSPORT")
78
+ if c.get("transport") == "app-usage":
79
+ require(c.get("profile") == "local-archive" and c.get("group") == "app-usage", "ARCHIVE_BINDING_REQUIRED")
80
+ require(isinstance(c.get("archivePath"), str) and Path(c["archivePath"]).is_absolute(), "ABSOLUTE_ARCHIVE_PATH_REQUIRED")
81
+ require(c["externalAccountId"] == app_usage.source_identity(c["archivePath"]), "ARCHIVE_IDENTITY_CHANGED")
82
+ else:
83
+ require("archivePath" not in c, "ARCHIVE_REQUIRES_APP_USAGE_TRANSPORT")
84
+ require(c.get("profile") in ("profile-1", "profile-2", "profile-3") and c.get("group"), "BROWSER_BINDING_REQUIRED")
85
+ if c.get("transport") == "http":
86
+ require(isinstance(c.get("sessionFile"), str) and Path(c["sessionFile"]).is_absolute(), "ABSOLUTE_SESSION_PATH_REQUIRED")
87
+ else:
88
+ require("sessionFile" not in c, "HTTP_SESSION_REQUIRES_HTTP_TRANSPORT")
89
+ if "effectiveFrom" in c:
90
+ require(dt.date.fromisoformat(c["effectiveFrom"]).isoformat() == c["effectiveFrom"], "INVALID_EFFECTIVE_DATE")
91
+ return c
92
+
93
+
94
+ @contextlib.contextmanager
95
+ def lock(c):
96
+ root = directory(c["stateDir"])
97
+ locks = directory(Path.home() / ".runweave" / "browse-record" / "locks")
98
+ path = locks / (c["serverId"] + "-" + c["ownerId"] + "-" + c["connectionId"] + ".lock")
99
+ require(not path.is_symlink(), "SYMLINK_REJECTED")
100
+ with os.fdopen(os.open(path, os.O_CREAT | os.O_RDWR, 0o600), "w") as stream:
101
+ try:
102
+ fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB)
103
+ except BlockingIOError:
104
+ raise suiji.Failure("CONNECTION_BUSY") from None
105
+ yield root
106
+
107
+
108
+ def command(argv, timeout=120):
109
+ result = subprocess.run(argv, capture_output=True, text=True, timeout=timeout, check=False)
110
+ if result.returncode != 0 or "### Error" in result.stdout:
111
+ code = next((c for c in ("NEEDS_LOGIN", "ACCOUNT_CHANGED_DURING_RUN", "ACCOUNT_CHANGED", "IDENTITY_UNAVAILABLE", "HISTORY_UNAVAILABLE_OR_PARSE_FAILED") if c in result.stdout), "BROWSER_UNAVAILABLE_OR_READ_FAILED")
112
+ raise suiji.Failure(code)
113
+ return result.stdout
114
+
115
+
116
+ def browser_read(c, options, folder):
117
+ group = c["group"]
118
+ scheduled_run = os.environ.get("RUNWEAVE_SCHEDULED_TASK_RUN_ID")
119
+ if scheduled_run:
120
+ require(c.get("scheduledGroup") is True, "SCHEDULED_GROUP_NOT_AUTHORIZED")
121
+ uuid.UUID(scheduled_run)
122
+ group = "browser-group-scheduled-" + scheduled_run
123
+ resolution = json.loads(command([os.environ.get("RUNWEAVE_CLI_BIN", "rw"), "browser", "profile", "resolve", "--profile", c["profile"].split("-")[-1], "--group-id", group, "--json"]))
124
+ require(resolution.get("profileId") == c["profile"] and resolution.get("browserGroupId") == group, "BROWSER_BINDING_CHANGED")
125
+ write(folder / "browser-binding.json", {"machine": c["machine"], "profile": c["profile"], "group": group, "scheduledRunId": scheduled_run})
126
+ session = "browse-" + c["connectionId"]
127
+ cli = ["pnpm", "exec", "playwright", "cli", "-s=" + session]
128
+ # CDP credentials exist in process memory only; never saved in the config or batch.
129
+ command(cli + ["attach", "--cdp=" + resolution["cdpEndpoint"]])
130
+ try:
131
+ source = Path(__file__).with_name("zhihu-read.js").read_text()
132
+ script = folder / "reader.js"
133
+ descriptor = os.open(script, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
134
+ with os.fdopen(descriptor, "w") as stream:
135
+ stream.write(source.replace("/*BROWSE_OPTIONS*/ {}", json.dumps(options)))
136
+ try:
137
+ output = command(cli + ["run-code", "--filename=" + str(script)], timeout=1800)
138
+ require("### Result\n" in output, "BROWSER_RESULT_UNAVAILABLE")
139
+ return json.JSONDecoder().raw_decode(output.split("### Result\n", 1)[1])[0]
140
+ finally:
141
+ script.unlink(missing_ok=True)
142
+ finally:
143
+ command(cli + ["detach"])
144
+
145
+
146
+ def authorize_http(c):
147
+ require(c.get("transport") == "http" and not os.environ.get("RUNWEAVE_SCHEDULED_TASK_RUN_ID"), "INTERACTIVE_HTTP_AUTHORIZATION_REQUIRED")
148
+ path = Path(c["sessionFile"])
149
+ directory(path.parent)
150
+ if path.exists() or path.is_symlink():
151
+ suiji.private_read(path)
152
+ resolution = json.loads(command([os.environ.get("RUNWEAVE_CLI_BIN", "rw"), "browser", "profile", "resolve", "--profile", c["profile"].split("-")[-1], "--group-id", c["group"], "--json"]))
153
+ require(resolution.get("profileId") == c["profile"] and resolution.get("browserGroupId") == c["group"], "BROWSER_BINDING_CHANGED")
154
+ cli = ["pnpm", "exec", "playwright", "cli", "-s=browse-http-auth-" + c["connectionId"]]
155
+ command(cli + ["attach", "--cdp=" + resolution["cdpEndpoint"]])
156
+ try:
157
+ script = """async page => {
158
+ const cookies = await page.context().cookies([
159
+ 'https://www.zhihu.com/api/v4/me',
160
+ 'https://api.zhihu.com/unify-consumption/read_history',
161
+ 'https://zhuanlan.zhihu.com/api/articles/1'
162
+ ]);
163
+ return {cookies, userAgent:await page.evaluate(() => navigator.userAgent)};
164
+ }"""
165
+ output = command(cli + ["run-code", script])
166
+ require("### Result\n" in output, "HTTP_AUTHORIZATION_UNAVAILABLE")
167
+ auth = json.JSONDecoder().raw_decode(output.split("### Result\n", 1)[1])[0]
168
+ auth["cookies"] = [cookie for cookie in auth["cookies"] if cookie.get("domain", "").lstrip(".") in ("zhihu.com", "www.zhihu.com", "api.zhihu.com", "zhuanlan.zhihu.com")]
169
+ auth.update(schemaVersion=1, **{k: c[k] for k in ("machine", "profile", "externalAccountId")})
170
+ require(zhihu_http.Session(auth, c).identity() == c["externalAccountId"], "ACCOUNT_CHANGED")
171
+ write(path, auth)
172
+ return {"authorized": True, "transport": "http", "sessionFile": str(path), "accountHash": hashlib.sha256(c["externalAccountId"].encode()).hexdigest()[:12]}
173
+ finally:
174
+ command(cli + ["detach"])
175
+
176
+
177
+ def client(c):
178
+ value, info = suiji.connect(read(c["suijiConfig"]), c["tokenFile"])
179
+ require(info.get("features", {}).get("browse"), "BROWSE_SERVICE_UNAVAILABLE")
180
+ return value
181
+
182
+
183
+ def adapter_receipt(c, start, end, result=None, error=None):
184
+ begin = dt.datetime.combine(dt.date.fromisoformat(start), dt.time(), ZoneInfo(c["timeZone"]))
185
+ finish = dt.datetime.combine(dt.date.fromisoformat(end) + dt.timedelta(days=1), dt.time(), ZoneInfo(c["timeZone"]))
186
+ items = {}
187
+ for value in (result or {}).get("items", []):
188
+ observed, entry = value["observation"], value["entry"]
189
+ key = observed["contentType"] + ":" + observed["contentId"] + ":" + entry["browseDate"]
190
+ item = {"id": key, "version": digest({k: entry[k] for k in ("title", "body", "sourceUrl", "contentState")}), "occurredAt": observed["readTime"]}
191
+ if key not in items or item["occurredAt"] > items[key]["occurredAt"]:
192
+ items[key] = item
193
+ provider = "app-usage" if c.get("transport") == "app-usage" else "zhihu"
194
+ return source_adapter.receipt("browse", "browse:" + provider + ":" + c["connectionId"], begin.isoformat(), finish.isoformat(), c.get("effectiveFrom"),
195
+ dt.datetime.now(dt.timezone.utc).isoformat(), "unavailable" if error else result["coverage"]["state"], list(items.values()),
196
+ [error] if error else result["coverage"]["errors"])
197
+
198
+
199
+ def prepare(c, config_path, start, end):
200
+ require(dt.date.fromisoformat(start).isoformat() == start and dt.date.fromisoformat(end).isoformat() == end and start <= end, "INVALID_DATE_RANGE")
201
+ with lock(c) as root:
202
+ active_root = directory(Path.home() / ".runweave" / "browse-record" / "active")
203
+ active_file = active_root / (c["serverId"] + "-" + c["ownerId"] + "-" + c["connectionId"] + ".json")
204
+ resume = None
205
+ if active_file.exists():
206
+ active = read(active_file)
207
+ path = Path(active["path"])
208
+ manifest = read(path / "manifest.json")
209
+ if manifest["state"] in ("prepared", "pending"):
210
+ require(path.parent == root.resolve(), "ORIGINAL_BATCH_BELONGS_TO_ANOTHER_STATE_DIRECTORY")
211
+ require(manifest["from"] == start and manifest["to"] == end, "RESOLVE_ORIGINAL_BATCH_FIRST")
212
+ return {"batch": str(path), "runId": manifest["runId"], "state": manifest["state"], "reused": True,
213
+ **({"adapterReceipt": str(path / "adapter-receipt.json")} if (path / "adapter-receipt.json").exists() else {})}
214
+ if manifest["state"] == "collecting":
215
+ require(path.parent == root.resolve() and manifest["from"] == start and manifest["to"] == end and manifest["configHash"] == digest(c), "RECOVER_ORIGINAL_RANGE_FIRST")
216
+ if (path / "payload.json").exists():
217
+ payload = read(path / "payload.json")
218
+ require(payload["identityBefore"] == c["externalAccountId"] == payload["identityAfter"], "ACCOUNT_CHANGED")
219
+ manifest.update(state="prepared", payloadHash=digest(payload))
220
+ write(path / "manifest.json", manifest)
221
+ return {"batch": str(path), "runId": manifest["runId"], "state": "prepared", "reused": True,
222
+ **({"adapterReceipt": str(path / "adapter-receipt.json")} if (path / "adapter-receipt.json").exists() else {})}
223
+ resume = (manifest["runId"], path)
224
+ run = resume[0] if resume else str(uuid.uuid4())
225
+ path = resume[1] if resume else directory(root / run)
226
+ manifest = {"state": "collecting", "runId": run, "from": start, "to": end, "config": str(Path(config_path).resolve()), "configHash": digest(c), "leaseExpiresAt": (dt.datetime.now(dt.timezone.utc) + dt.timedelta(minutes=30)).isoformat()}
227
+ write(path / "manifest.json", manifest)
228
+ write(active_file, {"path": str(path), "runId": run})
229
+ try:
230
+ options = {"from": start, "to": end, "externalAccountId": c["externalAccountId"], "connectionId": c["connectionId"]}
231
+ if c.get("transport") == "app-usage":
232
+ result = app_usage.collect(c, options)
233
+ elif c.get("transport") == "http":
234
+ write(path / "http-binding.json", {"machine": c["machine"], "profile": c["profile"], "transport": "http", "scheduledRunId": os.environ.get("RUNWEAVE_SCHEDULED_TASK_RUN_ID")})
235
+ result = zhihu_http.collect(c, options)
236
+ else:
237
+ result = browser_read(c, options, path)
238
+ require(result.get("identityBefore") == c["externalAccountId"] == result.get("identityAfter"), "ACCOUNT_CHANGED")
239
+ write(path / "adapter-receipt.json", adapter_receipt(c, start, end, result))
240
+ payload = {k: result[k] for k in ("identityBefore", "identityAfter", "items", "coverage")}
241
+ payload.update(runId=run, connectionId=c["connectionId"], externalAccountId=c["externalAccountId"], **{"from": start, "to": end})
242
+ write(path / "payload.json", payload)
243
+ manifest.update(state="prepared", payloadHash=digest(payload), historyUnchanged=result["historyUnchanged"])
244
+ write(path / "manifest.json", manifest)
245
+ return {"batch": str(path), "runId": run, "coverage": result["coverage"], "historyUnchanged": result["historyUnchanged"], "adapterReceipt": str(path / "adapter-receipt.json")}
246
+ except Exception as error:
247
+ code = str(error)
248
+ reason = "needs-login" if code == "NEEDS_LOGIN" else "identity-changed" if "ACCOUNT_CHANGED" in code else "parse-failed" if "PARSE" in code else "unavailable"
249
+ write(path / "adapter-receipt.json", adapter_receipt(c, start, end, error=reason))
250
+ manifest["error"] = reason
251
+ try:
252
+ client(c).call("report_browse_failure", {"idempotencyKey": run, "input": {"runId": run, "connectionId": c["connectionId"], "externalAccountId": c["externalAccountId"], "from": start, "to": end, "reason": reason}})
253
+ except Exception:
254
+ manifest["failureReceiptUnconfirmed"] = True
255
+ manifest["state"] = "failed"
256
+ write(path / "manifest.json", manifest)
257
+ raise
258
+
259
+
260
+ def batch(path):
261
+ path = Path(path).resolve()
262
+ meta = read(path / "manifest.json")
263
+ c = config(meta["config"])
264
+ require(digest(c) == meta["configHash"] and path.parent == Path(c["stateDir"]).resolve(), "BATCH_IDENTITY_CHANGED")
265
+ return path, meta, c
266
+
267
+
268
+ def import_batch(path, reconcile=False):
269
+ path, meta, c = batch(path)
270
+ with lock(c) as root:
271
+ if reconcile and meta["state"] == "failed":
272
+ cli = client(c)
273
+ try:
274
+ receipt = cli.call("get_browse_run", {"runId": meta["runId"]})
275
+ except suiji.Failure as error:
276
+ require(str(error) == "NOT_FOUND", "RECEIPT_READ_UNAVAILABLE")
277
+ receipt = cli.call("report_browse_failure", {"idempotencyKey": meta["runId"], "input": {"runId": meta["runId"], "connectionId": c["connectionId"], "externalAccountId": c["externalAccountId"], "from": meta["from"], "to": meta["to"], "reason": meta["error"]}})
278
+ require(meta["error"] in receipt["coverage"]["errors"], "FAILURE_RECEIPT_MISMATCH")
279
+ write(path / "receipt.json", receipt)
280
+ meta["failureReceiptUnconfirmed"] = False
281
+ write(path / "manifest.json", meta)
282
+ return {"runId": meta["runId"], "failed": True, "reported": True}
283
+ payload = read(path / "payload.json")
284
+ require(digest(payload) == meta["payloadHash"], "FROZEN_PAYLOAD_CHANGED")
285
+ require(meta["state"] in ("prepared", "pending", "confirmed"), "BATCH_NOT_PREPARED")
286
+ cli = client(c)
287
+ if reconcile or meta["state"] in ("pending", "confirmed"):
288
+ try:
289
+ receipt = cli.call("get_browse_run", {"runId": meta["runId"]})
290
+ except suiji.Failure as error:
291
+ require(str(error) == "NOT_FOUND", "RECEIPT_READ_UNAVAILABLE")
292
+ receipt = None
293
+ if receipt:
294
+ require(receipt["connectionId"] == c["connectionId"] and receipt["from"] == meta["from"] and receipt["to"] == meta["to"], "RECEIPT_IDENTITY_MISMATCH")
295
+ write(path / "receipt.json", receipt)
296
+ meta["state"] = "confirmed"; write(path / "manifest.json", meta)
297
+ write(root / (c["connectionId"] + ".checkpoint.json"), {"runId": meta["runId"], "from": meta["from"], "to": meta["to"], "coverage": receipt["coverage"]})
298
+ return {"runId": meta["runId"], "confirmed": True, "receipt": receipt["receipt"]}
299
+ meta["state"] = "pending"; write(path / "manifest.json", meta)
300
+ cli.call("import_browse_entries", {"idempotencyKey": meta["runId"], "input": payload})
301
+ # Independent readback confirms commit even when the original response was lost.
302
+ receipt = cli.call("get_browse_run", {"runId": meta["runId"]})
303
+ require(receipt["connectionId"] == c["connectionId"], "RECEIPT_IDENTITY_MISMATCH")
304
+ write(path / "receipt.json", receipt)
305
+ meta["state"] = "confirmed"; write(path / "manifest.json", meta)
306
+ write(root / (c["connectionId"] + ".checkpoint.json"), {"runId": meta["runId"], "from": meta["from"], "to": meta["to"], "coverage": receipt["coverage"]})
307
+ return {"runId": meta["runId"], "confirmed": True, "receipt": receipt["receipt"]}
308
+
309
+
310
+ def read_digest(c, config_path, day):
311
+ require(c.get("transport") != "app-usage", "APP_USAGE_HAS_NO_READING_DIGEST")
312
+ dt.date.fromisoformat(day)
313
+ with lock(c) as root:
314
+ cli = client(c)
315
+ entries, cursor, revision = [], None, None
316
+ while True:
317
+ args = {"source": "zhihu", "from": day, "to": day, "limit": 100}
318
+ if cursor:
319
+ args["cursor"] = cursor
320
+ page = cli.call("list_browse_entries", args)
321
+ require(revision in (None, page["browseRevision"]), "BROWSE_CHANGED: restart digest read")
322
+ revision = page["browseRevision"]
323
+ for entry in page["items"]:
324
+ if entry["source"]["connectionId"] == c["connectionId"]:
325
+ value = cli.call("get_browse_entry", {"id": entry["id"]})["entry"]
326
+ require(value["version"] == entry["version"] and not value["deletedAt"], "BROWSE_CHANGED")
327
+ entries.append(value)
328
+ cursor = page["nextCursor"]
329
+ if not cursor:
330
+ break
331
+ path = directory(root / str(uuid.uuid4()))
332
+ refs = [{"id": e["id"], "version": e["version"], "hash": e["contentHash"]} for e in entries]
333
+ value = {"date": day, "connectionId": c["connectionId"], "entries": entries, "entryRefs": refs, "inputRevision": revision}
334
+ write(path / "input.json", value)
335
+ write(path / "manifest.json", {"state": "digest-input", "config": str(Path(config_path).resolve()), "configHash": digest(c), "inputHash": digest(value), "key": str(uuid.uuid4())})
336
+ return {"batch": str(path), "input": str(path / "input.json"), "count": len(entries)}
337
+
338
+
339
+ def publish_digest(path, input_path):
340
+ path, meta, c = batch(path)
341
+ with lock(c):
342
+ frozen = read(path / "input.json")
343
+ require(digest(frozen) == meta["inputHash"], "DIGEST_INPUT_CHANGED")
344
+ value = read(input_path)
345
+ require(set(value) == {"title", "body", "entryRefs", "inputRevision"} and value["entryRefs"] == frozen["entryRefs"] and value["inputRevision"] == frozen["inputRevision"], "DIGEST_REFERENCES_CHANGED")
346
+ payload = {**value, "date": frozen["date"], "connectionId": c["connectionId"]}
347
+ if meta.get("payloadHash"):
348
+ require(meta["payloadHash"] == digest(payload), "FROZEN_DIGEST_CHANGED")
349
+ meta.update(state="pending-digest", payloadHash=digest(payload)); write(path / "manifest.json", meta)
350
+ cli = client(c)
351
+ result = cli.call("publish_browse_digest", {"idempotencyKey": meta["key"], "input": payload})
352
+ confirmed = cli.call("list_browse_digests", {"date": frozen["date"], "connectionId": c["connectionId"]})
353
+ require(any(item["id"] == result["digest"]["id"] and item["version"] == result["digest"]["version"] for item in confirmed["items"]), "DIGEST_READBACK_MISMATCH")
354
+ meta["state"] = "confirmed-digest"; write(path / "manifest.json", meta)
355
+ write(path / "receipt.json", result)
356
+ return {"confirmed": True, "id": result["digest"]["id"], "version": result["digest"]["version"]}
357
+
358
+
359
+ def main():
360
+ os.umask(0o077)
361
+ parser = argparse.ArgumentParser(description=__doc__)
362
+ parser.add_argument("command", choices=["authorize-http", "prepare", "import", "reconcile", "read", "publish-digest"])
363
+ for name in ("config", "from", "to", "batch", "date", "input"):
364
+ parser.add_argument("--" + name)
365
+ args = parser.parse_args()
366
+ if args.command in ("import", "reconcile"):
367
+ return import_batch(args.batch, args.command == "reconcile")
368
+ if args.command == "publish-digest":
369
+ return publish_digest(args.batch, args.input)
370
+ c = config(args.config)
371
+ if args.command == "authorize-http":
372
+ return authorize_http(c)
373
+ if args.command == "read":
374
+ return read_digest(c, args.config, args.date)
375
+ now = dt.datetime.now(ZoneInfo("Asia/Shanghai")).date()
376
+ start = getattr(args, "from") or max((now - dt.timedelta(days=1)).isoformat(), c.get("effectiveFrom", "0001-01-01"))
377
+ return prepare(c, args.config, start, args.to or now.isoformat())
378
+
379
+
380
+ if __name__ == "__main__":
381
+ try:
382
+ print(json.dumps(main(), ensure_ascii=False))
383
+ except Exception as error:
384
+ print(json.dumps({"ok": False, "error": str(error) if isinstance(error, suiji.Failure) else type(error).__name__}), file=sys.stderr)
385
+ sys.exit(1)
@@ -0,0 +1,126 @@
1
+ /* global fetch, AbortSignal, URL, DOMParser, crypto */
2
+ // Playwright run-code consumes this file as one function expression; evaluate callbacks run in the browser.
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
4
+ async page => {
5
+ // prepare substitutes only non-secret identity/range options into this expression.
6
+ const options = /*BROWSE_OPTIONS*/ {};
7
+ const tabs = new Map();
8
+ const errors = [];
9
+ const capturedAt = new Date().toISOString();
10
+ const deadline = Date.now() + 20 * 60 * 1000;
11
+ const apiOrigin = "https://api.zhihu.com";
12
+ const allowed = url => /^https:\/\/(?:api\.zhihu\.com\/unify-consumption\/read_history(?:\?[^#]*)?|www\.zhihu\.com\/api\/v4\/(?:me|answers\/\d+)(?:\?[^#]*)?|zhuanlan\.zhihu\.com\/api\/articles\/\d+)$/.test(url);
13
+ const request = async url => {
14
+ if (Date.now() > deadline && url !== "https://www.zhihu.com/api/v4/me") throw new Error("RUN_BUDGET_EXCEEDED");
15
+ if (!allowed(url)) throw new Error("UNTRUSTED_API_URL");
16
+ const origin = url.match(/^https:\/\/[^/]+/)[0];
17
+ let tab = tabs.get(origin);
18
+ if (!tab) { tab = await page.context().newPage(); tabs.set(origin, tab); await tab.goto(url, { timeout: 15000, waitUntil: "domcontentloaded" }); }
19
+ for (let attempt = 0; attempt < 3; attempt++) {
20
+ const result = await tab.evaluate(async target => {
21
+ try {
22
+ const response = await fetch(target, { credentials: "include", signal: AbortSignal.timeout(15000) });
23
+ const text = await response.text();
24
+ let data; try { data = JSON.parse(text); } catch { return { status: response.status, error: "NON_JSON_OR_VERIFICATION" }; }
25
+ return { status: response.status, data };
26
+ } catch { return { status: 0, error: "TIMEOUT_OR_NETWORK" }; }
27
+ }, url);
28
+ if ((result.status === 0 || result.status === 429 || result.status >= 500) && attempt < 2) { await tab.waitForTimeout(500 * (attempt + 1)); continue; }
29
+ return result;
30
+ }
31
+ };
32
+ const identity = async () => {
33
+ const r = await request("https://www.zhihu.com/api/v4/me");
34
+ if (r.status !== 200 || typeof r.data?.id !== "string" || !r.data.id) throw new Error(r.status === 401 || r.status === 403 ? "NEEDS_LOGIN" : "IDENTITY_UNAVAILABLE");
35
+ return r.data.id;
36
+ };
37
+ const historyUrl = `${apiOrigin}/unify-consumption/read_history?offset=0&limit=20`;
38
+ const parseHistory = response => {
39
+ if (response.status !== 200 || !Array.isArray(response.data?.data) || typeof response.data?.paging?.is_end !== "boolean") throw new Error(response.status === 401 || response.status === 403 ? "NEEDS_LOGIN" : "HISTORY_UNAVAILABLE_OR_PARSE_FAILED");
40
+ return response.data;
41
+ };
42
+ const parse = card => {
43
+ const value = card.card_type === "single_card" ? card.data : null, extra = value?.extra;
44
+ if (!extra || typeof extra.content_token !== "string" || typeof extra.content_type !== "string" || !Number.isFinite(extra.read_time)) throw new Error("UNKNOWN_HISTORY_STRUCTURE");
45
+ const occurredAt = new Date(extra.read_time * (extra.read_time > 1e12 ? 1 : 1000)).toISOString();
46
+ const day = new Date(Date.parse(occurredAt) + 8 * 3600000).toISOString().slice(0, 10);
47
+ return { type: extra.content_type, id: extra.content_token, occurredAt, day, questionId: extra.question_token,
48
+ title: typeof value.header?.title === "string" ? value.header.title : "知乎浏览内容" };
49
+ };
50
+ const signature = value => JSON.stringify(value.data.map(card => { const x = parse(card); return [x.type,x.id,x.occurredAt]; }));
51
+ try {
52
+ const identityBefore = await identity();
53
+ if (options.externalAccountId && identityBefore !== options.externalAccountId) throw new Error("ACCOUNT_CHANGED");
54
+ if (options.probe) return { identityBefore };
55
+ const observed = new Map(); let pages = 0, stable = false;
56
+ let baseline; let baselinePages = new Map();
57
+ for (let round = 0; round < 3; round++) {
58
+ baselinePages = new Map();
59
+ let next = historyUrl, first, complete = false; const seenPages = new Set();
60
+ for (let index = 0; index < 100; index++) {
61
+ if (seenPages.has(next)) { errors.push("PAGINATION_LOOP"); break; }
62
+ seenPages.add(next);
63
+ let value;
64
+ try { value = parseHistory(await request(next)); } catch (e) { errors.push(e.message); break; }
65
+ baselinePages.set(next, signature(value));
66
+ pages++; if (index === 0) { first = signature(value); baseline = baseline ?? first; }
67
+ if (value.data.length > 20) { errors.push("PAGE_LIMIT_EXCEEDED"); break; }
68
+ for (const card of value.data) {
69
+ try { const item = parse(card); if (item.day >= options.from && item.day <= options.to) observed.set(`${item.type}:${item.id}:${item.occurredAt}`, item); }
70
+ catch { errors.push("UNKNOWN_HISTORY_STRUCTURE"); }
71
+ }
72
+ if (value.paging.is_end) { complete = true; break; }
73
+ try { const url = await tabs.get(apiOrigin).evaluate(value => { const u = new URL(value); return { href: u.href, origin: u.origin, limit: u.searchParams.get("limit") }; }, value.paging.next); if (!allowed(url.href) || url.origin !== apiOrigin || url.limit !== "20") throw new Error(); next = url.href; }
74
+ catch { errors.push("UNTRUSTED_PAGINATION"); break; }
75
+ if (index === 99) errors.push("PAGE_CAP_REACHED");
76
+ }
77
+ try { const check = parseHistory(await request(historyUrl)); if (signature(check) === first) { stable = complete; break; } }
78
+ catch { errors.push("HOMEPAGE_RECHECK_FAILED"); break; }
79
+ if (round === 2) errors.push("HISTORY_UNSTABLE");
80
+ }
81
+ const plain = async html => {
82
+ const tab = tabs.get("https://www.zhihu.com");
83
+ return tab.evaluate(value => {
84
+ const safe = value.replace(/<(script|style|iframe|object|embed|video|audio)[\s\S]*?<\/\1\s*>/gi, "").replace(/<(img|source|link|meta)\b[^>]*>/gi, "");
85
+ const doc = new DOMParser().parseFromString(safe, "text/html");
86
+ for (const node of doc.querySelectorAll("p,div,br,li,h1,h2,h3,blockquote")) node.append("\n");
87
+ return (doc.body.textContent ?? "").replace(/\n{3,}/g, "\n\n").trim();
88
+ }, html);
89
+ };
90
+ const bodies = new Map(); const items = [];
91
+ for (const observation of observed.values()) {
92
+ const key = observation.type + ":" + observation.id;
93
+ let content = bodies.get(key);
94
+ if (!content) {
95
+ const supported = ["answer", "article"].includes(observation.type) && /^\d+$/.test(observation.id);
96
+ let body = "", state = "metadata-only", reason = supported ? null : "unsupported-content-type";
97
+ if (supported) {
98
+ const url = observation.type === "answer" ? `https://www.zhihu.com/api/v4/answers/${observation.id}?include=content,paid_info` : `https://zhuanlan.zhihu.com/api/articles/${observation.id}`;
99
+ try {
100
+ const r = await request(url);
101
+ if (r.status === 200 && String(r.data?.id) === observation.id && typeof r.data.content === "string") {
102
+ body = await plain(r.data.content); state = body ? "available" : "metadata-only";
103
+ if (r.data.is_paid === true && r.data.paid_info?.has_purchased !== true) { state = "metadata-only"; reason = "paid-content-limited"; }
104
+ if (!body) reason = "empty-content";
105
+ } else reason = r.status === 403 ? "forbidden" : r.status === 404 ? "not-found" : "content-unavailable-or-id-mismatch";
106
+ } catch { reason = "content-timeout-or-parse-failed"; }
107
+ }
108
+ content = { body, state, reason }; bodies.set(key, content);
109
+ }
110
+ const sourceUrl = observation.type === "answer" && /^\d+$/.test(observation.questionId ?? "") ? `https://www.zhihu.com/question/${observation.questionId}/answer/${observation.id}` : observation.type === "article" ? `https://zhuanlan.zhihu.com/p/${observation.id}` : null;
111
+ items.push({ observation: { contentType: observation.type, contentId: observation.id, readTime: observation.occurredAt, observedAt: capturedAt },
112
+ entry: { captureId: await tabs.get("https://www.zhihu.com").evaluate(() => crypto.randomUUID()), browseDate: observation.day, occurredAt: observation.occurredAt, capturedAt, title: observation.title, body: content.body, userNote: "", source: { provider: "zhihu", displayName: "知乎", connectionId: options.connectionId }, contentType: observation.type, captureMethod: "connector", sourceUrl, contentState: { state: content.state, reason: content.reason }, attachmentIds: [] } });
113
+ }
114
+ const identityAfter = await identity();
115
+ if (identityAfter !== identityBefore) throw new Error("ACCOUNT_CHANGED_DURING_RUN");
116
+ let historyUnchanged = false;
117
+ try { historyUnchanged = baseline === signature(parseHistory(await request(historyUrl))); }
118
+ catch { errors.push("HISTORY_RECHECK_FAILED"); }
119
+ for (const [url, before] of baselinePages) {
120
+ try { if (signature(parseHistory(await request(url))) !== before) historyUnchanged = false; }
121
+ catch { historyUnchanged = false; errors.push("HISTORY_RECHECK_FAILED"); }
122
+ }
123
+ if (!historyUnchanged) errors.push("HISTORY_CHANGED_DURING_CONTENT_READ");
124
+ return { identityBefore, identityAfter, items, historyUnchanged, coverage: { state: stable && errors.length === 0 ? "complete" : "partial", pages, metadata: items.length, content: items.filter(i => i.entry.contentState.state === "available").length, errors: [...new Set(errors)].slice(0,200) } };
125
+ } finally { for (const tab of tabs.values()) await tab.close(); }
126
+ }