@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.
Files changed (362) hide show
  1. package/LICENSE.md +110 -0
  2. package/README.md +272 -0
  3. package/dist/.build-info.json +4 -0
  4. package/dist/.build-stamp.json +3 -0
  5. package/dist/cli/colors.d.ts +13 -0
  6. package/dist/cli/colors.js +37 -0
  7. package/dist/cli/index.d.ts +2 -0
  8. package/dist/cli/index.js +221 -0
  9. package/dist/commands/check.d.ts +170 -0
  10. package/dist/commands/check.js +1227 -0
  11. package/dist/commands/docs.d.ts +2 -0
  12. package/dist/commands/docs.js +70 -0
  13. package/dist/commands/gc.d.ts +1 -0
  14. package/dist/commands/gc.js +57 -0
  15. package/dist/commands/init.d.ts +9 -0
  16. package/dist/commands/init.js +699 -0
  17. package/dist/commands/screenshots.d.ts +7 -0
  18. package/dist/commands/screenshots.js +77 -0
  19. package/dist/commands/serve.d.ts +55 -0
  20. package/dist/commands/serve.js +204 -0
  21. package/dist/commands/status.d.ts +140 -0
  22. package/dist/commands/status.js +569 -0
  23. package/dist/commands/stop.d.ts +1 -0
  24. package/dist/commands/stop.js +59 -0
  25. package/dist/commands/tasks.d.ts +47 -0
  26. package/dist/commands/tasks.js +334 -0
  27. package/dist/commands/tunnel.d.ts +1 -0
  28. package/dist/commands/tunnel.js +1271 -0
  29. package/dist/commands/ui-harness.d.ts +53 -0
  30. package/dist/commands/ui-harness.js +62 -0
  31. package/dist/commands/ui-smoke.d.ts +6 -0
  32. package/dist/commands/ui-smoke.js +220 -0
  33. package/dist/commands/upgrade.d.ts +15 -0
  34. package/dist/commands/upgrade.js +188 -0
  35. package/dist/core/auth-store.d.ts +81 -0
  36. package/dist/core/auth-store.js +530 -0
  37. package/dist/core/auth.d.ts +75 -0
  38. package/dist/core/auth.js +124 -0
  39. package/dist/core/bootstrap.d.ts +41 -0
  40. package/dist/core/bootstrap.js +236 -0
  41. package/dist/core/build.d.ts +92 -0
  42. package/dist/core/build.js +249 -0
  43. package/dist/core/canary.d.ts +10 -0
  44. package/dist/core/canary.js +14 -0
  45. package/dist/core/check-failure-summary.d.ts +10 -0
  46. package/dist/core/check-failure-summary.js +34 -0
  47. package/dist/core/config.d.ts +160 -0
  48. package/dist/core/config.js +1288 -0
  49. package/dist/core/context-pack.d.ts +74 -0
  50. package/dist/core/context-pack.js +778 -0
  51. package/dist/core/db.d.ts +236 -0
  52. package/dist/core/db.js +747 -0
  53. package/dist/core/detect.d.ts +69 -0
  54. package/dist/core/detect.js +251 -0
  55. package/dist/core/docs.d.ts +76 -0
  56. package/dist/core/docs.js +225 -0
  57. package/dist/core/frontmatter.d.ts +29 -0
  58. package/dist/core/frontmatter.js +215 -0
  59. package/dist/core/git.d.ts +505 -0
  60. package/dist/core/git.js +1453 -0
  61. package/dist/core/indexer.d.ts +33 -0
  62. package/dist/core/indexer.js +282 -0
  63. package/dist/core/input.d.ts +66 -0
  64. package/dist/core/input.js +222 -0
  65. package/dist/core/logger.d.ts +70 -0
  66. package/dist/core/logger.js +138 -0
  67. package/dist/core/models.d.ts +45 -0
  68. package/dist/core/models.js +291 -0
  69. package/dist/core/net-probe.d.ts +4 -0
  70. package/dist/core/net-probe.js +20 -0
  71. package/dist/core/providers/deepinfra.d.ts +23 -0
  72. package/dist/core/providers/deepinfra.js +44 -0
  73. package/dist/core/providers/index.d.ts +24 -0
  74. package/dist/core/providers/index.js +59 -0
  75. package/dist/core/providers/openrouter.d.ts +28 -0
  76. package/dist/core/providers/openrouter.js +82 -0
  77. package/dist/core/providers/spend.d.ts +86 -0
  78. package/dist/core/providers/spend.js +286 -0
  79. package/dist/core/providers/types.d.ts +56 -0
  80. package/dist/core/providers/types.js +39 -0
  81. package/dist/core/repoos.d.ts +59 -0
  82. package/dist/core/repoos.js +221 -0
  83. package/dist/core/runtime.d.ts +35 -0
  84. package/dist/core/runtime.js +167 -0
  85. package/dist/core/skills-registry.d.ts +39 -0
  86. package/dist/core/skills-registry.js +148 -0
  87. package/dist/core/skills-registry.test.d.ts +1 -0
  88. package/dist/core/skills-registry.test.js +52 -0
  89. package/dist/core/sysmem.d.ts +7 -0
  90. package/dist/core/sysmem.js +55 -0
  91. package/dist/core/tailscale.d.ts +2 -0
  92. package/dist/core/tailscale.js +19 -0
  93. package/dist/core/task.d.ts +53 -0
  94. package/dist/core/task.js +310 -0
  95. package/dist/core/toml-line.d.ts +20 -0
  96. package/dist/core/toml-line.js +52 -0
  97. package/dist/core/toml-validate.d.ts +44 -0
  98. package/dist/core/toml-validate.js +335 -0
  99. package/dist/core/tunnel-assistant.d.ts +30 -0
  100. package/dist/core/tunnel-assistant.js +65 -0
  101. package/dist/core/tunnel-registry.d.ts +96 -0
  102. package/dist/core/tunnel-registry.js +267 -0
  103. package/dist/core/tunnel-registry.test.d.ts +1 -0
  104. package/dist/core/tunnel-registry.test.js +309 -0
  105. package/dist/core/tunnel.d.ts +103 -0
  106. package/dist/core/tunnel.js +320 -0
  107. package/dist/core/types.d.ts +811 -0
  108. package/dist/core/types.js +27 -0
  109. package/dist/core/worktree-gc.d.ts +57 -0
  110. package/dist/core/worktree-gc.js +194 -0
  111. package/dist/server/__tests__/integration-job.test.d.ts +1 -0
  112. package/dist/server/__tests__/integration-job.test.js +129 -0
  113. package/dist/server/agents.d.ts +1020 -0
  114. package/dist/server/agents.js +4070 -0
  115. package/dist/server/attachments.d.ts +51 -0
  116. package/dist/server/attachments.js +130 -0
  117. package/dist/server/auto-engineering.d.ts +45 -0
  118. package/dist/server/auto-engineering.js +275 -0
  119. package/dist/server/branchless-release.d.ts +22 -0
  120. package/dist/server/branchless-release.js +67 -0
  121. package/dist/server/built-in-agents.d.ts +305 -0
  122. package/dist/server/built-in-agents.js +1838 -0
  123. package/dist/server/cto-monitor.d.ts +35 -0
  124. package/dist/server/cto-monitor.js +281 -0
  125. package/dist/server/cto.d.ts +125 -0
  126. package/dist/server/cto.js +590 -0
  127. package/dist/server/deployments.d.ts +119 -0
  128. package/dist/server/deployments.js +325 -0
  129. package/dist/server/diff-snapshot.d.ts +20 -0
  130. package/dist/server/diff-snapshot.js +73 -0
  131. package/dist/server/done.d.ts +160 -0
  132. package/dist/server/done.js +488 -0
  133. package/dist/server/freeform.d.ts +55 -0
  134. package/dist/server/freeform.js +179 -0
  135. package/dist/server/handoff.d.ts +99 -0
  136. package/dist/server/handoff.js +575 -0
  137. package/dist/server/hetzner.d.ts +45 -0
  138. package/dist/server/hetzner.js +117 -0
  139. package/dist/server/icons.d.ts +8 -0
  140. package/dist/server/icons.js +195 -0
  141. package/dist/server/integration-job.d.ts +75 -0
  142. package/dist/server/integration-job.js +144 -0
  143. package/dist/server/integration-orchestrator.d.ts +211 -0
  144. package/dist/server/integration-orchestrator.js +1299 -0
  145. package/dist/server/integration-status.d.ts +48 -0
  146. package/dist/server/integration-status.js +71 -0
  147. package/dist/server/live-index.d.ts +295 -0
  148. package/dist/server/live-index.js +452 -0
  149. package/dist/server/model-test.d.ts +20 -0
  150. package/dist/server/model-test.js +100 -0
  151. package/dist/server/ntfy.d.ts +57 -0
  152. package/dist/server/ntfy.js +112 -0
  153. package/dist/server/pm-attachments.d.ts +35 -0
  154. package/dist/server/pm-attachments.js +181 -0
  155. package/dist/server/pm-runs.d.ts +58 -0
  156. package/dist/server/pm-runs.js +80 -0
  157. package/dist/server/preview.d.ts +174 -0
  158. package/dist/server/preview.js +641 -0
  159. package/dist/server/release.d.ts +88 -0
  160. package/dist/server/release.js +449 -0
  161. package/dist/server/reload.d.ts +194 -0
  162. package/dist/server/reload.js +586 -0
  163. package/dist/server/remote-validation.d.ts +132 -0
  164. package/dist/server/remote-validation.js +478 -0
  165. package/dist/server/repo-lock.d.ts +36 -0
  166. package/dist/server/repo-lock.js +141 -0
  167. package/dist/server/review-guard.d.ts +14 -0
  168. package/dist/server/review-guard.js +159 -0
  169. package/dist/server/review.d.ts +243 -0
  170. package/dist/server/review.js +1211 -0
  171. package/dist/server/routes/agents.d.ts +6 -0
  172. package/dist/server/routes/agents.js +28 -0
  173. package/dist/server/routes/auth.d.ts +60 -0
  174. package/dist/server/routes/auth.js +755 -0
  175. package/dist/server/routes/config.d.ts +20 -0
  176. package/dist/server/routes/config.js +347 -0
  177. package/dist/server/routes/debugger.d.ts +33 -0
  178. package/dist/server/routes/debugger.js +360 -0
  179. package/dist/server/routes/deployments.d.ts +3 -0
  180. package/dist/server/routes/deployments.js +16 -0
  181. package/dist/server/routes/docs.d.ts +8 -0
  182. package/dist/server/routes/docs.js +135 -0
  183. package/dist/server/routes/helpers.d.ts +21 -0
  184. package/dist/server/routes/helpers.js +171 -0
  185. package/dist/server/routes/index.d.ts +19 -0
  186. package/dist/server/routes/index.js +18 -0
  187. package/dist/server/routes/info.d.ts +17 -0
  188. package/dist/server/routes/info.js +177 -0
  189. package/dist/server/routes/inputs.d.ts +7 -0
  190. package/dist/server/routes/inputs.js +125 -0
  191. package/dist/server/routes/model-providers.d.ts +27 -0
  192. package/dist/server/routes/model-providers.js +94 -0
  193. package/dist/server/routes/models.d.ts +3 -0
  194. package/dist/server/routes/models.js +36 -0
  195. package/dist/server/routes/notify.d.ts +2 -0
  196. package/dist/server/routes/notify.js +17 -0
  197. package/dist/server/routes/playground.d.ts +29 -0
  198. package/dist/server/routes/playground.js +81 -0
  199. package/dist/server/routes/release.d.ts +20 -0
  200. package/dist/server/routes/release.js +86 -0
  201. package/dist/server/routes/router.d.ts +12 -0
  202. package/dist/server/routes/router.js +32 -0
  203. package/dist/server/routes/skill-registry.d.ts +5 -0
  204. package/dist/server/routes/skill-registry.js +51 -0
  205. package/dist/server/routes/tasks.d.ts +58 -0
  206. package/dist/server/routes/tasks.js +1442 -0
  207. package/dist/server/routes/transcribe.d.ts +2 -0
  208. package/dist/server/routes/transcribe.js +124 -0
  209. package/dist/server/routes/types.d.ts +68 -0
  210. package/dist/server/routes/types.js +1 -0
  211. package/dist/server/routes/ui.d.ts +5 -0
  212. package/dist/server/routes/ui.js +81 -0
  213. package/dist/server/routes/utils.d.ts +3 -0
  214. package/dist/server/routes/utils.js +23 -0
  215. package/dist/server/serve-reaper.d.ts +135 -0
  216. package/dist/server/serve-reaper.js +491 -0
  217. package/dist/server/server.d.ts +49 -0
  218. package/dist/server/server.js +2171 -0
  219. package/dist/server/supervisor.d.ts +89 -0
  220. package/dist/server/supervisor.js +231 -0
  221. package/dist/server/system.d.ts +198 -0
  222. package/dist/server/system.js +480 -0
  223. package/dist/server/task-check.d.ts +45 -0
  224. package/dist/server/task-check.js +71 -0
  225. package/dist/server/task-transitions.d.ts +51 -0
  226. package/dist/server/task-transitions.js +78 -0
  227. package/dist/server/task-watchdog.d.ts +158 -0
  228. package/dist/server/task-watchdog.js +681 -0
  229. package/dist/server/test-run.d.ts +28 -0
  230. package/dist/server/test-run.js +81 -0
  231. package/dist/server/watcher.d.ts +21 -0
  232. package/dist/server/watcher.js +194 -0
  233. package/dist/server/write.d.ts +94 -0
  234. package/dist/server/write.js +345 -0
  235. package/dist/ui/assets/AgentsView-CF4wRvHh.css +1 -0
  236. package/dist/ui/assets/AgentsView-D-myL-kA.js +1 -0
  237. package/dist/ui/assets/ContextView-D3TC-RSp.css +1 -0
  238. package/dist/ui/assets/ContextView-DsOqAdyE.js +4 -0
  239. package/dist/ui/assets/DashboardView-BOhsIJqO.js +1 -0
  240. package/dist/ui/assets/DashboardView-Ck9o0RP2.css +1 -0
  241. package/dist/ui/assets/DeploymentsView--iIUVfEH.css +1 -0
  242. package/dist/ui/assets/DeploymentsView-DXu_FYR2.js +1 -0
  243. package/dist/ui/assets/InputsView-24HVbVPk.js +1 -0
  244. package/dist/ui/assets/InputsView-BLb7GHtx.css +1 -0
  245. package/dist/ui/assets/LoginView-6QlGg6cx.css +1 -0
  246. package/dist/ui/assets/LoginView-BqupI0Lt.js +1 -0
  247. package/dist/ui/assets/ReleasesView-BEG1A0YH.css +1 -0
  248. package/dist/ui/assets/ReleasesView-CAgMuxpe.js +3 -0
  249. package/dist/ui/assets/SettingsView-CA-NYSMK.css +1 -0
  250. package/dist/ui/assets/SettingsView-O3iZSnjg.js +3 -0
  251. package/dist/ui/assets/WorkView-D8Tl6qwy.js +1 -0
  252. package/dist/ui/assets/WorkView-DMYL2s3W.css +1 -0
  253. package/dist/ui/assets/abnfDiagram-VCTEODGH-J5lkSnEE.js +1 -0
  254. package/dist/ui/assets/arc-BHZnmhhM.js +1 -0
  255. package/dist/ui/assets/architecture-7GRP2DOG-pLdFt-xS.js +1 -0
  256. package/dist/ui/assets/architectureDiagram-5GKGNRK7-C3k89uKX.js +36 -0
  257. package/dist/ui/assets/array-BifhSqXX.js +1 -0
  258. package/dist/ui/assets/blockDiagram-I7D4REHJ-AWNoBzop.js +129 -0
  259. package/dist/ui/assets/button-lbikqSph.js +1 -0
  260. package/dist/ui/assets/c4Diagram-7LVT6UL2-DKG1D5gd.js +38 -0
  261. package/dist/ui/assets/channel-gA4qBv3A.js +1 -0
  262. package/dist/ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  263. package/dist/ui/assets/chunk-4HAMMTFA-D1YR8lF_.js +62 -0
  264. package/dist/ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  265. package/dist/ui/assets/chunk-75Z2AOVW-5GuBxfsL.js +2 -0
  266. package/dist/ui/assets/chunk-DU6HZSFF-DfdNNqwa.js +127 -0
  267. package/dist/ui/assets/chunk-F27PBJKO-BPZzinQ4.js +1 -0
  268. package/dist/ui/assets/chunk-FOHPRMQF-l2z4K2jD.js +161 -0
  269. package/dist/ui/assets/chunk-GMAD6QVW-DGWvSKj5.js +72 -0
  270. package/dist/ui/assets/chunk-GVQU2GXP-D6Aw75wJ.js +1 -0
  271. package/dist/ui/assets/chunk-IMKFNOWR-B8mMBKCW.js +231 -0
  272. package/dist/ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  273. package/dist/ui/assets/chunk-L3NEJ4N5-D5OJuXYE.js +1 -0
  274. package/dist/ui/assets/chunk-OSK3NFVY-B7r9w2UW.js +10 -0
  275. package/dist/ui/assets/chunk-P2QGCYS3-C74X4AQX.js +1 -0
  276. package/dist/ui/assets/chunk-POPQ4Y6H-C5_u1gUS.js +1 -0
  277. package/dist/ui/assets/chunk-PWAF6VOD-DfoTLtxn.js +1 -0
  278. package/dist/ui/assets/chunk-SHT3W25Y-D40m2eqz.js +168 -0
  279. package/dist/ui/assets/chunk-SVP7TREG-euxi0Kap.js +88 -0
  280. package/dist/ui/assets/chunk-TICWLB2K-fkdxngzN.js +206 -0
  281. package/dist/ui/assets/chunk-XXDRQBXY-DFBUG-OT.js +1 -0
  282. package/dist/ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  283. package/dist/ui/assets/classDiagram-ZZMXUADV-BzW8bq2P.js +1 -0
  284. package/dist/ui/assets/classDiagram-v2-VYDZK3BY-BzW8bq2P.js +1 -0
  285. package/dist/ui/assets/cose-bilkent-JH36ORCC-BTcgMon1.js +1 -0
  286. package/dist/ui/assets/cynefin-OW5HDTMX-B5m_COsS.js +1 -0
  287. package/dist/ui/assets/cynefinDiagram-5FMLGOSQ-C-AG6CGe.js +62 -0
  288. package/dist/ui/assets/cytoscape.esm-FBntH-9t.js +321 -0
  289. package/dist/ui/assets/dagre-GXQ25YYZ-DDfDnp8L.js +4 -0
  290. package/dist/ui/assets/dagre-PrKaheQc.js +1 -0
  291. package/dist/ui/assets/defaultLocale-BFoDCU3G.js +1 -0
  292. package/dist/ui/assets/diagram-S7CK7UJ4-DjQZAlEY.js +30 -0
  293. package/dist/ui/assets/diagram-UQ7AKVKN-DAZ9yp1B.js +41 -0
  294. package/dist/ui/assets/diagram-VSXAHHWV-BWepUKJD.js +3 -0
  295. package/dist/ui/assets/diagram-VX7I27RA-EcV1J81b.js +24 -0
  296. package/dist/ui/assets/diagram-Z3DM3KII-Dbz7XU_K.js +24 -0
  297. package/dist/ui/assets/dist-DTg6UBE_.js +1 -0
  298. package/dist/ui/assets/ebnfDiagram-PWID7BFC-BUyQw8Mm.js +1 -0
  299. package/dist/ui/assets/erDiagram-RLTQ6QDP-WVf8ukKt.js +99 -0
  300. package/dist/ui/assets/eventmodeling-NTZA5JFV-DHqyLOAn.js +1 -0
  301. package/dist/ui/assets/flowDiagram-HODETNUW-CYk_ze5K.js +1 -0
  302. package/dist/ui/assets/ganttDiagram-EL5Y4UJY-rB4MZuB-.js +292 -0
  303. package/dist/ui/assets/gitGraph-4MIJSDKK-BOTWM8dY.js +1 -0
  304. package/dist/ui/assets/gitGraphDiagram-WWUBYQGX-eYscuhtP.js +106 -0
  305. package/dist/ui/assets/graphlib-DS17s2tU.js +1 -0
  306. package/dist/ui/assets/index-BACBfnga.css +1 -0
  307. package/dist/ui/assets/index-BOQ-ust6.js +26 -0
  308. package/dist/ui/assets/info-A6RAGUB7-_67wOORe.js +1 -0
  309. package/dist/ui/assets/infoDiagram-27XIBGKW-XVXbddTA.js +2 -0
  310. package/dist/ui/assets/init-C-OQMol4.js +1 -0
  311. package/dist/ui/assets/ishikawaDiagram-5VMMS53U-ClLNiEru.js +70 -0
  312. package/dist/ui/assets/journeyDiagram-3NMN7TZE-NRawjR6i.js +139 -0
  313. package/dist/ui/assets/kanban-definition-UXKFOSKX-BgSzBfIx.js +89 -0
  314. package/dist/ui/assets/katex-DolUETbr.js +257 -0
  315. package/dist/ui/assets/line-KAyfuul4.js +1 -0
  316. package/dist/ui/assets/linear-cCCPOpu7.js +1 -0
  317. package/dist/ui/assets/mermaid-parser.core-CgXZk1iN.js +7 -0
  318. package/dist/ui/assets/mermaid.core-BUUkChHW.js +44 -0
  319. package/dist/ui/assets/mindmap-definition-YA3MSWOX-Cpn7vW4r.js +96 -0
  320. package/dist/ui/assets/ordinal-BDEzSJ7C.js +1 -0
  321. package/dist/ui/assets/packet-AYTQ26CC-0lZwM-d6.js +1 -0
  322. package/dist/ui/assets/path-COt_16Va.js +1 -0
  323. package/dist/ui/assets/pegDiagram-XKGWAZYB-impy0inn.js +1 -0
  324. package/dist/ui/assets/pie-WAS4IAKB-CqfdGHYg.js +1 -0
  325. package/dist/ui/assets/pieDiagram-E7YTZNPT-CxjL4I1h.js +39 -0
  326. package/dist/ui/assets/quadrantDiagram-AXDQQJYC-Cjd1QMIe.js +7 -0
  327. package/dist/ui/assets/radar-RG4KPBEZ-CRkQIwiv.js +1 -0
  328. package/dist/ui/assets/railroad-74A4TZTK-i67XSWTB.js +1 -0
  329. package/dist/ui/assets/railroad-abnf-HS5TGJTU-Cv9m5aS3.js +1 -0
  330. package/dist/ui/assets/railroad-ebnf-LZEXJU2U-C5ZIn-Jh.js +1 -0
  331. package/dist/ui/assets/railroad-peg-WCYAUIDC-CGra9Oq-.js +1 -0
  332. package/dist/ui/assets/railroadDiagram-O6MQD6OU-_pK0qyaY.js +1 -0
  333. package/dist/ui/assets/releases-B0WNV-SY.js +1 -0
  334. package/dist/ui/assets/repoos-cto-square.webp +0 -0
  335. package/dist/ui/assets/repoos-helpful-pm-square.webp +0 -0
  336. package/dist/ui/assets/repoos-orchestrator-square.webp +0 -0
  337. package/dist/ui/assets/repoos-performance-agent-square.webp +0 -0
  338. package/dist/ui/assets/repoos-ross-from-friends-square.webp +0 -0
  339. package/dist/ui/assets/repoos-tech-debt-agent-square.webp +0 -0
  340. package/dist/ui/assets/requirementDiagram-BXWQKSXE-CRrFX-yw.js +84 -0
  341. package/dist/ui/assets/rough.esm-By172zw-.js +1 -0
  342. package/dist/ui/assets/sankeyDiagram-P5KCCOFB-X_xFRj9E.js +40 -0
  343. package/dist/ui/assets/sequenceDiagram-WJ2MYXX4-Byb49lKf.js +162 -0
  344. package/dist/ui/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  345. package/dist/ui/assets/src-BH-TyZbA.js +1 -0
  346. package/dist/ui/assets/stateDiagram-D77RDMKH-CzaPrqYF.js +1 -0
  347. package/dist/ui/assets/stateDiagram-v2-MP3YSRHH-BCXckz7K.js +1 -0
  348. package/dist/ui/assets/swimlanes-42K2YHIH-Cx_1lnqr.js +1 -0
  349. package/dist/ui/assets/swimlanesDiagram-VR7AAH4N-54qsKi6A.js +8 -0
  350. package/dist/ui/assets/timeline-definition-24CTP7MA-B1IbZPQ-.js +120 -0
  351. package/dist/ui/assets/treeView-Q6P3EWNA-DFWhVE3d.js +1 -0
  352. package/dist/ui/assets/treemap-WGGIJYW6-BmbYPThn.js +1 -0
  353. package/dist/ui/assets/vendor-ui-DPm7zLxi.js +3 -0
  354. package/dist/ui/assets/vendor-vue-CobgyEJp.js +1 -0
  355. package/dist/ui/assets/vennDiagram-4TSXK5OY-Ae2AtRpt.js +34 -0
  356. package/dist/ui/assets/wardley-WFR3VGLG-BtD4X4tL.js +1 -0
  357. package/dist/ui/assets/wardleyDiagram-VM6X3IG4-B3YN2fTd.js +78 -0
  358. package/dist/ui/assets/xychartDiagram-S5SC5T6Z-CIzJfMKA.js +7 -0
  359. package/dist/ui/favicon.svg +11 -0
  360. package/dist/ui/index.html +49 -0
  361. package/dist/ui/sw.js +48 -0
  362. package/package.json +91 -0
