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
@@ -4,17 +4,51 @@ exports.registerLoopsRoutes = registerLoopsRoutes;
4
4
  const ids_1 = require("../../../ids");
5
5
  const feature_flags_1 = require("../../../feature-flags");
6
6
  const loops_store_1 = require("./loops-store");
7
+ const loop_graph_1 = require("./loop-graph");
8
+ const loop_definition_1 = require("./loop-definition");
9
+ const loop_compat_1 = require("./loop-compat");
10
+ const loop_migration_1 = require("./loop-migration");
11
+ const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
7
12
  const loop_templates_1 = require("./loop-templates");
8
13
  const loop_factory_1 = require("./loop-factory");
14
+ const builtin_loops_1 = require("./builtin-loops");
9
15
  const loop_command_catalog_1 = require("./loop-command-catalog");
10
16
  const loop_constants_1 = require("./loop-constants");
11
17
  const loop_preview_1 = require("./loop-preview");
18
+ const BUILTIN_DELETE_MESSAGE = 'Built-in loops cannot be deleted. Use Restore original to reset it, or Duplicate it to keep a separate copy.';
19
+ async function factoryCapabilities() {
20
+ try {
21
+ return (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities;
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
12
27
  function isNonEmptyString(v) {
13
28
  return typeof v === 'string' && v.trim().length > 0;
14
29
  }
15
30
  function registerLoopsRoutes(router, deps) {
16
31
  const { db } = deps;
17
32
  const isRunning = deps.isLoopRunning ?? (() => false);
33
+ const isBuiltin = (id) => Boolean((0, loops_store_1.getLoop)(db, id)?.builtinId);
34
+ // One seeding pass at a time; later calls re-check (Core may have appeared or
35
+ // changed) and are cheap because the Core API probe is cached by fingerprint.
36
+ let seeding = null;
37
+ function seedBuiltins() {
38
+ if (!deps.builtinLoops)
39
+ return Promise.resolve();
40
+ if (!seeding) {
41
+ seeding = (0, builtin_loops_1.ensureBuiltinLoops)(db, deps.builtinLoops)
42
+ .catch((error) => { console.warn('[loops] built-in loop seeding failed:', error instanceof Error ? error.message : error); })
43
+ .finally(() => { seeding = null; });
44
+ }
45
+ return seeding;
46
+ }
47
+ // Startup: the registry (and its desktop DB) exists when routes are registered.
48
+ // The Core API probe is synchronous on first use, so defer it past startup
49
+ // (and the listen call) instead of delaying the server by the probe timeout.
50
+ if ((0, feature_flags_1.isLoopsEnabled)() && deps.builtinLoops)
51
+ setImmediate(() => { void seedBuiltins(); }).unref?.();
18
52
  // Single gate: every loops route 404s when the feature is disabled.
19
53
  function guard(res) {
20
54
  if (!(0, feature_flags_1.isLoopsEnabled)()) {
@@ -24,11 +58,11 @@ function registerLoopsRoutes(router, deps) {
24
58
  return true;
25
59
  }
26
60
  // ── Templates ──────────────────────────────────────────────────────────────
27
- router.get('/loop-templates', (_req, res) => {
61
+ router.get('/loop-templates', async (_req, res) => {
28
62
  if (!guard(res))
29
63
  return;
30
64
  res.json({
31
- templates: loop_templates_1.LOOP_TEMPLATES.map((t) => ({
65
+ templates: (0, loop_templates_1.loopTemplatesForCapabilities)(await factoryCapabilities()).map((t) => ({
32
66
  id: t.id,
33
67
  name: t.name,
34
68
  description: t.description,
@@ -41,9 +75,10 @@ function registerLoopsRoutes(router, deps) {
41
75
  });
42
76
  });
43
77
  // ── List / get ───────────────────────────────────────────────────────────────
44
- router.get('/loops', (_req, res) => {
78
+ router.get('/loops', async (_req, res) => {
45
79
  if (!guard(res))
46
80
  return;
81
+ await seedBuiltins();
47
82
  res.json({ loops: (0, loops_store_1.listLoops)(db) });
48
83
  });
49
84
  // Magic-command catalog for the builder palette (drives the AI Step chips).
@@ -53,36 +88,65 @@ function registerLoopsRoutes(router, deps) {
53
88
  return;
54
89
  res.json({ commands: loop_command_catalog_1.LOOP_COMMANDS.map((c) => ({ name: c.name, label: c.label, description: c.description })) });
55
90
  });
56
- // ── Factory loops (built-in, locked) ─────────────────────────────────────────
57
- // Registered BEFORE `/loops/:id` so "factory" is not captured as an id.
58
- router.get('/loops/factory', (_req, res) => {
91
+ // Catalog belongs to the installed runtime; global authoring has no project state.
92
+ router.get('/loops/catalog', async (_req, res) => {
93
+ if (!guard(res))
94
+ return;
95
+ try {
96
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
97
+ if (runtime.api?.capabilities?.engineV2 !== 1 || !runtime.listWorkflows) {
98
+ res.status(409).json({ error: 'engine_unsupported', message: 'Update Core to author executable workflows.' });
99
+ return;
100
+ }
101
+ res.json(runtime.listWorkflows());
102
+ }
103
+ catch (error) {
104
+ res.status(503).json({ error: 'runtime_catalog_unavailable', message: error instanceof Error ? error.message : 'Core catalog unavailable' });
105
+ }
106
+ });
107
+ // ── Factory loops (built-ins) ────────────────────────────────────────────────
108
+ // Registered BEFORE `/loops/:id` so "factory" is not captured as an id. Kept
109
+ // for companion/mobile/MCP back-compat: each entry reflects its editable row
110
+ // when seeded (name, description and the graph a rail launch would run).
111
+ router.get('/loops/factory', async (_req, res) => {
59
112
  if (!guard(res))
60
113
  return;
114
+ await seedBuiltins();
115
+ const capabilities = await factoryCapabilities();
61
116
  res.json({
62
- factoryLoops: loop_factory_1.FACTORY_LOOPS.map((f) => ({
63
- id: f.id,
64
- name: f.name,
65
- description: f.description,
66
- mode: f.mode,
67
- requiredCapability: f.requiredCapability ?? null,
68
- // Absent means launchable (every pre-existing factory loop is).
69
- launchable: f.launchable !== false,
70
- graph: f.graph,
71
- })),
117
+ factoryLoops: (0, loop_factory_1.factoryLoopsForCapabilities)(capabilities).map((f) => {
118
+ const resolved = (0, builtin_loops_1.resolveBuiltinLoop)(db, f.id, capabilities);
119
+ return {
120
+ id: f.id,
121
+ name: resolved?.name ?? f.name,
122
+ description: resolved?.description ?? f.description,
123
+ mode: f.mode,
124
+ requiredCapability: f.requiredCapability ?? null,
125
+ // Absent means launchable (every pre-existing factory loop is).
126
+ launchable: f.launchable !== false,
127
+ graph: resolved?.graph ?? f.graph,
128
+ // Seeded built-ins are ordinary loops: edit them via PUT /loops/:id.
129
+ editable: Boolean(resolved?.row),
130
+ status: resolved?.row?.status ?? 'published',
131
+ modified: resolved?.row?.builtinModified ?? false,
132
+ };
133
+ }),
72
134
  });
73
135
  });
74
- // Fork a factory loop into a new editable user Draft (leaves the factory intact).
75
- router.post('/loops/factory/:id/fork', (req, res) => {
136
+ // Back-compat alias of `POST /loops/:id/duplicate` for a built-in: creates a
137
+ // separate Draft from the built-in's current content (the built-in is unchanged).
138
+ router.post('/loops/factory/:id/fork', async (req, res) => {
76
139
  if (!guard(res))
77
140
  return;
78
- const f = (0, loop_factory_1.getFactoryLoop)(req.params.id);
79
- if (!f) {
141
+ const resolved = (0, builtin_loops_1.resolveBuiltinLoop)(db, req.params.id, await factoryCapabilities());
142
+ if (!resolved) {
80
143
  res.status(404).json({ error: 'Factory loop not found' });
81
144
  return;
82
145
  }
83
146
  const body = req.body ?? {};
84
- const name = isNonEmptyString(body.name) ? body.name.trim() : `${f.name} (fork)`;
85
- const loop = (0, loops_store_1.createLoop)(db, { id: (0, ids_1.newId)(), name, description: f.description, graph: f.graph });
147
+ const name = isNonEmptyString(body.name) ? body.name.trim() : `${resolved.name} (fork)`;
148
+ const source = resolved.row ?? { description: resolved.description, graph: resolved.graph };
149
+ const loop = (0, loops_store_1.createLoop)(db, { id: (0, ids_1.newId)(), name, description: source.description, graph: source.graph });
86
150
  res.status(201).json({ loop });
87
151
  });
88
152
  // ── Constants library (global, cross-loop) ─────────────────────────────────
@@ -144,7 +208,12 @@ function registerLoopsRoutes(router, deps) {
144
208
  return;
145
209
  }
146
210
  const provider = typeof body.provider === 'string' ? body.provider : 'claude';
147
- res.json((0, loop_preview_1.previewLoop)(body.graph, { provider, constants: (0, loop_constants_1.loadConstantMap)(db) }));
211
+ try {
212
+ res.json((0, loop_preview_1.previewLoop)(body.graph, { provider, constants: (0, loop_constants_1.loadConstantMap)(db) }));
213
+ }
214
+ catch (error) {
215
+ res.status(400).json({ errors: [{ code: 'definition_invalid', message: error instanceof Error ? error.message : 'Invalid workflow' }] });
216
+ }
148
217
  });
149
218
  // Import loops from an export envelope. Duplicate NAMES are skipped (returned
150
219
  // in `skipped`); the rest land as fresh Drafts. Registered BEFORE `/loops/:id`.
@@ -158,6 +227,42 @@ function registerLoopsRoutes(router, deps) {
158
227
  }
159
228
  res.json((0, loops_store_1.importLoops)(db, body.loops, ids_1.newId));
160
229
  });
230
+ // Read-only migration assessment; registered before `/loops/:id`. It never
231
+ // converts or (un)publishes: conversion stays an explicit, reviewable step.
232
+ router.get('/loops/migration', async (_req, res) => {
233
+ if (!guard(res))
234
+ return;
235
+ try {
236
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
237
+ const catalog = runtime.listWorkflows?.();
238
+ if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1 || !catalog || catalog.nodeKindsVersion < 5) {
239
+ res.status(409).json({ error: 'engine_unsupported', message: 'Migration assessment requires Core catalog version 5 or later.' });
240
+ return;
241
+ }
242
+ const constants = (0, loop_constants_1.loadConstantMap)(db);
243
+ res.json((0, loop_migration_1.assessLoopMigration)((0, loops_store_1.listLoops)(db), {
244
+ isRunning,
245
+ validate: (loop, graph) => {
246
+ const definition = (0, loop_definition_1.compileLoopToDefinition)(graph, { id: loop.id, title: loop.name, constants, provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Migration assessment' } });
247
+ const validation = runtime.validateWorkflowDefinition(definition, { structural: true });
248
+ return validation.ok ? { ok: true } : { ok: false, errors: validation.errors.map(error => ({ ...error, nodeId: error.nodeId ?? error.path?.match(/^\/nodes\/([^/]+)/)?.[1] })) };
249
+ },
250
+ }));
251
+ }
252
+ catch (error) {
253
+ res.status(503).json({ error: 'runtime_validation_unavailable', message: error instanceof Error ? error.message : 'Core validation unavailable' });
254
+ }
255
+ });
256
+ router.get('/loops/:id/legacy-graph', (req, res) => {
257
+ if (!guard(res))
258
+ return;
259
+ const backup = (0, loops_store_1.readLegacyLoopGraph)(db, req.params.id);
260
+ if (!backup) {
261
+ res.status(404).json({ error: 'Original legacy graph not found' });
262
+ return;
263
+ }
264
+ res.json(backup);
265
+ });
161
266
  router.get('/loops/:id', (req, res) => {
162
267
  if (!guard(res))
163
268
  return;
@@ -186,10 +291,10 @@ function registerLoopsRoutes(router, deps) {
186
291
  res.status(201).json({ loop });
187
292
  });
188
293
  // ── Instantiate from a template ───────────────────────────────────────────────
189
- router.post('/loops/from-template/:templateId', (req, res) => {
294
+ router.post('/loops/from-template/:templateId', async (req, res) => {
190
295
  if (!guard(res))
191
296
  return;
192
- const template = (0, loop_templates_1.getLoopTemplate)(req.params.templateId);
297
+ const template = (0, loop_templates_1.getLoopTemplate)(req.params.templateId, await factoryCapabilities());
193
298
  if (!template) {
194
299
  res.status(404).json({ error: 'Template not found' });
195
300
  return;
@@ -213,7 +318,10 @@ function registerLoopsRoutes(router, deps) {
213
318
  res.status(404).json({ error: 'Loop not found' });
214
319
  return;
215
320
  }
216
- if (isRunning(id)) {
321
+ // Runs freeze a clone of their graph at launch, and built-in launches use
322
+ // the last Published snapshot while an edit is Draft, so a running built-in
323
+ // stays editable. Other loops keep the conservative running guard.
324
+ if (!isBuiltin(id) && isRunning(id)) {
217
325
  res.status(409).json({ error: 'Loop is running; stop the run before editing' });
218
326
  return;
219
327
  }
@@ -234,7 +342,59 @@ function registerLoopsRoutes(router, deps) {
234
342
  res.json({ loop });
235
343
  });
236
344
  // ── Publish / unpublish ───────────────────────────────────────────────────────
237
- router.post('/loops/:id/publish', (req, res) => {
345
+ router.post('/loops/:id/convert', async (req, res) => {
346
+ if (!guard(res))
347
+ return;
348
+ const id = req.params.id, current = (0, loops_store_1.getLoop)(db, id);
349
+ if (!current) {
350
+ res.status(404).json({ error: 'Loop not found' });
351
+ return;
352
+ }
353
+ if (isRunning(id)) {
354
+ res.status(409).json({ error: 'loop_running' });
355
+ return;
356
+ }
357
+ if ((0, loop_graph_1.isDefinitionGraph)(current.graph) && (0, loops_store_1.readLegacyLoopGraph)(db, id)) {
358
+ res.json({ loop: current, nodeIds: {}, issues: [], alreadyConverted: true });
359
+ return;
360
+ }
361
+ try {
362
+ const converted = (0, loop_compat_1.convertLegacyLoop)(current.graph, {
363
+ ...(typeof req.body?.repositoryId === 'string' && req.body.repositoryId.trim() ? { repositoryId: req.body.repositoryId.trim() } : {}),
364
+ });
365
+ if (!converted.ok) {
366
+ res.status(422).json({ error: 'conversion_invalid', errors: converted.issues });
367
+ return;
368
+ }
369
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
370
+ const catalog = runtime.listWorkflows?.();
371
+ if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1 ||
372
+ !catalog || catalog.nodeKindsVersion < 5) {
373
+ res.status(409).json({ error: 'engine_unsupported', message: 'Legacy conversion requires Core catalog version 5 or later.' });
374
+ return;
375
+ }
376
+ const definition = (0, loop_definition_1.compileLoopToDefinition)(converted.graph, { id: current.id, title: current.name, constants: (0, loop_constants_1.loadConstantMap)(db), provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Conversion preview' } });
377
+ const validation = runtime.validateWorkflowDefinition(definition, { structural: true });
378
+ if (!validation.ok) {
379
+ res.status(422).json({ error: 'conversion_invalid', errors: validation.errors });
380
+ return;
381
+ }
382
+ if (isRunning(id)) {
383
+ res.status(409).json({ error: 'loop_running' });
384
+ return;
385
+ }
386
+ const loop = (0, loops_store_1.updateLoop)(db, id, { graph: converted.graph }, current);
387
+ res.json({ loop, nodeIds: converted.nodeIds, issues: converted.issues });
388
+ }
389
+ catch (error) {
390
+ if (error instanceof loops_store_1.LoopPublicationConflict) {
391
+ res.status(409).json({ error: 'loop_changed' });
392
+ return;
393
+ }
394
+ res.status(400).json({ error: 'conversion_invalid', message: error instanceof Error ? error.message : 'Conversion failed' });
395
+ }
396
+ });
397
+ router.post('/loops/:id/publish', async (req, res) => {
238
398
  if (!guard(res))
239
399
  return;
240
400
  const id = req.params.id;
@@ -243,15 +403,40 @@ function registerLoopsRoutes(router, deps) {
243
403
  return;
244
404
  }
245
405
  try {
246
- const loop = (0, loops_store_1.publishLoop)(db, id);
406
+ const current = (0, loops_store_1.getLoop)(db, id);
407
+ if ((0, loop_graph_1.isDefinitionGraph)(current.graph)) {
408
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
409
+ if (runtime.api?.capabilities?.engineV2 !== 1 || runtime.api?.capabilities?.workflowDefinitions !== 1) {
410
+ res.status(409).json({ error: 'engine_unsupported', message: 'Update Core to publish executable workflows.' });
411
+ return;
412
+ }
413
+ let draft;
414
+ try {
415
+ draft = (0, loop_definition_1.compileLoopToDefinition)(current.graph, { id: current.id, title: current.name, constants: (0, loop_constants_1.loadConstantMap)(db), provider: 'claude', spec: { id: 1, title: 'Sample spec', description: 'Publication preview' } });
416
+ }
417
+ catch (error) {
418
+ res.status(400).json({ errors: [{ code: 'definition_invalid', message: error instanceof Error ? error.message : 'Invalid workflow' }] });
419
+ return;
420
+ }
421
+ const validation = runtime.validateWorkflowDefinition(draft, { structural: true });
422
+ if (!validation.ok) {
423
+ res.status(400).json({ errors: validation.errors.map(error => ({ ...error, nodeId: error.nodeId ?? error.path?.match(/^\/nodes\/([^/]+)/)?.[1] })) });
424
+ return;
425
+ }
426
+ }
427
+ const loop = (0, loops_store_1.publishLoop)(db, id, current);
247
428
  res.json({ loop });
248
429
  }
249
430
  catch (err) {
431
+ if (err instanceof loops_store_1.LoopPublicationConflict) {
432
+ res.status(409).json({ error: err.message, code: 'loop_changed' });
433
+ return;
434
+ }
250
435
  if (err instanceof loops_store_1.LoopValidationError) {
251
436
  res.status(422).json({ error: 'Loop graph is invalid', errors: err.errors });
252
437
  return;
253
438
  }
254
- throw err;
439
+ res.status(503).json({ error: 'runtime_validation_unavailable', message: err instanceof Error ? err.message : 'Core validation unavailable' });
255
440
  }
256
441
  });
257
442
  router.post('/loops/:id/unpublish', (req, res) => {
@@ -262,12 +447,40 @@ function registerLoopsRoutes(router, deps) {
262
447
  res.status(404).json({ error: 'Loop not found' });
263
448
  return;
264
449
  }
450
+ if (isBuiltin(id)) {
451
+ res.status(409).json({ error: 'builtin_loop', message: 'Built-in loops stay available on rails. Edit and publish it, or use Restore original.' });
452
+ return;
453
+ }
265
454
  if (isRunning(id)) {
266
455
  res.status(409).json({ error: 'Loop is running; stop the run first' });
267
456
  return;
268
457
  }
269
458
  res.json({ loop: (0, loops_store_1.unpublishLoop)(db, id) });
270
459
  });
460
+ // ── Restore a built-in to its current default ─────────────────────────────────
461
+ router.post('/loops/:id/restore-builtin', async (req, res) => {
462
+ if (!guard(res))
463
+ return;
464
+ const id = req.params.id;
465
+ if (!(0, loops_store_1.getLoop)(db, id)) {
466
+ res.status(404).json({ error: 'Loop not found' });
467
+ return;
468
+ }
469
+ if (!isBuiltin(id)) {
470
+ res.status(400).json({ error: 'not_builtin', message: 'Only built-in loops can be restored.' });
471
+ return;
472
+ }
473
+ if (isRunning(id)) {
474
+ res.status(409).json({ error: 'loop_running', message: 'Loop is running; stop the run before restoring it' });
475
+ return;
476
+ }
477
+ const loop = await (0, builtin_loops_1.restoreBuiltin)(db, id, deps.builtinLoops);
478
+ if (!loop) {
479
+ res.status(404).json({ error: 'Built-in loop default not found' });
480
+ return;
481
+ }
482
+ res.json({ loop });
483
+ });
271
484
  // ── Duplicate ─────────────────────────────────────────────────────────────────
272
485
  router.post('/loops/:id/duplicate', (req, res) => {
273
486
  if (!guard(res))
@@ -292,6 +505,10 @@ function registerLoopsRoutes(router, deps) {
292
505
  res.status(404).json({ error: 'Loop not found' });
293
506
  return;
294
507
  }
508
+ if (isBuiltin(id)) {
509
+ res.status(409).json({ error: 'builtin_loop', message: BUILTIN_DELETE_MESSAGE });
510
+ return;
511
+ }
295
512
  if (isRunning(id)) {
296
513
  res.status(409).json({ error: 'Loop is running; stop the run before deleting' });
297
514
  return;
@@ -1,15 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LoopValidationError = void 0;
3
+ exports.LoopPublicationConflict = exports.LoopValidationError = void 0;
4
+ exports.builtinContentHash = builtinContentHash;
4
5
  exports.listLoops = listLoops;
5
6
  exports.getLoop = getLoop;
6
7
  exports.createLoop = createLoop;
7
8
  exports.updateLoop = updateLoop;
9
+ exports.readLegacyLoopGraph = readLegacyLoopGraph;
8
10
  exports.publishLoop = publishLoop;
9
11
  exports.unpublishLoop = unpublishLoop;
10
12
  exports.duplicateLoop = duplicateLoop;
11
13
  exports.deleteLoop = deleteLoop;
12
14
  exports.importLoops = importLoops;
15
+ exports.seedBuiltinLoops = seedBuiltinLoops;
16
+ exports.restoreBuiltinLoop = restoreBuiltinLoop;
17
+ exports.getBuiltinLoop = getBuiltinLoop;
18
+ exports.readPublishedLoopGraph = readPublishedLoopGraph;
19
+ /**
20
+ * Loops store — CRUD + Draft/Published lifecycle over the app-level `loops`
21
+ * table in `desktop.sqlite` (see desktop-db.ts migration 14).
22
+ *
23
+ * Definitions are GLOBAL (cross-project): a loop is a reusable recipe consumed
24
+ * from any project's rail. The third lifecycle state "Running" is NOT stored
25
+ * here — it is derived from active per-project `loop_runs` and enforced at the
26
+ * router/service layer (which can see the project registry); this store stays
27
+ * focused on the persistent definition + the Draft⇄Published transition.
28
+ */
29
+ const crypto_1 = require("crypto");
13
30
  const loop_graph_1 = require("./loop-graph");
14
31
  /** Thrown by {@link publishLoop} when the graph fails validation. The router
15
32
  * maps this to a 422 carrying the per-node errors. */
@@ -22,6 +39,11 @@ class LoopValidationError extends Error {
22
39
  }
23
40
  }
24
41
  exports.LoopValidationError = LoopValidationError;
42
+ /** Validation cannot authorize publishing a different edit made while Core loaded. */
43
+ class LoopPublicationConflict extends Error {
44
+ constructor() { super('Loop changed during validation; reload and publish again'); this.name = 'LoopPublicationConflict'; }
45
+ }
46
+ exports.LoopPublicationConflict = LoopPublicationConflict;
25
47
  function mapRow(raw) {
26
48
  if (!raw)
27
49
  return undefined;
@@ -40,13 +62,25 @@ function mapRow(raw) {
40
62
  graph,
41
63
  createdAt: raw.created_at,
42
64
  updatedAt: raw.updated_at,
65
+ ...(raw.has_legacy_graph ? { hasLegacyGraph: true } : {}),
66
+ ...(raw.builtin_id ? {
67
+ builtinId: raw.builtin_id,
68
+ builtinModified: builtinContentHash({ name: raw.name, description: raw.description, graph }) !== raw.builtin_default_hash,
69
+ } : {}),
43
70
  };
44
71
  }
72
+ /** Content identity of a built-in row (name, description and graph). The
73
+ * stored hash is the default last seeded, so equality means "unedited". */
74
+ function builtinContentHash(content) {
75
+ return (0, crypto_1.createHash)('sha256').update(JSON.stringify([content.name, content.description, content.graph])).digest('hex');
76
+ }
77
+ // Backups can be large; load their contents only for an explicit export.
78
+ const loopColumns = 'id, name, description, status, graph, created_at, updated_at, (graph_legacy IS NOT NULL) AS has_legacy_graph, builtin_id, builtin_default_hash';
45
79
  function listLoops(db) {
46
- return db.prepare('SELECT * FROM loops ORDER BY updated_at DESC, created_at DESC').all().map((r) => mapRow(r));
80
+ return db.prepare(`SELECT ${loopColumns} FROM loops ORDER BY updated_at DESC, created_at DESC`).all().map((r) => mapRow(r));
47
81
  }
48
82
  function getLoop(db, id) {
49
- return mapRow(db.prepare('SELECT * FROM loops WHERE id = ?').get(id));
83
+ return mapRow(db.prepare(`SELECT ${loopColumns} FROM loops WHERE id = ?`).get(id));
50
84
  }
51
85
  /** Create a new loop. Always starts as a Draft. `graph` defaults to an empty
52
86
  * (structurally-invalid-to-publish) canvas the user fills in the builder. */
@@ -60,30 +94,56 @@ function createLoop(db, input) {
60
94
  * Published loop un-publishes it — it must be re-validated and re-published
61
95
  * before it is offered in the rail picker again).
62
96
  */
63
- function updateLoop(db, id, patch) {
64
- const existing = getLoop(db, id);
65
- if (!existing)
97
+ function updateLoop(db, id, patch, expected) {
98
+ return db.transaction(() => {
99
+ const existing = getLoop(db, id);
100
+ if (expected && (!existing || JSON.stringify([existing.name, existing.description, existing.graph, existing.status]) !==
101
+ JSON.stringify([expected.name, expected.description, expected.graph, expected.status])))
102
+ throw new LoopPublicationConflict();
103
+ if (!existing)
104
+ return undefined;
105
+ const name = patch.name ?? existing.name;
106
+ const description = patch.description !== undefined ? patch.description : existing.description;
107
+ const graph = patch.graph ?? existing.graph;
108
+ const converting = patch.graph !== undefined && Array.isArray(graph.nodes) && graph.nodes.some(node => node?.type === 'core') &&
109
+ Array.isArray(existing.graph.nodes) && existing.graph.nodes.some(node => ['ai-step', 'shell', 'decider', 'condition'].includes(node?.type));
110
+ // Both expressions read the old row. A failed save cannot leave a backup
111
+ // that claims a conversion happened, and retries cannot replace its bytes.
112
+ db.prepare(`UPDATE loops SET name = ?, description = ?,
113
+ graph_legacy = CASE WHEN ? THEN COALESCE(graph_legacy, graph) ELSE graph_legacy END,
114
+ graph_legacy_saved_at = CASE WHEN ? THEN COALESCE(graph_legacy_saved_at, datetime('now')) ELSE graph_legacy_saved_at END,
115
+ graph = ?, status = 'draft', updated_at = datetime('now') WHERE id = ?`)
116
+ .run(name, description, Number(converting), Number(converting), JSON.stringify(graph), id);
117
+ return getLoop(db, id);
118
+ }).immediate();
119
+ }
120
+ /** Read-only original export; does not restore, publish or execute a graph. */
121
+ function readLegacyLoopGraph(db, id) {
122
+ const row = db.prepare('SELECT graph_legacy, graph_legacy_saved_at FROM loops WHERE id = ?').get(id);
123
+ if (!row?.graph_legacy || !row.graph_legacy_saved_at)
66
124
  return undefined;
67
- const name = patch.name ?? existing.name;
68
- const description = patch.description !== undefined ? patch.description : existing.description;
69
- const graph = patch.graph ?? existing.graph;
70
- db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'draft', updated_at = datetime('now') WHERE id = ?`).run(name, description, JSON.stringify(graph), id);
71
- return getLoop(db, id);
125
+ return { graph: JSON.parse(row.graph_legacy), savedAt: row.graph_legacy_saved_at };
72
126
  }
73
127
  /**
74
128
  * Validate the graph and, if valid, mark the loop Published (so it becomes
75
129
  * selectable in the rail picker). Throws {@link LoopValidationError} otherwise,
76
130
  * leaving the loop in Draft.
77
131
  */
78
- function publishLoop(db, id) {
79
- const existing = getLoop(db, id);
80
- if (!existing)
81
- return undefined;
82
- const result = (0, loop_graph_1.validateLoopGraph)(existing.graph);
83
- if (!result.valid)
84
- throw new LoopValidationError(result.errors);
85
- db.prepare(`UPDATE loops SET status = 'published', updated_at = datetime('now') WHERE id = ?`).run(id);
86
- return getLoop(db, id);
132
+ function publishLoop(db, id, validated) {
133
+ return db.transaction(() => {
134
+ const existing = getLoop(db, id);
135
+ if (validated && (!existing || JSON.stringify([existing.name, existing.description, existing.graph, existing.status]) !==
136
+ JSON.stringify([validated.name, validated.description, validated.graph, validated.status])))
137
+ throw new LoopPublicationConflict();
138
+ if (!existing)
139
+ return undefined;
140
+ const result = (0, loop_graph_1.validateLoopGraph)(existing.graph);
141
+ if (!result.valid)
142
+ throw new LoopValidationError(result.errors);
143
+ // The snapshot is what rails launch while a later edit is still Draft.
144
+ db.prepare(`UPDATE loops SET status = 'published', published_graph = graph, updated_at = datetime('now') WHERE id = ?`).run(id);
145
+ return getLoop(db, id);
146
+ }).immediate();
87
147
  }
88
148
  /** Return a Published loop to Draft without changing its content. */
89
149
  function unpublishLoop(db, id) {
@@ -135,3 +195,63 @@ function importLoops(db, items, mintId) {
135
195
  }
136
196
  return { imported, skipped };
137
197
  }
198
+ /**
199
+ * Ensure one published row per built-in default. Missing rows are inserted
200
+ * (`INSERT OR IGNORE`, so concurrent seeders converge on one row). With
201
+ * `refresh`, a row whose content still equals the default it was seeded with
202
+ * (unedited) is moved to the new default when that default changed — user edits
203
+ * are never overwritten. Rows for built-ins absent from `defaults` are kept.
204
+ */
205
+ function seedBuiltinLoops(db, defaults, options) {
206
+ const result = { inserted: [], refreshed: [] };
207
+ db.transaction(() => {
208
+ for (const builtin of defaults) {
209
+ const hash = builtinContentHash({ name: builtin.name, description: builtin.description, graph: builtin.graph });
210
+ const graph = JSON.stringify(builtin.graph);
211
+ const inserted = db.prepare(`INSERT OR IGNORE INTO loops
212
+ (id, name, description, status, graph, builtin_id, builtin_default_hash, published_graph)
213
+ VALUES (?, ?, ?, 'published', ?, ?, ?, ?)`)
214
+ .run(builtin.id, builtin.name, builtin.description, graph, builtin.id, hash, graph);
215
+ if (inserted.changes > 0) {
216
+ result.inserted.push(builtin.id);
217
+ continue;
218
+ }
219
+ if (!options.refresh)
220
+ continue;
221
+ const row = db.prepare(`SELECT ${loopColumns} FROM loops WHERE builtin_id = ?`).get(builtin.id);
222
+ const current = mapRow(row);
223
+ if (!row || !current || current.builtinModified || row.builtin_default_hash === hash)
224
+ continue;
225
+ db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'published', published_graph = ?,
226
+ builtin_default_hash = ?, updated_at = datetime('now') WHERE builtin_id = ?`)
227
+ .run(builtin.name, builtin.description, graph, graph, hash, builtin.id);
228
+ result.refreshed.push(builtin.id);
229
+ }
230
+ }).immediate();
231
+ return result;
232
+ }
233
+ /** Reset a built-in row to `builtin` (its current default), Published. */
234
+ function restoreBuiltinLoop(db, builtin) {
235
+ const hash = builtinContentHash({ name: builtin.name, description: builtin.description, graph: builtin.graph });
236
+ const graph = JSON.stringify(builtin.graph);
237
+ const updated = db.prepare(`UPDATE loops SET name = ?, description = ?, graph = ?, status = 'published', published_graph = ?,
238
+ builtin_default_hash = ?, updated_at = datetime('now') WHERE builtin_id = ?`)
239
+ .run(builtin.name, builtin.description, graph, graph, hash, builtin.id);
240
+ return updated.changes > 0 ? getLoop(db, builtin.id) : undefined;
241
+ }
242
+ /** The built-in row for a canonical factory id, when seeded. */
243
+ function getBuiltinLoop(db, builtinId) {
244
+ return mapRow(db.prepare(`SELECT ${loopColumns} FROM loops WHERE builtin_id = ?`).get(builtinId));
245
+ }
246
+ /** Last published graph snapshot (null when the loop was never published). */
247
+ function readPublishedLoopGraph(db, id) {
248
+ const row = db.prepare('SELECT published_graph FROM loops WHERE id = ?').get(id);
249
+ if (!row?.published_graph)
250
+ return undefined;
251
+ try {
252
+ return JSON.parse(row.published_graph);
253
+ }
254
+ catch {
255
+ return undefined;
256
+ }
257
+ }