@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,575 @@
1
+ /**
2
+ * Trusted finalization for a runner-issued handoff capability.
3
+ *
4
+ * Agents only edit their assigned worktree and emit a structured readiness
5
+ * signal. The runner binds that signal to its active turn and calls this code
6
+ * after the process exits successfully. No agent-controlled command or path is
7
+ * executed: task, branch and worktree are all checked against RepoOS state.
8
+ */
9
+ import { spawn } from "node:child_process";
10
+ import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+ import { runGit, worktreePathForBranch, currentBranch, branchChangesSinceBase, } from "../core/git.js";
13
+ import { parseTask } from "../core/task.js";
14
+ import { parseDocument, serializeDocument } from "../core/frontmatter.js";
15
+ import { resolveAgentForTask } from "./agents.js";
16
+ import { patchTaskFile } from "./write.js";
17
+ import { guardReviewTransition } from "./review-guard.js";
18
+ function run(cmd, args, cwd, timeoutMs, env, onChunk) {
19
+ return new Promise((finish) => {
20
+ const child = spawn(cmd, args, { cwd, env: env ?? process.env });
21
+ let stdout = "";
22
+ let stderr = "";
23
+ let settled = false;
24
+ let timer;
25
+ const done = (status, error) => {
26
+ if (settled)
27
+ return;
28
+ settled = true;
29
+ clearTimeout(timer);
30
+ finish({ status, stdout, stderr, error });
31
+ };
32
+ child.stdout.on("data", (data) => {
33
+ const text = data.toString("utf8");
34
+ stdout += text;
35
+ onChunk?.(text);
36
+ });
37
+ child.stderr.on("data", (data) => {
38
+ const text = data.toString("utf8");
39
+ stderr += text;
40
+ onChunk?.(text);
41
+ });
42
+ child.on("error", (error) => done(null, error));
43
+ child.on("close", (code) => done(code));
44
+ timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
45
+ });
46
+ }
47
+ function samePath(a, b) {
48
+ try {
49
+ return realpathSync(a) === realpathSync(b);
50
+ }
51
+ catch {
52
+ return resolve(a) === resolve(b);
53
+ }
54
+ }
55
+ /**
56
+ * Clears a stale `check_retry_count` once a handoff's `repoos check` step
57
+ * passes. The field exists only to cap `scheduleCheckFailureRetry`'s retries
58
+ * and to let the board show "fixing check failure" while a retry is in
59
+ * flight (0265) — left on the file after the check that mattered has passed,
60
+ * it would mislabel any later, unrelated resume of this task's engineer
61
+ * session as still fixing a check failure. Best-effort: never fails the
62
+ * handoff over a cosmetic field.
63
+ */
64
+ function clearCheckRetryCount(absPath) {
65
+ try {
66
+ const raw = readFileSync(absPath, "utf8");
67
+ const doc = parseDocument(raw);
68
+ if (doc.data.check_retry_count === undefined)
69
+ return;
70
+ delete doc.data.check_retry_count;
71
+ writeFileSync(absPath, serializeDocument(doc.data, `\n${doc.body}\n`, Object.keys(doc.data)));
72
+ }
73
+ catch (err) {
74
+ console.error(`[repoos] could not clear check_retry_count for ${absPath}: ${err.message}`);
75
+ }
76
+ }
77
+ function concise(run) {
78
+ if (run.error)
79
+ return run.error.message;
80
+ return ([run.stdout, run.stderr]
81
+ .join("\n")
82
+ .split("\n")
83
+ .map((line) => line.trim())
84
+ .filter(Boolean)
85
+ .slice(-8)
86
+ .join(" · ") || `exit ${run.status}`);
87
+ }
88
+ async function runCheck(worktree, config, onChunk) {
89
+ // Prefer the assigned worktree's compiled CLI. A globally linked `repoos`
90
+ // resolves build freshness relative to its own package checkout, which can
91
+ // falsely pass or fail when finalizing a different linked worktree.
92
+ const localCli = join(worktree, "dist", "cli", "index.js");
93
+ const candidates = existsSync(localCli)
94
+ ? [[process.execPath, localCli, "check"]]
95
+ : [
96
+ ["repoos", "check"],
97
+ ["bun", "run", "repoos", "check"],
98
+ ];
99
+ // Scope the test step to what this branch actually changed since its
100
+ // merge-base with main (see changedTestRef in commands/check.ts): this is
101
+ // re-verifying the SAME isolated branch the engineer already self-checked,
102
+ // not main's merged state, so narrowing coverage here is safe and cuts a
103
+ // ~10-minute full run down to seconds for a typical small task. Falls back
104
+ // to an unscoped (full) run when the merge-base can't be resolved.
105
+ const baseBranch = currentBranch(config.root) ?? "main";
106
+ const { base } = branchChangesSinceBase(worktree, baseBranch);
107
+ const env = base ? { ...process.env, REPOOS_CHECK_CHANGED: base } : process.env;
108
+ let last = { status: null, stdout: "", stderr: "check command unavailable" };
109
+ for (const candidate of candidates) {
110
+ last = await run(candidate[0], [...candidate.slice(1)], worktree, 240_000, env, onChunk);
111
+ if (last.status === 0)
112
+ return last;
113
+ if (!last.error || (last.error.code !== "ENOENT" && last.error.code !== "EACCES"))
114
+ return last;
115
+ }
116
+ return last;
117
+ }
118
+ /**
119
+ * Finalize one capability issued by an active runner turn.
120
+ *
121
+ * Enforces a hard deadline (600s) on the entire finalization process. If any
122
+ * step (validate, check, commit, review, main) exceeds the deadline, the
123
+ * finalization is abandoned and returned as failed. This ensures the handoff
124
+ * promise never hangs indefinitely.
125
+ */
126
+ export async function handoffTask(config, task, request, onProgress, onStatusChange,
127
+ /** Records this finalization's `repoos check` run for the Debug tab (0310). */
128
+ taskChecks, onTaskCheckEvent) {
129
+ const HANDOFF_DEADLINE_MS = 600_000; // 10 minutes hard deadline
130
+ let settled = false;
131
+ // Race the entire handoff against a hard deadline. If the deadline fires,
132
+ // return a failure result immediately without awaiting inner cleanup.
133
+ const timeoutPromise = new Promise((resolve) => {
134
+ setTimeout(() => {
135
+ if (!settled) {
136
+ settled = true;
137
+ resolve({
138
+ ok: false,
139
+ step: "check",
140
+ detail: "server-side finalization timed out (deadline exceeded)",
141
+ });
142
+ }
143
+ }, HANDOFF_DEADLINE_MS);
144
+ });
145
+ const resultPromise = (async () => {
146
+ try {
147
+ onProgress?.("validate");
148
+ if (!request.runId || request.taskId !== task.id) {
149
+ return {
150
+ ok: false,
151
+ step: "validate",
152
+ detail: "handoff does not match the active runner session",
153
+ };
154
+ }
155
+ if (task.status !== "active" && task.status !== "review") {
156
+ return {
157
+ ok: false,
158
+ step: "validate",
159
+ detail: `task must be active or review, but is ${task.status}`,
160
+ };
161
+ }
162
+ if (!task.branch || request.branch !== task.branch) {
163
+ return {
164
+ ok: false,
165
+ step: "validate",
166
+ detail: "handoff branch does not match the task branch",
167
+ };
168
+ }
169
+ const isHotfix = task.hotfix === true;
170
+ const registered = worktreePathForBranch(config.root, task.branch);
171
+ const registeredIsRoot = registered ? samePath(registered, config.root) : false;
172
+ if (isHotfix) {
173
+ if (!registeredIsRoot || !samePath(config.root, request.workdir)) {
174
+ return {
175
+ ok: false,
176
+ step: "validate",
177
+ detail: "hotfix handoff must run in the main checkout",
178
+ };
179
+ }
180
+ }
181
+ else {
182
+ if (!registered || !samePath(registered, request.workdir) || !existsSync(registered)) {
183
+ return {
184
+ ok: false,
185
+ step: "validate",
186
+ detail: "handoff worktree does not match the registered task worktree",
187
+ };
188
+ }
189
+ }
190
+ // After validation, `registered` is non-null for both paths — hotfix
191
+ // requires registeredIsRoot, non-hotfix requires `registered` truthy.
192
+ const workdir = isHotfix ? config.root : registered;
193
+ const branch = await runGit(workdir, ["branch", "--show-current"], 10_000);
194
+ if (branch.status !== 0 || branch.stdout.trim() !== task.branch) {
195
+ return {
196
+ ok: false,
197
+ step: "validate",
198
+ detail: "registered worktree is not on the expected branch",
199
+ };
200
+ }
201
+ const worktreeTaskPath = join(workdir, task.path);
202
+ if (!existsSync(worktreeTaskPath)) {
203
+ return {
204
+ ok: false,
205
+ step: "validate",
206
+ detail: "task file is missing from the registered worktree",
207
+ };
208
+ }
209
+ let worktreeTask;
210
+ try {
211
+ worktreeTask = parseTask({
212
+ content: readFileSync(worktreeTaskPath, "utf8"),
213
+ absPath: worktreeTaskPath,
214
+ root: workdir,
215
+ defaultStatus: config.defaultStatus,
216
+ defaultAssignee: config.defaultAssignee,
217
+ });
218
+ }
219
+ catch (error) {
220
+ return {
221
+ ok: false,
222
+ step: "validate",
223
+ detail: `could not parse the worktree task: ${error.message}`,
224
+ };
225
+ }
226
+ if (worktreeTask.id !== task.id ||
227
+ (worktreeTask.branch && worktreeTask.branch !== task.branch)) {
228
+ return {
229
+ ok: false,
230
+ step: "validate",
231
+ detail: "worktree task identity does not match the active task",
232
+ };
233
+ }
234
+ if (task.status === "review" && worktreeTask.status === "review") {
235
+ return { ok: true, step: "done", detail: "handoff was already finalized" };
236
+ }
237
+ onProgress?.("check");
238
+ const checkHandle = taskChecks && onTaskCheckEvent
239
+ ? taskChecks.start(task.id, "handoff-finalize", onTaskCheckEvent)
240
+ : undefined;
241
+ const check = await runCheck(workdir, config, checkHandle?.chunk);
242
+ checkHandle?.done(check.status);
243
+ if (check.status !== 0) {
244
+ return { ok: false, step: "check", detail: `repoos check failed: ${concise(check)}` };
245
+ }
246
+ clearCheckRetryCount(task.absPath);
247
+ onProgress?.("commit");
248
+ const gate = await guardReviewTransition(config, worktreeTask);
249
+ if (!gate.ok) {
250
+ return { ok: false, step: "commit", detail: gate.detail };
251
+ }
252
+ onProgress?.("review");
253
+ if (worktreeTask.status !== "review" || worktreeTask.branch !== task.branch) {
254
+ try {
255
+ patchTaskFile({ ...config, root: workdir }, worktreeTaskPath, {
256
+ status: "review",
257
+ branch: task.branch,
258
+ });
259
+ }
260
+ catch (error) {
261
+ return {
262
+ ok: false,
263
+ step: "review",
264
+ detail: `could not update the worktree task: ${error.message}`,
265
+ };
266
+ }
267
+ }
268
+ onProgress?.("main");
269
+ if (task.status !== "review") {
270
+ try {
271
+ // A branch-mode hotfix runs in the root checkout, so task.absPath
272
+ // points at the hotfix branch's copy. Before the checkout returns to
273
+ // main (for example during a server reload), write the same metadata
274
+ // to main's canonical board copy as well. Otherwise the next server
275
+ // reads the pre-hotfix task and makes completed work look ready again.
276
+ if (isHotfix && task.hotfixTarget !== "main") {
277
+ const onHotfixBranch = await runGit(config.root, ["branch", "--show-current"], 10_000);
278
+ if (onHotfixBranch.status !== 0 || onHotfixBranch.stdout.trim() !== task.branch) {
279
+ return {
280
+ ok: false,
281
+ step: "main",
282
+ detail: "hotfix checkout changed before canonical task sync",
283
+ };
284
+ }
285
+ const checkoutMain = await runGit(config.root, ["checkout", "main"], 20_000);
286
+ if (checkoutMain.status !== 0) {
287
+ return {
288
+ ok: false,
289
+ step: "main",
290
+ detail: `could not check out main for task sync: ${concise(checkoutMain)}`,
291
+ };
292
+ }
293
+ try {
294
+ patchTaskFile(config, join(config.root, task.path), {
295
+ status: "review",
296
+ branch: task.branch,
297
+ hotfix: true,
298
+ hotfixTarget: task.hotfixTarget,
299
+ }, {
300
+ onStatusChange: onStatusChange
301
+ ? (updated, prev, next) => onStatusChange(updated, prev, next)
302
+ : undefined,
303
+ });
304
+ }
305
+ finally {
306
+ const restore = await runGit(config.root, ["checkout", task.branch], 20_000);
307
+ if (restore.status !== 0) {
308
+ return {
309
+ ok: false,
310
+ step: "main",
311
+ detail: `canonical task synced but could not restore hotfix checkout: ${concise(restore)}`,
312
+ };
313
+ }
314
+ }
315
+ }
316
+ else {
317
+ patchTaskFile(config, task.absPath, { status: "review" }, {
318
+ onStatusChange: onStatusChange
319
+ ? (updated, prev, next) => onStatusChange(updated, prev, next)
320
+ : undefined,
321
+ });
322
+ }
323
+ }
324
+ catch (error) {
325
+ return {
326
+ ok: false,
327
+ step: "main",
328
+ detail: `could not update the canonical task: ${error.message}`,
329
+ };
330
+ }
331
+ }
332
+ onProgress?.("done");
333
+ settled = true;
334
+ return { ok: true, step: "done" };
335
+ }
336
+ catch (err) {
337
+ settled = true;
338
+ const message = err instanceof Error ? err.message : String(err);
339
+ return { ok: false, step: "validate", detail: `unexpected error: ${message}` };
340
+ }
341
+ })();
342
+ return Promise.race([resultPromise, timeoutPromise]);
343
+ }
344
+ /** Automatic retries allowed for a `check`-step finalization failure before
345
+ * the task is left for the task watchdog / a human (mirrors review.ts's
346
+ * MAX_AUTO_REVIEW_ROUNDS bound on auto-bounce). */
347
+ const MAX_CHECK_RETRY_ATTEMPTS = 2;
348
+ /** Resume is deferred this long past the failed handoff so AgentRunner has
349
+ * cleared the task from `handoffsInFlight` — `send()` rejects while set. */
350
+ const CHECK_RETRY_DELAY_MS = 3_000;
351
+ /**
352
+ * On a finalization failure at the `check` step, automatically resume the
353
+ * same engineer session with the check output and ask it to fix (or re-verify,
354
+ * if the failure looks transient) and re-hand-off — up to
355
+ * `MAX_CHECK_RETRY_ATTEMPTS` times. Every other failure step (validate,
356
+ * commit, review, main) is structural/environmental, not something a plain
357
+ * retry of the same session fixes, so only `check` is retried.
358
+ *
359
+ * Returns true when a retry was scheduled. On give-up (cap reached, or no
360
+ * engineer configured) the failure reason is persisted to the task's Activity
361
+ * log via `AgentRunner.persistHandoffFailure` so the task watchdog classifies
362
+ * it correctly instead of falling back to its generic
363
+ * "exited without handoff" guess.
364
+ */
365
+ export function scheduleCheckFailureRetry(config, task, result, runner,
366
+ /** Called right after `check_retry_count` is persisted so the live index
367
+ * (and its SSE `task.updated` event) picks up the new count immediately —
368
+ * this write bypasses `patchTaskFile`, so nothing else refreshes it. */
369
+ onFileChange) {
370
+ if (result.step !== "check")
371
+ return false;
372
+ let retries = task.extra?.check_retry_count;
373
+ if (typeof retries !== "number")
374
+ retries = 0;
375
+ const detail = result.detail ?? "repoos check failed";
376
+ if (retries >= MAX_CHECK_RETRY_ATTEMPTS) {
377
+ runner.persistHandoffFailure(task.id, task, `check failed after ${MAX_CHECK_RETRY_ATTEMPTS} automatic retries · ${detail}`);
378
+ return false;
379
+ }
380
+ const engineer = resolveAgentForTask(config, task);
381
+ if (!engineer) {
382
+ runner.persistHandoffFailure(task.id, task, `check failed and no engineer is configured to retry · ${detail}`);
383
+ return false;
384
+ }
385
+ const attempt = retries + 1;
386
+ setTimeout(() => {
387
+ const message = [
388
+ `Server finalization's \`repoos check\` failed after your handoff (automatic retry ${attempt} of ${MAX_CHECK_RETRY_ATTEMPTS}):`,
389
+ "",
390
+ detail,
391
+ "",
392
+ "Fix the failure — or, if it looks like an unrelated flaky/timing test, just re-run and re-verify — then re-emit the handoff signal once `repoos check` passes.",
393
+ ].join("\n");
394
+ const sent = runner.send(task.id, message, engineer, { skipBoardDivergence: true });
395
+ if (!sent.ok) {
396
+ runner.system(task.id, `✗ automatic check-failure retry could not resume: ${sent.reason ?? "unknown error"}`);
397
+ runner.persistHandoffFailure(task.id, task, `could not auto-retry after check failure · ${sent.reason ?? "unknown error"}`);
398
+ return;
399
+ }
400
+ try {
401
+ const raw = readFileSync(task.absPath, "utf8");
402
+ const doc = parseDocument(raw);
403
+ doc.data.check_retry_count = attempt;
404
+ // Persist parseable handoff-check failure details
405
+ doc.data.last_check_failure = {
406
+ stage: "check",
407
+ command: "repoos check",
408
+ exitCode: result.detail?.match(/exit (\d+)/)?.[1] || null,
409
+ detail: detail,
410
+ timestamp: new Date().toISOString(),
411
+ };
412
+ const keys = Object.keys(doc.data).filter((k) => k !== "check_retry_count" && k !== "last_check_failure");
413
+ keys.unshift("check_retry_count", "last_check_failure");
414
+ writeFileSync(task.absPath, serializeDocument(doc.data, `\n${doc.body}\n`, keys));
415
+ onFileChange?.(task.absPath);
416
+ }
417
+ catch (err) {
418
+ console.error(`[repoos] could not persist check_retry_count for #${task.id}: ${err.message}`);
419
+ }
420
+ runner.system(task.id, `↻ automatically resuming after check failure (attempt ${attempt} of ${MAX_CHECK_RETRY_ATTEMPTS})`);
421
+ }, CHECK_RETRY_DELAY_MS);
422
+ return true;
423
+ }
424
+ /** Cap mirrors MAX_CHECK_RETRY_ATTEMPTS — same reasoning, different failure step. */
425
+ const MAX_MERGE_CONFLICT_RETRY_ATTEMPTS = 2;
426
+ const MERGE_CONFLICT_RETRY_DELAY_MS = 3_000;
427
+ /**
428
+ * On a close-out `validating`-phase failure caused by a REAL merge conflict
429
+ * (the candidate's merge of the feature branch into itself failed with named
430
+ * conflicting paths — not the task's own bookkeeping file or `dist/`/
431
+ * `screenshots/`, which auto-resolve, and not an infra failure), automatically
432
+ * resume the task's engineer session and ask it to merge main into ITS OWN
433
+ * branch and resolve the conflict there — the exact manual recovery
434
+ * docs/close-out-pipeline.md prescribes (#0271 follow-up: this was
435
+ * previously always a human/agent-operator manual step, discovered when
436
+ * task #0282 sat failed until someone noticed).
437
+ *
438
+ * Unlike `scheduleCheckFailureRetry`, this does NOT ask the engineer to
439
+ * re-emit the handoff signal — the task is already `review`, and a fresh
440
+ * handoff attempt against an already-`review` task just short-circuits as
441
+ * "already finalized" (see the check near the top of `handoffTask`) without
442
+ * re-running the close-out. Instead, server.ts's `agent.exited` handler
443
+ * watches for this exact session ending while its job is still the
444
+ * failed-on-this-conflict record, and re-enqueues the close-out itself —
445
+ * the same action a human clicking "Move to done" again performs. The
446
+ * engineer's job is just to fix the branch and end its turn.
447
+ *
448
+ * Otherwise mirrors `scheduleCheckFailureRetry` closely: same retry cap
449
+ * shape, same give-up-to-persistHandoffFailure fallback, same
450
+ * do-not-flip-status behavior (the task stays in `review` — TaskCard.vue can
451
+ * label this state the same way it labels a check-failure retry). Capped,
452
+ * because a conflict that survives two resolve attempts is very likely the
453
+ * agent resolving it WRONG in a way that reintroduces the same conflict, not
454
+ * something a third attempt fixes — same logic `MAX_CHECK_RETRY_ATTEMPTS`
455
+ * already encodes.
456
+ *
457
+ * Returns true when a retry was scheduled.
458
+ */
459
+ export function scheduleMergeConflictRetry(config, task, reason, runner,
460
+ /** Same purpose as in `scheduleCheckFailureRetry` — let the live index pick
461
+ * up the new retry count immediately via its own SSE event. */
462
+ onFileChange) {
463
+ let retries = task.extra?.merge_conflict_retry_count;
464
+ if (typeof retries !== "number")
465
+ retries = 0;
466
+ if (retries >= MAX_MERGE_CONFLICT_RETRY_ATTEMPTS) {
467
+ runner.persistHandoffFailure(task.id, task, `merge conflict unresolved after ${MAX_MERGE_CONFLICT_RETRY_ATTEMPTS} automatic retries · ${reason}`);
468
+ return false;
469
+ }
470
+ const engineer = resolveAgentForTask(config, task);
471
+ if (!engineer) {
472
+ runner.persistHandoffFailure(task.id, task, `merge conflict and no engineer is configured to retry · ${reason}`);
473
+ return false;
474
+ }
475
+ const attempt = retries + 1;
476
+ setTimeout(() => {
477
+ const message = [
478
+ `Close-out validation failed (automatic retry ${attempt} of ${MAX_MERGE_CONFLICT_RETRY_ATTEMPTS}): your branch has a real merge conflict with main.`,
479
+ "",
480
+ reason,
481
+ "",
482
+ 'In YOUR OWN branch\'s worktree (not the candidate — the candidate is discarded and rebuilt from your branch on every attempt): merge main into your branch, resolve the conflict by understanding what BOTH sides were trying to do — do not blindly prefer one side over the other unless one is genuinely obsolete — verify `repoos check` passes, and commit the merge. The task is already in `review`, so do NOT re-emit the handoff signal (it will just report "already finalized" and do nothing) — simply end your turn once the merge is committed and verified. The close-out retries automatically the moment your turn ends.',
483
+ ].join("\n");
484
+ const sent = runner.send(task.id, message, engineer, { skipBoardDivergence: true });
485
+ if (!sent.ok) {
486
+ runner.system(task.id, `✗ automatic merge-conflict retry could not resume: ${sent.reason ?? "unknown error"}`);
487
+ runner.persistHandoffFailure(task.id, task, `could not auto-retry after merge conflict · ${sent.reason ?? "unknown error"}`);
488
+ return;
489
+ }
490
+ try {
491
+ const raw = readFileSync(task.absPath, "utf8");
492
+ const doc = parseDocument(raw);
493
+ doc.data.merge_conflict_retry_count = attempt;
494
+ const keys = Object.keys(doc.data).filter((k) => k !== "merge_conflict_retry_count");
495
+ keys.unshift("merge_conflict_retry_count");
496
+ writeFileSync(task.absPath, serializeDocument(doc.data, `\n${doc.body}\n`, keys));
497
+ onFileChange?.(task.absPath);
498
+ }
499
+ catch (err) {
500
+ console.error(`[repoos] could not persist merge_conflict_retry_count for #${task.id}: ${err.message}`);
501
+ }
502
+ runner.system(task.id, `↻ automatically resuming after merge conflict (attempt ${attempt} of ${MAX_MERGE_CONFLICT_RETRY_ATTEMPTS})`);
503
+ }, MERGE_CONFLICT_RETRY_DELAY_MS);
504
+ return true;
505
+ }
506
+ /** Cap mirrors the other two retry schedulers — same reasoning, different failure step. */
507
+ const MAX_HANDOFF_SIGNAL_RETRY_ATTEMPTS = 2;
508
+ const HANDOFF_SIGNAL_RETRY_DELAY_MS = 3_000;
509
+ /**
510
+ * Third instance of the same pattern (#0271 follow-up), for the task-watchdog's
511
+ * `exited-without-handoff` classification (task-watchdog.ts): a session ran to
512
+ * completion but its final line wasn't exactly `::repoos-handoff-ready::`, so no
513
+ * handoff ever fired. Previously the watchdog could only surface this — move
514
+ * the task to `review`/`ready` and leave a human to notice and click Restart
515
+ * (task #0268 sat this way until someone asked "why hasn't this recovered on
516
+ * its own").
517
+ *
518
+ * Unlike the other two, this failure has no specific diagnostic to hand back —
519
+ * "you didn't finish right" is vaguer than a named conflict or check output —
520
+ * so it's a strictly weaker signal, which is exactly why the watchdog didn't
521
+ * already do this: auto-resuming on a vague prompt risks looping on genuine
522
+ * confusion, not just fixing a rendering hiccup. The cap bounds that risk the
523
+ * same way it bounds the other two; on exhaustion this returns false and the
524
+ * caller (task-watchdog.ts) falls through to its EXISTING surface behavior —
525
+ * there is no separate give-up path here, unlike the other two schedulers,
526
+ * because surfacing already IS the correct terminal state the watchdog was
527
+ * built for.
528
+ *
529
+ * Returns true when a retry was scheduled; false means "give up, surface it
530
+ * the normal way" — either the cap was hit or no engineer is configured.
531
+ */
532
+ export function scheduleHandoffSignalRetry(config, task, runner, onFileChange) {
533
+ let retries = task.extra?.handoff_signal_retry_count;
534
+ if (typeof retries !== "number")
535
+ retries = 0;
536
+ if (retries >= MAX_HANDOFF_SIGNAL_RETRY_ATTEMPTS)
537
+ return false;
538
+ const engineer = resolveAgentForTask(config, task);
539
+ if (!engineer)
540
+ return false;
541
+ const persistAttempt = () => {
542
+ // Persisted on EVERY attempt, success or failure of `send()` — otherwise a
543
+ // structural send failure (e.g. the runner reports busy every time) would
544
+ // never advance the counter and this could retry forever every watchdog
545
+ // scan instead of respecting the cap.
546
+ try {
547
+ const raw = readFileSync(task.absPath, "utf8");
548
+ const doc = parseDocument(raw);
549
+ doc.data.handoff_signal_retry_count = attempt;
550
+ const keys = Object.keys(doc.data).filter((k) => k !== "handoff_signal_retry_count");
551
+ keys.unshift("handoff_signal_retry_count");
552
+ writeFileSync(task.absPath, serializeDocument(doc.data, `\n${doc.body}\n`, keys));
553
+ onFileChange?.(task.absPath);
554
+ }
555
+ catch (err) {
556
+ console.error(`[repoos] could not persist handoff_signal_retry_count for #${task.id}: ${err.message}`);
557
+ }
558
+ };
559
+ const attempt = retries + 1;
560
+ setTimeout(() => {
561
+ const message = [
562
+ `Automatic recovery (attempt ${attempt} of ${MAX_HANDOFF_SIGNAL_RETRY_ATTEMPTS}): your previous turn on this task ended without the server detecting a clean handoff.`,
563
+ "",
564
+ "The handoff signal must be exactly `::repoos-handoff-ready::` on its own line — a rendering quirk can occasionally mangle it (see #0154/#0155).",
565
+ "",
566
+ "Check your last output and the current state of your worktree. If the work is actually complete and `repoos check` passes: emit the signal line correctly this time. If it is NOT complete: finish it, verify `repoos check` passes, then emit the signal.",
567
+ ].join("\n");
568
+ const sent = runner.send(task.id, message, engineer, { skipBoardDivergence: true });
569
+ persistAttempt();
570
+ if (!sent.ok)
571
+ return; // capped; the watchdog's next scan surfaces it normally
572
+ runner.system(task.id, `↻ automatically resuming after a missed handoff signal (attempt ${attempt} of ${MAX_HANDOFF_SIGNAL_RETRY_ATTEMPTS})`);
573
+ }, HANDOFF_SIGNAL_RETRY_DELAY_MS);
574
+ return true;
575
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Minimal Hetzner Cloud API client for the Remote Validation Runner (#RVR).
3
+ *
4
+ * Only the four calls the runner needs: create a server from a snapshot, poll
5
+ * its status, delete it, and list servers by label (leak reconciliation). Uses
6
+ * the platform's native `fetch` — no runtime dependency, same as ntfy.ts.
7
+ *
8
+ * The API token comes from `HETZNER_API_TOKEN` (env only, never a git-tracked
9
+ * config key). Every method throws `HetznerApiError` on a non-2xx response so
10
+ * the runner can classify the failure as transient infra trouble.
11
+ */
12
+ export declare class HetznerApiError extends Error {
13
+ readonly status?: number | undefined;
14
+ constructor(message: string, status?: number | undefined);
15
+ }
16
+ export interface HetznerServer {
17
+ id: number;
18
+ name: string;
19
+ /** "initializing" | "starting" | "running" | "stopping" | "off" | "deleting" | "rebuilding" | "migrating" | "unknown" */
20
+ status: string;
21
+ /** Public IPv4 address, or null before it is assigned. */
22
+ ipv4: string | null;
23
+ /** ISO 8601 creation timestamp. */
24
+ created: string;
25
+ labels: Record<string, string>;
26
+ }
27
+ export interface CreateServerOptions {
28
+ name: string;
29
+ serverType: string;
30
+ location: string;
31
+ /** Snapshot ID (number) or image name. */
32
+ image: string;
33
+ /** Names of SSH keys already registered in the Hetzner project. */
34
+ sshKeyNames: string[];
35
+ labels?: Record<string, string>;
36
+ /** cloud-init user data, if the snapshot needs any first-boot setup. */
37
+ userData?: string;
38
+ }
39
+ export interface HetznerClient {
40
+ createServer(opts: CreateServerOptions): Promise<HetznerServer>;
41
+ getServer(id: number): Promise<HetznerServer | null>;
42
+ deleteServer(id: number): Promise<void>;
43
+ listServers(labelSelector: string): Promise<HetznerServer[]>;
44
+ }
45
+ export declare function createHetznerClient(token: string): HetznerClient;