specrails-desktop 2.3.0 → 2.5.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 (345) hide show
  1. package/client/dist/assets/ActivityFeedPage-BTYWMRwB.js +1 -0
  2. package/client/dist/assets/AgentsPage-BfOCeHHt.js +86 -0
  3. package/client/dist/assets/{AnalyticsPage-Dyyz1ht3.js → AnalyticsPage-AbVXKh9v.js} +1 -1
  4. package/client/dist/assets/{BarChart-CMdLa6Es.js → BarChart-DlshJN3Z.js} +2 -2
  5. package/client/dist/assets/CodePage-DJCjDG4I.js +2 -0
  6. package/client/dist/assets/{DesktopAnalyticsPage-CTNwb639.js → DesktopAnalyticsPage-CTqZ9mbB.js} +1 -1
  7. package/client/dist/assets/DocsDialog-KiJOSRvX.js +11 -0
  8. package/client/dist/assets/DocsPage-B17CR54A.js +11 -0
  9. package/client/dist/assets/{ExportDropdown-DuoZcdYN.js → ExportDropdown-BAu6z3b6.js} +1 -1
  10. package/client/dist/assets/IntegrationsPage-CCG64Q-6.js +3 -0
  11. package/client/dist/assets/JobDetailPage-BnGJSMiS.js +16 -0
  12. package/client/dist/assets/JobsPage-B-tn4CIf.js +1 -0
  13. package/client/dist/assets/{cssMode-Cc6ozl-J.js → cssMode-DzNPAYFh.js} +1 -1
  14. package/client/dist/assets/dashboard--Ahnvfr3.js +1 -0
  15. package/client/dist/assets/dashboard-BN1C2pEh.js +1 -0
  16. package/client/dist/assets/dashboard-BZs_EzAn.js +1 -0
  17. package/client/dist/assets/dashboard-Bsw44L8_.js +1 -0
  18. package/client/dist/assets/dashboard-Bw3VECgY.js +1 -0
  19. package/client/dist/assets/{dashboard-Duo4DDCW.js → dashboard-CuOshSHn.js} +1 -1
  20. package/client/dist/assets/dashboard-DfouCM3_.js +1 -0
  21. package/client/dist/assets/dashboard-Pp5hwnZB.js +1 -0
  22. package/client/dist/assets/{dist-js-H6hyhSuv.js → dist-js-B16c3VyT.js} +1 -1
  23. package/client/dist/assets/{dist-js-4UEGaKhD.js → dist-js-P2FkJ6fA.js} +1 -1
  24. package/client/dist/assets/{editor.main-CfXxHimg.js → editor.main-C7Rmw-hR.js} +2 -2
  25. package/client/dist/assets/{freemarker2-DP7J1gG3.js → freemarker2-Cszs4SVo.js} +1 -1
  26. package/client/dist/assets/{handlebars-BjRlucw6.js → handlebars-Dp7Lsuym.js} +1 -1
  27. package/client/dist/assets/{html-OumBQJ-U.js → html-BURidrEm.js} +1 -1
  28. package/client/dist/assets/{htmlMode-CStc3zXM.js → htmlMode--k5M7GjZ.js} +1 -1
  29. package/client/dist/assets/index-AfVF6BgE.js +142 -0
  30. package/client/dist/assets/index-NlH5BbXJ.css +2 -0
  31. package/client/dist/assets/{integrations-Cublz3m6.js → integrations-2C7MkGT0.js} +1 -1
  32. package/client/dist/assets/{integrations-HIlUxXVs.js → integrations-BDC670cg.js} +1 -1
  33. package/client/dist/assets/integrations-BqUmRUef.js +1 -0
  34. package/client/dist/assets/{integrations-DmQYCUvN.js → integrations-C2jQtv-s.js} +1 -1
  35. package/client/dist/assets/{integrations-DRdbki5W.js → integrations-CB98NeH5.js} +1 -1
  36. package/client/dist/assets/{integrations-C3p12Ms6.js → integrations-CX4p_bij.js} +1 -1
  37. package/client/dist/assets/{integrations-DaC4SzzL.js → integrations-_SuVeQIG.js} +1 -1
  38. package/client/dist/assets/{integrations-Cr6hH7XR.js → integrations-eQPHAYsE.js} +1 -1
  39. package/client/dist/assets/{javascript-CMk--e7g.js → javascript-kJQz__44.js} +1 -1
  40. package/client/dist/assets/jira-C-ATCti0.js +1 -0
  41. package/client/dist/assets/jira-CmVfRM-b.js +1 -0
  42. package/client/dist/assets/jira-D7bkKAX8.js +1 -0
  43. package/client/dist/assets/jira-DKImM1YH.js +1 -0
  44. package/client/dist/assets/jira-DOw8bkIR.js +1 -0
  45. package/client/dist/assets/jira-DlA-wGp-.js +1 -0
  46. package/client/dist/assets/jira-Fob8EGxN.js +1 -0
  47. package/client/dist/assets/jira-xZA2lixb.js +1 -0
  48. package/client/dist/assets/jobs-BGkI19S_.js +1 -0
  49. package/client/dist/assets/jobs-Brp44JDd.js +1 -0
  50. package/client/dist/assets/jobs-D93lG6If.js +1 -0
  51. package/client/dist/assets/jobs-DAF8AGy5.js +1 -0
  52. package/client/dist/assets/jobs-Db3xrsp_.js +1 -0
  53. package/client/dist/assets/jobs-Do4Ltqdj.js +1 -0
  54. package/client/dist/assets/jobs-F5PGJwbW.js +1 -0
  55. package/client/dist/assets/jobs-fYWWxCUV.js +1 -0
  56. package/client/dist/assets/{jsonMode-C2h3ZcjZ.js → jsonMode-v5JYPpnz.js} +1 -1
  57. package/client/dist/assets/{lib-Cs5FrUJI.js → lib-rNNmltMb.js} +1 -1
  58. package/client/dist/assets/{liquid-mI3KJrBE.js → liquid-Dl9I6gWt.js} +1 -1
  59. package/client/dist/assets/{lspLanguageFeatures-DU09ggWi.js → lspLanguageFeatures-CPlEe0NK.js} +1 -1
  60. package/client/dist/assets/{mdx-C41VDTR_.js → mdx-Byl7TtzQ.js} +1 -1
  61. package/client/dist/assets/{monaco.contribution-CPObAXMC.js → monaco.contribution-YMAkHQcQ.js} +2 -2
  62. package/client/dist/assets/{python-Y27rKQtk.js → python-jWQwT6j2.js} +1 -1
  63. package/client/dist/assets/{razor-Cd5-q9Bp.js → razor-BWS3sP-E.js} +1 -1
  64. package/client/dist/assets/setup-C0dzw8j4.js +1 -0
  65. package/client/dist/assets/setup-C1IA-9YS.js +1 -0
  66. package/client/dist/assets/setup-CpfjaNut.js +1 -0
  67. package/client/dist/assets/setup-D3rNZA9A.js +1 -0
  68. package/client/dist/assets/setup-UD2aanGs.js +1 -0
  69. package/client/dist/assets/setup-WP6WOYQh.js +1 -0
  70. package/client/dist/assets/setup-gzLG8T6F.js +1 -0
  71. package/client/dist/assets/setup-pjgmYHx6.js +1 -0
  72. package/client/dist/assets/{specs-DaUTrNF9.js → specs-B4GuOzuZ.js} +1 -1
  73. package/client/dist/assets/{specs-CZ1PsXsC.js → specs-BVLKe2n5.js} +1 -1
  74. package/client/dist/assets/{specs-k0PyLDVt.js → specs-C62F2CDv.js} +1 -1
  75. package/client/dist/assets/specs-D-Sb6dre.js +1 -0
  76. package/client/dist/assets/{specs-B__C8-8a.js → specs-DFSkAeK8.js} +1 -1
  77. package/client/dist/assets/{specs-BFfu3u-a.js → specs-DfwDeADE.js} +1 -1
  78. package/client/dist/assets/{specs-D2FzlLn9.js → specs-VK-zXv7x.js} +1 -1
  79. package/client/dist/assets/{specs-Dyc5hYeE.js → specs-ghyBMnib.js} +1 -1
  80. package/client/dist/assets/{tsMode-B0y_xEci.js → tsMode-BbOGOuSV.js} +1 -1
  81. package/client/dist/assets/{typescript-BzK0OgwW.js → typescript-eBtFQJLs.js} +1 -1
  82. package/client/dist/assets/{useProjectCache-BZWYV-w-.js → useProjectCache-Cid_GxRM.js} +1 -1
  83. package/client/dist/assets/{workers-rt--R2Qy.js → workers-BvicOoDf.js} +1 -1
  84. package/client/dist/assets/{xml-eX9QXAmI.js → xml-BJepAPyM.js} +1 -1
  85. package/client/dist/assets/{yaml-fcsNkpOt.js → yaml-DabgV-eA.js} +1 -1
  86. package/client/dist/index.html +13 -12
  87. package/docs/jira-integration-plan.md +321 -0
  88. package/package.json +1 -1
  89. package/server/dist/chat-manager.js +19 -7
  90. package/server/dist/context-scope.js +29 -8
  91. package/server/dist/db.js +127 -2
  92. package/server/dist/feature-flags.js +26 -0
  93. package/server/dist/interactive-job-session.js +363 -0
  94. package/server/dist/jira/jira-adf.js +113 -0
  95. package/server/dist/jira/jira-backlog-config.js +58 -0
  96. package/server/dist/jira/jira-client.js +279 -0
  97. package/server/dist/jira/jira-credential-store.js +103 -0
  98. package/server/dist/jira/jira-db.js +341 -0
  99. package/server/dist/jira/jira-issue-fields.js +428 -0
  100. package/server/dist/jira/jira-materializer.js +250 -0
  101. package/server/dist/jira/jira-status-resolver.js +211 -0
  102. package/server/dist/jira/jira-sync-manager.js +1014 -0
  103. package/server/dist/jira/types.js +9 -0
  104. package/server/dist/jira-router.js +304 -0
  105. package/server/dist/project-registry.js +43 -1
  106. package/server/dist/project-router-jobs.js +42 -0
  107. package/server/dist/project-router-tickets.js +49 -1
  108. package/server/dist/project-router.js +4 -0
  109. package/server/dist/queue-manager.js +214 -54
  110. package/server/dist/rails-router.js +27 -1
  111. package/server/dist/util/stream-display.js +66 -0
  112. package/client/dist/assets/ActivityFeedPage-3Veccrvk.js +0 -1
  113. package/client/dist/assets/AgentsPage-2mFPghP4.js +0 -86
  114. package/client/dist/assets/CodePage-D7Xwjhut.js +0 -2
  115. package/client/dist/assets/DocsDialog-D8yoyZDD.js +0 -11
  116. package/client/dist/assets/DocsPage-CeO-fAxy.js +0 -11
  117. package/client/dist/assets/IntegrationsPage-iIZ0UEzf.js +0 -3
  118. package/client/dist/assets/JobDetailPage-DgJHAH2m.js +0 -16
  119. package/client/dist/assets/JobsPage-Bv_RpRAE.js +0 -1
  120. package/client/dist/assets/dashboard-B4ixDVk8.js +0 -1
  121. package/client/dist/assets/dashboard-BZBADHSj.js +0 -1
  122. package/client/dist/assets/dashboard-C1MfeUHs.js +0 -1
  123. package/client/dist/assets/dashboard-C7SK6xu5.js +0 -1
  124. package/client/dist/assets/dashboard-CB6Le1yN.js +0 -1
  125. package/client/dist/assets/dashboard-CoTpMOBM.js +0 -1
  126. package/client/dist/assets/dashboard-I19DXBxw.js +0 -1
  127. package/client/dist/assets/index-CGHKpC-N.js +0 -142
  128. package/client/dist/assets/index-D17R4Cjc.css +0 -2
  129. package/client/dist/assets/integrations-D28q1kF6.js +0 -1
  130. package/client/dist/assets/jobs-2N3RXDAM.js +0 -1
  131. package/client/dist/assets/jobs-2f6Hdc72.js +0 -1
  132. package/client/dist/assets/jobs-3j3_npyo.js +0 -1
  133. package/client/dist/assets/jobs-BqEbCCxD.js +0 -1
  134. package/client/dist/assets/jobs-DPPT6bV6.js +0 -1
  135. package/client/dist/assets/jobs-DRzjWI9u.js +0 -1
  136. package/client/dist/assets/jobs-cHYInoau.js +0 -1
  137. package/client/dist/assets/jobs-vGfzIDQa.js +0 -1
  138. package/client/dist/assets/setup--FMCsnQS.js +0 -1
  139. package/client/dist/assets/setup-B19ZpBNi.js +0 -1
  140. package/client/dist/assets/setup-BZPmkjSN.js +0 -1
  141. package/client/dist/assets/setup-BqYA02rS.js +0 -1
  142. package/client/dist/assets/setup-ChKQDHN9.js +0 -1
  143. package/client/dist/assets/setup-D2n9jMfM.js +0 -1
  144. package/client/dist/assets/setup-P3r6YP1D.js +0 -1
  145. package/client/dist/assets/setup-fnfEbwlv.js +0 -1
  146. package/client/dist/assets/specs-cKEh2LXt.js +0 -1
  147. /package/client/dist/assets/{abap-Bw6f2wDG.js → abap-s65oMlhi.js} +0 -0
  148. /package/client/dist/assets/{activity-BdrPln96.js → activity-BqqwnH_h.js} +0 -0
  149. /package/client/dist/assets/{activity-BEIp_Y1A.js → activity-C8qqEIoP.js} +0 -0
  150. /package/client/dist/assets/{activity-CpkRS8Sx.js → activity-CZVM4nlJ.js} +0 -0
  151. /package/client/dist/assets/{activity-DOUVEjJi.js → activity-Cyy07Tgo.js} +0 -0
  152. /package/client/dist/assets/{activity-DRwkql_y.js → activity-DlbWCa4y.js} +0 -0
  153. /package/client/dist/assets/{activity-DKCpESPt.js → activity-Dwq0heud.js} +0 -0
  154. /package/client/dist/assets/{activity-DcDQ7tjw.js → activity-qFTcMyW9.js} +0 -0
  155. /package/client/dist/assets/{addon-image-3WCl5Vhd.js → addon-image-CpF0L0jM.js} +0 -0
  156. /package/client/dist/assets/{addon-ligatures-C5OdliKs.js → addon-ligatures-hXysGZrA.js} +0 -0
  157. /package/client/dist/assets/{addon-webgl-BbX6pSjl.js → addon-webgl-Cn1slavz.js} +0 -0
  158. /package/client/dist/assets/{addspec-D33ocMxf.js → addspec-B1FTtI2a.js} +0 -0
  159. /package/client/dist/assets/{addspec-DFswZ0jK.js → addspec-BCT9vm_c.js} +0 -0
  160. /package/client/dist/assets/{addspec-DVZ15Jp8.js → addspec-DeDOztDr.js} +0 -0
  161. /package/client/dist/assets/{addspec-Fkv91Opc.js → addspec-DpRgmfmx.js} +0 -0
  162. /package/client/dist/assets/{addspec-BEeF5-zc.js → addspec-Dw-0Dg-4.js} +0 -0
  163. /package/client/dist/assets/{addspec-B5yl4Loj.js → addspec-rp496P_F.js} +0 -0
  164. /package/client/dist/assets/{addspec-DRE-jZv7.js → addspec-v8j6A7CD.js} +0 -0
  165. /package/client/dist/assets/{agents-DK-Dlc0i.js → agents-23iPejcA.js} +0 -0
  166. /package/client/dist/assets/{agents-Q6Ldfpxx.js → agents-BDx1RXcl.js} +0 -0
  167. /package/client/dist/assets/{agents-TeOSy-ax.js → agents-BFr3kUhK.js} +0 -0
  168. /package/client/dist/assets/{agents-Bm9rPqnt.js → agents-B_1L9xRg.js} +0 -0
  169. /package/client/dist/assets/{agents-1nCDWRmP.js → agents-BlPnx-mz.js} +0 -0
  170. /package/client/dist/assets/{agents-iTqjRajS.js → agents-DcxZHzNr.js} +0 -0
  171. /package/client/dist/assets/{agents-s87sMGzL.js → agents-G3shOewU.js} +0 -0
  172. /package/client/dist/assets/{agentstudio-B6Wb59E7.js → agentstudio-B-CMAQqy.js} +0 -0
  173. /package/client/dist/assets/{agentstudio-D3I62TLJ.js → agentstudio-Bk1eZcv4.js} +0 -0
  174. /package/client/dist/assets/{agentstudio-DuH9TogZ.js → agentstudio-ChxNuGAu.js} +0 -0
  175. /package/client/dist/assets/{agentstudio-Kw88_dUF.js → agentstudio-DNlme601.js} +0 -0
  176. /package/client/dist/assets/{agentstudio-BdidyBzZ.js → agentstudio-DpP9caEE.js} +0 -0
  177. /package/client/dist/assets/{agentstudio-BSnWLR63.js → agentstudio-Y3G0ddJ2.js} +0 -0
  178. /package/client/dist/assets/{agentstudio-BADhZ41e.js → agentstudio-kk9RB7Se.js} +0 -0
  179. /package/client/dist/assets/{aiedit-DJMny-D5.js → aiedit-5ETerMK1.js} +0 -0
  180. /package/client/dist/assets/{aiedit-D2ji6Qy0.js → aiedit-BBCrOpHq.js} +0 -0
  181. /package/client/dist/assets/{aiedit-DAhZTvtk.js → aiedit-BMtcGYNE.js} +0 -0
  182. /package/client/dist/assets/{aiedit-DvrcbwGv.js → aiedit-D9ddlgkM.js} +0 -0
  183. /package/client/dist/assets/{aiedit-WBSjT_C1.js → aiedit-De0SOH6S.js} +0 -0
  184. /package/client/dist/assets/{aiedit-BWxHGsYA.js → aiedit-DrfzQroF.js} +0 -0
  185. /package/client/dist/assets/{aiedit-DOcxERkU.js → aiedit-fMltW101.js} +0 -0
  186. /package/client/dist/assets/{analytics-C9Zc-rkM.js → analytics-BeTyviO8.js} +0 -0
  187. /package/client/dist/assets/{analytics-CrPCZRJ-.js → analytics-C4eEO260.js} +0 -0
  188. /package/client/dist/assets/{analytics-CYj0tfj7.js → analytics-C67cIA1b.js} +0 -0
  189. /package/client/dist/assets/{analytics-C6EzgtdE.js → analytics-CAguvW28.js} +0 -0
  190. /package/client/dist/assets/{analytics-CVx3YOc0.js → analytics-DBtt8Mgk.js} +0 -0
  191. /package/client/dist/assets/{analytics-CnY4kNG3.js → analytics-DUPtODxX.js} +0 -0
  192. /package/client/dist/assets/{analytics-BIdr0YfL.js → analytics-YIpQvPAc.js} +0 -0
  193. /package/client/dist/assets/{apex-Cw8_REBo.js → apex-BLUBIldB.js} +0 -0
  194. /package/client/dist/assets/{attachments-DYHGA2Dj.js → attachments-CCWasu-P.js} +0 -0
  195. /package/client/dist/assets/{attachments-Dd92KpUH.js → attachments-CHaDUfjB.js} +0 -0
  196. /package/client/dist/assets/{attachments-DzdU6DV6.js → attachments-CVSAbGNl.js} +0 -0
  197. /package/client/dist/assets/{attachments-Bcf6BG6V.js → attachments-Chg5poG1.js} +0 -0
  198. /package/client/dist/assets/{attachments-BW4L3l2L.js → attachments-DazTVJbH.js} +0 -0
  199. /package/client/dist/assets/{attachments-COcrGRFz.js → attachments-Dn-JImAK.js} +0 -0
  200. /package/client/dist/assets/{attachments-Bke8sCU4.js → attachments-LDA9kp2X.js} +0 -0
  201. /package/client/dist/assets/{azcli-Cz6HAoOw.js → azcli-DuWxh9mO.js} +0 -0
  202. /package/client/dist/assets/{bat-CcJ-xyqL.js → bat-UKoTejQm.js} +0 -0
  203. /package/client/dist/assets/{bicep-z1WDCKYz.js → bicep-4sTT4B3D.js} +0 -0
  204. /package/client/dist/assets/{browser-DGITz3fC.js → browser-BDd1dbFa.js} +0 -0
  205. /package/client/dist/assets/{browser-JsAIGCEW.js → browser-BWSgbfdX.js} +0 -0
  206. /package/client/dist/assets/{browser-M5-rbPlw.js → browser-D2Y_UAKA.js} +0 -0
  207. /package/client/dist/assets/{browser-BlYF4OOq.js → browser-DH9SGVfM.js} +0 -0
  208. /package/client/dist/assets/{browser-Bc-YdlVg.js → browser-DWOVYMlg.js} +0 -0
  209. /package/client/dist/assets/{browser-CT-ReZGt.js → browser-Dxc_VIRK.js} +0 -0
  210. /package/client/dist/assets/{browser-5ErDlJoR.js → browser-lTQwcDCI.js} +0 -0
  211. /package/client/dist/assets/{cameligo-BRewOpfa.js → cameligo-CAAryRYO.js} +0 -0
  212. /package/client/dist/assets/{chat-DwUm6W9z.js → chat-BO9MvVID.js} +0 -0
  213. /package/client/dist/assets/{chat-BEGuC03z.js → chat-CPgmgZOj.js} +0 -0
  214. /package/client/dist/assets/{chat-CboQguCi.js → chat-CUrG1eVg.js} +0 -0
  215. /package/client/dist/assets/{chat-DRCa9pOt.js → chat-CvOOKB2s.js} +0 -0
  216. /package/client/dist/assets/{chat-BEW60P_u.js → chat-DIh3hr6y.js} +0 -0
  217. /package/client/dist/assets/{chat-yoXwguQu.js → chat-UVVZqA57.js} +0 -0
  218. /package/client/dist/assets/{chat-BQNMD0PL.js → chat-mPn3UlMl.js} +0 -0
  219. /package/client/dist/assets/{clojure-DBjRWN6g.js → clojure-BlMERO1w.js} +0 -0
  220. /package/client/dist/assets/{clsx-DnqN-uhr.js → clsx-CnH-HMk3.js} +0 -0
  221. /package/client/dist/assets/{code-zCwBt3Uu.js → code-BwIz8agY.js} +0 -0
  222. /package/client/dist/assets/{code-g0qFMzyg.js → code-CD7yNSK0.js} +0 -0
  223. /package/client/dist/assets/{code-DDU0CRS0.js → code-CDFlxUFC.js} +0 -0
  224. /package/client/dist/assets/{code-L35Loak_.js → code-Cp3Fdng-.js} +0 -0
  225. /package/client/dist/assets/{code-D1z-YDt-.js → code-D24e1Crx.js} +0 -0
  226. /package/client/dist/assets/{code-BtsmPQLV.js → code-DtZBQTi9.js} +0 -0
  227. /package/client/dist/assets/{code-Coa8f2Sh.js → code-nKa0fkm_.js} +0 -0
  228. /package/client/dist/assets/{coffee-Cfk_XHGR.js → coffee-Cj8D-Wl1.js} +0 -0
  229. /package/client/dist/assets/{commands-sqrqsxyE.js → commands-B-MVT-2F.js} +0 -0
  230. /package/client/dist/assets/{commands-UD1NzmwX.js → commands-B0yFTp7e.js} +0 -0
  231. /package/client/dist/assets/{commands-DLrvnPNg.js → commands-BR1kDkHQ.js} +0 -0
  232. /package/client/dist/assets/{commands-CJxCry-o.js → commands-Cb21pDlG.js} +0 -0
  233. /package/client/dist/assets/{commands-CfgY-_of.js → commands-DWgp-8W1.js} +0 -0
  234. /package/client/dist/assets/{commands-B772IyDa.js → commands-ddsl1V91.js} +0 -0
  235. /package/client/dist/assets/{commands-BDDp6xFG.js → commands-t4frzhB0.js} +0 -0
  236. /package/client/dist/assets/{common-Dmm1GhdD.js → common-5ilvMOcH.js} +0 -0
  237. /package/client/dist/assets/{common-DltqHaAe.js → common-B4sqsKp7.js} +0 -0
  238. /package/client/dist/assets/{common-GbpxfPG8.js → common-BKpVwUIf.js} +0 -0
  239. /package/client/dist/assets/{common-DeDELLZJ.js → common-BzEC3kJU.js} +0 -0
  240. /package/client/dist/assets/{common-DnjcgkPH.js → common-CALKUpYm.js} +0 -0
  241. /package/client/dist/assets/{common-Dard9UNH.js → common-CTEbWVZS.js} +0 -0
  242. /package/client/dist/assets/{common-DCr6VzJ7.js → common-DQiza2Xp.js} +0 -0
  243. /package/client/dist/assets/{cpp-BVob6BaP.js → cpp-BPfKnaj_.js} +0 -0
  244. /package/client/dist/assets/{csharp-C4fbRuOu.js → csharp-gX-x5uD6.js} +0 -0
  245. /package/client/dist/assets/{csp-DthFP_vT.js → csp-DKGVt8SM.js} +0 -0
  246. /package/client/dist/assets/{css-CGMH0hcW.js → css-CPMdnAVq.js} +0 -0
  247. /package/client/dist/assets/{cypher-Pnf68BRV.js → cypher-ClMDrj9S.js} +0 -0
  248. /package/client/dist/assets/{dart-PMMOtxZX.js → dart-C4zbzpVv.js} +0 -0
  249. /package/client/dist/assets/{dockerfile-di1nsJCc.js → dockerfile-D9xw73D1.js} +0 -0
  250. /package/client/dist/assets/{ecl-D_WVcB5M.js → ecl-gqO8tIR9.js} +0 -0
  251. /package/client/dist/assets/{editor.api2-XLGzZfbc.js → editor.api2-BPnIxMjz.js} +0 -0
  252. /package/client/dist/assets/{elixir-OAdJEMOn.js → elixir-DSAhVF3_.js} +0 -0
  253. /package/client/dist/assets/{explore-D2EFgt8J.js → explore-BE5UmlbD.js} +0 -0
  254. /package/client/dist/assets/{explore-BV5Xxlsn.js → explore-BmTaI8dX.js} +0 -0
  255. /package/client/dist/assets/{explore-A8Ltoblq.js → explore-CCwkqoWq.js} +0 -0
  256. /package/client/dist/assets/{explore-4mFpnrKU.js → explore-CMdEoPDx.js} +0 -0
  257. /package/client/dist/assets/{explore-C3FSE42C.js → explore-CtdCL4QU.js} +0 -0
  258. /package/client/dist/assets/{explore-B9A3iN2W.js → explore-DHjxSkqQ.js} +0 -0
  259. /package/client/dist/assets/{explore-BrBJvfjP.js → explore-__BeALjE.js} +0 -0
  260. /package/client/dist/assets/{flow9-D3QEZjgn.js → flow9-DeQCSPOd.js} +0 -0
  261. /package/client/dist/assets/{format-command-CwGuwzGA.js → format-command-2VNoNnMv.js} +0 -0
  262. /package/client/dist/assets/{fsharp-BF0k_8N8.js → fsharp-CEfaXL-S.js} +0 -0
  263. /package/client/dist/assets/{go-BAQO5Jsz.js → go-Xp1OkZCh.js} +0 -0
  264. /package/client/dist/assets/{graphql-hdFVFkiV.js → graphql-BwRXrUwe.js} +0 -0
  265. /package/client/dist/assets/{hcl-DWnl1o-X.js → hcl-u06DtVFk.js} +0 -0
  266. /package/client/dist/assets/{ini-CB-6OVu3.js → ini-AmeIpFND.js} +0 -0
  267. /package/client/dist/assets/{java-d1CmfiHX.js → java-CyDbRQjX.js} +0 -0
  268. /package/client/dist/assets/{julia-Bgv08lKa.js → julia-BqialFRG.js} +0 -0
  269. /package/client/dist/assets/{kotlin-u98kaVTf.js → kotlin-Dzz8TWAt.js} +0 -0
  270. /package/client/dist/assets/{less-CjYwpgg5.js → less-DHRJD3TR.js} +0 -0
  271. /package/client/dist/assets/{lexon-YTjaAFBB.js → lexon-5Y3QgTmT.js} +0 -0
  272. /package/client/dist/assets/{lua-BzmkWv27.js → lua-sKvhfPn5.js} +0 -0
  273. /package/client/dist/assets/{m3-CFwk9fw0.js → m3-DWDVwkFG.js} +0 -0
  274. /package/client/dist/assets/{markdown-CR5iMpSZ.js → markdown-CD_aSBxW.js} +0 -0
  275. /package/client/dist/assets/{mips-CcEalc17.js → mips-687T03hg.js} +0 -0
  276. /package/client/dist/assets/{msdax-BQbkawnr.js → msdax-C1St-dIV.js} +0 -0
  277. /package/client/dist/assets/{mysql-GTlaaW_P.js → mysql-BG7r8oBS.js} +0 -0
  278. /package/client/dist/assets/{nav-C2YXcbZS.js → nav-B05EYB0b.js} +0 -0
  279. /package/client/dist/assets/{nav-D2bOGSEg.js → nav-BNGCq-0y.js} +0 -0
  280. /package/client/dist/assets/{nav-BEL3MTwK.js → nav-BRInPX8a.js} +0 -0
  281. /package/client/dist/assets/{nav-CtYwmMgu.js → nav-Bf87DRHD.js} +0 -0
  282. /package/client/dist/assets/{nav-iH1V5j6o.js → nav-BkVzzFpc.js} +0 -0
  283. /package/client/dist/assets/{nav-0fwkrgHt.js → nav-BzFLtS1W.js} +0 -0
  284. /package/client/dist/assets/{nav-ClzOE4mA.js → nav-DBDbQOYn.js} +0 -0
  285. /package/client/dist/assets/{nav-B_G-TJDW.js → nav-X9sVtUWC.js} +0 -0
  286. /package/client/dist/assets/{objective-c-Byu1T5if.js → objective-c-Ds1-m05L.js} +0 -0
  287. /package/client/dist/assets/{pascal-BrfzBfRm.js → pascal-BKK9FpIi.js} +0 -0
  288. /package/client/dist/assets/{pascaligo-BXXKFUeo.js → pascaligo-SRS3nwtO.js} +0 -0
  289. /package/client/dist/assets/{perl-B3OikKq-.js → perl-B2hTOlrF.js} +0 -0
  290. /package/client/dist/assets/{pgsql-CTsa0Acc.js → pgsql-DIQJYNpL.js} +0 -0
  291. /package/client/dist/assets/{php-DiQh3FUW.js → php-BEaVe8X2.js} +0 -0
  292. /package/client/dist/assets/{pla-92uH8Fzm.js → pla-oPLHpZ-Q.js} +0 -0
  293. /package/client/dist/assets/{postiats-BbeWkKUr.js → postiats-D_vzrAzD.js} +0 -0
  294. /package/client/dist/assets/{powerquery-DgDMzpsm.js → powerquery-BKG6w-FH.js} +0 -0
  295. /package/client/dist/assets/{powershell-BfdUUzaG.js → powershell-B3dLhDt4.js} +0 -0
  296. /package/client/dist/assets/{protobuf-BojW2ftW.js → protobuf-DC8SGjcl.js} +0 -0
  297. /package/client/dist/assets/{pug-BxqTg3IU.js → pug-D5E-4fI0.js} +0 -0
  298. /package/client/dist/assets/{qsharp-BX_A-MW9.js → qsharp-6vJAWv0x.js} +0 -0
  299. /package/client/dist/assets/{r-D9BMnxvJ.js → r-CDwsEcbM.js} +0 -0
  300. /package/client/dist/assets/{redis-5cJqEQJJ.js → redis-CuQbbESS.js} +0 -0
  301. /package/client/dist/assets/{redshift-d8BBqiwb.js → redshift-B9e1k-qI.js} +0 -0
  302. /package/client/dist/assets/{restructuredtext-C8a6yIcZ.js → restructuredtext-BiJ5IwaU.js} +0 -0
  303. /package/client/dist/assets/{ruby-egeh-6KX.js → ruby-B0UAHY9b.js} +0 -0
  304. /package/client/dist/assets/{rust-a3r9IInB.js → rust-Dg_spmFr.js} +0 -0
  305. /package/client/dist/assets/{sb-y8iRIDei.js → sb-DjU66I8Q.js} +0 -0
  306. /package/client/dist/assets/{scala-BPDK2AmK.js → scala-qvStIdfG.js} +0 -0
  307. /package/client/dist/assets/{scheme-BIWUEoOs.js → scheme-FstEk5Rh.js} +0 -0
  308. /package/client/dist/assets/{scss-CA-PSzwg.js → scss-w0U3rQLK.js} +0 -0
  309. /package/client/dist/assets/{settings-CTcwN9RE.js → settings-5tzo0Rn3.js} +0 -0
  310. /package/client/dist/assets/{settings-D_dujJZI.js → settings-BDAW3trC.js} +0 -0
  311. /package/client/dist/assets/{settings-Bg0A3zoS.js → settings-BEWv3VEu.js} +0 -0
  312. /package/client/dist/assets/{settings-BgPqg2nv.js → settings-BORg56um.js} +0 -0
  313. /package/client/dist/assets/{settings-BSze3_9q.js → settings-D3LurcR5.js} +0 -0
  314. /package/client/dist/assets/{settings-CSJ0ahZ8.js → settings-DcqWIEM6.js} +0 -0
  315. /package/client/dist/assets/{settings-DYIV89nV.js → settings-Dfz8QbZS.js} +0 -0
  316. /package/client/dist/assets/{settings-DDcfx_ca.js → settings-yMubjqYw.js} +0 -0
  317. /package/client/dist/assets/{shell--LiT1Bja.js → shell-DJ78wREd.js} +0 -0
  318. /package/client/dist/assets/{solidity-DdqZccZg.js → solidity-1aGIVsdX.js} +0 -0
  319. /package/client/dist/assets/{sophia-S6-YxNG_.js → sophia-40LqcGjB.js} +0 -0
  320. /package/client/dist/assets/{sparql-BSf5kMp2.js → sparql-Cz5dqG_g.js} +0 -0
  321. /package/client/dist/assets/{sql-D7KgjR8G.js → sql-64f62Ni4.js} +0 -0
  322. /package/client/dist/assets/{st-BnoDa-Ml.js → st-gJe2yG8J.js} +0 -0
  323. /package/client/dist/assets/{swift-DEUHTkUX.js → swift-C6ME22mv.js} +0 -0
  324. /package/client/dist/assets/{systemverilog-Tqb_KPnW.js → systemverilog-CEWz259w.js} +0 -0
  325. /package/client/dist/assets/{tcl-BmBFS2qq.js → tcl-CcLVIi3m.js} +0 -0
  326. /package/client/dist/assets/{terminal-Bje4ziIa.js → terminal-BYtreaaF.js} +0 -0
  327. /package/client/dist/assets/{terminal-CSONJOex.js → terminal-C0xx0SjA.js} +0 -0
  328. /package/client/dist/assets/{terminal-DeWzh6ys.js → terminal-CPpK58RC.js} +0 -0
  329. /package/client/dist/assets/{terminal-C2WYcFHF.js → terminal-CdxkpafL.js} +0 -0
  330. /package/client/dist/assets/{terminal-DEqzGtcr.js → terminal-Ciia0wh2.js} +0 -0
  331. /package/client/dist/assets/{terminal-80yDMgMF.js → terminal-DHIkiWcs.js} +0 -0
  332. /package/client/dist/assets/{terminal-lkZYR4wJ.js → terminal-DY42QANg.js} +0 -0
  333. /package/client/dist/assets/{terminal-YOlsJCQj.js → terminal-DoxtVdma.js} +0 -0
  334. /package/client/dist/assets/{tickets-DYvafSaY.js → tickets-0rM0lIXd.js} +0 -0
  335. /package/client/dist/assets/{tickets-DNOANUXr.js → tickets-1UIGf_oA.js} +0 -0
  336. /package/client/dist/assets/{tickets-DlpC_iTg.js → tickets-9kdPXInd.js} +0 -0
  337. /package/client/dist/assets/{tickets-CF2PYelu.js → tickets-C6pwZwt4.js} +0 -0
  338. /package/client/dist/assets/{tickets-CB7N30gm.js → tickets-DAjtxAVb.js} +0 -0
  339. /package/client/dist/assets/{tickets-DU1aqsbr.js → tickets-DNmXcAwu.js} +0 -0
  340. /package/client/dist/assets/{tickets-clefmXLv.js → tickets-n23kDqJT.js} +0 -0
  341. /package/client/dist/assets/{tickets-DucYgtdl.js → tickets-tGx5AR5b.js} +0 -0
  342. /package/client/dist/assets/{twig-BQV8igWC.js → twig-DvsO-WjW.js} +0 -0
  343. /package/client/dist/assets/{typespec-DlFroUGY.js → typespec-Brkt3IAA.js} +0 -0
  344. /package/client/dist/assets/{vb-BlrJpIMX.js → vb-r121Uzxt.js} +0 -0
  345. /package/client/dist/assets/{wgsl-BWgIc6FZ.js → wgsl-BRX8uYh4.js} +0 -0
