specrails-desktop 2.29.0 → 2.29.2

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 (545) hide show
  1. package/client/dist/assets/ActivityFeedPage-ClpD5CFj.js +1 -0
  2. package/client/dist/assets/AgentBrowserCapture-CSPs9Ofc.js +1 -0
  3. package/client/dist/assets/AgentModeCodePane-DwcyklA5.js +2 -0
  4. package/client/dist/assets/AgentModeJobsPane-C69bqlMa.js +1 -0
  5. package/client/dist/assets/AgentsPage-pRiRr9-W.js +86 -0
  6. package/client/dist/assets/AnalyticsPage-C14exKsC.js +1 -0
  7. package/client/dist/assets/BarChart-nBOMNfiy.js +33 -0
  8. package/client/dist/assets/CodePage-CK76JKm9.js +2 -0
  9. package/client/dist/assets/DesktopAnalyticsPage-Cnv0f3XQ.js +1 -0
  10. package/client/dist/assets/DocsDialog-CHUm_Ym0.js +11 -0
  11. package/client/dist/assets/DocsPage-COI-oDok.js +11 -0
  12. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +1 -0
  13. package/client/dist/assets/IntegrationsPage-Bs00_tl4.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-CypM7R1F.js +19 -0
  15. package/client/dist/assets/JobDetailModal-B_W0H9dw.js +1 -0
  16. package/client/dist/assets/JobDetailPage-CeeXOoKS.js +1 -0
  17. package/client/dist/assets/JobsPage-CwhJG9jw.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-C51t1WOM.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-Y2HgtIJu.js +1 -0
  20. package/client/dist/assets/LoopsPage-Bvn1Kw5V.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-DiFvZDPW.js +1 -0
  22. package/client/dist/assets/PluginsPage-BGPRJpdf.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-D_GTyGLc.js +1 -0
  24. package/client/dist/assets/TemplatePreviewModal-szwBhd7h.js +1 -0
  25. package/client/dist/assets/TicketDetailModal-9UEyK974.js +14 -0
  26. package/client/dist/assets/Trans-B_vKZ-Hb.js +1 -0
  27. package/client/dist/assets/activity-DiyjsgQV.js +1 -0
  28. package/client/dist/assets/addon-image-BRmmRHgQ.js +1 -0
  29. package/client/dist/assets/addon-ligatures-qIZUPvRV.js +2 -0
  30. package/client/dist/assets/{addon-webgl-DMbGeI_u.js → addon-webgl-C2d7lriQ.js} +3 -3
  31. package/client/dist/assets/addspec-Rw8uVBC1.js +1 -0
  32. package/client/dist/assets/agent-4rmXoo_p.js +1 -0
  33. package/client/dist/assets/agent-BxZuNGih.js +1 -0
  34. package/client/dist/assets/agent-C3uXOYv_.js +1 -0
  35. package/client/dist/assets/agent-CA8Y4wbw.js +1 -0
  36. package/client/dist/assets/agent-CASKrhG12.js +1 -0
  37. package/client/dist/assets/agent-Cv-jud5n2.js +1 -0
  38. package/client/dist/assets/agent-DitUxYh2.js +1 -0
  39. package/client/dist/assets/agent-Doow4X7o.js +1 -0
  40. package/client/dist/assets/agents-DhG7mg6-.js +1 -0
  41. package/client/dist/assets/agentstudio-BIUe1Im2.js +1 -0
  42. package/client/dist/assets/aiedit-iIjz2_Qk.js +1 -0
  43. package/client/dist/assets/analytics-DZ8Aoq3I.js +1 -0
  44. package/client/dist/assets/api-B2M7CtDw.js +1 -0
  45. package/client/dist/assets/arrow-left-CpIR4j7G.js +1 -0
  46. package/client/dist/assets/arrow-up-right-9DoMn7fD.js +1 -0
  47. package/client/dist/assets/attachments-B53PG733.js +1 -0
  48. package/client/dist/assets/brain-DI9lHkKY.js +1 -0
  49. package/client/dist/assets/browser-CXUcnTMI.js +1 -0
  50. package/client/dist/assets/button-BUIIBQPT.js +1 -0
  51. package/client/dist/assets/chat-BGD7MXAy.js +1 -0
  52. package/client/dist/assets/chevron-right-CViMxucY.js +1 -0
  53. package/client/dist/assets/chunk-KS7C4IRE-Cx5sqopX.js +3 -0
  54. package/client/dist/assets/circle-x-CMYsN4ZH.js +1 -0
  55. package/client/dist/assets/code-BDXEM4ml.js +1 -0
  56. package/client/dist/assets/commands-DyUZQ1FI.js +1 -0
  57. package/client/dist/assets/common-CD1QHriA2.js +1 -0
  58. package/client/dist/assets/common-CwwPaRxx.js +1 -0
  59. package/client/dist/assets/common-DVQuhs-m.js +1 -0
  60. package/client/dist/assets/common-Dg2jaX7d.js +1 -0
  61. package/client/dist/assets/common-DuBwj6tK.js +1 -0
  62. package/client/dist/assets/common-MRj9MHcc.js +1 -0
  63. package/client/dist/assets/common-O4ZpnMLp.js +1 -0
  64. package/client/dist/assets/common-fyeF0v7C2.js +1 -0
  65. package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +1 -0
  66. package/client/dist/assets/{css.worker-Wv5dxAWO.js → css.worker-yKcq13f2.js} +13 -13
  67. package/client/dist/assets/{cssMode-DBHbkBvI.js → cssMode-BM4z9OX0.js} +1 -1
  68. package/client/dist/assets/dashboard-B_NkSHYm.js +1 -0
  69. package/client/dist/assets/dashboard-ClBb_3pq.js +1 -0
  70. package/client/dist/assets/dashboard-CpCmJvMB.js +1 -0
  71. package/client/dist/assets/dashboard-Czohu35O2.js +1 -0
  72. package/client/dist/assets/dashboard-DcjbD0UK.js +1 -0
  73. package/client/dist/assets/dashboard-Dvx5F3we.js +1 -0
  74. package/client/dist/assets/dashboard-R1bOPjBh2.js +1 -0
  75. package/client/dist/assets/dashboard-owcDucDi.js +1 -0
  76. package/client/dist/assets/dialog-CzIekCw0.js +45 -0
  77. package/client/dist/assets/dist-Cez5CRkX.js +1 -0
  78. package/client/dist/assets/dist-bVqfYmby.js +1 -0
  79. package/client/dist/assets/{dist-js-UjXbEWhN.js → dist-js-CcBCbb32.js} +1 -1
  80. package/client/dist/assets/{dist-js-BTAcxhPb.js → dist-js-VwztRkGj.js} +1 -1
  81. package/client/dist/assets/download-CFHyyHcw.js +1 -0
  82. package/client/dist/assets/editor.api2-4ndOupAA.js +872 -0
  83. package/client/dist/assets/{editor.main-C9J2ikJK.js → editor.main-DxrmGfoq.js} +2 -2
  84. package/client/dist/assets/editor.worker-tytyn86m.js +26 -0
  85. package/client/dist/assets/explore-DgBACn39.js +1 -0
  86. package/client/dist/assets/external-link-BcU5EWZp.js +1 -0
  87. package/client/dist/assets/file-text-CFT-xXKN.js +1 -0
  88. package/client/dist/assets/formatDistanceToNow-ByHIauTQ.js +1 -0
  89. package/client/dist/assets/{freemarker2-J5iIIIK-.js → freemarker2-CELlTDJ2.js} +1 -1
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-BxHAksaU.js +1 -0
  91. package/client/dist/assets/git-branch-BRM0F5GN.js +1 -0
  92. package/client/dist/assets/{handlebars-DSw1l_tw.js → handlebars-NEjHJ7o_.js} +1 -1
  93. package/client/dist/assets/{html-h2-wE1Lj.js → html-CA10zN5A.js} +1 -1
  94. package/client/dist/assets/{html.worker-CQP8QQsS.js → html.worker-DSJ9zmd7.js} +12 -12
  95. package/client/dist/assets/{htmlMode-Cc_ij7lW.js → htmlMode-89rPYiyq.js} +1 -1
  96. package/client/dist/assets/i18n-CgaRxpII.js +2 -0
  97. package/client/dist/assets/index-BflYQpWm.css +2 -0
  98. package/client/dist/assets/index-C1_-BTXy.js +72 -0
  99. package/client/dist/assets/integrations-mB-7zoyT.js +1 -0
  100. package/client/dist/assets/{javascript-C1q3xv6N.js → javascript-B1gDmwzS.js} +1 -1
  101. package/client/dist/assets/jira-CmKWERE0.js +1 -0
  102. package/client/dist/assets/jira-api-BgFIr8eD.js +1 -0
  103. package/client/dist/assets/job-time-BCL7wee9.js +1 -0
  104. package/client/dist/assets/jobs-B-sDHEay2.js +1 -0
  105. package/client/dist/assets/jobs-BCtGfgUA.js +1 -0
  106. package/client/dist/assets/jobs-BHR_EnDI.js +1 -0
  107. package/client/dist/assets/jobs-BMbZJjJJ.js +1 -0
  108. package/client/dist/assets/jobs-CVFD8rar.js +1 -0
  109. package/client/dist/assets/jobs-DoSaFzAa.js +1 -0
  110. package/client/dist/assets/jobs-DzSIR8V62.js +1 -0
  111. package/client/dist/assets/jobs-lk1_Dq1U.js +1 -0
  112. package/client/dist/assets/json.worker-Oa9UrkKE.js +58 -0
  113. package/client/dist/assets/{jsonMode-CeaCdEhr.js → jsonMode-xHLMfzhF.js} +1 -1
  114. package/client/dist/assets/jsx-runtime-zu2_FqZY.js +1 -0
  115. package/client/dist/assets/lib-C7Mhm-gT.js +7 -0
  116. package/client/dist/assets/lib-CGdyxIWg.js +29 -0
  117. package/client/dist/assets/{liquid-eFsVK0V-.js → liquid-BY6hCUXG.js} +1 -1
  118. package/client/dist/assets/loader-circle-DcygOQpC.js +1 -0
  119. package/client/dist/assets/loops-BzfXWEwO.js +1 -0
  120. package/client/dist/assets/{lspLanguageFeatures-DT3-bbhn.js → lspLanguageFeatures-ira2aEJh.js} +1 -1
  121. package/client/dist/assets/mcp-uDkXYu4L.js +1 -0
  122. package/client/dist/assets/{mdx-Fuwnh1cX.js → mdx-BNx0xr3N.js} +1 -1
  123. package/client/dist/assets/monaco.contribution-Bt92R3Vw.js +2 -0
  124. package/client/dist/assets/nav-myFaXRHD.js +1 -0
  125. package/client/dist/assets/pencil-C9RkrIPZ.js +1 -0
  126. package/client/dist/assets/play-DNlrawbS.js +1 -0
  127. package/client/dist/assets/plus-CsnjfkmX.js +1 -0
  128. package/client/dist/assets/preload-helper-Czpn1I53.js +1 -0
  129. package/client/dist/assets/{python-BgoEs6Cj.js → python-DLdbtmNw.js} +1 -1
  130. package/client/dist/assets/{razor-CM1sPMMq.js → razor-B9gdhAbR.js} +1 -1
  131. package/client/dist/assets/react-dom-DiWzBDP9.js +1 -0
  132. package/client/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
  133. package/client/dist/assets/save-28sIlCdr.js +1 -0
  134. package/client/dist/assets/search-Cq_gE3Ik.js +1 -0
  135. package/client/dist/assets/settings-B0ypw9uw.js +1 -0
  136. package/client/dist/assets/{settings-Bp24JHlv2.js → settings-B9ilOSYO.js} +1 -1
  137. package/client/dist/assets/{settings-Bx2ObkTR2.js → settings-BqlWHRHA.js} +1 -1
  138. package/client/dist/assets/{settings-C_T0-j9d.js → settings-D8dlqt4d.js} +1 -1
  139. package/client/dist/assets/{settings-DhaFKpcw2.js → settings-DAZqjX2a2.js} +1 -1
  140. package/client/dist/assets/{settings-B_v3zgJf.js → settings-DRh39v1S2.js} +1 -1
  141. package/client/dist/assets/{settings-BemR_e3F.js → settings-Mj1OJbfd2.js} +1 -1
  142. package/client/dist/assets/{settings-BvIwQ3A1.js → settings-lRReaSYm.js} +1 -1
  143. package/client/dist/assets/setup-CmK87LuS.js +1 -0
  144. package/client/dist/assets/sparkles-DrY8hb0X.js +1 -0
  145. package/client/dist/assets/specs-DII0C8gw.js +1 -0
  146. package/client/dist/assets/spending-C4p06nen.js +1 -0
  147. package/client/dist/assets/terminal-BFRZnMPP.js +1 -0
  148. package/client/dist/assets/tickets-B8BuAaj5.js +1 -0
  149. package/client/dist/assets/tickets-B_yh4qQk.js +1 -0
  150. package/client/dist/assets/tickets-Bs37scwY2.js +1 -0
  151. package/client/dist/assets/tickets-CFk2n2Or.js +1 -0
  152. package/client/dist/assets/tickets-DQp7JS2R2.js +1 -0
  153. package/client/dist/assets/tickets-D_yDquEw.js +1 -0
  154. package/client/dist/assets/tickets-DpmdE8tv2.js +1 -0
  155. package/client/dist/assets/tickets-pAUJkDTu.js +1 -0
  156. package/client/dist/assets/trash-2-BOU3WdZZ.js +1 -0
  157. package/client/dist/assets/triangle-alert-H9T4jAmh.js +1 -0
  158. package/client/dist/assets/{ts.worker-METxwbDZ.js → ts.worker-BtjBFNHX.js} +48 -33
  159. package/client/dist/assets/{tsMode-BmT8WogI.js → tsMode-DEjSThU9.js} +1 -1
  160. package/client/dist/assets/{typescript-CQrQPsoW.js → typescript-CRRnD2Ts.js} +1 -1
  161. package/client/dist/assets/upload-DYMjAQ6M.js +1 -0
  162. package/client/dist/assets/useBrowserCaptureSession-2OqX0vap.js +1 -0
  163. package/client/dist/assets/useDesktop-X8YB9xzv.js +1 -0
  164. package/client/dist/assets/useSharedWebSocket-DaPh7QBL.js +1 -0
  165. package/client/dist/assets/utils-BC4DHlz2.js +1 -0
  166. package/client/dist/assets/value-s-gzBeyF.js +1 -0
  167. package/client/dist/assets/{workers-U0UXyLUf.js → workers-uOo7WLaa.js} +1 -1
  168. package/client/dist/assets/x-CBMI9ejn.js +1 -0
  169. package/client/dist/assets/{xml-WRxJs8_I.js → xml-EDREH2X7.js} +1 -1
  170. package/client/dist/assets/{yaml-CjpSTjJU.js → yaml-BjaDVX1E.js} +1 -1
  171. package/client/dist/assets/zap-CHo3aCD1.js +1 -0
  172. package/client/dist/index.html +73 -32
  173. package/docs/guide/en/integrations/5-mcp-server.md +2 -2
  174. package/docs/internals/safe-pr-review-flow.md +190 -132
  175. package/docs/mcp.md +15 -5
  176. package/package.json +11 -7
  177. package/server/dist/active-pr-continuation.js +16 -1
  178. package/server/dist/agent-chat-manager.js +360 -181
  179. package/server/dist/agent-cwd-manager.js +52 -8
  180. package/server/dist/agent-generator.js +90 -48
  181. package/server/dist/agent-mcp-config.js +69 -36
  182. package/server/dist/agent-operator-prompt.js +3 -2
  183. package/server/dist/agent-refine-manager.js +47 -6
  184. package/server/dist/agent-tier.js +13 -11
  185. package/server/dist/artifact-registry.js +33 -2
  186. package/server/dist/auth.js +5 -4
  187. package/server/dist/browser-capture-manager.js +92 -29
  188. package/server/dist/chat-manager.js +302 -46
  189. package/server/dist/contract-refine-runner.js +19 -6
  190. package/server/dist/db.js +665 -8
  191. package/server/dist/desktop-router.js +1 -2
  192. package/server/dist/headroom-manager.js +1310 -121
  193. package/server/dist/headroom-routing.js +120 -2
  194. package/server/dist/index.js +48 -13
  195. package/server/dist/interactive-job-session.js +237 -82
  196. package/server/dist/jira/jira-adf.js +10 -0
  197. package/server/dist/jira/jira-db.js +15 -3
  198. package/server/dist/jira/jira-sync-manager.js +182 -13
  199. package/server/dist/job-listing.js +140 -0
  200. package/server/dist/job-spawn-idempotency.js +67 -0
  201. package/server/dist/loop-run-manager.js +508 -98
  202. package/server/dist/loop-runs-store.js +266 -13
  203. package/server/dist/mcp/agent-capability.js +71 -0
  204. package/server/dist/mcp/guide.js +5 -2
  205. package/server/dist/mcp/mcp-server.js +5 -10
  206. package/server/dist/mcp/mcp-token.js +5 -5
  207. package/server/dist/mcp/tools/jobs.js +20 -13
  208. package/server/dist/mcp/tools/types.js +20 -42
  209. package/server/dist/mobile/mobile-router.js +1 -1
  210. package/server/dist/pr-lifecycle.js +77 -0
  211. package/server/dist/pr-publisher.js +94 -27
  212. package/server/dist/process-admission.js +82 -0
  213. package/server/dist/project-registry.js +572 -174
  214. package/server/dist/project-router-git.js +13 -2
  215. package/server/dist/project-router-jobs.js +111 -55
  216. package/server/dist/project-router-tickets.js +32 -2
  217. package/server/dist/providers/claude-adapter.js +21 -13
  218. package/server/dist/queue-manager.js +2332 -623
  219. package/server/dist/rail-isolated-launch.js +1141 -280
  220. package/server/dist/rail-isolation.js +4 -2
  221. package/server/dist/rail-pr-decision.js +756 -270
  222. package/server/dist/rail-pr-delivery.js +2 -1
  223. package/server/dist/rail-pr-store.js +317 -18
  224. package/server/dist/rail-pr-ticket-effects.js +328 -0
  225. package/server/dist/rail-worktree-release.js +28 -6
  226. package/server/dist/rails-router.js +149 -112
  227. package/server/dist/rails-store.js +106 -3
  228. package/server/dist/ticket-store.js +23 -1
  229. package/server/dist/transient-children.js +13 -0
  230. package/server/dist/util/cli-prompt.js +42 -8
  231. package/server/dist/util/secure-token-file.js +84 -0
  232. package/server/dist/worktree-manager.js +70 -21
  233. package/client/dist/assets/ActivityFeedPage-Be-6L0Ld.js +0 -1
  234. package/client/dist/assets/AgentBrowserCapture-CpZ-dd8O.js +0 -1
  235. package/client/dist/assets/AgentIntegrationsModal-DO1pN_Ez.js +0 -1
  236. package/client/dist/assets/AgentModeCodePane-DLltTOqp.js +0 -2
  237. package/client/dist/assets/AgentModeJobsPane-Dp5YvmW0.js +0 -1
  238. package/client/dist/assets/AgentsPage-ehHuVUds.js +0 -86
  239. package/client/dist/assets/AnalyticsPage-C4Ik95FX.js +0 -1
  240. package/client/dist/assets/BarChart-DVZgrhJd.js +0 -33
  241. package/client/dist/assets/CodePage-D4OCMfxL.js +0 -2
  242. package/client/dist/assets/DesktopAnalyticsPage-2Xf-52x2.js +0 -1
  243. package/client/dist/assets/DocsDialog-CHihLjWd.js +0 -11
  244. package/client/dist/assets/DocsPage-1vEk7XbH.js +0 -11
  245. package/client/dist/assets/ExportDropdown-DLHFQxVn.js +0 -1
  246. package/client/dist/assets/IntegrationsPage-CCb2YuyJ.js +0 -1
  247. package/client/dist/assets/InteractiveJobComposer-CxJJEnjw.js +0 -19
  248. package/client/dist/assets/JobDetailModal-CJI7qRkm.js +0 -1
  249. package/client/dist/assets/JobDetailPage-DwA9bbwx.js +0 -1
  250. package/client/dist/assets/JobsPage-BDIyFfd5.js +0 -1
  251. package/client/dist/assets/LoopBuilderPage-CpBaW59t.js +0 -7
  252. package/client/dist/assets/LoopPreviewModal-CwpKcv0Y.js +0 -1
  253. package/client/dist/assets/LoopsPage-BO0Pa8oz.js +0 -1
  254. package/client/dist/assets/PluginsPage-BBGTBpGh.js +0 -2
  255. package/client/dist/assets/ProjectSettingsDialog-M9zGQo0l.js +0 -1
  256. package/client/dist/assets/TemplatePreviewModal-eBI0zL5P.js +0 -1
  257. package/client/dist/assets/activity-Dbl71S-E.js +0 -1
  258. package/client/dist/assets/addon-image-CwoZj17F.js +0 -1
  259. package/client/dist/assets/addon-ligatures-CWDrED80.js +0 -2
  260. package/client/dist/assets/addspec-DrXqP8ip.js +0 -1
  261. package/client/dist/assets/agent-BEXYxDbs.js +0 -1
  262. package/client/dist/assets/agent-BoXSzaBk2.js +0 -1
  263. package/client/dist/assets/agent-C2-rROL52.js +0 -1
  264. package/client/dist/assets/agent-CeRTdGT12.js +0 -1
  265. package/client/dist/assets/agent-DZ8zTrQT.js +0 -1
  266. package/client/dist/assets/agent-DqyzT33X.js +0 -1
  267. package/client/dist/assets/agent-fQI_6tsA.js +0 -1
  268. package/client/dist/assets/agent-tJdAobaM.js +0 -1
  269. package/client/dist/assets/agents-CGDn_3kB.js +0 -1
  270. package/client/dist/assets/agentstudio-Dxe_94PK.js +0 -1
  271. package/client/dist/assets/aiedit-DrfHQnlz.js +0 -1
  272. package/client/dist/assets/analytics-CxDYcBl9.js +0 -1
  273. package/client/dist/assets/attachments-DvxQbBTC.js +0 -1
  274. package/client/dist/assets/brain-DecvjjPU.js +0 -1
  275. package/client/dist/assets/browser-DM8G151q.js +0 -1
  276. package/client/dist/assets/chat-0dg5hOo_.js +0 -1
  277. package/client/dist/assets/chunk-BNv3lrIs.js +0 -1
  278. package/client/dist/assets/code-B62fClk-.js +0 -1
  279. package/client/dist/assets/commands-DCJ-R9E4.js +0 -1
  280. package/client/dist/assets/common-BfQD173j.js +0 -1
  281. package/client/dist/assets/common-CF8iGXrG.js +0 -1
  282. package/client/dist/assets/common-Cfmwcrl52.js +0 -1
  283. package/client/dist/assets/common-D2cimsul.js +0 -1
  284. package/client/dist/assets/common-DH7fjyL1.js +0 -1
  285. package/client/dist/assets/common-DXrmj6WD.js +0 -1
  286. package/client/dist/assets/common-DZbXlmq72.js +0 -1
  287. package/client/dist/assets/common-DtXmzX2o2.js +0 -1
  288. package/client/dist/assets/dashboard-5C2FOZQX.js +0 -1
  289. package/client/dist/assets/dashboard-B6iQ-IL_.js +0 -1
  290. package/client/dist/assets/dashboard-BFfnCtq52.js +0 -1
  291. package/client/dist/assets/dashboard-BIC8KtRf2.js +0 -1
  292. package/client/dist/assets/dashboard-CEgg9NAr.js +0 -1
  293. package/client/dist/assets/dashboard-CiOyDh9L.js +0 -1
  294. package/client/dist/assets/dashboard-D6x5OnNj2.js +0 -1
  295. package/client/dist/assets/dashboard-DBuLfWo9.js +0 -1
  296. package/client/dist/assets/editor.api2-C3B0tp2x.js +0 -872
  297. package/client/dist/assets/editor.worker-Bd9IXS8d.js +0 -26
  298. package/client/dist/assets/explore-_XyU5e7R.js +0 -1
  299. package/client/dist/assets/index-BVbgKXUu.css +0 -2
  300. package/client/dist/assets/index-OBDI7bHm.js +0 -158
  301. package/client/dist/assets/integrations-By80EBkf.js +0 -1
  302. package/client/dist/assets/jira-DeIjwCK3.js +0 -1
  303. package/client/dist/assets/jobs-BFIdeDTw.js +0 -1
  304. package/client/dist/assets/jobs-BJ-Gcao22.js +0 -1
  305. package/client/dist/assets/jobs-DAGiPVIS2.js +0 -1
  306. package/client/dist/assets/jobs-DAU8C_tZ.js +0 -1
  307. package/client/dist/assets/jobs-Da0GaaSc.js +0 -1
  308. package/client/dist/assets/jobs-DrYmsKy5.js +0 -1
  309. package/client/dist/assets/jobs-P3unxtxo2.js +0 -1
  310. package/client/dist/assets/jobs-p360zdf1.js +0 -1
  311. package/client/dist/assets/json.worker-DzV-CpCQ.js +0 -58
  312. package/client/dist/assets/lib-C5ZxrPSR.js +0 -7
  313. package/client/dist/assets/loops-CO6q75W8.js +0 -1
  314. package/client/dist/assets/mcp-HwBbQDk0.js +0 -1
  315. package/client/dist/assets/monaco.contribution-Desq7oq4.js +0 -2
  316. package/client/dist/assets/nav-BTg8s-cD.js +0 -1
  317. package/client/dist/assets/preload-helper-DSXbuxSR.js +0 -1
  318. package/client/dist/assets/settings-BYCMtxOz.js +0 -1
  319. package/client/dist/assets/setup-D2CmgAro.js +0 -1
  320. package/client/dist/assets/specs-PGIOzMtc.js +0 -1
  321. package/client/dist/assets/terminal-CFt56A5f.js +0 -1
  322. package/client/dist/assets/tickets-BHv3ewvq.js +0 -1
  323. package/client/dist/assets/tickets-BIxU86j12.js +0 -1
  324. package/client/dist/assets/tickets-C2BhwzwR.js +0 -1
  325. package/client/dist/assets/tickets-D1IeX2EW.js +0 -1
  326. package/client/dist/assets/tickets-DZut8Ryt.js +0 -1
  327. package/client/dist/assets/tickets-LfK6LzKl2.js +0 -1
  328. package/client/dist/assets/tickets-dQfsc7Ph.js +0 -1
  329. package/client/dist/assets/tickets-sVun0I6v.js +0 -1
  330. package/client/dist/assets/upload-lgjKJgrl.js +0 -1
  331. package/client/dist/assets/value-Bc0_jlJA.js +0 -1
  332. /package/client/dist/assets/{abap-D0E4FfVW.js → abap-08VXUWAP.js} +0 -0
  333. /package/client/dist/assets/{activity-BbWiDfn-2.js → activity-B-rp4h_v.js} +0 -0
  334. /package/client/dist/assets/{activity-DvECTYGt.js → activity-C6kJpm9q2.js} +0 -0
  335. /package/client/dist/assets/{activity-D3Ic-HTS.js → activity-CGx4kb_j.js} +0 -0
  336. /package/client/dist/assets/{activity-BZZlEG3a.js → activity-CnoX2bH7.js} +0 -0
  337. /package/client/dist/assets/{activity-DtzRy92x.js → activity-DAyekdlt2.js} +0 -0
  338. /package/client/dist/assets/{activity-FleOlK0l2.js → activity-DCxtpT7j.js} +0 -0
  339. /package/client/dist/assets/{activity-DFbvNqhE2.js → activity-JzwRaWPD.js} +0 -0
  340. /package/client/dist/assets/{addspec-CFxGvmJR2.js → addspec-BXjvkh5q.js} +0 -0
  341. /package/client/dist/assets/{addspec-NBbcTbLc.js → addspec-C4bwrmpi.js} +0 -0
  342. /package/client/dist/assets/{addspec-BKrH2Ucz.js → addspec-CArHXUDM.js} +0 -0
  343. /package/client/dist/assets/{addspec-CR8MB5Fr2.js → addspec-DTKJcXiR.js} +0 -0
  344. /package/client/dist/assets/{addspec-B_LQMHW3.js → addspec-DbNBbrha2.js} +0 -0
  345. /package/client/dist/assets/{addspec-DaA-CbTt.js → addspec-htHGZHOe2.js} +0 -0
  346. /package/client/dist/assets/{addspec-Bw_YQwo62.js → addspec-lr_LP6jk.js} +0 -0
  347. /package/client/dist/assets/{agents-BHgn--3j.js → agents-6ZOJXWVk2.js} +0 -0
  348. /package/client/dist/assets/{agents-VsyFhGOS2.js → agents-BZ0XQKjg.js} +0 -0
  349. /package/client/dist/assets/{agents-HFCqQH0Z.js → agents-BqWklnHR.js} +0 -0
  350. /package/client/dist/assets/{agents-B_qGaB0F.js → agents-Bz-thHkB2.js} +0 -0
  351. /package/client/dist/assets/{agents-CP8_ezYG.js → agents-CJclElx6.js} +0 -0
  352. /package/client/dist/assets/{agents-B9pFSJQI2.js → agents-D_l9L0VA.js} +0 -0
  353. /package/client/dist/assets/{agents-CV-_xBvX2.js → agents-u_C0jPB5.js} +0 -0
  354. /package/client/dist/assets/{agentstudio-BEp7Sb46.js → agentstudio-BF19hbXj.js} +0 -0
  355. /package/client/dist/assets/{agentstudio-Br3WD7Xs.js → agentstudio-BLGvxWBx2.js} +0 -0
  356. /package/client/dist/assets/{agentstudio-BOjG6tgJ.js → agentstudio-D9UKiWTj2.js} +0 -0
  357. /package/client/dist/assets/{agentstudio-D2A5peWV2.js → agentstudio-Drs37nIM.js} +0 -0
  358. /package/client/dist/assets/{agentstudio-CuTt3ZGe2.js → agentstudio-nEnKkcXM.js} +0 -0
  359. /package/client/dist/assets/{agentstudio-RsxnZtUA.js → agentstudio-pyFcSBKy.js} +0 -0
  360. /package/client/dist/assets/{agentstudio-RfZavULt2.js → agentstudio-zfTzQh3U.js} +0 -0
  361. /package/client/dist/assets/{aiedit-DnBzjlVz2.js → aiedit-B0KUbpB6.js} +0 -0
  362. /package/client/dist/assets/{aiedit-D0Syl-mC.js → aiedit-BoG24nlg2.js} +0 -0
  363. /package/client/dist/assets/{aiedit-CcugGPbS.js → aiedit-CjlSRQVP.js} +0 -0
  364. /package/client/dist/assets/{aiedit-DrIjSojo.js → aiedit-DCjA7jLJ.js} +0 -0
  365. /package/client/dist/assets/{aiedit-CV520D4W2.js → aiedit-DNXxq_Dr.js} +0 -0
  366. /package/client/dist/assets/{aiedit-26p5A_HY2.js → aiedit-DQ-0_pvE.js} +0 -0
  367. /package/client/dist/assets/{aiedit-a-Z-g5iC.js → aiedit-olnv3QSt2.js} +0 -0
  368. /package/client/dist/assets/{analytics-DEe5BMz8.js → analytics-BC7mwR4s.js} +0 -0
  369. /package/client/dist/assets/{analytics-4HvXb0Vn2.js → analytics-BiNg8edP.js} +0 -0
  370. /package/client/dist/assets/{analytics-CpSTNfui.js → analytics-C0s_T2GN.js} +0 -0
  371. /package/client/dist/assets/{analytics-4-eskbtX.js → analytics-CdsAPAwZ2.js} +0 -0
  372. /package/client/dist/assets/{analytics-Bpe41fnX2.js → analytics-Cr1_q-lC.js} +0 -0
  373. /package/client/dist/assets/{analytics-DcOITHla2.js → analytics-IEsKZvpW2.js} +0 -0
  374. /package/client/dist/assets/{analytics-Bd9rHGjV.js → analytics-SRPf84OT.js} +0 -0
  375. /package/client/dist/assets/{apex-D_X_8J4d.js → apex-BWPQTe0t.js} +0 -0
  376. /package/client/dist/assets/{atom-one-dark-BMDPFwRZ.js → atom-one-dark-qiGhVQxg.js} +0 -0
  377. /package/client/dist/assets/{attachments-CgOQ9K-6.js → attachments-D0wXXt9T2.js} +0 -0
  378. /package/client/dist/assets/{attachments-DlEQu56g2.js → attachments-DenIjuVI.js} +0 -0
  379. /package/client/dist/assets/{attachments-CA2f3qL3.js → attachments-DhO7LPfy.js} +0 -0
  380. /package/client/dist/assets/{attachments-D2jwJXQ42.js → attachments-DoH5w0x4.js} +0 -0
  381. /package/client/dist/assets/{attachments-CHDbhKeH.js → attachments-Q9yNtiEM.js} +0 -0
  382. /package/client/dist/assets/{attachments-CKAY5aYG2.js → attachments-V_WZAYXx2.js} +0 -0
  383. /package/client/dist/assets/{attachments-jVdKzAXn.js → attachments-eaeScpjM.js} +0 -0
  384. /package/client/dist/assets/{auth-DpZq5p1e.js → auth-D_5bQv5W.js} +0 -0
  385. /package/client/dist/assets/{azcli-PZLnZ3jH.js → azcli-Bc_sGQ0U.js} +0 -0
  386. /package/client/dist/assets/{bat-KlBOxpO4.js → bat-i0X4ZdIN.js} +0 -0
  387. /package/client/dist/assets/{bicep-CgG9oF3V.js → bicep-B5-_aFwp.js} +0 -0
  388. /package/client/dist/assets/{browser-Cl-FwPXs.js → browser-C25DkQiO.js} +0 -0
  389. /package/client/dist/assets/{browser-JsiohNmW.js → browser-CiJ7-L32.js} +0 -0
  390. /package/client/dist/assets/{browser-BbJ_q_OT2.js → browser-CvgDySd9.js} +0 -0
  391. /package/client/dist/assets/{browser-BQFDxrRq.js → browser-CyR1Kp3m2.js} +0 -0
  392. /package/client/dist/assets/{browser-Do2jf8_N.js → browser-DwZljcRh2.js} +0 -0
  393. /package/client/dist/assets/{browser-C4VJ5CW12.js → browser-DxP-alBK.js} +0 -0
  394. /package/client/dist/assets/{browser-kA31eYhu2.js → browser-rRCwi7Y7.js} +0 -0
  395. /package/client/dist/assets/{cameligo-6R45j6Ph.js → cameligo-DMUM7wLl.js} +0 -0
  396. /package/client/dist/assets/{chat-BalviUAD.js → chat-2rIB8HhT.js} +0 -0
  397. /package/client/dist/assets/{chat-Ds8GvCnv.js → chat-BR8oe53x2.js} +0 -0
  398. /package/client/dist/assets/{chat-D5xEtsf3.js → chat-Bua03tty.js} +0 -0
  399. /package/client/dist/assets/{chat-SaG-ng1x2.js → chat-CZpAQan1.js} +0 -0
  400. /package/client/dist/assets/{chat-CJddjN3D2.js → chat-CdY5SGvS.js} +0 -0
  401. /package/client/dist/assets/{chat-n43nZGvn.js → chat-DZyu3ImY2.js} +0 -0
  402. /package/client/dist/assets/{chat-BMWD_Rx12.js → chat-D_I7nU5K.js} +0 -0
  403. /package/client/dist/assets/{clojure-CLAAMO7J.js → clojure-Cm7r79vr.js} +0 -0
  404. /package/client/dist/assets/{clsx-CNmW0Nip.js → clsx-CjueKrWZ.js} +0 -0
  405. /package/client/dist/assets/{code-C9U6M5-e2.js → code-76gS0IdL.js} +0 -0
  406. /package/client/dist/assets/{code-BotxM43N2.js → code-C1VwxlML.js} +0 -0
  407. /package/client/dist/assets/{code-HDK4P6Rx.js → code-CnMZyJpr.js} +0 -0
  408. /package/client/dist/assets/{code-DUe1Bouz.js → code-D20ef9Cf2.js} +0 -0
  409. /package/client/dist/assets/{code-Bit_z2Xd2.js → code-DdOXaWRY.js} +0 -0
  410. /package/client/dist/assets/{code-F_KjccGf.js → code-Duohuf9l.js} +0 -0
  411. /package/client/dist/assets/{code-CIPmDEd0.js → code-geHmknB92.js} +0 -0
  412. /package/client/dist/assets/{coffee-AruSygo-.js → coffee-Ba7i2nA0.js} +0 -0
  413. /package/client/dist/assets/{commands-BFQZc9AG.js → commands-B9RZO4qS.js} +0 -0
  414. /package/client/dist/assets/{commands-BMkC5-ki.js → commands-BEwbAjtD2.js} +0 -0
  415. /package/client/dist/assets/{commands-KBbhsnw8.js → commands-Bbxu8kLU.js} +0 -0
  416. /package/client/dist/assets/{commands-CzFSoIiq2.js → commands-CDU3qv68.js} +0 -0
  417. /package/client/dist/assets/{commands-CWX9WmDX2.js → commands-CzezgtQj.js} +0 -0
  418. /package/client/dist/assets/{commands-BllkJauO.js → commands-DTQBF_q52.js} +0 -0
  419. /package/client/dist/assets/{commands-cKTaLwwV2.js → commands-hOtKuOXc.js} +0 -0
  420. /package/client/dist/assets/{cpp-DEE5r4Pw.js → cpp-C7h46wYY.js} +0 -0
  421. /package/client/dist/assets/{csharp-BVXJUvbK.js → csharp-BKxtCVv1.js} +0 -0
  422. /package/client/dist/assets/{csp-cXOF0JoV.js → csp-bTuwJoIa.js} +0 -0
  423. /package/client/dist/assets/{css-C_zQuAoy.js → css-DIMkf-bt.js} +0 -0
  424. /package/client/dist/assets/{cypher-t95m_Q-O.js → cypher-CVaqCwHa.js} +0 -0
  425. /package/client/dist/assets/{dart-B4r1Tg-e.js → dart-onAF5SnQ.js} +0 -0
  426. /package/client/dist/assets/{dockerfile-6mt7zHQw.js → dockerfile-DZFCIeNp.js} +0 -0
  427. /package/client/dist/assets/{ecl-DEY_Fz_Q.js → ecl-D05T4iGw.js} +0 -0
  428. /package/client/dist/assets/{elixir-R6-q1c5t.js → elixir-6RTg0lbw.js} +0 -0
  429. /package/client/dist/assets/{explore-CAc8Qzu32.js → explore-BG99VGpL.js} +0 -0
  430. /package/client/dist/assets/{explore-BO_OinBD.js → explore-C0TjprJW2.js} +0 -0
  431. /package/client/dist/assets/{explore-Bhy7WWXv.js → explore-C2dgEo7q.js} +0 -0
  432. /package/client/dist/assets/{explore-ELbrSZyQ2.js → explore-C8NDHbob.js} +0 -0
  433. /package/client/dist/assets/{explore-DW_YTkQh.js → explore-CWWgzQGR.js} +0 -0
  434. /package/client/dist/assets/{explore-DCZL9gRN2.js → explore-CcQXpdtS.js} +0 -0
  435. /package/client/dist/assets/{explore-BEpIs343.js → explore-DsXAgnmJ2.js} +0 -0
  436. /package/client/dist/assets/{flow9-CNQvHBOt.js → flow9-C5_-GSwl.js} +0 -0
  437. /package/client/dist/assets/{format-command-Db1viey1.js → format-command-Dfa1o2d-.js} +0 -0
  438. /package/client/dist/assets/{fsharp-vPZkFXwv.js → fsharp-C8Ef5oNN.js} +0 -0
  439. /package/client/dist/assets/{go-KUzBMYJ_.js → go-C-y9NEjX.js} +0 -0
  440. /package/client/dist/assets/{graphql-C3W0rCsD.js → graphql-fmXr3nnJ.js} +0 -0
  441. /package/client/dist/assets/{hcl-BOOMFSNX.js → hcl-CpzslTdj.js} +0 -0
  442. /package/client/dist/assets/{ini-CFat1WJr.js → ini-sBoK_t0W.js} +0 -0
  443. /package/client/dist/assets/{integrations-CG2dp3Kd.js → integrations-CH0zy1Mw2.js} +0 -0
  444. /package/client/dist/assets/{integrations-CUNe3t9l2.js → integrations-CIAjhd7L.js} +0 -0
  445. /package/client/dist/assets/{integrations-CWjh5VRk.js → integrations-CZdwOe8A.js} +0 -0
  446. /package/client/dist/assets/{integrations-RJfzXkPx2.js → integrations-Ct7omMld.js} +0 -0
  447. /package/client/dist/assets/{integrations-CAjq5tdW2.js → integrations-D6MSxy3-.js} +0 -0
  448. /package/client/dist/assets/{integrations-JOHSCvB3.js → integrations-DbZa-Zyc.js} +0 -0
  449. /package/client/dist/assets/{integrations-CP_aiV_8.js → integrations-b8pH-vfl2.js} +0 -0
  450. /package/client/dist/assets/{java-Dee-CNAM.js → java-BEtHBSE6.js} +0 -0
  451. /package/client/dist/assets/{jira-CQ7mrZde.js → jira-BySfUuQZ.js} +0 -0
  452. /package/client/dist/assets/{jira-DIRkZk9U.js → jira-Byw2C0F8.js} +0 -0
  453. /package/client/dist/assets/{jira-C5UjxHE4.js → jira-CNeYi06w2.js} +0 -0
  454. /package/client/dist/assets/{jira-ZdKb-50F2.js → jira-D11gPhxz.js} +0 -0
  455. /package/client/dist/assets/{jira-DTYu3hJi2.js → jira-D38Gj7AT.js} +0 -0
  456. /package/client/dist/assets/{jira-B5wszJ1-2.js → jira-gAvOyije.js} +0 -0
  457. /package/client/dist/assets/{jira-DtZpokjJ.js → jira-pRaL7JQo2.js} +0 -0
  458. /package/client/dist/assets/{julia-CGNlLYuU.js → julia-Bri6UV-V.js} +0 -0
  459. /package/client/dist/assets/{kotlin-CY7lhknJ.js → kotlin-BOotOW0E.js} +0 -0
  460. /package/client/dist/assets/{less-D0KN7hfJ.js → less-B9JPFI3C.js} +0 -0
  461. /package/client/dist/assets/{lexon-CuRJufPs.js → lexon-CfSJPG6W.js} +0 -0
  462. /package/client/dist/assets/{loops-DKzYFcq6.js → loops-CPpgMapU.js} +0 -0
  463. /package/client/dist/assets/{loops-Du-bNVpc2.js → loops-CWWbKuVL.js} +0 -0
  464. /package/client/dist/assets/{loops-DDs4KPu_.js → loops-Crc9It8o2.js} +0 -0
  465. /package/client/dist/assets/{loops-Bi4ywYGn.js → loops-DH2hZcbK2.js} +0 -0
  466. /package/client/dist/assets/{loops-DvGbSfxw2.js → loops-DS5TmvIU2.js} +0 -0
  467. /package/client/dist/assets/{loops-D3wdUkZV.js → loops-P2f0cN8-.js} +0 -0
  468. /package/client/dist/assets/{loops-BjK8lmoZ2.js → loops-fJV-9hH7.js} +0 -0
  469. /package/client/dist/assets/{lua-DHvH62Zf.js → lua-CsQS60Ue.js} +0 -0
  470. /package/client/dist/assets/{m3-tlthQ8Fo.js → m3-D-oSqn_W.js} +0 -0
  471. /package/client/dist/assets/{markdown-Dbx1LgUZ.js → markdown-Cimd5fb3.js} +0 -0
  472. /package/client/dist/assets/{mcp-DfmzoT5t.js → mcp-Czd_iyp02.js} +0 -0
  473. /package/client/dist/assets/{mcp-BG-OCJca2.js → mcp-D2pc5GGx.js} +0 -0
  474. /package/client/dist/assets/{mcp-CrWdq5db.js → mcp-D4ZjtIx32.js} +0 -0
  475. /package/client/dist/assets/{mcp-CbJ04oFS2.js → mcp-DIfBUv2D2.js} +0 -0
  476. /package/client/dist/assets/{mcp-Dd844F5_.js → mcp-DsTwmjST.js} +0 -0
  477. /package/client/dist/assets/{mcp-zSJ2TsJj.js → mcp-SMkn8boG.js} +0 -0
  478. /package/client/dist/assets/{mcp-DyULurYS2.js → mcp-fSfiVYAc.js} +0 -0
  479. /package/client/dist/assets/{mips-D_O3IUz_.js → mips-CIPQ_RoX.js} +0 -0
  480. /package/client/dist/assets/{msdax-C_ZmCnRt.js → msdax-DauUninz.js} +0 -0
  481. /package/client/dist/assets/{mysql-C-dCEYSX.js → mysql-SOo6toE5.js} +0 -0
  482. /package/client/dist/assets/{nav-DUuw9VIU2.js → nav-BRJBxaGt2.js} +0 -0
  483. /package/client/dist/assets/{nav-D0Erv9ZJ2.js → nav-CAGKv3_F.js} +0 -0
  484. /package/client/dist/assets/{nav-BY_4EWRv.js → nav-CNskRJKQ2.js} +0 -0
  485. /package/client/dist/assets/{nav-CVXtv0wX2.js → nav-DY93irBL.js} +0 -0
  486. /package/client/dist/assets/{nav-B2_ToNqS.js → nav-DhjvYgqt.js} +0 -0
  487. /package/client/dist/assets/{nav-S4HM94vW.js → nav-Gb-r4wop.js} +0 -0
  488. /package/client/dist/assets/{nav-BAMkVEId.js → nav-TvumAFD22.js} +0 -0
  489. /package/client/dist/assets/{objective-c-DZY9Aqxd.js → objective-c-FvmIjYaQ.js} +0 -0
  490. /package/client/dist/assets/{pascal-BVHLXoor.js → pascal-DrH0SRf2.js} +0 -0
  491. /package/client/dist/assets/{pascaligo-XdG_6u3k.js → pascaligo-D-ptJ9y-.js} +0 -0
  492. /package/client/dist/assets/{perl-b9QV0BcD.js → perl-oz_6vUea.js} +0 -0
  493. /package/client/dist/assets/{pgsql-Du5FDOgT.js → pgsql-DTj74zXo.js} +0 -0
  494. /package/client/dist/assets/{php-WJD0z2Z_.js → php-nr791fC2.js} +0 -0
  495. /package/client/dist/assets/{pla-CbciTf2w.js → pla-CopQ2nXW.js} +0 -0
  496. /package/client/dist/assets/{postiats-C1QxmvVq.js → postiats-43DmfD33.js} +0 -0
  497. /package/client/dist/assets/{powerquery-BEYDhPLZ.js → powerquery-D3hlyOfw.js} +0 -0
  498. /package/client/dist/assets/{powershell-CfaYw4XL.js → powershell-DmHpPYUd.js} +0 -0
  499. /package/client/dist/assets/{protobuf-pwZEDOYa.js → protobuf-C531GsRP.js} +0 -0
  500. /package/client/dist/assets/{pug-5ftqSPVe.js → pug-Z5eAx3Zn.js} +0 -0
  501. /package/client/dist/assets/{qsharp-uI6qZMq4.js → qsharp-DkqhCAOL.js} +0 -0
  502. /package/client/dist/assets/{r-BK54P1EG.js → r-BwWrilGY.js} +0 -0
  503. /package/client/dist/assets/{redis-DI0yd7kN.js → redis-ClamHrr6.js} +0 -0
  504. /package/client/dist/assets/{redshift-Dov26eyI.js → redshift-DT7zqm-g.js} +0 -0
  505. /package/client/dist/assets/{restructuredtext-QqJ_1dC-.js → restructuredtext-BYgofb2h.js} +0 -0
  506. /package/client/dist/assets/{ruby-5WjveeVb.js → ruby-DezsRK8O.js} +0 -0
  507. /package/client/dist/assets/{rust-BzVSfqFH.js → rust-DdL9SqIa.js} +0 -0
  508. /package/client/dist/assets/{sb-DMIUfT-3.js → sb-CcwsVR0C.js} +0 -0
  509. /package/client/dist/assets/{scala-CSKA7lqt.js → scala-DHpiXF5c.js} +0 -0
  510. /package/client/dist/assets/{scheme-CojOP2Qc.js → scheme-BeGwcela.js} +0 -0
  511. /package/client/dist/assets/{scss-DoBZGJG6.js → scss-gp-XZpBa.js} +0 -0
  512. /package/client/dist/assets/{setup-JeBwoXoM.js → setup-CGZjy2IF2.js} +0 -0
  513. /package/client/dist/assets/{setup-1vN48B-m2.js → setup-CXa0Sx16.js} +0 -0
  514. /package/client/dist/assets/{setup-BmJ0Kx16.js → setup-D2ktKKMF2.js} +0 -0
  515. /package/client/dist/assets/{setup-T_ApeQDV2.js → setup-DG2H7ha5.js} +0 -0
  516. /package/client/dist/assets/{setup-DQWsu2TR.js → setup-DK5bHDUq2.js} +0 -0
  517. /package/client/dist/assets/{setup-q0wRHUp1.js → setup-DrWlIQTZ.js} +0 -0
  518. /package/client/dist/assets/{setup-mXnzww4f.js → setup-rzHUVgAu.js} +0 -0
  519. /package/client/dist/assets/{shell-YOaE4udl.js → shell-CC2rA5mh.js} +0 -0
  520. /package/client/dist/assets/{solidity-l3jSyVLu.js → solidity-BEEn4gHE.js} +0 -0
  521. /package/client/dist/assets/{sophia-BhSGTaQJ.js → sophia-CRfGWb83.js} +0 -0
  522. /package/client/dist/assets/{sparql-qhk6QD9w.js → sparql-D_Lu-MrJ.js} +0 -0
  523. /package/client/dist/assets/{specs-BGRKhM8t.js → specs-BcyxaCwf2.js} +0 -0
  524. /package/client/dist/assets/{specs-GkiL-snY.js → specs-C1q9geAi.js} +0 -0
  525. /package/client/dist/assets/{specs-DbtIzV8a.js → specs-CLZKysq-2.js} +0 -0
  526. /package/client/dist/assets/{specs-Ce5y2iFc2.js → specs-DZMS9o54.js} +0 -0
  527. /package/client/dist/assets/{specs-CT8exGOy2.js → specs-Dm6zUI-R.js} +0 -0
  528. /package/client/dist/assets/{specs-C6J2zrLM.js → specs-V3TvhKLX.js} +0 -0
  529. /package/client/dist/assets/{specs-DZDoZsDq.js → specs-dV3Mq5Mu2.js} +0 -0
  530. /package/client/dist/assets/{sql-DoO3y21-.js → sql-NEE52Syq.js} +0 -0
  531. /package/client/dist/assets/{st-ChZAY8oG.js → st-DbInun42.js} +0 -0
  532. /package/client/dist/assets/{swift-D290Tr7_.js → swift-Bxkupp3x.js} +0 -0
  533. /package/client/dist/assets/{systemverilog-DsxzgJ-o.js → systemverilog-Bz4Y3fRF.js} +0 -0
  534. /package/client/dist/assets/{tcl-BKDUHAAR.js → tcl-DISqw1ZD.js} +0 -0
  535. /package/client/dist/assets/{terminal-BTpE7iki.js → terminal-BZiIu3rf.js} +0 -0
  536. /package/client/dist/assets/{terminal-DEyt0prF.js → terminal-C2NAd7BW2.js} +0 -0
  537. /package/client/dist/assets/{terminal-a6Uq8B0p.js → terminal-IahE4LW92.js} +0 -0
  538. /package/client/dist/assets/{terminal-DDk_bTw0.js → terminal-QpXZU2Bn2.js} +0 -0
  539. /package/client/dist/assets/{terminal-BNSg2lR7.js → terminal-ScjyXNxQ.js} +0 -0
  540. /package/client/dist/assets/{terminal-Cvabyzm42.js → terminal-d7GadXbN.js} +0 -0
  541. /package/client/dist/assets/{terminal-D0z12_sJ2.js → terminal-tITa81iz.js} +0 -0
  542. /package/client/dist/assets/{twig-UpZQaM8G.js → twig-De2hgUGE.js} +0 -0
  543. /package/client/dist/assets/{typespec-DzaKvN8i.js → typespec-B8J7ngcE.js} +0 -0
  544. /package/client/dist/assets/{vb-CZpbb7tk.js → vb-DV3o63ZY.js} +0 -0
  545. /package/client/dist/assets/{wgsl-CNt3OIvl.js → wgsl-DpFanUEy.js} +0 -0
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ProjectRegistry = void 0;
7
+ exports.emitRecoveredPrDelivery = emitRecoveredPrDelivery;
8
+ exports.reprojectActivePrDeliveries = reprojectActivePrDeliveries;
7
9
  const path_1 = __importDefault(require("path"));
