@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,69 @@
|
|
|
1
|
+
"""Retire legacy browsing inputs without replaying unknown work writes."""
|
|
2
|
+
import copy
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from job_state import require, read, write, binding, process_identity, digest
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def blocked_scope(value):
|
|
8
|
+
return any(value.startswith(p) for p in ("browser:", "materials:", "browse:"))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def assert_payload(payload):
|
|
12
|
+
require(not payload.get("materialResults") and not any(
|
|
13
|
+
any(blocked_scope(s) for s in day.get("requestedScopes", [])) or
|
|
14
|
+
any(ref.get("channel") in ("browser-visit", "suiji-material") or blocked_scope(ref.get("sourceScope", ""))
|
|
15
|
+
for fact in day.get("facts", []) for ref in fact.get("sourceRefs", []))
|
|
16
|
+
for day in payload.get("days", [])), "BROWSING_IS_NOT_WORK: retire the old batch with isolate-work")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def migrate(state, config_path, dry_run, suiji):
|
|
20
|
+
require(not (state.root / "source-migration.json").exists(), "FINISH_SOURCE_MIGRATION_FIRST")
|
|
21
|
+
journal = state.root / "work-isolation.json"
|
|
22
|
+
if journal.exists():
|
|
23
|
+
saved = read(journal)
|
|
24
|
+
require(saved["configPath"] == str(Path(config_path).resolve()), "ISOLATION_TARGET_CHANGED")
|
|
25
|
+
original_state = read(state.root / "before-work-isolation-state.json")
|
|
26
|
+
original_config = read(state.root / "before-work-isolation-config.json")
|
|
27
|
+
require(state.data.get("binding") in (saved["state"]["binding"], original_state["binding"]) and state.c in (saved["config"], original_config), "ISOLATION_IDENTITY_CHANGED")
|
|
28
|
+
if not dry_run:
|
|
29
|
+
write(state.file, saved["state"])
|
|
30
|
+
write(config_path, saved["config"])
|
|
31
|
+
state.data = saved["state"]
|
|
32
|
+
return {"dryRun": dry_run, "isolated": not dry_run, "newContextRequired": True}
|
|
33
|
+
updated = copy.deepcopy(state.data)
|
|
34
|
+
config = copy.deepcopy(state.c)
|
|
35
|
+
for key in ("browser", "materials"):
|
|
36
|
+
config["sources"].pop(key, None)
|
|
37
|
+
active = updated.get("active")
|
|
38
|
+
receipt = None
|
|
39
|
+
if active:
|
|
40
|
+
path = Path(updated["batches"][active]["path"])
|
|
41
|
+
meta = read(path / "batch.json")
|
|
42
|
+
require(process_identity(meta["ownerPid"]) != meta["ownerProcess"], "BUSY: original Agent is still active")
|
|
43
|
+
pending = meta.get("pending")
|
|
44
|
+
if pending:
|
|
45
|
+
original = read(pending["request"])
|
|
46
|
+
require(digest(original) == pending["hash"], "FROZEN_REQUEST_CHANGED")
|
|
47
|
+
connection = read(config["suiji"]["configPath"])
|
|
48
|
+
client, _ = suiji.connect(connection, config["suiji"]["tokenFile"])
|
|
49
|
+
# This endpoint compares the original immutable operation hash and returns no fact bodies.
|
|
50
|
+
receipt = client.call("get_work_import_receipt", {"idempotencyKey": original["arguments"]["idempotencyKey"], "input": original["arguments"]["input"]})
|
|
51
|
+
updated["batches"][active] = {**updated["batches"][active], "phase": "retired-browsing", "receipt": receipt}
|
|
52
|
+
updated["active"] = None
|
|
53
|
+
updated["identities"] = {key: value for key, value in updated["identities"].items()
|
|
54
|
+
if not any(blocked_scope(a) for a in value.get("anchors", []))}
|
|
55
|
+
updated["forbiddenWorkThreads"] = updated.get("excludedThreads", [])
|
|
56
|
+
updated["workIsolationVersion"] = 1
|
|
57
|
+
updated["binding"] = binding(config)
|
|
58
|
+
for day in updated["days"].values():
|
|
59
|
+
day["receipts"] = [r for r in day.get("receipts", []) if not blocked_scope(r["sourceScope"])]
|
|
60
|
+
day["complete"] = bool(day["receipts"]) and all(r["status"] == "complete" for r in day["receipts"])
|
|
61
|
+
result = {"dryRun": dry_run, "retiredBatch": active, "retainedIdentities": len(updated["identities"]), "receipt": receipt, "newContextRequired": True}
|
|
62
|
+
if not dry_run:
|
|
63
|
+
write(state.root / "before-work-isolation-state.json", state.data)
|
|
64
|
+
write(state.root / "before-work-isolation-config.json", state.c)
|
|
65
|
+
write(journal, {"configPath": str(Path(config_path).resolve()), "state": updated, "config": config})
|
|
66
|
+
write(state.file, updated)
|
|
67
|
+
write(config_path, config)
|
|
68
|
+
state.data = updated
|
|
69
|
+
return result
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Opt-in user supplied materials, with separate scan and consumption checkpoints."""
|
|
2
|
+
import base64
|
|
3
|
+
import copy
|
|
4
|
+
import hashlib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from job_state import directory, read, require, stamp, window, write, materials_enabled
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def client_for(c, suiji):
|
|
10
|
+
connection = read(c["suiji"]["configPath"])
|
|
11
|
+
client, info = suiji.connect(connection, c["suiji"]["tokenFile"])
|
|
12
|
+
require(info.get("features", {}).get("materials") and info.get("features", {}).get("workMaterials"), "MATERIALS_UNAVAILABLE")
|
|
13
|
+
require(c["sources"]["materials"]["accountScope"] == info["serverId"] + ":" + info["ownerId"], "MATERIAL_ACCOUNT_MISMATCH")
|
|
14
|
+
return client
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def scope(c):
|
|
18
|
+
return "materials:" + c["sources"]["materials"]["accountScope"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def reconcile(state, suiji):
|
|
22
|
+
if not materials_enabled(state.c):
|
|
23
|
+
return
|
|
24
|
+
client = client_for(state.c, suiji)
|
|
25
|
+
snapshot = copy.deepcopy(state.data.get("materials", {"cursor": None, "records": {}}))
|
|
26
|
+
cursor, changed = snapshot["cursor"], set()
|
|
27
|
+
while True:
|
|
28
|
+
page = client.call("list_changes", {"limit": 50, **({"cursor": cursor} if cursor else {})})
|
|
29
|
+
for item in page["items"]:
|
|
30
|
+
if item["kind"] == "followup_added":
|
|
31
|
+
continue
|
|
32
|
+
changed.add(item["recordId"])
|
|
33
|
+
require(page["nextCursor"] != cursor or not page["hasMore"], "MATERIAL_CHANGE_CURSOR_STUCK")
|
|
34
|
+
cursor = page["nextCursor"]
|
|
35
|
+
if not page["hasMore"]:
|
|
36
|
+
break
|
|
37
|
+
affected = set()
|
|
38
|
+
for record_id in sorted(changed):
|
|
39
|
+
old = snapshot["records"].get(record_id)
|
|
40
|
+
if old:
|
|
41
|
+
affected.add(old["material"]["materialDate"])
|
|
42
|
+
try:
|
|
43
|
+
record = client.call("get_record", {"recordId": record_id})["record"]
|
|
44
|
+
except suiji.Failure as error:
|
|
45
|
+
if str(error) != "NOT_FOUND":
|
|
46
|
+
raise
|
|
47
|
+
record = None
|
|
48
|
+
if record and record.get("material") and not record.get("deletedAt"):
|
|
49
|
+
snapshot["records"][record_id] = record
|
|
50
|
+
affected.add(record["material"]["materialDate"])
|
|
51
|
+
else:
|
|
52
|
+
snapshot["records"].pop(record_id, None)
|
|
53
|
+
snapshot["cursor"] = cursor
|
|
54
|
+
state.data["materials"] = snapshot
|
|
55
|
+
for day in affected:
|
|
56
|
+
if materials_enabled(state.c, day):
|
|
57
|
+
state.data["days"].setdefault(day, {})["complete"] = False
|
|
58
|
+
# Index and cursor commit atomically. Consumption is only confirmed after publish readback.
|
|
59
|
+
write(state.file, state.data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def rows(state, day, bundle, suiji):
|
|
63
|
+
client = client_for(state.c, suiji)
|
|
64
|
+
directory(bundle)
|
|
65
|
+
result, complete = [], True
|
|
66
|
+
for expected in state.data.get("materials", {}).get("records", {}).values():
|
|
67
|
+
if expected["material"]["materialDate"] != day:
|
|
68
|
+
continue
|
|
69
|
+
record = client.call("get_record", {"recordId": expected["id"]})["record"]
|
|
70
|
+
material = record.get("material")
|
|
71
|
+
require(material and not record.get("deletedAt") and material["sourceHash"] == expected["material"]["sourceHash"], "MATERIAL_VERSION_CHANGED: recollect")
|
|
72
|
+
images, image_error = [], False
|
|
73
|
+
for attachment in record["attachments"]:
|
|
74
|
+
if attachment["kind"] != "image":
|
|
75
|
+
# First release consumes image/text/link only. Do not claim an unread attachment.
|
|
76
|
+
image_error = True
|
|
77
|
+
continue
|
|
78
|
+
try:
|
|
79
|
+
value = client.call("read_attachment", {"attachmentId": attachment["id"]})
|
|
80
|
+
blocks = [b for b in value.get("content", []) if b.get("type") == "image"]
|
|
81
|
+
metadata = value.get("structuredContent", {})
|
|
82
|
+
require(len(blocks) == 1 and metadata.get("attachmentId") == attachment["id"], "MATERIAL_IMAGE_IDENTITY_MISMATCH")
|
|
83
|
+
data = base64.b64decode(blocks[0]["data"], validate=True)
|
|
84
|
+
sha = hashlib.sha256(data).hexdigest()
|
|
85
|
+
require(len(data) == attachment["byteSize"] and blocks[0]["mimeType"] == attachment["mimeType"] and metadata.get("sha256") == sha, "MATERIAL_IMAGE_CONTENT_MISMATCH")
|
|
86
|
+
path = bundle / (attachment["id"] + (".png" if attachment["mimeType"] == "image/png" else ".jpg"))
|
|
87
|
+
with path.open("wb") as stream:
|
|
88
|
+
stream.write(data)
|
|
89
|
+
path.chmod(0o600)
|
|
90
|
+
images.append({"attachmentId": attachment["id"], "path": str(path), "sha256": sha})
|
|
91
|
+
except Exception:
|
|
92
|
+
image_error = True
|
|
93
|
+
current = client.call("get_record", {"recordId": record["id"]})["record"]
|
|
94
|
+
require(current.get("material", {}).get("sourceHash") == material["sourceHash"], "MATERIAL_VERSION_CHANGED: recollect")
|
|
95
|
+
complete = complete and not image_error
|
|
96
|
+
result.append({"id": "material:" + record["id"], "day": day, "role": "user", "text": record["body"],
|
|
97
|
+
"anchor": scope(state.c) + ":" + record["id"], "project": None, "images": images, "imageError": image_error,
|
|
98
|
+
"material": material, "ref": {"channel": "suiji-material", "sourceScope": scope(state.c),
|
|
99
|
+
"nativeId": record["id"], "revision": str(record["version"]), "hash": material["sourceHash"]}})
|
|
100
|
+
return result, {"sourceScope": scope(state.c), "status": "complete" if complete else "partial", "collectedAt": stamp(), **window(day)}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def acknowledge_image(path, meta, material, receipt_path):
|
|
104
|
+
"""Executor calls only AFTER passing the actual image to its vision-capable model."""
|
|
105
|
+
require(material["ref"]["channel"] == "suiji-material" and material["images"], "IMAGE_MATERIAL_REQUIRED")
|
|
106
|
+
receipt = read(receipt_path)
|
|
107
|
+
require(set(receipt) == {"materialId", "sourceHash", "reader", "toolCallId", "images", "observation"}, "INVALID_IMAGE_READ_RECEIPT")
|
|
108
|
+
require(receipt["materialId"] == material["id"] and receipt["sourceHash"] == material["ref"]["hash"], "IMAGE_READ_VERSION_MISMATCH")
|
|
109
|
+
require(all(isinstance(receipt[k], str) and receipt[k].strip() for k in ("reader", "toolCallId", "observation")), "ACTUAL_VISION_READ_REQUIRED")
|
|
110
|
+
expected = [{"attachmentId": i["attachmentId"], "sha256": i["sha256"]} for i in material["images"]]
|
|
111
|
+
require(receipt["images"] == expected, "IMAGE_READ_IDENTITY_MISMATCH")
|
|
112
|
+
for item in material["images"]:
|
|
113
|
+
require(hashlib.sha256(Path(item["path"]).read_bytes()).hexdigest() == item["sha256"], "IMAGE_FILE_CHANGED")
|
|
114
|
+
meta.setdefault("imageReads", {})[material["id"]] = receipt
|
|
115
|
+
write(path / (material["ref"]["nativeId"] + "-image-read.json"), receipt)
|
|
116
|
+
return {"acknowledged": material["id"], "sourceHash": material["ref"]["hash"]}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def results(materials, candidates, meta, exported):
|
|
120
|
+
source = {m["id"]: m for m in materials if m["ref"]["channel"] == "suiji-material"}
|
|
121
|
+
reviews = candidates.get("materialReviews", [])
|
|
122
|
+
require(isinstance(reviews, list) and len(reviews) == len(source) and {r.get("id") for r in reviews} == set(source), "REVIEW_EVERY_MATERIAL: include deferred when vision is unavailable")
|
|
123
|
+
output = []
|
|
124
|
+
for review in reviews:
|
|
125
|
+
require(set(review) == {"id", "outcome", "reason"} and review["outcome"] in ("included", "merged", "no-new-fact", "deferred") and isinstance(review["reason"], str) and review["reason"].strip(), "INVALID_MATERIAL_REVIEW")
|
|
126
|
+
m = source[review["id"]]
|
|
127
|
+
facts = [f for day in exported for f in day["facts"] if any(r["channel"] == "suiji-material" and r["nativeId"] == m["ref"]["nativeId"] for r in f["sourceRefs"])]
|
|
128
|
+
if review["outcome"] == "deferred":
|
|
129
|
+
require(not facts, "DEFERRED_MATERIAL_CANNOT_PROVE_FACT")
|
|
130
|
+
for day in exported:
|
|
131
|
+
if day["date"] == m["day"]:
|
|
132
|
+
for r in day["receipts"]:
|
|
133
|
+
if r["sourceScope"] == m["ref"]["sourceScope"]:
|
|
134
|
+
r["status"] = "partial"
|
|
135
|
+
continue
|
|
136
|
+
require(not m["imageError"], "MATERIAL_ATTACHMENT_UNREAD")
|
|
137
|
+
if m["images"]:
|
|
138
|
+
receipt = meta.get("imageReads", {}).get(m["id"], {})
|
|
139
|
+
require(receipt.get("sourceHash") == m["ref"]["hash"], "ACTUAL_VISION_READ_REQUIRED: text read does not read images")
|
|
140
|
+
for image in m["images"]:
|
|
141
|
+
require(hashlib.sha256(Path(image["path"]).read_bytes()).hexdigest() == image["sha256"], "IMAGE_FILE_CHANGED")
|
|
142
|
+
require(bool(facts) == (review["outcome"] != "no-new-fact"), "MATERIAL_OUTCOME_FACT_MISMATCH")
|
|
143
|
+
output.append({"recordId": m["ref"]["nativeId"], "recordVersion": int(m["ref"]["revision"]), "sourceHash": m["ref"]["hash"],
|
|
144
|
+
"outcome": review["outcome"], "eventKeys": sorted({f["eventKey"] for f in facts})})
|
|
145
|
+
return output
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def verify_versions(c, payload, suiji):
|
|
149
|
+
if not payload.get("materialResults"):
|
|
150
|
+
return
|
|
151
|
+
client = client_for(c, suiji)
|
|
152
|
+
for item in payload["materialResults"]:
|
|
153
|
+
record = client.call("get_record", {"recordId": item["recordId"]})["record"]
|
|
154
|
+
require(record.get("material", {}).get("sourceHash") == item["sourceHash"] and record["version"] == item["recordVersion"], "MATERIAL_VERSION_CHANGED: recollect before publishing")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Read a frozen, authenticated ScreenJournal export; never open the media store."""
|
|
2
|
+
import datetime as dt
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
from zoneinfo import ZoneInfo
|
|
7
|
+
from job_state import Failure, digest, read, require, stamp, window
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def source_scope(c):
|
|
11
|
+
return "screen:" + c["producerId"] + ":" + c["sources"]["screen"]["expectedDeviceID"] + ":observed"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def rows(bundle, day, c):
|
|
15
|
+
manifest = read(bundle / "manifest.json")
|
|
16
|
+
raw = (bundle / "export.json").read_bytes()
|
|
17
|
+
require(len(raw) <= 8 * 1024 * 1024 and hashlib.sha256(raw).hexdigest() == manifest["sha256"], "SCREEN_EXPORT_CHANGED")
|
|
18
|
+
value = json.loads(raw)
|
|
19
|
+
require(value["schemaVersion"] == 1 and value["deviceID"] == c["sources"]["screen"]["expectedDeviceID"] and value["day"] == day and value["timeZone"] == "Asia/Shanghai", "SCREEN_IDENTITY_MISMATCH")
|
|
20
|
+
require(value["revision"] == manifest["revision"] and value["snapshotID"] == manifest["snapshotID"] and not value.get("nextCursor"), "SCREEN_SNAPSHOT_INCOMPLETE")
|
|
21
|
+
canonical = dict(value, contentHash="")
|
|
22
|
+
expected_hash = hashlib.sha256(json.dumps(canonical, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
23
|
+
require(value.get("contentHash") == expected_hash, "SCREEN_CONTENT_HASH_MISMATCH")
|
|
24
|
+
require(value["coverage"] in ("complete", "partial", "unavailable"), "SCREEN_COVERAGE_INVALID")
|
|
25
|
+
result, seen = [], set()
|
|
26
|
+
for card in value["cards"]:
|
|
27
|
+
require(card["cardID"] not in seen and isinstance(card["revision"], int) and card["revision"] > 0, "SCREEN_CARD_IDENTITY_INVALID")
|
|
28
|
+
seen.add(card["cardID"])
|
|
29
|
+
require(card["evidenceIDs"] and card["spans"], "SCREEN_EVIDENCE_REQUIRED")
|
|
30
|
+
for span in card["spans"]:
|
|
31
|
+
for ms in (span["startMs"], span["endMs"]):
|
|
32
|
+
require(dt.datetime.fromtimestamp(ms / 1000, ZoneInfo("Asia/Shanghai")).date().isoformat() == day, "SCREEN_DAY_MISMATCH")
|
|
33
|
+
# Only facts and stable IDs cross the publish boundary. No JPEG, path,
|
|
34
|
+
# transient view ID or raw thread transcript is a source reference.
|
|
35
|
+
ref = {"channel": "screen-timeline", "sourceScope": source_scope(c), "nativeId": card["cardID"], "revision": str(card["revision"]), "hash": digest(card)}
|
|
36
|
+
result.append({"id": "screen:" + value["deviceID"] + ":" + card["cardID"], "day": day, "role": "observation", "text": json.dumps(card, ensure_ascii=False),
|
|
37
|
+
"occurredAt": dt.datetime.fromtimestamp(min(span["startMs"] for span in card["spans"]) / 1000, dt.timezone.utc).isoformat(),
|
|
38
|
+
"sourceVersion": str(card["revision"]) + ":" + ref["hash"],
|
|
39
|
+
"anchor": source_scope(c) + ":" + card["matterID"], "project": None, "ref": ref})
|
|
40
|
+
receipt = {"sourceScope": source_scope(c), "status": value["coverage"], "collectedAt": stamp(), **window(day)}
|
|
41
|
+
return result, receipt, value["revision"]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def unavailable(day, c):
|
|
45
|
+
return {"sourceScope": source_scope(c), "status": "unavailable", "collectedAt": stamp(), **window(day)}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def export(c, day, bundle=None):
|
|
49
|
+
argv = c["sources"]["screen"]["exporterArgv"] + ["--day", day, "--json"]
|
|
50
|
+
if bundle is not None:
|
|
51
|
+
argv += ["--out", str(bundle)]
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(argv, capture_output=True, text=True, timeout=120 if bundle is not None else 10, check=False)
|
|
54
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
55
|
+
require(False, "SCREEN_EXPORT_UNAVAILABLE")
|
|
56
|
+
require(result.returncode == 0 and len(result.stdout.encode()) <= 8 * 1024 * 1024, "SCREEN_EXPORT_UNAVAILABLE")
|
|
57
|
+
return json.loads(result.stdout)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def reconcile_revisions(state):
|
|
61
|
+
# Called only after the original active/pending package is resolved.
|
|
62
|
+
today = dt.datetime.now(ZoneInfo("Asia/Shanghai")).date()
|
|
63
|
+
screen = state.c["sources"].get("screen", {})
|
|
64
|
+
if not screen.get("enabled"):
|
|
65
|
+
return
|
|
66
|
+
for offset in range(1, 8):
|
|
67
|
+
day = (today - dt.timedelta(days=offset)).isoformat()
|
|
68
|
+
old = state.data["days"].get(day)
|
|
69
|
+
if old is None or day < screen["effectiveFrom"]:
|
|
70
|
+
continue
|
|
71
|
+
try:
|
|
72
|
+
value = export(state.c, day)
|
|
73
|
+
require(value["deviceID"] == screen["expectedDeviceID"] and value["day"] == day and value["schemaVersion"] == 1, "SCREEN_IDENTITY_MISMATCH")
|
|
74
|
+
if old.get("screenRevision") != value["revision"]:
|
|
75
|
+
old["complete"] = False
|
|
76
|
+
except (OSError, ValueError, KeyError, subprocess.SubprocessError, Failure):
|
|
77
|
+
old["complete"] = False
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Private local state for the shared scheduled Daily Record workflow."""
|
|
2
|
+
import datetime as dt
|
|
3
|
+
import fcntl
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
import sys
|
|
12
|
+
import copy
|
|
13
|
+
import uuid
|
|
14
|
+
from zoneinfo import ZoneInfo
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Failure(Exception):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def require(ok, message):
|
|
22
|
+
if not ok:
|
|
23
|
+
raise Failure(message)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def digest(value):
|
|
27
|
+
return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read(path):
|
|
31
|
+
path = Path(path)
|
|
32
|
+
require(not path.is_symlink() and path.is_file() and not path.stat().st_mode & 0o077, "PRIVATE_FILE_REQUIRED")
|
|
33
|
+
return json.loads(path.read_bytes())
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def write(path, value):
|
|
37
|
+
path = Path(path)
|
|
38
|
+
fd, name = tempfile.mkstemp(prefix=".daily-", dir=path.parent)
|
|
39
|
+
try:
|
|
40
|
+
with os.fdopen(fd, "wb") as stream:
|
|
41
|
+
stream.write((json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode())
|
|
42
|
+
stream.flush()
|
|
43
|
+
os.fsync(stream.fileno())
|
|
44
|
+
os.replace(name, path)
|
|
45
|
+
descriptor = os.open(path.parent, os.O_RDONLY)
|
|
46
|
+
try:
|
|
47
|
+
os.fsync(descriptor)
|
|
48
|
+
finally:
|
|
49
|
+
os.close(descriptor)
|
|
50
|
+
finally:
|
|
51
|
+
if os.path.exists(name):
|
|
52
|
+
os.unlink(name)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def directory(path):
|
|
56
|
+
path = Path(path)
|
|
57
|
+
require(not path.is_symlink(), "DIRECTORY_SYMLINK_REJECTED")
|
|
58
|
+
path.mkdir(mode=0o700, parents=True, exist_ok=True)
|
|
59
|
+
require(path.is_dir() and not path.stat().st_mode & 0o077, "PRIVATE_DIRECTORY_REQUIRED")
|
|
60
|
+
return path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def date(value):
|
|
64
|
+
parsed = dt.date.fromisoformat(value)
|
|
65
|
+
require(parsed.isoformat() == value, "INVALID_DATE")
|
|
66
|
+
return parsed
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def today():
|
|
70
|
+
return dt.datetime.now(ZoneInfo("Asia/Shanghai")).date()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def stamp():
|
|
74
|
+
return dt.datetime.now(dt.timezone.utc).isoformat()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def window(day):
|
|
78
|
+
start = dt.datetime.combine(date(day), dt.time(), ZoneInfo("Asia/Shanghai"))
|
|
79
|
+
return {"from": start.isoformat(), "to": (start + dt.timedelta(days=1)).isoformat()}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def exact(value, required, optional=()):
|
|
83
|
+
require(isinstance(value, dict) and set(required) <= set(value) and not set(value) - set(required) - set(optional), "INVALID_CONFIG_FIELDS")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def scope(value):
|
|
87
|
+
require(isinstance(value, str) and re.fullmatch(r"[A-Za-z0-9_.:-]{1,120}", value), "INVALID_SCOPE")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def config(path):
|
|
91
|
+
c = read(path)
|
|
92
|
+
exact(c, {"schemaVersion", "producerId", "sourceNamespace", "startDate", "timeZone", "stateDir", "collectorArgv", "sources", "projectAliases", "suiji"}, {"maxDaysPerRun"})
|
|
93
|
+
require(c["schemaVersion"] == 1 and c["timeZone"] == "Asia/Shanghai", "INVALID_JOB_VERSION_OR_TIMEZONE")
|
|
94
|
+
scope(c["producerId"])
|
|
95
|
+
scope(c["sourceNamespace"])
|
|
96
|
+
date(c["startDate"])
|
|
97
|
+
exact(c["sources"], {"localAgent", "feishu"}, {"browser", "screen", "materials"})
|
|
98
|
+
local, feishu = c["sources"]["localAgent"], c["sources"]["feishu"]
|
|
99
|
+
exact(local, {"providers"}, {"home"})
|
|
100
|
+
require(isinstance(local["providers"], list) and all(p in ("codex", "pi", "claude", "trae") for p in local["providers"]) and len(set(local["providers"])) == len(local["providers"]), "INVALID_PROVIDERS")
|
|
101
|
+
exact(feishu, {"enabled", "channels"}, {"accountScope"})
|
|
102
|
+
require(type(feishu["enabled"]) is bool and feishu["channels"] == "messages", "MESSAGES_ONLY")
|
|
103
|
+
if feishu["enabled"]:
|
|
104
|
+
scope(feishu.get("accountScope"))
|
|
105
|
+
browser = c["sources"].get("browser", {"enabled": False})
|
|
106
|
+
require(type(browser.get("enabled")) is bool, "INVALID_BROWSER_ENABLED")
|
|
107
|
+
if browser["enabled"]:
|
|
108
|
+
exact(browser, {"enabled", "effectiveFrom", "root", "collectorArgv", "expectedSourceIds"})
|
|
109
|
+
require(sys.platform == "darwin", "BROWSER_REQUIRES_MACOS")
|
|
110
|
+
date(browser["effectiveFrom"])
|
|
111
|
+
require(browser["effectiveFrom"] >= c["startDate"], "BROWSER_BEFORE_JOB_START")
|
|
112
|
+
require(isinstance(browser["root"], str) and Path(browser["root"]).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
|
|
113
|
+
require(isinstance(browser["collectorArgv"], list) and browser["collectorArgv"] and all(isinstance(v, str) and v and "\0" not in v for v in browser["collectorArgv"]), "INVALID_BROWSER_ARGV")
|
|
114
|
+
ids = browser["expectedSourceIds"]
|
|
115
|
+
require(isinstance(ids, list) and ids and len(ids) == len(set(ids)) and all(isinstance(v, str) and str(uuid.UUID(v)) == v for v in ids), "INVALID_BROWSER_SOURCE_IDS")
|
|
116
|
+
else:
|
|
117
|
+
exact(browser, {"enabled"})
|
|
118
|
+
# Keep the original binding byte-for-byte for old/disabled configurations.
|
|
119
|
+
c["sources"].pop("browser", None)
|
|
120
|
+
screen = c["sources"].get("screen", {"enabled": False})
|
|
121
|
+
require(type(screen.get("enabled")) is bool, "INVALID_SCREEN_ENABLED")
|
|
122
|
+
if screen["enabled"]:
|
|
123
|
+
exact(screen, {"enabled", "effectiveFrom", "expectedDeviceID", "exporterArgv"})
|
|
124
|
+
require(sys.platform == "darwin", "SCREEN_REQUIRES_MACOS")
|
|
125
|
+
date(screen["effectiveFrom"])
|
|
126
|
+
require(screen["effectiveFrom"] >= c["startDate"], "SCREEN_BEFORE_JOB_START")
|
|
127
|
+
require(isinstance(screen["expectedDeviceID"], str) and str(uuid.UUID(screen["expectedDeviceID"])).lower() == screen["expectedDeviceID"].lower(), "INVALID_SCREEN_DEVICE")
|
|
128
|
+
argv = screen["exporterArgv"]
|
|
129
|
+
require(isinstance(argv, list) and argv and all(isinstance(v, str) and v and "\0" not in v for v in argv), "INVALID_SCREEN_ARGV")
|
|
130
|
+
require(argv[1:] == ["screen-recording", "analysis", "export", "--instance", "stable"], "SCREEN_EXPORTER_MUST_USE_STABLE")
|
|
131
|
+
require(Path(argv[0]).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
|
|
132
|
+
else:
|
|
133
|
+
exact(screen, {"enabled"})
|
|
134
|
+
c["sources"].pop("screen", None)
|
|
135
|
+
materials = c["sources"].get("materials", {"enabled": False})
|
|
136
|
+
require(type(materials.get("enabled")) is bool, "INVALID_MATERIALS_ENABLED")
|
|
137
|
+
if materials["enabled"]:
|
|
138
|
+
exact(materials, {"enabled", "effectiveFrom", "accountScope"})
|
|
139
|
+
date(materials["effectiveFrom"])
|
|
140
|
+
require(materials["effectiveFrom"] >= c["startDate"], "MATERIALS_BEFORE_JOB_START")
|
|
141
|
+
connection = read(c["suiji"]["configPath"])
|
|
142
|
+
require(materials["accountScope"] == connection["serverId"] + ":" + connection["ownerId"], "MATERIAL_ACCOUNT_MISMATCH")
|
|
143
|
+
else:
|
|
144
|
+
exact(materials, {"enabled"})
|
|
145
|
+
c["sources"].pop("materials", None)
|
|
146
|
+
require(local["providers"] or feishu["enabled"] or (browser["enabled"] and browser["effectiveFrom"] == c["startDate"]) or (materials["enabled"] and materials["effectiveFrom"] == c["startDate"]), "NO_ENABLED_SOURCE_FOR_START_DATE")
|
|
147
|
+
require(isinstance(c["collectorArgv"], list) and c["collectorArgv"] and all(isinstance(v, str) and v and "\0" not in v for v in c["collectorArgv"]), "INVALID_COLLECTOR_ARGV")
|
|
148
|
+
exact(c["suiji"], {"configPath", "tokenFile", "autoPublish"})
|
|
149
|
+
require(type(c["suiji"]["autoPublish"]) is bool, "INVALID_AUTO_PUBLISH")
|
|
150
|
+
for value in [c["stateDir"], c["suiji"]["configPath"], c["suiji"]["tokenFile"]] + ([local["home"]] if "home" in local else []):
|
|
151
|
+
require(isinstance(value, str) and Path(value).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
|
|
152
|
+
require(isinstance(c["projectAliases"], dict) and all(isinstance(k, str) and isinstance(v, str) and 0 < len(v.strip()) <= 80 and not any(ch in v for ch in "\r\n\t") for k, v in c["projectAliases"].items()), "INVALID_PROJECT_ALIASES")
|
|
153
|
+
c.setdefault("maxDaysPerRun", 7)
|
|
154
|
+
require(type(c["maxDaysPerRun"]) is int and 1 <= c["maxDaysPerRun"] <= 366, "INVALID_DAY_BUDGET")
|
|
155
|
+
scopes(c)
|
|
156
|
+
return c
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def browser_enabled(c, day=None):
|
|
160
|
+
browser = c["sources"].get("browser", {})
|
|
161
|
+
return bool(browser.get("enabled") and (day is None or day >= browser["effectiveFrom"]))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def screen_enabled(c, day=None):
|
|
165
|
+
screen = c["sources"].get("screen", {})
|
|
166
|
+
return bool(screen.get("enabled") and (day is None or day >= screen["effectiveFrom"]))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def materials_enabled(c, day=None):
|
|
170
|
+
value = c["sources"].get("materials", {})
|
|
171
|
+
return bool(value.get("enabled") and (day is None or day >= value["effectiveFrom"]))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def binding(c):
|
|
175
|
+
connection = read(c["suiji"]["configPath"])
|
|
176
|
+
value = {k: c[k] for k in ("producerId", "sourceNamespace", "startDate", "sources")}
|
|
177
|
+
value["target"] = {k: connection[k] for k in ("endpoint", "serverId", "ownerId")}
|
|
178
|
+
return digest(value)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def scopes(c, day=None):
|
|
182
|
+
values = ["agent:" + c["producerId"] + ":" + p for p in c["sources"]["localAgent"]["providers"]]
|
|
183
|
+
if c["sources"]["feishu"]["enabled"]:
|
|
184
|
+
values.append("feishu:" + c["sources"]["feishu"]["accountScope"] + ":messages")
|
|
185
|
+
if browser_enabled(c, day):
|
|
186
|
+
values.append("browser:" + c["producerId"] + ":observed")
|
|
187
|
+
if screen_enabled(c, day):
|
|
188
|
+
values.append("screen:" + c["producerId"] + ":" + c["sources"]["screen"]["expectedDeviceID"] + ":observed")
|
|
189
|
+
if materials_enabled(c, day):
|
|
190
|
+
values.append("materials:" + c["sources"]["materials"]["accountScope"])
|
|
191
|
+
for value in values:
|
|
192
|
+
scope(value)
|
|
193
|
+
return values
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def process_identity(pid):
|
|
197
|
+
try:
|
|
198
|
+
result = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True, timeout=5, check=False)
|
|
199
|
+
return result.stdout.strip() if result.returncode == 0 else None
|
|
200
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
201
|
+
raise Failure("OWNER_CHECK_UNAVAILABLE") from None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class State:
|
|
205
|
+
def __init__(self, c, migrating=False):
|
|
206
|
+
self.c = c
|
|
207
|
+
self.migrating = migrating
|
|
208
|
+
self.root = directory(Path(c["stateDir"]).resolve())
|
|
209
|
+
self.file = self.root / "state.json"
|
|
210
|
+
self.lock = None
|
|
211
|
+
|
|
212
|
+
def __enter__(self):
|
|
213
|
+
lock_path = self.root / "lock"
|
|
214
|
+
require(not lock_path.is_symlink(), "LOCK_SYMLINK_REJECTED")
|
|
215
|
+
self.lock = os.fdopen(os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o600), "r+")
|
|
216
|
+
try:
|
|
217
|
+
fcntl.flock(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
218
|
+
except BlockingIOError:
|
|
219
|
+
self.lock.close()
|
|
220
|
+
raise Failure("BUSY") from None
|
|
221
|
+
try:
|
|
222
|
+
expected = binding(self.c)
|
|
223
|
+
journal = self.root / "source-migration.json"
|
|
224
|
+
require(self.migrating or not journal.exists(), "SOURCE_MIGRATION_IN_PROGRESS: resume migrate-sources")
|
|
225
|
+
self.data = read(self.file) if self.file.exists() else {"schemaVersion": 1, "binding": expected, "days": {}, "identities": {}, "batches": {}, "excludedThreads": []}
|
|
226
|
+
self.original_digest = digest(self.data)
|
|
227
|
+
if not (self.migrating and (journal.exists() or (self.root / "work-isolation.json").exists())):
|
|
228
|
+
require(self.data.get("binding") == expected, "JOB_IDENTITY_OR_SOURCES_CHANGED")
|
|
229
|
+
except Exception:
|
|
230
|
+
self.lock.close()
|
|
231
|
+
raise
|
|
232
|
+
return self
|
|
233
|
+
|
|
234
|
+
def __exit__(self, kind, error, traceback):
|
|
235
|
+
try:
|
|
236
|
+
if hasattr(self, "data") and digest(self.data) != self.original_digest:
|
|
237
|
+
write(self.file, self.data)
|
|
238
|
+
finally:
|
|
239
|
+
self.lock.close()
|
|
240
|
+
|
|
241
|
+
def batch(self, args):
|
|
242
|
+
path = Path(args.batch).absolute()
|
|
243
|
+
require(not path.is_symlink() and path.resolve().parent == (self.root / "batches").resolve(), "INVALID_BATCH_PATH")
|
|
244
|
+
meta = read(path / "batch.json")
|
|
245
|
+
require(meta["id"] in self.data["batches"] and self.data["active"] == meta["id"], "BATCH_NOT_ACTIVE")
|
|
246
|
+
require(meta["runId"] == args.run_id and meta["generation"] == args.generation, "BATCH_OWNER_CHANGED")
|
|
247
|
+
if meta.get("identitySnapshot") is not None:
|
|
248
|
+
self.data["identities"] = meta["identitySnapshot"]
|
|
249
|
+
self.data["days"].update(meta.get("confirmations", {}))
|
|
250
|
+
return path, meta
|
|
251
|
+
|
|
252
|
+
def save_batch(self, path, meta):
|
|
253
|
+
write(path / "batch.json", meta)
|
|
254
|
+
self.data["batches"][meta["id"]] = {"phase": meta["phase"], "path": str(path), "days": meta["days"]}
|
|
255
|
+
|
|
256
|
+
def missing(self):
|
|
257
|
+
result = []
|
|
258
|
+
day = date(self.c["startDate"])
|
|
259
|
+
while day < today():
|
|
260
|
+
if not self.data["days"].get(day.isoformat(), {}).get("complete", False):
|
|
261
|
+
result.append(day.isoformat())
|
|
262
|
+
day += dt.timedelta(days=1)
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def migrate_sources(state, current_path, next_path, dry_run):
|
|
267
|
+
"""Journal one additive browser or screen source under the existing lock."""
|
|
268
|
+
target = str(Path(current_path).resolve())
|
|
269
|
+
next_c = config(next_path)
|
|
270
|
+
journal_path = state.root / "source-migration.json"
|
|
271
|
+
completed_path = state.root / "source-migration-completed.json"
|
|
272
|
+
if not journal_path.exists() and state.c == next_c and completed_path.exists():
|
|
273
|
+
completed = read(completed_path)
|
|
274
|
+
require(completed["configPath"] == target and completed["newConfig"] == next_c and state.data["binding"] == completed["newState"]["binding"], "MIGRATION_TARGET_CHANGED")
|
|
275
|
+
return {"dryRun": dry_run, "migrated": True, "alreadyApplied": True, "affectedDays": completed["affectedDays"]}
|
|
276
|
+
|
|
277
|
+
if journal_path.exists():
|
|
278
|
+
journal = read(journal_path)
|
|
279
|
+
require(journal["configPath"] == target and journal["newConfig"] == next_c, "MIGRATION_TARGET_CHANGED")
|
|
280
|
+
require(state.data["binding"] in (journal["oldBinding"], journal["newState"]["binding"]) and state.c in (journal["oldConfig"], journal["newConfig"]), "MIGRATION_IDENTITY_CHANGED")
|
|
281
|
+
else:
|
|
282
|
+
added = [name for name, enabled in (("browser", browser_enabled), ("screen", screen_enabled), ("materials", materials_enabled)) if enabled(next_c) and not enabled(state.c)]
|
|
283
|
+
require(len(added) == 1, "ADDITIVE_SOURCE_MIGRATION_ONLY")
|
|
284
|
+
source = added[0]
|
|
285
|
+
old_base, new_base = copy.deepcopy(state.c), copy.deepcopy(next_c)
|
|
286
|
+
new_base["sources"].pop(source)
|
|
287
|
+
require(old_base == new_base, "MIGRATION_MAY_ONLY_ADD_ONE_SOURCE")
|
|
288
|
+
require(not state.data.get("active") and all(b["phase"] == "confirmed" for b in state.data["batches"].values()), "ACTIVE_OR_PENDING_BATCH: recover it before migration")
|
|
289
|
+
updated = copy.deepcopy(state.data)
|
|
290
|
+
updated["binding"] = binding(next_c)
|
|
291
|
+
effective = next_c["sources"][source]["effectiveFrom"]
|
|
292
|
+
affected = [d for d in updated["days"] if d >= effective]
|
|
293
|
+
for day in affected:
|
|
294
|
+
updated["days"][day]["complete"] = False
|
|
295
|
+
journal = {"source": source, "configPath": target, "oldConfig": state.c, "newConfig": next_c, "oldBinding": state.data["binding"], "newState": updated, "affectedDays": affected}
|
|
296
|
+
source = journal.get("source", "browser")
|
|
297
|
+
result = {"dryRun": dry_run, "effectiveFrom": next_c["sources"][source]["effectiveFrom"], "affectedDays": journal["affectedDays"], "oldBinding": journal["oldBinding"], "newBinding": journal["newState"]["binding"]}
|
|
298
|
+
if dry_run:
|
|
299
|
+
return result
|
|
300
|
+
if not journal_path.exists():
|
|
301
|
+
write(state.root / ("before-" + source + "-config.json"), journal["oldConfig"])
|
|
302
|
+
write(state.root / ("before-" + source + "-state.json"), state.data)
|
|
303
|
+
write(journal_path, journal)
|
|
304
|
+
write(state.file, journal["newState"])
|
|
305
|
+
write(target, next_c)
|
|
306
|
+
state.data = journal["newState"]
|
|
307
|
+
state.original_digest = digest(state.data)
|
|
308
|
+
journal_path.rename(state.root / "source-migration-completed.json")
|
|
309
|
+
return {**result, "migrated": True}
|