@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,112 @@
|
|
|
1
|
+
const NTFY_DEFAULT_BASE_URL = "https://ntfy.sh";
|
|
2
|
+
/** The ntfy server base URL: env var wins, then config, then the default. */
|
|
3
|
+
export function ntfyBaseUrl(config) {
|
|
4
|
+
const fromEnv = process.env.NTFY_BASE_URL?.trim();
|
|
5
|
+
return (fromEnv || config.ntfyBaseUrl || NTFY_DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
6
|
+
}
|
|
7
|
+
/** Truncate title to fit on one line with the headline. Aim for ~50 chars total. */
|
|
8
|
+
function truncateTitle(title, headlineLen) {
|
|
9
|
+
const maxLen = 50 - headlineLen - 3; // 3 for " · "
|
|
10
|
+
if (title.length <= maxLen)
|
|
11
|
+
return title;
|
|
12
|
+
return title.slice(0, Math.max(1, maxLen - 1)) + "…";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build a notification spec for a status transition, or null if no notification applies.
|
|
16
|
+
* For ready → active, returns "Started" (lower priority to avoid noise).
|
|
17
|
+
* For active/review → done, returns "Done" (lower priority).
|
|
18
|
+
* Other transitions do not currently trigger notifications.
|
|
19
|
+
*/
|
|
20
|
+
export function notificationForStatusChange(prev, next) {
|
|
21
|
+
if (prev === "ready" && next === "active") {
|
|
22
|
+
return { headline: "▶️ Started", priority: "low" };
|
|
23
|
+
}
|
|
24
|
+
if ((prev === "active" || prev === "review") && next === "done") {
|
|
25
|
+
return { headline: "✅ Done", priority: "low" };
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a notification spec for task creation.
|
|
31
|
+
* Returns "New" with low priority (informational).
|
|
32
|
+
*/
|
|
33
|
+
export function notificationForTaskCreated() {
|
|
34
|
+
return { headline: "🆕 New", priority: "low" };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a notification spec when a task needs human input.
|
|
38
|
+
* Returns "Needs you" with high priority and a subtitle explaining the situation.
|
|
39
|
+
*/
|
|
40
|
+
export function notificationForNeedsInput() {
|
|
41
|
+
return {
|
|
42
|
+
headline: "🙋 Needs you",
|
|
43
|
+
priority: "high",
|
|
44
|
+
subtitle: "Agent is waiting for your decision",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Format a notification spec into the final message string. */
|
|
48
|
+
export function formatNotification(spec, title) {
|
|
49
|
+
const truncated = truncateTitle(title, spec.headline.length);
|
|
50
|
+
const message = `${spec.headline} · ${truncated}`;
|
|
51
|
+
if (spec.subtitle) {
|
|
52
|
+
return `${message}\n${spec.subtitle}`;
|
|
53
|
+
}
|
|
54
|
+
return message;
|
|
55
|
+
}
|
|
56
|
+
/** True when notifications are enabled AND a topic is configured. */
|
|
57
|
+
export function shouldSend(config) {
|
|
58
|
+
if (config.ntfyEnabled !== true) {
|
|
59
|
+
console.log(`[ntfy] Notifications disabled (ntfyEnabled=${config.ntfyEnabled})`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const topic = (config.ntfyTopic ?? "").trim();
|
|
63
|
+
if (topic.length === 0) {
|
|
64
|
+
console.log(`[ntfy] No topic configured (ntfyTopic="${config.ntfyTopic}")`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
console.log(`[ntfy] Notifications enabled for topic: ${topic}`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/** Best-effort publish of a message to the configured topic. Never throws. */
|
|
71
|
+
export function publish(config, message, priority = "default") {
|
|
72
|
+
if (!shouldSend(config))
|
|
73
|
+
return;
|
|
74
|
+
const topic = encodeURIComponent((config.ntfyTopic ?? "").trim());
|
|
75
|
+
const url = `${ntfyBaseUrl(config)}/${topic}`;
|
|
76
|
+
void fetch(url, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "text/plain",
|
|
80
|
+
Title: "RepoOS",
|
|
81
|
+
Priority: priority,
|
|
82
|
+
},
|
|
83
|
+
body: message,
|
|
84
|
+
}).catch((err) => {
|
|
85
|
+
// Log errors for debugging, but never throw
|
|
86
|
+
console.error(`[ntfy] Failed to send notification to ${url}:`, err);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/** Fire a notification for a task status transition, if one applies. */
|
|
90
|
+
export function notifyStatusChange(config, task, prev, next) {
|
|
91
|
+
const spec = notificationForStatusChange(prev, next);
|
|
92
|
+
if (spec) {
|
|
93
|
+
const message = formatNotification(spec, task.title);
|
|
94
|
+
console.log(`[ntfy] Sending notification for ${task.id}: ${prev} → ${next}`);
|
|
95
|
+
publish(config, message, spec.priority);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(`[ntfy] No notification for ${task.id}: ${prev} → ${next} (no message configured)`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Fire a notification when a task is created (stretch from #0134). */
|
|
102
|
+
export function notifyTaskCreated(config, task) {
|
|
103
|
+
const spec = notificationForTaskCreated();
|
|
104
|
+
const message = formatNotification(spec, task.title);
|
|
105
|
+
publish(config, message, spec.priority);
|
|
106
|
+
}
|
|
107
|
+
/** Fire a notification when a task needs human input. */
|
|
108
|
+
export function notifyNeedsInput(config, task) {
|
|
109
|
+
const spec = notificationForNeedsInput();
|
|
110
|
+
const message = formatNotification(spec, task.title);
|
|
111
|
+
publish(config, message, spec.priority);
|
|
112
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
export interface QueuedPmImages {
|
|
3
|
+
batchId: string | null;
|
|
4
|
+
/** How many images actually landed in the pending batch. */
|
|
5
|
+
queued: number;
|
|
6
|
+
/** Per-image rejection reasons (bad type, empty, too large). */
|
|
7
|
+
errors: string[];
|
|
8
|
+
}
|
|
9
|
+
/** The shape the client posts per image (same as the attachment-upload endpoint). */
|
|
10
|
+
export interface IncomingPmImage {
|
|
11
|
+
name?: unknown;
|
|
12
|
+
mime?: unknown;
|
|
13
|
+
data?: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate and park images from one PM chat message as a pending batch keyed
|
|
17
|
+
* to that session. Never throws — per-image problems come back as `errors`
|
|
18
|
+
* while the rest of the batch still queues.
|
|
19
|
+
*/
|
|
20
|
+
export declare function queuePmImages(config: RepoOSConfig, sessionKey: string, images: IncomingPmImage[]): QueuedPmImages;
|
|
21
|
+
/** Remove a just-queued batch (e.g. the PM send was rejected) and its files. */
|
|
22
|
+
export declare function dropPmImages(batchId: string | null): void;
|
|
23
|
+
/**
|
|
24
|
+
* Attach the oldest pending batch whose PM session is still running to a
|
|
25
|
+
* freshly created task: move the files into the task's attachment folder
|
|
26
|
+
* under the usual `screenshot-N` names, reference them from the task body's
|
|
27
|
+
* `## Screenshots` section, and return the re-parsed task for the caller to
|
|
28
|
+
* apply to the index. Returns null when nothing is pending for a running
|
|
29
|
+
* session — the common case — or on any failure (best-effort; a failed
|
|
30
|
+
* attach never blocks the task creation itself, and the batch is dropped so
|
|
31
|
+
* a half-attached set cannot re-attach to a later task).
|
|
32
|
+
*/
|
|
33
|
+
export declare function attachPendingPmImages(config: RepoOSConfig, task: Task, isSessionRunning: (sessionKey: string) => boolean): Task | null;
|
|
34
|
+
/** Test hook: drop all in-memory batches (files under tmp roots are removed by the tests). */
|
|
35
|
+
export declare function resetPmImages(): void;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat-input screenshots carried onto the task the PM creates from them (0381).
|
|
3
|
+
*
|
|
4
|
+
* When a PM chat message arrives with images, the task it will spawn does not
|
|
5
|
+
* exist yet — the PM authors it later, mid-run, through `repoos new`. So the
|
|
6
|
+
* message route cannot attach the images the way the New-task panel does
|
|
7
|
+
* (there the task id is known at upload time). Instead the images are parked
|
|
8
|
+
* on disk as a pending batch keyed to the PM session, and the moment a task
|
|
9
|
+
* is created while that session is running, the batch is moved into the new
|
|
10
|
+
* task's attachment folder and referenced from its `## Screenshots` section —
|
|
11
|
+
* the exact storage/serving convention every other screenshot uses
|
|
12
|
+
* (`work/.attachments/<taskId>/`, gitignored, served from disk).
|
|
13
|
+
*
|
|
14
|
+
* Matching rule: the oldest pending batch whose PM session is still running
|
|
15
|
+
* wins. Runs are short and a user attaches images seconds before the PM
|
|
16
|
+
* starts working, so "created while that session is running" almost always
|
|
17
|
+
* means "created from this message"; a human creating a task inside that
|
|
18
|
+
* same window is the rare false positive, bounded by a 10-minute TTL.
|
|
19
|
+
* Unclaimed batches (the PM never creates a task) expire and are deleted —
|
|
20
|
+
* the pending folder never accumulates.
|
|
21
|
+
*/
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { MAX_SCREENSHOT_BYTES, SCREENSHOT_MIME, attachmentsDir, sanitizeName, screenshotUrl, } from "./attachments.js";
|
|
26
|
+
import { patchTaskFile } from "./write.js";
|
|
27
|
+
const batches = new Map();
|
|
28
|
+
/** A batch unclaimed this long is deleted — the PM run that would claim it is long over. */
|
|
29
|
+
const PENDING_TTL_MS = 10 * 60 * 1000;
|
|
30
|
+
/** Upper bound on queued batches per PM session (each send with images = one batch). */
|
|
31
|
+
const MAX_BATCHES_PER_SESSION = 8;
|
|
32
|
+
/** Pending storage lives beside the task attachment folders, under the gitignored work dir. */
|
|
33
|
+
function pendingRoot(config) {
|
|
34
|
+
return join(config.root, config.workDir, ".attachments", "_pm-pending");
|
|
35
|
+
}
|
|
36
|
+
/** Delete every batch past its TTL, and its files. Runs on every entry point. */
|
|
37
|
+
function prune() {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
for (const [id, batch] of batches) {
|
|
40
|
+
if (now - batch.at <= PENDING_TTL_MS)
|
|
41
|
+
continue;
|
|
42
|
+
batches.delete(id);
|
|
43
|
+
rmSync(batch.dir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate and park images from one PM chat message as a pending batch keyed
|
|
48
|
+
* to that session. Never throws — per-image problems come back as `errors`
|
|
49
|
+
* while the rest of the batch still queues.
|
|
50
|
+
*/
|
|
51
|
+
export function queuePmImages(config, sessionKey, images) {
|
|
52
|
+
prune();
|
|
53
|
+
const errors = [];
|
|
54
|
+
if (!Array.isArray(images) || images.length === 0) {
|
|
55
|
+
return { batchId: null, queued: 0, errors };
|
|
56
|
+
}
|
|
57
|
+
const dir = join(pendingRoot(config), `${Date.now()}-${randomUUID().slice(0, 8)}`);
|
|
58
|
+
const saved = [];
|
|
59
|
+
images.forEach((img, i) => {
|
|
60
|
+
const mime = typeof img?.mime === "string" ? img.mime : "";
|
|
61
|
+
const ext = SCREENSHOT_MIME[mime];
|
|
62
|
+
if (!ext) {
|
|
63
|
+
errors.push(`image ${i + 1}: unsupported type "${mime || "unknown"}" — supported: ${Object.keys(SCREENSHOT_MIME).join(", ")}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const data = typeof img?.data === "string" ? img.data : "";
|
|
67
|
+
let buf;
|
|
68
|
+
try {
|
|
69
|
+
buf = Buffer.from(data, "base64");
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
errors.push(`image ${i + 1}: invalid base64 data`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (buf.length === 0) {
|
|
76
|
+
errors.push(`image ${i + 1}: image data is empty`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (buf.length > MAX_SCREENSHOT_BYTES) {
|
|
80
|
+
errors.push(`image ${i + 1}: too large (max ${Math.round(MAX_SCREENSHOT_BYTES / 1024 / 1024)} MB)`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
const file = `${String(saved.length + 1).padStart(3, "0")}${ext}`;
|
|
86
|
+
const absPath = join(dir, file);
|
|
87
|
+
writeFileSync(absPath, buf);
|
|
88
|
+
saved.push({
|
|
89
|
+
absPath,
|
|
90
|
+
name: sanitizeName(typeof img?.name === "string" ? img.name : ""),
|
|
91
|
+
mime,
|
|
92
|
+
size: buf.length,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
errors.push(`image ${i + 1}: could not store — ${err instanceof Error ? err.message : err}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (saved.length === 0) {
|
|
100
|
+
rmSync(dir, { recursive: true, force: true });
|
|
101
|
+
return { batchId: null, queued: 0, errors };
|
|
102
|
+
}
|
|
103
|
+
const id = randomUUID();
|
|
104
|
+
batches.set(id, { id, sessionKey, dir, images: saved, at: Date.now() });
|
|
105
|
+
// Cap per session: drop the session's oldest batches beyond the limit.
|
|
106
|
+
const forSession = [...batches.values()]
|
|
107
|
+
.filter((b) => b.sessionKey === sessionKey)
|
|
108
|
+
.sort((a, b) => a.at - b.at);
|
|
109
|
+
for (const old of forSession.slice(0, Math.max(0, forSession.length - MAX_BATCHES_PER_SESSION))) {
|
|
110
|
+
batches.delete(old.id);
|
|
111
|
+
rmSync(old.dir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
return { batchId: id, queued: saved.length, errors };
|
|
114
|
+
}
|
|
115
|
+
/** Remove a just-queued batch (e.g. the PM send was rejected) and its files. */
|
|
116
|
+
export function dropPmImages(batchId) {
|
|
117
|
+
if (!batchId)
|
|
118
|
+
return;
|
|
119
|
+
const batch = batches.get(batchId);
|
|
120
|
+
if (!batch)
|
|
121
|
+
return;
|
|
122
|
+
batches.delete(batchId);
|
|
123
|
+
rmSync(batch.dir, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Attach the oldest pending batch whose PM session is still running to a
|
|
127
|
+
* freshly created task: move the files into the task's attachment folder
|
|
128
|
+
* under the usual `screenshot-N` names, reference them from the task body's
|
|
129
|
+
* `## Screenshots` section, and return the re-parsed task for the caller to
|
|
130
|
+
* apply to the index. Returns null when nothing is pending for a running
|
|
131
|
+
* session — the common case — or on any failure (best-effort; a failed
|
|
132
|
+
* attach never blocks the task creation itself, and the batch is dropped so
|
|
133
|
+
* a half-attached set cannot re-attach to a later task).
|
|
134
|
+
*/
|
|
135
|
+
export function attachPendingPmImages(config, task, isSessionRunning) {
|
|
136
|
+
prune();
|
|
137
|
+
const batch = [...batches.values()]
|
|
138
|
+
.filter((b) => isSessionRunning(b.sessionKey))
|
|
139
|
+
.sort((a, b) => a.at - b.at)[0];
|
|
140
|
+
if (!batch)
|
|
141
|
+
return null;
|
|
142
|
+
batches.delete(batch.id);
|
|
143
|
+
try {
|
|
144
|
+
const dir = attachmentsDir(config.root, config.workDir, task.id);
|
|
145
|
+
mkdirSync(dir, { recursive: true });
|
|
146
|
+
const taken = new Set(existsSync(dir) ? readdirSync(dir) : []);
|
|
147
|
+
const metas = [];
|
|
148
|
+
for (const img of batch.images) {
|
|
149
|
+
const ext = img.absPath.slice(img.absPath.lastIndexOf("."));
|
|
150
|
+
let next = 1;
|
|
151
|
+
while (taken.has(`screenshot-${next}${ext}`))
|
|
152
|
+
next++;
|
|
153
|
+
const file = `screenshot-${next}${ext}`;
|
|
154
|
+
renameSync(img.absPath, join(dir, file));
|
|
155
|
+
taken.add(file);
|
|
156
|
+
metas.push({
|
|
157
|
+
id: String(next),
|
|
158
|
+
name: img.name,
|
|
159
|
+
path: `${config.workDir.split("\\").join("/")}/.attachments/${task.id}/${file}`,
|
|
160
|
+
url: screenshotUrl(task.id, file),
|
|
161
|
+
size: img.size,
|
|
162
|
+
mime: img.mime,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
rmSync(batch.dir, { recursive: true, force: true });
|
|
166
|
+
if (metas.length === 0)
|
|
167
|
+
return null;
|
|
168
|
+
return patchTaskFile(config, task.absPath, { addScreenshots: metas });
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
rmSync(batch.dir, { recursive: true, force: true });
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Test hook: drop all in-memory batches (files under tmp roots are removed by the tests). */
|
|
176
|
+
export function resetPmImages() {
|
|
177
|
+
for (const batch of batches.values()) {
|
|
178
|
+
rmSync(batch.dir, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
batches.clear();
|
|
181
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live tracking of the PM agent's in-flight work per task (0335, 0381).
|
|
3
|
+
*
|
|
4
|
+
* Two distinct PM activities surface as the same "PM is working" indicator:
|
|
5
|
+
*
|
|
6
|
+
* - The freeform-create flesh-out — a fire-and-forget async closure in
|
|
7
|
+
* `createFreeformTask` that never enters the AgentRunner, so nothing else
|
|
8
|
+
* knows it is in flight and a fresh draft is visually indistinguishable
|
|
9
|
+
* from an idle one. Tracked here by task id; the route marks it when the
|
|
10
|
+
* run starts and clears it in a `finally` on every exit path.
|
|
11
|
+
* - The PM chat (task drawer's PM tab) — an AgentRunner session keyed by
|
|
12
|
+
* `pm-task-v2:<taskId>` (plus a `::<email>` per-user suffix, 0248). The
|
|
13
|
+
* route marks it when a message is accepted; the runner's `agent.exited`
|
|
14
|
+
* event (success, error, or user interrupt — every exit path) clears it
|
|
15
|
+
* via the emit hook in server.ts. Tracked by session key so two users can
|
|
16
|
+
* chat about the same task concurrently without one exit dropping the
|
|
17
|
+
* other's indicator.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately in-memory (like `reviews.isRunning`): the state is transient —
|
|
20
|
+
* a server restart kills the runs along with the registry, so a stale
|
|
21
|
+
* "working" flag cannot outlive the process. Task-file frontmatter would be
|
|
22
|
+
* the wrong home for the same reason.
|
|
23
|
+
*/
|
|
24
|
+
/** Mark a task's draft as being fleshed out by the PM agent right now. */
|
|
25
|
+
export declare function markPmWorking(taskId: string): void;
|
|
26
|
+
/** Clear the in-flight marker — must be called on every run exit path. */
|
|
27
|
+
export declare function clearPmWorking(taskId: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* Mark a PM chat session as live for a task (0381) — the runner accepted a
|
|
30
|
+
* message and a turn is starting (or queued behind maxConcurrentAgents).
|
|
31
|
+
*/
|
|
32
|
+
export declare function markPmChatSession(sessionKey: string, taskId: string): void;
|
|
33
|
+
/** Clear one PM chat session — called when the runner reports its exit. */
|
|
34
|
+
export declare function clearPmChatSession(sessionKey: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* The task id a runner session key belongs to, when that key is a PM chat
|
|
37
|
+
* session (`pm-task-v2:<id>` with an optional `::<email>` suffix) — null for
|
|
38
|
+
* anything else (engineer keys are bare task ids, review sessions are
|
|
39
|
+
* `review:<id>`, board chats have their own keys). Only the current
|
|
40
|
+
* `pm-task-v2` scheme is matched: the legacy `pm-task:` / `pm:` forms have no
|
|
41
|
+
* live sessions to clear.
|
|
42
|
+
*/
|
|
43
|
+
export declare function pmChatSessionTaskId(sessionKey: string): string | null;
|
|
44
|
+
/** True while the PM is working on this task — freeform flesh-out or chat. */
|
|
45
|
+
export declare function isPmWorking(taskId: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Augment any task payload with the live PM flag — the same shape
|
|
48
|
+
* `withReviewStatus` uses for review activity. Applied to every endpoint the
|
|
49
|
+
* board/drawer reads (`/api/board`, `/api/index`, `/api/tasks`, `/api/tasks/:id`,
|
|
50
|
+
* the freeform-create responses) so a refresh/reconnect always reconciles the
|
|
51
|
+
* indicator, not just live SSE. Null passes through (route handlers previously
|
|
52
|
+
* serialized `index.getTask()`'s null directly).
|
|
53
|
+
*/
|
|
54
|
+
export declare function withPmWorking<T extends {
|
|
55
|
+
id: string;
|
|
56
|
+
}>(task: T | null): (T & {
|
|
57
|
+
pmWorking: boolean;
|
|
58
|
+
}) | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live tracking of the PM agent's in-flight work per task (0335, 0381).
|
|
3
|
+
*
|
|
4
|
+
* Two distinct PM activities surface as the same "PM is working" indicator:
|
|
5
|
+
*
|
|
6
|
+
* - The freeform-create flesh-out — a fire-and-forget async closure in
|
|
7
|
+
* `createFreeformTask` that never enters the AgentRunner, so nothing else
|
|
8
|
+
* knows it is in flight and a fresh draft is visually indistinguishable
|
|
9
|
+
* from an idle one. Tracked here by task id; the route marks it when the
|
|
10
|
+
* run starts and clears it in a `finally` on every exit path.
|
|
11
|
+
* - The PM chat (task drawer's PM tab) — an AgentRunner session keyed by
|
|
12
|
+
* `pm-task-v2:<taskId>` (plus a `::<email>` per-user suffix, 0248). The
|
|
13
|
+
* route marks it when a message is accepted; the runner's `agent.exited`
|
|
14
|
+
* event (success, error, or user interrupt — every exit path) clears it
|
|
15
|
+
* via the emit hook in server.ts. Tracked by session key so two users can
|
|
16
|
+
* chat about the same task concurrently without one exit dropping the
|
|
17
|
+
* other's indicator.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately in-memory (like `reviews.isRunning`): the state is transient —
|
|
20
|
+
* a server restart kills the runs along with the registry, so a stale
|
|
21
|
+
* "working" flag cannot outlive the process. Task-file frontmatter would be
|
|
22
|
+
* the wrong home for the same reason.
|
|
23
|
+
*/
|
|
24
|
+
const pmWorking = new Map();
|
|
25
|
+
/** Active PM chat sessions: runner session key → the task it is about. */
|
|
26
|
+
const pmChatWorking = new Map();
|
|
27
|
+
/** Mark a task's draft as being fleshed out by the PM agent right now. */
|
|
28
|
+
export function markPmWorking(taskId) {
|
|
29
|
+
pmWorking.set(taskId, { startedAt: new Date().toISOString() });
|
|
30
|
+
}
|
|
31
|
+
/** Clear the in-flight marker — must be called on every run exit path. */
|
|
32
|
+
export function clearPmWorking(taskId) {
|
|
33
|
+
pmWorking.delete(taskId);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Mark a PM chat session as live for a task (0381) — the runner accepted a
|
|
37
|
+
* message and a turn is starting (or queued behind maxConcurrentAgents).
|
|
38
|
+
*/
|
|
39
|
+
export function markPmChatSession(sessionKey, taskId) {
|
|
40
|
+
pmChatWorking.set(sessionKey, { taskId, startedAt: new Date().toISOString() });
|
|
41
|
+
}
|
|
42
|
+
/** Clear one PM chat session — called when the runner reports its exit. */
|
|
43
|
+
export function clearPmChatSession(sessionKey) {
|
|
44
|
+
pmChatWorking.delete(sessionKey);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The task id a runner session key belongs to, when that key is a PM chat
|
|
48
|
+
* session (`pm-task-v2:<id>` with an optional `::<email>` suffix) — null for
|
|
49
|
+
* anything else (engineer keys are bare task ids, review sessions are
|
|
50
|
+
* `review:<id>`, board chats have their own keys). Only the current
|
|
51
|
+
* `pm-task-v2` scheme is matched: the legacy `pm-task:` / `pm:` forms have no
|
|
52
|
+
* live sessions to clear.
|
|
53
|
+
*/
|
|
54
|
+
export function pmChatSessionTaskId(sessionKey) {
|
|
55
|
+
const m = sessionKey.match(/^pm-task-v2:([^:]+)(?:::.*)?$/i);
|
|
56
|
+
return m ? m[1] || null : null;
|
|
57
|
+
}
|
|
58
|
+
/** True while the PM is working on this task — freeform flesh-out or chat. */
|
|
59
|
+
export function isPmWorking(taskId) {
|
|
60
|
+
if (pmWorking.has(taskId))
|
|
61
|
+
return true;
|
|
62
|
+
for (const { taskId: chatTaskId } of pmChatWorking.values()) {
|
|
63
|
+
if (chatTaskId === taskId)
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Augment any task payload with the live PM flag — the same shape
|
|
70
|
+
* `withReviewStatus` uses for review activity. Applied to every endpoint the
|
|
71
|
+
* board/drawer reads (`/api/board`, `/api/index`, `/api/tasks`, `/api/tasks/:id`,
|
|
72
|
+
* the freeform-create responses) so a refresh/reconnect always reconciles the
|
|
73
|
+
* indicator, not just live SSE. Null passes through (route handlers previously
|
|
74
|
+
* serialized `index.getTask()`'s null directly).
|
|
75
|
+
*/
|
|
76
|
+
export function withPmWorking(task) {
|
|
77
|
+
if (!task)
|
|
78
|
+
return null;
|
|
79
|
+
return { ...task, pmWorking: isPmWorking(task.id) };
|
|
80
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { RepoOSConfig, Task } from "../core/types.js";
|
|
2
|
+
import type { RepoEvent } from "./live-index.js";
|
|
3
|
+
export interface PreviewInfo {
|
|
4
|
+
port: number;
|
|
5
|
+
url: string;
|
|
6
|
+
startedAt: string;
|
|
7
|
+
pid: number;
|
|
8
|
+
/**
|
|
9
|
+
* Path polled on the preview URL for readiness (#0362): `/` (or the target's
|
|
10
|
+
* override). Always a project-declared path since #0370 removed the implicit
|
|
11
|
+
* `repoos serve` target.
|
|
12
|
+
*/
|
|
13
|
+
readyPath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Resolved custom command the preview child was started with (#0362), used to
|
|
16
|
+
* identify the process during boot-time orphan cleanup. Every preview now
|
|
17
|
+
* carries one — the implicit `repoos serve` fallback was removed (#0370).
|
|
18
|
+
*/
|
|
19
|
+
command?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Human label for which preview target ran (#0362 review): the matched
|
|
22
|
+
* `[[preview.targets]]` name, or "default" for the bare `[preview] command`.
|
|
23
|
+
* Aids debugging on a foreign repo with several configured targets.
|
|
24
|
+
*/
|
|
25
|
+
label?: string;
|
|
26
|
+
/**
|
|
27
|
+
* True when the preview child leads its own process group (POSIX custom
|
|
28
|
+
* commands), so stop/reap signals must target the group (`-pid`) to take the
|
|
29
|
+
* shell's whole tree down with it.
|
|
30
|
+
*/
|
|
31
|
+
processGroup?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface PreviewResult {
|
|
34
|
+
ok: boolean;
|
|
35
|
+
port?: number;
|
|
36
|
+
url?: string;
|
|
37
|
+
/** Readiness path for the started preview, so callers probe the right endpoint. */
|
|
38
|
+
readyPath?: string;
|
|
39
|
+
/** Which preview target ran (#0362 review) — see `PreviewInfo.label`. */
|
|
40
|
+
label?: string;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Result of a trusted server-side health/static probe of a preview URL (#0121). */
|
|
44
|
+
export interface PreviewProbe {
|
|
45
|
+
ok: boolean;
|
|
46
|
+
detail?: string;
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
/** The preview target resolved for one task (#0362): a project-declared
|
|
50
|
+
* shell command selected by the task's `area`. */
|
|
51
|
+
export type PreviewTarget = {
|
|
52
|
+
kind: "command";
|
|
53
|
+
/** Human label for diagnostics: the target name, or "default". */
|
|
54
|
+
label: string;
|
|
55
|
+
command: string;
|
|
56
|
+
cwd?: string;
|
|
57
|
+
readyPath: string;
|
|
58
|
+
/** How long to wait for this target before giving up (ms). See PreviewTargetConfig.readyTimeoutMs. */
|
|
59
|
+
readyTimeoutMs: number;
|
|
60
|
+
};
|
|
61
|
+
/** Resolution result: a runnable target, or a clean "nothing configured". */
|
|
62
|
+
export type PreviewTargetResolution = PreviewTarget | {
|
|
63
|
+
kind: "none";
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* A selectable preview target for the UI (#0379). Every configured target whose
|
|
68
|
+
* `areas` include the task's `area` is a candidate; when more than one matches,
|
|
69
|
+
* the drawer must let the user choose rather than silently previewing the first.
|
|
70
|
+
*/
|
|
71
|
+
export interface PreviewTargetOption {
|
|
72
|
+
/** Human label shown in the drawer and recorded on the run: the target's
|
|
73
|
+
* `name`, or "default" for the bare `[preview] command`. */
|
|
74
|
+
name: string;
|
|
75
|
+
/** Task areas this target declared (empty for the default command). */
|
|
76
|
+
areas: string[];
|
|
77
|
+
}
|
|
78
|
+
/** Options for `PreviewManager.start` (#0379). */
|
|
79
|
+
export interface PreviewStartOptions {
|
|
80
|
+
/**
|
|
81
|
+
* Permit starting the config-order first match when the task's area resolves
|
|
82
|
+
* to more than one target. The UI never sets this — it must make the user
|
|
83
|
+
* choose — so an ambiguous start with no `targetName` is rejected. The
|
|
84
|
+
* agent-request path has no picker, so it opts in and reports the chosen
|
|
85
|
+
* label in its transcript instead of claiming a choice was made.
|
|
86
|
+
*/
|
|
87
|
+
allowAmbiguous?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The preview targets the UI can offer for `task` (#0379). One entry for the
|
|
91
|
+
* common single-match case; several when the task's `area` is claimed by more
|
|
92
|
+
* than one `[[preview.targets]]`; empty when nothing is configured for it.
|
|
93
|
+
* Exported for the board/task routes and tests.
|
|
94
|
+
*/
|
|
95
|
+
export declare function previewTargetOptions(config: RepoOSConfig, task: Task): PreviewTargetOption[];
|
|
96
|
+
/**
|
|
97
|
+
* Decide how to preview `task` from the repo's `[preview]` config (#0362).
|
|
98
|
+
*
|
|
99
|
+
* Precedence: named targets whose `areas` include the task's `area` (config
|
|
100
|
+
* order); then a default `[preview] command`; then a `none` result with an
|
|
101
|
+
* actionable message (never a spawn failure). A project with no `[preview]`
|
|
102
|
+
* config at all is the same clean `none`, not an implicit RepoOS-board preview
|
|
103
|
+
* (#0370). When `targetName` is given — the UI's picker choice (#0379) — that
|
|
104
|
+
* exact target is used; an unknown name is a clean `none`, never a silent
|
|
105
|
+
* fallback to a different target. Exported for tests.
|
|
106
|
+
*/
|
|
107
|
+
export declare function resolvePreviewTarget(config: RepoOSConfig, task: Task, targetName?: string): PreviewTargetResolution;
|
|
108
|
+
/**
|
|
109
|
+
* Probe a live preview URL from the trusted server side (#0121): the target's
|
|
110
|
+
* configured readiness path (`/` by default — #0362). A healthy readiness
|
|
111
|
+
* endpoint is enough for a pass. Used when a sandboxed agent cannot open the
|
|
112
|
+
* returned URL itself.
|
|
113
|
+
*/
|
|
114
|
+
export declare function probePreview(url: string, readyPath?: string): Promise<PreviewProbe>;
|
|
115
|
+
export declare class PreviewManager {
|
|
116
|
+
private registry;
|
|
117
|
+
private readonly file;
|
|
118
|
+
private readonly config;
|
|
119
|
+
private readonly emit;
|
|
120
|
+
/** Last stderr lines from a still-booting preview, for diagnostics. */
|
|
121
|
+
private bootErrors;
|
|
122
|
+
/** Starts still in flight per task, so concurrent calls never double-spawn. */
|
|
123
|
+
private inflight;
|
|
124
|
+
constructor(config: RepoOSConfig, emit: (e: RepoEvent) => void);
|
|
125
|
+
/**
|
|
126
|
+
* PIDs of preview servers this manager started. Used by the serve-process
|
|
127
|
+
* census (#0216) so a legitimate preview is never counted as a stray.
|
|
128
|
+
*/
|
|
129
|
+
knownPids(): number[];
|
|
130
|
+
get(taskId: string): PreviewInfo | null;
|
|
131
|
+
/**
|
|
132
|
+
* Start a preview for a task: resolve its preview target from `[preview]`
|
|
133
|
+
* config (#0362), allocate an ephemeral port, spawn the project-declared
|
|
134
|
+
* command rooted at the worktree, and wait for it to come up. Returns
|
|
135
|
+
* `{ ok, port, url }` or a human-readable error.
|
|
136
|
+
*
|
|
137
|
+
* Every check happens BEFORE anything starts: the task id must resolve, the
|
|
138
|
+
* task must be in an allowed state (active/review), it must carry a
|
|
139
|
+
* registered branch, and that branch must have a real linked worktree. The
|
|
140
|
+
* worktree path is resolved from the task's own branch — never an
|
|
141
|
+
* agent-supplied path (ADR-0005). Repeated requests are idempotent and
|
|
142
|
+
* return the existing healthy preview.
|
|
143
|
+
*
|
|
144
|
+
* When the task's area matches more than one target (#0379), `targetName`
|
|
145
|
+
* picks the one the user chose in the drawer. Starting without a choice is
|
|
146
|
+
* rejected (never a silent first pick) unless the caller is the agent-request
|
|
147
|
+
* path, which opts in via `allowAmbiguous` and reports the label it got.
|
|
148
|
+
*/
|
|
149
|
+
start(task: Task, targetName?: string, opts?: PreviewStartOptions): Promise<PreviewResult>;
|
|
150
|
+
private doStart;
|
|
151
|
+
/** Stop a task's preview. Idempotent: stopping nothing is a no-op success. */
|
|
152
|
+
stop(taskId: string): Promise<void>;
|
|
153
|
+
/** Stop every preview — used on main-server shutdown. */
|
|
154
|
+
stopAll(): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Boot-time cleanup: kill any preview servers a previous (crashed) main
|
|
157
|
+
* server recorded and left running, then drop the registry file. Skipped in
|
|
158
|
+
* preview children (they serve the worktree, never the main registry).
|
|
159
|
+
*/
|
|
160
|
+
cleanupOrphans(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Enforce the concurrent cap (#0198): when at `MAX_PREVIEWS` and the task
|
|
163
|
+
* being started isn't already running, terminate the OLDEST running preview
|
|
164
|
+
* (FIFO) before a new one spawns. Logs the eviction with task id + timestamp.
|
|
165
|
+
*/
|
|
166
|
+
private evictIfAtCapacity;
|
|
167
|
+
/** Best-effort lifecycle log — task id and an ISO-8601 timestamp (#0198). */
|
|
168
|
+
private logLifecycle;
|
|
169
|
+
private spawnPreview;
|
|
170
|
+
/** Graceful SIGTERM, then SIGKILL after a short grace period. */
|
|
171
|
+
private kill;
|
|
172
|
+
/** Persist the registry so a crashed main server's previews can be reaped. */
|
|
173
|
+
private persist;
|
|
174
|
+
}
|