@repo-os/repoos 0.5.45
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/LICENSE.md +110 -0
- package/README.md +272 -0
- package/dist/.build-info.json +4 -0
- package/dist/.build-stamp.json +3 -0
- package/dist/cli/colors.d.ts +13 -0
- package/dist/cli/colors.js +37 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +221 -0
- package/dist/commands/check.d.ts +170 -0
- package/dist/commands/check.js +1227 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.js +70 -0
- package/dist/commands/gc.d.ts +1 -0
- package/dist/commands/gc.js +57 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +699 -0
- package/dist/commands/screenshots.d.ts +7 -0
- package/dist/commands/screenshots.js +77 -0
- package/dist/commands/serve.d.ts +55 -0
- package/dist/commands/serve.js +204 -0
- package/dist/commands/status.d.ts +140 -0
- package/dist/commands/status.js +569 -0
- package/dist/commands/stop.d.ts +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/tasks.d.ts +47 -0
- package/dist/commands/tasks.js +334 -0
- package/dist/commands/tunnel.d.ts +1 -0
- package/dist/commands/tunnel.js +1271 -0
- package/dist/commands/ui-harness.d.ts +53 -0
- package/dist/commands/ui-harness.js +62 -0
- package/dist/commands/ui-smoke.d.ts +6 -0
- package/dist/commands/ui-smoke.js +220 -0
- package/dist/commands/upgrade.d.ts +15 -0
- package/dist/commands/upgrade.js +188 -0
- package/dist/core/auth-store.d.ts +81 -0
- package/dist/core/auth-store.js +530 -0
- package/dist/core/auth.d.ts +75 -0
- package/dist/core/auth.js +124 -0
- package/dist/core/bootstrap.d.ts +41 -0
- package/dist/core/bootstrap.js +236 -0
- package/dist/core/build.d.ts +92 -0
- package/dist/core/build.js +249 -0
- package/dist/core/canary.d.ts +10 -0
- package/dist/core/canary.js +14 -0
- package/dist/core/check-failure-summary.d.ts +10 -0
- package/dist/core/check-failure-summary.js +34 -0
- package/dist/core/config.d.ts +160 -0
- package/dist/core/config.js +1288 -0
- package/dist/core/context-pack.d.ts +74 -0
- package/dist/core/context-pack.js +778 -0
- package/dist/core/db.d.ts +236 -0
- package/dist/core/db.js +747 -0
- package/dist/core/detect.d.ts +69 -0
- package/dist/core/detect.js +251 -0
- package/dist/core/docs.d.ts +76 -0
- package/dist/core/docs.js +225 -0
- package/dist/core/frontmatter.d.ts +29 -0
- package/dist/core/frontmatter.js +215 -0
- package/dist/core/git.d.ts +505 -0
- package/dist/core/git.js +1453 -0
- package/dist/core/indexer.d.ts +33 -0
- package/dist/core/indexer.js +282 -0
- package/dist/core/input.d.ts +66 -0
- package/dist/core/input.js +222 -0
- package/dist/core/logger.d.ts +70 -0
- package/dist/core/logger.js +138 -0
- package/dist/core/models.d.ts +45 -0
- package/dist/core/models.js +291 -0
- package/dist/core/net-probe.d.ts +4 -0
- package/dist/core/net-probe.js +20 -0
- package/dist/core/providers/deepinfra.d.ts +23 -0
- package/dist/core/providers/deepinfra.js +44 -0
- package/dist/core/providers/index.d.ts +24 -0
- package/dist/core/providers/index.js +59 -0
- package/dist/core/providers/openrouter.d.ts +28 -0
- package/dist/core/providers/openrouter.js +82 -0
- package/dist/core/providers/spend.d.ts +86 -0
- package/dist/core/providers/spend.js +286 -0
- package/dist/core/providers/types.d.ts +56 -0
- package/dist/core/providers/types.js +39 -0
- package/dist/core/repoos.d.ts +59 -0
- package/dist/core/repoos.js +221 -0
- package/dist/core/runtime.d.ts +35 -0
- package/dist/core/runtime.js +167 -0
- package/dist/core/skills-registry.d.ts +39 -0
- package/dist/core/skills-registry.js +148 -0
- package/dist/core/skills-registry.test.d.ts +1 -0
- package/dist/core/skills-registry.test.js +52 -0
- package/dist/core/sysmem.d.ts +7 -0
- package/dist/core/sysmem.js +55 -0
- package/dist/core/tailscale.d.ts +2 -0
- package/dist/core/tailscale.js +19 -0
- package/dist/core/task.d.ts +53 -0
- package/dist/core/task.js +310 -0
- package/dist/core/toml-line.d.ts +20 -0
- package/dist/core/toml-line.js +52 -0
- package/dist/core/toml-validate.d.ts +44 -0
- package/dist/core/toml-validate.js +335 -0
- package/dist/core/tunnel-assistant.d.ts +30 -0
- package/dist/core/tunnel-assistant.js +65 -0
- package/dist/core/tunnel-registry.d.ts +96 -0
- package/dist/core/tunnel-registry.js +267 -0
- package/dist/core/tunnel-registry.test.d.ts +1 -0
- package/dist/core/tunnel-registry.test.js +309 -0
- package/dist/core/tunnel.d.ts +103 -0
- package/dist/core/tunnel.js +320 -0
- package/dist/core/types.d.ts +811 -0
- package/dist/core/types.js +27 -0
- package/dist/core/worktree-gc.d.ts +57 -0
- package/dist/core/worktree-gc.js +194 -0
- package/dist/server/__tests__/integration-job.test.d.ts +1 -0
- package/dist/server/__tests__/integration-job.test.js +129 -0
- package/dist/server/agents.d.ts +1020 -0
- package/dist/server/agents.js +4070 -0
- package/dist/server/attachments.d.ts +51 -0
- package/dist/server/attachments.js +130 -0
- package/dist/server/auto-engineering.d.ts +45 -0
- package/dist/server/auto-engineering.js +275 -0
- package/dist/server/branchless-release.d.ts +22 -0
- package/dist/server/branchless-release.js +67 -0
- package/dist/server/built-in-agents.d.ts +305 -0
- package/dist/server/built-in-agents.js +1838 -0
- package/dist/server/cto-monitor.d.ts +35 -0
- package/dist/server/cto-monitor.js +281 -0
- package/dist/server/cto.d.ts +125 -0
- package/dist/server/cto.js +590 -0
- package/dist/server/deployments.d.ts +119 -0
- package/dist/server/deployments.js +325 -0
- package/dist/server/diff-snapshot.d.ts +20 -0
- package/dist/server/diff-snapshot.js +73 -0
- package/dist/server/done.d.ts +160 -0
- package/dist/server/done.js +488 -0
- package/dist/server/freeform.d.ts +55 -0
- package/dist/server/freeform.js +179 -0
- package/dist/server/handoff.d.ts +99 -0
- package/dist/server/handoff.js +575 -0
- package/dist/server/hetzner.d.ts +45 -0
- package/dist/server/hetzner.js +117 -0
- package/dist/server/icons.d.ts +8 -0
- package/dist/server/icons.js +195 -0
- package/dist/server/integration-job.d.ts +75 -0
- package/dist/server/integration-job.js +144 -0
- package/dist/server/integration-orchestrator.d.ts +211 -0
- package/dist/server/integration-orchestrator.js +1299 -0
- package/dist/server/integration-status.d.ts +48 -0
- package/dist/server/integration-status.js +71 -0
- package/dist/server/live-index.d.ts +295 -0
- package/dist/server/live-index.js +452 -0
- package/dist/server/model-test.d.ts +20 -0
- package/dist/server/model-test.js +100 -0
- package/dist/server/ntfy.d.ts +57 -0
- package/dist/server/ntfy.js +112 -0
- package/dist/server/pm-attachments.d.ts +35 -0
- package/dist/server/pm-attachments.js +181 -0
- package/dist/server/pm-runs.d.ts +58 -0
- package/dist/server/pm-runs.js +80 -0
- package/dist/server/preview.d.ts +174 -0
- package/dist/server/preview.js +641 -0
- package/dist/server/release.d.ts +88 -0
- package/dist/server/release.js +449 -0
- package/dist/server/reload.d.ts +194 -0
- package/dist/server/reload.js +586 -0
- package/dist/server/remote-validation.d.ts +132 -0
- package/dist/server/remote-validation.js +478 -0
- package/dist/server/repo-lock.d.ts +36 -0
- package/dist/server/repo-lock.js +141 -0
- package/dist/server/review-guard.d.ts +14 -0
- package/dist/server/review-guard.js +159 -0
- package/dist/server/review.d.ts +243 -0
- package/dist/server/review.js +1211 -0
- package/dist/server/routes/agents.d.ts +6 -0
- package/dist/server/routes/agents.js +28 -0
- package/dist/server/routes/auth.d.ts +60 -0
- package/dist/server/routes/auth.js +755 -0
- package/dist/server/routes/config.d.ts +20 -0
- package/dist/server/routes/config.js +347 -0
- package/dist/server/routes/debugger.d.ts +33 -0
- package/dist/server/routes/debugger.js +360 -0
- package/dist/server/routes/deployments.d.ts +3 -0
- package/dist/server/routes/deployments.js +16 -0
- package/dist/server/routes/docs.d.ts +8 -0
- package/dist/server/routes/docs.js +135 -0
- package/dist/server/routes/helpers.d.ts +21 -0
- package/dist/server/routes/helpers.js +171 -0
- package/dist/server/routes/index.d.ts +19 -0
- package/dist/server/routes/index.js +18 -0
- package/dist/server/routes/info.d.ts +17 -0
- package/dist/server/routes/info.js +177 -0
- package/dist/server/routes/inputs.d.ts +7 -0
- package/dist/server/routes/inputs.js +125 -0
- package/dist/server/routes/model-providers.d.ts +27 -0
- package/dist/server/routes/model-providers.js +94 -0
- package/dist/server/routes/models.d.ts +3 -0
- package/dist/server/routes/models.js +36 -0
- package/dist/server/routes/notify.d.ts +2 -0
- package/dist/server/routes/notify.js +17 -0
- package/dist/server/routes/playground.d.ts +29 -0
- package/dist/server/routes/playground.js +81 -0
- package/dist/server/routes/release.d.ts +20 -0
- package/dist/server/routes/release.js +86 -0
- package/dist/server/routes/router.d.ts +12 -0
- package/dist/server/routes/router.js +32 -0
- package/dist/server/routes/skill-registry.d.ts +5 -0
- package/dist/server/routes/skill-registry.js +51 -0
- package/dist/server/routes/tasks.d.ts +58 -0
- package/dist/server/routes/tasks.js +1442 -0
- package/dist/server/routes/transcribe.d.ts +2 -0
- package/dist/server/routes/transcribe.js +124 -0
- package/dist/server/routes/types.d.ts +68 -0
- package/dist/server/routes/types.js +1 -0
- package/dist/server/routes/ui.d.ts +5 -0
- package/dist/server/routes/ui.js +81 -0
- package/dist/server/routes/utils.d.ts +3 -0
- package/dist/server/routes/utils.js +23 -0
- package/dist/server/serve-reaper.d.ts +135 -0
- package/dist/server/serve-reaper.js +491 -0
- package/dist/server/server.d.ts +49 -0
- package/dist/server/server.js +2171 -0
- package/dist/server/supervisor.d.ts +89 -0
- package/dist/server/supervisor.js +231 -0
- package/dist/server/system.d.ts +198 -0
- package/dist/server/system.js +480 -0
- package/dist/server/task-check.d.ts +45 -0
- package/dist/server/task-check.js +71 -0
- package/dist/server/task-transitions.d.ts +51 -0
- package/dist/server/task-transitions.js +78 -0
- package/dist/server/task-watchdog.d.ts +158 -0
- package/dist/server/task-watchdog.js +681 -0
- package/dist/server/test-run.d.ts +28 -0
- package/dist/server/test-run.js +81 -0
- package/dist/server/watcher.d.ts +21 -0
- package/dist/server/watcher.js +194 -0
- package/dist/server/write.d.ts +94 -0
- package/dist/server/write.js +345 -0
- package/dist/ui/assets/AgentsView-CF4wRvHh.css +1 -0
- package/dist/ui/assets/AgentsView-D-myL-kA.js +1 -0
- package/dist/ui/assets/ContextView-D3TC-RSp.css +1 -0
- package/dist/ui/assets/ContextView-DsOqAdyE.js +4 -0
- package/dist/ui/assets/DashboardView-BOhsIJqO.js +1 -0
- package/dist/ui/assets/DashboardView-Ck9o0RP2.css +1 -0
- package/dist/ui/assets/DeploymentsView--iIUVfEH.css +1 -0
- package/dist/ui/assets/DeploymentsView-DXu_FYR2.js +1 -0
- package/dist/ui/assets/InputsView-24HVbVPk.js +1 -0
- package/dist/ui/assets/InputsView-BLb7GHtx.css +1 -0
- package/dist/ui/assets/LoginView-6QlGg6cx.css +1 -0
- package/dist/ui/assets/LoginView-BqupI0Lt.js +1 -0
- package/dist/ui/assets/ReleasesView-BEG1A0YH.css +1 -0
- package/dist/ui/assets/ReleasesView-CAgMuxpe.js +3 -0
- package/dist/ui/assets/SettingsView-CA-NYSMK.css +1 -0
- package/dist/ui/assets/SettingsView-O3iZSnjg.js +3 -0
- package/dist/ui/assets/WorkView-D8Tl6qwy.js +1 -0
- package/dist/ui/assets/WorkView-DMYL2s3W.css +1 -0
- package/dist/ui/assets/abnfDiagram-VCTEODGH-J5lkSnEE.js +1 -0
- package/dist/ui/assets/arc-BHZnmhhM.js +1 -0
- package/dist/ui/assets/architecture-7GRP2DOG-pLdFt-xS.js +1 -0
- package/dist/ui/assets/architectureDiagram-5GKGNRK7-C3k89uKX.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-I7D4REHJ-AWNoBzop.js +129 -0
- package/dist/ui/assets/button-lbikqSph.js +1 -0
- package/dist/ui/assets/c4Diagram-7LVT6UL2-DKG1D5gd.js +38 -0
- package/dist/ui/assets/channel-gA4qBv3A.js +1 -0
- package/dist/ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/ui/assets/chunk-4HAMMTFA-D1YR8lF_.js +62 -0
- package/dist/ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/ui/assets/chunk-75Z2AOVW-5GuBxfsL.js +2 -0
- package/dist/ui/assets/chunk-DU6HZSFF-DfdNNqwa.js +127 -0
- package/dist/ui/assets/chunk-F27PBJKO-BPZzinQ4.js +1 -0
- package/dist/ui/assets/chunk-FOHPRMQF-l2z4K2jD.js +161 -0
- package/dist/ui/assets/chunk-GMAD6QVW-DGWvSKj5.js +72 -0
- package/dist/ui/assets/chunk-GVQU2GXP-D6Aw75wJ.js +1 -0
- package/dist/ui/assets/chunk-IMKFNOWR-B8mMBKCW.js +231 -0
- package/dist/ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/ui/assets/chunk-L3NEJ4N5-D5OJuXYE.js +1 -0
- package/dist/ui/assets/chunk-OSK3NFVY-B7r9w2UW.js +10 -0
- package/dist/ui/assets/chunk-P2QGCYS3-C74X4AQX.js +1 -0
- package/dist/ui/assets/chunk-POPQ4Y6H-C5_u1gUS.js +1 -0
- package/dist/ui/assets/chunk-PWAF6VOD-DfoTLtxn.js +1 -0
- package/dist/ui/assets/chunk-SHT3W25Y-D40m2eqz.js +168 -0
- package/dist/ui/assets/chunk-SVP7TREG-euxi0Kap.js +88 -0
- package/dist/ui/assets/chunk-TICWLB2K-fkdxngzN.js +206 -0
- package/dist/ui/assets/chunk-XXDRQBXY-DFBUG-OT.js +1 -0
- package/dist/ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/ui/assets/classDiagram-ZZMXUADV-BzW8bq2P.js +1 -0
- package/dist/ui/assets/classDiagram-v2-VYDZK3BY-BzW8bq2P.js +1 -0
- package/dist/ui/assets/cose-bilkent-JH36ORCC-BTcgMon1.js +1 -0
- package/dist/ui/assets/cynefin-OW5HDTMX-B5m_COsS.js +1 -0
- package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-C-AG6CGe.js +62 -0
- package/dist/ui/assets/cytoscape.esm-FBntH-9t.js +321 -0
- package/dist/ui/assets/dagre-GXQ25YYZ-DDfDnp8L.js +4 -0
- package/dist/ui/assets/dagre-PrKaheQc.js +1 -0
- package/dist/ui/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/ui/assets/diagram-S7CK7UJ4-DjQZAlEY.js +30 -0
- package/dist/ui/assets/diagram-UQ7AKVKN-DAZ9yp1B.js +41 -0
- package/dist/ui/assets/diagram-VSXAHHWV-BWepUKJD.js +3 -0
- package/dist/ui/assets/diagram-VX7I27RA-EcV1J81b.js +24 -0
- package/dist/ui/assets/diagram-Z3DM3KII-Dbz7XU_K.js +24 -0
- package/dist/ui/assets/dist-DTg6UBE_.js +1 -0
- package/dist/ui/assets/ebnfDiagram-PWID7BFC-BUyQw8Mm.js +1 -0
- package/dist/ui/assets/erDiagram-RLTQ6QDP-WVf8ukKt.js +99 -0
- package/dist/ui/assets/eventmodeling-NTZA5JFV-DHqyLOAn.js +1 -0
- package/dist/ui/assets/flowDiagram-HODETNUW-CYk_ze5K.js +1 -0
- package/dist/ui/assets/ganttDiagram-EL5Y4UJY-rB4MZuB-.js +292 -0
- package/dist/ui/assets/gitGraph-4MIJSDKK-BOTWM8dY.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-eYscuhtP.js +106 -0
- package/dist/ui/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/ui/assets/index-BACBfnga.css +1 -0
- package/dist/ui/assets/index-BOQ-ust6.js +26 -0
- package/dist/ui/assets/info-A6RAGUB7-_67wOORe.js +1 -0
- package/dist/ui/assets/infoDiagram-27XIBGKW-XVXbddTA.js +2 -0
- package/dist/ui/assets/init-C-OQMol4.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-5VMMS53U-ClLNiEru.js +70 -0
- package/dist/ui/assets/journeyDiagram-3NMN7TZE-NRawjR6i.js +139 -0
- package/dist/ui/assets/kanban-definition-UXKFOSKX-BgSzBfIx.js +89 -0
- package/dist/ui/assets/katex-DolUETbr.js +257 -0
- package/dist/ui/assets/line-KAyfuul4.js +1 -0
- package/dist/ui/assets/linear-cCCPOpu7.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-CgXZk1iN.js +7 -0
- package/dist/ui/assets/mermaid.core-BUUkChHW.js +44 -0
- package/dist/ui/assets/mindmap-definition-YA3MSWOX-Cpn7vW4r.js +96 -0
- package/dist/ui/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/ui/assets/packet-AYTQ26CC-0lZwM-d6.js +1 -0
- package/dist/ui/assets/path-COt_16Va.js +1 -0
- package/dist/ui/assets/pegDiagram-XKGWAZYB-impy0inn.js +1 -0
- package/dist/ui/assets/pie-WAS4IAKB-CqfdGHYg.js +1 -0
- package/dist/ui/assets/pieDiagram-E7YTZNPT-CxjL4I1h.js +39 -0
- package/dist/ui/assets/quadrantDiagram-AXDQQJYC-Cjd1QMIe.js +7 -0
- package/dist/ui/assets/radar-RG4KPBEZ-CRkQIwiv.js +1 -0
- package/dist/ui/assets/railroad-74A4TZTK-i67XSWTB.js +1 -0
- package/dist/ui/assets/railroad-abnf-HS5TGJTU-Cv9m5aS3.js +1 -0
- package/dist/ui/assets/railroad-ebnf-LZEXJU2U-C5ZIn-Jh.js +1 -0
- package/dist/ui/assets/railroad-peg-WCYAUIDC-CGra9Oq-.js +1 -0
- package/dist/ui/assets/railroadDiagram-O6MQD6OU-_pK0qyaY.js +1 -0
- package/dist/ui/assets/releases-B0WNV-SY.js +1 -0
- package/dist/ui/assets/repoos-cto-square.webp +0 -0
- package/dist/ui/assets/repoos-helpful-pm-square.webp +0 -0
- package/dist/ui/assets/repoos-orchestrator-square.webp +0 -0
- package/dist/ui/assets/repoos-performance-agent-square.webp +0 -0
- package/dist/ui/assets/repoos-ross-from-friends-square.webp +0 -0
- package/dist/ui/assets/repoos-tech-debt-agent-square.webp +0 -0
- package/dist/ui/assets/requirementDiagram-BXWQKSXE-CRrFX-yw.js +84 -0
- package/dist/ui/assets/rough.esm-By172zw-.js +1 -0
- package/dist/ui/assets/sankeyDiagram-P5KCCOFB-X_xFRj9E.js +40 -0
- package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-Byb49lKf.js +162 -0
- package/dist/ui/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dist/ui/assets/src-BH-TyZbA.js +1 -0
- package/dist/ui/assets/stateDiagram-D77RDMKH-CzaPrqYF.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BCXckz7K.js +1 -0
- package/dist/ui/assets/swimlanes-42K2YHIH-Cx_1lnqr.js +1 -0
- package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-54qsKi6A.js +8 -0
- package/dist/ui/assets/timeline-definition-24CTP7MA-B1IbZPQ-.js +120 -0
- package/dist/ui/assets/treeView-Q6P3EWNA-DFWhVE3d.js +1 -0
- package/dist/ui/assets/treemap-WGGIJYW6-BmbYPThn.js +1 -0
- package/dist/ui/assets/vendor-ui-DPm7zLxi.js +3 -0
- package/dist/ui/assets/vendor-vue-CobgyEJp.js +1 -0
- package/dist/ui/assets/vennDiagram-4TSXK5OY-Ae2AtRpt.js +34 -0
- package/dist/ui/assets/wardley-WFR3VGLG-BtD4X4tL.js +1 -0
- package/dist/ui/assets/wardleyDiagram-VM6X3IG4-B3YN2fTd.js +78 -0
- package/dist/ui/assets/xychartDiagram-S5SC5T6Z-CIzJfMKA.js +7 -0
- package/dist/ui/favicon.svg +11 -0
- package/dist/ui/index.html +49 -0
- package/dist/ui/sw.js +48 -0
- package/package.json +91 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System resource sampler. Uses `node:os` for machine-wide facts and `ps` for
|
|
3
|
+
* per-process CPU/memory stats. One `ps` invocation per interval covers all
|
|
4
|
+
* tracked PIDs — never one call per process. Zero runtime dependencies.
|
|
5
|
+
*
|
|
6
|
+
* Orphan detection: a live process not in the AgentRunner registry, or whose
|
|
7
|
+
* ppid is no longer the server's pid, is marked orphaned.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync, realpathSync, writeFileSync, mkdirSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { cpus, totalmem, freemem, loadavg, platform } from "node:os";
|
|
13
|
+
import { availableMemBytes } from "../core/sysmem.js";
|
|
14
|
+
const OWNERSHIP_FILE = "repoos-ownership.json";
|
|
15
|
+
const KB = 1024;
|
|
16
|
+
function safeExecFileSync(cmd, args) {
|
|
17
|
+
try {
|
|
18
|
+
return execFileSync(cmd, args, {
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
21
|
+
timeout: 5000,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function parsePsLine(line) {
|
|
29
|
+
const parts = line.trim().split(/\s+/);
|
|
30
|
+
if (parts.length < 6)
|
|
31
|
+
return null;
|
|
32
|
+
const pid = Number(parts[0]);
|
|
33
|
+
const ppid = Number(parts[1]);
|
|
34
|
+
const cpuPercent = Number(parts[2]);
|
|
35
|
+
const memPercent = Number(parts[3]);
|
|
36
|
+
const rssKB = Number(parts[4]);
|
|
37
|
+
const elapsed = parts.slice(5).join(" ");
|
|
38
|
+
if (isNaN(pid) || isNaN(ppid) || isNaN(cpuPercent) || isNaN(memPercent) || isNaN(rssKB)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return { pid, ppid, cpuPercent, memPercent, rssKB, elapsed };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse `ps -o pid=,ppid=,%cpu=,%mem=,rss=,etime=` output into records.
|
|
45
|
+
* One `ps` call covers all PIDs at once. Export for testing.
|
|
46
|
+
*/
|
|
47
|
+
export function parsePsOutput(output) {
|
|
48
|
+
return output
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map(parsePsLine)
|
|
51
|
+
.filter((r) => r !== null);
|
|
52
|
+
}
|
|
53
|
+
function machineInfo() {
|
|
54
|
+
return {
|
|
55
|
+
cpuCount: cpus().length,
|
|
56
|
+
totalMem: totalmem(),
|
|
57
|
+
freeMem: freemem(),
|
|
58
|
+
availableMem: availableMemBytes(),
|
|
59
|
+
loadavg: loadavg(),
|
|
60
|
+
platform: platform(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
let repoStatsCache = null;
|
|
64
|
+
const REPO_STATS_TTL_MS = 30_000;
|
|
65
|
+
/**
|
|
66
|
+
* git-derived codebase size: worktree count, tracked-file count, and total
|
|
67
|
+
* lines across tracked text files. Three `git` spawns, so memoised for 30s —
|
|
68
|
+
* the SSE loop calls this every 5s and these numbers change on the order of
|
|
69
|
+
* commits, not seconds. Returns the last good value (or null) on git failure.
|
|
70
|
+
*/
|
|
71
|
+
export function sampleRepoStats(root, worktreeWarnThreshold = 20) {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (repoStatsCache &&
|
|
74
|
+
repoStatsCache.root === root &&
|
|
75
|
+
now - repoStatsCache.at < REPO_STATS_TTL_MS) {
|
|
76
|
+
return { ...repoStatsCache.stats, worktreeWarnThreshold };
|
|
77
|
+
}
|
|
78
|
+
const worktreeOut = safeExecFileSync("git", ["-C", root, "worktree", "list", "--porcelain"]);
|
|
79
|
+
const filesOut = safeExecFileSync("git", ["-C", root, "ls-files"]);
|
|
80
|
+
if (worktreeOut === null || filesOut === null) {
|
|
81
|
+
// Git unavailable or not a repo: keep serving a stale value for THIS root,
|
|
82
|
+
// never another root's.
|
|
83
|
+
return repoStatsCache?.root === root
|
|
84
|
+
? { ...repoStatsCache.stats, worktreeWarnThreshold }
|
|
85
|
+
: null;
|
|
86
|
+
}
|
|
87
|
+
// Empty pattern matches every line; -I skips binary files. One spawn yields
|
|
88
|
+
// `path:<linecount>` per text file.
|
|
89
|
+
const locOut = safeExecFileSync("git", ["-C", root, "grep", "-I", "--count", "-e", ""]);
|
|
90
|
+
let linesOfCode = 0;
|
|
91
|
+
if (locOut !== null) {
|
|
92
|
+
for (const line of locOut.split("\n")) {
|
|
93
|
+
const m = line.match(/:(\d+)$/);
|
|
94
|
+
if (m)
|
|
95
|
+
linesOfCode += Number(m[1]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const stats = {
|
|
99
|
+
worktrees: (worktreeOut.match(/^worktree /gm) ?? []).length,
|
|
100
|
+
trackedFiles: filesOut.split("\n").filter(Boolean).length,
|
|
101
|
+
linesOfCode,
|
|
102
|
+
worktreeWarnThreshold,
|
|
103
|
+
};
|
|
104
|
+
repoStatsCache = { at: now, root, stats };
|
|
105
|
+
return stats;
|
|
106
|
+
}
|
|
107
|
+
function sampleProcesses(pids) {
|
|
108
|
+
if (pids.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const pidArgs = pids.map(String);
|
|
111
|
+
const output = safeExecFileSync("ps", [
|
|
112
|
+
"-o",
|
|
113
|
+
"pid=,ppid=,%cpu=,%mem=,rss=,etime=",
|
|
114
|
+
"-p",
|
|
115
|
+
...pidArgs,
|
|
116
|
+
]);
|
|
117
|
+
if (!output)
|
|
118
|
+
return [];
|
|
119
|
+
return parsePsOutput(output);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Persist RepoOS process ownership so orphan detection survives a server restart.
|
|
123
|
+
*/
|
|
124
|
+
function readOwnership(cacheDir) {
|
|
125
|
+
const file = join(cacheDir, OWNERSHIP_FILE);
|
|
126
|
+
if (!existsSync(file))
|
|
127
|
+
return [];
|
|
128
|
+
try {
|
|
129
|
+
const raw = readFileSync(file, "utf8");
|
|
130
|
+
return JSON.parse(raw);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function writeOwnership(cacheDir, records) {
|
|
137
|
+
try {
|
|
138
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
139
|
+
writeFileSync(join(cacheDir, OWNERSHIP_FILE), JSON.stringify(records, null, 2), "utf8");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* best-effort: sampling should never fail on a writable-directory problem */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update the persisted ownership records: add entries for newly-spawned agents
|
|
147
|
+
* (registry entries not yet persisted) and remove entries whose PIDs are dead.
|
|
148
|
+
*/
|
|
149
|
+
function syncOwnership(cacheDir, running, livePids) {
|
|
150
|
+
const existing = readOwnership(cacheDir);
|
|
151
|
+
const byPid = new Map();
|
|
152
|
+
for (const r of existing)
|
|
153
|
+
byPid.set(r.pid, r);
|
|
154
|
+
for (const info of running) {
|
|
155
|
+
if (info.pid <= 0)
|
|
156
|
+
continue;
|
|
157
|
+
if (!byPid.has(info.pid)) {
|
|
158
|
+
byPid.set(info.pid, {
|
|
159
|
+
pid: info.pid,
|
|
160
|
+
taskId: info.id,
|
|
161
|
+
startedAt: info.startedAt,
|
|
162
|
+
executable: "",
|
|
163
|
+
workdir: info.workdir ?? "",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const out = [];
|
|
168
|
+
for (const [pid, rec] of byPid) {
|
|
169
|
+
if (livePids.has(pid) || running.some((r) => r.pid === pid)) {
|
|
170
|
+
out.push(rec);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
writeOwnership(cacheDir, out);
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A stray count at or above this is a real problem, not untidiness: it is the
|
|
178
|
+
* regime where #0211 failed its close-out gate twice on unrelated flaky tests.
|
|
179
|
+
*/
|
|
180
|
+
const SERVE_WARN_THRESHOLD = 4;
|
|
181
|
+
/** Matches both a compiled (`dist/cli/index.js`) and a dev (`src/cli/index.ts`) serve. */
|
|
182
|
+
const SERVE_CMD_RE = /[/\\](?:dist[/\\]cli[/\\]index\.js|src[/\\]cli[/\\]index\.ts)\s+serve\b/;
|
|
183
|
+
/** Extract the repo root a serve process is running from, or null. */
|
|
184
|
+
export function parseServeRoot(command) {
|
|
185
|
+
const m = command.match(/(\S+?)[/\\](?:dist[/\\]cli[/\\]index\.js|src[/\\]cli[/\\]index\.ts)\s+serve\b/);
|
|
186
|
+
return m ? m[1] : null;
|
|
187
|
+
}
|
|
188
|
+
/** True when two repo-root paths point at the same directory (symlink-safe). */
|
|
189
|
+
function sameRoot(a, b) {
|
|
190
|
+
if (!a || !b)
|
|
191
|
+
return false;
|
|
192
|
+
if (a === b)
|
|
193
|
+
return true;
|
|
194
|
+
try {
|
|
195
|
+
return realpathSync(a) === realpathSync(b);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** Extract `--port N` from a serve command line, or null. */
|
|
202
|
+
export function parseServePort(command) {
|
|
203
|
+
const m = command.match(/--port[= ](\d+)/);
|
|
204
|
+
return m ? Number(m[1]) : null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Parse `ps ax -o pid=,ppid=,command=` output into serve-process records.
|
|
208
|
+
*
|
|
209
|
+
* Classification order matters. The control plane is checked first because the
|
|
210
|
+
* reload handoff deliberately detaches it, so it also runs with `ppid` 1 and
|
|
211
|
+
* would otherwise look abandoned. Everything else that RepoOS did not start is
|
|
212
|
+
* abandoned only if its parent is gone — a fixture server with a live vitest
|
|
213
|
+
* worker above it is the gate doing its job, not a leak.
|
|
214
|
+
*
|
|
215
|
+
* Exported for testing so classification can be exercised without spawning servers.
|
|
216
|
+
*/
|
|
217
|
+
export function parseServeScan(output, serverPid, knownPids, rootExists, ownRoot) {
|
|
218
|
+
const rows = [];
|
|
219
|
+
const livePids = new Set();
|
|
220
|
+
for (const line of output.split("\n")) {
|
|
221
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/);
|
|
222
|
+
if (!m)
|
|
223
|
+
continue;
|
|
224
|
+
const pid = Number(m[1]);
|
|
225
|
+
livePids.add(pid);
|
|
226
|
+
rows.push({ pid, ppid: Number(m[2]), command: m[3] });
|
|
227
|
+
}
|
|
228
|
+
const processes = [];
|
|
229
|
+
for (const { pid, ppid, command } of rows) {
|
|
230
|
+
if (!SERVE_CMD_RE.test(command))
|
|
231
|
+
continue;
|
|
232
|
+
const root = parseServeRoot(command);
|
|
233
|
+
const rootIsLive = rootExists(root);
|
|
234
|
+
// A parent of 1 means the process was reparented after its spawner died.
|
|
235
|
+
const supervised = ppid !== 1 && livePids.has(ppid);
|
|
236
|
+
const kind = pid === serverPid
|
|
237
|
+
? "control-plane"
|
|
238
|
+
: knownPids.has(pid)
|
|
239
|
+
? "known-preview"
|
|
240
|
+
: supervised
|
|
241
|
+
? "in-flight"
|
|
242
|
+
: // A detached serve for another repo whose root still exists is that
|
|
243
|
+
// repo's control plane (also ppid 1) — not ours to touch. Only an
|
|
244
|
+
// orphan of OUR root, or one whose root is gone, is a real stray.
|
|
245
|
+
ownRoot && root && rootIsLive && !sameRoot(root, ownRoot)
|
|
246
|
+
? "foreign"
|
|
247
|
+
: "stray";
|
|
248
|
+
processes.push({
|
|
249
|
+
pid,
|
|
250
|
+
ppid,
|
|
251
|
+
port: parseServePort(command),
|
|
252
|
+
root,
|
|
253
|
+
rootExists: rootIsLive,
|
|
254
|
+
kind,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const strays = processes.filter((p) => p.kind === "stray");
|
|
258
|
+
const deadRoot = strays.filter((p) => !p.rootExists).length;
|
|
259
|
+
const level = strays.length === 0 ? "ok" : strays.length >= SERVE_WARN_THRESHOLD ? "warn" : "notice";
|
|
260
|
+
return {
|
|
261
|
+
total: processes.length,
|
|
262
|
+
strays: strays.length,
|
|
263
|
+
inFlight: processes.filter((p) => p.kind === "in-flight").length,
|
|
264
|
+
deadRoot,
|
|
265
|
+
level,
|
|
266
|
+
processes,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
let serveScanCache = null;
|
|
270
|
+
/** The census walks every process on the machine, so it runs far less often than the 5s sample. */
|
|
271
|
+
const SERVE_SCAN_TTL_MS = 20_000;
|
|
272
|
+
/**
|
|
273
|
+
* Census live `repoos serve` processes. Cached — `ps ax` lists every process on
|
|
274
|
+
* the machine, which is much heavier than the targeted per-PID sample.
|
|
275
|
+
*/
|
|
276
|
+
export function scanServeProcesses(serverPid, knownPids, ownRoot) {
|
|
277
|
+
const now = Date.now();
|
|
278
|
+
if (serveScanCache && now - serveScanCache.at < SERVE_SCAN_TTL_MS)
|
|
279
|
+
return serveScanCache.scan;
|
|
280
|
+
const output = safeExecFileSync("ps", ["ax", "-o", "pid=,ppid=,command="]);
|
|
281
|
+
if (output === null)
|
|
282
|
+
return serveScanCache?.scan ?? null;
|
|
283
|
+
const scan = parseServeScan(output, serverPid, knownPids, (root) => root !== null && existsSync(root), ownRoot);
|
|
284
|
+
serveScanCache = { at: now, scan };
|
|
285
|
+
return scan;
|
|
286
|
+
}
|
|
287
|
+
/** Drop the cached census. Exported for tests. */
|
|
288
|
+
export function resetServeScanCache() {
|
|
289
|
+
serveScanCache = null;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Reap every `stray` process the census finds — a process whose spawning
|
|
293
|
+
* parent is confirmed dead (reparented to PID 1). This is safe by
|
|
294
|
+
* construction: `in-flight` processes (a live parent still supervising them,
|
|
295
|
+
* e.g. the close-out gate's own test suite mid-run) are never included, and
|
|
296
|
+
* neither is the control plane or a known preview. Killing a stray destroys
|
|
297
|
+
* no live work — nothing will ever come back to claim it.
|
|
298
|
+
*
|
|
299
|
+
* Root cause this offsets (#0216): a failed reload-replacement handoff kills
|
|
300
|
+
* only the direct child process (`ReloadManager.killChild`, plain SIGTERM,
|
|
301
|
+
* not a process-group kill), so anything that child spawned before failing
|
|
302
|
+
* its readiness check survives as an orphan. Under repeated failed reload
|
|
303
|
+
* attempts this accumulates fast enough to strain the whole machine, not just
|
|
304
|
+
* starve the close-out gate. This periodic sweep is the general safety net;
|
|
305
|
+
* the process-group fix in reload.ts is the more targeted root-cause fix,
|
|
306
|
+
* tracked separately.
|
|
307
|
+
*/
|
|
308
|
+
export function reapStrayServeProcesses(serverPid, knownPids, kill = process.kill.bind(process), scan, ownRoot) {
|
|
309
|
+
const doScan = scan ??
|
|
310
|
+
((p, k) => {
|
|
311
|
+
resetServeScanCache();
|
|
312
|
+
return scanServeProcesses(p, k, ownRoot);
|
|
313
|
+
});
|
|
314
|
+
const result = doScan(serverPid, knownPids);
|
|
315
|
+
if (!result)
|
|
316
|
+
return 0;
|
|
317
|
+
let reaped = 0;
|
|
318
|
+
for (const p of result.processes) {
|
|
319
|
+
if (p.kind !== "stray")
|
|
320
|
+
continue;
|
|
321
|
+
try {
|
|
322
|
+
kill(p.pid, "SIGTERM");
|
|
323
|
+
reaped++;
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
/* already gone */
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return reaped;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Kill a single process by PID: SIGTERM first, escalating to SIGKILL after a
|
|
333
|
+
* grace period if it hasn't exited. Same sequence AgentRunner.stop() and the
|
|
334
|
+
* stray-serve reaper already use elsewhere. Backs the manual "Kill" action in
|
|
335
|
+
* the System Resources panel — callers must verify the PID is one RepoOS
|
|
336
|
+
* itself is tracking (a fresh sampleSystem()/scanServeProcesses() result)
|
|
337
|
+
* before calling this; it does not re-check.
|
|
338
|
+
*/
|
|
339
|
+
export function killTrackedProcess(pid, kill = process.kill.bind(process)) {
|
|
340
|
+
try {
|
|
341
|
+
kill(pid, "SIGTERM");
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return false; // already gone, or no such process
|
|
345
|
+
}
|
|
346
|
+
const t = setTimeout(() => {
|
|
347
|
+
try {
|
|
348
|
+
kill(pid, "SIGKILL");
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
/* already gone */
|
|
352
|
+
}
|
|
353
|
+
}, 3000);
|
|
354
|
+
t.unref?.();
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Sample the system: machine facts, RepoOS process totals, and a per-process
|
|
359
|
+
* breakdown with orphan detection. One `ps` call covers all tracked + agent PIDs.
|
|
360
|
+
*/
|
|
361
|
+
export function sampleSystem(opts) {
|
|
362
|
+
const machine = machineInfo();
|
|
363
|
+
const serverPid = opts.serverPid;
|
|
364
|
+
const agentPids = new Set(opts.runningAgents.map((a) => a.pid).filter((p) => p > 0));
|
|
365
|
+
const idByPid = new Map();
|
|
366
|
+
for (const a of opts.runningAgents) {
|
|
367
|
+
if (a.pid > 0)
|
|
368
|
+
idByPid.set(a.pid, a.id);
|
|
369
|
+
}
|
|
370
|
+
const allPids = [serverPid, ...agentPids];
|
|
371
|
+
const ownership = syncOwnership(opts.cacheDir, opts.runningAgents, new Set(allPids));
|
|
372
|
+
const ownershipByPid = new Map();
|
|
373
|
+
for (const o of ownership)
|
|
374
|
+
ownershipByPid.set(o.pid, o);
|
|
375
|
+
const records = sampleProcesses(allPids);
|
|
376
|
+
const psByPid = new Map();
|
|
377
|
+
for (const r of records)
|
|
378
|
+
psByPid.set(r.pid, r);
|
|
379
|
+
let totalCpu = 0;
|
|
380
|
+
let totalMem = 0;
|
|
381
|
+
let totalMemPercent = 0;
|
|
382
|
+
const processes = [];
|
|
383
|
+
for (const pid of allPids) {
|
|
384
|
+
const ps = psByPid.get(pid);
|
|
385
|
+
if (!ps)
|
|
386
|
+
continue;
|
|
387
|
+
const memBytes = ps.rssKB * KB;
|
|
388
|
+
totalCpu += ps.cpuPercent;
|
|
389
|
+
totalMem += memBytes;
|
|
390
|
+
totalMemPercent += ps.memPercent;
|
|
391
|
+
const owned = ownershipByPid.get(pid);
|
|
392
|
+
const taskId = idByPid.get(pid) ?? owned?.taskId ?? null;
|
|
393
|
+
const orphaned = ps.ppid !== serverPid && pid !== serverPid;
|
|
394
|
+
processes.push({
|
|
395
|
+
pid,
|
|
396
|
+
taskId,
|
|
397
|
+
cpuPercent: ps.cpuPercent,
|
|
398
|
+
memBytes,
|
|
399
|
+
elapsed: ps.elapsed,
|
|
400
|
+
orphaned,
|
|
401
|
+
unverified: !idByPid.has(pid) && !owned,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
// Discover orphaned RepoOS processes: PIDs that are NOT in the current
|
|
405
|
+
// running registry but WERE persisted as RepoOS-owned. Also include
|
|
406
|
+
// REGISTERED agents whose ppid is no longer the server.
|
|
407
|
+
const seenPids = new Set(allPids);
|
|
408
|
+
for (const [pid, owned] of ownershipByPid) {
|
|
409
|
+
if (seenPids.has(pid))
|
|
410
|
+
continue;
|
|
411
|
+
if (!isProcessAlive(pid))
|
|
412
|
+
continue;
|
|
413
|
+
const ps = sampleSingleProcess(pid);
|
|
414
|
+
if (!ps)
|
|
415
|
+
continue;
|
|
416
|
+
const memBytes = ps.rssKB * KB;
|
|
417
|
+
totalCpu += ps.cpuPercent;
|
|
418
|
+
totalMem += memBytes;
|
|
419
|
+
totalMemPercent += ps.memPercent;
|
|
420
|
+
processes.push({
|
|
421
|
+
pid,
|
|
422
|
+
taskId: owned.taskId,
|
|
423
|
+
cpuPercent: ps.cpuPercent,
|
|
424
|
+
memBytes,
|
|
425
|
+
elapsed: ps.elapsed,
|
|
426
|
+
orphaned: true,
|
|
427
|
+
unverified: false,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
// Sort: orphans first, then by CPU descending
|
|
431
|
+
processes.sort((a, b) => {
|
|
432
|
+
if (a.orphaned !== b.orphaned)
|
|
433
|
+
return a.orphaned ? -1 : 1;
|
|
434
|
+
return b.cpuPercent - a.cpuPercent;
|
|
435
|
+
});
|
|
436
|
+
return {
|
|
437
|
+
machine,
|
|
438
|
+
totals: {
|
|
439
|
+
cpuPercent: Math.round(totalCpu * 10) / 10,
|
|
440
|
+
memBytes: totalMem,
|
|
441
|
+
memPercent: Math.round(totalMemPercent * 10) / 10,
|
|
442
|
+
},
|
|
443
|
+
processes,
|
|
444
|
+
serve: scanServeProcesses(serverPid, new Set(opts.knownServePids ?? []), opts.root),
|
|
445
|
+
repo: opts.root ? sampleRepoStats(opts.root, opts.worktreeWarnThreshold) : null,
|
|
446
|
+
serverPid,
|
|
447
|
+
at: new Date().toISOString(),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function isProcessAlive(pid) {
|
|
451
|
+
try {
|
|
452
|
+
process.kill(pid, 0);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function sampleSingleProcess(pid) {
|
|
460
|
+
const output = safeExecFileSync("ps", [
|
|
461
|
+
"-o",
|
|
462
|
+
"pid=,ppid=,%cpu=,%mem=,rss=,etime=",
|
|
463
|
+
"-p",
|
|
464
|
+
String(pid),
|
|
465
|
+
]);
|
|
466
|
+
if (!output)
|
|
467
|
+
return null;
|
|
468
|
+
const records = parsePsOutput(output);
|
|
469
|
+
return records[0] ?? null;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* True when the current platform can run the `ps` command with the expected
|
|
473
|
+
* flags (pid=,ppid=,%cpu=,%mem=,rss=,etime=). Windows `ps` (PowerShell alias)
|
|
474
|
+
* does not support these flags, so per-process stats are unavailable there.
|
|
475
|
+
*/
|
|
476
|
+
export function psAvailable() {
|
|
477
|
+
if (platform() === "win32")
|
|
478
|
+
return false;
|
|
479
|
+
return safeExecFileSync("ps", ["-o", "pid=", "-p", String(process.pid)]) !== null;
|
|
480
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the `repoos check` invocations RepoOS's own server spawns directly
|
|
3
|
+
* for a task — the handoff-finalize check (handoff.ts's runCheck()) and the
|
|
4
|
+
* MTD merge-gate check (integration-orchestrator.ts's validateCandidate) —
|
|
5
|
+
* so the Debug tab (0310) can show when each ran, how long it took, whether
|
|
6
|
+
* it passed, and stream a currently-running one's output live.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately in-memory only, same tradeoff as TestRunManager: this is
|
|
9
|
+
* observability for the current server session, not a durable audit log. A
|
|
10
|
+
* server restart mid-check simply loses the in-flight run; the check itself
|
|
11
|
+
* gets re-attempted by whatever triggered it.
|
|
12
|
+
*/
|
|
13
|
+
export type TaskCheckKind = "handoff-finalize" | "merge-gate";
|
|
14
|
+
export interface TaskCheckRun {
|
|
15
|
+
id: string;
|
|
16
|
+
taskId: string;
|
|
17
|
+
kind: TaskCheckKind;
|
|
18
|
+
startedAt: string;
|
|
19
|
+
finishedAt: string | null;
|
|
20
|
+
durationMs: number | null;
|
|
21
|
+
running: boolean;
|
|
22
|
+
/** null while running; true/false once done() is called. */
|
|
23
|
+
passed: boolean | null;
|
|
24
|
+
code: number | null;
|
|
25
|
+
output: string;
|
|
26
|
+
}
|
|
27
|
+
export type TaskCheckEventKind = "started" | "output" | "done";
|
|
28
|
+
export type TaskCheckListener = (run: TaskCheckRun, event: TaskCheckEventKind, chunk?: string) => void;
|
|
29
|
+
export interface TaskCheckHandle {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
chunk(text: string): void;
|
|
32
|
+
done(code: number | null): void;
|
|
33
|
+
}
|
|
34
|
+
export declare class TaskCheckManager {
|
|
35
|
+
private runsByTask;
|
|
36
|
+
private seq;
|
|
37
|
+
/**
|
|
38
|
+
* Begins tracking one check run for `taskId`. Returns a handle the caller
|
|
39
|
+
* feeds output chunks into as the underlying subprocess streams them, and
|
|
40
|
+
* calls `done()` on once the process exits.
|
|
41
|
+
*/
|
|
42
|
+
start(taskId: string, kind: TaskCheckKind, onEvent: TaskCheckListener): TaskCheckHandle;
|
|
43
|
+
/** Past + in-progress runs for a task, oldest first. */
|
|
44
|
+
getRuns(taskId: string): TaskCheckRun[];
|
|
45
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the `repoos check` invocations RepoOS's own server spawns directly
|
|
3
|
+
* for a task — the handoff-finalize check (handoff.ts's runCheck()) and the
|
|
4
|
+
* MTD merge-gate check (integration-orchestrator.ts's validateCandidate) —
|
|
5
|
+
* so the Debug tab (0310) can show when each ran, how long it took, whether
|
|
6
|
+
* it passed, and stream a currently-running one's output live.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately in-memory only, same tradeoff as TestRunManager: this is
|
|
9
|
+
* observability for the current server session, not a durable audit log. A
|
|
10
|
+
* server restart mid-check simply loses the in-flight run; the check itself
|
|
11
|
+
* gets re-attempted by whatever triggered it.
|
|
12
|
+
*/
|
|
13
|
+
/** Cap on retained output per run so a very noisy check can't grow this
|
|
14
|
+
* without bound in memory — old text is dropped from the front. */
|
|
15
|
+
const MAX_BUFFERED_CHARS = 500_000;
|
|
16
|
+
/** How many past runs to retain per task — enough to inspect recent
|
|
17
|
+
* history without letting a task that gets checked repeatedly (retries)
|
|
18
|
+
* grow this without bound. */
|
|
19
|
+
const MAX_RUNS_PER_TASK = 10;
|
|
20
|
+
export class TaskCheckManager {
|
|
21
|
+
runsByTask = new Map();
|
|
22
|
+
seq = 0;
|
|
23
|
+
/**
|
|
24
|
+
* Begins tracking one check run for `taskId`. Returns a handle the caller
|
|
25
|
+
* feeds output chunks into as the underlying subprocess streams them, and
|
|
26
|
+
* calls `done()` on once the process exits.
|
|
27
|
+
*/
|
|
28
|
+
start(taskId, kind, onEvent) {
|
|
29
|
+
const id = `${taskId}-${kind}-${++this.seq}`;
|
|
30
|
+
const run = {
|
|
31
|
+
id,
|
|
32
|
+
taskId,
|
|
33
|
+
kind,
|
|
34
|
+
startedAt: new Date().toISOString(),
|
|
35
|
+
finishedAt: null,
|
|
36
|
+
durationMs: null,
|
|
37
|
+
running: true,
|
|
38
|
+
passed: null,
|
|
39
|
+
code: null,
|
|
40
|
+
output: "",
|
|
41
|
+
};
|
|
42
|
+
const list = this.runsByTask.get(taskId) ?? [];
|
|
43
|
+
list.push(run);
|
|
44
|
+
while (list.length > MAX_RUNS_PER_TASK)
|
|
45
|
+
list.shift();
|
|
46
|
+
this.runsByTask.set(taskId, list);
|
|
47
|
+
onEvent(run, "started");
|
|
48
|
+
return {
|
|
49
|
+
id,
|
|
50
|
+
chunk: (text) => {
|
|
51
|
+
run.output += text;
|
|
52
|
+
if (run.output.length > MAX_BUFFERED_CHARS) {
|
|
53
|
+
run.output = run.output.slice(run.output.length - MAX_BUFFERED_CHARS);
|
|
54
|
+
}
|
|
55
|
+
onEvent(run, "output", text);
|
|
56
|
+
},
|
|
57
|
+
done: (code) => {
|
|
58
|
+
run.running = false;
|
|
59
|
+
run.finishedAt = new Date().toISOString();
|
|
60
|
+
run.durationMs = Math.max(0, Date.parse(run.finishedAt) - Date.parse(run.startedAt));
|
|
61
|
+
run.code = code;
|
|
62
|
+
run.passed = code === 0;
|
|
63
|
+
onEvent(run, "done");
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Past + in-progress runs for a task, oldest first. */
|
|
68
|
+
getRuns(taskId) {
|
|
69
|
+
return this.runsByTask.get(taskId) ?? [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical status-transition table (lifecycle audit, #0295-adjacent).
|
|
3
|
+
*
|
|
4
|
+
* Before this, `patchTaskFile` accepted any `(from, to)` status pair with no
|
|
5
|
+
* rule about which ones make sense — reachable from the task-panel dropdown,
|
|
6
|
+
* board drag-drop, and a direct PATCH, none of which know an agent might be
|
|
7
|
+
* running or a review mid-flight. This module is the single reference for
|
|
8
|
+
* which edges are legitimate and, of those, which are safe to perform as a
|
|
9
|
+
* bare metadata write versus which require a dedicated action endpoint
|
|
10
|
+
* (start/pause/done/abandon/reopen) because they carry real side effects
|
|
11
|
+
* (spawning or stopping an agent, running the close-out pipeline).
|
|
12
|
+
*
|
|
13
|
+
* Twelve edges total. Six are cheap enough (no side effect beyond the write
|
|
14
|
+
* itself, or a side effect — `guardReviewTransition` — that already runs
|
|
15
|
+
* safely inline within a PATCH request) to allow through the generic patch
|
|
16
|
+
* path; those are `GENERIC_PATCH_EDGES` below. The other six — spawning an
|
|
17
|
+
* agent, running the close-out pipeline, stopping a live agent/review, or
|
|
18
|
+
* re-provisioning a deleted worktree — each already have (or, for the two
|
|
19
|
+
* new ones, now have) their own `/api/tasks/:id/<action>` endpoint that
|
|
20
|
+
* performs the effect and writes the status as part of the same request.
|
|
21
|
+
* `GENERIC_PATCH_EDGES` is the enforcement point for the *other* six: they
|
|
22
|
+
* are rejected outright, from every generic-write caller (PATCH, the task
|
|
23
|
+
* drawer's dropdown, board drag-drop), no matter how the request arrives.
|
|
24
|
+
*
|
|
25
|
+
* `reopen` (done -> active in the original design sketch) lands on `ready`
|
|
26
|
+
* instead: the close-out pipeline deletes the task's branch and worktree, so
|
|
27
|
+
* re-provisioning them is exactly what `/start` (ready -> active) already
|
|
28
|
+
* does. Landing reopen on `ready` reuses that machinery instead of
|
|
29
|
+
* duplicating it.
|
|
30
|
+
*/
|
|
31
|
+
import type { Status } from "../core/types.js";
|
|
32
|
+
export interface TransitionEdge {
|
|
33
|
+
/** Human-readable one-liner: what happens when this edge fires. */
|
|
34
|
+
effect: string;
|
|
35
|
+
/** Where this edge is actually performed. */
|
|
36
|
+
via: "generic-patch" | "action:start" | "action:done" | "action:pause" | "action:abandon" | "action:reopen" | "automated";
|
|
37
|
+
}
|
|
38
|
+
/** The full 12-edge allow-list. Absence of a `(from, to)` pair here means the
|
|
39
|
+
* transition is rejected, from every caller, with no exceptions. */
|
|
40
|
+
export declare const TRANSITIONS: Partial<Record<Status, Partial<Record<Status, TransitionEdge>>>>;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a bare status write (no accompanying action) from `from` to `to`
|
|
43
|
+
* is allowed. `from === to` is always fine (no-op). Returns a reason and a
|
|
44
|
+
* pointer to the right endpoint when rejected.
|
|
45
|
+
*/
|
|
46
|
+
export declare function checkGenericStatusPatch(from: Status, to: Status): {
|
|
47
|
+
ok: true;
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
reason: string;
|
|
51
|
+
};
|