package/server/dist/db.js CHANGED
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DEFAULT_ULTRACODE_PRE_PROMPT = void 0;
7
7
  exports.initDb = initDb;
8
8
  exports.createJob = createJob;
9
+ exports.accumulateInteractiveTurn = accumulateInteractiveTurn;
10
+ exports.finalizeInteractiveJob = finalizeInteractiveJob;
9
11
  exports.finishJob = finishJob;
10
12
  exports.appendEvent = appendEvent;
11
13
  exports.upsertPhase = upsertPhase;
@@ -559,6 +561,98 @@ const MIGRATIONS = [
559
561
  );
560
562
  `);
561
563
  },
564
+ // Migration 29: Jira integration (per-project). Each project syncs with its
565
+ // own Jira board, so every Jira table lives here in the per-project jobs.sqlite
566
+ // and is keyed by nothing but its own rows. See docs/jira-integration-plan.md.
567
+ // - jira_connection: one row, the connection config (token stored encrypted).
568
+ // - jira_links: spec↔issue map keyed on the IMMUTABLE Jira numeric id.
569
+ // - jira_outbox: durable transactional write-back queue (status + comments).
570
+ (db) => {
571
+ db.exec(`
572
+ CREATE TABLE IF NOT EXISTS jira_connection (
573
+ project_id TEXT PRIMARY KEY,
574
+ base_url TEXT NOT NULL,
575
+ deployment TEXT NOT NULL,
576
+ api_version TEXT NOT NULL,
577
+ auth_scheme TEXT NOT NULL,
578
+ account_email TEXT,
579
+ jira_project_key TEXT NOT NULL,
580
+ jira_project_id TEXT NOT NULL,
581
+ encrypted_token TEXT,
582
+ enabled INTEGER NOT NULL DEFAULT 1,
583
+ status_map TEXT,
584
+ high_water_ms INTEGER,
585
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
586
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
587
+ );
588
+
589
+ CREATE TABLE IF NOT EXISTS jira_links (
590
+ local_id INTEGER PRIMARY KEY,
591
+ jira_issue_id TEXT NOT NULL UNIQUE,
592
+ jira_key TEXT,
593
+ jira_project_id TEXT NOT NULL,
594
+ deployment TEXT NOT NULL,
595
+ status_category TEXT,
596
+ state TEXT NOT NULL DEFAULT 'linked',
597
+ tombstoned INTEGER NOT NULL DEFAULT 0,
598
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
599
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
600
+ );
601
+ CREATE INDEX IF NOT EXISTS idx_jira_links_issue ON jira_links(jira_issue_id);
602
+
603
+ CREATE TABLE IF NOT EXISTS jira_outbox (
604
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
605
+ jira_issue_id TEXT NOT NULL,
606
+ op_type TEXT NOT NULL,
607
+ idempotency_key TEXT NOT NULL UNIQUE,
608
+ payload TEXT NOT NULL,
609
+ state TEXT NOT NULL DEFAULT 'pending',
610
+ attempts INTEGER NOT NULL DEFAULT 0,
611
+ next_attempt_at TEXT,
612
+ last_error TEXT,
613
+ dead_reason TEXT,
614
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
615
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
616
+ );
617
+ CREATE INDEX IF NOT EXISTS idx_jira_outbox_state ON jira_outbox(state);
618
+ CREATE INDEX IF NOT EXISTS idx_jira_outbox_issue ON jira_outbox(jira_issue_id);
619
+ `);
620
+ },
621
+ // Migration 30: Jira sprint custom-field id. The field that holds an issue's
622
+ // sprint(s) is a custom field whose id varies per instance; we discover it
623
+ // (schema com.pyxis.greenhopper.jira:gh-sprint) and cache it here. NULL =
624
+ // not yet checked, 'none' = checked and no sprint field exists, '<id>' = found.
625
+ (db) => {
626
+ try {
627
+ db.exec(`ALTER TABLE jira_connection ADD COLUMN sprint_field_id TEXT`);
628
+ }
629
+ catch {
630
+ // Column may already exist on a partially-migrated DB — no-op.
631
+ }
632
+ },
633
+ // Migration 31: Jira discard target status. The user-configured status name to
634
+ // which a discarded spec's issue is transitioned (instead of being deleted) in
635
+ // a Jira-synced project. NULL/empty = not configured (delete behaves normally).
636
+ (db) => {
637
+ try {
638
+ db.exec(`ALTER TABLE jira_connection ADD COLUMN discard_status TEXT`);
639
+ }
640
+ catch {
641
+ // Column may already exist on a partially-migrated DB — no-op.
642
+ }
643
+ },
644
+ // Migration 32: jobs.interactive — 1 when the job is an interactive persistent
645
+ // ultracode session (the user sends multiple prompts across turns; the job
646
+ // stays 'running' until an explicit finalize, at which point every turn's real
647
+ // tokens/cost/num_turns are already summed into the row and status flips to
648
+ // 'completed'); 0 (default) for standard autonomous jobs. Additive + idempotent
649
+ // (guarded by PRAGMA table_info, mirroring migrations 18–26).
650
+ (db) => {
651
+ const cols = db.prepare(`PRAGMA table_info(jobs)`).all().map((r) => r.name);
652
+ if (!cols.includes('interactive')) {
653
+ db.exec(`ALTER TABLE jobs ADD COLUMN interactive INTEGER NOT NULL DEFAULT 0`);
654
+ }
655
+ },
562
656
  ];
563
657
  function applyMigrations(db) {
564
658
  // Ensure the migrations table exists (migration 1 creates it, but we need
@@ -620,8 +714,39 @@ function initDb(dbPath) {
620
714
  function createJob(db, job) {
621
715
  // INSERT OR IGNORE handles the case where the job row already exists (restored from DB
622
716
  // after server restart). The UPDATE that follows always sets status and started_at.
623
- db.prepare('INSERT OR IGNORE INTO jobs (id, command, started_at, status, priority, depends_on_job_id, pipeline_id) VALUES (?, ?, ?, ?, ?, ?, ?)').run(job.id, job.command, job.started_at, 'running', job.priority ?? 'normal', job.depends_on_job_id ?? null, job.pipeline_id ?? null);
624
- db.prepare('UPDATE jobs SET status = ?, started_at = ? WHERE id = ?').run('running', job.started_at, job.id);
717
+ db.prepare('INSERT OR IGNORE INTO jobs (id, command, started_at, status, priority, depends_on_job_id, pipeline_id, interactive) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(job.id, job.command, job.started_at, 'running', job.priority ?? 'normal', job.depends_on_job_id ?? null, job.pipeline_id ?? null, job.interactive ? 1 : 0);
718
+ db.prepare('UPDATE jobs SET status = ?, started_at = ?, interactive = ? WHERE id = ?').run('running', job.started_at, job.interactive ? 1 : 0, job.id);
719
+ }
720
+ /**
721
+ * Add one completed interactive turn's REAL usage into the job row. Token/cost/
722
+ * turn columns accumulate (COALESCE so the first turn starts from a clean base);
723
+ * model + session_id are stamped from the first turn that reports them. The job
724
+ * stays 'running' — only finalizeInteractiveJob flips the terminal status. This
725
+ * keeps the live Job Detail totals honest (sum of completed turns, never an
726
+ * estimate) between turns.
727
+ */
728
+ function accumulateInteractiveTurn(db, jobId, turn) {
729
+ db.prepare(`
730
+ UPDATE jobs SET
731
+ tokens_in = COALESCE(tokens_in, 0) + ?,
732
+ tokens_out = COALESCE(tokens_out, 0) + ?,
733
+ tokens_cache_read = COALESCE(tokens_cache_read, 0) + ?,
734
+ tokens_cache_create = COALESCE(tokens_cache_create, 0) + ?,
735
+ total_cost_usd = COALESCE(total_cost_usd, 0) + ?,
736
+ num_turns = COALESCE(num_turns, 0) + ?,
737
+ total_cost_usd_estimated = 0,
738
+ model = COALESCE(model, ?),
739
+ session_id = COALESCE(?, session_id)
740
+ WHERE id = ?
741
+ `).run(turn.tokens_in, turn.tokens_out, turn.tokens_cache_read, turn.tokens_cache_create, turn.total_cost_usd, turn.num_turns, turn.model ?? null, turn.session_id ?? null, jobId);
742
+ }
743
+ /**
744
+ * Flip an interactive job to its terminal status (completed on finalize, failed
745
+ * on crash) and stamp finished_at. Token/cost/turn columns are left untouched —
746
+ * they were already accumulated turn-by-turn via accumulateInteractiveTurn.
747
+ */
748
+ function finalizeInteractiveJob(db, jobId, status) {
749
+ db.prepare('UPDATE jobs SET status = ?, finished_at = ? WHERE id = ?').run(status, new Date().toISOString(), jobId);
625
750
  }
626
751
  function finishJob(db, jobId, result) {
627
752
  db.prepare(`
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isCodeExplorerEnabled = isCodeExplorerEnabled;
4
4
  exports.isBrowserCaptureEnabled = isBrowserCaptureEnabled;
