specrails-desktop 2.57.0 → 2.58.0

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 (336) hide show
  1. package/README.md +1 -1
  2. package/cli/dist/args.js +10 -4
  3. package/cli/dist/help.js +1 -1
  4. package/client/dist/assets/{ActivityFeedPage-DdMNmVBk.js → ActivityFeedPage-BQGppIR0.js} +1 -1
  5. package/client/dist/assets/{AgentBrowserCapture-CeerUf--.js → AgentBrowserCapture-PI-cxyxi.js} +1 -1
  6. package/client/dist/assets/{AgentModeAnalyticsPane-BjGb-8rh.js → AgentModeAnalyticsPane-mQsOqdXx.js} +2 -2
  7. package/client/dist/assets/{AgentModeCodePane-8XRkQEo_.js → AgentModeCodePane-BL-vpmlY.js} +2 -2
  8. package/client/dist/assets/{AgentModeJobsPane-BN3dgJOA.js → AgentModeJobsPane-DR_06oqH.js} +1 -1
  9. package/client/dist/assets/AgentsPage-BKIKjm_1.js +118 -0
  10. package/client/dist/assets/{AnalyticsPage-CyfhYiBn.js → AnalyticsPage-069npRVo.js} +1 -1
  11. package/client/dist/assets/{CodePage-C2Nf-Q5W.js → CodePage-Alitorrv.js} +2 -2
  12. package/client/dist/assets/DesktopAnalyticsPage-qtWAPM47.js +1 -0
  13. package/client/dist/assets/{DocsDialog-Br3bczCp.js → DocsDialog-DwL6rgcT.js} +1 -1
  14. package/client/dist/assets/{DocsPage-BcDr2cBm.js → DocsPage-BA1hiJZ-.js} +1 -1
  15. package/client/dist/assets/{ExportDropdown-BVGKEgkd.js → ExportDropdown-BcMpvV89.js} +1 -1
  16. package/client/dist/assets/InteractiveJobComposer-B3H1YHr6.js +19 -0
  17. package/client/dist/assets/JobDetailModal-XZQ9eAAy.js +1 -0
  18. package/client/dist/assets/{JobDetailPage-vBbNeRuz.js → JobDetailPage-B-SIaq_q.js} +1 -1
  19. package/client/dist/assets/{JobsPage-CJCtPhIz.js → JobsPage-iQXAv2mx.js} +1 -1
  20. package/client/dist/assets/LoopBuilderPage-B_gbFAHn.js +1 -0
  21. package/client/dist/assets/LoopPreviewModal-je5pSCf-.js +1 -0
  22. package/client/dist/assets/LoopsPage-BZwcY5cu.js +2 -0
  23. package/client/dist/assets/{MinimizedChatsContext-D3QzgV0B.js → MinimizedChatsContext-CNx_0bZu.js} +1 -1
  24. package/client/dist/assets/{PluginsPage-DOpTV75p.js → PluginsPage-DFocnoHX.js} +2 -2
  25. package/client/dist/assets/{ProjectSettingsDialog-Dkwrn0z1.js → ProjectSettingsDialog-Bh3S6EBB.js} +1 -1
  26. package/client/dist/assets/{RepositoryDeliveries-Du2UCf1a.js → RepositoryDeliveries-BiwJuS8r.js} +1 -1
  27. package/client/dist/assets/{RepositoryScopeSelector-hPGZNRBm.js → RepositoryScopeSelector-C2mvWBQ6.js} +1 -1
  28. package/client/dist/assets/{ReviewPacketPage-DcOq-UTA.js → ReviewPacketPage-CrAf-WDy.js} +2 -2
  29. package/client/dist/assets/TemplatePreviewModal-C7DordpN.js +1 -0
  30. package/client/dist/assets/{TicketDetailModalContext-DAK-NoK6.js → TicketDetailModalContext-B-DySpKL.js} +1 -1
  31. package/client/dist/assets/{Trans-DEbgkuAY.js → Trans-D3AfDylf.js} +1 -1
  32. package/client/dist/assets/agentRuntime-BeUb3g9g.js +1 -0
  33. package/client/dist/assets/agentRuntime-Bg9tbmqm.js +1 -0
  34. package/client/dist/assets/agentRuntime-D-GJVGkj.js +1 -0
  35. package/client/dist/assets/agentRuntime-D2__CVI_.js +1 -0
  36. package/client/dist/assets/agentRuntime-D35dGVOP.js +1 -0
  37. package/client/dist/assets/agentRuntime-DGz_IqKW.js +1 -0
  38. package/client/dist/assets/agentRuntime-O1liDt6G.js +1 -0
  39. package/client/dist/assets/agentRuntime-hInH9Fzj.js +1 -0
  40. package/client/dist/assets/agentstudio-B2CTtQgZ.js +1 -0
  41. package/client/dist/assets/agentstudio-BWAKeGgB.js +1 -0
  42. package/client/dist/assets/{agentstudio-DR7doNBV.js → agentstudio-Cja8Sjcp.js} +1 -1
  43. package/client/dist/assets/agentstudio-DtfPcr99.js +1 -0
  44. package/client/dist/assets/agentstudio-QdAJIDef.js +1 -0
  45. package/client/dist/assets/agentstudio-X70BKf6u.js +1 -0
  46. package/client/dist/assets/agentstudio-fLXpRGIi.js +1 -0
  47. package/client/dist/assets/agentstudio-qXKqVnWS.js +1 -0
  48. package/client/dist/assets/{analytics-jzCkOec3.js → analytics-B1DIQd4m.js} +1 -1
  49. package/client/dist/assets/{analytics-CoEzWPx4.js → analytics-BAIZ9vE_.js} +1 -1
  50. package/client/dist/assets/{analytics-DjLlE7jS.js → analytics-BTqgmgBd.js} +1 -1
  51. package/client/dist/assets/analytics-BWynXPEm.js +1 -0
  52. package/client/dist/assets/{analytics-nZIjjtCc.js → analytics-DGcUsQ44.js} +1 -1
  53. package/client/dist/assets/{analytics-BNgZ1Nj7.js → analytics-GnSnpJ4W.js} +1 -1
  54. package/client/dist/assets/{analytics-DFh5lSOl.js → analytics-TWgYaoO0.js} +1 -1
  55. package/client/dist/assets/{analytics-DZp-DS2U.js → analytics-kJy6Tuko.js} +1 -1
  56. package/client/dist/assets/builder-C0FyvM83.js +1 -0
  57. package/client/dist/assets/builder-C1neIYGR.js +1 -0
  58. package/client/dist/assets/builder-CvnFm0im.js +1 -0
  59. package/client/dist/assets/builder-CzW5ovyI.js +1 -0
  60. package/client/dist/assets/builder-D06L7iPy.js +1 -0
  61. package/client/dist/assets/builder-DGETbtZj.js +1 -0
  62. package/client/dist/assets/builder-DXEJUlC0.js +1 -0
  63. package/client/dist/assets/builder-DyQperXU.js +1 -0
  64. package/client/dist/assets/common-BCK7c9Pv.js +1 -0
  65. package/client/dist/assets/common-C0BOdeNi.js +1 -0
  66. package/client/dist/assets/common-C4Yvvhp6.js +1 -0
  67. package/client/dist/assets/{common-BrlwEnL7.js → common-DmsclwjP.js} +1 -1
  68. package/client/dist/assets/common-DpLWwgI1.js +1 -0
  69. package/client/dist/assets/common-OCvl3c8t.js +1 -0
  70. package/client/dist/assets/common-c0aujEZC.js +1 -0
  71. package/client/dist/assets/common-od8WThlf.js +1 -0
  72. package/client/dist/assets/dashboard-B1ndRHuC.js +1 -0
  73. package/client/dist/assets/dashboard-BX7KXiqA.js +1 -0
  74. package/client/dist/assets/dashboard-B_BSNxcN.js +1 -0
  75. package/client/dist/assets/dashboard-CCeFN7-V.js +1 -0
  76. package/client/dist/assets/dashboard-CRl0WtFJ.js +1 -0
  77. package/client/dist/assets/dashboard-XGdQ5mjw.js +1 -0
  78. package/client/dist/assets/dashboard-_oltA6Jd.js +1 -0
  79. package/client/dist/assets/dashboard-bAuf5KVk.js +1 -0
  80. package/client/dist/assets/{formatDistanceToNow-Cc2u5FwQ.js → formatDistanceToNow-aBss1Nai.js} +1 -1
  81. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BHm2U-w1.js → getTimezoneOffsetInMilliseconds-DOGj1Mfz.js} +1 -1
  82. package/client/dist/assets/index-BS7KCWkV.css +2 -0
  83. package/client/dist/assets/{index-Cxb288Rj.js → index-C4Mv6tV6.js} +32 -32
  84. package/client/dist/assets/{jira-api-DrGxiD9p.js → jira-api-BYbEPlkr.js} +1 -1
  85. package/client/dist/assets/{jobs-DSp9DoD_.js → jobs-6_-r75yC.js} +1 -1
  86. package/client/dist/assets/{jobs-C6qmvUAi.js → jobs-B1yqyId5.js} +1 -1
  87. package/client/dist/assets/{jobs-DoFaEl9g.js → jobs-BFuV7ttH.js} +1 -1
  88. package/client/dist/assets/{jobs-DTQO08nJ.js → jobs-BKa8LuwA.js} +1 -1
  89. package/client/dist/assets/{jobs-DrF8Do8s.js → jobs-BKiK-ZWh.js} +1 -1
  90. package/client/dist/assets/{jobs-DLHdDkHc.js → jobs-DmKV7b7d.js} +1 -1
  91. package/client/dist/assets/{jobs-C9j2PuwW.js → jobs-KG4Sn12X.js} +1 -1
  92. package/client/dist/assets/{jobs-Dpw6AEOP.js → jobs-pPv4KWjb.js} +1 -1
  93. package/client/dist/assets/loop-layout-BF8xMiNv.js +7 -0
  94. package/client/dist/assets/loops-B4_D-nbg.js +1 -0
  95. package/client/dist/assets/loops-BJDY9FTh.js +1 -0
  96. package/client/dist/assets/loops-BO3V8244.js +1 -0
  97. package/client/dist/assets/loops-C-hZK8pf.js +1 -0
  98. package/client/dist/assets/loops-CDOWZ0ci.js +1 -0
  99. package/client/dist/assets/loops-Dv849ckF.js +1 -0
  100. package/client/dist/assets/loops-Q9LIgGkq.js +1 -0
  101. package/client/dist/assets/loops-eUnCfLjC.js +1 -0
  102. package/client/dist/assets/{project-repositories-9Wzs0coQ.js → project-repositories-CSnfzUpg.js} +1 -1
  103. package/client/dist/assets/settings-3o4Ntzi5.js +1 -0
  104. package/client/dist/assets/settings-B97jlkwD.js +1 -0
  105. package/client/dist/assets/settings-BTaT_bsS.js +1 -0
  106. package/client/dist/assets/settings-CT8w7WiC.js +1 -0
  107. package/client/dist/assets/settings-Cvr8_tvh.js +1 -0
  108. package/client/dist/assets/settings-Dn1qgAC8.js +1 -0
  109. package/client/dist/assets/settings-EShDdg4J.js +1 -0
  110. package/client/dist/assets/settings-tfLhQZRN.js +1 -0
  111. package/client/dist/assets/{setup-Dwzt5nO1.js → setup-CBMu3tqH.js} +1 -1
  112. package/client/dist/assets/{setup-B5FHuxvB.js → setup-CPh-e6J2.js} +1 -1
  113. package/client/dist/assets/{setup-DpqzaEEA.js → setup-CRKLp_ke.js} +1 -1
  114. package/client/dist/assets/{setup-CROyqD5N.js → setup-Cy302v4J.js} +1 -1
  115. package/client/dist/assets/{setup-C9Fy9PKQ.js → setup-D681vTPf.js} +1 -1
  116. package/client/dist/assets/{setup-BihzXadx.js → setup-DYK0VBkE.js} +1 -1
  117. package/client/dist/assets/{setup-BtC6Hg4K.js → setup-KAuiIiVs.js} +1 -1
  118. package/client/dist/assets/{setup-zLEDnG7N.js → setup-OGX-oCfI.js} +1 -1
  119. package/client/dist/assets/{spending-T5GM7wpn.js → spending-HBSk-6jv.js} +1 -1
  120. package/client/dist/assets/{useDesktop-QogiN0zR.js → useDesktop-0unHDBJJ.js} +1 -1
  121. package/client/dist/assets/useRuntimeRuns-BN1rhyEm.js +1 -0
  122. package/client/dist/assets/{useSharedWebSocket-Dz-HI6De.js → useSharedWebSocket-BZMrX4jr.js} +2 -2
  123. package/client/dist/index.html +25 -25
  124. package/docs/agent-live-steering.md +16 -0
  125. package/docs/cli.md +7 -7
  126. package/docs/codex.md +2 -2
  127. package/docs/customizing.md +1 -1
  128. package/docs/gemini.md +1 -1
  129. package/docs/getting-started.md +1 -1
  130. package/docs/guide/de/integrations/7-local-engines.md +1 -1
  131. package/docs/guide/de/pipeline/1-rails-and-jobs.md +8 -6
  132. package/docs/guide/de/pipeline/2-the-job-detail-view.md +11 -2
  133. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
  134. package/docs/guide/de/pipeline/5-the-loop-builder.md +24 -5
  135. package/docs/guide/en/integrations/7-local-engines.md +1 -1
  136. package/docs/guide/en/pipeline/1-rails-and-jobs.md +8 -6
  137. package/docs/guide/en/pipeline/2-the-job-detail-view.md +11 -2
  138. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
  139. package/docs/guide/en/pipeline/5-the-loop-builder.md +34 -5
  140. package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +1 -1
  141. package/docs/guide/es/integrations/7-local-engines.md +1 -1
  142. package/docs/guide/es/pipeline/1-rails-and-jobs.md +8 -6
  143. package/docs/guide/es/pipeline/2-the-job-detail-view.md +11 -2
  144. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
  145. package/docs/guide/es/pipeline/5-the-loop-builder.md +30 -5
  146. package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +1 -2
  147. package/docs/guide/fr/integrations/7-local-engines.md +1 -1
  148. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +8 -6
  149. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +11 -2
  150. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
  151. package/docs/guide/fr/pipeline/5-the-loop-builder.md +24 -5
  152. package/docs/guide/it/integrations/7-local-engines.md +1 -1
  153. package/docs/guide/it/pipeline/1-rails-and-jobs.md +8 -6
  154. package/docs/guide/it/pipeline/2-the-job-detail-view.md +11 -2
  155. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
  156. package/docs/guide/it/pipeline/5-the-loop-builder.md +24 -5
  157. package/docs/guide/ja/integrations/7-local-engines.md +1 -1
  158. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +8 -6
  159. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +11 -2
  160. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
  161. package/docs/guide/ja/pipeline/5-the-loop-builder.md +24 -5
  162. package/docs/guide/pt/integrations/7-local-engines.md +1 -1
  163. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +8 -6
  164. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +11 -2
  165. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
  166. package/docs/guide/pt/pipeline/5-the-loop-builder.md +24 -5
  167. package/docs/guide/zh/integrations/7-local-engines.md +1 -1
  168. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +8 -6
  169. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +11 -2
  170. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
  171. package/docs/guide/zh/pipeline/5-the-loop-builder.md +24 -5
  172. package/docs/internals/api-reference.md +2 -2
  173. package/docs/internals/ci-performance.md +71 -0
  174. package/docs/internals/companion-rails-as-loops-contract.md +4 -3
  175. package/docs/internals/configuration.md +3 -3
  176. package/docs/internals/core-runtime-updates.md +3 -2
  177. package/docs/internals/interactive-jobs.md +2 -2
  178. package/docs/internals/loop-step-log-explorer.md +42 -0
  179. package/docs/internals/mission-rail-cards.md +1 -1
  180. package/docs/internals/operations-runbook.md +22 -0
  181. package/docs/internals/programmatic-agent-runtime.md +130 -7
  182. package/docs/internals/project-builder.md +35 -16
  183. package/docs/internals/source-map.md +48 -2
  184. package/docs/kimi.md +2 -2
  185. package/docs/local-providers.md +1 -1
  186. package/docs/running-pipelines.md +26 -18
  187. package/docs/tracking-cost.md +10 -1
  188. package/package.json +2 -1
  189. package/server/dist/core-compat.js +8 -3
  190. package/server/dist/core-execution.js +49 -3
  191. package/server/dist/db/migrations.js +101 -0
  192. package/server/dist/desktop-db.js +28 -0
  193. package/server/dist/desktop-router.js +2 -0
  194. package/server/dist/mcp/guide.js +6 -3
  195. package/server/dist/mcp/tools/jobs.js +14 -1
  196. package/server/dist/mcp/tools/loops.js +17 -8
  197. package/server/dist/mcp/tools/rails.js +3 -3
  198. package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +313 -9
  199. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls-router.js +34 -1
  200. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +203 -21
  201. package/server/dist/modules/agent-runtime/runtime/agent-runtime-effective-config.js +46 -1
  202. package/server/dist/modules/agent-runtime/runtime/agent-runtime-engines.js +10 -0
  203. package/server/dist/modules/agent-runtime/runtime/agent-runtime-loader.js +68 -3
  204. package/server/dist/modules/agent-runtime/runtime/agent-runtime-metrics.js +16 -8
  205. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-gc.js +81 -0
  206. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package-lock.js +37 -0
  207. package/server/dist/modules/agent-runtime/runtime/agent-runtime-package.js +30 -24
  208. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-host.js +87 -0
  209. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-quarantine.js +231 -0
  210. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention-records.js +32 -0
  211. package/server/dist/modules/agent-runtime/runtime/agent-runtime-retention.js +102 -0
  212. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings-router.js +12 -7
  213. package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +57 -13
  214. package/server/dist/modules/agents/runtime/agent-catalog.js +32 -0
  215. package/server/dist/modules/agents/runtime/agent-refine-manager.js +12 -0
  216. package/server/dist/modules/agents/runtime/agent-role-descriptor.js +64 -0
  217. package/server/dist/modules/agents/runtime/profiles-router.js +26 -34
  218. package/server/dist/modules/builder/runtime/milestone-chain.js +121 -73
  219. package/server/dist/modules/delivery/runtime/definition-fork.js +158 -0
  220. package/server/dist/modules/delivery/runtime/delivery-evidence.js +56 -10
  221. package/server/dist/modules/delivery/runtime/isolated-settlement-reconstruction.js +99 -0
  222. package/server/dist/modules/delivery/runtime/isolated-settlement-store.js +53 -0
  223. package/server/dist/modules/delivery/runtime/multi-repo-execution-store.js +3 -1
  224. package/server/dist/modules/delivery/runtime/rail-isolated-launch.js +388 -203
  225. package/server/dist/modules/delivery/runtime/rail-launch-parser.js +3 -2
  226. package/server/dist/modules/delivery/runtime/rail-pr-store.js +6 -1
  227. package/server/dist/modules/delivery/runtime/rails-router.js +66 -10
  228. package/server/dist/modules/execution/runtime/queue-manager.js +11 -2
  229. package/server/dist/modules/loops/runtime/builtin-loops.js +70 -0
  230. package/server/dist/modules/loops/runtime/definition-cancellation.js +31 -0
  231. package/server/dist/modules/loops/runtime/legacy-launch-telemetry.js +78 -0
  232. package/server/dist/modules/loops/runtime/loop-command-catalog.js +13 -12
  233. package/server/dist/modules/loops/runtime/loop-compat.js +219 -0
  234. package/server/dist/modules/loops/runtime/loop-core-factory.js +42 -0
  235. package/server/dist/modules/loops/runtime/loop-definition-controls.js +48 -0
  236. package/server/dist/modules/loops/runtime/loop-definition-events.js +145 -0
  237. package/server/dist/modules/loops/runtime/loop-definition-recovery.js +106 -0
  238. package/server/dist/modules/loops/runtime/loop-definition-run.js +36 -0
  239. package/server/dist/modules/loops/runtime/loop-definition.js +246 -0
  240. package/server/dist/modules/loops/runtime/loop-effect.js +10 -1
  241. package/server/dist/modules/loops/runtime/loop-executors.js +63 -3
  242. package/server/dist/modules/loops/runtime/loop-factory.js +35 -15
  243. package/server/dist/modules/loops/runtime/loop-graph.js +84 -8
  244. package/server/dist/modules/loops/runtime/loop-migration.js +36 -0
  245. package/server/dist/modules/loops/runtime/loop-preview.js +10 -0
  246. package/server/dist/modules/loops/runtime/loop-run-manager.js +206 -13
  247. package/server/dist/modules/loops/runtime/loop-runs-store.js +271 -7
  248. package/server/dist/modules/loops/runtime/loop-templates.js +205 -45
  249. package/server/dist/modules/loops/runtime/loops-router.js +246 -29
  250. package/server/dist/modules/loops/runtime/loops-store.js +140 -20
  251. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +14 -11
  252. package/server/dist/modules/specs/runtime/spec-addenda.js +28 -0
  253. package/server/dist/project-registry.js +87 -51
  254. package/server/dist/project-router-jobs.js +17 -6
  255. package/server/dist/project-router-loop-runs.js +200 -0
  256. package/server/dist/project-router-spending.js +17 -0
  257. package/server/dist/project-router.js +8 -6
  258. package/server/dist/schemas/agent-runtime.schema.json +93 -1
  259. package/server/dist/schemas/workflow-definition.schema.json +312 -0
  260. package/server/dist/vitest-setup.js +22 -0
  261. package/server/dist/worktree-overlay.js +1 -1
  262. package/client/dist/assets/AgentsPage-DfHWgP_x.js +0 -87
  263. package/client/dist/assets/DesktopAnalyticsPage-tc0p4jJW.js +0 -1
  264. package/client/dist/assets/InteractiveJobComposer-DFPpdg2D.js +0 -19
  265. package/client/dist/assets/JobDetailModal-BdOv-l-M.js +0 -1
  266. package/client/dist/assets/LoopBuilderPage-BemvUOxy.js +0 -7
  267. package/client/dist/assets/LoopPreviewModal-C6hNAeYN.js +0 -1
  268. package/client/dist/assets/LoopsPage-BZr0quvh.js +0 -1
  269. package/client/dist/assets/TemplatePreviewModal-Cejcquah.js +0 -1
  270. package/client/dist/assets/agentRuntime-BUimiX__.js +0 -1
  271. package/client/dist/assets/agentRuntime-BcN97sAB.js +0 -1
  272. package/client/dist/assets/agentRuntime-Ckyg-ahT.js +0 -1
  273. package/client/dist/assets/agentRuntime-DJCwyX4t.js +0 -1
  274. package/client/dist/assets/agentRuntime-DVzPJOl0.js +0 -1
  275. package/client/dist/assets/agentRuntime-V9AZTG3c.js +0 -1
  276. package/client/dist/assets/agentRuntime-bAAjw1vw.js +0 -1
  277. package/client/dist/assets/agentRuntime-v4qKAYH-.js +0 -1
  278. package/client/dist/assets/agentstudio-BNUth0uj.js +0 -1
  279. package/client/dist/assets/agentstudio-BNdB5hkr.js +0 -1
  280. package/client/dist/assets/agentstudio-C7-bt8FO.js +0 -1
  281. package/client/dist/assets/agentstudio-C8uNchpV.js +0 -1
  282. package/client/dist/assets/agentstudio-CKabyMlg.js +0 -1
  283. package/client/dist/assets/agentstudio-CN2pokQu.js +0 -1
  284. package/client/dist/assets/agentstudio-y2dVDXeM.js +0 -1
  285. package/client/dist/assets/analytics-BlypLLR6.js +0 -1
  286. package/client/dist/assets/builder-765NeYTp.js +0 -1
  287. package/client/dist/assets/builder-8MG3V6ea.js +0 -1
  288. package/client/dist/assets/builder-B-1UBs5K.js +0 -1
  289. package/client/dist/assets/builder-BTiGnPT0.js +0 -1
  290. package/client/dist/assets/builder-Cx9Rz-7u.js +0 -1
  291. package/client/dist/assets/builder-DKFMELBl.js +0 -1
  292. package/client/dist/assets/builder-OcmKrC0h.js +0 -1
  293. package/client/dist/assets/builder-P9qVBXFZ.js +0 -1
  294. package/client/dist/assets/common-B5AZNYMH.js +0 -1
  295. package/client/dist/assets/common-BFZ0c_vN.js +0 -1
  296. package/client/dist/assets/common-BttQx3z4.js +0 -1
  297. package/client/dist/assets/common-CM65k_-e.js +0 -1
  298. package/client/dist/assets/common-D4mvFFlu.js +0 -1
  299. package/client/dist/assets/common-TZ9Ny96S.js +0 -1
  300. package/client/dist/assets/common-nFpSF4GC.js +0 -1
  301. package/client/dist/assets/dashboard-B9uWNGO2.js +0 -1
  302. package/client/dist/assets/dashboard-BIwmO9wc.js +0 -1
  303. package/client/dist/assets/dashboard-BV4vOZkR.js +0 -1
  304. package/client/dist/assets/dashboard-Bf6MPVsM.js +0 -1
  305. package/client/dist/assets/dashboard-C8klWVvZ.js +0 -1
  306. package/client/dist/assets/dashboard-D1ORhPF0.js +0 -1
  307. package/client/dist/assets/dashboard-DSubvs9x.js +0 -1
  308. package/client/dist/assets/dashboard-px_vlRx_.js +0 -1
  309. package/client/dist/assets/index-C51x61Bz.css +0 -2
  310. package/client/dist/assets/loops-B7YpGizO.js +0 -1
  311. package/client/dist/assets/loops-BomD2J-h.js +0 -1
  312. package/client/dist/assets/loops-BrQHSQw3.js +0 -1
  313. package/client/dist/assets/loops-BvBwvTPA.js +0 -1
  314. package/client/dist/assets/loops-CODk6Slz.js +0 -1
  315. package/client/dist/assets/loops-DC6k-Ym-.js +0 -1
  316. package/client/dist/assets/loops-DSEg8-uY.js +0 -1
  317. package/client/dist/assets/loops-DhCoXD_v.js +0 -1
  318. package/client/dist/assets/settings-BsehKpty.js +0 -1
  319. package/client/dist/assets/settings-CQWob3SB.js +0 -1
  320. package/client/dist/assets/settings-CgCl0hzy.js +0 -1
  321. package/client/dist/assets/settings-DLRLd22L.js +0 -1
  322. package/client/dist/assets/settings-Dn5prAFl.js +0 -1
  323. package/client/dist/assets/settings-HZSu-K4l.js +0 -1
  324. package/client/dist/assets/settings-p4OVIyRh.js +0 -1
  325. package/client/dist/assets/settings-rddaldUp.js +0 -1
  326. package/client/dist/assets/useRuntimeRuns-Dqmz9fS9.js +0 -1
  327. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  328. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +0 -126
  329. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +0 -114
  330. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  331. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  332. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  333. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  334. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +0 -84
  335. package/server/dist/modules/loops/runtime/loop-templates-ported.js +0 -675
  336. /package/client/dist/assets/{LoopBuilderPage-DtxX27Jz.css → loop-layout-DtxX27Jz.css} +0 -0
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  // Server-durable milestone launch chain (premium-milestone-progress, D3/D4).
3
3
  //
