@runweave/server-linux-x64 0.1.7 → 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.
- package/manifest.json +1487 -156
- package/native.tar.gz +0 -0
- package/package.json +1 -1
- package/payload/.agents/plugins/marketplace.json +20 -0
- package/payload/app-server/build-info.json +3 -3
- package/payload/app-server/index.cjs +1 -1
- package/payload/backend/build-info.json +4 -4
- package/payload/backend/index.cjs +2416 -1684
- package/payload/backend/ios-simulators/pool.py +3 -3
- package/payload/backend/scheduled-tasks-sqlite-worker.cjs +224 -171
- package/payload/frontend/dist/assets/{_baseUniq-i1Ki3twP.js → _baseUniq-qHFga5V8.js} +1 -1
- package/payload/frontend/dist/assets/{access-DK9V0f3W.js → access-DgHvmn15.js} +1 -1
- package/payload/frontend/dist/assets/{advanced-B13lWv1d.js → advanced-C1Fh14vw.js} +1 -1
- package/payload/frontend/dist/assets/{appearance-DumRYmkD.js → appearance-Bs7cSXqO.js} +1 -1
- package/payload/frontend/dist/assets/{arc-HKn3_xyV.js → arc-E3iYHQG_.js} +1 -1
- package/payload/frontend/dist/assets/{architectureDiagram-Q4EWVU46-C09x7b94.js → architectureDiagram-Q4EWVU46-DgpYpTb9.js} +1 -1
- package/payload/frontend/dist/assets/{blockDiagram-DXYQGD6D-CFLFTR6d.js → blockDiagram-DXYQGD6D-CfFOEKZ7.js} +1 -1
- package/payload/frontend/dist/assets/{c4Diagram-AHTNJAMY-8P8Q-xqC.js → c4Diagram-AHTNJAMY-BxlHBUj3.js} +1 -1
- package/payload/frontend/dist/assets/channel-amxyNKnk.js +1 -0
- package/payload/frontend/dist/assets/{chunk-4BX2VUAB-B1KEkTal.js → chunk-4BX2VUAB-xWocgDo8.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-4TB4RGXK-Bu_Z47kJ.js → chunk-4TB4RGXK-Bl1cNICf.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-55IACEB6-xzHVCHyi.js → chunk-55IACEB6-DVtE836u.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-EDXVE4YY-h3HAW06h.js → chunk-EDXVE4YY-Dbd3tDCr.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-FMBD7UC4-CQ4faPSh.js → chunk-FMBD7UC4-MPJaTsyN.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-OYMX7WX6-CubmLoGT.js → chunk-OYMX7WX6-D3LuudlW.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-QZHKN3VN-xk0zKNja.js → chunk-QZHKN3VN-Dn1aF6iQ.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-YZCP3GAM-CHpTv8sd.js → chunk-YZCP3GAM-jZYbHnCw.js} +1 -1
- package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-B8cTijjT.js +1 -0
- package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-B8cTijjT.js +1 -0
- package/payload/frontend/dist/assets/clone-xzI2eGRI.js +1 -0
- package/payload/frontend/dist/assets/connection-V2iI-6rM.js +12 -0
- package/payload/frontend/dist/assets/{connections-A7InZw2T.js → connections-G_Q7342g.js} +1 -1
- package/payload/frontend/dist/assets/{cose-bilkent-S5V4N54A-CQn27vi_.js → cose-bilkent-S5V4N54A-75FLD3RS.js} +1 -1
- package/payload/frontend/dist/assets/{dagre-KV5264BT-CClOwB9v.js → dagre-KV5264BT-DEm-DpJL.js} +1 -1
- package/payload/frontend/dist/assets/{desktop-BgryfXXM.js → desktop-g5JC-CQd.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-5BDNPKRD-DcUJOAmV.js → diagram-5BDNPKRD-BV6P2UTy.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-G4DWMVQ6-DdabmTwk.js → diagram-G4DWMVQ6-DuCK-MM2.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-MMDJMWI5-DmcxjBIk.js → diagram-MMDJMWI5-D5_GlPgO.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-TYMM5635-BUSpXaiN.js → diagram-TYMM5635-QRbOYR1P.js} +1 -1
- package/payload/frontend/dist/assets/{erDiagram-SMLLAGMA-vkAXcv5_.js → erDiagram-SMLLAGMA-DefCheAw.js} +1 -1
- package/payload/frontend/dist/assets/{flowDiagram-DWJPFMVM-BHDU7BUJ.js → flowDiagram-DWJPFMVM-DVzyWIso.js} +1 -1
- package/payload/frontend/dist/assets/{ganttDiagram-T4ZO3ILL-C3lAG7_6.js → ganttDiagram-T4ZO3ILL-BJaClUEF.js} +1 -1
- package/payload/frontend/dist/assets/{gitGraphDiagram-UUTBAWPF-DHLyYJf7.js → gitGraphDiagram-UUTBAWPF-BLfbApNz.js} +1 -1
- package/payload/frontend/dist/assets/{graph-Bp5bIzXM.js → graph-C-YqFh68.js} +1 -1
- package/payload/frontend/dist/assets/{image-DkcKNLTV.js → image-CZo_2Awc.js} +1 -1
- package/payload/frontend/dist/assets/{index-BUS7FSlk.js → index-6q2aeqkt.js} +95 -105
- package/payload/frontend/dist/assets/{infoDiagram-42DDH7IO-CVp9zvmA.js → infoDiagram-42DDH7IO-BakVOutT.js} +1 -1
- package/payload/frontend/dist/assets/{ishikawaDiagram-UXIWVN3A-D07QPX3O.js → ishikawaDiagram-UXIWVN3A-D_1Oa5Dc.js} +1 -1
- package/payload/frontend/dist/assets/{journeyDiagram-VCZTEJTY-D-iZPDHM.js → journeyDiagram-VCZTEJTY-Y8TVFv6B.js} +1 -1
- package/payload/frontend/dist/assets/{kanban-definition-6JOO6SKY-BR4eWb08.js → kanban-definition-6JOO6SKY-DxeNhTIB.js} +1 -1
- package/payload/frontend/dist/assets/{layout-Cyv0DyJm.js → layout-5cMeOTAp.js} +1 -1
- package/payload/frontend/dist/assets/{linear-D5M7Pvj1.js → linear-Bnc3ev5g.js} +1 -1
- package/payload/frontend/dist/assets/main-3TPtEl66.js +706 -0
- package/payload/frontend/dist/assets/main-D4_uoZQa.css +33 -0
- package/payload/frontend/dist/assets/{min-BFlloFGg.js → min-BRXKkrS8.js} +1 -1
- package/payload/frontend/dist/assets/{mindmap-definition-QFDTVHPH-AKUF178o.js → mindmap-definition-QFDTVHPH-RYZO_BEr.js} +1 -1
- package/payload/frontend/dist/assets/{monitoring-B2okceCB.js → monitoring-Fv7kZ4s6.js} +1 -1
- package/payload/frontend/dist/assets/{notifications-0lql4_qJ.js → notifications-BzrYqyMH.js} +1 -1
- package/payload/frontend/dist/assets/{pieDiagram-DEJITSTG-CRiHmwn7.js → pieDiagram-DEJITSTG-Bzw0dhjz.js} +1 -1
- package/payload/frontend/dist/assets/{quadrantDiagram-34T5L4WZ-CzXrq7IJ.js → quadrantDiagram-34T5L4WZ-CQJY_msa.js} +1 -1
- package/payload/frontend/dist/assets/{record-content-DTCQKbEj.js → record-content-B7-6olQt.js} +1 -1
- package/payload/frontend/dist/assets/{requirementDiagram-MS252O5E-Bbn5ulLV.js → requirementDiagram-MS252O5E-DuP8hAV8.js} +1 -1
- package/payload/frontend/dist/assets/{sankeyDiagram-XADWPNL6-D9ZZ46pQ.js → sankeyDiagram-XADWPNL6-D-fQnCZZ.js} +1 -1
- package/payload/frontend/dist/assets/{sequenceDiagram-FGHM5R23-DxOP2gDa.js → sequenceDiagram-FGHM5R23-DaZNEccV.js} +1 -1
- package/payload/frontend/dist/assets/{stateDiagram-FHFEXIEX-NF_Qq5Cq.js → stateDiagram-FHFEXIEX-CkOLt5R1.js} +1 -1
- package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-atg1K4nB.js +1 -0
- package/payload/frontend/dist/assets/{status-2U71YpVe.js → status-CIIPajZp.js} +1 -1
- package/payload/frontend/dist/assets/suiji-C453W-Sg.js +1 -0
- package/payload/frontend/dist/assets/{svg-DpI3GSCw.js → svg-BUqAWGtg.js} +1 -1
- package/payload/frontend/dist/assets/{tabs-CYKuiiHg.js → tabs-Cwlp1Meu.js} +1 -1
- package/payload/frontend/dist/assets/{timeline-definition-GMOUNBTQ-ChEZSNF_.js → timeline-definition-GMOUNBTQ-BOKDMwHS.js} +1 -1
- package/payload/frontend/dist/assets/{vennDiagram-DHZGUBPP-Ch26SNbB.js → vennDiagram-DHZGUBPP-fSZjDS41.js} +1 -1
- package/payload/frontend/dist/assets/{wardley-RL74JXVD-D0ap5vv0.js → wardley-RL74JXVD-D1KIsvoD.js} +1 -1
- package/payload/frontend/dist/assets/{wardleyDiagram-NUSXRM2D-btdjsmHO.js → wardleyDiagram-NUSXRM2D-FMe67dwE.js} +1 -1
- package/payload/frontend/dist/assets/{xychartDiagram-5P7HB3ND-CklPtFGT.js → xychartDiagram-5P7HB3ND-B9AM9_9w.js} +1 -1
- package/payload/frontend/dist/build-info.json +4 -4
- package/payload/frontend/dist/companion.html +1 -1
- package/payload/frontend/dist/index.html +3 -3
- package/payload/packages/agent-bridge/src/install-pi.mjs +81 -0
- package/payload/plugins/pi/dist/SKILL.md +22 -0
- package/payload/plugins/pi/dist/bridge/app-server-client.cjs +82 -0
- package/payload/plugins/pi/dist/bridge/feishu_stop_notify.sh +169 -0
- package/payload/plugins/pi/dist/bridge/runweave-hook-bridge.cjs +583 -0
- package/payload/plugins/pi/dist/bridge/runweave-hook-completion.cjs +42 -0
- package/payload/plugins/pi/dist/bridge/runweave-hook-payload.cjs +578 -0
- package/payload/plugins/pi/dist/bridge/runweave-task-supervision.cjs +167 -0
- package/payload/plugins/pi/dist/runweave.js +316 -0
- package/payload/plugins/toolkit/.codex-plugin/plugin.json +31 -0
- package/payload/plugins/toolkit/README.md +185 -0
- package/payload/plugins/toolkit/hooks/AGENTS.md +6 -0
- package/payload/plugins/toolkit/hooks/app-server-client.cjs +82 -0
- package/payload/plugins/toolkit/hooks/feishu_stop_notify.sh +169 -0
- package/payload/plugins/toolkit/hooks/runweave-hook-bridge.cjs +583 -0
- package/payload/plugins/toolkit/hooks/runweave-hook-completion.cjs +42 -0
- package/payload/plugins/toolkit/hooks/runweave-hook-dispatch.cjs +123 -0
- package/payload/plugins/toolkit/hooks/runweave-hook-payload.cjs +578 -0
- package/payload/plugins/toolkit/hooks/runweave-task-supervision.cjs +167 -0
- package/payload/plugins/toolkit/hooks.json +108 -0
- package/payload/plugins/toolkit/skills/acceptance/SKILL.md +70 -0
- package/payload/plugins/toolkit/skills/acceptance/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/acceptance/references/capture.md +38 -0
- package/payload/plugins/toolkit/skills/acceptance/references/delivery.md +54 -0
- package/payload/plugins/toolkit/skills/acceptance/references/gallery-contract.md +84 -0
- package/payload/plugins/toolkit/skills/acceptance/references/gallery-copy.md +34 -0
- package/payload/plugins/toolkit/skills/acceptance/references/video.md +52 -0
- package/payload/plugins/toolkit/skills/acceptance/scripts/capture.py +137 -0
- package/payload/plugins/toolkit/skills/acceptance/scripts/common.py +63 -0
- package/payload/plugins/toolkit/skills/acceptance/scripts/gallery.py +353 -0
- package/payload/plugins/toolkit/skills/acceptance/scripts/preview.py +109 -0
- package/payload/plugins/toolkit/skills/acceptance/scripts/video.py +283 -0
- package/payload/plugins/toolkit/skills/acceptance/templates/reader.css +576 -0
- package/payload/plugins/toolkit/skills/acceptance/templates/reader.html +63 -0
- package/payload/plugins/toolkit/skills/acceptance/templates/reader.js +326 -0
- package/payload/plugins/toolkit/skills/acceptance/templates/viewer.css +55 -0
- package/payload/plugins/toolkit/skills/acceptance-gallery/SKILL.md +8 -0
- package/payload/plugins/toolkit/skills/acceptance-gallery/agents/openai.yaml +6 -0
- package/payload/plugins/toolkit/skills/acceptance-gallery/references/capture-and-copy.md +3 -0
- package/payload/plugins/toolkit/skills/acceptance-gallery/references/content-contract.md +3 -0
- package/payload/plugins/toolkit/skills/acceptance-gallery/scripts/gallery.py +9 -0
- package/payload/plugins/toolkit/skills/acceptance-video/SKILL.md +8 -0
- package/payload/plugins/toolkit/skills/acceptance-video/agents/openai.yaml +6 -0
- package/payload/plugins/toolkit/skills/acceptance-video/references/input-and-capture.md +3 -0
- package/payload/plugins/toolkit/skills/acceptance-video/references/mobile-review.md +3 -0
- package/payload/plugins/toolkit/skills/acceptance-video/scripts/capture.py +9 -0
- package/payload/plugins/toolkit/skills/acceptance-video/scripts/video.py +9 -0
- package/payload/plugins/toolkit/skills/agent-device/SKILL.md +86 -0
- package/payload/plugins/toolkit/skills/agent-device/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/agent-device/references/ios-setup.md +36 -0
- package/payload/plugins/toolkit/skills/agent-device/scripts/device.py +398 -0
- package/payload/plugins/toolkit/skills/agent-device/scripts/resources.py +248 -0
- package/payload/plugins/toolkit/skills/architecture-flow-prototyping/SKILL.md +128 -0
- package/payload/plugins/toolkit/skills/architecture-flow-prototyping/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-checklist.md +59 -0
- package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-guide.md +135 -0
- package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-handoff-template.md +57 -0
- package/payload/plugins/toolkit/skills/ask-first/SKILL.md +81 -0
- package/payload/plugins/toolkit/skills/brainstorming/SKILL.md +41 -0
- package/payload/plugins/toolkit/skills/browse-record/SKILL.md +35 -0
- package/payload/plugins/toolkit/skills/browse-record/references/app-usage.md +19 -0
- package/payload/plugins/toolkit/skills/browse-record/references/daily-reporting.md +115 -0
- package/payload/plugins/toolkit/skills/browse-record/references/zhihu.md +33 -0
- package/payload/plugins/toolkit/skills/browse-record/scripts/app_usage.py +110 -0
- package/payload/plugins/toolkit/skills/browse-record/scripts/browse_job.py +385 -0
- package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu-read.js +126 -0
- package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu_http.py +273 -0
- package/payload/plugins/toolkit/skills/clear-technical-explanations/LICENSE +21 -0
- package/payload/plugins/toolkit/skills/clear-technical-explanations/SKILL.md +103 -0
- package/payload/plugins/toolkit/skills/code-grounded-requirements/SKILL.md +54 -0
- package/payload/plugins/toolkit/skills/daily-doc-maintenance/SKILL.md +151 -0
- package/payload/plugins/toolkit/skills/daily-record/SKILL.md +63 -0
- package/payload/plugins/toolkit/skills/daily-record/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/daily-record/references/browser-history.md +6 -0
- package/payload/plugins/toolkit/skills/daily-record/references/feishu.md +90 -0
- package/payload/plugins/toolkit/skills/daily-record/references/scheduled-reporting.md +144 -0
- package/payload/plugins/toolkit/skills/daily-record/references/scheduled-task.template.json +15 -0
- package/payload/plugins/toolkit/skills/daily-record/references/suiji-work-records.md +57 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/feishu.py +280 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job.py +416 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_browser.py +77 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_facts.py +132 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_isolation.py +69 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_materials.py +154 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_screen.py +77 -0
- package/payload/plugins/toolkit/skills/daily-record/scripts/job_state.py +309 -0
- package/payload/plugins/toolkit/skills/daily-refactor/SKILL.md +101 -0
- package/payload/plugins/toolkit/skills/daily-refactor/scripts/compare-architecture-reports.mjs +168 -0
- package/payload/plugins/toolkit/skills/debugging-strategies/SKILL.md +38 -0
- package/payload/plugins/toolkit/skills/debugging-strategies/resources/implementation-playbook.md +520 -0
- package/payload/plugins/toolkit/skills/diagnostic-log-debugging/SKILL.md +79 -0
- package/payload/plugins/toolkit/skills/doc-coauthoring/SKILL.md +64 -0
- package/payload/plugins/toolkit/skills/eli5/LICENSE +21 -0
- package/payload/plugins/toolkit/skills/eli5/SKILL.md +18 -0
- package/payload/plugins/toolkit/skills/experience/SKILL.md +71 -0
- package/payload/plugins/toolkit/skills/experience/package.json +13 -0
- package/payload/plugins/toolkit/skills/git-advanced-workflows/SKILL.md +457 -0
- package/payload/plugins/toolkit/skills/github-pr/SKILL.md +109 -0
- package/payload/plugins/toolkit/skills/github-pr/scripts/github-access.mjs +292 -0
- package/payload/plugins/toolkit/skills/github-pr/scripts/preflight.mjs +191 -0
- package/payload/plugins/toolkit/skills/github-pr/scripts/source-worktree.mjs +354 -0
- package/payload/plugins/toolkit/skills/grill-with-docs/SKILL.md +66 -0
- package/payload/plugins/toolkit/skills/grill-with-docs/agents/openai.yaml +6 -0
- package/payload/plugins/toolkit/skills/grilling/SKILL.md +14 -0
- package/payload/plugins/toolkit/skills/handoff/SKILL.md +98 -0
- package/payload/plugins/toolkit/skills/karpathy-guidelines/SKILL.md +73 -0
- package/payload/plugins/toolkit/skills/playwright-cli/SKILL.md +562 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/element-attributes.md +23 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/request-mocking.md +87 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/running-code.md +240 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/session-management.md +226 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/spec-driven-testing.md +312 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/storage-state.md +275 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/test-generation.md +138 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/tracing.md +142 -0
- package/payload/plugins/toolkit/skills/playwright-cli/references/video-recording.md +157 -0
- package/payload/plugins/toolkit/skills/playwright-cli/templates/standalone.config.json +7 -0
- package/payload/plugins/toolkit/skills/product-exploration/SKILL.md +64 -0
- package/payload/plugins/toolkit/skills/product-exploration/agents/openai.yaml +5 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/SKILL.md +134 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/README.md +88 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/app.js +145 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/index.html +229 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/mock-state.json +60 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/implementation-plan-handoff-template.md +61 -0
- package/payload/plugins/toolkit/skills/prototype-first-development/references/prototype-workflow-checklist.md +64 -0
- package/payload/plugins/toolkit/skills/publish-runweave-server/SKILL.md +64 -0
- package/payload/plugins/toolkit/skills/publish-runweave-server/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/react-best-practices/AGENTS.md +3227 -0
- package/payload/plugins/toolkit/skills/react-best-practices/README.md +127 -0
- package/payload/plugins/toolkit/skills/react-best-practices/SKILL.md +143 -0
- package/payload/plugins/toolkit/skills/react-best-practices/metadata.json +15 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/event-handler-refs.md +55 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/init-once.md +42 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/use-latest.md +39 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/async/api-routes.md +38 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/async/defer-await.md +80 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/async/dependencies.md +52 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/async/parallel.md +28 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/async/suspense-boundaries.md +99 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/barrel-imports.md +59 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/conditional.md +37 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/defer-third-party.md +49 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/dynamic-imports.md +35 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/preload.md +46 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/client/event-listeners.md +78 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/client/localstorage-schema.md +77 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/client/passive-event-listeners.md +48 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/client/swr-dedup.md +56 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/batch-dom-css.md +110 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-function-results.md +80 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-property-access.md +28 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-storage.md +70 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/combine-iterations.md +32 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/early-exit.md +50 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/flatmap-filter.md +55 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/hoist-regexp.md +45 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/index-maps.md +37 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/length-check-first.md +50 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/min-max-loop.md +82 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/set-map-lookups.md +24 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/js/tosorted-immutable.md +57 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/sections.md +46 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/template.md +28 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/activity.md +26 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/animate-svg-wrapper.md +38 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/conditional-render.md +32 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/content-visibility.md +38 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hoist-jsx.md +36 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-no-flicker.md +72 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-suppress-warning.md +26 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/resource-hints.md +89 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/script-defer-async.md +71 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/svg-precision.md +28 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/usetransition-loading.md +75 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/defer-reads.md +39 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/dependencies.md +45 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state-no-effect.md +40 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state.md +29 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/functional-setstate.md +77 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/lazy-state-init.md +58 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo-with-default-value.md +36 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo.md +44 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/move-effect-to-event.md +45 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/no-inline-components.md +83 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/simple-expression-in-memo.md +35 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/transitions.md +40 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/use-ref-transient-values.md +73 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/after-nonblocking.md +74 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/auth-actions.md +96 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-lru.md +41 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-react.md +76 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/dedup-props.md +65 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/hoist-static-io.md +139 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/parallel-fetching.md +83 -0
- package/payload/plugins/toolkit/skills/react-best-practices/rules/server/serialization.md +38 -0
- package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/SKILL.md +8 -0
- package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/agents/openai.yaml +6 -0
- package/payload/plugins/toolkit/skills/reproduce-before-fix/SKILL.md +93 -0
- package/payload/plugins/toolkit/skills/reproduce-before-fix/agents/openai.yaml +7 -0
- package/payload/plugins/toolkit/skills/review-only/SKILL.md +72 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/SKILL.md +134 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/agents/openai.yaml +7 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/references/contracts.md +114 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/classify-runtime-verdict.mjs +140 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/run-static-review.mjs +389 -0
- package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/static-review-contract.mjs +243 -0
- package/payload/plugins/toolkit/skills/run-test-cases/SKILL.md +63 -0
- package/payload/plugins/toolkit/skills/runweave-change-validation/SKILL.md +196 -0
- package/payload/plugins/toolkit/skills/runweave-change-validation/agents/openai.yaml +7 -0
- package/payload/plugins/toolkit/skills/runweave-change-validation/scripts/assert-dev-session-status.mjs +181 -0
- package/payload/plugins/toolkit/skills/runweave-dev-session/SKILL.md +126 -0
- package/payload/plugins/toolkit/skills/runweave-dev-session/agents/openai.yaml +7 -0
- package/payload/plugins/toolkit/skills/runweave-management/SKILL.md +47 -0
- package/payload/plugins/toolkit/skills/scheduled-task/SKILL.md +35 -0
- package/payload/plugins/toolkit/skills/shadcn/SKILL.md +245 -0
- package/payload/plugins/toolkit/skills/shadcn/agents/openai.yml +5 -0
- package/payload/plugins/toolkit/skills/shadcn/assets/shadcn-small.png +0 -0
- package/payload/plugins/toolkit/skills/shadcn/assets/shadcn.png +0 -0
- package/payload/plugins/toolkit/skills/shadcn/cli.md +276 -0
- package/payload/plugins/toolkit/skills/shadcn/customization.md +209 -0
- package/payload/plugins/toolkit/skills/shadcn/evals/evals.json +47 -0
- package/payload/plugins/toolkit/skills/shadcn/mcp.md +94 -0
- package/payload/plugins/toolkit/skills/shadcn/rules/base-vs-radix.md +310 -0
- package/payload/plugins/toolkit/skills/shadcn/rules/composition.md +197 -0
- package/payload/plugins/toolkit/skills/shadcn/rules/forms.md +198 -0
- package/payload/plugins/toolkit/skills/shadcn/rules/icons.md +101 -0
- package/payload/plugins/toolkit/skills/shadcn/rules/styling.md +163 -0
- package/payload/plugins/toolkit/skills/suiji/SKILL.md +54 -0
- package/payload/plugins/toolkit/skills/suiji/references/api.md +72 -0
- package/payload/plugins/toolkit/skills/suiji/references/source-adapter.md +40 -0
- package/payload/plugins/toolkit/skills/suiji/references/work-records.md +36 -0
- package/payload/plugins/toolkit/skills/suiji/scripts/source_adapter.py +46 -0
- package/payload/plugins/toolkit/skills/suiji/scripts/suiji.py +358 -0
- package/payload/plugins/toolkit/skills/suiji/scripts/work_records.py +328 -0
- package/payload/plugins/toolkit/skills/suiji/scripts/work_reports.py +75 -0
- package/payload/plugins/toolkit/skills/sync-branch-hard-reset/SKILL.md +59 -0
- package/payload/plugins/toolkit/skills/update-runweave-backend/SKILL.md +90 -0
- package/payload/plugins/toolkit/skills/update-runweave-backend/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/update-runweave-backend/references/npm-update.md +68 -0
- package/payload/plugins/toolkit/skills/update-runweave-backend/references/systemd.md +34 -0
- package/payload/plugins/toolkit/skills/update-runweave-desktop/SKILL.md +102 -0
- package/payload/plugins/toolkit/skills/update-runweave-ios/SKILL.md +16 -0
- package/payload/plugins/toolkit/skills/update-runweave-ios/references/ios-wireless-recovery.md +54 -0
- package/payload/plugins/toolkit/skills/using-rw/SKILL.md +265 -0
- package/payload/plugins/toolkit/skills/write-plan/SKILL.md +72 -0
- package/payload/plugins/toolkit/skills/write-test-cases/SKILL.md +100 -0
- package/payload/plugins/toolkit/skills/write-test-cases/assets/test-plan-template.yaml +15 -0
- package/payload/plugins/toolkit/skills/write-test-cases/references/coverage-checklist.md +75 -0
- package/payload/plugins/toolkit/skills/write-test-cases/references/design-techniques.md +91 -0
- package/payload/plugins/toolkit/skills/youtube-reading/SKILL.md +35 -0
- package/payload/plugins/toolkit/skills/youtube-reading/agents/openai.yaml +4 -0
- package/payload/plugins/toolkit/skills/youtube-reading/assets/runtime-lock.json +92 -0
- package/payload/plugins/toolkit/skills/youtube-reading/references/content-contract.md +52 -0
- package/payload/plugins/toolkit/skills/youtube-reading/references/setup.md +70 -0
- package/payload/plugins/toolkit/skills/youtube-reading/requirements.lock +9 -0
- package/payload/plugins/toolkit/skills/youtube-reading/scripts/artifacts.py +206 -0
- package/payload/plugins/toolkit/skills/youtube-reading/scripts/runtime.py +424 -0
- package/payload/plugins/toolkit/skills/youtube-reading/scripts/youtube_reading.py +314 -0
- package/payload/plugins/toolkit-release.json +1 -0
- package/payload/scripts/toolkit/codex-state.mjs +75 -0
- package/payload/scripts/toolkit/release.mjs +210 -0
- package/payload/scripts/toolkit/sync-runtime.mjs +108 -0
- package/payload/scripts/toolkit/sync.mjs +585 -0
- package/payload/frontend/dist/assets/channel-CuSisha8.js +0 -1
- package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-ClJkT7PY.js +0 -1
- package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-ClJkT7PY.js +0 -1
- package/payload/frontend/dist/assets/clone-CPcyXi9g.js +0 -1
- package/payload/frontend/dist/assets/connection-BhcgnOMb.js +0 -12
- package/payload/frontend/dist/assets/main-BDBhLSX2.js +0 -696
- package/payload/frontend/dist/assets/main-Cg6Oo1xK.css +0 -33
- package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-DhdOfEAv.js +0 -1
- package/payload/frontend/dist/assets/suiji-DXtfgwxj.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")}
|