@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,590 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, readdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { parseDocument, serializeDocument } from "../core/frontmatter.js";
|
|
5
|
+
import { commitTaskFile } from "../core/git.js";
|
|
6
|
+
import { parseTask, serializeTask, recordChange } from "../core/task.js";
|
|
7
|
+
import { extractOneShotReportText, parseOneShotLine, resolveCto, runPrompt, reviewCommand, usageCostSource, INTERRUPTED_MARKER, } from "./agents.js";
|
|
8
|
+
import { getRepoOSDb } from "../core/db.js";
|
|
9
|
+
const REPORT_CHARS = 20_000;
|
|
10
|
+
const SESSION_LINES_CAP = 2000;
|
|
11
|
+
const SESSION_WRITE_DEBOUNCE_MS = 300;
|
|
12
|
+
const CTO_SESSION_ID_PREFIX = "cto:";
|
|
13
|
+
const CTO_TIMEOUT_MS = 300_000; // 5 minutes
|
|
14
|
+
const REPORT_KEYS = ["at", "agent", "cli", "model", "state"];
|
|
15
|
+
const now = () => new Date().toISOString();
|
|
16
|
+
export function ctoMission(config, agent, boardDigest) {
|
|
17
|
+
const role = agent.instructions?.trim();
|
|
18
|
+
const parts = [];
|
|
19
|
+
if (role)
|
|
20
|
+
parts.push(role, "");
|
|
21
|
+
parts.push("You are monitoring the RepoOS board. Below is a digest of current state:", "- Tasks by status and staleness", "- Stuck signals (handoff requested but no done, active with idle session, stale review)", "- Serve processes and build freshness", "", "## Board digest", "", boardDigest, "", "## What to do", "", "1. Read the digest and identify real problems (not false positives).", "2. For each problem, decide: ignore (healthy), nudge (send a message), escalate", " (set needs_input), or file a follow-up bug.", "3. Output a concise status report (under 300 words). List what you found and what", " you're doing about it. NO CHATTER when nothing is wrong — silent is golden.", "", "## Hard boundaries", "", "- Do NOT edit, create, or delete any file outside this repo.", "- Do NOT commit, push, merge, or delete branches.", "- Do NOT move a task to 'done'.", "- Do NOT spend money or change config.", "- Do NOT run servers or long-lived processes.", "- The monitor may send one standard completion nudge to an active engineer after", " five minutes with no worktree activity. It records and announces that nudge.", "- Ask the human before every other action (set needs_input, file a bug, or any", " task/status change).", "- Keep nudges rare and brief. A task is only nudged once per digest unless", " new information arrives.", "", "## Output format", "", "Output ONLY the report as markdown, with no preamble:", "- Title (one line): what you found (e.g., '2 stuck tasks, 1 stale review')", "- What you found (bullet points)", "- What you're doing (bullet points)", "- Write 'Nothing to report' if the board is healthy.");
|
|
22
|
+
return parts.join("\n");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The CTO manager (0174).
|
|
26
|
+
*
|
|
27
|
+
* Reload-durability note (0288 "Also check"): unlike the reviewer, the CTO is
|
|
28
|
+
* INTENTIONALLY kept on the one-shot `runPrompt` path and is NOT durable. Both
|
|
29
|
+
* roles share the same `runPrompt` reload-death exposure, but the blast radius
|
|
30
|
+
* differs fundamentally:
|
|
31
|
+
*
|
|
32
|
+
* - The review produces a ONE-TIME sign-off artifact (.repoos/reviews/<id>.md)
|
|
33
|
+
* that is permanently lost if the process reloads mid-run — hence #0288 made
|
|
34
|
+
* it durable.
|
|
35
|
+
* - The CTO is a PERIODIC board monitor (default 5-min cadence). A run lost to
|
|
36
|
+
* a reload merely delays the next sweep; the monitor simply re-runs on its
|
|
37
|
+
* own next tick and self-heals. It never produces a one-of-a-kind artifact.
|
|
38
|
+
*
|
|
39
|
+
* So the CTO's `runPrompt` exposure is accepted (and it is force-cancelled on
|
|
40
|
+
* reload/close, which is correct *because* it is non-durable). If the CTO ever
|
|
41
|
+
* needs to guarantee a specific run survives a reload, it should be moved onto
|
|
42
|
+
* the same durable `AgentRunner` path (#0288) — but that is out of scope here.
|
|
43
|
+
*/
|
|
44
|
+
export class CTOManager {
|
|
45
|
+
config;
|
|
46
|
+
emit;
|
|
47
|
+
runs = new Map();
|
|
48
|
+
sessions = new Map();
|
|
49
|
+
sessionTimers = new Map();
|
|
50
|
+
runner;
|
|
51
|
+
/** Database for recording CTO sessions (0230). */
|
|
52
|
+
db;
|
|
53
|
+
constructor(config, emit, runner) {
|
|
54
|
+
this.config = config;
|
|
55
|
+
this.emit = emit;
|
|
56
|
+
this.runner = runner;
|
|
57
|
+
this.db = getRepoOSDb(config.root);
|
|
58
|
+
}
|
|
59
|
+
enabled() {
|
|
60
|
+
return resolveCto(this.config) !== null;
|
|
61
|
+
}
|
|
62
|
+
isRunning() {
|
|
63
|
+
return this.runs.size > 0;
|
|
64
|
+
}
|
|
65
|
+
runningCount() {
|
|
66
|
+
return this.runs.size;
|
|
67
|
+
}
|
|
68
|
+
path() {
|
|
69
|
+
return join(this.config.root, this.config.cacheDir, "cto", "report.md");
|
|
70
|
+
}
|
|
71
|
+
read() {
|
|
72
|
+
const file = this.path();
|
|
73
|
+
if (!existsSync(file))
|
|
74
|
+
return null;
|
|
75
|
+
let raw;
|
|
76
|
+
try {
|
|
77
|
+
raw = readFileSync(file, "utf8");
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const doc = parseDocument(raw);
|
|
83
|
+
const str = (key) => {
|
|
84
|
+
const v = doc.data[key];
|
|
85
|
+
return v === null || v === undefined ? "" : String(v);
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
id: "cto",
|
|
89
|
+
at: str("at"),
|
|
90
|
+
agent: str("agent"),
|
|
91
|
+
cli: str("cli"),
|
|
92
|
+
model: str("model"),
|
|
93
|
+
state: str("state") === "failed" ? "failed" : "ok",
|
|
94
|
+
markdown: doc.body.trim(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
canRun() {
|
|
98
|
+
if (this.runs.size > 0) {
|
|
99
|
+
return { ok: false, reason: "a CTO run is already in progress" };
|
|
100
|
+
}
|
|
101
|
+
const agent = resolveCto(this.config);
|
|
102
|
+
if (!agent) {
|
|
103
|
+
return { ok: false, reason: "the CTO agent is disabled on the Agents page" };
|
|
104
|
+
}
|
|
105
|
+
return { ok: true };
|
|
106
|
+
}
|
|
107
|
+
async run(boardDigest) {
|
|
108
|
+
if (this.runs.size > 0) {
|
|
109
|
+
return { ok: false, reason: "a CTO run is already in progress" };
|
|
110
|
+
}
|
|
111
|
+
const agent = resolveCto(this.config);
|
|
112
|
+
if (!agent) {
|
|
113
|
+
return { ok: false, reason: "the CTO agent is disabled" };
|
|
114
|
+
}
|
|
115
|
+
this.resetSession();
|
|
116
|
+
const run = { cancelled: false };
|
|
117
|
+
this.runs.set("cto", run);
|
|
118
|
+
this.emit({ type: "cto", state: "running", at: now() });
|
|
119
|
+
this.appendMarker(`CTO monitoring started — ${agent.name} (${agent.cli})`);
|
|
120
|
+
const mission = ctoMission(this.config, agent, boardDigest);
|
|
121
|
+
let result;
|
|
122
|
+
try {
|
|
123
|
+
result = await runPrompt(agent, mission, {
|
|
124
|
+
cwd: this.config.root,
|
|
125
|
+
timeoutMs: CTO_TIMEOUT_MS,
|
|
126
|
+
command: reviewCommand(agent, mission, this.config.root),
|
|
127
|
+
onSpawn: (proc) => {
|
|
128
|
+
run.proc = proc;
|
|
129
|
+
if (run.cancelled)
|
|
130
|
+
proc.kill("SIGTERM");
|
|
131
|
+
},
|
|
132
|
+
onLine: (line) => this.appendSessionLine(agent.cli, line),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
this.runs.delete("cto");
|
|
137
|
+
}
|
|
138
|
+
if (run.cancelled) {
|
|
139
|
+
// Persist the in-memory lines (including the interrupted marker appended
|
|
140
|
+
// by `interrupt()`) so a user-initiated stop survives reload/restart.
|
|
141
|
+
this.persistSession();
|
|
142
|
+
this.recordRun(agent, result, now(), false, "cancelled");
|
|
143
|
+
this.emit({ type: "cto", state: "cancelled", at: now() });
|
|
144
|
+
return { ok: false, reason: "CTO run cancelled" };
|
|
145
|
+
}
|
|
146
|
+
const state = result.ok && result.output ? "ok" : "failed";
|
|
147
|
+
const body = state === "ok"
|
|
148
|
+
? extractOneShotReportText(agent.cli, result.output ?? "")
|
|
149
|
+
: `The CTO produced no report: ${result.error ?? "unknown error"}`;
|
|
150
|
+
const report = {
|
|
151
|
+
id: "cto",
|
|
152
|
+
at: now(),
|
|
153
|
+
agent: agent.name,
|
|
154
|
+
cli: agent.cli,
|
|
155
|
+
model: agent.model,
|
|
156
|
+
state,
|
|
157
|
+
markdown: body.slice(0, REPORT_CHARS),
|
|
158
|
+
};
|
|
159
|
+
this.write(report);
|
|
160
|
+
this.appendMarker(state === "ok"
|
|
161
|
+
? "✓ CTO monitoring complete"
|
|
162
|
+
: `✗ CTO run failed: ${result.error ?? "no report"}`);
|
|
163
|
+
this.persistSession();
|
|
164
|
+
this.recordRun(agent, result, report.at, state === "ok");
|
|
165
|
+
this.emit({
|
|
166
|
+
type: "cto",
|
|
167
|
+
state: state === "ok" ? "ready" : "failed",
|
|
168
|
+
at: report.at,
|
|
169
|
+
...(state === "failed" ? { error: result.error } : {}),
|
|
170
|
+
});
|
|
171
|
+
return state === "ok"
|
|
172
|
+
? { ok: true, report }
|
|
173
|
+
: { ok: false, reason: result.error ?? "the CTO agent failed", report };
|
|
174
|
+
}
|
|
175
|
+
async send(text) {
|
|
176
|
+
if (this.runs.size > 0) {
|
|
177
|
+
return { ok: false, reason: "a CTO run is already in progress" };
|
|
178
|
+
}
|
|
179
|
+
const agent = resolveCto(this.config);
|
|
180
|
+
if (!agent) {
|
|
181
|
+
return { ok: false, reason: "the CTO agent is disabled" };
|
|
182
|
+
}
|
|
183
|
+
if (!this.hasSession()) {
|
|
184
|
+
this.sessions.set("cto", []);
|
|
185
|
+
}
|
|
186
|
+
this.appendEntry({ type: "human", text });
|
|
187
|
+
const run = { cancelled: false };
|
|
188
|
+
this.runs.set("cto", run);
|
|
189
|
+
this.emit({ type: "cto", state: "running", at: now() });
|
|
190
|
+
const mission = `You are the CTO. Respond to this question about the board and the tasks:\n\n${text}`;
|
|
191
|
+
let result;
|
|
192
|
+
try {
|
|
193
|
+
result = await runPrompt(agent, mission, {
|
|
194
|
+
cwd: this.config.root,
|
|
195
|
+
timeoutMs: CTO_TIMEOUT_MS,
|
|
196
|
+
command: reviewCommand(agent, mission, this.config.root),
|
|
197
|
+
onSpawn: (proc) => {
|
|
198
|
+
run.proc = proc;
|
|
199
|
+
if (run.cancelled)
|
|
200
|
+
proc.kill("SIGTERM");
|
|
201
|
+
},
|
|
202
|
+
onLine: (line) => this.appendSessionLine(agent.cli, line),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
this.runs.delete("cto");
|
|
207
|
+
}
|
|
208
|
+
if (run.cancelled) {
|
|
209
|
+
// Persist the in-memory lines (including the interrupted marker appended
|
|
210
|
+
// by `interrupt()`) so a user-initiated stop survives reload/restart.
|
|
211
|
+
this.persistSession();
|
|
212
|
+
this.recordRun(agent, result, now(), false, "cancelled");
|
|
213
|
+
this.emit({ type: "cto", state: "cancelled", at: now() });
|
|
214
|
+
return { ok: false, reason: "CTO run cancelled" };
|
|
215
|
+
}
|
|
216
|
+
if (!result.ok) {
|
|
217
|
+
this.appendMarker(`✗ the CTO could not answer: ${result.error ?? "unknown error"}`);
|
|
218
|
+
this.persistSession();
|
|
219
|
+
this.recordRun(agent, result, now(), false);
|
|
220
|
+
this.emit({ type: "cto", state: "failed", at: now() });
|
|
221
|
+
return { ok: false, reason: result.error ?? "the CTO failed to answer" };
|
|
222
|
+
}
|
|
223
|
+
const completedAt = now();
|
|
224
|
+
this.persistSession();
|
|
225
|
+
this.recordRun(agent, result, completedAt, true);
|
|
226
|
+
this.emit({ type: "cto", state: "ready", at: completedAt });
|
|
227
|
+
return { ok: true };
|
|
228
|
+
}
|
|
229
|
+
session() {
|
|
230
|
+
const lines = this.sessions.get("cto");
|
|
231
|
+
if (lines)
|
|
232
|
+
return lines;
|
|
233
|
+
const loaded = this.readSession();
|
|
234
|
+
return loaded ?? [];
|
|
235
|
+
}
|
|
236
|
+
cancel() {
|
|
237
|
+
const run = this.runs.get("cto");
|
|
238
|
+
if (!run)
|
|
239
|
+
return;
|
|
240
|
+
run.cancelled = true;
|
|
241
|
+
try {
|
|
242
|
+
run.proc?.kill("SIGTERM");
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
/* already gone */
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
cancelAll() {
|
|
249
|
+
this.cancel();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Interrupt an in-progress CTO run/response. Stops the in-flight agent process
|
|
253
|
+
* and appends a persistent "response interrupted" marker to the board
|
|
254
|
+
* conversation so it reads as user-stopped rather than completed normally.
|
|
255
|
+
* Idempotent — safe to call when nothing is running.
|
|
256
|
+
*/
|
|
257
|
+
interrupt() {
|
|
258
|
+
const wasRunning = this.runs.has("cto");
|
|
259
|
+
this.cancel();
|
|
260
|
+
if (wasRunning) {
|
|
261
|
+
this.appendMarker(INTERRUPTED_MARKER);
|
|
262
|
+
}
|
|
263
|
+
return { stopped: wasRunning };
|
|
264
|
+
}
|
|
265
|
+
// ---- Guard-railed actions ----
|
|
266
|
+
/**
|
|
267
|
+
* Send a message to a task's agent session (nudge or escalation).
|
|
268
|
+
* The caller records any automatic nudge separately so it is visible to the
|
|
269
|
+
* human in the task activity feed.
|
|
270
|
+
*/
|
|
271
|
+
sendTaskMessage(taskId, message, agent) {
|
|
272
|
+
if (!this.runner || !agent)
|
|
273
|
+
return false;
|
|
274
|
+
try {
|
|
275
|
+
return this.runner.send(taskId, message, agent).ok;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Persist and announce the one automatic action the CTO monitor may take:
|
|
283
|
+
* a completion reminder to an idle active engineer. Keeping this separate
|
|
284
|
+
* from `sendTaskMessage` makes the human-facing audit entry durable even
|
|
285
|
+
* when the agent transcript is later compacted or the server reloads.
|
|
286
|
+
*/
|
|
287
|
+
recordAutomaticNudge(task) {
|
|
288
|
+
try {
|
|
289
|
+
const current = parseTask({
|
|
290
|
+
content: readFileSync(task.absPath, "utf8"),
|
|
291
|
+
absPath: task.absPath,
|
|
292
|
+
root: this.config.root,
|
|
293
|
+
defaultStatus: this.config.defaultStatus,
|
|
294
|
+
defaultAssignee: this.config.defaultAssignee,
|
|
295
|
+
});
|
|
296
|
+
if (current.status !== "active")
|
|
297
|
+
return false;
|
|
298
|
+
const note = "CTO nudge: sent engineer a completion reminder after 5m without worktree activity";
|
|
299
|
+
recordChange(current, note);
|
|
300
|
+
writeFileSync(task.absPath, serializeTask(current));
|
|
301
|
+
commitTaskFile(this.config.root, task.absPath, `docs(${current.id}): record CTO nudge`);
|
|
302
|
+
this.appendMarker(`↗ CTO nudged engineer for #${current.id} after 5m idle — human notified`);
|
|
303
|
+
this.emit({ type: "task.corrected", id: current.id, path: current.path, note, at: now() });
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
console.error(`[repoos] CTO: failed to record nudge for #${task.id}: ${err.message}`);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Run repoos check on a task's worktree to verify it passes before
|
|
313
|
+
* taking automatic actions. Returns true if check passes, false otherwise.
|
|
314
|
+
*/
|
|
315
|
+
runRepoosCheck(workdir) {
|
|
316
|
+
try {
|
|
317
|
+
execSync("repoos check", { cwd: workdir, stdio: "pipe" });
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Move a task from review to active (or mark needs_input) when genuinely stuck.
|
|
326
|
+
* Only call this after human confirmation or verification that the task is
|
|
327
|
+
* actually stuck (not just slow). The action is logged to task activity.
|
|
328
|
+
*/
|
|
329
|
+
moveTaskStatus(taskAbsPath, fromStatus, toStatus, reason) {
|
|
330
|
+
try {
|
|
331
|
+
const content = readFileSync(taskAbsPath, "utf8");
|
|
332
|
+
const task = parseTask({
|
|
333
|
+
content,
|
|
334
|
+
absPath: taskAbsPath,
|
|
335
|
+
root: this.config.root,
|
|
336
|
+
defaultStatus: this.config.defaultStatus,
|
|
337
|
+
defaultAssignee: this.config.defaultAssignee,
|
|
338
|
+
});
|
|
339
|
+
if (task.status !== fromStatus) {
|
|
340
|
+
return false; // Status changed since check
|
|
341
|
+
}
|
|
342
|
+
// Guard: never move to 'done'
|
|
343
|
+
if (toStatus === "done") {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
// Record the change and update
|
|
347
|
+
recordChange(task, `CTO action: ${reason}`);
|
|
348
|
+
task.status = toStatus;
|
|
349
|
+
writeFileSync(taskAbsPath, serializeTask(task));
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Set needs_input flag on a task to escalate to human review.
|
|
358
|
+
* This surfaces the task in the UI and fires notifications.
|
|
359
|
+
*/
|
|
360
|
+
setNeedsInput(taskAbsPath, reason) {
|
|
361
|
+
try {
|
|
362
|
+
const content = readFileSync(taskAbsPath, "utf8");
|
|
363
|
+
const task = parseTask({
|
|
364
|
+
content,
|
|
365
|
+
absPath: taskAbsPath,
|
|
366
|
+
root: this.config.root,
|
|
367
|
+
defaultStatus: this.config.defaultStatus,
|
|
368
|
+
defaultAssignee: this.config.defaultAssignee,
|
|
369
|
+
});
|
|
370
|
+
if (task.needsInput) {
|
|
371
|
+
return true; // Already set
|
|
372
|
+
}
|
|
373
|
+
recordChange(task, `CTO action: ${reason}`);
|
|
374
|
+
task.needsInput = true;
|
|
375
|
+
task.needsInputReason = "cto-escalation";
|
|
376
|
+
writeFileSync(taskAbsPath, serializeTask(task));
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a follow-up bug task for systemic issues detected by the CTO.
|
|
385
|
+
* Returns the new task id on success, null on failure.
|
|
386
|
+
*/
|
|
387
|
+
createFollowUpBug(title, body, area) {
|
|
388
|
+
try {
|
|
389
|
+
const workDir = join(this.config.root, this.config.workDir);
|
|
390
|
+
let maxId = 0;
|
|
391
|
+
const files = readdirSync(workDir);
|
|
392
|
+
for (const file of files) {
|
|
393
|
+
const m = file.match(/^(\d+)/);
|
|
394
|
+
if (m)
|
|
395
|
+
maxId = Math.max(maxId, parseInt(m[1], 10));
|
|
396
|
+
}
|
|
397
|
+
const nextId = String(maxId + 1).padStart(4, "0");
|
|
398
|
+
const filename = `${nextId}-${title.toLowerCase().replace(/\s+/g, "-")}.md`;
|
|
399
|
+
const filePath = join(workDir, filename);
|
|
400
|
+
const now = new Date().toISOString();
|
|
401
|
+
const content = `---
|
|
402
|
+
id: "${nextId}"
|
|
403
|
+
title: "${title}"
|
|
404
|
+
type: "bug"
|
|
405
|
+
status: "inbox"
|
|
406
|
+
priority: "p2"
|
|
407
|
+
area: "${area}"
|
|
408
|
+
assigned_to: "unassigned"
|
|
409
|
+
created_at: "${now}"
|
|
410
|
+
updated_at: "${now}"
|
|
411
|
+
---
|
|
412
|
+
## Problem
|
|
413
|
+
Created by CTO monitoring (0174): ${title}
|
|
414
|
+
|
|
415
|
+
## Context
|
|
416
|
+
${body}
|
|
417
|
+
|
|
418
|
+
## Activity
|
|
419
|
+
- ${now} · created · CTO`;
|
|
420
|
+
writeFileSync(filePath, content, "utf8");
|
|
421
|
+
return nextId;
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// ---- private methods ----
|
|
428
|
+
/**
|
|
429
|
+
* Record a CTO run to the database (0230). Persisted with the cto session
|
|
430
|
+
* type, real wall-clock elapsed time, and any CLI-reported tokens/cost.
|
|
431
|
+
* Best-effort — never crashes the monitor. Cancelled runs record too (0331):
|
|
432
|
+
* the process ran and may have burned tokens before the stop landed, and the
|
|
433
|
+
* board panel must show every role's real spend.
|
|
434
|
+
*/
|
|
435
|
+
recordRun(agent, result, completedAt, success, statusOverride) {
|
|
436
|
+
if (!this.db)
|
|
437
|
+
return;
|
|
438
|
+
try {
|
|
439
|
+
const sessionId = `cto:${completedAt}`;
|
|
440
|
+
const elapsedMs = result.elapsedMs ?? 0;
|
|
441
|
+
const costSource = usageCostSource(agent, result);
|
|
442
|
+
this.db.upsertSession({
|
|
443
|
+
sessionId,
|
|
444
|
+
sessionType: "cto",
|
|
445
|
+
taskId: null, // CTO observes the whole board, not one task
|
|
446
|
+
agent: agent.name,
|
|
447
|
+
model: agent.model,
|
|
448
|
+
codingAgent: agent.cli,
|
|
449
|
+
startedAt: new Date(Date.parse(completedAt) - elapsedMs).toISOString(),
|
|
450
|
+
endedAt: completedAt,
|
|
451
|
+
elapsedMs,
|
|
452
|
+
inputTokens: result.inputTokens ?? undefined,
|
|
453
|
+
outputTokens: result.outputTokens ?? undefined,
|
|
454
|
+
totalTokens: result.totalTokens ?? undefined,
|
|
455
|
+
costUsd: result.costUsd ?? undefined,
|
|
456
|
+
costSource,
|
|
457
|
+
status: statusOverride ?? (success ? "finished" : "errored"),
|
|
458
|
+
lastActivityAt: completedAt,
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// Database recording is best-effort and must never crash.
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
sessionId() {
|
|
466
|
+
return `${CTO_SESSION_ID_PREFIX}board`;
|
|
467
|
+
}
|
|
468
|
+
hasSession() {
|
|
469
|
+
if (this.sessions.has("cto"))
|
|
470
|
+
return true;
|
|
471
|
+
const file = this.sessionFile();
|
|
472
|
+
return file !== null && existsSync(file);
|
|
473
|
+
}
|
|
474
|
+
sessionFile() {
|
|
475
|
+
return join(this.config.root, this.config.cacheDir, "cto", "session.json");
|
|
476
|
+
}
|
|
477
|
+
readSession() {
|
|
478
|
+
const file = this.sessionFile();
|
|
479
|
+
if (!file || !existsSync(file))
|
|
480
|
+
return null;
|
|
481
|
+
try {
|
|
482
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
483
|
+
if (!Array.isArray(parsed.lines))
|
|
484
|
+
return null;
|
|
485
|
+
const lines = parsed.lines;
|
|
486
|
+
this.sessions.set("cto", lines.slice(-SESSION_LINES_CAP));
|
|
487
|
+
return lines;
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
persistSession() {
|
|
494
|
+
const timer = this.sessionTimers.get("cto");
|
|
495
|
+
if (timer)
|
|
496
|
+
clearTimeout(timer);
|
|
497
|
+
this.sessionTimers.delete("cto");
|
|
498
|
+
const lines = this.sessions.get("cto");
|
|
499
|
+
const file = this.sessionFile();
|
|
500
|
+
if (!lines || !file)
|
|
501
|
+
return;
|
|
502
|
+
try {
|
|
503
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
504
|
+
const temp = `${file}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
505
|
+
try {
|
|
506
|
+
writeFileSync(temp, JSON.stringify({ lines }, null, 2), "utf8");
|
|
507
|
+
renameSync(temp, file);
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
if (existsSync(temp))
|
|
511
|
+
unlinkSync(temp);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
/* best-effort */
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
scheduleSessionWrite() {
|
|
519
|
+
const existing = this.sessionTimers.get("cto");
|
|
520
|
+
if (existing)
|
|
521
|
+
clearTimeout(existing);
|
|
522
|
+
const timer = setTimeout(() => this.persistSession(), SESSION_WRITE_DEBOUNCE_MS);
|
|
523
|
+
timer.unref?.();
|
|
524
|
+
this.sessionTimers.set("cto", timer);
|
|
525
|
+
}
|
|
526
|
+
resetSession() {
|
|
527
|
+
const timer = this.sessionTimers.get("cto");
|
|
528
|
+
if (timer) {
|
|
529
|
+
clearTimeout(timer);
|
|
530
|
+
this.sessionTimers.delete("cto");
|
|
531
|
+
}
|
|
532
|
+
this.sessions.delete("cto");
|
|
533
|
+
const file = this.sessionFile();
|
|
534
|
+
if (file) {
|
|
535
|
+
try {
|
|
536
|
+
if (existsSync(file))
|
|
537
|
+
unlinkSync(file);
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
/* best-effort */
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
appendEntry(entry) {
|
|
545
|
+
let lines = this.sessions.get("cto");
|
|
546
|
+
if (!lines) {
|
|
547
|
+
lines = [];
|
|
548
|
+
this.sessions.set("cto", lines);
|
|
549
|
+
}
|
|
550
|
+
if (!entry.at)
|
|
551
|
+
entry = { ...entry, at: new Date().toISOString() };
|
|
552
|
+
lines.push(entry);
|
|
553
|
+
if (lines.length > SESSION_LINES_CAP)
|
|
554
|
+
lines.splice(0, lines.length - SESSION_LINES_CAP);
|
|
555
|
+
this.emit({
|
|
556
|
+
type: "agent.output",
|
|
557
|
+
id: this.sessionId(),
|
|
558
|
+
entry,
|
|
559
|
+
stream: "out",
|
|
560
|
+
at: now(),
|
|
561
|
+
});
|
|
562
|
+
this.scheduleSessionWrite();
|
|
563
|
+
}
|
|
564
|
+
appendSessionLine(cli, line) {
|
|
565
|
+
const entry = parseOneShotLine(cli, line);
|
|
566
|
+
if (!entry)
|
|
567
|
+
return; // a recognized-but-voiceless structured event
|
|
568
|
+
this.appendEntry(entry);
|
|
569
|
+
}
|
|
570
|
+
appendMarker(text) {
|
|
571
|
+
this.appendEntry({ type: "sys", d: text });
|
|
572
|
+
}
|
|
573
|
+
write(report) {
|
|
574
|
+
const file = this.path();
|
|
575
|
+
try {
|
|
576
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
577
|
+
const data = {
|
|
578
|
+
at: report.at,
|
|
579
|
+
agent: report.agent,
|
|
580
|
+
cli: report.cli,
|
|
581
|
+
model: report.model,
|
|
582
|
+
state: report.state,
|
|
583
|
+
};
|
|
584
|
+
writeFileSync(file, serializeDocument(data, `\n${report.markdown}\n`, REPORT_KEYS));
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
console.error(`[repoos] could not write the CTO report: ${err.message}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|