4
- // "Launch Milestone N" in sequential mode: chunk the milestone's todo specs
5
- // (≤3 per rail), launch chunk 1 through the ORDINARY rails launch route, and
6
- // — when that chunk's delivery settles — launch the next chunk STACKED on the
7
- // delivered branch, so a greenfield walking skeleton accumulates without
8
- // waiting for a merge. The plan lives in SQLite (survives window close, app
4
+ // "Launch Milestone N": order the milestone's todo specs by their declared
5
+ // dependencies (prerequisites, then execution_order), put ONE spec on each
6
+ // rail (mode `implement`), launch the first through the ORDINARY rails launch
7
+ // route, and — when that spec's delivery settles — launch the next one STACKED
8
+ // on the delivered branch, so a greenfield walking skeleton accumulates without
9
+ // waiting for a merge. Launches are always sequential (the Parallel option was
10
+ // removed); auto-continue is on by default and a failure always pauses. The plan lives in SQLite (survives window close, app
9
11
  // restart, machine sleep), advances from the delivery-settle chokepoint (the
10
12
  // `rail.pr_state` broadcast tapped in the project's bound broadcast — the
11
13
  // engine's `onLoopRunFinished` fires BEFORE the delivery row leaves
@@ -13,32 +15,63 @@
13
15
  // pauses with a typed reason on any failure (never skips ahead), and is