8
10
  const fs_1 = __importDefault(require("fs"));
9
11
  const os_1 = __importDefault(require("os"));
@@ -37,10 +39,49 @@ const loop_run_manager_1 = require("./loop-run-manager");
37
39
  const loop_executors_1 = require("./loop-executors");
38
40
  const rail_isolated_launch_1 = require("./rail-isolated-launch");
39
41
  const rail_isolation_1 = require("./rail-isolation");
42
+ const rail_pr_store_1 = require("./rail-pr-store");
43
+ const agent_chat_registry_1 = require("./agent-chat-registry");
44
+ const rail_pr_ticket_effects_1 = require("./rail-pr-ticket-effects");
40
45
  const loop_runs_store_1 = require("./loop-runs-store");
41
46
  const rails_store_1 = require("./rails-store");
42
47
  const desktop_db_1 = require("./desktop-db");
43
48
  const config_1 = require("./config");
49
+ const process_admission_1 = require("./process-admission");
50
+ // ─── Types ────────────────────────────────────────────────────────────────────
51
+ /** The two live queue states are the only non-terminal members of JobStatus.
52
+ * Deriving terminality from that invariant avoids a second status list that can
53
+ * silently fall behind the queue contract when a new terminal outcome is added. */
54
+ function isTerminalJobStatus(status) {
55
+ return status !== 'queued' && status !== 'running';
56
+ }
57
+ /** Rehydrate both authoritative PR-decision surfaces after startup recovery.
58
+ * Recovery has already committed the row; broadcasts/cards are advisory and
59
+ * may be retried by normal hydration if a surface is unavailable. */
60
+ function emitRecoveredPrDelivery(ctx, deliveryId) {
61
+ const row = (0, rail_pr_store_1.getPrDelivery)(ctx.db, deliveryId);
62
+ if (!row)
63
+ return;
64
+ const snapshot = (0, rail_pr_store_1.toPrDeliverySnapshot)(row);
65
+ try {
66
+ ctx.broadcast((0, rail_pr_store_1.toRailPrStateMessage)(ctx.project.id, snapshot));
67
+ }
68
+ catch { /* durable row is authoritative */ }
69
+ if (!row.origin_conversation_id)
70
+ return;
71
+ try {
72
+ (0, agent_chat_registry_1.getAgentChatManager)()?.updatePrDecisionCard(row.origin_conversation_id, (0, rail_pr_store_1.toPrDecisionCardEnvelope)(ctx.project.id, snapshot));
73
+ }
74
+ catch { /* conversation hydration can retry */ }
75
+ }
76
+ /** Migration/recovery or a crash after a terminal CAS can repair the ledger
77
+ * without touching system envelopes persisted in agent conversations. Project
78
+ * active AND terminal origin-linked rows so obsolete cards cannot stay pinned. */
79
+ function reprojectActivePrDeliveries(ctx) {
80
+ const rows = (0, rail_pr_store_1.listOriginLinkedPrDeliveries)(ctx.db);
81
+ for (const row of rows)
82
+ emitRecoveredPrDelivery(ctx, row.id);
83
+ return rows.length;
84
+ }
44
85
  // ─── ProjectRegistry ──────────────────────────────────────────────────────────