@@ -0,0 +1,586 @@
1
+ /**
2
+ * Auto-reload for `repoos serve` (task 0066).
3
+ *
4
+ * A long-lived serve process loads compiled JS from dist/ at startup and never
5
+ * reloads, so after a task merges to main and `bun run build` writes a new
6
+ * dist/, the running server keeps serving the OLD build — the live API/UI
7
+ * silently diverge from main until a human restarts. This manager closes that
8
+ * gap:
9
+ *
10
+ * - On boot it captures the build hash from dist/.build-info.json. If a newer
11
+ * build already landed on disk, it reloads immediately (stale-boot
12
+ * self-heal).
13
+ * - It watches dist/.build-info.json (fs.watch on the dist dir, plus a
14
+ * low-frequency hash poll as a platform-proof fallback). A hash CHANGE
15
+ * schedules a reload — the same hash mechanism the CLI staleness guard uses,
16
+ * never mtimes or timers.
17
+ * - A reload no longer defers for agent turns (0214): agent child stdout/stderr
18
+ * is durable (per-task log file in .repoos/agent-logs/), so the replacement
19
+ * server re-attaches to still-running children via the durable registry
20
+ * (.repoos/agents.json). A restart proceeds immediately regardless of how
21
+ * many agent turns are in flight.
22
+ * - A close-out (0143) is a harder deferral: the pipeline itself runs builds
23
+ * and checks, so reloading under it would kill the server mid-close-out.
24
+ * While the close-out lock is held, a new build is PARKED instead — the UI
25
+ * is told a new version is available and the reload fires only when the
26
+ * user asks for it (POST /api/server/restart). A reload already in flight
27
+ * when a close-out begins is aborted at handover so the pipeline is never
28
+ * killed.
29
+ * - Reload = spawn a replacement `repoos serve` on the same host/port
30
+ * (REPOOS_RELOAD=1 + a per-reload handshake secret), confirm it is up by
31
+ * polling /api/health for the handshake, then exit cleanly. The replacement
32
+ * retries its bind while the parent holds the port; the parent releases its
33
+ * listener (a brief graceful drain) so the replacement can bind, then exits
34
+ * only after the replacement is confirmed ready. If the replacement never
35
+ * becomes ready the parent re-binds and keeps serving — no outage.
36
+ * SO_REUSEPORT is attempted by the replacement for a zero-downtime handoff
37
+ * on platforms that support it; where it is unsupported (ENOTSUP on macOS,
38
+ * the platform this repo runs on) the drain path is used instead.
39
+ *
40
+ * Zero runtime deps: node:fs / node:path / node:http / node:child_process.
41
+ */
42
+ import { spawn } from "node:child_process";
43
+ import { randomBytes } from "node:crypto";
44
+ import { existsSync, readFileSync, watch } from "node:fs";
45
+ import http from "node:http";
46
+ import { join } from "node:path";
47
+ import { fileURLToPath } from "node:url";
48
+ import { readBuildStamp } from "../core/build.js";
49
+ import { isPortListening } from "./serve-reaper.js";
50
+ /** True when running from src/ (dev mode) rather than compiled dist/. */
51
+ export function isDevBuild() {
52
+ const pathSep = process.platform === "win32" ? "\\\\" : "/";
53
+ return fileURLToPath(import.meta.url).includes(`${pathSep}src${pathSep}`);
54
+ }
55
+ /** Read the build hash from dist/.build-info.json, or null when absent. */
56
+ export function readBuildHash(root) {
57
+ const file = join(root, "dist", ".build-info.json");
58
+ try {
59
+ const info = JSON.parse(readFileSync(file, "utf8"));
60
+ return typeof info.hash === "string" && info.hash ? info.hash : null;
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ /**
67
+ * Read the build timestamp from dist/.build-stamp.json, or null. Kept separate
68
+ * from the hash marker so `.build-info.json` stays byte-identical across
69
+ * rebuilds of identical source — see readBuildStamp in core/build.ts.
70
+ */
71
+ export function readBuildAt(root) {
72
+ return readBuildStamp(root);
73
+ }
74
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
75
+ const DEFAULT_POLL_MS = 5000;
76
+ const DEFAULT_RETRY_MS = 2000;
77
+ const DEFAULT_GRACE_MS = 2000;
78
+ const DEFAULT_HANDSHAKE_TIMEOUT_MS = 30_000;
79
+ /** Sustained-health window before a replacement handoff is confirmed. */
80
+ const DEFAULT_CONFIRM_MS = 750;
81
+ const RELOAD_POLL_MS = 150;
82
+ const REBIND_TIMEOUT_MS = 5000;
83
+ const WATCH_DEBOUNCE_MS = 60;
84
+ /**
85
+ * Backoff after a failed handoff (#0271 incident): a replacement that fails
86
+ * to become ready gets retried on the very next poll tick with no cooldown.
87
+ * If builds keep landing (several close-outs in quick succession) that
88
+ * becomes an unbounded retry loop — a full server boot, every ~5s, forever —
89
+ * which is what actually took the control plane down (29 failed attempts
90
+ * back to back). Doubles per consecutive failure, capped, and resets on the
91
+ * next successful handoff.
92
+ */
93
+ const BACKOFF_BASE_MS = 10_000;
94
+ const BACKOFF_MAX_MS = 5 * 60_000;
95
+ /** GET a URL and JSON.parse it; resolves null on any failure (never throws). */
96
+ function probeJson(url) {
97
+ return new Promise((resolve) => {
98
+ const req = http.get(url, (res) => {
99
+ let data = "";
100
+ res.setEncoding("utf8");
101
+ res.on("data", (c) => {
102
+ data += c;
103
+ });
104
+ res.on("end", () => {
105
+ try {
106
+ resolve(JSON.parse(data));
107
+ }
108
+ catch {
109
+ resolve(null);
110
+ }
111
+ });
112
+ });
113
+ req.on("error", () => resolve(null));
114
+ req.setTimeout(2000, () => {
115
+ req.destroy();
116
+ resolve(null);
117
+ });
118
+ });
119
+ }
120
+ export class ReloadManager {
121
+ options;
122
+ loadedHash;
123
+ fsWatcher = null;
124
+ debounceTimer = null;
125
+ pollTimer = null;
126
+ retryTimer = null;
127
+ reloading = false;
128
+ pending = false;
129
+ stopped = false;
130
+ child = null;
131
+ childExited = false;
132
+ /** Whether we released our own HTTP listener for the replacement to bind. */
133
+ drained = false;
134
+ /** Consecutive failed handoffs. Reset on the next successful one. */
135
+ consecutiveFailures = 0;
136
+ /** Automatic (non-manual) reloads are refused until this time, post-failure. */
137
+ backoffUntil = 0;
138
+ /**
139
+ * Hash of a build parked by a close-out (0143). Never auto-reloaded — it
140
+ * waits for the user to trigger POST /api/server/restart.
141
+ */
142
+ buildAvailableHash = null;
143
+ /** On-disk build timestamp of the parked build (dist/.build-info.json). */
144
+ buildAvailableAt = null;
145
+ constructor(options) {
146
+ this.options = options;
147
+ this.loadedHash = options.loadedHash;
148
+ }
149
+ get enabled() {
150
+ return this.options.enabled !== false;
151
+ }
152
+ /** True while the manager is actively handing over to a replacement process. */
153
+ get isReloading() {
154
+ return this.reloading;
155
+ }
156
+ /**
157
+ * The build parked by a close-out (0143), if any — hash plus its on-disk
158
+ * build timestamp (dist/.build-info.json generatedAt). Exposed so /api/health
159
+ * can report it and a (re)connecting UI can surface the "new build available"
160
+ * notice without ever having seen the one-shot build.available SSE event.
161
+ * Null while nothing is parked.
162
+ */
163
+ get buildAvailable() {
164
+ return this.buildAvailableHash === null
165
+ ? null
166
+ : { hash: this.buildAvailableHash, buildAt: this.buildAvailableAt };
167
+ }
168
+ /** Begin watching dist/.build-info.json (fs.watch + fallback hash poll). */
169
+ start() {
170
+ if (!this.enabled)
171
+ return;
172
+ this.startWatching();
173
+ this.pollTimer = setInterval(() => this.onPoll(), this.options.pollMs ?? DEFAULT_POLL_MS);
174
+ this.pollTimer.unref?.();
175
+ }
176
+ /** Stop watching and cancel timers. Idempotent. */
177
+ stop() {
178
+ this.stopped = true;
179
+ this.clearTimers();
180
+ if (this.fsWatcher) {
181
+ try {
182
+ this.fsWatcher.close();
183
+ }
184
+ catch {
185
+ /* ignore */
186
+ }
187
+ this.fsWatcher = null;
188
+ }
189
+ }
190
+ /**
191
+ * Stale-boot self-heal: if a newer build landed while we were starting,
192
+ * reload immediately (deferred while an agent runs). The replacement child
193
+ * (REPOOS_RELOAD=1) skips this — it boots fresh on the current build.
194
+ */
195
+ bootSelfHeal() {
196
+ if (this.stopped || !this.enabled)
197
+ return;
198
+ if (this.options.isReplacement)
199
+ return;
200
+ const current = readBuildHash(this.options.root);
201
+ if (current !== null && this.loadedHash !== null && current !== this.loadedHash) {
202
+ void this.requestReload("stale boot");
203
+ }
204
+ }
205
+ /**
206
+ * Public trigger used by the watcher, the boot self-heal, and
207
+ * POST /api/server/restart. Fires the reload when stale and idle; defers
208
+ * while an agent turn runs; reports not-stale when nothing is out of date.
209
+ *
210
+ * `manual` (POST /api/server/restart, a human explicitly asking) bypasses
211
+ * the post-failure backoff below — automatic triggers (the poll, boot
212
+ * self-heal) respect it so a run of failed handoffs doesn't retry every
213
+ * ~5s forever (#0271 incident).
214
+ */
215
+ requestReload(reason, opts = {}) {
216
+ if (!this.enabled)
217
+ return { state: "not-stale", reason: "auto-reload disabled" };
218
+ if (this.stopped)
219
+ return { state: "not-stale", reason: "server is shutting down" };
220
+ if (this.reloading)
221
+ return { state: "reloading", reason };
222
+ const running = this.options.isBusy();
223
+ if (!opts.manual && Date.now() < this.backoffUntil) {
224
+ return {
225
+ state: "deferred",
226
+ running,
227
+ reason: `backing off after ${this.consecutiveFailures} failed reload attempt(s) — retrying automatically at ${new Date(this.backoffUntil).toISOString()}, or POST /api/server/restart to retry now`,
228
+ };
229
+ }
230
+ // Close-out deferral (0143): the close-out pipeline merges the branch and
231
+ // runs build/screenshots/check, all of which would be killed if the server
232
+ // reloaded itself mid-flight. Park the new build and surface it to the UI —
233
+ // the user reloads when they choose (POST /api/server/restart).
234
+ if (this.options.closingOut()) {
235
+ const current = readBuildHash(this.options.root);
236
+ if (this.loadedHash !== null && current !== null && current !== this.loadedHash) {
237
+ this.parkBuild(current);
238
+ }
239
+ return { state: "deferred", running, reason };
240
+ }
241
+ // Agent-turn deferral removed (0214): in-flight agent children are now
242
+ // durable — stdout/stderr writes to a per-task log file and the new server
243
+ // re-attaches via adoptRunningAgents(). The restart proceeds immediately.
244
+ const current = readBuildHash(this.options.root);
245
+ if (this.loadedHash === null || current === null || current === this.loadedHash) {
246
+ return { state: "not-stale", reason };
247
+ }
248
+ void this.reload(reason);
249
+ return { state: "reloading", reason };
250
+ }
251
+ /**
252
+ * The SSE event funnel calls this so a drained runner unblocks a deferred
253
+ * reload. `review` events count: an agent review is a spawned turn too, and
254
+ * one finishing can be what makes the server idle.
255
+ */
256
+ onEvent(e) {
257
+ if (e.type === "agent.exited" || e.type === "review")
258
+ this.tryFirePending();
259
+ }
260
+ onPoll() {
261
+ if (this.stopped || this.reloading)
262
+ return;
263
+ const current = readBuildHash(this.options.root);
264
+ if (current === null || this.loadedHash === null || current === this.loadedHash)
265
+ return;
266
+ // A close-out landed a new build on disk: park it for the user instead of
267
+ // reloading under the pipeline that is orchestrating the close-out. This
268
+ // runs even while a busy-deferral is armed — the park supersedes it.
269
+ if (this.options.closingOut()) {
270
+ this.parkBuild(current);
271
+ return;
272
+ }
273
+ if (this.pending)
274
+ return;
275
+ // A build parked by a finished close-out is not auto-reloadable — only a
276
+ // fresh hash beyond it (a normal dev build) triggers live reload.
277
+ if (current === this.buildAvailableHash)
278
+ return;
279
+ void this.requestReload("build changed (poll)");
280
+ }
281
+ tryFirePending() {
282
+ if (!this.pending || this.reloading || this.stopped || this.options.isBusy() !== 0)
283
+ return;
284
+ // A close-out started while the reload was deferred: the build it produces
285
+ // must wait for the user, not auto-fire on drain.
286
+ if (this.options.closingOut()) {
287
+ this.pending = false;
288
+ this.clearRetry();
289
+ const current = readBuildHash(this.options.root);
290
+ if (current !== null && this.loadedHash !== null && current !== this.loadedHash) {
291
+ this.parkBuild(current);
292
+ }
293
+ return;
294
+ }
295
+ this.pending = false;
296
+ this.clearRetry();
297
+ void this.reload("deferred reload: agents drained");
298
+ }
299
+ /**
300
+ * Park a build that landed during a close-out: it is served only once the
301
+ * user triggers a reload. Idempotent per hash. Supersedes any armed
302
+ * busy-deferral so an eventual drain does not auto-fire it.
303
+ */
304
+ parkBuild(hash) {
305
+ if (this.buildAvailableHash === hash)
306
+ return;
307
+ this.buildAvailableHash = hash;
308
+ this.buildAvailableAt = readBuildAt(this.options.root);
309
+ if (this.pending) {
310
+ this.pending = false;
311
+ this.clearRetry();
312
+ }
313
+ this.options.onBuildAvailable?.(hash);
314
+ }
315
+ startWatching() {
316
+ const dir = join(this.options.root, "dist");
317
+ if (!existsSync(dir))
318
+ return;
319
+ try {
320
+ this.fsWatcher = watch(dir, (_event, filename) => {
321
+ if (filename && filename.toString() !== ".build-info.json")
322
+ return;
323
+ if (this.debounceTimer)
324
+ clearTimeout(this.debounceTimer);
325
+ this.debounceTimer = setTimeout(() => {
326
+ this.debounceTimer = null;
327
+ this.onPoll();
328
+ }, WATCH_DEBOUNCE_MS);
329
+ });
330
+ }
331
+ catch {
332
+ // fs.watch unavailable on the platform — the hash poll covers us.
333
+ this.fsWatcher = null;
334
+ }
335
+ }
336
+ async reload(reason) {
337
+ if (this.reloading || this.stopped)
338
+ return;
339
+ this.reloading = true;
340
+ this.pending = false;
341
+ this.clearRetry();
342
+ const entry = this.options.cliEntry();
343
+ if (!entry) {
344
+ this.reloading = false;
345
+ this.log("reload: could not locate the repoos CLI — staying on this build");
346
+ return;
347
+ }
348
+ const secret = randomBytes(16).toString("hex");
349
+ this.childExited = false;
350
+ this.drained = false;
351
+ let child;
352
+ try {
353
+ child = spawn(process.execPath, [entry, "serve", "--port", String(this.options.port), "--host", this.options.host], {
354
+ cwd: this.options.root,
355
+ // A replacement can start server-owned children (notably previews)
356
+ // before a failed handover is detected. Give it its own process group
357
+ // so failure cleanup can terminate that whole replacement tree rather
358
+ // than leaving descendants alive under an otherwise-live ancestor.
359
+ // Without this, the periodic orphan sweep cannot safely classify the
360
+ // leftovers: their immediate parent is still alive.
361
+ detached: process.platform !== "win32",
362
+ // The replacement must inherit the durable streams of the process
363
+ // supervisor (for example nohup's server.out). Pipes belong to this
364
+ // old process; once it hands over and exits, a later console write
365
+ // in the replacement can hit EPIPE and leave the control port down.
366
+ stdio: ["ignore", "inherit", "inherit"],
367
+ env: { ...process.env, REPOOS_RELOAD: "1", REPOOS_RELOAD_SECRET: secret },
368
+ });
369
+ }
370
+ catch (err) {
371
+ this.reloading = false;
372
+ this.log(`reload: could not spawn the replacement: ${err.message}`);
373
+ return;
374
+ }
375
+ this.child = child;
376
+ child.on("error", (err) => {
377
+ this.childExited = true;
378
+ this.log(`reload: replacement pid ${child.pid ?? "unknown"} spawn error: ${err.message}`);
379
+ });
380
+ child.on("exit", (code, signal) => {
381
+ this.childExited = true;
382
+ this.log(`reload: replacement pid ${child.pid ?? "unknown"} exited (code=${code ?? "null"}, signal=${signal ?? "none"})`);
383
+ });
384
+ this.log(`reload: spawning replacement pid ${child.pid ?? "unknown"} on ${this.options.host}:${this.options.port} (${reason})`);
385
+ const confirmed = await this.waitForReplacement(secret);
386
+ // #0096: a confirmed handshake is only trustworthy when the replacement
387
+ // process is still alive at the moment of handover. If it died during the
388
+ // sustained-health window or right at confirmation, we must NOT log
389
+ // "replacement is up" or exit — we re-bind and keep serving instead.
390
+ if (confirmed && !this.childExited && this.options.closingOut()) {
391
+ // A close-out started while the replacement was warming up. Handing over
392
+ // now would kill the server mid-pipeline (0143), so abort the reload:
393
+ // kill the replacement, re-bind, and park the new build for the user.
394
+ // `reloading` stays true until cleanup finishes — see the failure branch
395
+ // below for why resetting it earlier is unsafe (#leak).
396
+ await this.killChild();
397
+ const rebound = await this.tryRebind();
398
+ this.reloading = false;
399
+ const current = readBuildHash(this.options.root);
400
+ if (current !== null && this.loadedHash !== null && current !== this.loadedHash) {
401
+ this.parkBuild(current);
402
+ }
403
+ this.log(`reload: handover aborted — close-out in progress${rebound === "bound" ? "" : ` — ${rebound} (server may be down)`}`);
404
+ }
405
+ else if (confirmed && !this.childExited) {
406
+ // A real handoff: whatever run of failures preceded it is over.
407
+ this.consecutiveFailures = 0;
408
+ this.backoffUntil = 0;
409
+ this.stopped = true;
410
+ child.unref?.();
411
+ this.log(`reload: replacement is up on ${this.options.host}:${this.options.port} — handing over`);
412
+ await this.options.onReloadConfirmed();
413
+ }
414
+ else {
415
+ // #leak: `reloading` must stay true for the whole cleanup, not just the
416
+ // spawn+wait phase. Resetting it before killChild()/tryRebind() complete
417
+ // reopens the reload() re-entry guard mid-cleanup — a poll tick or
418
+ // fs.watch event firing in that window starts a NEW reload(), which
419
+ // overwrites `this.child` with the new attempt before this failed
420
+ // attempt's killChild() reads it. killChild() then kills the WRONG
421
+ // (newer) child while the original failed replacement leaks forever,
422
+ // unmanaged. Under load (many poll ticks racing a slow cleanup) this
423
+ // fires repeatedly and accumulates orphaned `repoos serve` processes.
424
+ await this.killChild();
425
+ const rebound = await this.tryRebind();
426
+ this.reloading = false;
427
+ // Backoff (#0271 incident): a genuine failure to become ready — not a
428
+ // close-out abort, that's handled in the branch above — arms a cooldown
429
+ // so the next AUTOMATIC trigger (poll tick, boot self-heal) doesn't
430
+ // retry in ~5s. Doubles per consecutive failure, capped at
431
+ // BACKOFF_MAX_MS; a manual POST /api/server/restart still bypasses it.
432
+ this.consecutiveFailures++;
433
+ const backoffMs = Math.min(BACKOFF_BASE_MS * 2 ** (this.consecutiveFailures - 1), BACKOFF_MAX_MS);
434
+ this.backoffUntil = Date.now() + backoffMs;
435
+ this.log(`reload: replacement failed to become ready${rebound === "bound" ? "" : ` — ${rebound} (server may be down)`}` +
436
+ ` — backing off ${Math.round(backoffMs / 1000)}s (${this.consecutiveFailures} consecutive failure${this.consecutiveFailures === 1 ? "" : "s"})`);
437
+ this.options.onReloadFailed?.(rebound === "bound"
438
+ ? "replacement did not become ready"
439
+ : `replacement failed and ${rebound}`);
440
+ }
441
+ this.child = null;
442
+ }
443
+ /**
444
+ * Poll /api/health for the replacement's handshake until it answers or the
445
+ * window closes. While the parent holds the port the replacement cannot bind
446
+ * (no SO_REUSEPORT sharing on this platform), so after a short grace we
447
+ * release our own listener — the drain window — and keep polling. Any answer
448
+ * from the parent itself has no handshake, so a spurious self-poll never
449
+ * confirms a replacement.
450
+ *
451
+ * A single handshake answer is NOT enough (#0096): the replacement must stay
452
+ * healthy for the confirm window so a briefly-up-then-dying replacement is
453
+ * treated as a failed reload — the parent re-binds and keeps serving rather
454
+ * than handing over to a process that will leave the port listenerless.
455
+ */
456
+ async waitForReplacement(secret) {
457
+ const deadline = Date.now() + (this.options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS);
458
+ const graceUntil = Date.now() + (this.options.graceMs ?? DEFAULT_GRACE_MS);
459
+ const confirmMs = this.options.confirmMs ?? DEFAULT_CONFIRM_MS;
460
+ const url = `http://${this.options.host}:${this.options.port}/api/health?reload=${secret}`;
461
+ let firstConfirmedAt = 0;
462
+ while (Date.now() < deadline) {
463
+ if (this.childExited)
464
+ return false;
465
+ const body = await probeJson(url);
466
+ if (body && body.reloadHandshake === true) {
467
+ if (firstConfirmedAt === 0)
468
+ firstConfirmedAt = Date.now();
469
+ if (Date.now() - firstConfirmedAt >= confirmMs)
470
+ return true;
471
+ }
472
+ if (!this.drained && (body === null || Date.now() >= graceUntil)) {
473
+ this.drained = true;
474
+ await this.options.stopListening();
475
+ }
476
+ await sleep(RELOAD_POLL_MS);
477
+ }
478
+ return false;
479
+ }
480
+ async killChild() {
481
+ const child = this.child;
482
+ if (!child || this.childExited)
483
+ return;
484
+ const pid = child.pid;
485
+ const killTree = (signal) => {
486
+ // A detached Unix child is the leader of a new process group. A negative
487
+ // PID signals the group, including any previews it created while trying
488
+ // to boot. Windows has no compatible process-group signal API, so retain
489
+ // Node's direct-child behaviour there.
490
+ if (process.platform !== "win32" && pid && pid > 0) {
491
+ try {
492
+ process.kill(-pid, signal);
493
+ return;
494
+ }
495
+ catch {
496
+ // The group may already be gone; direct signalling below preserves
497
+ // the existing best-effort cleanup semantics.
498
+ }
499
+ }
500
+ try {
501
+ child.kill(signal);
502
+ }
503
+ catch {
504
+ /* already gone */
505
+ }
506
+ };
507
+ try {
508
+ killTree("SIGTERM");
509
+ }
510
+ catch {
511
+ return; // already gone
512
+ }
513
+ await sleep(400);
514
+ try {
515
+ child.kill(0);
516
+ }
517
+ catch {
518
+ return; // exited on SIGTERM
519
+ }
520
+ try {
521
+ killTree("SIGKILL");
522
+ }
523
+ catch {
524
+ /* ignore */
525
+ }
526
+ }
527
+ /**
528
+ * Re-bind the listener after an aborted reload so the old process keeps
529
+ * serving. Returns `"bound"` when the listener is back, or a reason string
530
+ * describing why it could not be recovered.
531
+ *
532
+ * #0284: a bind attempt by an unrelated `repoos serve` landing in our drain
533
+ * window can steal the port out from under us — we released the listener in
534
+ * waitForReplacement, it grabbed it, and now we can never re-bind (each
535
+ * attempt EADDRINUSEs). Distinguishing that from a transient failure lets
536
+ * the old process surface a loud, actionable error instead of silently
537
+ * looping as a listenerless zombie. We probe the port once the retry window
538
+ * closes: if *something else* is listening there now, the port was stolen
539
+ * rather than merely not-yet-freed.
540
+ */
541
+ async tryRebind() {
542
+ if (!this.drained)
543
+ return "bound"; // we never released the listener
544
+ const deadline = Date.now() + (this.options.rebindTimeoutMs ?? REBIND_TIMEOUT_MS);
545
+ while (Date.now() < deadline) {
546
+ try {
547
+ await this.options.listen();
548
+ return "bound";
549
+ }
550
+ catch {
551
+ await sleep(200);
552
+ }
553
+ }
554
+ const probeHost = this.options.host === "0.0.0.0" ? "127.0.0.1" : this.options.host;
555
+ const stolen = await isPortListening(this.options.port, probeHost);
556
+ return stolen
557
+ ? `port ${this.options.port} was stolen by another process during the reload drain`
558
+ : `could not re-bind port ${this.options.port} within ${this.options.rebindTimeoutMs ?? REBIND_TIMEOUT_MS}ms`;
559
+ }
560
+ armRetry() {
561
+ if (this.retryTimer)
562
+ return;
563
+ this.retryTimer = setInterval(() => this.tryFirePending(), this.options.retryMs ?? DEFAULT_RETRY_MS);
564
+ this.retryTimer.unref?.();
565
+ }
566
+ clearRetry() {
567
+ if (this.retryTimer) {
568
+ clearInterval(this.retryTimer);
569
+ this.retryTimer = null;
570
+ }
571
+ }
572
+ clearTimers() {
573
+ if (this.pollTimer) {
574
+ clearInterval(this.pollTimer);
575
+ this.pollTimer = null;
576
+ }
577
+ if (this.debounceTimer) {
578
+ clearTimeout(this.debounceTimer);
579
+ this.debounceTimer = null;
580
+ }
581
+ this.clearRetry();
582
+ }
583
+ log(msg) {
584
+ this.options.log?.(msg);
585
+ }
586
+ }