@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,416 @@
1
+ #!/usr/bin/env python3
2
+ """One local Daily Record workflow, reused by scheduled Agents on every machine."""
3
+ import argparse
4
+ import datetime as dt
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import subprocess
10
+ import sys
11
+ import time
12
+ from types import SimpleNamespace
13
+ import uuid
14
+
15
+ sys.dont_write_bytecode = True
16
+ from job_state import Failure, State, config, date, digest, directory, process_identity, read, require, scopes, stamp, window, write, browser_enabled, screen_enabled, materials_enabled, migrate_sources
17
+ import job_isolation
18
+ import job_facts
19
+ import job_screen
20
+
21
+
22
+ def suiji_module():
23
+ location = Path(__file__).resolve().parents[2] / "suiji" / "scripts"
24
+ require((location / "suiji.py").is_file(), "SUIJI_SKILL_REQUIRED: install the matching suiji skill")
25
+ sys.path.insert(0, str(location))
26
+ import suiji
27
+ return suiji
28
+
29
+
30
+ def command(argv, accepted=(0,)):
31
+ try:
32
+ result = subprocess.run(argv, capture_output=True, text=True, timeout=600, check=False)
33
+ except (OSError, subprocess.TimeoutExpired):
34
+ raise Failure("COLLECTOR_UNAVAILABLE_OR_TIMEOUT") from None
35
+ require(result.returncode in accepted, "COLLECTION_FAILED: inspect the private source manifest")
36
+ return result
37
+
38
+
39
+ def adapter_receipts(c, day, rows, receipts, errors):
40
+ suiji_module() # Loads only helpers; no connection or credentials are opened.
41
+ import source_adapter
42
+ results = []
43
+ for actual in receipts:
44
+ scope = actual["sourceScope"]
45
+ if actual["status"] != "complete" and not errors.get(scope):
46
+ errors[scope] = ["SOURCE_" + actual["status"].upper()]
47
+ selected = [r for r in rows if r["day"] == day and r["ref"]["sourceScope"] == scope]
48
+ effective = c["sources"]["screen"]["effectiveFrom"] if scope.startswith("screen:") else c["startDate"]
49
+ args = ("work", scope, actual["from"], actual["to"], effective, actual["collectedAt"])
50
+ try:
51
+ items = [{"id": r["anchor"] + ":" + r["role"] if r["ref"]["channel"] == "local-agent" else r["id"],
52
+ "version": r["sourceVersion"], "occurredAt": r["occurredAt"]} for r in selected]
53
+ result = source_adapter.receipt(*args, actual["status"], items, errors.get(scope, []))
54
+ except (ValueError, KeyError, TypeError):
55
+ # An invalid adapter cannot invalidate evidence from another source.
56
+ rows[:] = [r for r in rows if not (r["day"] == day and r["ref"]["sourceScope"] == scope)]
57
+ actual.update(status="unavailable", collectedAt=stamp(), **window(day))
58
+ errors[scope] = ["INVALID_ADAPTER_RESULT"]
59
+ result = source_adapter.receipt("work", scope, actual["from"], actual["to"], effective, actual["collectedAt"], "unavailable", [], errors[scope])
60
+ results.append(result)
61
+ return results
62
+
63
+
64
+ def collect(state, path, meta):
65
+ c = state.c
66
+ rows, meta["receipts"], meta["sourceDirs"] = [], {}, {}
67
+ meta["sourceErrors"], meta["adapterReceipts"] = {}, {}
68
+ for day in meta["days"]:
69
+ day_root = directory(path / (day + "-g" + str(meta["generation"])))
70
+ meta["sourceDirs"][day] = str(day_root)
71
+ receipts, errors = [], {}
72
+ local = c["sources"]["localAgent"]
73
+ for provider in local["providers"]:
74
+ bundle = day_root / ("agent-" + provider)
75
+ scope = "agent:" + c["producerId"] + ":" + provider
76
+ argv = c["collectorArgv"] + ["collect", "--from", day, "--to", day, "--timezone", c["timeZone"], "--out", str(bundle), "--providers", provider, "--machine-id", c["producerId"]]
77
+ if local.get("home"):
78
+ argv += ["--home", local["home"]]
79
+ for thread in state.data["excludedThreads"]:
80
+ argv += ["--exclude-thread", thread]
81
+ try:
82
+ command(argv, (0, 2))
83
+ material, actual = job_facts.native_rows(bundle, c, day)
84
+ require(len(actual) == 1 and actual[0]["sourceScope"] == scope and all(m["ref"]["sourceScope"] == scope for m in material), "AGENT_SOURCE_IDENTITY_CHANGED")
85
+ rows.extend(material)
86
+ receipts.extend(actual)
87
+ except (Failure, OSError, ValueError, KeyError, TypeError) as error:
88
+ errors[scope] = [str(error) if isinstance(error, Failure) else "INVALID_SOURCE_DATA"]
89
+ receipts.append({"sourceScope": scope, "status": "unavailable", "collectedAt": stamp(), **window(day)})
90
+ if c["sources"]["feishu"]["enabled"]:
91
+ bundle = day_root / "feishu"
92
+ try:
93
+ command([sys.executable, str(Path(__file__).with_name("feishu.py")), "discover", "--from", day, "--to", day, "--channels", "messages", "--timezone", c["timeZone"], "--out", str(bundle)], (0, 2))
94
+ material, receipt, identity = job_facts.feishu_rows(bundle, day, c)
95
+ require(identity and identity.get("openId"), "FEISHU_IDENTITY_UNAVAILABLE")
96
+ previous = state.data.get("feishuIdentity")
97
+ require(previous in (None, identity["openId"]), "FEISHU_IDENTITY_CHANGED")
98
+ state.data["feishuIdentity"] = identity["openId"]
99
+ rows.extend(material)
100
+ receipts.append(receipt)
101
+ except (Failure, OSError, ValueError, KeyError, TypeError) as error:
102
+ scope = "feishu:" + c["sources"]["feishu"]["accountScope"] + ":messages"
103
+ errors[scope] = [str(error) if isinstance(error, Failure) else "INVALID_SOURCE_DATA"]
104
+ receipts.append({"sourceScope": scope, "status": "unavailable", "collectedAt": stamp(), **window(day)})
105
+ if screen_enabled(c, day):
106
+ bundle = day_root / "screen"
107
+ try:
108
+ job_screen.export(c, day, bundle)
109
+ material, receipt, revision = job_screen.rows(bundle, day, c)
110
+ rows.extend(material)
111
+ receipts.append(receipt)
112
+ meta.setdefault("screenRevisions", {})[day] = revision
113
+ except (Failure, OSError, ValueError, KeyError, TypeError):
114
+ receipts.append(job_screen.unavailable(day, c))
115
+ reports = adapter_receipts(c, day, rows, receipts, errors)
116
+ write(day_root / "adapter-receipts.json", reports)
117
+ meta["adapterReceipts"][day] = str(day_root / "adapter-receipts.json")
118
+ meta["sourceErrors"][day] = errors
119
+ meta["receipts"][day] = receipts
120
+ write(path / "evidence.json", rows)
121
+ meta.update(phase="reviewed", evidenceHash=digest(rows), read={}, imageReads={})
122
+ template(path, meta)
123
+
124
+
125
+ def template(path, meta):
126
+ write(path / "facts.template.json", {"batchId": meta["id"], "generation": meta["generation"], "evidenceHash": meta["evidenceHash"], "days": [{"date": d, "items": []} for d in meta["days"]]})
127
+
128
+
129
+ def prepare(state, args):
130
+ require(args.thread_id not in state.data.get("forbiddenWorkThreads", []), "NEW_WORK_CONTEXT_REQUIRED")
131
+ require(args.run_id and args.thread_id and args.owner_pid and args.owner_pid > 1, "RUN_THREAD_AND_AGENT_PID_REQUIRED")
132
+ owner = process_identity(args.owner_pid)
133
+ require(owner, "OWNER_PROCESS_NOT_RUNNING")
134
+ require(state.data.get("workIsolationVersion") == 1 or not state.data.get("identities"), "LEGACY_IDENTITIES_REQUIRE_ISOLATE_WORK")
135
+ state.data["workIsolationVersion"] = 1
136
+ active = state.data.get("active")
137
+ if active:
138
+ path = Path(state.data["batches"][active]["path"])
139
+ meta = read(path / "batch.json")
140
+ require(meta.get("workIsolationVersion") == 1, "LEGACY_BATCH_REQUIRES_ISOLATE_WORK")
141
+ # batch.json is the durable journal if a process exits before state.json commits.
142
+ if meta.get("identitySnapshot") is not None:
143
+ state.data["identities"] = meta["identitySnapshot"]
144
+ state.data["days"].update(meta.get("confirmations", {}))
145
+ if meta["phase"] == "confirmed":
146
+ state.data["active"] = None
147
+ return prepare(state, args)
148
+ same_owner = meta["ownerPid"] == args.owner_pid and meta["ownerProcess"] == owner
149
+ # A scheduled continuation retains its run ID but starts a new process.
150
+ if meta["runId"] != args.run_id or not same_owner:
151
+ require(not (process_identity(meta["ownerPid"]) == meta["ownerProcess"]), "BUSY: original Agent is still active")
152
+ require(args.recover_run_id == meta["runId"], "EXPLICIT_RECOVERY_REQUIRED")
153
+ meta.update(runId=args.run_id, generation=meta["generation"] + 1, ownerPid=args.owner_pid, ownerProcess=owner)
154
+ if "evidenceHash" in meta:
155
+ template(path, meta)
156
+ else:
157
+ job_screen.reconcile_revisions(state)
158
+ days = state.missing()[:state.c["maxDaysPerRun"]]
159
+ if days:
160
+ days = [d for d in days if (date(d) - date(days[0])).days < 366]
161
+ if not days:
162
+ return {"complete": True, "remainingDays": []}
163
+ batch = str(uuid.uuid4())
164
+ path = directory(directory(state.root / "batches") / batch)
165
+ meta = {"workIsolationVersion": 1, "id": batch, "phase": "prepared", "days": days, "runId": args.run_id, "generation": 1,
166
+ "ownerPid": args.owner_pid, "ownerProcess": owner, "sentDays": [], "pending": None}
167
+ state.data["active"] = batch
168
+ if args.recollect:
169
+ require(not meta.get("pending") and not meta["sentDays"], "PUBLISHED_OR_PENDING_BATCH_CANNOT_BE_RECOLLECTED")
170
+ meta.update(phase="prepared", generation=meta["generation"] + 1)
171
+ if args.thread_id not in state.data["excludedThreads"]:
172
+ state.data["excludedThreads"].append(args.thread_id)
173
+ # Persist ownership before subprocess collection, including interruptions.
174
+ state.save_batch(path, meta)
175
+ write(state.file, state.data)
176
+ if meta["phase"] in ("prepared", "collecting"):
177
+ meta["phase"] = "collecting"
178
+ state.save_batch(path, meta)
179
+ try:
180
+ collect(state, path, meta)
181
+ except Exception:
182
+ meta["phase"] = "collection-failed"
183
+ state.save_batch(path, meta)
184
+ raise
185
+ require(meta["phase"] != "collection-failed", "COLLECTION_INTERRUPTED: recover with a new run and --recollect")
186
+ state.save_batch(path, meta)
187
+ return {"batch": str(path), "runId": meta["runId"], "generation": meta["generation"], "phase": meta["phase"],
188
+ "template": str(path / "facts.template.json"), "receipts": meta.get("receipts"), "priorFacts": state.data["identities"],
189
+ "adapterReceipts": meta.get("adapterReceipts", {}),
190
+ "next": "reconcile" if meta.get("pending") else "publish" if meta["phase"] in ("validated", "pending") else "review/read then validate/publish", "remainingDays": state.missing()}
191
+
192
+
193
+ def confirm(state, path, meta, suiji):
194
+ pending = meta["pending"]
195
+ request, receipt = Path(pending["request"]), Path(pending["receipt"])
196
+ if request.exists():
197
+ intent = read(request)
198
+ require(digest(intent) == pending["hash"], "FROZEN_REQUEST_CHANGED")
199
+ recovery_args = SimpleNamespace(job_config=state.config_path, request=str(request), config=state.c["suiji"]["configPath"])
200
+ connection = json.loads(suiji.private_read(recovery_args.config))
201
+ frozen = suiji.work_records.recovery_intent(recovery_args, connection, suiji.Failure, suiji.private_read)
202
+ client, info = suiji.connect(connection, state.c["suiji"]["tokenFile"])
203
+ require(info.get("features", {}).get("workRecords"), "WORK_RECORDS_UNAVAILABLE")
204
+ if any(scope.startswith("screen:") for day in frozen["arguments"]["input"]["days"] for scope in day["requestedScopes"]):
205
+ require(info.get("features", {}).get("workScreenTimeline"), "SCREEN_TIMELINE_UNAVAILABLE")
206
+ try:
207
+ suiji.work_records.reconcile(client, str(request), frozen, suiji.Failure, suiji.run_intent)
208
+ except suiji.Failure as error:
209
+ if str(error) in ("WORK_FEED_CHANGED", "VERSION_CONFLICT"):
210
+ os.rename(request, str(request) + ".rejected")
211
+ meta["pending"] = None
212
+ if str(error) == "VERSION_CONFLICT":
213
+ meta["days"] = [d for d in meta["days"] if d not in meta["sentDays"]]
214
+ meta["sentDays"] = []
215
+ meta["phase"] = "collection-failed"
216
+ else:
217
+ meta["phase"] = "validated"
218
+ state.save_batch(path, meta)
219
+ raise
220
+ result = read(receipt)
221
+ require(result.get("importId"), "INVALID_IMPORT_RECEIPT")
222
+ # run_intent persisted this receipt only after its independent readback.
223
+ for day in pending["days"]:
224
+ state.data["days"][day] = {"complete": all(r["status"] == "complete" for r in meta["receipts"][day]), "receipts": meta["receipts"][day], "importId": result["importId"]}
225
+ if day in meta.get("screenRevisions", {}):
226
+ state.data["days"][day]["screenRevision"] = meta["screenRevisions"][day]
227
+ meta.setdefault("confirmations", {})[day] = state.data["days"][day]
228
+ if day not in meta["sentDays"]:
229
+ meta["sentDays"].append(day)
230
+ meta["pending"] = None
231
+ state.save_batch(path, meta)
232
+ return result
233
+
234
+
235
+ def publish(state, path, meta):
236
+ require(state.c["suiji"]["autoPublish"], "AUTO_PUBLISH_DISABLED")
237
+ require(meta["phase"] in ("validated", "pending"), "VALIDATED_BATCH_REQUIRED")
238
+ suiji = suiji_module()
239
+ job_isolation.assert_payload(read(path / "export.json"))
240
+ if meta.get("pending"):
241
+ confirm(state, path, meta, suiji)
242
+ payload = read(path / "export.json")
243
+ job_isolation.assert_payload(payload)
244
+ require(digest(payload) == meta["exportHash"], "VALIDATED_EXPORT_CHANGED")
245
+ connection = read(state.c["suiji"]["configPath"])
246
+ client, info = suiji.connect(connection, state.c["suiji"]["tokenFile"])
247
+ require(info.get("features", {}).get("workRecords"), "WORK_RECORDS_UNAVAILABLE")
248
+ require(info.get("features", {}).get("workSources"), "WORK_SOURCES_UNAVAILABLE")
249
+ if any(scope.startswith("screen:") for day in payload["days"] for scope in day["requestedScopes"]):
250
+ require(info.get("features", {}).get("workScreenTimeline"), "SCREEN_TIMELINE_UNAVAILABLE")
251
+ source_page = client.call("list_work_sources", {"from": meta["days"][0], "to": meta["days"][-1]})
252
+ for day in meta["days"]:
253
+ for scope in scopes(state.c, day):
254
+ require(any(s["sourceScope"] == scope and s["sourceNamespace"] == state.c["sourceNamespace"] and s["producerId"] == state.c["producerId"] and s["effectiveFrom"] <= day and (not s["effectiveTo"] or s["effectiveTo"] > day) for s in source_page["items"]), "SOURCE_NOT_REGISTERED")
255
+ conflicts = 0
256
+ while len(meta["sentDays"]) < len(meta["days"]):
257
+ remaining = {**payload, "days": [d for d in payload["days"] if d["date"] not in meta["sentDays"]]}
258
+ if "materialResults" in payload:
259
+ remaining["materialResults"] = [r for r in payload["materialResults"] if meta["materialDates"][r["recordId"]] not in meta["sentDays"]]
260
+ remaining["sourceCollectedAt"] = max((r["collectedAt"] for d in remaining["days"] for r in d["receipts"]), key=lambda v: dt.datetime.fromisoformat(v.replace("Z", "+00:00")))
261
+ attempt = directory(path / ("publish-" + str(uuid.uuid4())))
262
+ write(attempt / "input.json", remaining)
263
+ args = SimpleNamespace(input=str(attempt / "input.json"), out=str(attempt / "prepared"), config=state.c["suiji"]["configPath"])
264
+ prepared = suiji.work_records.prepare(client, args, suiji.Failure, suiji.private_read, suiji.exclusive_write)
265
+ value = read(prepared["prepared"])
266
+ receipt = attempt / "receipt.json"
267
+ request = attempt / "request.json"
268
+ intent = {"identity": value["identity"], "operation": "import_work_facts", "arguments": {"idempotencyKey": str(uuid.uuid4()), "input": value["payload"]}, "indexPath": value["indexPath"], "receiptPath": str(receipt)}
269
+ suiji.exclusive_write(request, json.dumps(intent, ensure_ascii=False).encode())
270
+ meta["pending"] = {"request": str(request), "receipt": str(receipt), "hash": digest(intent), "days": [d["date"] for d in value["payload"]["days"]]}
271
+ meta["phase"] = "pending"
272
+ state.save_batch(path, meta)
273
+ write(state.file, state.data)
274
+ try:
275
+ confirm(state, path, meta, suiji)
276
+ except suiji.Failure as error:
277
+ if str(error) != "WORK_FEED_CHANGED":
278
+ raise
279
+ conflicts += 1
280
+ if conflicts >= 3:
281
+ raise
282
+ time.sleep(conflicts)
283
+ meta["phase"] = "confirmed"
284
+ state.data["active"] = None
285
+ state.save_batch(path, meta)
286
+ return {"confirmedDays": meta["sentDays"], "remainingDays": state.missing(), "complete": not state.missing()}
287
+
288
+
289
+ def context(state, path, meta, args):
290
+ require(meta["phase"] == "reviewed" and state.c["sources"]["feishu"]["enabled"], "CONTEXT_NOT_ALLOWED")
291
+ require(args.date in meta["days"] and args.message_id, "CONTEXT_DAY_AND_SEED_REQUIRED")
292
+ source_dir = Path(meta["sourceDirs"][args.date])
293
+ bundle = source_dir / "feishu"
294
+ out = source_dir / ("context-" + str(uuid.uuid4()))
295
+ try:
296
+ command([sys.executable, str(Path(__file__).with_name("feishu.py")), "context", "--bundle", str(bundle), "--message-id", args.message_id, "--minutes", str(args.minutes), "--out", str(out)])
297
+ rows, receipt, identity = job_facts.feishu_rows(out, args.date, state.c)
298
+ require(identity and identity["openId"] == state.data["feishuIdentity"], "FEISHU_IDENTITY_CHANGED")
299
+ except (Failure, OSError, ValueError, KeyError, TypeError):
300
+ for r in meta["receipts"][args.date]:
301
+ if r["sourceScope"].startswith("feishu:"):
302
+ r.update(status="partial", collectedAt=stamp())
303
+ if meta.get("adapterReceipts", {}).get(args.date):
304
+ reports = adapter_receipts(state.c, args.date, job_facts.evidence(path, meta), meta["receipts"][args.date], meta.setdefault("sourceErrors", {}).setdefault(args.date, {}))
305
+ write(Path(meta["adapterReceipts"][args.date]), reports)
306
+ state.save_batch(path, meta)
307
+ raise
308
+ materials = job_facts.evidence(path, meta)
309
+ previous = {m["id"]: m for m in materials}
310
+ additions = {}
311
+ for row in rows:
312
+ existing = previous.get(row["id"], additions.get(row["id"]))
313
+ if existing is not None:
314
+ if any(value != row.get(key) for key, value in existing.items()):
315
+ for r in meta["receipts"][args.date]:
316
+ if r["sourceScope"].startswith("feishu:"):
317
+ r.update(status="partial", collectedAt=stamp())
318
+ if meta.get("adapterReceipts", {}).get(args.date):
319
+ reports = adapter_receipts(state.c, args.date, materials, meta["receipts"][args.date], meta.setdefault("sourceErrors", {}).setdefault(args.date, {}))
320
+ write(Path(meta["adapterReceipts"][args.date]), reports)
321
+ state.save_batch(path, meta)
322
+ raise Failure("FEISHU_MESSAGE_CHANGED: recollect instead of mixing snapshots")
323
+ else:
324
+ additions[row["id"]] = row
325
+ materials.extend(additions.values())
326
+ for r in meta["receipts"][args.date]:
327
+ if r["sourceScope"] == receipt["sourceScope"]:
328
+ r["collectedAt"] = receipt["collectedAt"]
329
+ if meta.get("adapterReceipts", {}).get(args.date):
330
+ reports = adapter_receipts(state.c, args.date, materials, meta["receipts"][args.date], meta.setdefault("sourceErrors", {}).setdefault(args.date, {}))
331
+ write(Path(meta["adapterReceipts"][args.date]), reports)
332
+ write(path / "evidence.json", materials)
333
+ meta["evidenceHash"] = digest(materials)
334
+ template(path, meta)
335
+ state.save_batch(path, meta)
336
+ return {"messages": len(rows), "template": str(path / "facts.template.json")}
337
+
338
+
339
+ def main():
340
+ os.umask(0o077)
341
+ parser = argparse.ArgumentParser(description=__doc__)
342
+ parser.add_argument("command", choices=["prepare", "review", "read", "context", "validate", "publish", "reconcile", "status", "migrate-sources", "image-read", "isolate-work"])
343
+ parser.add_argument("--config", required=True)
344
+ for name in ("batch", "facts", "run-id", "thread-id", "recover-run-id", "id", "date", "message-id", "next-config"):
345
+ parser.add_argument("--" + name)
346
+ parser.add_argument("--image-receipt")
347
+ parser.add_argument("--dry-run", action="store_true")
348
+ parser.add_argument("--verify-source", action="store_true")
349
+ parser.add_argument("--recollect", action="store_true")
350
+ parser.add_argument("--generation", type=int)
351
+ parser.add_argument("--owner-pid", type=int)
352
+ parser.add_argument("--offset", type=int, default=0)
353
+ parser.add_argument("--limit", type=int, default=30)
354
+ parser.add_argument("--max-chars", type=int, default=8000)
355
+ parser.add_argument("--minutes", type=int, default=30)
356
+ args = parser.parse_args()
357
+ c = config(args.config)
358
+ if args.command not in ("status", "isolate-work"):
359
+ require(read(c["suiji"]["configPath"]).get("mcpPath") == "/mcp/work", "WORK_ONLY_CREDENTIAL_REQUIRED")
360
+ require(not browser_enabled(c) and not materials_enabled(c), "BROWSING_SOURCES_MOVED: run isolate-work, then start a new work context")
361
+ if args.command == "status":
362
+ file = Path(c["stateDir"]) / "state.json"
363
+ stored = read(file) if file.exists() else {}
364
+ return {"producerId": c["producerId"], "sources": scopes(c), "days": stored.get("days", {}), "active": stored.get("active"), "batches": stored.get("batches", {})}
365
+ with State(c, migrating=args.command in ("migrate-sources", "isolate-work")) as state:
366
+ state.config_path = str(Path(args.config).resolve())
367
+ if args.command == "isolate-work":
368
+ return job_isolation.migrate(state, args.config, args.dry_run, suiji_module())
369
+ if args.command == "migrate-sources":
370
+ require(args.next_config, "NEXT_CONFIG_REQUIRED")
371
+ return migrate_sources(state, args.config, args.next_config, args.dry_run)
372
+ if args.command == "prepare":
373
+ return prepare(state, args)
374
+ require(args.batch and args.run_id and args.generation, "BATCH_OWNER_REQUIRED")
375
+ path, meta = state.batch(args)
376
+ require(meta.get("workIsolationVersion") == 1, "LEGACY_BATCH_REQUIRES_ISOLATE_WORK")
377
+ if args.command == "context":
378
+ return context(state, path, meta, args)
379
+ if args.command in ("review", "read", "image-read"):
380
+ materials = job_facts.evidence(path, meta)
381
+ require(args.offset >= 0 and 1 <= args.limit <= 100 and 1 <= args.max_chars <= 100000, "INVALID_READ_RANGE")
382
+ if args.command == "review":
383
+ selected = [m for m in materials if not args.date or m["day"] == args.date]
384
+ return {"total": len(selected), "items": [{k: v for k, v in m.items() if k not in ("text", "browserVisits", "browserBundle")} | {"preview": m["text"][:500], "truncated": len(m["text"]) > 500} for m in selected[args.offset:args.offset + args.limit]], "nextOffset": args.offset + args.limit if len(selected) > args.offset + args.limit else None}
385
+ m = next((m for m in materials if m["id"] == args.id), None)
386
+ require(m, "MATERIAL_NOT_FOUND")
387
+ require(args.command != "image-read" and not args.verify_source, "BROWSE_READ_REQUIRES_BROWSE_SKILL")
388
+ end = min(len(m["text"]), args.offset + args.max_chars)
389
+ require(args.offset <= len(m["text"]), "INVALID_READ_OFFSET")
390
+ seen = meta.setdefault("read", {}).get(m["id"], 0)
391
+ if args.offset <= seen:
392
+ meta["read"][m["id"]] = max(seen, end)
393
+ state.save_batch(path, meta)
394
+ return {**{k: v for k, v in m.items() if k not in ("text", "browserVisits", "browserBundle")}, "text": m["text"][args.offset:end], "offset": args.offset, "nextOffset": end if end < len(m["text"]) else None}
395
+ if args.command == "validate":
396
+ require(args.facts, "FACTS_REQUIRED")
397
+ result = job_facts.validate(path, meta, args.facts, state, suiji_module().work_records)
398
+ state.save_batch(path, meta)
399
+ return result
400
+ if args.command == "publish":
401
+ # The validated immutable export is the only publish input.
402
+ return publish(state, path, meta)
403
+ require(meta.get("pending"), "NO_PENDING_REQUEST")
404
+ require(c["suiji"]["autoPublish"], "AUTO_PUBLISH_DISABLED")
405
+ result = confirm(state, path, meta, suiji_module())
406
+ return {"receipt": result, "next": "publish to finish remaining days"}
407
+
408
+
409
+ if __name__ == "__main__":
410
+ try:
411
+ print(json.dumps(main(), ensure_ascii=False))
412
+ except Exception as error:
413
+ # Never echo provider stderr, tokens or private input bodies.
414
+ known = isinstance(error, Failure) or type(error).__module__ == "suiji"
415
+ print(json.dumps({"ok": False, "error": str(error) if known else type(error).__name__}), file=sys.stderr)
416
+ sys.exit(1)
@@ -0,0 +1,77 @@
1
+ """Read-only browser adapter: merge page visits locally, publish concise themes."""
2
+ import datetime as dt
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ from urllib.parse import urlsplit
7
+ from zoneinfo import ZoneInfo
8
+
9
+ from job_state import digest, read, require, stamp, window
10
+
11
+
12
+ MAX_THEMES = 3
13
+ MAX_THEME_CHARS = 160
14
+
15
+
16
+ def source_scope(c):
17
+ return "browser:" + c["producerId"] + ":observed"
18
+
19
+
20
+ def rows(bundle, day, c):
21
+ manifest = read(bundle / "manifest.json")
22
+ raw = (bundle / "visits.jsonl").read_bytes()
23
+ require(hashlib.sha256(raw).hexdigest() == manifest["materialsSha256"], "BROWSER_MATERIALS_CHANGED")
24
+ require(manifest["schemaVersion"] == 1 and manifest["coverage"] == "observed-only" and manifest["from"] == day and manifest["to"] == day and manifest["timeZone"] == c["timeZone"], "INVALID_BROWSER_MANIFEST")
25
+ require(Path(manifest["sourceRoot"]).resolve() == Path(c["sources"]["browser"]["root"]).resolve(), "BROWSER_ROOT_CHANGED")
26
+ expected = set(c["sources"]["browser"]["expectedSourceIds"])
27
+ receipts = manifest["receipts"]
28
+ require({r["sourceId"] for r in receipts} == expected and len(receipts) == len(expected), "BROWSER_SOURCE_SET_CHANGED")
29
+ require(all(r["readStatus"] in ("complete", "partial", "unavailable") for r in receipts), "INVALID_BROWSER_RECEIPTS")
30
+ groups, identities = {}, set()
31
+ for line in raw.decode().splitlines():
32
+ material = json.loads(line)
33
+ visit = material["visit"]
34
+ require(visit["sourceId"] in expected and material["id"] == visit["sourceId"] + ":" + visit["visitId"] and material["id"] not in identities, "INVALID_BROWSER_IDENTITY")
35
+ identities.add(material["id"])
36
+ require(visit["schemaVersion"] == 1 and visit["actor"] == "unknown", "INVALID_BROWSER_OBSERVATION")
37
+ url = urlsplit(visit["url"])
38
+ require(url.scheme in ("http", "https") and not (url.query or url.fragment or url.username or url.password), "UNSAFE_BROWSER_URL")
39
+ occurred = dt.datetime.fromisoformat(visit["visitedAt"].replace("Z", "+00:00"))
40
+ require(occurred.tzinfo and occurred.astimezone(ZoneInfo(c["timeZone"])).date().isoformat() == day, "BROWSER_DAY_MISMATCH")
41
+ groups.setdefault(visit["url"], []).append(material)
42
+ result = []
43
+ for url, visits in sorted(groups.items()):
44
+ visits.sort(key=lambda m: m["id"])
45
+ page_key = hashlib.sha256(url.encode()).hexdigest()
46
+ titles = sorted({m["visit"]["title"] for m in visits if m["visit"]["title"]})
47
+ text = json.dumps({"url": url, "titles": titles, "browsers": sorted({m["visit"]["browser"] for m in visits}), "visits": len(visits), "actor": "unknown", "coverage": "observed-only"}, ensure_ascii=False)
48
+ ref = {"channel": "browser-visit", "sourceScope": source_scope(c), "nativeId": "page:" + page_key, "hash": digest([m["visit"] for m in visits])}
49
+ result.append({"id": "browser:" + day + ":" + page_key, "day": day, "role": "observation", "text": text,
50
+ "anchor": source_scope(c) + ":" + day + ":" + page_key, "project": None,
51
+ "ref": ref, "browserBundle": str(bundle), "browserVisits": visits})
52
+ statuses = [r["readStatus"] for r in receipts]
53
+ status = "complete" if all(s == "complete" for s in statuses) else "unavailable" if all(s == "unavailable" for s in statuses) else "partial"
54
+ if result and status == "unavailable":
55
+ status = "partial"
56
+ receipt = {"sourceScope": source_scope(c), "status": status, "collectedAt": manifest["collectedAt"], **window(day)}
57
+ return result, receipt
58
+
59
+
60
+ def unavailable(day, c):
61
+ return {"sourceScope": source_scope(c), "status": "unavailable", "collectedAt": stamp(), **window(day)}
62
+
63
+
64
+ def compact_ref(materials):
65
+ """One digest-backed reference per theme; every original visit stays local."""
66
+ visits = sorted((v for m in materials for v in m["browserVisits"]), key=lambda v: v["id"])
67
+ require(len({v["id"] for v in visits}) == len(visits), "DUPLICATE_BROWSER_VISITS")
68
+ ref = {"channel": "browser-visit", "sourceScope": materials[0]["ref"]["sourceScope"],
69
+ "nativeId": "visits:" + digest([v["id"] for v in visits]), "hash": digest([v["visit"] for v in visits])}
70
+ return ref, {"materialIds": [m["id"] for m in materials], "visits": visits, "ref": ref}
71
+
72
+
73
+ def verify(material, c, command):
74
+ for visit in material["browserVisits"]:
75
+ response = command(c["sources"]["browser"]["collectorArgv"] + ["read", "--bundle", material["browserBundle"], "--id", visit["id"], "--verify-source"])
76
+ value = json.loads(response.stdout)
77
+ require(value.get("verifiedSource") is True and value["visit"] == visit["visit"] and value["locator"] == visit["locator"], "BROWSER_SOURCE_MISMATCH")
@@ -0,0 +1,132 @@
1
+ """Bind cross-channel candidate facts to collected, read evidence and stable keys."""
2
+ import copy
3
+ import json
4
+ from pathlib import Path
5
+ import uuid
6
+
7
+ from job_state import Failure, date, digest, read, require, scopes, window, write
8
+
9
+
10
+ def evidence(path, meta):
11
+ value = read(path / "evidence.json")
12
+ require(digest(value) == meta["evidenceHash"], "MATERIALS_CHANGED")
13
+ require(all(m.get("ref", {}).get("channel") not in ("browser-visit", "suiji-material") and not any(m.get("ref", {}).get("sourceScope", "").startswith(p) for p in ("browser:", "materials:", "browse:")) for m in value), "BROWSING_INPUT_REJECTED: start a new work context")
14
+ return value
15
+
16
+
17
+ def native_rows(bundle, c, day=None):
18
+ manifest = read(bundle / "manifest.json")
19
+ require(isinstance(manifest, dict), "INVALID_NATIVE_MANIFEST")
20
+ require(manifest.get("schemaVersion") == 1 and manifest.get("machineId") == c["producerId"] and manifest.get("timeZone") == c["timeZone"], "NATIVE_COLLECTION_IDENTITY_CHANGED")
21
+ require(manifest.get("from") == manifest.get("to") and (day is None or manifest["from"] == day), "NATIVE_COLLECTION_RANGE_CHANGED")
22
+ require(isinstance(manifest.get("sources"), list) and all(isinstance(r, dict) and r.get("status") in ("complete", "partial", "unavailable") for r in manifest["sources"]), "INVALID_NATIVE_RECEIPTS")
23
+ raw = (bundle / "materials.jsonl").read_bytes()
24
+ import hashlib
25
+ require(hashlib.sha256(raw).hexdigest() == manifest["materialsSha256"], "NATIVE_MATERIALS_CHANGED")
26
+ result = []
27
+ for line in raw.decode().splitlines():
28
+ m = json.loads(line)
29
+ require(m["day"] == manifest["from"], "NATIVE_DAY_MISMATCH")
30
+ require(hashlib.sha256(m["text"].encode()).hexdigest() == m["textSha256"], "NATIVE_TEXT_CHANGED")
31
+ scope = "agent:" + c["producerId"] + ":" + m["provider"]
32
+ project = m.get("project", {})
33
+ result.append({"id": "agent:" + m["id"], "day": m["day"], "role": m["role"], "text": m["text"],
34
+ "occurredAt": m["occurredAt"], "sourceVersion": m["textSha256"],
35
+ "anchor": scope + ":" + m["threadId"] + ":" + str(m.get("nativeId", m["locator"]["byteOffset"])),
36
+ "threadId": m["threadId"], "origin": m["origin"], "phase": m.get("phase"),
37
+ "project": c["projectAliases"].get(project.get("id"), project.get("name") if project.get("id") != "unclassified" else None),
38
+ "ref": {"channel": "local-agent", "sourceScope": scope, "nativeId": m["threadId"], "hash": m["textSha256"]}})
39
+ receipts = [{"sourceScope": "agent:" + c["producerId"] + ":" + r["provider"], "status": r["status"],
40
+ "collectedAt": manifest["collectedAt"], **window(manifest["from"])} for r in manifest["sources"]]
41
+ return result, receipts
42
+
43
+
44
+ def feishu_rows(bundle, day, c):
45
+ manifest = read(bundle / "manifest.json")
46
+ path = bundle / "messages.json"
47
+ rows = read(path) if path.exists() else []
48
+ if rows:
49
+ import hashlib
50
+ require(hashlib.sha256(path.read_bytes()).hexdigest() == manifest["messagesSha256"], "FEISHU_MATERIALS_CHANGED")
51
+ result = []
52
+ scope = "feishu:" + c["sources"]["feishu"]["accountScope"] + ":messages"
53
+ from feishu import stamp as message_stamp
54
+ from zoneinfo import ZoneInfo
55
+ for m in rows:
56
+ when = message_stamp(m["create_time"], ZoneInfo("Asia/Shanghai")).astimezone(ZoneInfo("Asia/Shanghai"))
57
+ occurred = when.date().isoformat()
58
+ require(occurred == day, "FEISHU_DAY_MISMATCH")
59
+ text = m.get("content", "")
60
+ text = text if isinstance(text, str) else json.dumps(text, ensure_ascii=False)
61
+ result.append({"id": "feishu:" + m["message_id"], "day": day, "role": "user", "text": text,
62
+ "occurredAt": when.isoformat(),
63
+ "author": m.get("sender", {}).get("id"), "anchor": scope + ":" + m["message_id"], "project": None,
64
+ "ref": {"channel": "feishu-message", "sourceScope": scope, "nativeId": m["message_id"], "hash": digest({"id": m["message_id"], "author": m.get("sender", {}).get("id"), "text": text, "day": occurred})}})
65
+ result[-1]["sourceVersion"] = result[-1]["ref"]["hash"]
66
+ receipt = {"sourceScope": scope, "status": "complete" if manifest["status"] == "complete" else "partial",
67
+ "collectedAt": manifest["collectedAt"], **window(day)}
68
+ return result, receipt, manifest.get("identity")
69
+
70
+
71
+ def validate(path, meta, candidate_path, state, work_records):
72
+ require(meta["phase"] not in ("pending", "confirmed"), "BATCH_FROZEN")
73
+ materials = evidence(path, meta)
74
+ candidates = read(candidate_path)
75
+ require(isinstance(candidates, dict) and set(candidates) >= {"batchId", "generation", "evidenceHash", "days"} and not set(candidates) - {"batchId", "generation", "evidenceHash", "days", "materialReviews"}, "INVALID_FACTS_FIELDS")
76
+ require(candidates["batchId"] == meta["id"] and candidates["generation"] == meta["generation"] and candidates["evidenceHash"] == meta["evidenceHash"], "FACTS_BATCH_CHANGED")
77
+ require([d["date"] for d in candidates["days"]] == meta["days"], "FACT_DAYS_MISMATCH")
78
+ by_id = {m["id"]: m for m in materials}
79
+ read_ids = meta.get("read", {})
80
+ require(all(read_ids.get(m["id"], 0) >= len(m["text"]) for m in materials if m["role"] != "tool"), "UNREAD_MESSAGES: use job read, including all continuation chunks")
81
+ identities = copy.deepcopy(state.data["identities"])
82
+ exported = []
83
+ for day in candidates["days"]:
84
+ require(set(day) == {"date", "items"} and isinstance(day["items"], list), "INVALID_FACT_DAY")
85
+ date(day["date"])
86
+ facts, used = [], set()
87
+ for item in day["items"]:
88
+ require(isinstance(item, dict) and set(item) >= {"title", "statement", "status", "project", "sourceIds"} and not set(item) - {"title", "statement", "status", "project", "sourceIds", "eventKey"}, "INVALID_CANDIDATE_FIELDS")
89
+ ids = item["sourceIds"]
90
+ require(isinstance(ids, list) and ids and len(set(ids)) == len(ids) and all(i in by_id for i in ids), "INVALID_SOURCE_IDS")
91
+ sources = [by_id[i] for i in ids]
92
+ require(all(m["day"] == day["date"] for m in sources), "FACT_DAY_MISMATCH")
93
+ require(all(read_ids.get(m["id"], 0) >= len(m["text"]) for m in sources), "UNREAD_FACT_SOURCE")
94
+ if all(m["ref"]["channel"] == "screen-timeline" for m in sources):
95
+ require(item["status"] in ("documented", "discussed"), "SCREEN_ONLY_IS_OBSERVATION")
96
+ if item["status"] == "verified":
97
+ require(any(m["role"] == "tool" and m["ref"]["channel"] == "local-agent" for m in sources), "VERIFIED_REQUIRES_TOOL_RECEIPT")
98
+ projects = {m["project"] for m in sources if m["ref"]["channel"] == "local-agent"}
99
+ if projects:
100
+ expected = next(iter(projects)) if len(projects) == 1 else None
101
+ require(item["project"] == expected, "PROJECT_SOURCE_MISMATCH")
102
+ anchors = {m["anchor"] for m in sources}
103
+ matching = {key for key, old in identities.items() if old["date"] == day["date"] and anchors.intersection(old["anchors"])}
104
+ if item.get("eventKey"):
105
+ matching.update(k for k, old in identities.items() if old["date"] == day["date"] and old["eventKey"] == item["eventKey"])
106
+ require(len(matching) <= 1, "AMBIGUOUS_FACT_MERGE")
107
+ key = next(iter(matching)) if matching else str(uuid.uuid4())
108
+ require(key not in used, "AMBIGUOUS_FACT_SPLIT")
109
+ used.add(key)
110
+ old = identities.get(key)
111
+ event = old["eventKey"] if old else item.get("eventKey") or str(uuid.uuid4())
112
+ require(not item.get("eventKey") or item["eventKey"] == event, "EVENT_IDENTITY_CHANGED")
113
+ identities[key] = {"date": day["date"], "eventKey": event, "externalKey": old["externalKey"] if old else str(uuid.uuid4()), "anchors": sorted(anchors | set(old["anchors"] if old else []))}
114
+ refs = {digest(m["ref"]): m["ref"] for m in sources}
115
+ facts.append({k: item[k] for k in ("title", "statement", "status", "project")} | {"eventKey": event, "externalKey": identities[key]["externalKey"], "sourceRefs": list(refs.values())})
116
+ old_keys = {k for k, v in state.data["identities"].items() if v["date"] == day["date"]}
117
+ # An omission cannot silently leave an earlier, now contradictory fact behind.
118
+ require(not all(r["status"] == "complete" for r in meta["receipts"][day["date"]]) or old_keys <= used, "OMITTED_PRIOR_FACT: preserve or explicitly resolve existing fact identities")
119
+ receipts = meta["receipts"][day["date"]]
120
+ exported.append({"date": day["date"], "requestedScopes": scopes(state.c, day["date"]), "receipts": receipts, "facts": facts})
121
+ payload = {"schemaVersion": 1, "sourceNamespace": state.c["sourceNamespace"], "sourceCollectedAt": max(r["collectedAt"] for d in exported for r in d["receipts"]), "days": exported}
122
+ require(not candidates.get("materialReviews"), "BROWSING_IS_NOT_WORK")
123
+ work_records.validate(payload, Failure, enforce_limits=False)
124
+ for day in exported:
125
+ single = {**payload, "days": [day], "sourceCollectedAt": max(r["collectedAt"] for r in day["receipts"])}
126
+ if "materialResults" in payload:
127
+ single["materialResults"] = [r for r in payload["materialResults"] if meta["materialDates"][r["recordId"]] == day["date"]]
128
+ work_records.validate(single, Failure)
129
+ write(path / "export.json", payload)
130
+ state.data["identities"] = identities
131
+ meta.update(phase="validated", exportHash=digest(payload), identitySnapshot=identities)
132
+ return {"valid": True, "facts": sum(len(d["facts"]) for d in exported), "export": str(path / "export.json")}