45
86
  class ProjectRegistry {
46
87
  _desktopDb;
@@ -125,30 +166,65 @@ class ProjectRegistry {
125
166
  }
126
167
  addProject(opts) {
127
168
  const row = (0, desktop_db_1.addProject)(this._desktopDb, opts);
169
+ let previousRegistryEntry;
170
+ let mirroredRegistryEntry;
171
+ let registryMirrored = false;
128
172
  // Mirror the new project into the shared artifact registry so specrails-core
129
173
  // resolves its relocated artifacts. Wrapped so a registry write failure never
130
174
  // breaks project creation — the startup reconcile will recreate the entry.
131
175
  try {
132
- (0, artifact_registry_1.mirrorProjectEntry)({
176
+ const mutation = (0, artifact_registry_1.mirrorProjectEntryWithPrevious)({
133
177
  repoPath: row.path,
134
178
  slug: row.slug,
135
179
  providers: row.providers,
136
180
  primaryProvider: row.provider,
137
181
  desktopProjectId: row.id,
138
182
  });
183
+ previousRegistryEntry = mutation.previousEntry;
184
+ mirroredRegistryEntry = mutation.entry;
185
+ registryMirrored = true;
139
186
  }
140
187
  catch (err) {
141
188
  console.error('[project-registry] registry mirror failed (non-fatal):', err);
142
189
  }
143
- return this._loadProjectContext(row);
190
+ try {
191
+ const context = this._loadProjectContext(row);
192
+ this._failedProjects.delete(row.id);
193
+ return context;
194
+ }
195
+ catch (err) {
196
+ // Registration is one logical operation. A row that cannot be hydrated is
197
+ // unusable through the API and would make every retry hit UNIQUE forever.
198
+ this._contexts.delete(row.id);
199
+ this._failedProjects.delete(row.id);
200
+ // The mirror may have ADOPTED a pre-existing core-standalone entry. Undo
201
+ // only a successful mirror and restore that entry verbatim; deleting it
202
+ // would strand core's relocated artifacts. A fresh mirror has no prior
203
+ // entry, so the same rollback removes the newly-created projection.
204
+ if (registryMirrored && mirroredRegistryEntry) {
205
+ try {
206
+ (0, artifact_registry_1.restoreRegistryEntry)(row.path, previousRegistryEntry, mirroredRegistryEntry);
207
+ }
208
+ catch { /* best effort */ }
209
+ }
210
+ try {
211
+ (0, desktop_db_1.removeProject)(this._desktopDb, row.id);
212
+ }
213
+ catch { /* best effort */ }
214
+ throw err;
215
+ }
144
216
  }
145
217
  removeProject(id) {
146
218
  // Resolve the repo path BEFORE the DB row is deleted below so we can drop the
147
219
  // shared artifact-registry entry. Prefer the live context, fall back to the
148
220
  // desktop DB row (project may be registered-but-not-loaded, e.g. M9 failure).
149
- const repoPath = this._contexts.get(id)?.project.path ?? (0, desktop_db_1.getProject)(this._desktopDb, id)?.path;
221
+ const persistedProject = (0, desktop_db_1.getProject)(this._desktopDb, id);
222
+ const repoPath = this._contexts.get(id)?.project.path ?? persistedProject?.path;
150
223
  const ctx = this._contexts.get(id);
151
224
  if (ctx) {
225
+ // Invalidate async route continuations before any killed child can emit a
226
+ // late `close` against the DB that this removal is about to close.
227
+ (0, process_admission_1.beginProjectProcessQuiescence)(id);
152
228
  // Tear down spawners BEFORE closing the DB. QueueManager.shutdown() drops
153
229
  // its DB handle so a late child 'close' can't run prepared statements on
154
230
  // the closed connection (which would crash the app) and terminates any
@@ -156,10 +232,21 @@ class ProjectRegistry {
156
232
  // kills in-flight chat/Explore children and clears their idle timers.
157
233
  // SetupManager.abort() stops the 3s install poll and kills install/enrich
158
234
  // children. All are idempotent no-ops when nothing is running.
235
+ let queueRecoveryComplete = false;
159
236
  try {
160
- ctx.queueManager.shutdown();
237
+ queueRecoveryComplete = typeof ctx.queueManager.shutdown === 'function'
238
+ ? ctx.queueManager.shutdown() !== false
239
+ : true;
240
+ }
241
+ catch (err) {
242
+ console.error(`[project-registry] queue shutdown failed for ${id}:`, err);
243
+ }
244
+ if (!queueRecoveryComplete) {
245
+ // The context remains registered with its DB intact. A retry can drain
246
+ // the durable callback/outbox once the external ticket store recovers;
247
+ // deleting the data dir here would make that effect unrecoverable.
248
+ throw new Error('Project removal deferred: terminal job recovery is still pending');
161
249
  }
162
- catch { /* ignore */ }
163
250
  try {
164
251
  ctx.chatManager.shutdown();
165
252
  }
@@ -238,24 +325,20 @@ class ProjectRegistry {
238
325
  ctx.db.close();
239
326
  }
240
327
  catch { /* ignore */ }
241
- // B54: remove the ENTIRE app-managed data dir for this project, not just
242
- // the telemetry subdir. It also holds user-mcp.json (a copy of the user's
243
- // MCP config that can contain API keys), profile snapshots, codex-home, and
244
- // terminal shim dirs — all secret-bearing residue that previously survived
245
- // project removal. Guard on a non-empty slug so we never rm the projects/
246
- // root itself.
247
- try {
248
- const slug = ctx.project.slug;
249
- if (slug && slug.trim() && !slug.includes('/') && !slug.includes('..')) {
250
- const projectDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug);
251
- if (fs_1.default.existsSync(projectDir)) {
252
- fs_1.default.rmSync(projectDir, { recursive: true, force: true });
253
- }
254
- }
255
- }
256
- catch { /* ignore — non-fatal */ }
257
328
  this._contexts.delete(id);
258
329
  }
330
+ // B54: remove the ENTIRE app-managed data dir even when context hydration
331
+ // failed. The persisted row is authoritative for registered-but-not-loaded
332
+ // projects and contains the slug needed for safe cleanup.
333
+ try {
334
+ const slug = ctx?.project.slug ?? persistedProject?.slug;
335
+ if (slug && slug.trim() && !slug.includes('/') && !slug.includes('..')) {
336
+ const projectDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug);
337
+ if (fs_1.default.existsSync(projectDir))
338
+ fs_1.default.rmSync(projectDir, { recursive: true, force: true });
339
+ }
340
+ }
341
+ catch { /* ignore — non-fatal */ }
259
342
  // Drop the relocated WORKSPACE for an adopted project whose REGISTRY slug
260
343
  // differs from the desktop slug. The per-project data-dir rm above only
261
344
  // removes `projects/<desktop-slug>`; an adopted repo's workspace lives under
@@ -288,6 +371,7 @@ class ProjectRegistry {
288
371
  console.error('[project-registry] registry remove failed (non-fatal):', err);
289
372
  }
290
373
  }
374
+ this._failedProjects.delete(id);
291
375
  (0, desktop_db_1.removeProject)(this._desktopDb, id);
292
376
  }
293
377
  getContext(id) {
@@ -376,17 +460,19 @@ class ProjectRegistry {
376
460
  const existing = this._contexts.get(project.id);
377
461
  if (existing)
378
462
  return existing;
463
+ // A context is visible synchronously, but no subprocess launch may enter
464
+ // until durable loop callbacks and isolated worktrees have been reconciled.
465
+ (0, process_admission_1.beginProjectProcessQuiescence)(project.id);
379
466
  const db = (0, db_1.initDb)(project.db_path);
380
467
  // Bind broadcast with projectId so all WS messages carry context.
381
468
  // Also wire agent status: when a queued job reaches a terminal state,
382
469
  // clear current_job_id on any agent that was assigned to it.
383
- const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'canceled']);
384
470
  const boundBroadcast = (msg) => {
385
471
  const enriched = { ...msg, projectId: project.id };
386
472
  this._broadcast(enriched);
387
473
  if (msg.type === 'queue') {
388
474
  for (const job of msg.jobs) {
389
- if (TERMINAL_JOB_STATUSES.has(job.status)) {
475
+ if (isTerminalJobStatus(job.status)) {
390
476
  (0, desktop_db_1.clearAgentJob)(this._desktopDb, job.id);
391
477
  }
392
478
  }
@@ -405,6 +491,10 @@ class ProjectRegistry {
405
491
  catch { /* queue_state table may not exist yet */ }
406
492
  const webhookManager = this._webhookManager;
407
493
  const railJobs = new Map();
494
+ const ticketStorePath = () => {
495
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
496
+ return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
497
+ };
408
498
  // Jira sync (per-project, inert until a connection is configured). Constructed
409
499
  // before QueueManager so the onJobFinished closure can reference it.
410
500
  const jiraSyncManager = new jira_sync_manager_1.JiraSyncManager({
@@ -419,6 +509,26 @@ class ProjectRegistry {
419
509
  projectId: project.id,
420
510
  projectSlug: project.slug,
421
511
  desktopPort: this._desktopPort,
512
+ onJobAdmission: (projectDb, job) => {
513
+ const ticketIds = (0, ticket_store_1.extractTicketIdsFromCommand)(job.command);
514
+ (0, rails_store_1.claimTicketOutcomeOwners)(projectDb, ticketIds, job.id);
515
+ const wanted = new Set(ticketIds);
516
+ for (const rail of (0, rails_store_1.getRails)(projectDb)) {
517
+ const assigned = rail.ticketIds.filter((ticketId) => wanted.has(ticketId));
518
+ if (assigned.length > 0)
519
+ (0, rails_store_1.claimRailTickets)(projectDb, rail.railIndex, assigned, job.id);
520
+ }
521
+ // Distinguish current launches from genuinely pre-migration rows even
522
+ // when their current ticket owner later changes or becomes unreadable.
523
+ // This update shares the queue-admission transaction with the claims.
524
+ job.causalOwnership = true;
525
+ const marked = projectDb.prepare(`
526
+ UPDATE queued_jobs SET causal_ownership = 1 WHERE id = ?
527
+ `).run(job.id);
528
+ if (marked.changes !== 1) {
529
+ throw new Error(`Failed to persist causal admission for job ${job.id}`);
530
+ }
531
+ },
422
532
  getCostAlertThreshold: () => {
423
533
  const val = (0, desktop_db_1.getDesktopSetting)(this._desktopDb, 'cost_alert_threshold_usd');
424
534
  return val != null ? parseFloat(val) : null;
@@ -434,6 +544,9 @@ class ProjectRegistry {
434
544
  return { budget, totalSpend };
435
545
  },
436
546
  onBudgetExceeded: (event, data) => {
547
+ if (event === 'desktop_daily_budget_exceeded') {
548
+ this._pauseAllQueuesForDesktopBudget();
549
+ }
437
550
  // Deliver the budget event to this project's subscribed webhooks — the
438
551
  // WS broadcast alone never reached webhook subscribers, so a
439
552
  // daily_budget_exceeded / desktop_daily_budget_exceeded subscription was
@@ -444,15 +557,32 @@ class ProjectRegistry {
444
557
  catch { /* best-effort */ }
445
558
  },
446
559
  onJobFinished: (jobId, status, costUsd, opts) => {
447
- const jobRow = db.prepare('SELECT command, duration_ms FROM jobs WHERE id = ?').get(jobId);
560
+ // Recovery can deliver this callback without another queue snapshot.
561
+ // Clear the assignment here as the authoritative terminal effect; the
562
+ // bound queue broadcast above remains an idempotent live-path fallback.
563
+ if (isTerminalJobStatus(status)) {
564
+ (0, desktop_db_1.clearAgentJob)(this._desktopDb, jobId);
565
+ }
566
+ const jobRow = db.prepare('SELECT command, duration_ms, causal_ownership FROM jobs WHERE id = ?').get(jobId);
567
+ // Recovery callbacks carry their own immutable inputs. Job history is
568
+ // independently deletable/purgeable once a row is terminal; replaying
569
+ // from that row would otherwise checkpoint an empty callback and strand
570
+ // the ticket/rail state forever.
571
+ const recoveryCommand = opts?.recoveryReplay ? opts.recoveryCommand : undefined;
572
+ const effectiveCommand = recoveryCommand ?? jobRow?.command ?? '';
573
+ const effectiveDurationMs = opts?.recoveryReplay
574
+ ? (opts.recoveryDurationMs ?? jobRow?.duration_ms ?? null)
575
+ : (jobRow?.duration_ms ?? null);
576
+ const ticketOutcomeStatus = status === 'skipped'
577
+ ? 'canceled'
578
+ : status;
579
+ const affectsTickets = status === 'completed' || status === 'failed' ||
580
+ status === 'canceled' || status === 'zombie_terminated' || status === 'skipped';
448
581
  const event = status === 'completed' ? 'job.completed' : status === 'canceled' ? 'job.canceled' : 'job.failed';
449
- webhookManager.deliver(project.id, event, {
450
- jobId,
451
- command: jobRow?.command ?? '',
452
- status,
453
- costUsd: costUsd ?? null,
454
- durationMs: jobRow?.duration_ms ?? null,
455
- });
582
+ const criticalFailures = [];
583
+ let outcomeStore = null;
584
+ let changedTicketIds = [];
585
+ let effectTicketIds = [];
456
586
  // Broadcast rail.job_completed if this job was launched by a rail
457
587
  const railMeta = railJobs.get(jobId);
458
588
  if (railMeta) {
@@ -461,11 +591,28 @@ class ProjectRegistry {
461
591
  // Determine ticket IDs: from rail metadata, or parse from command as fallback
462
592
  // (railJobs Map is in-memory and lost on server restart)
463
593
  let completedTicketIds = railMeta?.ticketIds ?? [];
464
- if (completedTicketIds.length === 0 && jobRow?.command) {
465
- const matches = jobRow.command.match(/#(\d+)/g);
594
+ if (completedTicketIds.length === 0 && opts?.recoveryReplay && opts.recoveryTicketIds) {
595
+ completedTicketIds = [...new Set(opts.recoveryTicketIds)];
596
+ }
597
+ if (completedTicketIds.length === 0 && effectiveCommand) {
598
+ const matches = effectiveCommand.match(/#(\d+)/g);
466
599
  if (matches)
467
600
  completedTicketIds = matches.map((m) => parseInt(m.slice(1), 10));
468
601
  }
602
+ const ticketOwners = new Map();
603
+ let ownershipReadable = true;
604
+ try {
605
+ for (const ticketId of completedTicketIds) {
606
+ ticketOwners.set(ticketId, (0, rails_store_1.ticketOutcomeOwner)(db, ticketId));
607
+ }
608
+ }
609
+ catch (err) {
610
+ ownershipReadable = false;
611
+ criticalFailures.push(`ticket ownership: ${err instanceof Error ? err.message : String(err)}`);
612
+ }
613
+ const callbackCausalOwnership = opts?.recoveryReplay
614
+ ? opts.recoveryCausalOwnership === true
615
+ : jobRow?.causal_ownership === 1;
469
616
  // Apply the job outcome to its tickets. Success promotes todo/in_progress
470
617
  // → done (→ Specs Done) — or → on_review under the ask-first PR-delivery
471
618
  // methodology (below); failure/cancel/zombie reverts in_progress → todo
@@ -482,7 +629,7 @@ class ProjectRegistry {
482
629
  // paths) ⇒ 'done', byte-identical to the pre-change promotion.
483
630
  const completedStatus = opts?.ticketCompletionStatus ?? 'done';
484
631
  if (completedTicketIds.length > 0 &&
485
- (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated')) {
632
+ affectsTickets) {
486
633
  try {
487
634
  // Relocate-artifacts gate: write the job outcome to the workspace
488
635
  // ticket store when relocated, else the repo-relative store (legacy).
@@ -491,53 +638,44 @@ class ProjectRegistry {
491
638
  ? outcomeExec.ticketsPath
492
639
  : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
493
640
  const now = new Date().toISOString();
494
- let changedIds = [];
641
+ const causallyOwnedTicketIds = completedTicketIds.filter((ticketId) => {
642
+ if (!ownershipReadable)
643
+ return false;
644
+ const owner = ticketOwners.get(ticketId) ?? null;
645
+ return owner === jobId || (!callbackCausalOwnership && owner === null);
646
+ });
495
647
  const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
496
- changedIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, completedTicketIds, status, now, { completedStatus });
648
+ changedTicketIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, causallyOwnedTicketIds, ticketOutcomeStatus, now, {
649
+ completedStatus,
650
+ effectId: jobId,
651
+ causalOwnerConfirmed: true,
652
+ });
653
+ });
654
+ outcomeStore = store;
655
+ effectTicketIds = causallyOwnedTicketIds.filter((ticketId) => {
656
+ const marker = store.tickets[String(ticketId)]?.metadata.specrails_outcome;
657
+ return marker?.owner_id === jobId && marker.applied_effect_id === jobId;
497
658
  });
498
- for (const tid of changedIds) {
659
+ for (const tid of changedTicketIds) {
499
660
  const ticket = store.tickets[String(tid)];
500
661
  if (!ticket)
501
662
  continue;
502
- boundBroadcast({
503
- type: 'ticket_updated',
504
- ticket: ticket,
505
- projectId: project.id,
506
- timestamp: ticket.updated_at,
507
- });
508
- }
509
- // Jira write-back (inert for non-Jira projects): enqueue the status
510
- // transition + completion comment per linked ticket. The LOCAL mutation
511
- // above stays synchronous; only the durable outbox enqueue happens here,
512
- // wrapped so a Jira failure can never break the job-exit handler.
513
- if (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated') {
514
663
  try {
515
- if (status === 'completed' && completedStatus === 'on_review') {
516
- // Under review, not done — enqueue the Jira on_review
517
- // transition instead of the done-flavoured completion
518
- // (comment + Done). Mirrors onLoopRunFinished's split; the
519
- // Done transition rides the later PR-merge / manual move.
520
- jiraSyncManager.onRailReview(changedIds, jobId);
521
- }
522
- else {
523
- const needsReviewIds = completedTicketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
524
- jiraSyncManager.onJobOutcome({
525
- ticketIds: completedTicketIds,
526
- status,
527
- jobId,
528
- costUsd: costUsd ?? null,
529
- durationMs: jobRow?.duration_ms ?? null,
530
- needsReviewIds,
531
- });
532
- }
664
+ boundBroadcast({
665
+ type: 'ticket_updated',
666
+ ticket: ticket,
667
+ projectId: project.id,
668
+ timestamp: ticket.updated_at,
669
+ });
533
670
  }
534
- catch (err) {
535
- console.error('[project-registry] jira onJobOutcome failed:', err);
671
+ catch {
672
+ // The file mutation is authoritative; a startup client reloads it.
536
673
  }
537
674
  }
538
675
  }
539
676
  catch (err) {
540
677
  console.error('[project-registry] failed to apply job outcome to tickets:', err);
678
+ criticalFailures.push(`ticket outcome: ${err instanceof Error ? err.message : String(err)}`);
541
679
  }
542
680
  }
543
681
  // Release the job's tickets from any rail that still holds them. The
@@ -549,40 +687,113 @@ class ProjectRegistry {
549
687
  // rails (not just railMeta.railIndex) so it also heals after a server
550
688
  // restart, when the in-memory railJobs map is lost.
551
689
  if (completedTicketIds.length > 0 &&
552
- (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated')) {
690
+ affectsTickets) {
553
691
  try {
554
- for (const rail of (0, rails_store_1.getRails)(db)) {
555
- const remaining = rail.ticketIds.filter((id) => !completedTicketIds.includes(id));
556
- if (remaining.length === rail.ticketIds.length)
557
- continue;
558
- (0, rails_store_1.setRailTickets)(db, rail.railIndex, remaining, rail.mode, rail.profileName, rail.aiEngine);
559
- boundBroadcast({
560
- type: 'rail.updated',
561
- projectId: project.id,
562
- railIndex: rail.railIndex,
563
- changed: 'tickets',
564
- ticketIds: remaining,
565
- name: rail.name ?? null,
566
- mode: rail.mode,
567
- profileName: rail.profileName ?? null,
568
- aiEngine: rail.aiEngine ?? null,
569
- });
692
+ const releasableTicketIds = completedTicketIds.filter((ticketId) => {
693
+ if (!ownershipReadable)
694
+ return false;
695
+ const owner = ticketOwners.get(ticketId) ?? null;
696
+ return owner === jobId || (!callbackCausalOwnership && owner === null);
697
+ });
698
+ const releasedRails = (0, rails_store_1.releaseRailTicketsOwnedBy)(db, jobId, releasableTicketIds, {
699
+ railIndex: railMeta?.railIndex ?? null,
700
+ allowUnowned: !callbackCausalOwnership,
701
+ });
702
+ for (const released of releasedRails) {
703
+ const rail = released.rail;
704
+ try {
705
+ boundBroadcast({
706
+ type: 'rail.updated',
707
+ projectId: project.id,
708
+ railIndex: rail.railIndex,
709
+ changed: 'tickets',
710
+ ticketIds: rail.ticketIds,
711
+ name: rail.name ?? null,
712
+ mode: rail.mode,
713
+ profileName: rail.profileName ?? null,
714
+ aiEngine: rail.aiEngine ?? null,
715
+ });
716
+ }
717
+ catch {
718
+ // Persisted rail state is authoritative; WS delivery is advisory.
719
+ }
570
720
  }
571
721
  }
572
722
  catch (err) {
573
723
  console.error('[project-registry] failed to release rail tickets after job exit:', err);
724
+ criticalFailures.push(`rail release: ${err instanceof Error ? err.message : String(err)}`);
725
+ }
726
+ }
727
+ // QueueManager's recovery outbox checkpoints the callback only when it
728
+ // returns normally. Ticket-file and rail mutations are the two critical
729
+ // domain invariants, so surface either failure during a recovery replay.
730
+ // Live exits keep their long-standing best-effort behavior because they
731
+ // have no durable retry owner; they still continue to Jira/webhooks below.
732
+ // Both critical operations are idempotent, so partial replay is safe.
733
+ if (criticalFailures.length > 0 && opts?.recoveryReplay) {
734
+ throw new Error(`critical job outcome effects failed: ${criticalFailures.join('; ')}`);
735
+ }
736
+ // Jira is already backed by its own idempotent durable outbox. Keep it
737
+ // best-effort for this callback: a Jira configuration/network fault must
738
+ // never prevent the local recovery checkpoint.
739
+ if (outcomeStore &&
740
+ effectTicketIds.length > 0 &&
741
+ affectsTickets) {
742
+ try {
743
+ if (status === 'completed' && completedStatus === 'on_review') {
744
+ // On a replay after the ticket mutation committed but a later rail
745
+ // mutation failed, changedTicketIds is empty. Derive the intended
746
+ // review set from the converged store so Jira still receives it.
747
+ const reviewIds = effectTicketIds.filter((tid) => outcomeStore?.tickets[String(tid)]?.status === 'on_review');
748
+ jiraSyncManager.onRailReview(reviewIds, jobId);
749
+ }
750
+ else {
751
+ const needsReviewIds = effectTicketIds.filter((tid) => outcomeStore?.tickets[String(tid)]?.needs_review === true);
752
+ jiraSyncManager.onJobOutcome({
753
+ ticketIds: effectTicketIds,
754
+ status: ticketOutcomeStatus,
755
+ jobId,
756
+ costUsd: costUsd ?? null,
757
+ durationMs: effectiveDurationMs,
758
+ needsReviewIds,
759
+ });
760
+ }
761
+ }
762
+ catch (err) {
763
+ console.error('[project-registry] jira onJobOutcome failed:', err);
764
+ }
765
+ }
766
+ // Webhook delivery is fire-and-forget and non-durable by design. A bad
767
+ // registration/desktop DB must not hold the local recovery outbox open.
768
+ if (status !== 'skipped') {
769
+ try {
770
+ webhookManager.deliver(project.id, event, {
771
+ jobId,
772
+ command: effectiveCommand,
773
+ status,
774
+ costUsd: costUsd ?? null,
775
+ durationMs: effectiveDurationMs,
776
+ });
777
+ }
778
+ catch (err) {
779
+ console.error('[project-registry] webhook delivery scheduling failed:', err);
574
780
  }
575
781
  }
576
782
  // Broadcast rail.job_completed if we know the rail index
577
783
  if (railMeta) {
578
- boundBroadcast({
579
- type: 'rail.job_completed',
580
- projectId: project.id,
581
- railIndex: railMeta.railIndex,
582
- jobId,
583
- status,
584
- ticketIds: completedTicketIds,
585
- });
784
+ try {
785
+ boundBroadcast({
786
+ type: 'rail.job_completed',
787
+ projectId: project.id,
788
+ railIndex: railMeta.railIndex,
789
+ jobId,
790
+ status,
791
+ ticketIds: completedTicketIds,
792
+ });
793
+ }
794
+ catch {
795
+ // Advisory only; local ticket/rail state already committed.
796
+ }
586
797
  }
587
798
  },
588
799
  });
@@ -670,20 +881,14 @@ class ProjectRegistry {
670
881
  });
671
882
  // ── Loops engine (the Loops feature) ──────────────────────────────────────
672
883
  const railLoopRuns = new Map();
673
- // A restart kills any in-flight loop process, so reconcile orphan 'running'
674
- // rows to terminal — otherwise they keep the loop un-editable (isRunning 409).
884
+ // Capture before runtime construction. The status transition is deliberately
885
+ // delayed until onLoopRunFinished exists so crash recovery replays ticket,
886
+ // rail and Jira invariants instead of bypassing them with raw SQL.
887
+ let orphanLoopRuns = [];
675
888
  try {
676
- const orphans = (0, loop_runs_store_1.reconcileOrphanLoopRuns)(db, new Date().toISOString());
677
- if (orphans > 0)
678
- console.log(`[loops] reconciled ${orphans} orphan loop run(s) for ${project.slug}`);
889
+ orphanLoopRuns = (0, loop_runs_store_1.listActiveLoopRuns)(db, project.id);
679
890
  }
680
891
  catch { /* non-fatal */ }
681
- // Sweep worktrees left behind by a crashed parallel-rail fan-out (no-op +
682
- // no git calls when isolation was never used). Best-effort, non-blocking.
683
- void (0, rail_isolated_launch_1.reconcileRailWorktrees)(db, project.path)
684
- .then((n) => { if (n > 0)
685
- console.log(`[loops] reconciled ${n} orphan worktree(s) for ${project.slug}`); })
686
- .catch(() => { });
687
892
  // Loop executors resolve their base env LAZILY per step: a RELOCATED project
688
893
  // injects core's env-first artifact indirection (tickets/backlog/profiles/
689
894
  // state → the workspace) so an ISOLATED worktree run — whose cwd-relative
@@ -724,93 +929,286 @@ class ProjectRegistry {
724
929
  // emitted loop.run_completed; this mirrors onJobFinished's ticket/rail
725
930
  // release (kept separate so the critical job path is untouched).
726
931
  const onLoopRunFinished = (runId, outcome, opts) => {
932
+ const intent = (0, loop_runs_store_1.getLoopTerminalRecovery)(db, runId);
933
+ let payload = null;
934
+ if (intent) {
935
+ try {
936
+ const parsed = JSON.parse(intent.payload);
937
+ if (parsed?.runId !== runId || !Array.isArray(parsed.ticketIds)) {
938
+ throw new Error('loop terminal payload identity is invalid');
939
+ }
940
+ payload = parsed;
941
+ }
942
+ catch (err) {
943
+ console.error(`[loops] invalid terminal recovery payload for ${runId}:`, err);
944
+ return;
945
+ }
946
+ }
727
947
  const meta = railLoopRuns.get(runId);
728
- if (!meta)
729
- return;
730
- railLoopRuns.delete(runId);
731
- const ticketIds = meta.ticketIds;
732
- if (ticketIds.length === 0)
948
+ if (!intent && meta?.requiresTerminalIntent) {
949
+ // Current launches atomically settle their run/job/outbox. If there is
950
+ // no outbox, the launch either never committed or failed before the
951
+ // engine acquired terminal authority. Do not release/revert tickets by
952
+ // falling through to the pre-outbox compatibility path.
953
+ if (!(0, loop_runs_store_1.getLoopRun)(db, runId))
954
+ railLoopRuns.delete(runId);
733
955
  return;
734
- // `blocked`/`stalled` (Decider-flagged human blocker / non-convergence
735
- // abort) are controlled halts: treat like a user stop → `canceled`, so
736
- // tickets revert to todo (never done/on_review) and no PR delivery fires.
737
- const status = outcome === 'success' ? 'completed'
738
- : outcome === 'stopped' || outcome === 'blocked' || outcome === 'stalled' ? 'canceled'
956
+ }
957
+ if (payload && payload.outcomeFinalized === false) {
958
+ const finalizedOutcome = outcome;
959
+ payload = {
960
+ ...payload,
961
+ outcome: finalizedOutcome,
962
+ jobStatus: finalizedOutcome === 'success'
963
+ ? 'completed'
964
+ : finalizedOutcome === 'stopped' || finalizedOutcome === 'blocked' || finalizedOutcome === 'stalled'
965
+ ? 'canceled'
966
+ : 'failed',
967
+ outcomeFinalized: true,
968
+ };
969
+ try {
970
+ // Monotonic decision commit precedes effect delivery. A rail/ticket
971
+ // failure may roll back its own transaction, but can never revert a
972
+ // verified isolated result to the conservative startup fallback.
973
+ db.prepare(`UPDATE loop_terminal_recovery SET payload = ? WHERE run_id = ?`)
974
+ .run(JSON.stringify(payload), runId);
975
+ }
976
+ catch (err) {
977
+ console.error(`[loops] failed to persist final outcome for ${runId}:`, err);
978
+ return;
979
+ }
980
+ }
981
+ const ticketIds = payload?.ticketIds ?? meta?.ticketIds ?? [];
982
+ const railIndex = payload?.railIndex ?? meta?.railIndex ?? null;
983
+ const effectiveOutcome = payload?.outcome ?? outcome;
984
+ const status = effectiveOutcome === 'success' ? 'completed'
985
+ : effectiveOutcome === 'stopped' || effectiveOutcome === 'blocked' || effectiveOutcome === 'stalled'
986
+ ? 'canceled'
739
987
  : 'failed';
740
- // Ask-first PR delivery parks a completed run's tickets at on_review (the
741
- // user decides done vs discard via the PR decision flow / a manual move).
742
- // Explicit opts (the isolated-rail launch passes its launch-captured
743
- // prMode) always win — idempotent with the default. ABSENT opts
744
- // (shared-cwd rail runs, standalone loop runs, isolation-unavailable
745
- // fallbacks) derive the default from the PR-delivery flag, read ONCE here
746
- // and reused by the Jira split below so one settle can never split across
747
- // the two paths. Kill-switch off ⇒ 'done', the legacy promotion
748
- // byte-identical; failures ignore the field.
749
- const completedStatus = opts?.ticketCompletionStatus ?? ((0, rail_isolation_1.isRailPrDeliveryEnabled)() ? 'on_review' : 'done');
988
+ const completedStatus = payload?.ticketCompletionStatus
989
+ ?? opts?.ticketCompletionStatus
990
+ ?? ((0, rail_isolation_1.isRailPrDeliveryEnabled)() ? 'on_review' : 'done');
750
991
  try {
751
- const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
752
- const ticketFile = exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
753
- const now = new Date().toISOString();
754
- let changedIds = [];
755
- const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
756
- changedIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, ticketIds, status, now, { completedStatus });
992
+ const deliver = db.transaction(() => {
993
+ let changedIds = [];
994
+ let effectTicketIds = [];
995
+ let store = null;
996
+ const causallyOwnedTicketIds = ticketIds.filter((ticketId) => {
997
+ const owner = (0, rails_store_1.ticketOutcomeOwner)(db, ticketId);
998
+ return owner === runId || (payload?.causalOwnership !== true && owner === null);
999
+ });
1000
+ if (causallyOwnedTicketIds.length > 0) {
1001
+ store = (0, ticket_store_1.mutateStore)(ticketStorePath(), (s) => {
1002
+ changedIds = (0, ticket_store_1.applyJobOutcomeToTickets)(s, causallyOwnedTicketIds, status, new Date().toISOString(), {
1003
+ completedStatus,
1004
+ effectId: runId,
1005
+ causalOwnerConfirmed: true,
1006
+ });
1007
+ });
1008
+ effectTicketIds = causallyOwnedTicketIds.filter((ticketId) => {
1009
+ const marker = store?.tickets[String(ticketId)]?.metadata.specrails_outcome;
1010
+ return marker?.owner_id === runId && marker.applied_effect_id === runId;
1011
+ });
1012
+ }
1013
+ const released = (0, rails_store_1.releaseRailTicketsOwnedBy)(db, runId, causallyOwnedTicketIds, {
1014
+ railIndex,
1015
+ allowUnowned: payload ? payload.causalOwnership !== true : true,
1016
+ });
1017
+ // Jira has its own idempotent outbox keyed by runId/ticketId. Only
1018
+ // tickets whose causal marker belongs to this run may be enqueued.
1019
+ if (store && effectTicketIds.length > 0) {
1020
+ try {
1021
+ if (status === 'completed' && completedStatus === 'on_review') {
1022
+ const reviewIds = effectTicketIds.filter((ticketId) => store?.tickets[String(ticketId)]?.status === 'on_review');
1023
+ jiraSyncManager.onRailReview(reviewIds, runId);
1024
+ }
1025
+ else {
1026
+ const needsReviewIds = effectTicketIds.filter((ticketId) => store?.tickets[String(ticketId)]?.needs_review === true);
1027
+ jiraSyncManager.onJobOutcome({
1028
+ ticketIds: effectTicketIds,
1029
+ status,
1030
+ jobId: runId,
1031
+ costUsd: null,
1032
+ durationMs: null,
1033
+ needsReviewIds,
1034
+ });
1035
+ }
1036
+ }
1037
+ catch (err) {
1038
+ console.error('[project-registry] jira loop onJobOutcome failed:', err);
1039
+ }
1040
+ }
1041
+ if (intent)
1042
+ (0, loop_runs_store_1.completeLoopTerminalRecovery)(db, runId);
1043
+ return { changedIds, store, released };
757
1044
  });
758
- for (const tid of changedIds) {
759
- const ticket = store.tickets[String(tid)];
1045
+ const delivered = deliver();
1046
+ railLoopRuns.delete(runId);
1047
+ for (const ticketId of delivered.changedIds) {
1048
+ const ticket = delivered.store?.tickets[String(ticketId)];
760
1049
  if (!ticket)
761
1050
  continue;
762
- boundBroadcast({
763
- type: 'ticket_updated',
764
- ticket: ticket,
765
- projectId: project.id,
766
- timestamp: ticket.updated_at,
1051
+ try {
1052
+ boundBroadcast({
1053
+ type: 'ticket_updated',
1054
+ ticket: ticket,
1055
+ projectId: project.id,
1056
+ timestamp: ticket.updated_at,
1057
+ });
1058
+ }
1059
+ catch { /* persisted file is authoritative */ }
1060
+ }
1061
+ for (const released of delivered.released) {
1062
+ try {
1063
+ boundBroadcast({
1064
+ type: 'rail.updated',
1065
+ projectId: project.id,
1066
+ railIndex: released.rail.railIndex,
1067
+ changed: 'tickets',
1068
+ ticketIds: released.rail.ticketIds,
1069
+ name: released.rail.name ?? null,
1070
+ mode: released.rail.mode,
1071
+ profileName: released.rail.profileName ?? null,
1072
+ aiEngine: released.rail.aiEngine ?? null,
1073
+ });
1074
+ }
1075
+ catch { /* advisory */ }
1076
+ }
1077
+ }
1078
+ catch (err) {
1079
+ // Durable intents remain pending. Ticket JSON may already contain the
1080
+ // effect marker; replay is safe and cannot outrank a later owner.
1081
+ console.error(`[loops] terminal effects failed for ${runId}:`, err);
1082
+ }
1083
+ };
1084
+ const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb };
1085
+ this._contexts.set(project.id, ctx);
1086
+ const loopRecoveryOk = this._recoverOrphanLoopRuns(project, db, railLoopRuns, onLoopRunFinished, orphanLoopRuns);
1087
+ if (loopRecoveryOk) {
1088
+ // Decision tokens belong to the dead process. Clear them before any
1089
+ // recovery projection while admission is still closed; otherwise every
1090
+ // action on the card remains disabled and cannot trigger lease reclaim.
1091
+ const orphanedDecisionLeases = (0, rail_pr_store_1.clearOrphanedPrDeliveryOperations)(db);
1092
+ if (orphanedDecisionLeases > 0) {
1093
+ console.log(`[safe-pr] cleared ${orphanedDecisionLeases} orphaned decision lease(s) for ${project.slug}`);
1094
+ }
1095
+ // Worktree/delivery recovery owns the same repo mutex as launch
1096
+ // allocation. Admission opens only after it settles, so a request cannot
1097
+ // reuse a ticket-keyed path while startup still inspects it.
1098
+ void (0, rail_isolated_launch_1.reconcileRailWorktrees)(db, project.path)
1099
+ .then(async (n) => {
1100
+ if (n > 0)
1101
+ console.log(`[loops] reconciled ${n} orphan worktree(s) for ${project.slug}`);
1102
+ if (this._contexts.get(project.id) === ctx) {
1103
+ const effectDeps = {
1104
+ db,
1105
+ project: { id: project.id, slug: project.slug, path: project.path },
1106
+ broadcast: boundBroadcast,
1107
+ jiraSyncManager,
1108
+ };
1109
+ const ticketEffects = await (0, rail_pr_ticket_effects_1.replayRailPrTicketEffectsUntilSettled)(effectDeps, {
1110
+ isCurrent: () => this._contexts.get(project.id) === ctx,
1111
+ onAttempt: (result) => {
1112
+ if (result.attempted > 0) {
1113
+ console.log(`[safe-pr] replayed ${result.completed}/${result.attempted} ticket effect(s) for ${project.slug}; ${result.pending} pending`);
1114
+ // A failed attempt persists cleanup_incomplete on the terminal
1115
+ // delivery; project it immediately while admission remains
1116
+ // closed so both cards explain the recovery state.
1117
+ for (const deliveryId of result.attemptedDeliveryIds) {
1118
+ emitRecoveredPrDelivery(ctx, deliveryId);
1119
+ }
1120
+ }
1121
+ },
767
1122
  });
1123
+ if (!ticketEffects.settled || this._contexts.get(project.id) !== ctx)
1124
+ return;
1125
+ reprojectActivePrDeliveries(ctx);
1126
+ (0, process_admission_1.openProjectProcessAdmission)(project.id);
768
1127
  }
1128
+ })
1129
+ .catch((err) => {
1130
+ console.error(`[loops] isolated recovery failed for ${project.slug}; admission remains closed:`, err);
1131
+ });
1132
+ }
1133
+ return ctx;
1134
+ }
1135
+ _recoverOrphanLoopRuns(project, db, railLoopRuns, onLoopRunFinished, orphans) {
1136
+ try {
1137
+ const recoveredSteps = (0, loop_run_manager_1.recoverOrphanLoopStepAccounting)(db);
1138
+ if (recoveredSteps > 0) {
1139
+ console.log(`[loops] recovered ${recoveredSteps} interrupted step invocation(s) for ${project.slug}`);
1140
+ }
1141
+ const countByRail = new Map();
1142
+ for (const run of orphans) {
1143
+ if (run.rail_index != null)
1144
+ countByRail.set(run.rail_index, (countByRail.get(run.rail_index) ?? 0) + 1);
1145
+ }
1146
+ const rails = (0, rails_store_1.getRails)(db);
1147
+ const legacyTicketIds = new Map();
1148
+ for (const run of orphans) {
1149
+ if (run.rail_index == null)
1150
+ continue;
1151
+ let persistedIds = [];
769
1152
  try {
770
- if (status === 'completed' && completedStatus === 'on_review') {
771
- // Under review, not done — enqueue the Jira on_review transition
772
- // instead of the done-flavoured completion (comment + Done). The
773
- // Done transition rides the later PR-merge / manual move.
774
- jiraSyncManager.onRailReview(changedIds, runId);
775
- }
776
- else {
777
- const needsReviewIds = ticketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
778
- jiraSyncManager.onJobOutcome({ ticketIds, status, jobId: runId, costUsd: null, durationMs: null, needsReviewIds });
1153
+ const parsed = JSON.parse(run.ticket_ids_json ?? '[]');
1154
+ if (Array.isArray(parsed)) {
1155
+ persistedIds = parsed.filter((id) => Number.isSafeInteger(id) && id > 0);
779
1156
  }
780
1157
  }
781
- catch (err) {
782
- console.error('[project-registry] jira loop onJobOutcome failed:', err);
1158
+ catch { /* pre-migration malformed value → legacy fallback */ }
1159
+ let ticketIds = persistedIds.length > 0
1160
+ ? persistedIds
1161
+ : (run.ticket_id == null ? [] : [run.ticket_id]);
1162
+ // Compatibility only for rows launched before migration 45. New rows
1163
+ // persist the exact set and never use this ambiguous heuristic.
1164
+ if (persistedIds.length === 0 && (countByRail.get(run.rail_index) ?? 0) === 1) {
1165
+ const rail = rails.find((candidate) => candidate.railIndex === run.rail_index);
1166
+ if (rail && rail.ticketIds.length > 0)
1167
+ ticketIds = [...rail.ticketIds];
783
1168
  }
1169
+ legacyTicketIds.set(run.id, ticketIds);
1170
+ railLoopRuns.set(run.id, { railIndex: run.rail_index, ticketIds });
784
1171
  }
785
- catch (err) {
786
- console.error('[project-registry] failed to apply loop outcome to tickets:', err);
1172
+ if (orphans.length > 0) {
1173
+ (0, loop_runs_store_1.reconcileOrphanLoopRuns)(db, new Date().toISOString(), legacyTicketIds);
787
1174
  }
788
- try {
789
- for (const rail of (0, rails_store_1.getRails)(db)) {
790
- const remaining = rail.ticketIds.filter((id) => !ticketIds.includes(id));
791
- if (remaining.length === rail.ticketIds.length)
792
- continue;
793
- (0, rails_store_1.setRailTickets)(db, rail.railIndex, remaining, rail.mode, rail.profileName, rail.aiEngine);
794
- boundBroadcast({
795
- type: 'rail.updated',
796
- projectId: project.id,
797
- railIndex: rail.railIndex,
798
- changed: 'tickets',
799
- ticketIds: remaining,
800
- name: rail.name ?? null,
801
- mode: rail.mode,
802
- profileName: rail.profileName ?? null,
803
- aiEngine: rail.aiEngine ?? null,
804
- });
1175
+ // Includes orphan intents created above AND normally-completed runs whose
1176
+ // process died after the atomic settle but before its `.then` callback.
1177
+ const pending = (0, loop_runs_store_1.listPendingLoopTerminalRecoveries)(db);
1178
+ for (const row of pending) {
1179
+ let terminalOutcome = 'failed';
1180
+ try {
1181
+ const payload = JSON.parse(row.payload);
1182
+ terminalOutcome = payload.outcome;
1183
+ if (payload.outcomeFinalized === false) {
1184
+ const durableRun = (0, loop_runs_store_1.getLoopRun)(db, row.run_id);
1185
+ if (durableRun?.status === 'completed' && durableRun.final_outcome) {
1186
+ terminalOutcome = durableRun.final_outcome;
1187
+ }
1188
+ }
805
1189
  }
1190
+ catch { /* handler logs and retains malformed intent */ }
1191
+ onLoopRunFinished(row.run_id, terminalOutcome);
806
1192
  }
807
- catch (err) {
808
- console.error('[project-registry] failed to release rail tickets after loop run:', err);
1193
+ if (orphans.length > 0) {
1194
+ console.log(`[loops] reconciled ${orphans.length} orphan loop run(s) for ${project.slug}`);
809
1195
  }
810
- };
811
- const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs, loopRunManager, railLoopRuns, onLoopRunFinished, getTicketSpec, desktopDb: this._desktopDb };
812
- this._contexts.set(project.id, ctx);
813
- return ctx;
1196
+ return true;
1197
+ }
1198
+ catch (err) {
1199
+ console.error(`[loops] orphan reconciliation failed for ${project.slug}:`, err);
1200
+ return false;
1201
+ }
1202
+ }
1203
+ /** Enforce the app-wide budget through every per-project queue authority. */
1204
+ _pauseAllQueuesForDesktopBudget() {
1205
+ for (const context of this.listContexts()) {
1206
+ try {
1207
+ if (!context.queueManager.isPaused())
1208
+ context.queueManager.pause();
1209
+ }
1210
+ catch { /* a concurrently removed project is best-effort */ }
1211
+ }
814
1212
  }
815
1213
  }
816
1214
  exports.ProjectRegistry = ProjectRegistry;