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
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileLoopToDefinition = compileLoopToDefinition;
4
+ const loop_command_catalog_1 = require("./loop-command-catalog");
5
+ const loop_constants_1 = require("./loop-constants");
6
+ const loop_graph_1 = require("./loop-graph");
7
+ const AI_KINDS = new Set(['prompt', 'role-turn', 'decider', 'implementation']);
8
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
9
+ /** No I/O, model call, hash implementation or scheduling belongs in this adapter. */
10
+ function compileLoopToDefinition(graph, launch) {
11
+ (0, loop_graph_1.assertDefinitionGraph)(graph);
12
+ const allGraphs = [graph, ...Object.values(graph.components ?? {})];
13
+ for (const body of allGraphs) {
14
+ (0, loop_graph_1.assertDefinitionGraph)(body);
15
+ const validation = (0, loop_graph_1.validateLoopGraph)(body);
16
+ if (!validation.valid)
17
+ throw new Error(validation.errors.map((error) => `${error.nodeId ?? 'graph'}: ${error.message}`).join('\n'));
18
+ if (body !== graph && body.components && Object.keys(body.components).length) {
19
+ throw new Error('Reusable components belong in the top-level component library; nested pieces reference that library.');
20
+ }
21
+ }
22
+ const writes = allGraphs.some((body) => body.nodes.some((node) => pieceWrites(node, launch.roles)));
23
+ const roleIds = new Set();
24
+ const compileBody = (body) => {
25
+ const start = body.nodes.find((node) => node.type === 'start');
26
+ const entry = body.edges.find((edge) => edge.source === start.id).target;
27
+ if (body.edges.some((edge) => edge.target === start.id))
28
+ throw new Error('A Core workflow cannot route back to the visual Start node.');
29
+ const nodes = {};
30
+ for (const node of [...body.nodes].sort((a, b) => a.id.localeCompare(b.id, 'en'))) {
31
+ if (node.type === 'start')
32
+ continue;
33
+ const kind = node.type === 'end' ? 'end' : node.data.kind;
34
+ const rawParams = node.type === 'end'
35
+ ? {
36
+ outcome: node.data?.outcome ?? 'success',
37
+ ...(typeof node.data?.requiresVerified === 'boolean'
38
+ ? { requiresVerified: node.data.requiresVerified }
39
+ : {}),
40
+ ...(typeof node.data?.exit === 'string' ? { exit: node.data.exit } : {}),
41
+ ...(typeof node.data?.reason === 'string' ? { reason: node.data.reason } : {}),
42
+ }
43
+ : node.data.params;
44
+ const engineInput = object(rawParams.engine) ? rawParams.engine : {};
45
+ const provider = typeof engineInput.provider === 'string' ? engineInput.provider : launch.provider;
46
+ const params = resolveValue(rawParams, launch, provider);
47
+ if (kind === 'prompt') {
48
+ params.engine = {
49
+ provider,
50
+ ...(provider === launch.provider && launch.model ? { model: launch.model } : {}),
51
+ ...(provider === launch.provider && launch.effort ? { effort: launch.effort } : {}),
52
+ ...engineInput,
53
+ };
54
+ if (typeof rawParams.text === 'string' &&
55
+ /^\s*\{\{\s*cmd:[\w:-]+\s*\}\}/.test(rawParams.text) &&
56
+ typeof params.text === 'string') {
57
+ const native = params.text.match(/^\s*(?:\/|\$)([a-z][a-z0-9:_-]*)(?:\s+([\s\S]*))?$/);
58
+ if (native) {
59
+ params.nativeCommand = { id: native[1].replace(/^skill:/, ''), args: native[2] ?? '' };
60
+ delete params.text;
61
+ }
62
+ }
63
+ }
64
+ if (launch.briefing && ['prompt', 'role-turn', 'decider'].includes(kind)) {
65
+ const suffix = '\n\nFrozen launch context:\n' + launch.briefing;
66
+ if (object(params.nativeCommand))
67
+ params.nativeCommand = { ...params.nativeCommand, args: String(params.nativeCommand.args ?? '') + suffix };
68
+ else
69
+ for (const field of ['text', 'prompt', 'goal'])
70
+ if (typeof params[field] === 'string')
71
+ params[field] += suffix;
72
+ }
73
+ if (kind === 'implementation')
74
+ for (const role of ['architect', 'developer', 'reviewer'])
75
+ roleIds.add(role);
76
+ if ((kind === 'role-turn' || kind === 'decider') && typeof params.roleId === 'string')
77
+ roleIds.add(params.roleId);
78
+ if (kind === 'end' && params.outcome === 'success' && params.requiresVerified === undefined)
79
+ params.requiresVerified = writes;
80
+ const ends = Object.fromEntries(body.edges.filter((edge) => edge.source === node.id).map((edge) => [edge.label, edge.target]));
81
+ nodes[node.id] = {
82
+ kind,
83
+ params,
84
+ ends,
85
+ ...(node.data?.retry
86
+ ? {
87
+ retry: {
88
+ ...node.data.retry,
89
+ ...(node.data.retry.retryOn ? { retryOn: [...node.data.retry.retryOn] } : {}),
90
+ },
91
+ }
92
+ : {}),
93
+ ...(typeof node.data?.label === 'string' ? { label: node.data.label } : {}),
94
+ };
95
+ }
96
+ return {
97
+ entry,
98
+ nodes,
99
+ maxTransitions: expandedTransitionBound(body, graph.components ?? {}, launch),
100
+ ...(body.inputs ? { inputs: [...body.inputs] } : {}),
101
+ ...(body.outputs ? { outputs: [...body.outputs] } : {}),
102
+ };
103
+ };
104
+ const body = compileBody(graph);
105
+ const components = Object.fromEntries(Object.entries(graph.components ?? {})
106
+ .sort(([a], [b]) => a.localeCompare(b, 'en'))
107
+ .map(([id, component]) => [id, compileBody(component)]));
108
+ const implementation = [body, ...Object.values(components)].some((component) => Object.values(component.nodes).some((node) => node.kind === 'implementation'));
109
+ const budget = {
110
+ ...(graph.config.maxCostUsd && graph.config.maxCostUsd > 0
111
+ ? { maxCostUsd: graph.config.maxCostUsd }
112
+ : {}),
113
+ ...(graph.config.maxTokens ? { maxTokens: graph.config.maxTokens } : {}),
114
+ ...(graph.config.timeoutMinutes > 0
115
+ ? { maxDurationMs: Math.round(graph.config.timeoutMinutes * 60_000) }
116
+ : {}),
117
+ };
118
+ const id = (launch.id ?? 'desktop-workflow')
119
+ .toLowerCase()
120
+ .replace(/[^a-z0-9-]/g, '-')
121
+ .slice(0, 64);
122
+ if (!/^[a-z0-9]/.test(id))
123
+ throw new Error('Workflow id must start with a letter or number.');
124
+ return {
125
+ schemaVersion: 1,
126
+ id,
127
+ title: launch.title ?? 'Workflow',
128
+ journal: graph.config.journal ?? (implementation ? 'implementation' : 'ledger-only'),
129
+ change: graph.config.change ?? (implementation ? (launch.changeId ? 'existing' : 'new') : 'none'),
130
+ ...body,
131
+ maxTransitions: body.maxTransitions,
132
+ roles: [...roleIds].sort(),
133
+ ...(Object.keys(budget).length ? { budget } : {}),
134
+ ...(graph.config.policies ? { policies: { ...graph.config.policies } } : {}),
135
+ ...(Object.keys(components).length ? { components } : {}),
136
+ delivery: { requiresVerified: writes },
137
+ };
138
+ }
139
+ function pieceWrites(node, roles) {
140
+ if (node.type !== 'core')
141
+ return false;
142
+ const kind = node.data?.kind, params = node.data?.params ?? {};
143
+ if (kind === 'prompt')
144
+ return params.access !== 'read';
145
+ if (kind === 'role-turn')
146
+ return roles?.[String(params.roleId)]?.access !== 'read';
147
+ return ['verify', 'shell', 'openspec-archive', 'implementation'].includes(kind ?? '');
148
+ }
149
+ /** Expand templates once. Text inserted by spec/constants cannot introduce another token. */
150
+ function resolveValue(value, launch, provider) {
151
+ if (typeof value === 'string') {
152
+ return value.replace(/\{\{\{\{|\{\{\s*(?:spec\.\w+|const:[A-Za-z0-9_.-]+|cmd:[\w:-]+)\s*\}\}/g, (token) => {
153
+ if (token === '{{{{')
154
+ return token; // Core resolves the literal escape at execution.
155
+ if (/^\{\{\s*spec\./.test(token))
156
+ return (0, loop_graph_1.interpolateSpec)(token, launch.spec);
157
+ if (/^\{\{\s*const:/.test(token))
158
+ return (0, loop_constants_1.resolveConstants)(token.replace(/\s+/g, ''), launch.constants);
159
+ return (0, loop_command_catalog_1.expandCommands)(token, { provider, ticketIds: launch.spec?.ticketIds, specId: launch.spec?.id });
160
+ });
161
+ }
162
+ if (Array.isArray(value))
163
+ return value.map((item) => resolveValue(item, launch, provider));
164
+ if (object(value))
165
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, resolveValue(child, launch, provider)]));
166
+ return value;
167
+ }
168
+ /** Nested visits share Core's global budget. Dynamic fan-out requires an explicit cap or the engine ceiling. */
169
+ function expandedTransitionBound(body, library, launch, ancestors = []) {
170
+ if (body.config.maxTransitions !== undefined)
171
+ return body.config.maxTransitions;
172
+ const weights = new Map();
173
+ for (const node of body.nodes) {
174
+ const kind = node.data?.kind, params = node.data?.params ?? {};
175
+ if (kind === 'implementation' || (kind === 'map' && object(params.over)))
176
+ return 10_000;
177
+ if (kind !== 'component' && kind !== 'map')
178
+ continue;
179
+ const ref = String(kind === 'component' ? params.ref : params.body);
180
+ if (ancestors.includes(ref))
181
+ throw new Error('Reusable components cannot recursively reference themselves.');
182
+ const child = library[ref];
183
+ if (!child)
184
+ continue; // Core reports the exact missing-reference diagnostic.
185
+ const visits = expandedTransitionBound(child, library, launch, [...ancestors, ref]);
186
+ if (visits === 10_000)
187
+ return 10_000;
188
+ const count = kind === 'map'
189
+ ? params.over === 'tickets'
190
+ ? Math.max(1, launch.spec?.ticketIds?.length ?? 1)
191
+ : launch.repositoryCount
192
+ : 1;
193
+ if (count === undefined)
194
+ return 10_000;
195
+ weights.set(node.id, 1 + visits * count);
196
+ }
197
+ return transitionBound(body, weights);
198
+ }
199
+ /** Count the largest strongly connected region; retries have a separate bounded allowance. */
200
+ function transitionBound(graph, weights = new Map()) {
201
+ const nodes = graph.nodes.filter((node) => node.type !== 'start');
202
+ const adjacency = new Map(nodes.map((node) => [
203
+ node.id,
204
+ graph.edges.filter((edge) => edge.source === node.id).map((edge) => edge.target),
205
+ ]));
206
+ let nextIndex = 0, largestCycle = 0, cyclicNodes = 0;
207
+ const indices = new Map(), low = new Map(), stack = [], active = new Set();
208
+ const visit = (id) => {
209
+ indices.set(id, nextIndex);
210
+ low.set(id, nextIndex++);
211
+ stack.push(id);
212
+ active.add(id);
213
+ for (const target of adjacency.get(id) ?? []) {
214
+ if (!indices.has(target)) {
215
+ visit(target);
216
+ low.set(id, Math.min(low.get(id), low.get(target)));
217
+ }
218
+ else if (active.has(target))
219
+ low.set(id, Math.min(low.get(id), indices.get(target)));
220
+ }
221
+ if (low.get(id) !== indices.get(id))
222
+ return;
223
+ let count = 0, members = 0, current;
224
+ do {
225
+ current = stack.pop();
226
+ active.delete(current);
227
+ count += weights.get(current) ?? 1;
228
+ members += 1;
229
+ } while (current !== id);
230
+ if (members > 1 || adjacency.get(id)?.includes(id)) {
231
+ largestCycle = Math.max(largestCycle, count);
232
+ cyclicNodes += count;
233
+ }
234
+ };
235
+ for (const node of nodes)
236
+ if (!indices.has(node.id))
237
+ visit(node.id);
238
+ const retryNodes = nodes.filter((node) => node.data?.retry ? (node.data.retry.maxAttempts ?? 1) > 1 : AI_KINDS.has(node.data?.kind)).length;
239
+ const bound = Math.max([...nodes].reduce((sum, node) => sum + (weights.get(node.id) ?? 1), 0), graph.config.maxIterations * largestCycle +
240
+ nodes.reduce((sum, node) => sum + (weights.get(node.id) ?? 1), 0) -
241
+ cyclicNodes) +
242
+ 2 * retryNodes;
243
+ if (bound > 10_000)
244
+ throw new Error('Workflow transition budget exceeds Core limit of 10000.');
245
+ return Math.max(1, bound);
246
+ }
@@ -3,7 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.classifyLoopEffect = classifyLoopEffect;
4
4
  exports.loopIsMutating = loopIsMutating;
5
5
  function classifyLoopEffect(graph) {
6
- const writes = graph.nodes.some((n) => n.type === 'ai-step' || n.type === 'shell');
6
+ const writes = graph.nodes.some(n => {
7
+ if (n.type === 'ai-step' || n.type === 'shell')
8
+ return true;
9
+ if (n.type !== 'core')
10
+ return false;
11
+ if (n.data?.kind === 'prompt')
12
+ return n.data.params?.access !== 'read';
13
+ // Role access binds at launch; isolation is conservative before that binding.
14
+ return ['role-turn', 'verify', 'shell', 'openspec-archive', 'implementation', 'component', 'map'].includes(String(n.data?.kind));
15
+ }) || Object.values(graph.components ?? {}).some(component => classifyLoopEffect(component) === 'mutating');
7
16
  return writes ? 'mutating' : 'read-only';
8
17
  }
9
18
  /** Convenience: true when the loop mutates the repo (derived from its nodes). */
@@ -9,6 +9,9 @@ exports.createLoopExecutors = createLoopExecutors;
9
9
  * from coverage; the engine's traversal/decision logic is unit-tested against
10
10
  * fake executors in `loop-run-manager.test.ts`.
11
11
  */
12
+ const loop_definition_1 = require("./loop-definition");
13
+ const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
14
+ const agent_runtime_engines_1 = require("../../agent-runtime/runtime/agent-runtime-engines");
12
15
  const core_completion_1 = require("../../../core-completion");
13
16
  const core_execution_1 = require("../../../core-execution");
14
17
  const agent_runtime_bridge_1 = require("../../agent-runtime/runtime/agent-runtime-bridge");
@@ -33,6 +36,7 @@ const repo_map_1 = require("../../../repo-map");
33
36
  const feature_flags_1 = require("../../../feature-flags");
34
37
  const loop_step_idle_1 = require("./loop-step-idle");
35
38
  const runtime_1 = require("../../../providers/runtime");
39
+ const loop_run_manager_1 = require("./loop-run-manager");
36
40
  const loop_shell_invocation_1 = require("./loop-shell-invocation");
37
41
  // Per-step wall-clock caps so a single hung step can't block the engine's
38
42
  // overall deadline check (which only runs between nodes).
@@ -177,6 +181,62 @@ function createLoopExecutors(opts = {}) {
177
181
  const completionContexts = new Map();
178
182
  const runtimeConfigPath = (cwd) => (0, node_path_1.join)(opts.pluginScope?.().stateRoot ?? cwd, '.specrails', 'agent-runtime.json');
179
183
  return {
184
+ async cancelDefinition({ runId, contextPath, requestId }) {
185
+ const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(contextPath, resolveEnv(), runId);
186
+ await (0, agent_runtime_bridge_1.runAgentRuntimeControl)({ kind: 'cancel', runId, contextPath, requestId, ...host });
187
+ },
188
+ async assertDefinitionSupport() {
189
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
190
+ if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1)
191
+ throw new Error('engine_unsupported: Update Core to run workflow definitions');
192
+ },
193
+ async assertLegacyEngineSupport() {
194
+ // Legacy traversal predates Core: only a Core that positively lacks engine 1 blocks it.
195
+ let runtime;
196
+ try {
197
+ runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
198
+ }
199
+ catch {
200
+ return;
201
+ }
202
+ if (!(0, agent_runtime_engines_1.supportedEngines)(runtime.api).includes(1))
203
+ throw new Error('legacy_engine_unavailable: The installed Core runs only workflow definitions. Convert this loop to Core first.');
204
+ },
205
+ async runDefinition(input) {
206
+ const { request, runId } = input;
207
+ if (input.resume) {
208
+ const contextPath = input.contextPath ?? runtimeContextPath(request.cwd, runId, resolveEnv());
209
+ const host = (0, agent_runtime_bridge_1.readFrozenRuntimeHost)(contextPath, resolveEnv(), runId);
210
+ const result = await (0, agent_runtime_bridge_1.runAgentRuntimeInvocation)({ contextPath, ...host, engineVersion: 2, resume: true, answer: input.answer, approve: input.approve, recover: input.recover, interruptId: input.interruptId, onLine: input.onLine, onRuntimeEvent: input.onRuntimeEvent, onSpawn: input.onSpawn, timeoutMs: input.timeoutMs });
211
+ return { ...result, runtimeStatus: result.runtimeStatus ?? 'failed' };
212
+ }
213
+ const seeded = (0, loop_run_manager_1.seedChangeId)({ ...request, runId });
214
+ const briefing = [request.followUp?.briefing, request.addenda?.briefing].filter(Boolean).join('\n\n');
215
+ const spec = request.spec && briefing ? { ...request.spec, description: [request.spec.description, briefing].filter(Boolean).join('\n\n'), ...(request.spec.tickets ? { tickets: request.spec.tickets.map(ticket => ({ ...ticket, description: [ticket.description, briefing].filter(Boolean).join('\n\n') })) } : {}) } : request.spec;
216
+ const env = { ...programmaticStepEnv(resolveEnv(), request.repoDir, request.executionManifest), SPECRAILS_GIT_AUTO: 'false' };
217
+ const core = (0, core_execution_1.prepareCoreExecution)({ run: { runId, projectId: request.projectId, repositoryId: request.repositoryId, spec, goal: request.spec?.title ?? request.loopName }, cwd: request.cwd, repoDir: request.repoDir, manifest: request.executionManifest, env, sourcePath: request.executionManifest ? undefined : opts.sourcePath?.() });
218
+ const result = await (0, agent_runtime_bridge_1.runAgentRuntimeInvocation)({
219
+ contextPath: core.contextPath, cwd: request.cwd, env: core.env,
220
+ configPath: runtimeConfigPath(request.cwd), engineVersion: 2,
221
+ change: seeded?.id ?? (0, agent_runtime_bridge_1.runtimeChangeName)(runId),
222
+ resume: input.resume, answer: input.answer, approve: input.approve, interruptId: input.interruptId,
223
+ defaultProvider: request.provider, providerOverride: request.runtimeProviderOverride,
224
+ ...(request.graph.config.legacyDeciderRole ? { workflowRoleBindings: {
225
+ [request.graph.config.legacyDeciderRole]: {
226
+ ...(request.deciderEngine ?? { provider: request.provider, model: request.model, effort: request.effort }),
227
+ access: 'read', artifacts: 'none',
228
+ },
229
+ } } : {}),
230
+ ...(!input.resume ? { prepareDefinition: (config) => (0, loop_definition_1.compileLoopToDefinition)(request.graph, {
231
+ id: request.loopId, title: request.loopName, spec: request.spec,
232
+ constants: request.constants ?? {}, provider: request.provider, model: request.model, effort: request.effort,
233
+ roles: { architect: { access: 'read' }, developer: { access: 'write' }, reviewer: { access: 'read' }, ...config.roles }, repositoryCount: request.executionManifest?.repositories.length ?? 1,
234
+ changeId: seeded?.id, briefing,
235
+ }) } : {}),
236
+ onPrepared: input.onPrepared, onLine: input.onLine, onRuntimeEvent: input.onRuntimeEvent, onSpawn: input.onSpawn, timeoutMs: input.timeoutMs,
237
+ });
238
+ return { ...result, runtimeStatus: result.runtimeStatus ?? 'failed' };
239
+ },
180
240
  async readCoreCompletion(runId) {
181
241
  const context = completionContexts.get(runId);
182
242
  completionContexts.delete(runId);
@@ -237,7 +297,7 @@ function createLoopExecutors(opts = {}) {
237
297
  const existingContext = coreRun ? runtimeContextPath(cwd, coreRun.runId, baseEnv) : undefined;
238
298
  const programmatic = coreRun && existingContext && (coreRun.implementation || (coreRun.verificationStep && existsRuntimeRequest(existingContext)));
239
299
  const baseStepEnv = programmatic ? programmaticStepEnv(baseEnv, repoDir, executionManifest) : withProfileEnv(aiStepEnv(baseEnv, repoDir, executionManifest), provider, profileName);
240
- const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
300
+ const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() }) : undefined;
241
301
  const stepEnv = core?.env ?? baseStepEnv;
242
302
  if (coreRun?.implementation && core) {
243
303
  completionContexts.set(coreRun.runId, { cwd, contextPath: core.contextPath, env: stepEnv, runId: coreRun.runId });
@@ -476,7 +536,7 @@ function createLoopExecutors(opts = {}) {
476
536
  if (!adapter.capabilities.persistentStdin)
477
537
  return null;
478
538
  const baseStepEnv = withProfileEnv(aiStepEnv(resolveEnv(), repoDir, executionManifest), provider, profileName);
479
- const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv }) : undefined;
539
+ const core = coreRun ? (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() }) : undefined;
480
540
  const stepEnv = core?.env ?? baseStepEnv;
481
541
  const extraArgs = aiStepExtraArgs(adapter, cwd, repoDir, executionManifest);
482
542
  if (repoDir) {
@@ -532,7 +592,7 @@ function createLoopExecutors(opts = {}) {
532
592
  const baseEnv = resolveEnv();
533
593
  const programmatic = existsRuntimeRequest(runtimeContextPath(cwd, coreRun.runId, baseEnv));
534
594
  const baseStepEnv = programmatic ? programmaticStepEnv(baseEnv, repoDir, executionManifest) : withProfileEnv(aiStepEnv(baseEnv, repoDir, executionManifest), provider, profileName);
535
- const core = (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv });
595
+ const core = (0, core_execution_1.prepareCoreExecution)({ run: coreRun, cwd, repoDir, manifest: executionManifest, env: baseStepEnv, sourcePath: executionManifest ? undefined : opts.sourcePath?.() });
536
596
  if (existsRuntimeRequest(core.contextPath))
537
597
  return (0, core_execution_1.checkCoreCompletion)(core.contextPath, cwd, { ...programmaticStepEnv(resolveEnv(), repoDir, executionManifest), SPECRAILS_EXECUTION_CONTEXT: core.contextPath }, coreRun.runId);
538
598
  const env = (0, runtime_1.buildProviderEnv)((0, providers_1.getAdapter)(provider), { prompt: '', model, reasoning_effort: effort }, core.env);
@@ -1,10 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FACTORY_LOOPS = exports.FACTORY_REVISION_LOOP_ID = void 0;
4
+ exports.factoryLoopsForCapabilities = factoryLoopsForCapabilities;
4
5
  exports.getFactoryLoop = getFactoryLoop;
5
6
  exports.isFactoryLoopId = isFactoryLoopId;
6
7
  exports.factoryLoopMode = factoryLoopMode;
7
8
  exports.factoryLoopForMode = factoryLoopForMode;
9
+ /**
10
+ * Built-in "factory" loops — the app-owned loops that replace the old rail modes
11
+ * (`implement` / Freestyle's `freestyle` mode). These are the code DEFAULTS;
12
+ * `builtin-loops.ts` seeds them as editable loop rows (same ids) and resolves
13
+ * launches to an edited, published row when one exists.
14
+ *
15
+ * Each carries the canonical rail `mode` it maps to. The rail launch routes a
16
+ * factory loop to the matching engine via that mode (QueueManager slash command /
17
+ * Freestyle prompt) — the graph here is the faithful representation used for the
18
+ * gallery preview and as the seed when forked into a custom loop.
19
+ *
20
+ * IDs are namespaced `factory:<mode-ish>` so they never collide with user loop ids
21
+ * (which are random) and the rail resolver can recognise a factory loop.
22
+ */
23
+ const loop_core_factory_1 = require("./loop-core-factory");
8
24
  const loop_templates_1 = require("./loop-templates");
9
25
  const GREEN_GOAL = 'Stop only when the latest verification step reports {{const:VERIFICATION_PASS}} and the history proves every acceptance criterion is implemented across all required tickets and repositories, with the relevant behavioral checks and configured tests/build passing. Green baseline checks, setup, or planning alone do not prove completion.';
10
26
  // Factory loops run the WHOLE architect→developer→reviewer pipeline inside a
@@ -48,17 +64,10 @@ exports.FACTORY_LOOPS = [
48
64
  {
49
65
  id: 'factory:implement',
50
66
  name: 'Implement',
51
- description: 'Run the Core implementation pipeline for the spec. Core owns implementation, review, verification and corrections.',
67
+ description: 'Implements every spec on the rail in one run: plan, implement, review and verify, fixing failures until the checks pass.',
52
68
  mode: 'implement',
53
69
  graph: coreImplementationGraph('implement'),
54
70
  },
55
- {
56
- id: 'factory:batch',
57
- name: 'Batch Implement',
58
- description: 'Run the Core batch implementation pipeline for all the rail\'s tickets. Core owns implementation, review, verification and corrections across the batch.',
59
- mode: 'batch-implement',
60
- graph: coreImplementationGraph('batch'),
61
- },
62
71
  {
63
72
  id: 'factory:freestyle',
64
73
  // User-facing name for the canonical freestyle mode: it hands the spec straight to the model
@@ -73,6 +82,8 @@ exports.FACTORY_LOOPS = [
73
82
  SDD_QUICK_OPENSPEC_FACTORY,
74
83
  ];
75
84
  const FACTORY_ALIASES = new Map([
85
+ // Batch was removed: `implement` already runs every rail ticket in one aggregate run.
86
+ ['factory:batch', exports.FACTORY_LOOPS.find(f => f.id === 'factory:implement')],
76
87
  ['factory:openspec', SDD_QUICK_OPENSPEC_FACTORY],
77
88
  [exports.FACTORY_REVISION_LOOP_ID, SDD_QUICK_OPENSPEC_FACTORY],
78
89
  ]);
@@ -80,8 +91,16 @@ const FACTORY_BY_ID = new Map([
80
91
  ...exports.FACTORY_LOOPS.map((f) => [f.id, f]),
81
92
  ...FACTORY_ALIASES,
82
93
  ]);
83
- function getFactoryLoop(id) {
84
- return FACTORY_BY_ID.get(id);
94
+ function factoryLoopsForCapabilities(capabilities) {
95
+ if (capabilities?.engineV2 !== 1 || capabilities.workflowDefinitions !== 1)
96
+ return exports.FACTORY_LOOPS;
97
+ return exports.FACTORY_LOOPS.map(factory => ({ ...factory, graph: (0, loop_core_factory_1.coreFactoryGraph)(factory.id === 'factory:implement' ? 'implement' : factory.id === 'factory:freestyle' ? 'freestyle' : 'quick-sdd') }));
98
+ }
99
+ function getFactoryLoop(id, capabilities) {
100
+ const legacy = FACTORY_BY_ID.get(id);
101
+ if (!legacy)
102
+ return undefined;
103
+ return factoryLoopsForCapabilities(capabilities).find(factory => factory.id === legacy.id);
85
104
  }
86
105
  /** True for any `factory:*` loop id. */
87
106
  function isFactoryLoopId(id) {
@@ -93,12 +112,13 @@ function factoryLoopMode(id) {
93
112
  }
94
113
  /** Map a legacy rail mode → the matching factory loop (so a legacy rail with a
95
114
  * `mode` and no selected loop resolves to a factory loop on read). */
96
- function factoryLoopForMode(mode) {
115
+ function factoryLoopForMode(mode, capabilities) {
97
116
  if (mode === 'implement')
98
- return FACTORY_BY_ID.get('factory:implement');
99
- if (mode === 'batch-implement')
100
- return FACTORY_BY_ID.get('factory:batch');
117
+ return getFactoryLoop('factory:implement', capabilities);
118
+ // Removed Batch mode: stored `batch-implement` / `batch` rails resolve to Implement.
119
+ if (mode === 'batch-implement' || mode === 'batch')
120
+ return getFactoryLoop('factory:implement', capabilities);
101
121
  if (mode === 'freestyle')
102
- return FACTORY_BY_ID.get('factory:freestyle');
122
+ return getFactoryLoop('factory:freestyle', capabilities);
103
123
  return undefined;
104
124
  }
@@ -10,6 +10,10 @@
10
10
  * trivially unit-testable and reused verbatim by both the store and the engine.
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.CORE_NODE_KINDS = void 0;
14
+ exports.isDefinitionReviewerPath = isDefinitionReviewerPath;
15
+ exports.isDefinitionGraph = isDefinitionGraph;
16
+ exports.assertDefinitionGraph = assertDefinitionGraph;
13
17
  exports.assertLoopShellRepositoryScope = assertLoopShellRepositoryScope;
14
18
  exports.assertLoopFailureRecovery = assertLoopFailureRecovery;
15
19
  exports.emptyLoopGraph = emptyLoopGraph;
@@ -19,6 +23,35 @@ exports.findStartNode = findStartNode;
19
23
  exports.successors = successors;
20
24
  exports.loopNeedsTicket = loopNeedsTicket;
21
25
  exports.interpolateSpec = interpolateSpec;
26
+ exports.CORE_NODE_KINDS = ['prompt', 'role-turn', 'decider', 'condition', 'assign', 'verify', 'shell',
27
+ 'openspec-validate', 'openspec-archive', 'approval', 'question', 'gate', 'map', 'join',
28
+ 'component', 'implementation', 'end'];
29
+ function isDefinitionReviewerPath(graph, value) {
30
+ if (typeof value !== 'string' || !value || value.length > 2048)
31
+ return false;
32
+ const segments = value.split('/');
33
+ if (segments.length > 32)
34
+ return false;
35
+ let body = graph;
36
+ for (let index = 0; index < segments.length; index++) {
37
+ const node = body?.nodes.find(item => item.id === segments[index]);
38
+ if (!node || node.type !== 'core')
39
+ return false;
40
+ if (index === segments.length - 1)
41
+ return node.data?.kind === 'role-turn';
42
+ const ref = node.data?.kind === 'component' ? node.data.params?.ref : node.data?.kind === 'map' ? node.data.params?.body : undefined;
43
+ body = typeof ref === 'string' ? graph.components?.[ref] : undefined;
44
+ }
45
+ return false;
46
+ }
47
+ function isDefinitionGraph(graph) {
48
+ return graph.nodes.some(node => node.type === 'core');
49
+ }
50
+ function assertDefinitionGraph(graph) {
51
+ if (!isDefinitionGraph(graph) || graph.nodes.some(node => !['start', 'core', 'end'].includes(node.type))) {
52
+ throw new Error('A Core workflow must contain Core pieces and cannot mix Desktop execution steps.');
53
+ }
54
+ }
22
55
  /** A named shell target must never silently fall back to another checkout. */
23
56
  function assertLoopShellRepositoryScope(graph, repositoryIds) {
24
57
  for (const node of graph.nodes) {
@@ -61,20 +94,33 @@ function emptyLoopGraph() {
61
94
  * Returns all errors found (not just the first) so the builder can highlight
62
95
  * every problem at once.
63
96
  */
64
- function validateLoopGraph(graph) {
97
+ function validateLoopGraph(graph, catalog) {
65
98
  const errors = [];
66
99
  if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
67
100
  return { valid: false, errors: [{ code: 'INVALID_CONFIG', message: 'A loop graph must contain nodes and edges arrays.' }] };
68
101
  }
69
102
  const nodes = graph.nodes;
70
103
  const edges = graph.edges;
71
- const kinds = new Set(['start', 'ai-step', 'shell', 'decider', 'condition', 'end']);
104
+ const kinds = new Set(['start', 'ai-step', 'shell', 'decider', 'condition', 'core', 'end']);
72
105
  if (nodes.some((node) => !node || typeof node.id !== 'string' || !node.id.trim() || !kinds.has(node.type))) {
73
106
  return { valid: false, errors: [{ code: 'INVALID_NODE', message: 'Every node needs a non-empty id and a supported node type.' }] };
74
107
  }
75
108
  if (edges.some((edge) => !edge || typeof edge.id !== 'string' || !edge.id.trim() || typeof edge.source !== 'string' || typeof edge.target !== 'string')) {
76
109
  return { valid: false, errors: [{ code: 'DANGLING_EDGE', message: 'Every edge needs a non-empty id, source and target.' }] };
77
110
  }
111
+ const definition = isDefinitionGraph(graph);
112
+ if (definition && nodes.some(node => !['start', 'core', 'end'].includes(node.type))) {
113
+ errors.push({ code: 'MIXED_ENGINES', message: 'Core workflows cannot mix Core pieces with Desktop execution steps.' });
114
+ }
115
+ for (const node of nodes) {
116
+ if (definition && (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,119}$/.test(node.id) || ['START', 'END', '__start__', '__end__', 'next'].includes(node.id))) {
117
+ errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'Core node IDs must be safe, non-reserved identifiers.' });
118
+ }
119
+ if (node.type === 'core' && (!exports.CORE_NODE_KINDS.includes(node.data?.kind) || !node.data?.params ||
120
+ typeof node.data.params !== 'object' || Array.isArray(node.data.params))) {
121
+ errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'A Core piece needs a supported kind and an object of parameters.' });
122
+ }
123
+ }
78
124
  for (const [items, code] of [[nodes, 'DUPLICATE_NODE'], [edges, 'DUPLICATE_EDGE']]) {
79
125
  const ids = new Set();
80
126
  for (const item of items) {
@@ -110,11 +156,16 @@ function validateLoopGraph(graph) {
110
156
  }
111
157
  }
112
158
  // ── End presence ────────────────────────────────────────────────────────────
113
- if (!nodes.some((n) => n.type === 'end')) {
159
+ if (!nodes.some((n) => n.type === 'end' || n.type === 'core' && n.data?.kind === 'end')) {
114
160
  errors.push({ code: 'NO_END', message: 'The loop must have at least one End node.' });
115
161
  }
116
162
  // ── Config sanity ─────────────────────────────────────────────────────────────
117
163
  const cfg = graph.config;
164
+ if (cfg?.legacyDeciderRole !== undefined && (!definition ||
165
+ typeof cfg.legacyDeciderRole !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(cfg.legacyDeciderRole) ||
166
+ !nodes.some(node => node.type === 'core' && node.data?.kind === 'decider' && node.data.params?.roleId === cfg.legacyDeciderRole))) {
167
+ errors.push({ code: 'INVALID_CONFIG', message: 'The legacy decision binding must identify a declared Core decider role.' });
168
+ }
118
169
  if (!cfg ||
119
170
  !Number.isInteger(cfg.maxIterations) ||
120
171
  cfg.maxIterations < 1 ||
@@ -122,10 +173,13 @@ function validateLoopGraph(graph) {
122
173
  !Number.isFinite(cfg.timeoutMinutes) ||
123
174
  cfg.timeoutMinutes < 0 ||
124
175
  (cfg.aiStepTimeoutMinutes !== undefined && (!Number.isFinite(cfg.aiStepTimeoutMinutes) || cfg.aiStepTimeoutMinutes < 0)) ||
125
- (cfg.maxCostUsd !== undefined && !Number.isFinite(cfg.maxCostUsd))) {
176
+ (cfg.maxCostUsd !== undefined && !Number.isFinite(cfg.maxCostUsd)) ||
177
+ (cfg.maxTransitions !== undefined && (!Number.isInteger(cfg.maxTransitions) || cfg.maxTransitions < 1 || cfg.maxTransitions > 10000)) ||
178
+ (cfg.maxTokens !== undefined && (!Number.isInteger(cfg.maxTokens) || cfg.maxTokens < 1)) ||
179
+ (cfg.reviewerStepId !== undefined && !isDefinitionReviewerPath(graph, cfg.reviewerStepId))) {
126
180
  errors.push({
127
181
  code: 'INVALID_CONFIG',
128
- message: 'maxIterations must be ≥ 1; timeouts must be finite and ≥ 0 (0 = no timeout); maxCostUsd must be finite.',
182
+ message: 'maxIterations must be ≥ 1; timeouts must be finite and ≥ 0 (0 = no timeout); maxCostUsd must be finite; reviewerStepId must identify a Core role-turn.',
129
183
  });
130
184
  }
131
185
  // ── Dangling edges ────────────────────────────────────────────────────────────
@@ -143,10 +197,26 @@ function validateLoopGraph(graph) {
143
197
  // shell or condition node would silently skip all but its first branch.
144
198
  for (const node of nodes) {
145
199
  const out = edges.filter((edge) => edge.source === node.id);
146
- if (node.type === 'end') {
200
+ if (node.type === 'end' || node.type === 'core' && node.data?.kind === 'end') {
147
201
  if (out.length)
148
202
  errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, message: 'End nodes cannot have outgoing edges.' });
149
203
  }
204
+ else if (node.type === 'core') {
205
+ const descriptor = catalog?.find(piece => piece.kind === node.data?.kind);
206
+ if (catalog && !descriptor)
207
+ errors.push({ code: 'INVALID_NODE', nodeId: node.id, message: 'The installed Core does not advertise this piece.' });
208
+ if (!out.length)
209
+ errors.push({ code: 'DEAD_END', nodeId: node.id, message: 'A Core piece needs labeled outcome edges.' });
210
+ const labels = new Set();
211
+ for (const edge of out) {
212
+ if (typeof edge.label !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/.test(edge.label) || labels.has(edge.label) ||
213
+ (descriptor && !descriptor.outcomes.includes(edge.label))) {
214
+ errors.push({ code: 'INVALID_BRANCH', nodeId: node.id, edgeId: edge.id, message: 'Core outcome edges need unique labels advertised by the piece.' });
215
+ }
216
+ if (edge.label)
217
+ labels.add(edge.label);
218
+ }
219
+ }
150
220
  else if (node.type === 'decider') {
151
221
  const labeled = out.some((edge) => edge.branch !== undefined);
152
222
  const validBranches = labeled
@@ -218,7 +288,8 @@ function successors(graph, nodeId) {
218
288
  .filter((n) => n !== undefined);
219
289
  }
220
290
  /** Whether a loop "needs a ticket/spec" — it references a `{{spec.*}}` token or a
221
- * ticket-consuming command (`{{cmd:implement|batch|freestyle}}`). Ticket-needing
291
+ * ticket-consuming command (`{{cmd:implement|freestyle}}`, plus the legacy
292
+ * `{{cmd:batch}}` alias of implement). Ticket-needing
222
293
  * loops belong on a rail (the rail provides the spec). Ticket-LESS loops are
223
294
  * standalone (repo-wide CI watch, lint, audit…) and run from the Loops page's
224
295
  * "Run" action, NOT a rail — launching one on a rail would just re-run the same
@@ -229,12 +300,17 @@ function loopNeedsTicket(graph) {
229
300
  if (!graph)
230
301
  return false;
231
302
  for (const node of graph.nodes) {
232
- const text = [node.data?.prompt, node.data?.command, node.data?.goal]
303
+ if (node.type === 'core' && node.data?.kind === 'implementation')
304
+ return true;
305
+ const text = [node.data?.prompt, node.data?.command, node.data?.goal,
306
+ node.type === 'core' ? JSON.stringify(node.data?.params) : undefined]
233
307
  .filter((v) => typeof v === 'string')
234
308
  .join('\n');
235
309
  if (SPEC_TOKEN_RE.test(text) || TICKET_CMD_RE.test(text))
236
310
  return true;
237
311
  }
312
+ if (Object.values(graph.components ?? {}).some(component => loopNeedsTicket(component)))
313
+ return true;
238
314
  return false;
239
315
  }
240
316
  function interpolateSpec(text, spec) {