5
+ exports.isJiraEnabled = isJiraEnabled;
6
+ exports.isInteractiveJobsEnabled = isInteractiveJobsEnabled;
5
7
  function isCodeExplorerEnabled() {
6
8
  return process.env.SPECRAILS_CODE_EXPLORER !== 'false';
7
9
  }
@@ -15,3 +17,27 @@ function isCodeExplorerEnabled() {
15
17
  function isBrowserCaptureEnabled() {
16
18
  return process.env.SPECRAILS_BROWSER_CAPTURE !== 'false';
17
19
  }
20
+ /**
21
+ * Jira integration ("spec = Jira issue", per-project hot-swap local↔Jira).
22
+ * Server-side default ON; set SPECRAILS_JIRA_SECTION="false" to 404 the routes
23
+ * and skip all sync (emergency rollback). The feature is inert until a project
24
+ * actually configures a Jira connection, so default-on is safe. The client gates
25
+ * separately on VITE_FEATURE_JIRA.
26
+ */
27
+ function isJiraEnabled() {
28
+ return process.env.SPECRAILS_JIRA_SECTION !== 'false';
29
+ }
30
+ /**
31
+ * Interactive ultracode jobs: when launched with the rail's "Interactive"
32
+ * toggle, an ultracode (Claude-only) job becomes a persistent chat session —
33
+ * the user sends multiple prompts across turns, the job stays resident until an
34
+ * explicit "Finalize Job" action, and every turn's real token spend is summed
35
+ * into the single job row. Server-side default ON; set
36
+ * SPECRAILS_INTERACTIVE_JOBS="false" to reject the toggle + the per-job
37
+ * message/finalize routes (emergency rollback). Inert unless a launch actually
38
+ * sets interactive=true, so default-on is safe. The client gates separately on
39
+ * VITE_FEATURE_INTERACTIVE_JOBS.
40
+ */
41
+ function isInteractiveJobsEnabled() {
42
+ return process.env.SPECRAILS_INTERACTIVE_JOBS !== 'false';
43
+ }
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ // Interactive ultracode job sessions (resident persistent-stdin transport).
3
+ //
4
+ // A standard ultracode job spawns `claude -p <prompt>` once and settles when the
5
+ // child closes. An INTERACTIVE ultracode job instead keeps ONE `claude -p
6
+ // --input-format stream-json` child resident across many user turns (the same
7
+ // transport ExploreStdinSessions uses for Explore chat): each user prompt is a
8
+ // newline-delimited stream-json message written to stdin, the agent works, and
9
+ // the turn ends on a `result` event WITHOUT killing the child. The session stays
10
+ // alive until the user finalizes (SIGTERM) — at which point QueueManager flips
11
+ // the job to a terminal status. Every turn's REAL token usage is summed into the
12
+ // job row as it completes, so the live Job Detail totals are honest (never an
13
+ // estimate) and the finalized job carries the full conversation's spend.
14
+ //
15
+ // This module owns the transport + per-turn streaming/persistence/accounting.
16
+ // QueueManager owns spawn-arg construction and the terminal settle (slot release,
17
+ // rail/ticket completion, ai_invocations) via the onSettle callback.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.InteractiveJobSession = void 0;
20
+ const node_readline_1 = require("node:readline");
21
+ const cli_prompt_1 = require("./util/cli-prompt");
22
+ const explore_stdin_session_1 = require("./explore-stdin-session");
23
+ const result_event_1 = require("./result-event");
24
+ const db_1 = require("./db");
25
+ const stream_display_1 = require("./util/stream-display");
26
+ function zeroUsage() {
27
+ return {
28
+ tokens_in: 0,
29
+ tokens_out: 0,
30
+ tokens_cache_read: 0,
31
+ tokens_cache_create: 0,
32
+ total_cost_usd: 0,
33
+ num_turns: 0,
34
+ };
35
+ }
36
+ /** SIGTERM → SIGKILL escalation window on finalize (ms). */
37
+ const FINALIZE_KILL_GRACE_MS = 2000;
38
+ class InteractiveJobSession {
39
+ _jobId;
40
+ _projectId;
41
+ _db;
42
+ _adapter;
43
+ _broadcast;
44
+ _onSettle;
45
+ _spawn;
46
+ _child = null;
47
+ _stdoutReader = null;
48
+ _stderrReader = null;
49
+ _eventSeq = 0;
50
+ _streaming = false;
51
+ /** True between writing a turn to stdin and receiving its `result` event.
52
+ * Guards _onTurnResult against double-counting a duplicate `result` frame. */
53
+ _awaitingResult = false;
54
+ _pending = [];
55
+ _turnEvents = [];
56
+ _accum = zeroUsage();
57
+ _model = null;
58
+ _sessionId = null;
59
+ _finalizing = false;
60
+ _settled = false;
61
+ _disposed = false;
62
+ _killTimer = null;
63
+ constructor(deps) {
64
+ this._jobId = deps.jobId;
65
+ this._projectId = deps.projectId;
66
+ this._db = deps.db;
67
+ this._adapter = deps.adapter;
68
+ this._broadcast = deps.broadcast;
69
+ this._onSettle = deps.onSettle;
70
+ this._spawn = deps.spawn ?? cli_prompt_1.spawnAiCli;
71
+ }
72
+ /** Spawn the resident child and run the first turn (the ultracode prompt). */
73
+ start(spec, firstPrompt) {
74
+ const child = this._spawn(spec.binary, spec.args, {
75
+ env: spec.env ?? process.env,
76
+ // stdin MUST be piped — it is the per-turn transport.
77
+ stdio: ['pipe', 'pipe', 'pipe'],
78
+ cwd: spec.cwd,
79
+ });
80
+ this._child = child;
81
+ // Absorb spawn 'error' (e.g. ENOENT) so it does not crash the process; the
82
+ // 'close' that follows settles the job as crashed through _handleClose.
83
+ child.on('error', (err) => {
84
+ console.error(`[interactive-job] spawn failed for ${this._jobId}: ${err.message}`);
85
+ });
86
+ if (child.stdout) {
87
+ this._stdoutReader = (0, node_readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
88
+ this._stdoutReader.on('line', (line) => this._handleStdoutLine(line));
89
+ }
90
+ if (child.stderr) {
91
+ this._stderrReader = (0, node_readline_1.createInterface)({ input: child.stderr, crlfDelay: Infinity });
92
+ this._stderrReader.on('line', (line) => this._handleStderrLine(line));
93
+ }
94
+ child.on('close', (code) => this._handleClose(code));
95
+ this.send(firstPrompt);
96
+ }
97
+ /** Accept a user prompt. Echoed immediately to the in-job chat; written to the
98
+ * child now if idle, else queued and fed when the active turn's `result`
99
+ * fires. Returns false if the session is gone. */
100
+ send(text) {
101
+ if (this._disposed || this._finalizing || !this._child)
102
+ return false;
103
+ const queued = this._streaming;
104
+ // Surface the user turn in the transcript via the existing `log` channel so
105
+ // it both renders live (the client's 'log' handler) and survives a reload
106
+ // (persisted as a log event, picked up by GET /jobs/:id). The 🧑 prefix marks
107
+ // it as the user's prompt amid the agent's streamed work.
108
+ const line = `🧑 ${text}`;
109
+ this._persistLog('stdout', line);
110
+ this._emitLog('stdout', line);
111
+ // Control signal for the in-job chat UI (streaming-state / queued hint).
112
+ this._broadcast({
113
+ type: 'job.turn_user',
114
+ projectId: this._projectId,
115
+ jobId: this._jobId,
116
+ text,
117
+ queued,
118
+ timestamp: new Date().toISOString(),
119
+ });
120
+ if (queued) {
121
+ this._pending.push(text);
122
+ }
123
+ else {
124
+ this._writeTurn(text);
125
+ }
126
+ return true;
127
+ }
128
+ isStreaming() {
129
+ return this._streaming;
130
+ }
131
+ getTotals() {
132
+ return { ...this._accum };
133
+ }
134
+ /** User-initiated end: SIGTERM the child (SIGKILL after a grace window). The
135
+ * subsequent 'close' settles the job as 'finalized'. Idempotent. */
136
+ finalize() {
137
+ if (this._finalizing || this._settled)
138
+ return;
139
+ this._finalizing = true;
140
+ const child = this._child;
141
+ if (!child || child.killed || !child.pid) {
142
+ this._settle('finalized');
143
+ return;
144
+ }
145
+ try {
146
+ child.kill('SIGTERM');
147
+ }
148
+ catch { /* already gone */ }
149
+ this._killTimer = setTimeout(() => {
150
+ try {
151
+ if (this._child && !this._child.killed)
152
+ this._child.kill('SIGKILL');
153
+ }
154
+ catch { /* gone */ }
155
+ }, FINALIZE_KILL_GRACE_MS);
156
+ }
157
+ /** Teardown without settling (project removal / shutdown). */
158
+ dispose() {
159
+ if (this._disposed)
160
+ return;
161
+ this._disposed = true;
162
+ this._clearKillTimer();
163
+ this._closeReaders();
164
+ try {
165
+ if (this._child && !this._child.killed)
166
+ this._child.kill('SIGTERM');
167
+ }
168
+ catch { /* gone */ }
169
+ this._child = null;
170
+ }
171
+ // ─── internals ─────────────────────────────────────────────────────────────
172
+ _writeTurn(text) {
173
+ this._streaming = true;
174
+ this._awaitingResult = true;
175
+ this._turnEvents = [];
176
+ const child = this._child;
177
+ if (!child || !child.stdin || child.stdin.destroyed)
178
+ return;
179
+ try {
180
+ child.stdin.write((0, explore_stdin_session_1.frameStreamJsonUserMessage)(text));
181
+ }
182
+ catch (err) {
183
+ console.error('[interactive-job] stdin write failed:', err);
184
+ }
185
+ }
186
+ _handleStdoutLine(line) {
187
+ let parsed = null;
188
+ try {
189
+ parsed = JSON.parse(line);
190
+ }
191
+ catch { /* plain text */ }
192
+ const adapterEv = this._adapter.parseStreamLine(line);
193
+ if (adapterEv)
194
+ this._turnEvents.push(adapterEv);
195
+ if (parsed) {
196
+ const eventType = parsed.type ?? 'unknown';
197
+ this._persistEvent(eventType, line);
198
+ this._broadcast({
199
+ type: 'event',
200
+ jobId: this._jobId,
201
+ event_type: eventType,
202
+ source: 'stdout',
203
+ payload: line,
204
+ timestamp: new Date().toISOString(),
205
+ seq: this._eventSeq - 1,
206
+ });
207
+ if (eventType === 'result') {
208
+ this._onTurnResult(parsed);
209
+ }
210
+ const displayText = (0, stream_display_1.extractDisplayText)(parsed);
211
+ if (displayText !== null) {
212
+ this._persistLog('stdout', displayText);
213
+ this._emitLog('stdout', displayText);
214
+ }
215
+ }
216
+ else {
217
+ this._persistLog('stdout', line);
218
+ if (adapterEv?.kind === 'text-delta') {
219
+ this._emitLog('stdout', adapterEv.text);
220
+ }
221
+ else {
222
+ this._emitLog('stdout', line);
223
+ }
224
+ }
225
+ }
226
+ _handleStderrLine(line) {
227
+ this._persistLog('stderr', line);
228
+ this._emitLog('stderr', line);
229
+ }
230
+ _onTurnResult(_parsed) {
231
+ // Guard against a duplicate `result` frame for the same turn — without it a
232
+ // second result would double-count this turn's tokens into _accum + the row.
233
+ if (!this._awaitingResult)
234
+ return;
235
+ this._awaitingResult = false;
236
+ this._streaming = false;
237
+ const { result: normalised } = (0, result_event_1.finaliseInvocationResult)(this._adapter, this._turnEvents, {});
238
+ this._accum.tokens_in += normalised.tokens_in ?? 0;
239
+ this._accum.tokens_out += normalised.tokens_out ?? 0;
240
+ this._accum.tokens_cache_read += normalised.tokens_cache_read ?? 0;
241
+ this._accum.tokens_cache_create += normalised.tokens_cache_create ?? 0;
242
+ this._accum.total_cost_usd += normalised.total_cost_usd ?? 0;
243
+ this._accum.num_turns += normalised.num_turns ?? 1;
244
+ if (!this._model && normalised.model)
245
+ this._model = normalised.model;
246
+ if (normalised.session_id)
247
+ this._sessionId = normalised.session_id;
248
+ if (this._db) {
249
+ try {
250
+ (0, db_1.accumulateInteractiveTurn)(this._db, this._jobId, {
251
+ tokens_in: normalised.tokens_in ?? 0,
252
+ tokens_out: normalised.tokens_out ?? 0,
253
+ tokens_cache_read: normalised.tokens_cache_read ?? 0,
254
+ tokens_cache_create: normalised.tokens_cache_create ?? 0,
255
+ total_cost_usd: normalised.total_cost_usd ?? 0,
256
+ num_turns: normalised.num_turns ?? 1,
257
+ model: normalised.model,
258
+ session_id: normalised.session_id,
259
+ });
260
+ }
261
+ catch (err) {
262
+ console.error('[interactive-job] accumulate turn failed:', err);
263
+ }
264
+ }
265
+ this._broadcast({
266
+ type: 'job.turn_done',
267
+ projectId: this._projectId,
268
+ jobId: this._jobId,
269
+ totals: { ...this._accum },
270
+ timestamp: new Date().toISOString(),
271
+ });
272
+ // Feed the next queued prompt (if any) now that the turn is idle.
273
+ if (!this._finalizing && this._pending.length > 0) {
274
+ const next = this._pending.shift();
275
+ this._writeTurn(next);
276
+ }
277
+ }
278
+ _persistEvent(eventType, payload) {
279
+ if (!this._db)
280
+ return;
281
+ try {
282
+ (0, db_1.appendEvent)(this._db, this._jobId, this._eventSeq++, {
283
+ event_type: eventType,
284
+ source: 'stdout',
285
+ payload,
286
+ });
287
+ }
288
+ catch (err) {
289
+ console.error('[interactive-job] persist event failed:', err);
290
+ }
291
+ }
292
+ _persistLog(source, line) {
293
+ if (!this._db)
294
+ return;
295
+ try {
296
+ (0, db_1.appendEvent)(this._db, this._jobId, this._eventSeq++, {
297
+ event_type: 'log',
298
+ source,
299
+ payload: JSON.stringify({ line }),
300
+ });
301
+ }
302
+ catch (err) {
303
+ console.error('[interactive-job] persist log failed:', err);
304
+ }
305
+ }
306
+ _emitLog(source, line) {
307
+ this._broadcast({
308
+ type: 'log',
309
+ source,
310
+ line,
311
+ timestamp: new Date().toISOString(),
312
+ processId: this._jobId,
313
+ });
314
+ }
315
+ _handleClose(_code) {
316
+ if (this._disposed || this._settled)
317
+ return;
318
+ this._settle(this._finalizing ? 'finalized' : 'crashed');
319
+ }
320
+ _settle(reason) {
321
+ if (this._settled)
322
+ return;
323
+ this._settled = true;
324
+ this._streaming = false;
325
+ this._awaitingResult = false;
326
+ this._clearKillTimer();
327
+ this._closeReaders();
328
+ // The child is gone — any prompts still queued (turn died without a `result`,
329
+ // or the user finalized mid-turn) can never run. Surface them in the
330
+ // transcript instead of dropping them silently.
331
+ if (this._pending.length > 0) {
332
+ const note = `⚠️ ${this._pending.length} queued prompt(s) were not sent — the session ended.`;
333
+ this._persistLog('stderr', note);
334
+ this._emitLog('stderr', note);
335
+ this._pending = [];
336
+ }
337
+ this._onSettle({
338
+ reason,
339
+ totals: { ...this._accum },
340
+ model: this._model,
341
+ sessionId: this._sessionId,
342
+ });
343
+ }
344
+ _clearKillTimer() {
345
+ if (this._killTimer !== null) {
346
+ clearTimeout(this._killTimer);
347
+ this._killTimer = null;
348
+ }
349
+ }
350
+ _closeReaders() {
351
+ try {
352
+ this._stdoutReader?.close();
353
+ }
354
+ catch { /* best-effort */ }
355
+ try {
356
+ this._stderrReader?.close();
357
+ }
358
+ catch { /* best-effort */ }
359
+ this._stdoutReader = null;
360
+ this._stderrReader = null;
361
+ }
362
+ }
363
+ exports.InteractiveJobSession = InteractiveJobSession;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ // Atlassian Document Format (ADF) helpers.
3
+ //
4
+ // Jira Cloud (REST v3) requires comment/description bodies in ADF JSON. Jira
5
+ // Server/Data Center (REST v2) expects a plain wiki-markup string. We keep a
6
+ // single internal "text" model and render it to either format at the client
7
+ // boundary (see jira-client.ts `bodyForDeployment`).
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SPECRAILS_COMMENT_PROP_KEY = void 0;
10
+ exports.textToAdf = textToAdf;
11
+ exports.bodyForDeployment = bodyForDeployment;
12
+ exports.commentMarker = commentMarker;
13
+ exports.discardCommentMarker = discardCommentMarker;
14
+ exports.bodyContainsMarker = bodyContainsMarker;
15
+ exports.commentHasMarker = commentHasMarker;
16
+ exports.adfToText = adfToText;
17
+ /** Build a minimal ADF document from plain text (newlines → paragraphs). */
18
+ function textToAdf(text) {
19
+ const paragraphs = text.split('\n');
20
+ return {
21
+ type: 'doc',
22
+ version: 1,
23
+ content: paragraphs.map((line) => line.length === 0
24
+ ? { type: 'paragraph' }
25
+ : { type: 'paragraph', content: [{ type: 'text', text: line }] }),
26
+ };
27
+ }
28
+ /** Render a body for the target deployment: ADF for Cloud v3, plain for DC v2. */
29
+ function bodyForDeployment(text, deployment) {
30
+ return deployment === 'cloud' ? textToAdf(text) : text;
31
+ }
32
+ /**
33
+ * Jira comment-property key under which we store the idempotency marker. Comment
34
+ * properties are metadata that NEVER render in the comment body, so the marker
35
+ * stays invisible to users while still letting us dedup on retry. Supported on
36
+ * both Cloud (v3) and Data Center (v2).
37
+ */
38
+ exports.SPECRAILS_COMMENT_PROP_KEY = 'sh.specrails.marker';
39
+ /**
40
+ * Deterministic idempotency marker. Jira has no native comment idempotency, so
41
+ * before re-posting on retry we GET the issue comments and skip if one already
42
+ * carries this marker (now via an invisible comment property — see
43
+ * SPECRAILS_COMMENT_PROP_KEY — with a legacy body-scan fallback).
44
+ */
45
+ function commentMarker(jobId, ticketId) {
46
+ return `[specrails:job=${jobId}:ticket=${ticketId}]`;
47
+ }
48
+ /**
49
+ * Idempotency marker for a user-initiated "discard / move-to" comment. The
50
+ * `nonce` (captured at enqueue) makes each discard distinct so a later re-discard
51
+ * of the same spec posts a fresh comment instead of being deduped away.
52
+ */
53
+ function discardCommentMarker(ticketId, nonce) {
54
+ return `[specrails:discard=${nonce}:ticket=${ticketId}]`;
55
+ }
56
+ /** True when an ADF doc or wiki string already contains the given marker. */
57
+ function bodyContainsMarker(body, marker) {
58
+ if (typeof body === 'string')
59
+ return body.includes(marker);
60
+ try {
61
+ return JSON.stringify(body).includes(marker);
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * True when a fetched comment already carries the marker — preferring the
69
+ * invisible comment property, with a fallback to a legacy body-embedded marker
70
+ * (comments posted before the property move). `comment.properties` comes from
71
+ * `GET …/comment?expand=properties`.
72
+ */
73
+ function commentHasMarker(comment, marker) {
74
+ const prop = comment.properties?.find((p) => p.key === exports.SPECRAILS_COMMENT_PROP_KEY);
75
+ if (prop) {
76
+ try {
77
+ if (JSON.stringify(prop.value ?? '').includes(marker))
78
+ return true;
79
+ }
80
+ catch {
81
+ /* fall through to body scan */
82
+ }
83
+ }
84
+ return bodyContainsMarker(comment.body, marker);
85
+ }
86
+ /**
87
+ * Flatten an ADF document (or plain string) back to text — used to read inbound
88
+ * Jira descriptions/comments into the local cache.
89
+ */
90
+ function adfToText(body) {
91
+ if (body == null)
92
+ return '';
93
+ if (typeof body === 'string')
94
+ return body;
95
+ const out = [];
96
+ const walk = (node) => {
97
+ if (!node || typeof node !== 'object')
98
+ return;
99
+ if (node.type === 'text' && typeof node.text === 'string')
100
+ out.push(node.text);
101
+ if (node.type === 'hardBreak')
102
+ out.push('\n');
103
+ if (Array.isArray(node.content)) {
104
+ for (const child of node.content)
105
+ walk(child);
106
+ // paragraph / block separators
107
+ if (node.type === 'paragraph' || node.type === 'heading')
108
+ out.push('\n');
109
+ }
110
+ };
111
+ walk(body);
112
+ return out.join('').replace(/\n{3,}/g, '\n\n').trim();
113
+ }