@runweave/server-linux-x64 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +1482 -151
- 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 +45 -9
- package/payload/backend/ios-simulators/pool.py +3 -3
- package/payload/frontend/dist/assets/{_baseUniq-vo4uuhMd.js → _baseUniq-qHFga5V8.js} +1 -1
- package/payload/frontend/dist/assets/{access-_jCu7Rgu.js → access-DgHvmn15.js} +1 -1
- package/payload/frontend/dist/assets/{advanced-CqOhObYj.js → advanced-C1Fh14vw.js} +1 -1
- package/payload/frontend/dist/assets/{appearance-CrrD5gzC.js → appearance-Bs7cSXqO.js} +1 -1
- package/payload/frontend/dist/assets/{arc-dbfyHSF0.js → arc-E3iYHQG_.js} +1 -1
- package/payload/frontend/dist/assets/{architectureDiagram-Q4EWVU46-CntSCAsO.js → architectureDiagram-Q4EWVU46-DgpYpTb9.js} +1 -1
- package/payload/frontend/dist/assets/{blockDiagram-DXYQGD6D-Chp0D4qy.js → blockDiagram-DXYQGD6D-CfFOEKZ7.js} +1 -1
- package/payload/frontend/dist/assets/{c4Diagram-AHTNJAMY-CF91pOuW.js → c4Diagram-AHTNJAMY-BxlHBUj3.js} +1 -1
- package/payload/frontend/dist/assets/channel-amxyNKnk.js +1 -0
- package/payload/frontend/dist/assets/{chunk-4BX2VUAB-B1gxlVu2.js → chunk-4BX2VUAB-xWocgDo8.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-4TB4RGXK-Cxt3mj9b.js → chunk-4TB4RGXK-Bl1cNICf.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-55IACEB6-BJT6qNik.js → chunk-55IACEB6-DVtE836u.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-EDXVE4YY-yI59bCa0.js → chunk-EDXVE4YY-Dbd3tDCr.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-FMBD7UC4-CDvncKKJ.js → chunk-FMBD7UC4-MPJaTsyN.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-OYMX7WX6-BblOIU57.js → chunk-OYMX7WX6-D3LuudlW.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-QZHKN3VN-rODzNdO0.js → chunk-QZHKN3VN-Dn1aF6iQ.js} +1 -1
- package/payload/frontend/dist/assets/{chunk-YZCP3GAM-B6D4wQwk.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-DqDmxS-O.js → connections-G_Q7342g.js} +1 -1
- package/payload/frontend/dist/assets/{cose-bilkent-S5V4N54A-BvwVWp29.js → cose-bilkent-S5V4N54A-75FLD3RS.js} +1 -1
- package/payload/frontend/dist/assets/{dagre-KV5264BT-h_K1TH11.js → dagre-KV5264BT-DEm-DpJL.js} +1 -1
- package/payload/frontend/dist/assets/{desktop-BQ3kGtKG.js → desktop-g5JC-CQd.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-5BDNPKRD-B8g2SV6L.js → diagram-5BDNPKRD-BV6P2UTy.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-G4DWMVQ6-BOEnrqYm.js → diagram-G4DWMVQ6-DuCK-MM2.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-MMDJMWI5-DlaN_z71.js → diagram-MMDJMWI5-D5_GlPgO.js} +1 -1
- package/payload/frontend/dist/assets/{diagram-TYMM5635-C_pSKftn.js → diagram-TYMM5635-QRbOYR1P.js} +1 -1
- package/payload/frontend/dist/assets/{erDiagram-SMLLAGMA-C2dL-h3I.js → erDiagram-SMLLAGMA-DefCheAw.js} +1 -1
- package/payload/frontend/dist/assets/{flowDiagram-DWJPFMVM-BmXhKWFj.js → flowDiagram-DWJPFMVM-DVzyWIso.js} +1 -1
- package/payload/frontend/dist/assets/{ganttDiagram-T4ZO3ILL-D_Odnssf.js → ganttDiagram-T4ZO3ILL-BJaClUEF.js} +1 -1
- package/payload/frontend/dist/assets/{gitGraphDiagram-UUTBAWPF-DcQdcwKZ.js → gitGraphDiagram-UUTBAWPF-BLfbApNz.js} +1 -1
- package/payload/frontend/dist/assets/{graph-C_XsxefA.js → graph-C-YqFh68.js} +1 -1
- package/payload/frontend/dist/assets/{image-D3hgVr8O.js → image-CZo_2Awc.js} +1 -1
- package/payload/frontend/dist/assets/{index-CEUsFFQq.js → index-6q2aeqkt.js} +95 -105
- package/payload/frontend/dist/assets/{infoDiagram-42DDH7IO-C6TnRyMi.js → infoDiagram-42DDH7IO-BakVOutT.js} +1 -1
- package/payload/frontend/dist/assets/{ishikawaDiagram-UXIWVN3A-BL7rfALJ.js → ishikawaDiagram-UXIWVN3A-D_1Oa5Dc.js} +1 -1
- package/payload/frontend/dist/assets/{journeyDiagram-VCZTEJTY-C_6407GW.js → journeyDiagram-VCZTEJTY-Y8TVFv6B.js} +1 -1
- package/payload/frontend/dist/assets/{kanban-definition-6JOO6SKY-CK61Fd0p.js → kanban-definition-6JOO6SKY-DxeNhTIB.js} +1 -1
- package/payload/frontend/dist/assets/{layout-DVn6JKbl.js → layout-5cMeOTAp.js} +1 -1
- package/payload/frontend/dist/assets/{linear-DyWskq8H.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-apbLpSJ3.js → min-BRXKkrS8.js} +1 -1
- package/payload/frontend/dist/assets/{mindmap-definition-QFDTVHPH-MVHZZbma.js → mindmap-definition-QFDTVHPH-RYZO_BEr.js} +1 -1
- package/payload/frontend/dist/assets/{monitoring-BApsVez_.js → monitoring-Fv7kZ4s6.js} +1 -1
- package/payload/frontend/dist/assets/{notifications-CjxSNLU1.js → notifications-BzrYqyMH.js} +1 -1
- package/payload/frontend/dist/assets/{pieDiagram-DEJITSTG-Dhbb0AYU.js → pieDiagram-DEJITSTG-Bzw0dhjz.js} +1 -1
- package/payload/frontend/dist/assets/{quadrantDiagram-34T5L4WZ-Yk6LOF0S.js → quadrantDiagram-34T5L4WZ-CQJY_msa.js} +1 -1
- package/payload/frontend/dist/assets/{record-content-BCJ80KS5.js → record-content-B7-6olQt.js} +1 -1
- package/payload/frontend/dist/assets/{requirementDiagram-MS252O5E-BUhS0PEq.js → requirementDiagram-MS252O5E-DuP8hAV8.js} +1 -1
- package/payload/frontend/dist/assets/{sankeyDiagram-XADWPNL6-D2sdxz14.js → sankeyDiagram-XADWPNL6-D-fQnCZZ.js} +1 -1
- package/payload/frontend/dist/assets/{sequenceDiagram-FGHM5R23-CRere4Fy.js → sequenceDiagram-FGHM5R23-DaZNEccV.js} +1 -1
- package/payload/frontend/dist/assets/{stateDiagram-FHFEXIEX-_pk-OMlm.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-Dk5rRVQF.js → status-CIIPajZp.js} +1 -1
- package/payload/frontend/dist/assets/suiji-C453W-Sg.js +1 -0
- package/payload/frontend/dist/assets/{svg-X7YkMMvx.js → svg-BUqAWGtg.js} +1 -1
- package/payload/frontend/dist/assets/{tabs-BmKbcPVh.js → tabs-Cwlp1Meu.js} +1 -1
- package/payload/frontend/dist/assets/{timeline-definition-GMOUNBTQ-BPsI2pMC.js → timeline-definition-GMOUNBTQ-BOKDMwHS.js} +1 -1
- package/payload/frontend/dist/assets/{vennDiagram-DHZGUBPP-BNJkZ1Az.js → vennDiagram-DHZGUBPP-fSZjDS41.js} +1 -1
- package/payload/frontend/dist/assets/{wardley-RL74JXVD-CVcYeATS.js → wardley-RL74JXVD-D1KIsvoD.js} +1 -1
- package/payload/frontend/dist/assets/{wardleyDiagram-NUSXRM2D-BpLJ0alk.js → wardleyDiagram-NUSXRM2D-FMe67dwE.js} +1 -1
- package/payload/frontend/dist/assets/{xychartDiagram-5P7HB3ND-BdsYYk4M.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-Bf3IeONI.js +0 -1
- package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-CFsG2SvG.js +0 -1
- package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-CFsG2SvG.js +0 -1
- package/payload/frontend/dist/assets/clone-CeQXm2YN.js +0 -1
- package/payload/frontend/dist/assets/connection-XriXysSc.js +0 -12
- package/payload/frontend/dist/assets/main-Cg6Oo1xK.css +0 -33
- package/payload/frontend/dist/assets/main-H5R-3_w1.js +0 -696
- package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-CzpdUl_0.js +0 -1
- package/payload/frontend/dist/assets/suiji-CO_L5W1X.js +0 -1
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Isolated iOS agent-device sessions; command status is not business acceptance."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import concurrent.futures
|
|
6
|
+
import json
|
|
7
|
+
import importlib.util
|
|
8
|
+
from contextlib import nullcontext
|
|
9
|
+
import os
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import re
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
import time
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
VERSION = "0.21.3"
|
|
20
|
+
sys.dont_write_bytecode = True
|
|
21
|
+
import resources
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def capture(args, timeout=15):
|
|
25
|
+
result = subprocess.run(args, capture_output=True, text=True, timeout=timeout)
|
|
26
|
+
if result.returncode:
|
|
27
|
+
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or args[0])
|
|
28
|
+
return result.stdout.strip()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def verify_version():
|
|
32
|
+
actual = capture(["agent-device", "--version"])
|
|
33
|
+
if not re.search(rf"(?<![\d.]){re.escape(VERSION)}(?![\d.])", actual):
|
|
34
|
+
raise RuntimeError(f"Expected agent-device {VERSION}; found {actual}")
|
|
35
|
+
return actual
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check_node():
|
|
39
|
+
actual = capture(["node", "--version"])
|
|
40
|
+
parts = tuple(int(part) for part in actual.lstrip("v").split(".")[:2])
|
|
41
|
+
if parts < (22, 12):
|
|
42
|
+
raise RuntimeError(f"Node 22.12+ required; found {actual}")
|
|
43
|
+
return actual
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_json(path, data):
|
|
47
|
+
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
|
|
48
|
+
path.chmod(0o600)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def device_json(config, topic):
|
|
52
|
+
with tempfile.TemporaryDirectory(prefix="agent-device-check-") as temporary:
|
|
53
|
+
output = Path(temporary) / "result.json"
|
|
54
|
+
capture([
|
|
55
|
+
"xcrun", "devicectl", "device", "info", topic,
|
|
56
|
+
"--device", config["udid"], "--timeout", "10",
|
|
57
|
+
"--json-output", str(output),
|
|
58
|
+
])
|
|
59
|
+
return json.loads(output.read_text())["result"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def check_target(config):
|
|
63
|
+
if config["kind"] == "simulator":
|
|
64
|
+
data = json.loads(capture(["xcrun", "simctl", "list", "devices", "--json"]))
|
|
65
|
+
device = next((d for group in data["devices"].values() for d in group
|
|
66
|
+
if d["udid"] == config["udid"]), None)
|
|
67
|
+
if not device or not device.get("isAvailable") or device["state"] != "Booted":
|
|
68
|
+
raise RuntimeError("Selected simulator is missing, unavailable or not booted")
|
|
69
|
+
return {"name": device["name"], "udid": device["udid"], "state": device["state"]}
|
|
70
|
+
data = device_json(config, "details")
|
|
71
|
+
hardware = data["hardwareProperties"]
|
|
72
|
+
connection = data["connectionProperties"]
|
|
73
|
+
properties = data["deviceProperties"]
|
|
74
|
+
if hardware.get("udid") != config["udid"]:
|
|
75
|
+
raise RuntimeError("Use the exact hardware UDID, not a device name or CoreDevice alias")
|
|
76
|
+
if connection.get("pairingState") != "paired":
|
|
77
|
+
raise RuntimeError("Selected device is not paired")
|
|
78
|
+
if properties.get("developerModeStatus") != "enabled":
|
|
79
|
+
raise RuntimeError("Selected device Developer Mode is not enabled")
|
|
80
|
+
return {"name": properties.get("name"), "udid": hardware["udid"],
|
|
81
|
+
"transport": connection.get("transportType"),
|
|
82
|
+
"developerMode": properties["developerModeStatus"]}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def check_lock(config):
|
|
86
|
+
data = device_json(config, "lockState")
|
|
87
|
+
if data.get("passcodeRequired") or not data.get("unlockedSinceBoot"):
|
|
88
|
+
raise RuntimeError("Unlock the selected iPhone on the device")
|
|
89
|
+
return data
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def check_developer_tools():
|
|
93
|
+
output = capture(["/usr/sbin/DevToolsSecurity", "-status"])
|
|
94
|
+
if "enabled" not in output.lower():
|
|
95
|
+
raise RuntimeError(output)
|
|
96
|
+
return output
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_signing(config):
|
|
100
|
+
if not (config.get("teamId") and config.get("runnerId")):
|
|
101
|
+
raise RuntimeError("UI automation requires --team-id at init; launch does not")
|
|
102
|
+
resources.shared_runner(config["teamId"], config["runnerId"])
|
|
103
|
+
return "Runner signing identifiers configured; provisioning is not verified"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def preflight(root, config, automation=True):
|
|
107
|
+
checks = {
|
|
108
|
+
"xcode": lambda: capture(["xcodebuild", "-version"]),
|
|
109
|
+
"target": lambda: check_target(config),
|
|
110
|
+
}
|
|
111
|
+
if automation:
|
|
112
|
+
checks.update(cli=verify_version, node=check_node)
|
|
113
|
+
if config["kind"] == "device":
|
|
114
|
+
checks.update(lock=lambda: check_lock(config))
|
|
115
|
+
if automation:
|
|
116
|
+
checks.update(developerTools=check_developer_tools, signing=lambda: check_signing(config))
|
|
117
|
+
results = {}
|
|
118
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=len(checks)) as pool:
|
|
119
|
+
pending = {name: pool.submit(check) for name, check in checks.items()}
|
|
120
|
+
for name, future in pending.items():
|
|
121
|
+
try:
|
|
122
|
+
results[name] = {"ok": True, "detail": future.result()}
|
|
123
|
+
except (OSError, RuntimeError, ValueError, KeyError, subprocess.TimeoutExpired) as error:
|
|
124
|
+
results[name] = {"ok": False, "error": str(error)}
|
|
125
|
+
report = {"preflightOk": all(r["ok"] for r in results.values()),
|
|
126
|
+
"purpose": "automation" if automation else "launch",
|
|
127
|
+
"automationReady": False, "checks": results,
|
|
128
|
+
"unchecked": ["runner provisioning/install quota", "UI Automation authorization",
|
|
129
|
+
"other XCTest users", "app build provenance"]}
|
|
130
|
+
write_json(root / "preflight.json", report)
|
|
131
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
132
|
+
return 0 if report["preflightOk"] else 2
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def simulator_pool(root, kind):
|
|
136
|
+
if kind != "simulator":
|
|
137
|
+
return None
|
|
138
|
+
anchor = root
|
|
139
|
+
while not anchor.exists() and anchor != anchor.parent:
|
|
140
|
+
anchor = anchor.parent
|
|
141
|
+
repo = None
|
|
142
|
+
# A new/nonexistent task directory must not bypass the current project's policy.
|
|
143
|
+
for directory in (Path.cwd(), anchor):
|
|
144
|
+
result = subprocess.run(["git", "-C", str(directory), "rev-parse", "--show-toplevel"],
|
|
145
|
+
capture_output=True, text=True)
|
|
146
|
+
if result.returncode == 0:
|
|
147
|
+
candidate = Path(result.stdout.strip())
|
|
148
|
+
if (candidate / "packages/app-ios/ios/RunweaveNative.xcodeproj").exists():
|
|
149
|
+
repo = candidate
|
|
150
|
+
break
|
|
151
|
+
if repo is None:
|
|
152
|
+
return None
|
|
153
|
+
path = repo / "scripts/ios-simulators/pool.py"
|
|
154
|
+
if not path.exists():
|
|
155
|
+
raise RuntimeError("This Runweave worktree needs the shared simulator tooling update")
|
|
156
|
+
spec = importlib.util.spec_from_file_location("runweave_simulator_pool", path)
|
|
157
|
+
module = importlib.util.module_from_spec(spec)
|
|
158
|
+
spec.loader.exec_module(module)
|
|
159
|
+
return module
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def launch_app(root, config):
|
|
163
|
+
"""Native launch only: wireless updates must not depend on XCTest readiness."""
|
|
164
|
+
if config["kind"] != "device":
|
|
165
|
+
raise RuntimeError("launch is for physical devices; simulators use the managed run entry")
|
|
166
|
+
if preflight(root, config, automation=False):
|
|
167
|
+
return 2
|
|
168
|
+
stamp = str(time.time_ns())
|
|
169
|
+
native = root / f"{stamp}-launch-native.json"
|
|
170
|
+
started = time.monotonic()
|
|
171
|
+
print("Launching installed App via CoreDevice; no XCTest or UI verification.", file=sys.stderr, flush=True)
|
|
172
|
+
result = subprocess.run([
|
|
173
|
+
"xcrun", "devicectl", "device", "process", "launch", "--device", config["udid"],
|
|
174
|
+
"--timeout", "30", "--json-output", str(native), config["app"],
|
|
175
|
+
], capture_output=True, text=True, timeout=40)
|
|
176
|
+
data = json.loads(native.read_text()) if native.exists() else {}
|
|
177
|
+
pid = data.get("result", {}).get("process", {}).get("processIdentifier")
|
|
178
|
+
code = result.returncode or (0 if type(pid) is int and pid > 0 else 2)
|
|
179
|
+
report = {"command": "launch", "app": config["app"], "exit": code,
|
|
180
|
+
"seconds": round(time.monotonic() - started, 3), "processIdentifier": pid,
|
|
181
|
+
"automationReady": False, "uiVerified": False, "nativeResult": str(native)}
|
|
182
|
+
write_json(root / f"{stamp}-launch.json", report)
|
|
183
|
+
if native.exists():
|
|
184
|
+
native.chmod(0o600)
|
|
185
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
186
|
+
if code:
|
|
187
|
+
print("Native launch failed; inspect nativeResult. No automatic retry.", file=sys.stderr)
|
|
188
|
+
return code
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def runner_diagnostic(root, runner_log, offset, output):
|
|
192
|
+
# Inspect only this invocation, not a failure left by an earlier runner.
|
|
193
|
+
text = ""
|
|
194
|
+
try:
|
|
195
|
+
with runner_log.open("rb") as stream:
|
|
196
|
+
size = runner_log.stat().st_size
|
|
197
|
+
stream.seek(max(offset if size >= offset else 0, size - 256_000))
|
|
198
|
+
text = stream.read().decode("utf-8", errors="replace")
|
|
199
|
+
except OSError:
|
|
200
|
+
pass
|
|
201
|
+
markers = [marker for marker in (
|
|
202
|
+
"exited with code 74", "Exiting due to IDE disconnection",
|
|
203
|
+
"Failed to retrieve test configuration from IDE", "Connection peer refused channel request",
|
|
204
|
+
) if marker in text]
|
|
205
|
+
if not markers and "xcodebuild exited early" not in output:
|
|
206
|
+
return None
|
|
207
|
+
transport = None
|
|
208
|
+
try:
|
|
209
|
+
transport = json.loads((root / "preflight.json").read_text())["checks"]["target"]["detail"].get("transport")
|
|
210
|
+
except (OSError, ValueError, KeyError, AttributeError):
|
|
211
|
+
pass
|
|
212
|
+
return {"code": "ios_xctest_bootstrap_failed" if markers else "ios_runner_exited_early",
|
|
213
|
+
"transportAtPreflight": transport, "runnerLog": str(runner_log),
|
|
214
|
+
"evidence": markers, "automationReady": False,
|
|
215
|
+
"hint": "App launch and UI automation are separate. For launch-only use launch; "
|
|
216
|
+
"UI acceptance remains blocked. Inspect the runner log before retrying; "
|
|
217
|
+
"increasing a connection timeout cannot revive an exited runner."}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def invoke(root, config, command, private=False, daemon=False, operation=None):
|
|
221
|
+
verify_version()
|
|
222
|
+
env = os.environ.copy()
|
|
223
|
+
env["AGENT_DEVICE_STATE_DIR"] = str(root / "state")
|
|
224
|
+
env["AGENT_DEVICE_NO_UPDATE_NOTIFIER"] = "1"
|
|
225
|
+
# Do not inherit another project's signing overrides.
|
|
226
|
+
for key in ("AGENT_DEVICE_IOS_TEAM_ID", "AGENT_DEVICE_IOS_BUNDLE_ID",
|
|
227
|
+
"AGENT_DEVICE_IOS_PROVISIONING_PROFILE", "AGENT_DEVICE_IOS_SIGNING_IDENTITY"):
|
|
228
|
+
env.pop(key, None)
|
|
229
|
+
if config["kind"] == "device":
|
|
230
|
+
if not daemon and command[0] != "close":
|
|
231
|
+
check_signing(config)
|
|
232
|
+
if config.get("teamId") and config.get("runnerId"):
|
|
233
|
+
env["AGENT_DEVICE_IOS_TEAM_ID"] = config["teamId"]
|
|
234
|
+
env["AGENT_DEVICE_IOS_BUNDLE_ID"] = config["runnerId"]
|
|
235
|
+
args = ["agent-device", *command]
|
|
236
|
+
if not daemon:
|
|
237
|
+
args.extend(["--session", config["session"], "--platform", "ios", "--udid", config["udid"]])
|
|
238
|
+
started = time.monotonic()
|
|
239
|
+
runner_log = root / "state" / "sessions" / config["session"] / "runner.log"
|
|
240
|
+
runner_offset = runner_log.stat().st_size if runner_log.exists() else 0
|
|
241
|
+
print(f"COMMAND_START {command[0]}: waiting for agent-device; UI commands may start XCTest.",
|
|
242
|
+
file=sys.stderr, flush=True)
|
|
243
|
+
if operation:
|
|
244
|
+
operation.owner["automationPending"] = True
|
|
245
|
+
operation.owner["automationStateDir"] = str(root / "state")
|
|
246
|
+
# Persist intent before a daemon may be spawned outside this command's process group.
|
|
247
|
+
pool = simulator_pool(root, config["kind"])
|
|
248
|
+
pool.save_owner(operation.owner)
|
|
249
|
+
result = operation.run(args, env=env, capture_output=True)
|
|
250
|
+
else:
|
|
251
|
+
result = subprocess.run(args, env=env, stdout=subprocess.PIPE,
|
|
252
|
+
stderr=subprocess.STDOUT, text=True)
|
|
253
|
+
output = result.stdout
|
|
254
|
+
code = result.returncode
|
|
255
|
+
# Pinned upstream CLI can return zero without a usable initial snapshot or settle result.
|
|
256
|
+
if code == 0 and "initial interactive snapshot failed" in output:
|
|
257
|
+
code = 2
|
|
258
|
+
if code == 0 and "not settled after" in output:
|
|
259
|
+
code = 3
|
|
260
|
+
try:
|
|
261
|
+
if code == 0 and json.loads(output).get("success") is False:
|
|
262
|
+
code = 2
|
|
263
|
+
except (ValueError, AttributeError):
|
|
264
|
+
pass
|
|
265
|
+
stamp = f"{time.time_ns()}-{uuid.uuid4().hex[:6]}"
|
|
266
|
+
if not private:
|
|
267
|
+
logfile = root / f"{stamp}.log"
|
|
268
|
+
logfile.write_text(output)
|
|
269
|
+
logfile.chmod(0o600)
|
|
270
|
+
metric = {"id": stamp, "command": command[0], "seconds": round(time.monotonic() - started, 3),
|
|
271
|
+
"upstreamExit": result.returncode, "exit": code, "outputSaved": not private}
|
|
272
|
+
if code and not private and config["kind"] == "device":
|
|
273
|
+
diagnostic = runner_diagnostic(root, runner_log, runner_offset, output)
|
|
274
|
+
if diagnostic:
|
|
275
|
+
write_json(root / f"{stamp}.diagnostic.json", diagnostic)
|
|
276
|
+
metric["diagnostic"] = diagnostic["code"]
|
|
277
|
+
print("AUTOMATION_DIAGNOSTIC " + json.dumps(diagnostic), file=sys.stderr)
|
|
278
|
+
with (root / "metrics.jsonl").open("a") as stream:
|
|
279
|
+
stream.write(json.dumps(metric) + "\n")
|
|
280
|
+
(root / "metrics.jsonl").chmod(0o600)
|
|
281
|
+
sys.stdout.write(output)
|
|
282
|
+
print("COMMAND_RESULT " + json.dumps(metric), file=sys.stderr)
|
|
283
|
+
if code != result.returncode:
|
|
284
|
+
print("Observation incomplete. Inspect current state before any retry.", file=sys.stderr)
|
|
285
|
+
return code
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def main():
|
|
289
|
+
os.umask(0o077)
|
|
290
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
291
|
+
sub = parser.add_subparsers(dest="action", required=True)
|
|
292
|
+
init = sub.add_parser("init", help="Create a new local task; no device mutation")
|
|
293
|
+
init.add_argument("root", type=Path)
|
|
294
|
+
init.add_argument("--kind", choices=("device", "simulator"), required=True)
|
|
295
|
+
init.add_argument("--udid", required=True)
|
|
296
|
+
init.add_argument("--app", required=True)
|
|
297
|
+
init.add_argument("--team-id")
|
|
298
|
+
init.add_argument("--runner-id")
|
|
299
|
+
for action in ("check", "stop", "launch"):
|
|
300
|
+
sub.add_parser(action).add_argument("root", type=Path)
|
|
301
|
+
run = sub.add_parser("run", help="Pass a CLI command after --; open uses the bound App")
|
|
302
|
+
run.add_argument("root", type=Path)
|
|
303
|
+
# REMAINDER keeps upstream flags untouched. A leading --private belongs to this wrapper.
|
|
304
|
+
run.add_argument("command", nargs=argparse.REMAINDER)
|
|
305
|
+
options = parser.parse_args()
|
|
306
|
+
root = options.root.resolve()
|
|
307
|
+
if options.action == "init":
|
|
308
|
+
if options.runner_id and not options.team_id:
|
|
309
|
+
parser.error("--runner-id requires --team-id")
|
|
310
|
+
if options.kind == "device" and options.team_id:
|
|
311
|
+
with resources.guard():
|
|
312
|
+
options.runner_id = resources.shared_runner(options.team_id, options.runner_id)
|
|
313
|
+
if not re.fullmatch(r"[A-Za-z0-9-]+", options.udid):
|
|
314
|
+
parser.error("invalid UDID")
|
|
315
|
+
for value in (options.app, options.runner_id):
|
|
316
|
+
if value and not re.fullmatch(r"[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+", value):
|
|
317
|
+
parser.error("invalid Bundle ID")
|
|
318
|
+
pool = simulator_pool(root, options.kind)
|
|
319
|
+
if not pool:
|
|
320
|
+
root.mkdir(parents=True, exist_ok=False)
|
|
321
|
+
context = pool.operation(root, options.app, options.udid) if pool else nullcontext()
|
|
322
|
+
with context:
|
|
323
|
+
if (root / "session.json").exists():
|
|
324
|
+
raise RuntimeError("Task already has a session; use a new task directory")
|
|
325
|
+
config = {"kind": options.kind, "udid": options.udid, "app": options.app,
|
|
326
|
+
"teamId": options.team_id, "runnerId": options.runner_id,
|
|
327
|
+
"session": "qa-" + uuid.uuid4().hex[:12], "version": VERSION}
|
|
328
|
+
write_json(root / "session.json", config)
|
|
329
|
+
with resources.guard():
|
|
330
|
+
resources.register(root)
|
|
331
|
+
print(root)
|
|
332
|
+
return 0
|
|
333
|
+
config = json.loads((root / "session.json").read_text())
|
|
334
|
+
if config.get("version") != VERSION:
|
|
335
|
+
raise RuntimeError("Task was created for a different CLI version; create a new task")
|
|
336
|
+
if options.action == "launch":
|
|
337
|
+
if config["kind"] != "device":
|
|
338
|
+
raise RuntimeError("launch is for physical devices")
|
|
339
|
+
with resources.physical_operation(root, config):
|
|
340
|
+
result = launch_app(root, config)
|
|
341
|
+
resources.finished(root, config)
|
|
342
|
+
return result
|
|
343
|
+
if options.action == "run" and config["kind"] == "device":
|
|
344
|
+
check_signing(config)
|
|
345
|
+
pool = simulator_pool(root, config["kind"])
|
|
346
|
+
context = pool.operation(root, config["app"], config["udid"]) if pool else (
|
|
347
|
+
resources.physical_operation(root, config) if config["kind"] == "device" and options.action != "check"
|
|
348
|
+
else nullcontext())
|
|
349
|
+
with context as op:
|
|
350
|
+
if options.action == "check":
|
|
351
|
+
return preflight(root, config)
|
|
352
|
+
if options.action == "stop":
|
|
353
|
+
# Cleanup is allowed even after preflight failed; never stop another state directory.
|
|
354
|
+
close_code = invoke(root, config, ["close"], operation=op)
|
|
355
|
+
stop_code = invoke(root, config, ["daemon", "stop", "--state-dir", str(root / "state"), "--clean"], daemon=True, operation=op)
|
|
356
|
+
if not (stop_code or close_code) and config["kind"] == "device":
|
|
357
|
+
resources.finished(root, config)
|
|
358
|
+
return stop_code or close_code
|
|
359
|
+
command = options.command
|
|
360
|
+
private = bool(command and command[0] == "--private")
|
|
361
|
+
if private:
|
|
362
|
+
command = command[1:]
|
|
363
|
+
if command and command[0] == "--":
|
|
364
|
+
command = command[1:]
|
|
365
|
+
if not command:
|
|
366
|
+
parser.error("run requires an agent-device command after --")
|
|
367
|
+
forbidden = ("--session", "--platform", "--udid", "--device", "--state-dir", "--serial")
|
|
368
|
+
if any(arg == flag or arg.startswith(flag + "=") for arg in command for flag in forbidden):
|
|
369
|
+
parser.error("device/session overrides are fixed by init; create a separate task")
|
|
370
|
+
if command[0] in ("daemon", "devices", "install", "reinstall", "uninstall"):
|
|
371
|
+
parser.error("use stop for cleanup; installation/discovery uses explicit native tooling")
|
|
372
|
+
if command[0] == "open":
|
|
373
|
+
if len(command) > 1 and not command[1].startswith("-"):
|
|
374
|
+
parser.error("open uses the App bound by init; do not pass another App")
|
|
375
|
+
command.insert(1, config["app"])
|
|
376
|
+
if command[0] == "open" and preflight(root, config):
|
|
377
|
+
return 2
|
|
378
|
+
return invoke(root, config, command, private, operation=op)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
if __name__ == "__main__":
|
|
383
|
+
try:
|
|
384
|
+
result = main()
|
|
385
|
+
if result == 0 and len(sys.argv) > 1 and sys.argv[1] == "stop":
|
|
386
|
+
try:
|
|
387
|
+
resources.prune(apply=True)
|
|
388
|
+
except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as error:
|
|
389
|
+
print(f"Artifact pruning deferred: {error}", file=sys.stderr)
|
|
390
|
+
sys.exit(result)
|
|
391
|
+
except (OSError, RuntimeError, ValueError, KeyError, subprocess.TimeoutExpired) as error:
|
|
392
|
+
code = getattr(error, "code", None)
|
|
393
|
+
if code is not None:
|
|
394
|
+
print(json.dumps({"ok": False, "code": code, "detail": str(error)},
|
|
395
|
+
ensure_ascii=False), file=sys.stderr)
|
|
396
|
+
sys.exit(getattr(error, "exit_code", 2))
|
|
397
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
398
|
+
sys.exit(2)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared signing identity, physical-device ownership and bounded QA artifacts."""
|
|
3
|
+
import argparse
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
import fcntl
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import plistlib
|
|
10
|
+
import re
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import time
|
|
14
|
+
|
|
15
|
+
BASE = Path.home() / ".runweave" / "agent-device"
|
|
16
|
+
RUNNER_ID = "com.runweave.agentdevice"
|
|
17
|
+
KEEP_TASKS = 20
|
|
18
|
+
MAX_AGE = 14 * 86400
|
|
19
|
+
MAX_BYTES = 1024 ** 3
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def read(path):
|
|
23
|
+
return json.loads(path.read_text())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def write(path, value):
|
|
27
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
temporary = path.with_suffix(".tmp")
|
|
29
|
+
temporary.write_text(json.dumps(value, indent=2) + "\n")
|
|
30
|
+
temporary.chmod(0o600)
|
|
31
|
+
temporary.replace(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def guard():
|
|
36
|
+
BASE.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
with (BASE / "resources.lock").open("a+") as stream:
|
|
38
|
+
try:
|
|
39
|
+
fcntl.flock(stream, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
40
|
+
except BlockingIOError:
|
|
41
|
+
raise RuntimeError("device_busy: another managed operation is active")
|
|
42
|
+
try:
|
|
43
|
+
yield
|
|
44
|
+
finally:
|
|
45
|
+
fcntl.flock(stream, fcntl.LOCK_UN)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def shared_runner(team, requested=None):
|
|
49
|
+
if not re.fullmatch(r"[A-Z0-9]{10}", team):
|
|
50
|
+
raise RuntimeError("Expected a ten-character signing Team ID")
|
|
51
|
+
if requested and requested != RUNNER_ID:
|
|
52
|
+
raise RuntimeError(f"Runner identity is shared: use {RUNNER_ID}; task-specific IDs are forbidden")
|
|
53
|
+
path = BASE / "runners" / f"{team}.json"
|
|
54
|
+
if not path.exists():
|
|
55
|
+
write(path, {"teamId": team, "runnerId": RUNNER_ID})
|
|
56
|
+
if read(path) != {"teamId": team, "runnerId": RUNNER_ID}:
|
|
57
|
+
raise RuntimeError("Shared runner registry differs; inspect before changing signing identity")
|
|
58
|
+
return RUNNER_ID
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def register(root):
|
|
62
|
+
paths = read(BASE / "tasks.json") if (BASE / "tasks.json").exists() else []
|
|
63
|
+
if str(root) not in paths:
|
|
64
|
+
write(BASE / "tasks.json", [*paths, str(root)])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def device_lock(config):
|
|
68
|
+
udid = config["udid"]
|
|
69
|
+
if not re.fullmatch(r"[A-Fa-f0-9-]+", udid):
|
|
70
|
+
raise RuntimeError("Invalid hardware UDID")
|
|
71
|
+
return Path.home() / ".runweave/native-device/locks" / udid.upper()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@contextmanager
|
|
75
|
+
def physical_operation(root, config):
|
|
76
|
+
with guard():
|
|
77
|
+
path = device_lock(config)
|
|
78
|
+
if path.exists():
|
|
79
|
+
owner = read(path / "owner.json")
|
|
80
|
+
if owner.get("kind") != "agent-device" or owner.get("taskDir") != str(root):
|
|
81
|
+
raise RuntimeError(f"device_busy: {path / 'owner.json'}")
|
|
82
|
+
else:
|
|
83
|
+
active_local()
|
|
84
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
path.mkdir() # Atomic with the existing native XCTest entry's lock.
|
|
86
|
+
write(path / "owner.json", {"kind": "agent-device", "taskDir": str(root),
|
|
87
|
+
"udid": config["udid"], "session": config["session"], "startedAt": time.time()})
|
|
88
|
+
register(root)
|
|
89
|
+
yield
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def finished(root, config):
|
|
93
|
+
path = device_lock(config)
|
|
94
|
+
owner = read(path / "owner.json")
|
|
95
|
+
if owner.get("kind") != "agent-device" or owner.get("taskDir") != str(root):
|
|
96
|
+
raise RuntimeError("Device owner changed; refusing release")
|
|
97
|
+
shutil.rmtree(path)
|
|
98
|
+
write(root / "finished.json", {"finishedAt": time.time()})
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def active_local():
|
|
102
|
+
output = subprocess.check_output(["ps", "-ww", "-axo", "pid=,comm=,args="], text=True)
|
|
103
|
+
for line in output.splitlines():
|
|
104
|
+
fields = line.strip().split(None, 2)
|
|
105
|
+
if len(fields) != 3 or int(fields[0]) == os.getpid():
|
|
106
|
+
continue
|
|
107
|
+
name = Path(fields[1]).name
|
|
108
|
+
if name in ("xcodebuild", "xctest") or (name == "node" and "agent-device" in fields[2]):
|
|
109
|
+
raise RuntimeError(f"Resources busy: PID {fields[0]} ({name}); stop its owner before pruning")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def size(path):
|
|
113
|
+
return int(subprocess.check_output(["du", "-sk", str(path)], text=True).split()[0]) * 1024
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def task_paths(workspace=None):
|
|
117
|
+
paths = set(read(BASE / "tasks.json") if (BASE / "tasks.json").exists() else [])
|
|
118
|
+
if workspace:
|
|
119
|
+
output = subprocess.check_output(["git", "-C", str(workspace), "worktree", "list", "--porcelain"], text=True)
|
|
120
|
+
for line in output.splitlines():
|
|
121
|
+
if line.startswith("worktree "):
|
|
122
|
+
base = Path(line[9:]) / ".runweave/mobile-qa"
|
|
123
|
+
paths.update(str(p.parent) for p in base.glob("*/session.json"))
|
|
124
|
+
return [Path(p) for p in sorted(paths)]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def legacy_builds(workspace, protected, all_history):
|
|
128
|
+
if not workspace:
|
|
129
|
+
return []
|
|
130
|
+
output = subprocess.check_output(["git", "-C", str(workspace), "worktree", "list", "--porcelain"], text=True)
|
|
131
|
+
builds = []
|
|
132
|
+
for line in output.splitlines():
|
|
133
|
+
if not line.startswith("worktree "):
|
|
134
|
+
continue
|
|
135
|
+
base = Path(line[9:]) / ".runweave"
|
|
136
|
+
for root, directories, files in os.walk(base, followlinks=False):
|
|
137
|
+
path = Path(root)
|
|
138
|
+
if any(path == Path(p) or Path(p) in path.parents for p in protected):
|
|
139
|
+
directories[:] = []
|
|
140
|
+
continue
|
|
141
|
+
if "info.plist" in files and (path / "Build").is_dir():
|
|
142
|
+
try:
|
|
143
|
+
data = plistlib.loads((path / "info.plist").read_bytes())
|
|
144
|
+
except (OSError, ValueError, plistlib.InvalidFileException):
|
|
145
|
+
data = {}
|
|
146
|
+
source = str(data.get("WorkspacePath", ""))
|
|
147
|
+
if source.endswith(".xcodeproj") and source.startswith(str(base.parent) + "/"):
|
|
148
|
+
builds.append({"path": str(path), "bytes": size(path), "workspace": source,
|
|
149
|
+
"remove": all_history})
|
|
150
|
+
directories[:] = []
|
|
151
|
+
continue
|
|
152
|
+
if path.name in ("Build", "node_modules", "state", "browser-profile", "user-data"):
|
|
153
|
+
directories[:] = []
|
|
154
|
+
return builds
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def inventory(workspace=None, all_history=False):
|
|
158
|
+
protected = set()
|
|
159
|
+
for p in (Path.home() / ".runweave/native-device/locks").glob("*/owner.json"):
|
|
160
|
+
owner = read(p)
|
|
161
|
+
if owner.get("taskDir"):
|
|
162
|
+
protected.add(owner["taskDir"])
|
|
163
|
+
tasks = []
|
|
164
|
+
for path in task_paths(workspace):
|
|
165
|
+
if not path.exists() or path.is_symlink() or not (path / "session.json").is_file():
|
|
166
|
+
continue
|
|
167
|
+
config = read(path / "session.json")
|
|
168
|
+
if config.get("app") not in ("com.runweave.app.native", "com.runweave.suiji"):
|
|
169
|
+
continue
|
|
170
|
+
if path.name in ("", "state") or str(path) in protected:
|
|
171
|
+
continue
|
|
172
|
+
tasks.append({"path": str(path), "bytes": size(path), "modifiedAt": path.stat().st_mtime})
|
|
173
|
+
tasks.sort(key=lambda x: x["modifiedAt"], reverse=True)
|
|
174
|
+
total = 0
|
|
175
|
+
for index, task in enumerate(tasks):
|
|
176
|
+
total += task["bytes"]
|
|
177
|
+
task["remove"] = (all_history or index >= KEEP_TASKS or total > MAX_BYTES
|
|
178
|
+
or time.time() - task["modifiedAt"] > MAX_AGE)
|
|
179
|
+
protected_sessions = {read(Path(p) / "session.json").get("session") for p in protected
|
|
180
|
+
if (Path(p) / "session.json").is_file()}
|
|
181
|
+
logs = []
|
|
182
|
+
for path in (Path.home() / ".agent-device/logs").glob("qa-*"):
|
|
183
|
+
if path.is_symlink() or not path.is_dir() or path.name in protected_sessions:
|
|
184
|
+
continue
|
|
185
|
+
if not re.fullmatch(r"qa-[a-f0-9]{12}", path.name):
|
|
186
|
+
continue
|
|
187
|
+
logs.append({"path": str(path), "bytes": size(path), "modifiedAt": path.stat().st_mtime})
|
|
188
|
+
total = 0
|
|
189
|
+
for index, log in enumerate(sorted(logs, key=lambda x: x["modifiedAt"], reverse=True)):
|
|
190
|
+
total += log["bytes"]
|
|
191
|
+
log["remove"] = (all_history or index >= KEEP_TASKS or total > MAX_BYTES
|
|
192
|
+
or time.time() - log["modifiedAt"] > MAX_AGE)
|
|
193
|
+
caches = []
|
|
194
|
+
base = Path.home() / ".agent-device/apple-runner/derived"
|
|
195
|
+
for path in base.glob("*/*"):
|
|
196
|
+
manifest = path / ".agent-device-runner-cache.json"
|
|
197
|
+
if path.is_symlink() or not manifest.is_file():
|
|
198
|
+
continue
|
|
199
|
+
data = read(manifest)
|
|
200
|
+
bundle = next((v.split("=", 1)[1] for v in data.get("runnerBundleBuildSettings", [])
|
|
201
|
+
if v.startswith("AGENT_DEVICE_IOS_RUNNER_APP_BUNDLE_ID=")), "")
|
|
202
|
+
if not bundle.startswith("com.runweave."):
|
|
203
|
+
continue
|
|
204
|
+
caches.append({"path": str(path), "bytes": size(path), "bundleId": bundle,
|
|
205
|
+
"group": [data.get("deviceKind"), data.get("runnerSigningBuildSettings")],
|
|
206
|
+
"modifiedAt": manifest.stat().st_mtime, "remove": bundle != RUNNER_ID})
|
|
207
|
+
kept = []
|
|
208
|
+
for cache in sorted(caches, key=lambda x: x["modifiedAt"], reverse=True):
|
|
209
|
+
if not cache["remove"]:
|
|
210
|
+
if cache["group"] in kept:
|
|
211
|
+
cache["remove"] = True
|
|
212
|
+
else:
|
|
213
|
+
kept.append(cache["group"])
|
|
214
|
+
return {"tasks": tasks, "caches": caches, "logs": logs,
|
|
215
|
+
"legacyBuilds": legacy_builds(workspace, protected, all_history), "protectedTasks": sorted(protected),
|
|
216
|
+
"limits": {"tasks": KEEP_TASKS, "days": MAX_AGE // 86400, "bytes": MAX_BYTES}}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def prune(workspace=None, apply=False, all_history=False):
|
|
220
|
+
with guard():
|
|
221
|
+
active_local()
|
|
222
|
+
report = inventory(workspace, all_history)
|
|
223
|
+
if apply:
|
|
224
|
+
# Keep a small audit, not a second copy of the deleted build products.
|
|
225
|
+
write(BASE / "last-cleanup.json", report)
|
|
226
|
+
for item in [*report["tasks"], *report["caches"], *report["logs"], *report["legacyBuilds"]]:
|
|
227
|
+
if item["remove"] and Path(item["path"]).exists():
|
|
228
|
+
shutil.rmtree(item["path"])
|
|
229
|
+
write(BASE / "tasks.json", [t["path"] for t in report["tasks"] if not t["remove"]] + report["protectedTasks"])
|
|
230
|
+
return report
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def main():
|
|
234
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
235
|
+
parser.add_argument("action", choices=("status", "prune"))
|
|
236
|
+
parser.add_argument("--workspace", type=Path)
|
|
237
|
+
parser.add_argument("--apply", action="store_true")
|
|
238
|
+
parser.add_argument("--all-history", action="store_true")
|
|
239
|
+
args = parser.parse_args()
|
|
240
|
+
report = prune(args.workspace, args.apply, args.all_history) if args.action == "prune" else inventory(args.workspace)
|
|
241
|
+
print(json.dumps(report, indent=2))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
try:
|
|
246
|
+
main()
|
|
247
|
+
except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as error:
|
|
248
|
+
raise SystemExit(str(error))
|