14
16
  // visible/controllable (resume / cancel) from every surface.
15
17
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.MilestoneChainManager = exports.MAX_TICKETS_PER_CHAIN_CHUNK = void 0;
17
- exports.isMilestoneChainEnabled = isMilestoneChainEnabled;
18
+ exports.MilestoneChainManager = exports.RAIL_LIMIT_REACHED = exports.MAX_TICKETS_PER_CHAIN_CHUNK = void 0;
18
19
  exports.chunkTickets = chunkTickets;
20
+ exports.orderChainTickets = orderChainTickets;
19
21
  exports.chainRailName = chainRailName;
22
+ exports.isBuilderRailName = isBuilderRailName;
20
23
  const milestone_progress_1 = require("./milestone-progress");
21
24
  const milestone_chain_store_1 = require("./milestone-chain-store");
22
25
  const ids_1 = require("../../../ids");
23
- exports.MAX_TICKETS_PER_CHAIN_CHUNK = 3;
24
- /** `SPECRAILS_MILESTONE_CHAIN=false|0|off` ⇒ every milestone launch is parallel and no row is written. */
25
- function isMilestoneChainEnabled(env = process.env) {
26
- const raw = env.SPECRAILS_MILESTONE_CHAIN;
27
- if (raw === undefined || raw === '')
28
- return true;
29
- const normalized = raw.trim().toLowerCase();
30
- return !(normalized === '0' || normalized === 'false' || normalized === 'off');
31
- }
26
+ /** One spec per rail: every chunk of a milestone chain carries exactly one spec. */
27
+ exports.MAX_TICKETS_PER_CHAIN_CHUNK = 1;
32
28
  function chunkTickets(ticketIds, size = exports.MAX_TICKETS_PER_CHAIN_CHUNK) {
33
29
  const chunks = [];
34
30
  for (let i = 0; i < ticketIds.length; i += size)
35
31
  chunks.push(ticketIds.slice(i, i + size));
36
32
  return chunks;
37
33
  }
38
- function chainRailName(n, chunkIndex, totalChunks) {
34
+ /**
35
+ * Launch order for a milestone's specs: a stable topological order over the
36
+ * specs' `prerequisites` (edges to specs outside the set — already done or in
37
+ * another milestone — are ignored), ties broken by `execution_order` (unset
38
+ * last) and then id. A dependency cycle cannot block the milestone: the
39
+ * remaining specs follow in tie order.
40
+ */
41
+ function orderChainTickets(tickets) {
42
+ const byId = new Map(tickets.map((t) => [t.id, t]));
43
+ const rank = (a, b) => {
44
+ const ea = byId.get(a)?.executionOrder ?? Number.POSITIVE_INFINITY;
45
+ const eb = byId.get(b)?.executionOrder ?? Number.POSITIVE_INFINITY;
46
+ return ea !== eb ? (ea < eb ? -1 : 1) : a - b;
47
+ };
48
+ const pending = new Map();
49
+ for (const t of tickets) {
50
+ pending.set(t.id, new Set((t.prerequisites ?? []).filter((id) => id !== t.id && byId.has(id))));
51
+ }
52
+ const order = [];
53
+ while (pending.size > 0) {
54
+ const ready = [...pending.entries()].filter(([, deps]) => deps.size === 0).map(([id]) => id).sort(rank);
55
+ // Cycle: take the best-ranked remaining spec so the chain still progresses.
56
+ const next = ready[0] ?? [...pending.keys()].sort(rank)[0];
57
+ order.push(next);
58
+ pending.delete(next);
59
+ for (const deps of pending.values())
60
+ deps.delete(next);
61
+ }
62
+ return order;
63
+ }
64
+ /** Builder rails are named after their spec: `M1 · #12`. */
65
+ function chainRailName(n, ticketIds) {
39
66
  const label = (0, milestone_progress_1.milestoneLabelFor)(n);
40
- return totalChunks === 1 ? label : `${label} · ${chunkIndex + 1}`;
67
+ return ticketIds.length ? `${label} · ${ticketIds.map((id) => `#${id}`).join(' ')}` : label;
68
+ }
69
+ /** A rail name the Project Builder owns (`M<n>` or `M<n> · …`). */
70
+ function isBuilderRailName(name) {
71
+ return typeof name === 'string' && /^M\d+(?: · .+)?$/.test(name);
41
72
  }
73
+ /** Pause reason when every rail slot is taken by undecided work. */
74
+ exports.RAIL_LIMIT_REACHED = 'rail_limit_reached';
42
75
  const SUCCESS_DECISIONS = new Set(['on_review', 'no_changes', 'pr_draft', 'pr_ready', 'completed', 'merged']);
43
76
  class MilestoneChainManager {
44
77
  db;
@@ -52,9 +85,6 @@ class MilestoneChainManager {
52
85
  now() {
53
86
  return this.io.now?.() ?? Date.now();
54
87
  }
55
- enabled() {
56
- return this.io.enabled?.() ?? isMilestoneChainEnabled();
57
- }
58
88
  /** Non-terminal chains. */
59
89
  listActive() {
60
90
  return (0, milestone_chain_store_1.listActiveChains)(this.db).map(milestone_chain_store_1.toChainSnapshot);
@@ -88,34 +118,37 @@ class MilestoneChainManager {
88
118
  }
89
119
  todoTicketIds(n) {
90
120
  const label = (0, milestone_progress_1.milestoneLabelFor)(n);
91
- return this.io.readTickets()
92
- .filter((t) => t.status === 'todo' && Array.isArray(t.labels) && t.labels.includes(label))
93
- .map((t) => t.id)
94
- .sort((a, b) => a - b);
121
+ return orderChainTickets(this.io.readTickets()
122
+ .filter((t) => t.status === 'todo' && Array.isArray(t.labels) && t.labels.includes(label)));
95
123
  }
96
- /** Create → assign → launch ONE chunk on a fresh rail — or, for a retry,
97
- * on the rail the failed attempt used when nothing undecided sits on it. */
98
- async launchChunk(n, chunks, chunkIndex, baseBranch, reuseRailIndex = null, baseDeliveryIds) {
124
+ /** Assign → launch ONE spec on a rail: the retried chunk's previous rail,
125
+ * else a free builder-owned rail (same name, this chain's, then any builder
126
+ * rail), else a fresh one. At the rail limit it fails `rail_limit_reached`. */
127
+ async launchChunk(n, chunks, chunkIndex, baseBranch, reuseRailIndex = null, baseDeliveryIds, chain) {
99
128
  const ticketIds = chunks[chunkIndex];
100
129
  let railIndex;
101
- const name = chainRailName(n, chunkIndex, chunks.length);
102
- const byName = reuseRailIndex === null ? this.freeRailNamed(name) : null;
103
- if (reuseRailIndex !== null) {
104
- railIndex = reuseRailIndex;
105
- }
106
- else if (byName !== null) {
107
- railIndex = byName;
130
+ const name = chainRailName(n, ticketIds);
131
+ const reused = reuseRailIndex ?? this.freeRailNamed(name) ?? this.freeBuilderRail(chain);
132
+ if (reused !== null) {
133
+ railIndex = reused;
134
+ if (this.railName(reused) !== name && this.io.renameRail) {
135
+ // Best effort: a stale label is cosmetic, never a reason to stop.
136
+ try {
137
+ await this.io.renameRail(reused, name);
138
+ }
139
+ catch { /* keep the old label */ }
140
+ }
108
141
  }
109
142
  else {
110
143
  const rail = await this.io.createRail(name);
111
144
  if (!rail.ok)
112
- return rail;
145
+ return rail.error === exports.RAIL_LIMIT_REACHED ? { ...rail, status: 409 } : rail;
113
146
  railIndex = rail.railIndex;
114
147
  }
115
148
  const assigned = await this.io.assignTickets(railIndex, ticketIds);
116
149
  if (!assigned.ok)
117
150
  return assigned;
118
- const launched = await this.io.launch(railIndex, { mode: 'batch-implement', ...(baseDeliveryIds?.length ? { baseDeliveryIds } : baseBranch ? { baseBranch } : {}) });
151
+ const launched = await this.io.launch(railIndex, { mode: 'implement', ...(baseDeliveryIds?.length ? { baseDeliveryIds } : baseBranch ? { baseBranch } : {}) });
119
152
  if (!launched.ok)
120
153
  return launched;
121
154
  const delivery = this.io.activeDeliveryForRail(railIndex);
@@ -131,7 +164,39 @@ class MilestoneChainManager {
131
164
  const index = this.io.findRailByName?.(name) ?? null;
132
165
  if (index === null)
133
166
  return null;
134
- return this.io.activeDeliveryForRail(index) ? null : index;
167
+ return this.railFree(index) ? index : null;
168
+ }
169
+ catch {
170
+ return null;
171
+ }
172
+ }
173
+ railName(railIndex) {
174
+ try {
175
+ return this.io.listRails?.().find((r) => r.railIndex === railIndex)?.name ?? null;
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ /** Nothing undecided, nothing running, and no other active chain mid-flight on it. */
182
+ railFree(railIndex) {
183
+ if (this.io.activeDeliveryForRail(railIndex))
184
+ return false;
185
+ if (this.io.railBusy?.(railIndex))
186
+ return false;
187
+ return !(0, milestone_chain_store_1.listActiveChains)(this.db).some((row) => row.current_rail_index === railIndex && (row.current_delivery_id !== null || (0, milestone_chain_store_1.parseRunIds)(row).length > 0));
188
+ }
189
+ /** Before allocating a new rail slot: a rail this chain already used, else
190
+ * any builder-named rail, that is free (its PR decided or nothing on it). */
191
+ freeBuilderRail(chain) {
192
+ try {
193
+ const own = chain ? (0, milestone_chain_store_1.parseLaunched)(chain).map((l) => l.railIndex) : [];
194
+ const builder = (this.io.listRails?.() ?? []).filter((r) => isBuilderRailName(r.name)).map((r) => r.railIndex);
195
+ for (const index of [...new Set([...own, ...builder])]) {
196
+ if (this.railFree(index))
197
+ return index;
198
+ }
199
+ return null;
135
200
  }
136
201
  catch {
137
202
  return null;
@@ -151,10 +216,13 @@ class MilestoneChainManager {
151
216
  return null;
152
217
  }
153
218
  }
154
- async start(n, requestedMode, opts = {}) {
155
- const enabled = this.enabled();
219
+ /**
220
+ * Launch a milestone. Always sequential: one spec per rail, each stacked on
221
+ * the previous delivered branch. `autoAdvance` defaults to ON; turning it off
222
+ * parks the chain at a checkpoint after every delivered rail.
223
+ */
224
+ async start(n, opts = {}) {
156
225
  const autoAdvance = opts.autoAdvance !== false;
157
- const mode = enabled ? requestedMode : 'parallel';
158
226
  const active = (0, milestone_chain_store_1.listActiveChains)(this.db).find((c) => c.milestone_n === n);
159
227
  if (active)
160
228
  return { ok: false, status: 409, error: 'chain_active', chainId: active.id };
@@ -166,40 +234,15 @@ class MilestoneChainManager {
166
234
  if (ticketIds.length === 0)
167
235
  return { ok: false, status: 400, error: 'no_tickets', detail: `no todo specs labeled ${(0, milestone_progress_1.milestoneLabelFor)(n)}` };
168
236
  const chunks = chunkTickets(ticketIds);
169
- if (mode === 'parallel') {
170
- const launched = [];
171
- let failure = null;
172
- for (let i = 0; i < chunks.length; i++) {
173
- const r = await this.launchChunk(n, chunks, i, null);
174
- if (!r.ok) {
175
- failure = r;
176
- break;
177
- }
178
- launched.push(r.entry);
179
- }
180
- if (launched.length === 0 && failure)
181
- return { ok: false, status: failure.status, error: failure.error, detail: failure.detail };
182
- let chainId = null;
183
- if (enabled) {
184
- // Recorded `completed` so parallel milestones show chunk ordering in
185
- // the same progress model; never advances anything.
186
- const row = (0, milestone_chain_store_1.createChain)(this.db, { id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode, chunks, integrationBranch: await this.safeIntegrationBranch(), status: 'completed', nowMs: this.now() });
187
- (0, milestone_chain_store_1.updateChain)(this.db, row.id, 'completed', { nextChunk: launched.length, launched }, this.now());
188
- chainId = row.id;
189
- const fresh = (0, milestone_chain_store_1.getChain)(this.db, row.id);
190
- if (fresh)
191
- this.emit(fresh);
192
- }
193
- return { ok: true, status: 202, chainId, launched, pending: chunks.slice(launched.length) };
194
- }
195
237
  const row = (0, milestone_chain_store_1.createChain)(this.db, {
196
- id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode, chunks,
238
+ id: (0, ids_1.newId)(), milestoneN: n, milestoneId: milestone.id, mode: 'sequential', chunks,
197
239
  integrationBranch: await this.safeIntegrationBranch(), autoAdvance, nowMs: this.now(),
198
240
  });
199
- const first = await this.launchChunk(n, chunks, 0, null);
241
+ const first = await this.launchChunk(n, chunks, 0, null, null, undefined, row);
200
242
  if (!first.ok) {
201
- // Nothing launched ⇒ no chain remains active (the route relays the guard).
202
- (0, milestone_chain_store_1.updateChain)(this.db, row.id, 'running', { status: 'cancelled', pauseReason: `launch_rejected:${first.error}` }, this.now());
243
+ // Nothing launched ⇒ no chain remains active (the route relays the guard,
244
+ // including `rail_limit_reached` when every rail holds undecided work).
245
+ (0, milestone_chain_store_1.updateChain)(this.db, row.id, 'running', { status: 'cancelled', pauseReason: first.error === exports.RAIL_LIMIT_REACHED ? exports.RAIL_LIMIT_REACHED : `launch_rejected:${first.error}` }, this.now());
203
246
  return { ok: false, status: first.status, error: first.error, detail: first.detail, chainId: row.id };
204
247
  }
205
248
  this.recordLaunched(row.id, first.entry);
@@ -272,12 +315,17 @@ class MilestoneChainManager {
272
315
  }
273
316
  (0, milestone_chain_store_1.updateChain)(this.db, row.id, row.status, { status: 'waiting', headBranch: head, pauseReason: null }, this.now());
274
317
  const reuse = chunkIndex < row.next_chunk ? this.reusableRailFor(row, chunkIndex) : null;
275
- const r = await this.launchChunk(row.milestone_n, chunks, chunkIndex, head, reuse, multiRepositoryHistory ? earlierDeliveries : undefined);
318
+ const r = await this.launchChunk(row.milestone_n, chunks, chunkIndex, head, reuse, multiRepositoryHistory ? earlierDeliveries : undefined, row);
276
319
  const current = (0, milestone_chain_store_1.getChain)(this.db, chainId);
277
320
  if (!current || !(0, milestone_chain_store_1.isActiveChainStatus)(current.status))
278
321
  return;
279
322
  if (!r.ok) {
280
- this.pause(current, `launch_rejected:${r.error}`);
323
+ // Every rail holds undecided work: wait for the user to decide pending
324
+ // PRs, then Resume launches this same spec.
325
+ if (r.error === exports.RAIL_LIMIT_REACHED)
326
+ this.pause(current, exports.RAIL_LIMIT_REACHED);
327
+ else
328
+ this.pause(current, `launch_rejected:${r.error}`);
281
329
  return;
282
330
  }
283
331
  this.recordLaunched(chainId, r.entry);
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateDefinitionForkRequest = validateDefinitionForkRequest;
4
+ exports.forkDefinitionRun = forkDefinitionRun;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const db_1 = require("../../../db");
9
+ const agent_runtime_bridge_1 = require("../../agent-runtime/runtime/agent-runtime-bridge");
10
+ const loop_runs_store_1 = require("../../loops/runtime/loop-runs-store");
11
+ const loop_definition_recovery_1 = require("../../loops/runtime/loop-definition-recovery");
12
+ const rail_pr_store_1 = require("./rail-pr-store");
13
+ const isolated_settlement_store_1 = require("./isolated-settlement-store");
14
+ const rail_worktrees_store_1 = require("./rail-worktrees-store");
15
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
16
+ const canonical = (value) => Array.isArray(value) ? value.map(canonical) : object(value)
17
+ ? Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])])) : value;
18
+ function validateDefinitionForkRequest(value) {
19
+ if (!object(value) || Object.keys(value).some(key => !['requestId', 'fromNodePath', 'scopeId', 'visit', 'state'].includes(key)) ||
20
+ typeof value.requestId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value.requestId) ||
21
+ typeof value.fromNodePath !== 'string' || !value.fromNodePath.trim() || value.fromNodePath.length > 2048 ||
22
+ value.scopeId !== undefined && (typeof value.scopeId !== 'string' || !value.scopeId || value.scopeId.length > 2048) ||
23
+ value.visit !== undefined && (!Number.isSafeInteger(value.visit) || Number(value.visit) < 1) ||
24
+ value.state !== undefined && (!object(value.state) || Object.keys(value.state).some(key => !['$vars', '$outputs'].includes(key)) || Object.values(value.state).some(channel => !object(channel))))
25
+ throw new Error('invalid_fork_request');
26
+ if (JSON.stringify(value).length > 2 * 1024 * 1024)
27
+ throw new Error('invalid_fork_request: State exceeds 2 MiB');
28
+ return value;
29
+ }
30
+ /** Core owns the historical cut; Desktop atomically moves only host ownership.
31
+ * A durable request precedes Core publication so a lost acknowledgement can be
32
+ * retried. The source job, frozen inputs, events and accounting remain untouched.
33
+ */
34
+ async function forkDefinitionRun(ctx, runId, input, io = {}) {
35
+ const db = ctx.db, projectId = ctx.project.id;
36
+ const frozen = (0, loop_runs_store_1.readDefinitionRun)(db, runId), sourceJob = (0, db_1.getJob)(db, runId);
37
+ if (!frozen || frozen.row.project_id !== projectId || !frozen.metadata.contextPath || !sourceJob)
38
+ throw new Error('runtime_run_not_found');
39
+ const request = validateDefinitionForkRequest(input), encoded = JSON.stringify(canonical(request));
40
+ const prior = db.prepare('SELECT * FROM definition_fork_operations WHERE project_id=? AND source_run_id=? AND request_id=?').get(projectId, runId, request.requestId);
41
+ if (prior && prior.request_json !== encoded)
42
+ throw new Error('fork_request_conflict');
43
+ if (prior?.adopted && prior.result_json)
44
+ return JSON.parse(prior.result_json);
45
+ if ((0, loop_runs_store_1.readDefinitionSuccessor)(db, runId))
46
+ throw new Error('runtime_fork_owns_worktree');
47
+ if (!prior && db.prepare('SELECT 1 FROM definition_fork_operations WHERE source_run_id=? LIMIT 1').get(runId))
48
+ throw new Error('fork_request_pending: Retry the original request');
49
+ const childRunId = prior?.child_run_id ?? `fork-${(0, node_crypto_1.createHash)('sha256').update(JSON.stringify([projectId, runId, request.requestId])).digest('hex').slice(0, 32)}`;
50
+ const observed = (0, loop_runs_store_1.readDefinitionExecutionClaim)(db, runId);
51
+ const probe = await (io.probe ?? loop_definition_recovery_1.probeDefinitionRun)({ db, cwd: ctx.project.path, env: process.env }, runId);
52
+ if (probe.status === 'unavailable')
53
+ throw new Error('runtime_status_unavailable');
54
+ if (probe.lease?.active || ctx.loopRunManager.isDefinitionCancellationPending(runId))
55
+ throw new Error('runtime_run_active');
56
+ if ((0, loop_runs_store_1.readDefinitionRun)(db, runId)?.row.status === 'running')
57
+ throw new Error('runtime_run_active: Wait for the host pause or terminal settlement');
58
+ if (observed && !ctx.loopRunManager.isDefinitionRunActive(runId))
59
+ db.prepare(`DELETE FROM definition_execution_claims WHERE run_id=? AND owner=?
60
+ AND EXISTS (SELECT 1 FROM loop_runs WHERE id=? AND status='paused' AND restart_reason='restart')`).run(runId, observed.owner, runId);
61
+ const token = `fork:${(0, node_crypto_1.randomUUID)()}`;
62
+ const execution = (0, loop_runs_store_1.claimDefinitionExecution)(db, runId, { owner: token, repositoryMounts: (0, loop_runs_store_1.definitionRepositoryMounts)(frozen.request) });
63
+ if (!execution.ok)
64
+ throw new Error('runtime_run_active');
65
+ const claimed = [];
66
+ try {
67
+ const owned = db.prepare('SELECT delivery_id FROM definition_delivery_settlements WHERE project_id=? AND run_id=? AND superseded_by IS NULL').all(projectId, runId);
68
+ const deliveries = owned.map(row => (0, rail_pr_store_1.getPrDelivery)(db, row.delivery_id));
69
+ const parentIds = deliveries.flatMap(row => row?.parent_delivery_id ? [row.parent_delivery_id] : []);
70
+ const all = [...deliveries, ...[...new Set(parentIds)].map(id => (0, rail_pr_store_1.getPrDelivery)(db, id))];
71
+ if (frozen.request.deferTerminalOutcome && !deliveries.length)
72
+ throw new Error('Original isolated settlement snapshot is unavailable');
73
+ if (frozen.row.status === 'completed' && deliveries.some(row => !(0, isolated_settlement_store_1.readIsolatedSettlementRecords)(db, projectId, row.id).find(record => record.snapshot.run.runId === runId)?.result))
74
+ throw new Error('runtime_settlement_pending: Finish original settlement before forking');
75
+ if (all.some(row => !row || ['discarded', 'superseded', 'merged', 'completed'].includes(row.decision)))
76
+ throw new Error('Delivery ownership has already changed');
77
+ const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(frozen.metadata.contextPath, process.env, runId);
78
+ const forkOptions = { ...request, kind: 'fork', runId, childRunId, contextPath: frozen.metadata.contextPath, cwd: host.cwd, env: host.env };
79
+ if (!io.fork)
80
+ await (0, agent_runtime_bridge_1.assertRetainedForkSupport)(forkOptions);
81
+ db.transaction(() => {
82
+ for (const row of all) {
83
+ if (!(0, rail_pr_store_1.claimPrDeliveryOperation)(db, row.id, row.decision, 'recover-and-retry', token))
84
+ throw new Error('Delivery is busy');
85
+ claimed.push(row);
86
+ }
87
+ if (!prior)
88
+ db.prepare('INSERT INTO definition_fork_operations(project_id,source_run_id,request_id,child_run_id,request_json) VALUES (?,?,?,?,?)').run(projectId, runId, request.requestId, childRunId, encoded);
89
+ })();
90
+ let fork;
91
+ try {
92
+ fork = io.fork ? await io.fork(forkOptions) : await (0, agent_runtime_bridge_1.runAgentRuntimeControl)(forkOptions);
93
+ }
94
+ catch (error) {
95
+ // An authoritative pre-publication rejection is not a lost receipt. Only
96
+ // drop its intent when the destination does not exist; uncertain failures
97
+ // and all published/partial children keep the exact retry identity.
98
+ if (error instanceof Error && /^(fork_ambiguous|fork_checkpoint_missing|invalid_arguments|resume_incompatible|lease_held):/.test(error.message) &&
99
+ !(0, node_fs_1.existsSync)((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.dirname)(frozen.metadata.contextPath)), childRunId))) {
100
+ db.prepare('DELETE FROM definition_fork_operations WHERE project_id=? AND source_run_id=? AND request_id=? AND adopted=0 AND result_json IS NULL').run(projectId, runId, request.requestId);
101
+ }
102
+ throw error;
103
+ }
104
+ if (fork.runId !== childRunId || fork.forkOf !== runId)
105
+ throw new Error('Core returned another fork identity');
106
+ const result = { loopRunId: childRunId, forkOf: runId, fromNodePath: fork.fromNodePath, scopeId: fork.scopeId, visit: fork.visit };
107
+ db.transaction(() => {
108
+ // Recheck all ownership after asynchronous Core work; a failed adoption
109
+ // rolls back entirely and retains the published child for the same retry.
110
+ const tickets = JSON.parse(frozen.row.ticket_ids_json ?? '[]');
111
+ for (const ticket of tickets) {
112
+ const owner = db.prepare('SELECT owner_id FROM ticket_outcome_ownership WHERE ticket_id=?').get(ticket);
113
+ if (owner?.owner_id !== runId)
114
+ throw new Error('Ticket ownership changed before fork adoption');
115
+ }
116
+ (0, loop_runs_store_1.createLoopRun)(db, { id: childRunId, projectId, loopId: frozen.row.loop_id, loopName: frozen.row.loop_name, railIndex: frozen.row.rail_index,
117
+ ticketId: frozen.row.ticket_id, ticketIds: tickets, ticketCompletionStatus: frozen.row.ticket_completion_status, causalOwnership: !!frozen.row.causal_ownership,
118
+ provider: frozen.row.provider, model: frozen.row.model, reasoningEffort: frozen.row.reasoning_effort, iterationLimit: frozen.row.iteration_limit, startedAt: new Date().toISOString() });
119
+ (0, db_1.createJob)(db, { id: childRunId, command: sourceJob.command, owner: 'loop', provider: sourceJob.provider, causal_ownership: !!frozen.row.causal_ownership, started_at: new Date().toISOString() });
120
+ (0, loop_runs_store_1.saveDefinitionRun)(db, childRunId, { ...frozen.metadata, contextPath: fork.contextPath, runtimeDirectory: fork.runtimeDirectory,
121
+ definitionPath: fork.definitionPath, configPath: fork.configPath, context: fork.context,
122
+ inheritedAddendaClaims: [...(frozen.metadata.inheritedAddendaClaims ?? []), ...(frozen.request.addenda?.ids.length ? [{ runId, ids: frozen.request.addenda.ids }] : [])],
123
+ request: { ...frozen.request, runId: childRunId } });
124
+ (0, loop_runs_store_1.linkDefinitionFork)(db, childRunId, { forkOf: runId, forkCut: { fromNodePath: fork.fromNodePath, scopeId: fork.scopeId, visit: fork.visit, revision: fork.revision } });
125
+ db.prepare("UPDATE loop_runs SET status='paused',restart_reason='fork' WHERE id=?").run(childRunId);
126
+ if (frozen.request.executionManifest)
127
+ db.prepare('UPDATE loop_runs SET execution_manifest=? WHERE id=?').run(JSON.stringify(frozen.request.executionManifest), childRunId);
128
+ db.prepare("UPDATE ticket_outcome_ownership SET owner_id=?,generation=generation+1,claimed_at=datetime('now') WHERE owner_id=?").run(childRunId, runId);
129
+ db.prepare("UPDATE rail_ticket_ownership SET owner_id=?,generation=generation+1,claimed_at=datetime('now') WHERE owner_id=?").run(childRunId, runId);
130
+ for (const row of deliveries) {
131
+ const source = (0, isolated_settlement_store_1.readIsolatedSettlementRecords)(db, projectId, row.id).find(record => record.snapshot.run.runId === runId);
132
+ if (!source)
133
+ throw new Error('Original isolated allocation is missing');
134
+ const snapshot = source.snapshot, ledger = (0, rail_worktrees_store_1.getRailWorktree)(db, snapshot.run.ledgerId);
135
+ if (!ledger || ledger.run_id !== runId || ledger.branch !== snapshot.run.handle.branch || ledger.worktree_path !== snapshot.run.handle.worktreePath)
136
+ throw new Error('Worktree ownership changed before fork adoption');
137
+ (0, isolated_settlement_store_1.saveIsolatedSettlementSnapshot)(db, { ...snapshot, run: { ...snapshot.run, runId: childRunId } });
138
+ db.prepare('UPDATE definition_delivery_settlements SET superseded_by=? WHERE project_id=? AND delivery_id=? AND run_id=? AND superseded_by IS NULL').run(childRunId, projectId, row.id, runId);
139
+ db.prepare("UPDATE rail_worktrees SET run_id=?,merge_state='building' WHERE id=? AND run_id=?").run(childRunId, ledger.id, runId);
140
+ }
141
+ for (const row of all) {
142
+ const runIds = JSON.parse(row.run_ids).map(id => id === runId ? childRunId : id);
143
+ if (!runIds.includes(childRunId))
144
+ runIds.push(childRunId);
145
+ const branches = JSON.parse(row.branches).filter(branch => branch.runId !== runId);
146
+ if (!(0, rail_pr_store_1.transitionClaimedDecision)(db, row.id, row.decision, 'building', token, { runIds, branches, implementationOutcome: 'unknown', deliveryOutcome: 'not_started', statusCode: 'restart_pending', statusDetail: 'Fork created; resume the linked run to continue.', settleEvidence: null }))
147
+ throw new Error('Delivery changed before fork adoption');
148
+ }
149
+ db.prepare('UPDATE definition_fork_operations SET result_json=?,adopted=1 WHERE project_id=? AND source_run_id=? AND request_id=?').run(JSON.stringify(result), projectId, runId, request.requestId);
150
+ })();
151
+ return result;
152
+ }
153
+ finally {
154
+ for (const row of claimed)
155
+ (0, rail_pr_store_1.releasePrDeliveryOperation)(db, row.id, token);
156
+ execution.release();
157
+ }
158
+ }
@@ -8,6 +8,7 @@ exports.extractVerifyStepText = extractVerifyStepText;
8
8
  exports.parseConfidenceScore = parseConfidenceScore;
9
9
  exports.harvestDeliveryEvidence = harvestDeliveryEvidence;
10
10
  exports.readRuntimeEvidence = readRuntimeEvidence;
11
+ exports.projectDefinitionRuntimeEvidence = projectDefinitionRuntimeEvidence;
11
12
  exports.healRuntimeEvidence = healRuntimeEvidence;
12
13
  exports.readSettleEvidence = readSettleEvidence;
13
14
  /**
@@ -283,12 +284,13 @@ function harvestDeliveryEvidence(io, units) {
283
284
  const evidence = {
284
285
  ticketId: unit.ticketId,
285
286
  runId: unit.runId,
287
+ ...(unit.reviewerStepId ? { reviewerStepId: unit.reviewerStepId } : {}),
286
288
  sentinel: 'absent',
287
289
  sentinelDetail: null,
288
290
  verifyTail: null,
289
291
  confidence: null,
290
292
  };
291
- let unitErrored = false;
293
+ let unitErrored = unit.definitionStatus?.status === 'unavailable';
292
294
  let verifyStartedAtMs = null;
293
295
  try {
294
296
  if (unit.runId) {
@@ -321,8 +323,12 @@ function harvestDeliveryEvidence(io, units) {
321
323
  unitErrored = true;
322
324
  }
323
325
  try {
324
- if (unit.runtimeDir && unit.runId) {
325
- const runtime = readRuntimeEvidence(unit.runtimeDir, unit.runId, fsIo);
326
+ if (unit.runId && (unit.definitionStatus || unit.runtimeDir)) {
327
+ const runtime = unit.definitionStatus
328
+ ? projectDefinitionRuntimeEvidence(unit.definitionStatus, unit.reviewerStepId)
329
+ : readRuntimeEvidence(unit.runtimeDir, unit.runId, fsIo);
330
+ if (unit.definitionStatus)
331
+ evidence.runtime = runtime;
326
332
  if (runtime) {
327
333
  evidence.runtime = runtime;
328
334
  // The runtime reviewer's structured verdict IS the reviewer score for
@@ -427,11 +433,18 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
427
433
  checks.push({ name, status: str(raw.status, 40) ?? 'unknown', required: raw.required === true, hostRun, evidence, scope: str(raw.scope), limitations: str(raw.limitations) });
428
434
  }
429
435
  const findings = strList(acceptance?.findings, 6);
430
- let review = null;
431
436
  const checkpoint = readJson(fsIo, path_1.default.join(runtimeDir, 'agent-workflow', runId, 'checkpoint.json'));
432
437
  const steps = isRecord(checkpoint) && isRecord(checkpoint.state) && isRecord(checkpoint.state.steps) ? checkpoint.state.steps : null;
433
438
  const reviewer = steps && isRecord(steps.reviewer) ? steps.reviewer : null;
434
439
  const output = reviewer && isRecord(reviewer.output) ? reviewer.output : null;
440
+ const review = parseRuntimeReview(output);
441
+ if (commands.length === 0 && checks.length === 0 && findings.length === 0 && !review)
442
+ return null;
443
+ return { commands, checks, findings, review };
444
+ }
445
+ /** A reviewer verdict stays AI-reported; it cannot create host verification. */
446
+ function parseRuntimeReview(value) {
447
+ const output = isRecord(value) ? value : null;
435
448
  if (output && ('score' in output || 'approved' in output)) {
436
449
  const aspects = {};
437
450
  if (isRecord(output.aspects)) {
@@ -442,7 +455,7 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
442
455
  const issues = Array.isArray(output.issues)
443
456
  ? output.issues.slice(0, 6).map((i) => (typeof i === 'string' ? i : isRecord(i) ? (str(i.message) ?? str(i.title) ?? str(i.description) ?? JSON.stringify(i).slice(0, RUNTIME_TEXT_CAP)) : null)).filter((x) => !!x)
444
457
  : [];
445
- review = {
458
+ return {
446
459
  approved: typeof output.approved === 'boolean' ? output.approved : null,
447
460
  score: typeof output.score === 'number' && Number.isFinite(output.score) ? output.score : null,
448
461
  aspects,
@@ -450,9 +463,39 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
450
463
  summary: str(output.summary, 1200),
451
464
  };
452
465
  }
453
- if (commands.length === 0 && checks.length === 0 && findings.length === 0 && !review)
466
+ return null;
467
+ }
468
+ /** Pure projection of exact committed scopes; never read Core's private database. */
469
+ function projectDefinitionRuntimeEvidence(status, reviewerStepId) {
470
+ if (status.status === 'unavailable' || !status.scopes)
454
471
  return null;
455
- return { commands, checks, findings, review };
472
+ const commands = [];
473
+ const scopedReviews = [];
474
+ for (const scope of status.scopes) {
475
+ if (!['succeeded', 'failed'].includes(scope.status) || !scope.attemptId || !isRecord(scope.output))
476
+ continue;
477
+ const output = scope.output;
478
+ if (scope.kind === 'verify' && typeof output.receiptId === 'string' && Array.isArray(output.commands)) {
479
+ for (const raw of output.commands) {
480
+ if (commands.length >= RUNTIME_LIST_CAP)
481
+ break;
482
+ if (!isRecord(raw) || typeof raw.command !== 'string' || !Number.isInteger(raw.exitCode))
483
+ continue;
484
+ const exitCode = raw.exitCode;
485
+ commands.push({ label: raw.command.slice(0, 200), outcome: exitCode === 0 ? 'passed' : 'failed', exitCode,
486
+ durationMs: null, required: true, hostRun: true, outputTail: null });
487
+ }
488
+ }
489
+ const review = scope.status !== 'succeeded' ? null : scope.kind === 'implementation' ? parseRuntimeReview(output.review)
490
+ : reviewerStepId === scope.nodePath && scope.kind === 'role-turn' ? parseRuntimeReview(output.structured ?? output) : null;
491
+ if (review)
492
+ scopedReviews.push({ nodePath: scope.nodePath, scopeId: scope.scopeId, attemptId: scope.attemptId, review });
493
+ }
494
+ // Multiple branch verdicts have no defined aggregate confidence score.
495
+ // Keep that absence explicit instead of letting the last branch win.
496
+ const review = scopedReviews.length === 1 ? scopedReviews[0].review : null;
497
+ const findings = scopedReviews.length > 1 ? ['Multiple scoped reviewer verdicts; no aggregate confidence score is defined.'] : [];
498
+ return commands.length || review || findings.length ? { commands, checks: [], findings, review, scopedReviews } : null;
456
499
  }
457
500
  /**
458
501
  * Heal a persisted settle evidence whose units predate the runtime harvest:
@@ -460,7 +503,7 @@ function readRuntimeEvidence(runtimeDir, runId, fsIo) {
460
503
  * now. Returns the healed evidence when anything was added, else null (so the
461
504
  * caller neither rewrites the row nor re-broadcasts). Pure apart from `fsIo`.
462
505
  */
463
- function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}) {
506
+ function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}, definitionStatuses = new Map()) {
464
507
  const io = {
465
508
  readFile: fsIo.readFile ?? ((p) => fs_1.default.readFileSync(p, 'utf8')),
466
509
  listDir: fsIo.listDir ?? ((p) => fs_1.default.readdirSync(p)),
@@ -469,11 +512,14 @@ function healRuntimeEvidence(evidence, pipelineDir, fsIo = {}) {
469
512
  };
470
513
  let changed = false;
471
514
  const units = evidence.units.map((unit) => {
472
- if (unit.runtime !== undefined || !unit.runId)
515
+ if (!unit.runId || unit.runtime)
516
+ return unit;
517
+ const definition = definitionStatuses.get(unit.runId);
518
+ if (definition?.status === 'unavailable' || !definition && unit.runtime !== undefined)
473
519
  return unit;
474
520
  let runtime = null;
475
521
  try {
476
- runtime = readRuntimeEvidence(path_1.default.join(pipelineDir, unit.runId), unit.runId, io);
522
+ runtime = definition ? projectDefinitionRuntimeEvidence(definition, unit.reviewerStepId) : readRuntimeEvidence(path_1.default.join(pipelineDir, unit.runId), unit.runId, io);
477
523
  }
478
524
  catch {
479
525
  runtime = null;