specrails-desktop 2.16.0 → 2.18.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 (424) hide show
  1. package/client/dist/assets/{ActivityFeedPage-C_clKk8d.js → ActivityFeedPage-mGnv5SOe.js} +1 -1
  2. package/client/dist/assets/AgentsPage-BH1isg5j.js +86 -0
  3. package/client/dist/assets/AnalyticsPage-ByyshYRI.js +1 -0
  4. package/client/dist/assets/{BarChart-Dq1h8eYR.js → BarChart-BqHUOnl9.js} +3 -3
  5. package/client/dist/assets/CodePage-CU7T_HYs.js +2 -0
  6. package/client/dist/assets/{DesktopAnalyticsPage-CYhXhJoQ.js → DesktopAnalyticsPage-CR-m97Ui.js} +1 -1
  7. package/client/dist/assets/DocsDialog-CEmZp_8V.js +11 -0
  8. package/client/dist/assets/DocsPage-DYbAQ1AT.js +11 -0
  9. package/client/dist/assets/{ExportDropdown-DdB2ncYh.js → ExportDropdown-C-o1Q4ef.js} +1 -1
  10. package/client/dist/assets/IntegrationsPage-C6fKdwVH.js +3 -0
  11. package/client/dist/assets/JobDetailPage-CKfIfBgE.js +16 -0
  12. package/client/dist/assets/JobsPage-nBng0riZ.js +1 -0
  13. package/client/dist/assets/LoopBuilderPage-Co_TYqYk.js +7 -0
  14. package/client/dist/assets/LoopsPage-a2hd37Ya.js +1 -0
  15. package/client/dist/assets/{activity-Ca2XoBi-.js → activity-D8vQJrpW.js} +1 -1
  16. package/client/dist/assets/{addon-image-biWX2UUm.js → addon-image-Ba-TY3wr.js} +1 -1
  17. package/client/dist/assets/{addon-ligatures-D4EiDb5r.js → addon-ligatures-T4ARLiuu.js} +1 -1
  18. package/client/dist/assets/{addon-webgl-CDCIZQ03.js → addon-webgl-Cd73n8eh.js} +1 -1
  19. package/client/dist/assets/{addspec-DE_VfIyP.js → addspec-BloGn_ey.js} +1 -1
  20. package/client/dist/assets/agent-9WdSxPU5.js +1 -0
  21. package/client/dist/assets/agent-ASIyVcg0.js +1 -0
  22. package/client/dist/assets/agent-Bm2tPrdr.js +1 -0
  23. package/client/dist/assets/agent-C4bO1l_v.js +1 -0
  24. package/client/dist/assets/agent-CgKsh88Z2.js +1 -0
  25. package/client/dist/assets/agent-DMUrz9ZS2.js +1 -0
  26. package/client/dist/assets/agent-Dwkz5mPS.js +1 -0
  27. package/client/dist/assets/agent-pO8x4jbR.js +1 -0
  28. package/client/dist/assets/{agents-DcoUUFZl.js → agents-52t8NVpS.js} +1 -1
  29. package/client/dist/assets/{agents-DCX9D3-Q.js → agents-BAxAwHy-.js} +1 -1
  30. package/client/dist/assets/{agents-Jc-CjBDB.js → agents-BUYBb825.js} +1 -1
  31. package/client/dist/assets/{agents-CIhRSOna.js → agents-BjT_xLjv.js} +1 -1
  32. package/client/dist/assets/{agents-BYjUOrLD.js → agents-C07al8rI.js} +1 -1
  33. package/client/dist/assets/{agents-1lhqEcXH.js → agents-DfGvYtpq2.js} +1 -1
  34. package/client/dist/assets/{agents-CQlu1nOm.js → agents-Dli56gr82.js} +1 -1
  35. package/client/dist/assets/{agents-DVYpVGur.js → agents-Eqw_AQ6H.js} +1 -1
  36. package/client/dist/assets/{agentstudio-DCTpIqOx.js → agentstudio-CZm2rOzt.js} +1 -1
  37. package/client/dist/assets/{aiedit-Dirqf3Bm.js → aiedit-DYtnZU9H.js} +1 -1
  38. package/client/dist/assets/{analytics-BE49LbUk.js → analytics-B4FJ_pry.js} +1 -1
  39. package/client/dist/assets/{attachments-C2JPppoP.js → attachments-Ci6_42Kh.js} +1 -1
  40. package/client/dist/assets/{browser-Dpo_ug4c.js → browser-ZB1afzjZ.js} +1 -1
  41. package/client/dist/assets/{chat-mq1x9cxZ.js → chat-VMflhzxn.js} +1 -1
  42. package/client/dist/assets/chunk-BNv3lrIs.js +1 -0
  43. package/client/dist/assets/{code-DtIatVES.js → code-wtcs0ivb.js} +1 -1
  44. package/client/dist/assets/commands-Bc8y9eNo2.js +1 -0
  45. package/client/dist/assets/commands-Bl3-KVfj.js +1 -0
  46. package/client/dist/assets/commands-C5WQ9blx2.js +1 -0
  47. package/client/dist/assets/commands-CM99Upsz2.js +1 -0
  48. package/client/dist/assets/commands-Cc2WpuXa.js +1 -0
  49. package/client/dist/assets/commands-CckaBWdV.js +1 -0
  50. package/client/dist/assets/commands-DASXJD-l.js +1 -0
  51. package/client/dist/assets/commands-k9wbNYoi.js +1 -0
  52. package/client/dist/assets/{common-kftoE0Q-.js → common-BPQQkgJZ.js} +1 -1
  53. package/client/dist/assets/{cssMode-DSHXamUH.js → cssMode-DsArsSCT.js} +1 -1
  54. package/client/dist/assets/{dashboard-BQ3Oa8CE.js → dashboard-Cyzp4ox9.js} +1 -1
  55. package/client/dist/assets/{dist-js-Ele_sHO7.js → dist-js-BHhtSerX.js} +1 -1
  56. package/client/dist/assets/{dist-js-D27I-bfi.js → dist-js-CUegfF6k.js} +1 -1
  57. package/client/dist/assets/{editor.api2-Cwla1nvV.js → editor.api2-T7u8kQWG.js} +1 -1
  58. package/client/dist/assets/{editor.main-Cd8qBRAT.js → editor.main-G3Sl9C67.js} +2 -2
  59. package/client/dist/assets/{explore-CBYU76JD.js → explore-BKVV5JSu.js} +1 -1
  60. package/client/dist/assets/{freemarker2-CkEKTBm2.js → freemarker2-pBzQNgFi.js} +1 -1
  61. package/client/dist/assets/{handlebars-BRGx0JpL.js → handlebars-CtBTCnBI.js} +1 -1
  62. package/client/dist/assets/{html-BfRIaGgN.js → html-Dx3DYBzY.js} +1 -1
  63. package/client/dist/assets/{htmlMode-BTpSnsDE.js → htmlMode-In7frFOo.js} +1 -1
  64. package/client/dist/assets/index-B0FxQALR.css +2 -0
  65. package/client/dist/assets/index-B7vB20Jw.js +152 -0
  66. package/client/dist/assets/{integrations-jJGaKwff.js → integrations-CuWnMR5L.js} +1 -1
  67. package/client/dist/assets/{javascript-DMmE2kKN.js → javascript-BKcn-YkN.js} +1 -1
  68. package/client/dist/assets/{jira-BwACz875.js → jira-DXMoAKWD.js} +1 -1
  69. package/client/dist/assets/{jobs-DRInW0AE.js → jobs---dwa4Nf.js} +1 -1
  70. package/client/dist/assets/{jsonMode-hYuaUleY.js → jsonMode-jNREoQ3C.js} +1 -1
  71. package/client/dist/assets/{lib-qdRfWwE1.js → lib-PQoHlR5u.js} +2 -2
  72. package/client/dist/assets/{liquid-C-VbuTr7.js → liquid-CtRkGqBu.js} +1 -1
  73. package/client/dist/assets/{loops-DfGadne0.js → loops-D8Iu6Jss.js} +1 -1
  74. package/client/dist/assets/{lspLanguageFeatures-BALx-vjn.js → lspLanguageFeatures-Cl3gvD_W.js} +1 -1
  75. package/client/dist/assets/mcp-B4irfLJ2.js +1 -0
  76. package/client/dist/assets/mcp-B6nwJJmR.js +1 -0
  77. package/client/dist/assets/mcp-BOoEGTSQ.js +1 -0
  78. package/client/dist/assets/mcp-BP7PpbDr2.js +1 -0
  79. package/client/dist/assets/mcp-CIH0pH1K.js +1 -0
  80. package/client/dist/assets/mcp-CNgFiTEh.js +1 -0
  81. package/client/dist/assets/mcp-D3E4_Mmk2.js +1 -0
  82. package/client/dist/assets/mcp-mupPuPDO2.js +1 -0
  83. package/client/dist/assets/{mdx-Cs8MNCzn.js → mdx-B_nJldHz.js} +1 -1
  84. package/client/dist/assets/{monaco.contribution-CIvCAJs4.js → monaco.contribution-PUeaZk8T.js} +2 -2
  85. package/client/dist/assets/{nav-CCz4krPP.js → nav-DE3yWQw6.js} +1 -1
  86. package/client/dist/assets/{python-CGShLxuI.js → python-DS1Ce-A6.js} +1 -1
  87. package/client/dist/assets/{razor-CvOBQGd5.js → razor-L41Qg9OK.js} +1 -1
  88. package/client/dist/assets/settings-BKmORxlB2.js +1 -0
  89. package/client/dist/assets/settings-BVv2JgQ8.js +1 -0
  90. package/client/dist/assets/settings-Bghm3pAV.js +1 -0
  91. package/client/dist/assets/settings-BuC_aplX.js +1 -0
  92. package/client/dist/assets/settings-Bw_Qk8E32.js +1 -0
  93. package/client/dist/assets/settings-CAKgJv7p.js +1 -0
  94. package/client/dist/assets/settings-CEUgk1Ck.js +1 -0
  95. package/client/dist/assets/settings-C_PDE20s2.js +1 -0
  96. package/client/dist/assets/setup-4us-Bz3H.js +1 -0
  97. package/client/dist/assets/setup-BYoDYoPY2.js +1 -0
  98. package/client/dist/assets/setup-Bf9eLKxU.js +1 -0
  99. package/client/dist/assets/setup-C5UbM0RP.js +1 -0
  100. package/client/dist/assets/setup-CqGrWb6G.js +1 -0
  101. package/client/dist/assets/setup-JFkWWZnR.js +1 -0
  102. package/client/dist/assets/setup-faMApo0U2.js +1 -0
  103. package/client/dist/assets/setup-tloaTobG2.js +1 -0
  104. package/client/dist/assets/{specs-C_8j4ELZ.js → specs-BYeAbIEy.js} +1 -1
  105. package/client/dist/assets/{terminal-Cj4HzP3p.js → terminal-DFPNRLKp.js} +1 -1
  106. package/client/dist/assets/{tickets-DBQXMYYA.js → tickets-CkfbISuK.js} +1 -1
  107. package/client/dist/assets/{tsMode-04uOpiiP.js → tsMode-BC1g186U.js} +1 -1
  108. package/client/dist/assets/{typescript-CX0CwyIZ.js → typescript-BGlRGvJW.js} +1 -1
  109. package/client/dist/assets/upload-PnJDPHZH.js +1 -0
  110. package/client/dist/assets/{value-BRVOAvhY.js → value-BTDefcOV.js} +1 -1
  111. package/client/dist/assets/{workers-yr2u4c3C.js → workers-CXcXtO3b.js} +1 -1
  112. package/client/dist/assets/{xml-C0OFlt5X.js → xml-JdZZaIhJ.js} +1 -1
  113. package/client/dist/assets/{yaml-DgabaPs5.js → yaml-Cm82BVBt.js} +1 -1
  114. package/client/dist/index.html +30 -28
  115. package/docs/README.md +2 -0
  116. package/docs/guide/de/integrations/5-mcp-server.md +74 -0
  117. package/docs/guide/de/integrations/6-agent-chat.md +64 -0
  118. package/docs/guide/en/integrations/5-mcp-server.md +74 -0
  119. package/docs/guide/en/integrations/6-agent-chat.md +64 -0
  120. package/docs/guide/es/integrations/5-mcp-server.md +74 -0
  121. package/docs/guide/es/integrations/6-agent-chat.md +64 -0
  122. package/docs/guide/fr/integrations/5-mcp-server.md +74 -0
  123. package/docs/guide/fr/integrations/6-agent-chat.md +64 -0
  124. package/docs/guide/it/integrations/5-mcp-server.md +74 -0
  125. package/docs/guide/it/integrations/6-agent-chat.md +64 -0
  126. package/docs/guide/ja/integrations/5-mcp-server.md +74 -0
  127. package/docs/guide/ja/integrations/6-agent-chat.md +64 -0
  128. package/docs/guide/pt/integrations/5-mcp-server.md +74 -0
  129. package/docs/guide/pt/integrations/6-agent-chat.md +64 -0
  130. package/docs/guide/zh/integrations/5-mcp-server.md +74 -0
  131. package/docs/guide/zh/integrations/6-agent-chat.md +64 -0
  132. package/docs/mcp.md +249 -0
  133. package/package.json +8 -5
  134. package/server/dist/agent-chat-manager.js +224 -0
  135. package/server/dist/agent-chat-router.js +148 -0
  136. package/server/dist/agent-cwd-manager.js +94 -0
  137. package/server/dist/agent-mcp-config.js +208 -0
  138. package/server/dist/agent-store.js +59 -0
  139. package/server/dist/agent-tier.js +86 -0
  140. package/server/dist/desktop-db.js +27 -0
  141. package/server/dist/index.js +38 -0
  142. package/server/dist/mcp/guide.js +82 -0
  143. package/server/dist/mcp/index.js +21 -0
  144. package/server/dist/mcp/mcp-admin-router.js +71 -0
  145. package/server/dist/mcp/mcp-server.js +143 -0
  146. package/server/dist/mcp/mcp-tiers.js +48 -0
  147. package/server/dist/mcp/mcp-token.js +119 -0
  148. package/server/dist/mcp/resources.js +24 -0
  149. package/server/dist/mcp/tools/agents.js +319 -0
  150. package/server/dist/mcp/tools/analytics.js +168 -0
  151. package/server/dist/mcp/tools/app-settings.js +81 -0
  152. package/server/dist/mcp/tools/catalog.js +45 -0
  153. package/server/dist/mcp/tools/chat.js +147 -0
  154. package/server/dist/mcp/tools/code.js +145 -0
  155. package/server/dist/mcp/tools/jira.js +222 -0
  156. package/server/dist/mcp/tools/jobs.js +258 -0
  157. package/server/dist/mcp/tools/loops.js +229 -0
  158. package/server/dist/mcp/tools/meta.js +96 -0
  159. package/server/dist/mcp/tools/plugins.js +83 -0
  160. package/server/dist/mcp/tools/projects.js +62 -0
  161. package/server/dist/mcp/tools/rails.js +181 -0
  162. package/server/dist/mcp/tools/setup.js +167 -0
  163. package/server/dist/mcp/tools/specs.js +327 -0
  164. package/server/dist/mcp/tools/types.js +156 -0
  165. package/server/dist/mcp/tools/watch.js +93 -0
  166. package/server/dist/mobile/mobile-event-bus.js +2 -0
  167. package/server/dist/project-router-setup.js +15 -1
  168. package/server/dist/project-router-tickets.js +2 -2
  169. package/server/dist/providers/codex-adapter.js +19 -3
  170. package/server/dist/rails-router.js +5 -0
  171. package/server/dist/setup-manager.js +28 -0
  172. package/server/dist/spawn-lifecycle.js +21 -13
  173. package/server/dist/workspace-manager.js +11 -0
  174. package/client/dist/assets/AgentsPage-jcPUtPhP.js +0 -86
  175. package/client/dist/assets/AnalyticsPage-DZkVU3yd.js +0 -1
  176. package/client/dist/assets/CodePage-BcbZT88K.js +0 -2
  177. package/client/dist/assets/DocsDialog-CS3Syf61.js +0 -11
  178. package/client/dist/assets/DocsPage-l3ZcTO2E.js +0 -11
  179. package/client/dist/assets/IntegrationsPage-CJ_Om1zu.js +0 -3
  180. package/client/dist/assets/JobDetailPage-D5Cxw0md.js +0 -16
  181. package/client/dist/assets/JobsPage-3v_iX83r.js +0 -1
  182. package/client/dist/assets/LoopBuilderPage-4s_adZy5.js +0 -7
  183. package/client/dist/assets/LoopsPage-DA94-o0j.js +0 -1
  184. package/client/dist/assets/chunk-CilyBKbf.js +0 -1
  185. package/client/dist/assets/commands-B9XEyMj-.js +0 -1
  186. package/client/dist/assets/commands-BI1Ta-E7.js +0 -1
  187. package/client/dist/assets/commands-BdI0CtOL.js +0 -1
  188. package/client/dist/assets/commands-C2ewlyXo.js +0 -1
  189. package/client/dist/assets/commands-CwypztRT.js +0 -1
  190. package/client/dist/assets/commands-DSX3yJyr.js +0 -1
  191. package/client/dist/assets/commands-Ic1DJDxm.js +0 -1
  192. package/client/dist/assets/commands-yNn4IA4C.js +0 -1
  193. package/client/dist/assets/index-BRMyOS1B.css +0 -2
  194. package/client/dist/assets/index-meTWPBzY.js +0 -144
  195. package/client/dist/assets/settings-0m2iuTzd.js +0 -1
  196. package/client/dist/assets/settings-BA1V6KdK.js +0 -1
  197. package/client/dist/assets/settings-BN7OusER.js +0 -1
  198. package/client/dist/assets/settings-Bi5FFkIw.js +0 -1
  199. package/client/dist/assets/settings-CRJxyTNN.js +0 -1
  200. package/client/dist/assets/settings-DyBxunCh.js +0 -1
  201. package/client/dist/assets/settings-fk-WlnUt.js +0 -1
  202. package/client/dist/assets/settings-iQe5Dim-.js +0 -1
  203. package/client/dist/assets/setup-BOmQ4vh2.js +0 -1
  204. package/client/dist/assets/setup-BwO2-kuK.js +0 -1
  205. package/client/dist/assets/setup-CUTNWz-0.js +0 -1
  206. package/client/dist/assets/setup-Cf3hjChU.js +0 -1
  207. package/client/dist/assets/setup-CsuXrbJ2.js +0 -1
  208. package/client/dist/assets/setup-Df4cJOqu.js +0 -1
  209. package/client/dist/assets/setup-Do0fWx6W.js +0 -1
  210. package/client/dist/assets/setup-GlkDhBlJ.js +0 -1
  211. package/client/dist/assets/upload-DPG87J4t.js +0 -1
  212. /package/client/dist/assets/{abap-CK68cr9-.js → abap-CpHUwCVK.js} +0 -0
  213. /package/client/dist/assets/{activity-Di2f50_Z.js → activity-B82z3Pna.js} +0 -0
  214. /package/client/dist/assets/{activity-B2MvEH54.js → activity-BId0pwuS2.js} +0 -0
  215. /package/client/dist/assets/{activity-D4dhNEwO.js → activity-BVpZrs0J.js} +0 -0
  216. /package/client/dist/assets/{activity-DHjfmAyT.js → activity-BySXFT2J2.js} +0 -0
  217. /package/client/dist/assets/{activity-usnNa7FX.js → activity-DIWnV0Pl.js} +0 -0
  218. /package/client/dist/assets/{activity-C7VdmeGb.js → activity-Uj576ckT.js} +0 -0
  219. /package/client/dist/assets/{activity-BbNkUmW4.js → activity-w1qMFwxh.js} +0 -0
  220. /package/client/dist/assets/{addspec-CO1xv6BL.js → addspec-B9dY_F5T.js} +0 -0
  221. /package/client/dist/assets/{addspec-CNyAUGz_.js → addspec-B9nhMikb.js} +0 -0
  222. /package/client/dist/assets/{addspec-IozoiiGs.js → addspec-BtNaJHu2.js} +0 -0
  223. /package/client/dist/assets/{addspec-BxMn39wn.js → addspec-D43TlMVp2.js} +0 -0
  224. /package/client/dist/assets/{addspec-1HEtmLv1.js → addspec-DfgIZ5lO.js} +0 -0
  225. /package/client/dist/assets/{addspec-DfuR6EAl.js → addspec-W0r16BOC2.js} +0 -0
  226. /package/client/dist/assets/{addspec-DAveBiCE.js → addspec-tbxr3sHa.js} +0 -0
  227. /package/client/dist/assets/{agentstudio-DCyDW8sy.js → agentstudio-0o3XKrcy.js} +0 -0
  228. /package/client/dist/assets/{agentstudio-9NbsoGXk.js → agentstudio-Bu8m5jDw.js} +0 -0
  229. /package/client/dist/assets/{agentstudio-B1y0JWY-.js → agentstudio-C7xxqVWn.js} +0 -0
  230. /package/client/dist/assets/{agentstudio-1E-bag77.js → agentstudio-D-UoNAIA.js} +0 -0
  231. /package/client/dist/assets/{agentstudio-B-jQgYV-.js → agentstudio-OJjjz6Uf2.js} +0 -0
  232. /package/client/dist/assets/{agentstudio-Q0Sq_jTg.js → agentstudio-ObTpVbBP.js} +0 -0
  233. /package/client/dist/assets/{agentstudio-Bwa72b1S.js → agentstudio-PzFB3vfh2.js} +0 -0
  234. /package/client/dist/assets/{aiedit-DUpe7NxI.js → aiedit-BIQqv2XM2.js} +0 -0
  235. /package/client/dist/assets/{aiedit-kAU7KADg.js → aiedit-BaLZalLa.js} +0 -0
  236. /package/client/dist/assets/{aiedit-CvSspQmE.js → aiedit-BjlUzasF.js} +0 -0
  237. /package/client/dist/assets/{aiedit-DSLtsYIu.js → aiedit-BmDufyGU.js} +0 -0
  238. /package/client/dist/assets/{aiedit-qTGI_PGs.js → aiedit-DAM_WRAl2.js} +0 -0
  239. /package/client/dist/assets/{aiedit-CfBNanHE.js → aiedit-DRrNiaB4.js} +0 -0
  240. /package/client/dist/assets/{aiedit-CGxeml8D.js → aiedit-sP_iao2j.js} +0 -0
  241. /package/client/dist/assets/{analytics-CgEkAUiP.js → analytics-BDqqCzN9.js} +0 -0
  242. /package/client/dist/assets/{analytics-DEsEAJZL.js → analytics-B_rxNC9X.js} +0 -0
  243. /package/client/dist/assets/{analytics-D1ElChhs.js → analytics-C4z_NgJZ.js} +0 -0
  244. /package/client/dist/assets/{analytics-DDqve9Ma.js → analytics-DL2vJCbq.js} +0 -0
  245. /package/client/dist/assets/{analytics-CQqu1yE5.js → analytics-Ds_PNxtO2.js} +0 -0
  246. /package/client/dist/assets/{analytics-BDlELxc7.js → analytics-MVJRjcY52.js} +0 -0
  247. /package/client/dist/assets/{analytics-DBGO8B6-.js → analytics-e1mkNTgy2.js} +0 -0
  248. /package/client/dist/assets/{apex-BHHyAVNb.js → apex-DlD7oJX6.js} +0 -0
  249. /package/client/dist/assets/{atom-one-dark-DrWoEEbZ.js → atom-one-dark-BrRN8wBy.js} +0 -0
  250. /package/client/dist/assets/{attachments-BNgH6Tcu.js → attachments-8QCKHCEF2.js} +0 -0
  251. /package/client/dist/assets/{attachments-BLqqPGyy.js → attachments-BP0Ac1yt.js} +0 -0
  252. /package/client/dist/assets/{attachments-UJtEq892.js → attachments-CJaO1vCH.js} +0 -0
  253. /package/client/dist/assets/{attachments-D5fVsMgb.js → attachments-CPBVeHX72.js} +0 -0
  254. /package/client/dist/assets/{attachments-3CuBBRFu.js → attachments-CV2uaTGE.js} +0 -0
  255. /package/client/dist/assets/{attachments-CppM7de8.js → attachments-CaOjdN332.js} +0 -0
  256. /package/client/dist/assets/{attachments-pW3zm9nu.js → attachments-Db7JfA8a.js} +0 -0
  257. /package/client/dist/assets/{azcli-C56lfIEP.js → azcli-CmXFAbc-.js} +0 -0
  258. /package/client/dist/assets/{bat-BLamSaf-.js → bat-DdNJq4hH.js} +0 -0
  259. /package/client/dist/assets/{bicep-lrOcEItS.js → bicep-Bppz7WOA.js} +0 -0
  260. /package/client/dist/assets/{browser-C__Vz7KR.js → browser-0xeW8fUa2.js} +0 -0
  261. /package/client/dist/assets/{browser-EFmPpkvO.js → browser-543jbxE4.js} +0 -0
  262. /package/client/dist/assets/{browser-DhwTc0ry.js → browser-C9rUUxo4.js} +0 -0
  263. /package/client/dist/assets/{browser-C91MQBxx.js → browser-CEqx11iy.js} +0 -0
  264. /package/client/dist/assets/{browser-B7i7Ysc8.js → browser-CLC0etB_2.js} +0 -0
  265. /package/client/dist/assets/{browser-DwE289IS.js → browser-DyG2l9Wa.js} +0 -0
  266. /package/client/dist/assets/{browser-BVQgd1_1.js → browser-ExmELH702.js} +0 -0
  267. /package/client/dist/assets/{cameligo-BK_p0bgU.js → cameligo-0MRMdSmg.js} +0 -0
  268. /package/client/dist/assets/{chat-C5nJF6L0.js → chat-CCytD3P8.js} +0 -0
  269. /package/client/dist/assets/{chat-UMl6H55K.js → chat-CN8BMAMc.js} +0 -0
  270. /package/client/dist/assets/{chat-BXVx6rb5.js → chat-Cu6F3d6g2.js} +0 -0
  271. /package/client/dist/assets/{chat-CE8bx6Rz.js → chat-DagUyoVA.js} +0 -0
  272. /package/client/dist/assets/{chat-PVakYp92.js → chat-IjutwlBg.js} +0 -0
  273. /package/client/dist/assets/{chat-9RcD5Lcn.js → chat-NQPCBQo72.js} +0 -0
  274. /package/client/dist/assets/{chat-BnJPyeUK.js → chat-vVpwkOp52.js} +0 -0
  275. /package/client/dist/assets/{clojure-BQ7JOykv.js → clojure-B5dKj863.js} +0 -0
  276. /package/client/dist/assets/{clsx-CjLeUJfG.js → clsx-1dnHRn8W.js} +0 -0
  277. /package/client/dist/assets/{code-OoLqhjoI.js → code-32l6Naxu2.js} +0 -0
  278. /package/client/dist/assets/{code-Lg4awa35.js → code-BaG05zK62.js} +0 -0
  279. /package/client/dist/assets/{code-BPMR8_Vp.js → code-C5cibF2F.js} +0 -0
  280. /package/client/dist/assets/{code-aCUqkVN_.js → code-Ce-nBIwj.js} +0 -0
  281. /package/client/dist/assets/{code-BsOBm1Fb.js → code-Dap54vOb.js} +0 -0
  282. /package/client/dist/assets/{code-u9Sg9ohM.js → code-Dhb6jbAU.js} +0 -0
  283. /package/client/dist/assets/{code-eh4vO2-_.js → code-_NOCo-Ol2.js} +0 -0
  284. /package/client/dist/assets/{coffee-BD3DiL5s.js → coffee-DSbWjzk7.js} +0 -0
  285. /package/client/dist/assets/{common-C9VyfY7g.js → common-0aY9HQBP.js} +0 -0
  286. /package/client/dist/assets/{common-LDwESeCX.js → common-5jScBJqt.js} +0 -0
  287. /package/client/dist/assets/{common-ULlk84G4.js → common-BdaRciTu.js} +0 -0
  288. /package/client/dist/assets/{common-CzCQ0rpW.js → common-CHARW6MZ2.js} +0 -0
  289. /package/client/dist/assets/{common-CNoRVpnA.js → common-CQCJzAQq2.js} +0 -0
  290. /package/client/dist/assets/{common-Dmpyh323.js → common-DNhhqjlJ.js} +0 -0
  291. /package/client/dist/assets/{common-CcHFVATT.js → common-YdyZtmaI2.js} +0 -0
  292. /package/client/dist/assets/{cpp-splnCwCh.js → cpp-SBF8SAh9.js} +0 -0
  293. /package/client/dist/assets/{csharp-BCpsl-o3.js → csharp-Lzuq_1t-.js} +0 -0
  294. /package/client/dist/assets/{csp-tkCWrZmb.js → csp-BEzxZqi1.js} +0 -0
  295. /package/client/dist/assets/{css-B_xdnReq.js → css-C3D3PKln.js} +0 -0
  296. /package/client/dist/assets/{cypher-OL1zq-y3.js → cypher-CQ3dEPmv.js} +0 -0
  297. /package/client/dist/assets/{dart-CtTlOkrm.js → dart-DCPmxolI.js} +0 -0
  298. /package/client/dist/assets/{dashboard-C52PElPV.js → dashboard-BTiH361F.js} +0 -0
  299. /package/client/dist/assets/{dashboard-C-dXxut-.js → dashboard-COSm_14E.js} +0 -0
  300. /package/client/dist/assets/{dashboard-CUjyhohl.js → dashboard-CfOs70FN2.js} +0 -0
  301. /package/client/dist/assets/{dashboard-zH9quXks.js → dashboard-CzHDp14_2.js} +0 -0
  302. /package/client/dist/assets/{dashboard-03EpOoMM.js → dashboard-D6Woy2B5.js} +0 -0
  303. /package/client/dist/assets/{dashboard-C2owt3eN.js → dashboard-DwbH4oVH.js} +0 -0
  304. /package/client/dist/assets/{dashboard-Ch6H-457.js → dashboard-cWhU3krA2.js} +0 -0
  305. /package/client/dist/assets/{dockerfile-DcQ5CNiW.js → dockerfile-B_2_aaTO.js} +0 -0
  306. /package/client/dist/assets/{ecl-DZpiDRt6.js → ecl-rADUeXsV.js} +0 -0
  307. /package/client/dist/assets/{elixir-Dzj2lqtK.js → elixir-DOd-TuJO.js} +0 -0
  308. /package/client/dist/assets/{explore-CCcPXrHg.js → explore-B82VMUJ_.js} +0 -0
  309. /package/client/dist/assets/{explore-DQ0XEjed.js → explore-BGz1rvQE.js} +0 -0
  310. /package/client/dist/assets/{explore-xQoaak6e.js → explore-BzdMEpuf.js} +0 -0
  311. /package/client/dist/assets/{explore-B2n8r9Ye.js → explore-CbW0381i2.js} +0 -0
  312. /package/client/dist/assets/{explore-4SD1DxR0.js → explore-ChtxIiSM.js} +0 -0
  313. /package/client/dist/assets/{explore-BxR9q8Hq.js → explore-DfcUfiPw2.js} +0 -0
  314. /package/client/dist/assets/{explore-CZt4Ncpa.js → explore-yRF7wFSp2.js} +0 -0
  315. /package/client/dist/assets/{flow9-CFENCHIZ.js → flow9-Bf7zg_j0.js} +0 -0
  316. /package/client/dist/assets/{format-command-CMsBTOPN.js → format-command-D96da4fZ.js} +0 -0
  317. /package/client/dist/assets/{fsharp-D-4bv7FB.js → fsharp-BqROZjbA.js} +0 -0
  318. /package/client/dist/assets/{go-djxbTJq7.js → go-oQTP5uLe.js} +0 -0
  319. /package/client/dist/assets/{graphql-DHIFrkZ6.js → graphql-CSHfTU3q.js} +0 -0
  320. /package/client/dist/assets/{hcl-CXVoowNb.js → hcl-CeG-RoFJ.js} +0 -0
  321. /package/client/dist/assets/{ini-Crzc5kly.js → ini-B2Dv4ohc.js} +0 -0
  322. /package/client/dist/assets/{integrations-C7u7SInG.js → integrations-B0OBPU9_.js} +0 -0
  323. /package/client/dist/assets/{integrations-B17KyA-i.js → integrations-BA5pM05t.js} +0 -0
  324. /package/client/dist/assets/{integrations-DkBi-Gsh.js → integrations-BJvrlcc7.js} +0 -0
  325. /package/client/dist/assets/{integrations-DwSVdCpf.js → integrations-CPKYkG36.js} +0 -0
  326. /package/client/dist/assets/{integrations-A9f08E0o.js → integrations-CnrX7vqw2.js} +0 -0
  327. /package/client/dist/assets/{integrations-9qxUNhPD.js → integrations-DBou159M2.js} +0 -0
  328. /package/client/dist/assets/{integrations-B46ac3sf.js → integrations-DeJFRyXz2.js} +0 -0
  329. /package/client/dist/assets/{java-CYLaJrdE.js → java-aUDDeufb.js} +0 -0
  330. /package/client/dist/assets/{jira-Dht8qNck.js → jira-BDgxAFTF2.js} +0 -0
  331. /package/client/dist/assets/{jira-4Fin3fu7.js → jira-BwLBgmfI2.js} +0 -0
  332. /package/client/dist/assets/{jira-0ldfMByG.js → jira-D3GGzXOI.js} +0 -0
  333. /package/client/dist/assets/{jira-BpnpUFlb.js → jira-DeotPx6P2.js} +0 -0
  334. /package/client/dist/assets/{jira-DzIVvNmy.js → jira-NwlUCcQD.js} +0 -0
  335. /package/client/dist/assets/{jira-CBU0200d.js → jira-Srx2P5zS.js} +0 -0
  336. /package/client/dist/assets/{jira-01H1RfQj.js → jira-mi37H4Kt.js} +0 -0
  337. /package/client/dist/assets/{jobs-jEQidSIb.js → jobs-B56vQEkh2.js} +0 -0
  338. /package/client/dist/assets/{jobs-DM9owF1j.js → jobs-BYIk6Tg_.js} +0 -0
  339. /package/client/dist/assets/{jobs-Bw4hqEro.js → jobs-C-lM-iax2.js} +0 -0
  340. /package/client/dist/assets/{jobs-BF9raUkL.js → jobs-CYdevGgT.js} +0 -0
  341. /package/client/dist/assets/{jobs-ym72SIR9.js → jobs-D8adm2BX.js} +0 -0
  342. /package/client/dist/assets/{jobs-DcCONARH.js → jobs-K_Y8KAZ2.js} +0 -0
  343. /package/client/dist/assets/{jobs-Oz3UtLQn.js → jobs-OwM0ZWhI2.js} +0 -0
  344. /package/client/dist/assets/{julia-BJ7tUU-T.js → julia-C5LbruO5.js} +0 -0
  345. /package/client/dist/assets/{kotlin-CeWcU3lC.js → kotlin-C6mOuSKk.js} +0 -0
  346. /package/client/dist/assets/{less-CviwWNG4.js → less-D5qC3caW.js} +0 -0
  347. /package/client/dist/assets/{lexon-Dt28jqDS.js → lexon-Dr2La-fj.js} +0 -0
  348. /package/client/dist/assets/{loops-DE-2cmLw.js → loops-BDhEfnx82.js} +0 -0
  349. /package/client/dist/assets/{loops-mcoeFMat.js → loops-BM5QggwH.js} +0 -0
  350. /package/client/dist/assets/{loops-D_ZxkD4l.js → loops-CPjLAAxj.js} +0 -0
  351. /package/client/dist/assets/{loops-BYNUJVyD.js → loops-DKnk79TB2.js} +0 -0
  352. /package/client/dist/assets/{loops-B_gS4Pqu.js → loops-DLW0aFMm.js} +0 -0
  353. /package/client/dist/assets/{loops-Fv4Bs-9A.js → loops-Dcv3dFy_2.js} +0 -0
  354. /package/client/dist/assets/{loops-B_w7STrw.js → loops-y7wFRx6M.js} +0 -0
  355. /package/client/dist/assets/{lua-B6AOaJSi.js → lua-Zy6u5z6Z.js} +0 -0
  356. /package/client/dist/assets/{m3-VnMkHJcy.js → m3-C7XHeDz_.js} +0 -0
  357. /package/client/dist/assets/{markdown-8vKNdvIM.js → markdown-PiF7r-2h.js} +0 -0
  358. /package/client/dist/assets/{mips-DoEcuzan.js → mips-nn07GiyE.js} +0 -0
  359. /package/client/dist/assets/{msdax-CNzvHyEp.js → msdax-DaKFVMST.js} +0 -0
  360. /package/client/dist/assets/{mysql-Buk3vUtL.js → mysql-C6d6xrbU.js} +0 -0
  361. /package/client/dist/assets/{nav-Cw3_40fA.js → nav-1hj2izqB2.js} +0 -0
  362. /package/client/dist/assets/{nav-DNDJ3H-_.js → nav-BBUKMAu-2.js} +0 -0
  363. /package/client/dist/assets/{nav-CrG_mYRr.js → nav-CLqkP-26.js} +0 -0
  364. /package/client/dist/assets/{nav-DVp1nEsi.js → nav-Cv46L837.js} +0 -0
  365. /package/client/dist/assets/{nav-DbAbG5_k.js → nav-DgA-rjLk2.js} +0 -0
  366. /package/client/dist/assets/{nav-B5W9IMnf.js → nav-EG2pr6vN.js} +0 -0
  367. /package/client/dist/assets/{nav-_0vAgZ_k.js → nav-r3mIJVfU.js} +0 -0
  368. /package/client/dist/assets/{objective-c-DfOmda-5.js → objective-c-B6KMtI-k.js} +0 -0
  369. /package/client/dist/assets/{pascal-DoAA2yjy.js → pascal-DYr8MPE7.js} +0 -0
  370. /package/client/dist/assets/{pascaligo-C65Vn1fu.js → pascaligo-B3pd-jJ_.js} +0 -0
  371. /package/client/dist/assets/{perl-INlfQ_ro.js → perl-z2xUzoaL.js} +0 -0
  372. /package/client/dist/assets/{pgsql-DtwRzglx.js → pgsql-DUvT-09-.js} +0 -0
  373. /package/client/dist/assets/{php-DfHqVNet.js → php-CGyOmJfy.js} +0 -0
  374. /package/client/dist/assets/{pla-D3dNVRYk.js → pla-CMqgvyD0.js} +0 -0
  375. /package/client/dist/assets/{postiats-UKXD1rM4.js → postiats-Dx5LUn4Z.js} +0 -0
  376. /package/client/dist/assets/{powerquery-zE3E3O7s.js → powerquery-DazYxvJ6.js} +0 -0
  377. /package/client/dist/assets/{powershell-Df9b3zQl.js → powershell-B3nwlDka.js} +0 -0
  378. /package/client/dist/assets/{protobuf-BF0nUVrW.js → protobuf-BGuu2u2s.js} +0 -0
  379. /package/client/dist/assets/{pug-Bj6Yikv_.js → pug-Dbte1MXA.js} +0 -0
  380. /package/client/dist/assets/{qsharp-BI4h-raZ.js → qsharp-DJLwBOvG.js} +0 -0
  381. /package/client/dist/assets/{r-DhpnmaXx.js → r-DmGBRSHj.js} +0 -0
  382. /package/client/dist/assets/{redis-DwYYPr4d.js → redis-VvUYn0a4.js} +0 -0
  383. /package/client/dist/assets/{redshift-D2f3Bc-4.js → redshift-6GdDrxg3.js} +0 -0
  384. /package/client/dist/assets/{restructuredtext-Dkiif6fe.js → restructuredtext-CfAB8aRU.js} +0 -0
  385. /package/client/dist/assets/{ruby-C6hLmHy0.js → ruby-CIYK6MXY.js} +0 -0
  386. /package/client/dist/assets/{rust-X8WAGtxB.js → rust-CTHZfjj9.js} +0 -0
  387. /package/client/dist/assets/{sb-4M2la0Kr.js → sb-CkKTdkz_.js} +0 -0
  388. /package/client/dist/assets/{scala-CaCXz58E.js → scala-Bx-Wr9TM.js} +0 -0
  389. /package/client/dist/assets/{scheme-CMdjpmP7.js → scheme-Dcfi6f83.js} +0 -0
  390. /package/client/dist/assets/{scss-QopmvPyp.js → scss-ByL-7PFQ.js} +0 -0
  391. /package/client/dist/assets/{shell-CDBchS00.js → shell-Csnb9Yqd.js} +0 -0
  392. /package/client/dist/assets/{solidity-DvBfi5mC.js → solidity-Dbw5-0QP.js} +0 -0
  393. /package/client/dist/assets/{sophia-HSnXR17u.js → sophia-ZQ3saZQx.js} +0 -0
  394. /package/client/dist/assets/{sparql-DhG_ppup.js → sparql-BEtOel8u.js} +0 -0
  395. /package/client/dist/assets/{specs-CUE3xSFM.js → specs-B5XOoH-02.js} +0 -0
  396. /package/client/dist/assets/{specs-C2wW5bq8.js → specs-B8mwScoM2.js} +0 -0
  397. /package/client/dist/assets/{specs-Bku8SypT.js → specs-Bj_3fDrQ.js} +0 -0
  398. /package/client/dist/assets/{specs-CnH7dOiF.js → specs-CWNKAScX2.js} +0 -0
  399. /package/client/dist/assets/{specs-BvsFNKTs.js → specs-D4z5d8uq.js} +0 -0
  400. /package/client/dist/assets/{specs-D4ORogcT.js → specs-DgvIRZtM.js} +0 -0
  401. /package/client/dist/assets/{specs-D6Y76Bgi.js → specs-DuRzE5wE.js} +0 -0
  402. /package/client/dist/assets/{sql-DHeIS-tf.js → sql-DGPDbzB7.js} +0 -0
  403. /package/client/dist/assets/{st-CMWchiRU.js → st-GeW2MOgh.js} +0 -0
  404. /package/client/dist/assets/{swift-CDXvBefj.js → swift-BVYTN8jq.js} +0 -0
  405. /package/client/dist/assets/{systemverilog-0RTukYUE.js → systemverilog-Deg2a8Lr.js} +0 -0
  406. /package/client/dist/assets/{tcl-CbBF_Ttl.js → tcl-6DnLmROF.js} +0 -0
  407. /package/client/dist/assets/{terminal-BLh00ouM.js → terminal-B2sExUis.js} +0 -0
  408. /package/client/dist/assets/{terminal-Cz_Puz0q.js → terminal-CH0fdHvP.js} +0 -0
  409. /package/client/dist/assets/{terminal-DFEH9XTQ.js → terminal-CPMigUX7.js} +0 -0
  410. /package/client/dist/assets/{terminal-CB33IsO1.js → terminal-DOwfkxqa2.js} +0 -0
  411. /package/client/dist/assets/{terminal-CR6t-Oey.js → terminal-DjSSa5qP.js} +0 -0
  412. /package/client/dist/assets/{terminal-CY_sPfdD.js → terminal-DoMTHedv2.js} +0 -0
  413. /package/client/dist/assets/{terminal-BTanJIt_.js → terminal-w0i-OCp32.js} +0 -0
  414. /package/client/dist/assets/{tickets-D6f3GSYn.js → tickets-CQTana1u.js} +0 -0
  415. /package/client/dist/assets/{tickets-blQnNA4N.js → tickets-D00mSt832.js} +0 -0
  416. /package/client/dist/assets/{tickets-68LH1xp6.js → tickets-D7CSuWjO.js} +0 -0
  417. /package/client/dist/assets/{tickets-OrJwUxJR.js → tickets-Dq7HDy8n.js} +0 -0
  418. /package/client/dist/assets/{tickets-PtHNxQo5.js → tickets-DvbFg_-c2.js} +0 -0
  419. /package/client/dist/assets/{tickets-REr-EQx9.js → tickets-heDAaj8C2.js} +0 -0
  420. /package/client/dist/assets/{tickets-jxsTrApR.js → tickets-onKZz6Dd.js} +0 -0
  421. /package/client/dist/assets/{twig-DCcGd8FE.js → twig-XbbXW9Yz.js} +0 -0
  422. /package/client/dist/assets/{typespec-HTphp-Uh.js → typespec-CRlV8GhI.js} +0 -0
  423. /package/client/dist/assets/{vb-Cb_yUcVd.js → vb-BHE2izPH.js} +0 -0
  424. /package/client/dist/assets/{wgsl-DrKU3YXj.js → wgsl-Bf5vkfmY.js} +0 -0
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentChatManager = void 0;
4
+ const providers_1 = require("./providers");
5
+ const spawn_lifecycle_1 = require("./spawn-lifecycle");
6
+ const agent_cwd_manager_1 = require("./agent-cwd-manager");
7
+ const agent_mcp_config_1 = require("./agent-mcp-config");
8
+ const agent_tier_1 = require("./agent-tier");
9
+ const agent_store_1 = require("./agent-store");
10
+ const types_1 = require("./mcp/tools/types");
11
+ // ─── AgentChatManager (design D1) ─────────────────────────────────────────────
12
+ //
13
+ // App-level sibling of ChatManager: spawns an AI CLI from the app-owned agent cwd
14
+ // with the Specrails MCP bridge as its tool source, streams the turn over the
15
+ // app-global `agent_*` WS events (no projectId — fans to all subscribers), and
16
+ // persists to the app registry DB. It reuses the shared spawn→stream→settle core
17
+ // (runAiCliInvocation) rather than re-implementing ChatManager's loop.
18
+ const OPERATOR_SYSTEM_PROMPT = 'You are the Specrails operator agent. Drive the Specrails Desktop app on the ' +
19
+ "user's behalf using the specrails_* MCP tools. Target a project with " +
20
+ 'specrails_select_project (or the projectId argument); if none is selected and ' +
21
+ 'the request is project-specific, ask whether to create a project or search all. ' +
22
+ 'Follow HTTP-202 actions to completion with specrails_watch. Respect the ' +
23
+ 'permission ladder: if a tool is refused for the current level, tell the user ' +
24
+ 'which level it needs rather than working around it. Be concise; report tool ' +
25
+ 'outputs faithfully, including failures. Format replies for easy reading: ' +
26
+ 'separate distinct ideas into short paragraphs with a blank line between them ' +
27
+ '(not one dense block), and use bullet lists for enumerations. ' +
28
+ 'When the user has no projects yet, or wants to add one, first explain the UI ' +
29
+ 'steps (click "Add Project" / the + in the left sidebar, enter the repo\'s ' +
30
+ 'folder path, pick an AI provider, then run setup), THEN offer to do it for ' +
31
+ 'them from here: ask for the repo folder path and which AI providers to set up, ' +
32
+ 'then with their go-ahead call specrails_setup add_project (Edit level) and a ' +
33
+ 'single specrails_setup install (Operate level) — one install provisions ALL the ' +
34
+ 'chosen providers in one shot (no need to install them one by one). Setup is ' +
35
+ 'QUICK-only (offline); do NOT offer or mention a "full" or "enrich" install. Use ' +
36
+ 'specrails_setup prerequisites / available_providers first to check the machine.';
37
+ class AgentChatManager {
38
+ _broadcast;
39
+ _db;
40
+ _port;
41
+ _active = new Map();
42
+ constructor(broadcast, db, port) {
43
+ this._broadcast = broadcast;
44
+ this._db = db;
45
+ this._port = port;
46
+ }
47
+ /** True while a turn is streaming for this conversation. */
48
+ isStreaming(conversationId) {
49
+ return this._active.has(conversationId);
50
+ }
51
+ /**
52
+ * Runs one agent turn: persists the user message, spawns the AI CLI pointed at
53
+ * the Specrails MCP, streams deltas + tool-use as `agent_*` events, then
54
+ * persists the assistant reply and the session id. Settles once.
55
+ */
56
+ async sendMessage(conversationId, userText, options = {}) {
57
+ const conversation = (0, agent_store_1.getAgentConversation)(this._db, conversationId);
58
+ if (!conversation) {
59
+ this._emitError(conversationId, 'Unknown conversation');
60
+ return;
61
+ }
62
+ if (this._active.has(conversationId)) {
63
+ this._emitError(conversationId, 'The agent is busy. Try again in a moment.');
64
+ return;
65
+ }
66
+ const tierLevel = (0, agent_tier_1.normalizeLevel)(options.tierLevel ?? conversation.tier_level);
67
+ const adapter = (0, providers_1.getAdapter)(conversation.provider);
68
+ // Resolve a model that is VALID for this provider. A stale model from another
69
+ // provider (e.g. claude's "sonnet" after switching to codex) is rejected by
70
+ // the provider — fall back to the provider's default instead of failing.
71
+ const catalog = new Set(adapter.modelCatalog().map((m) => m.value));
72
+ const requested = options.model || conversation.model;
73
+ const model = requested && catalog.has(requested) ? requested : adapter.defaultModel();
74
+ // Make the pinned project (Cursor-style selector) the MCP active project for
75
+ // this turn, so project-scoped tools resolve without the agent having to call
76
+ // specrails_select_project. Home (null) clears it → app-global mode.
77
+ (0, types_1.setActiveProject)(conversation.pinned_project_id ?? null);
78
+ (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'user', content: userText });
79
+ // Tell the agent which project is pinned (so it phrases + passes projectId).
80
+ const prompt = conversation.pinned_project_id
81
+ ? `[Active project: projectId="${conversation.pinned_project_id}". Use it for project-scoped tools unless told otherwise.]\n\n${userText}`
82
+ : userText;
83
+ const cwd = (0, agent_cwd_manager_1.ensureAgentCwd)();
84
+ let mcpArgs = [];
85
+ let mcpEnv = {};
86
+ try {
87
+ const wiring = (0, agent_mcp_config_1.prepareAgentMcp)({
88
+ adapterId: adapter.id,
89
+ conversationId,
90
+ cwd,
91
+ port: this._port,
92
+ tierLevel,
93
+ activeProjectId: conversation.pinned_project_id,
94
+ });
95
+ mcpArgs = wiring.extraArgs;
96
+ mcpEnv = wiring.env;
97
+ }
98
+ catch (err) {
99
+ // A bad conversation id or fs failure shouldn't poison the chat — run tool-less.
100
+ console.error('[agent-chat] failed to prepare mcp:', err);
101
+ }
102
+ const timestamp = () => new Date().toISOString();
103
+ const invoke = async (useResume) => {
104
+ const action = useResume ? 'chat-resume' : 'chat-turn';
105
+ let streamed = '';
106
+ let capturedSessionId = useResume ? conversation.session_id ?? null : null;
107
+ let capturedError = null;
108
+ console.log(`[agent-chat] turn start conv=${conversationId} provider=${adapter.id} binary=${adapter.binary} ` +
109
+ `action=${action} model=${model} cwd=${cwd} mcp=${mcpArgs.length ? 'mcp-config' : Object.keys(mcpEnv).join(',') || 'none'}`);
110
+ const result = await (0, spawn_lifecycle_1.runAiCliInvocation)({
111
+ adapter,
112
+ action,
113
+ cwd,
114
+ env: { ...process.env, ...mcpEnv },
115
+ buildOpts: {
116
+ prompt,
117
+ systemPrompt: adapter.capabilities.systemPromptArg ? OPERATOR_SYSTEM_PROMPT : undefined,
118
+ model,
119
+ sessionId: useResume ? conversation.session_id ?? undefined : undefined,
120
+ extraArgs: mcpArgs,
121
+ },
122
+ onSpawn: (child) => {
123
+ this._active.set(conversationId, child);
124
+ },
125
+ onEvent: (ev) => {
126
+ switch (ev.kind) {
127
+ case 'text-delta':
128
+ streamed += ev.text;
129
+ this._broadcast({ type: 'agent_stream', conversationId, delta: ev.text, timestamp: timestamp() });
130
+ break;
131
+ case 'tool-use':
132
+ this._broadcast({ type: 'agent_tool', conversationId, tool: ev.name, timestamp: timestamp() });
133
+ break;
134
+ case 'session-started':
135
+ if (ev.sessionId)
136
+ capturedSessionId = ev.sessionId;
137
+ break;
138
+ case 'result': {
139
+ const sid = ev.payload.session_id;
140
+ if (sid)
141
+ capturedSessionId = sid;
142
+ break;
143
+ }
144
+ case 'error':
145
+ capturedError = ev.message;
146
+ break;
147
+ case 'other':
148
+ break;
149
+ }
150
+ },
151
+ });
152
+ this._active.delete(conversationId);
153
+ const text = streamed.trim();
154
+ console.log(`[agent-chat] turn end conv=${conversationId} provider=${adapter.id} code=${result.code} ` +
155
+ `spawnFailed=${result.spawnFailed} chars=${text.length} err=${capturedError ?? ''}`);
156
+ if (result.stderrTail && (result.spawnFailed || (result.code ?? 0) !== 0 || !text)) {
157
+ console.error(`[agent-chat] ${adapter.id} stderr:\n${result.stderrTail}`);
158
+ }
159
+ return { text, sessionId: capturedSessionId, error: capturedError, code: result.code, spawnFailed: result.spawnFailed, stderrTail: result.stderrTail };
160
+ };
161
+ const canResume = !!conversation.session_id && adapter.capabilities.nativeResume;
162
+ let r = await invoke(canResume);
163
+ // Auto-heal a stale/foreign session: a resume that produced no text (e.g. a
164
+ // provider switch leaving another provider's session id, or codex
165
+ // "no rollout found for thread id") retries once as a fresh turn.
166
+ if (canResume && !r.spawnFailed && !r.text) {
167
+ console.log(`[agent-chat] resume produced no text — retrying fresh conv=${conversationId}`);
168
+ (0, agent_store_1.updateAgentConversation)(this._db, conversationId, { session_id: null });
169
+ r = await invoke(false);
170
+ }
171
+ if (r.spawnFailed) {
172
+ this._emitError(conversationId, `Failed to launch ${adapter.binary}. Is it installed and on PATH?`);
173
+ return;
174
+ }
175
+ if (r.text) {
176
+ (0, agent_store_1.addAgentMessage)(this._db, { conversationId, role: 'assistant', content: r.text });
177
+ (0, agent_store_1.updateAgentConversation)(this._db, conversationId, {
178
+ session_id: r.sessionId,
179
+ provider: conversation.provider,
180
+ model,
181
+ tier_level: tierLevel,
182
+ });
183
+ this._broadcast({ type: 'agent_done', conversationId, fullText: r.text, timestamp: timestamp() });
184
+ return;
185
+ }
186
+ // Failure: reset the session so the NEXT turn starts fresh, and surface the
187
+ // real reason instead of silently doing nothing.
188
+ (0, agent_store_1.updateAgentConversation)(this._db, conversationId, { session_id: null, provider: conversation.provider, model, tier_level: tierLevel });
189
+ const reason = r.error ||
190
+ (r.stderrTail ? r.stderrTail.split('\n').filter(Boolean).slice(-3).join(' ').slice(0, 300) : '') ||
191
+ (r.code !== 0 ? `${adapter.binary} exited with code ${r.code}` : 'The agent returned no output.');
192
+ this._emitError(conversationId, reason);
193
+ }
194
+ /** Aborts the active turn for a conversation, if any. */
195
+ abort(conversationId) {
196
+ const child = this._active.get(conversationId);
197
+ if (!child)
198
+ return false;
199
+ try {
200
+ child.kill('SIGTERM');
201
+ }
202
+ catch {
203
+ /* ignore */
204
+ }
205
+ this._active.delete(conversationId);
206
+ return true;
207
+ }
208
+ /** Kills all active turns (graceful shutdown). */
209
+ async shutdown() {
210
+ for (const [, child] of this._active) {
211
+ try {
212
+ child.kill('SIGTERM');
213
+ }
214
+ catch {
215
+ /* ignore */
216
+ }
217
+ }
218
+ this._active.clear();
219
+ }
220
+ _emitError(conversationId, error) {
221
+ this._broadcast({ type: 'agent_error', conversationId, error, timestamp: new Date().toISOString() });
222
+ }
223
+ }
224
+ exports.AgentChatManager = AgentChatManager;
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isAgentChatEnabled = isAgentChatEnabled;
4
+ exports.createAgentChatRouter = createAgentChatRouter;
5
+ const express_1 = require("express");
6
+ const providers_1 = require("./providers");
7
+ const agent_tier_1 = require("./agent-tier");
8
+ const agent_store_1 = require("./agent-store");
9
+ function isAgentChatEnabled() {
10
+ return process.env.SPECRAILS_AGENT_CHAT !== 'false';
11
+ }
12
+ function validProvider(provider) {
13
+ if (typeof provider !== 'string' || !provider)
14
+ return null;
15
+ try {
16
+ (0, providers_1.getAdapter)(provider);
17
+ return provider;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function createAgentChatRouter(deps) {
24
+ const { manager, desktopDb } = deps;
25
+ const router = (0, express_1.Router)();
26
+ // Feature gate: 404 the whole surface when disabled.
27
+ router.use((_req, res, next) => {
28
+ if (!isAgentChatEnabled()) {
29
+ res.status(404).json({ error: 'Agent chat is disabled' });
30
+ return;
31
+ }
32
+ next();
33
+ });
34
+ // Per-provider model catalog for the header model selector.
35
+ router.get('/models', (req, res) => {
36
+ const provider = validProvider(req.query.provider) ?? 'claude';
37
+ res.json({ provider, models: (0, providers_1.getAdapter)(provider).modelCatalog() });
38
+ });
39
+ router.get('/conversations', (_req, res) => {
40
+ res.json({ conversations: (0, agent_store_1.listAgentConversations)(desktopDb) });
41
+ });
42
+ router.post('/conversations', (req, res) => {
43
+ const body = (req.body ?? {});
44
+ let provider = 'claude';
45
+ if (body.provider !== undefined) {
46
+ const v = validProvider(body.provider);
47
+ if (!v) {
48
+ res.status(400).json({ error: 'Unknown or unavailable provider' });
49
+ return;
50
+ }
51
+ provider = v;
52
+ }
53
+ const conversation = (0, agent_store_1.createAgentConversation)(desktopDb, {
54
+ provider,
55
+ model: typeof body.model === 'string' ? body.model : null,
56
+ pinnedProjectId: typeof body.pinnedProjectId === 'string' ? body.pinnedProjectId : null,
57
+ tierLevel: body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : 0,
58
+ });
59
+ res.status(201).json({ conversation });
60
+ });
61
+ router.get('/conversations/:id', (req, res) => {
62
+ const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
63
+ if (!conversation) {
64
+ res.status(404).json({ error: 'Unknown conversation' });
65
+ return;
66
+ }
67
+ res.json({ conversation, messages: (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id) });
68
+ });
69
+ router.patch('/conversations/:id', (req, res) => {
70
+ const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
71
+ if (!conversation) {
72
+ res.status(404).json({ error: 'Unknown conversation' });
73
+ return;
74
+ }
75
+ const body = (req.body ?? {});
76
+ const patch = {};
77
+ if (body.title !== undefined)
78
+ patch.title = typeof body.title === 'string' ? body.title : null;
79
+ if (body.pinnedProjectId !== undefined) {
80
+ patch.pinned_project_id = typeof body.pinnedProjectId === 'string' ? body.pinnedProjectId : null;
81
+ }
82
+ if (body.tierLevel !== undefined)
83
+ patch.tier_level = (0, agent_tier_1.normalizeLevel)(body.tierLevel);
84
+ if (body.provider !== undefined) {
85
+ const v = validProvider(body.provider);
86
+ if (!v) {
87
+ res.status(400).json({ error: 'Unknown or unavailable provider' });
88
+ return;
89
+ }
90
+ patch.provider = v;
91
+ // Switching provider invalidates the stored session id AND model (both
92
+ // belong to the previous provider) — clear them so the next turn starts a
93
+ // fresh session with the new provider's default model (codex rejects
94
+ // claude's "sonnet"; resume of a foreign thread → "no rollout found").
95
+ if (v !== conversation.provider) {
96
+ patch.session_id = null;
97
+ patch.model = null;
98
+ }
99
+ }
100
+ // Explicit model pick — validated against the (new or current) provider's catalog.
101
+ if (body.model !== undefined && patch.model === undefined) {
102
+ if (body.model === null) {
103
+ patch.model = null;
104
+ }
105
+ else if (typeof body.model === 'string') {
106
+ const effectiveProvider = patch.provider ?? conversation.provider;
107
+ const valid = new Set((0, providers_1.getAdapter)(effectiveProvider).modelCatalog().map((m) => m.value));
108
+ if (!valid.has(body.model)) {
109
+ res.status(400).json({ error: 'Model not available for this provider' });
110
+ return;
111
+ }
112
+ patch.model = body.model;
113
+ }
114
+ }
115
+ res.json({ conversation: (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, patch) });
116
+ });
117
+ router.delete('/conversations/:id', (req, res) => {
118
+ manager.abort(String(req.params.id));
119
+ (0, agent_store_1.deleteAgentConversation)(desktopDb, String(req.params.id));
120
+ res.json({ ok: true });
121
+ });
122
+ router.post('/conversations/:id/send', (req, res) => {
123
+ const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
124
+ if (!conversation) {
125
+ res.status(404).json({ error: 'Unknown conversation' });
126
+ return;
127
+ }
128
+ const body = (req.body ?? {});
129
+ const text = typeof body.text === 'string' ? body.text.trim() : '';
130
+ if (!text) {
131
+ res.status(400).json({ error: 'text is required' });
132
+ return;
133
+ }
134
+ const tierLevel = body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : undefined;
135
+ const model = typeof body.model === 'string' ? body.model : undefined;
136
+ // Fire-and-forget: the turn streams over WS. Persist the chosen tier first so
137
+ // a refresh mid-turn restores the right level.
138
+ if (tierLevel !== undefined)
139
+ (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, { tier_level: tierLevel });
140
+ void manager.sendMessage(conversation.id, text, { tierLevel, model });
141
+ res.status(202).json({ accepted: true });
142
+ });
143
+ router.post('/conversations/:id/abort', (req, res) => {
144
+ const aborted = manager.abort(String(req.params.id));
145
+ res.json({ aborted });
146
+ });
147
+ return router;
148
+ }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.agentCwdPath = agentCwdPath;
7
+ exports.ensureAgentCwd = ensureAgentCwd;
8
+ exports.removeAgentCwd = removeAgentCwd;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const os_1 = __importDefault(require("os"));
12
+ // ─── App-level agent working directory (design D1) ────────────────────────────
13
+ //
14
+ // The agent chat spawns the AI CLI from an app-owned cwd (NOT a project path), so
15
+ // no project's CLAUDE.md is auto-loaded and the agent starts with a focused
16
+ // "operator" stance. Mirrors explore-cwd-manager but is app-global (one dir, no
17
+ // project symlink). The operator instructions are written for every provider
18
+ // (CLAUDE.md / AGENTS.md / GEMINI.md) so the chosen CLI picks them up.
19
+ function homeDir() {
20
+ return process.env.SPECRAILS_REGISTRY_HOME || os_1.default.homedir();
21
+ }
22
+ function agentCwdPath() {
23
+ return path_1.default.join(homeDir(), '.specrails', 'agent-cwd');
24
+ }
25
+ const INSTRUCTION_FILES = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
26
+ const OPERATOR_INSTRUCTIONS = `# Specrails Operator Agent
27
+
28
+ You are the in-app Specrails operator. You drive the Specrails Desktop app on the
29
+ user's behalf through the \`specrails_*\` MCP tools (projects, specs, rails, jobs,
30
+ chat, agents, plugins, jira, loops, code, analytics, setup, settings).
31
+
32
+ ## How to work
33
+ - Use \`specrails_select_project\` (or the projectId argument) to target a project.
34
+ If no project is selected ("Home") and the request is project-specific, ASK the
35
+ user whether to create a new project or search across all projects — do not guess.
36
+ - For long-running actions that return HTTP 202, use \`specrails_watch\` to follow
37
+ them to completion instead of assuming they finished.
38
+ - Prefer the meta tools \`specrails_guide\` / \`specrails_search\` / \`specrails_describe\`
39
+ to discover the exact action/arguments before calling a domain tool.
40
+
41
+ ## Permission ladder
42
+ Your actions are gated by a cumulative level the user sets live:
43
+ observe (read) ▸ edit (write) ▸ operate (launch AI, costs money) ▸ autonomous
44
+ (delete/kill). If a tool is refused for the current level, tell the user which
45
+ level it needs — do not try to work around the refusal.
46
+
47
+ ## Stance
48
+ Be concise and action-oriented. Confirm what you did with concrete references
49
+ (ids, names). Never fabricate results — report tool outputs faithfully, including
50
+ failures.
51
+
52
+ ## Formatting
53
+ Make replies easy to read: separate distinct ideas into short paragraphs with a
54
+ blank line between them (avoid one dense block of text), and use bullet lists for
55
+ enumerations. Give the conversation some breathing room.
56
+
57
+ ## Adding a project
58
+ When the user has no projects yet, or asks to add one:
59
+ 1. Explain the UI path: click "Add Project" (the + in the left sidebar), enter the
60
+ repo's folder path, pick an AI provider, then run setup.
61
+ 2. Then OFFER to do it for them from here: ask for the repo folder path and which
62
+ AI providers to set up, then with their go-ahead call
63
+ \`specrails_setup(add_project, path, providers)\` (Edit level) and a SINGLE
64
+ \`specrails_setup(install, projectId)\` (Operate level) — one install provisions
65
+ ALL the chosen providers in one shot (do not install them one at a time).
66
+ Use \`specrails_setup(prerequisites)\` / \`available_providers\` first to confirm
67
+ the machine is ready.
68
+
69
+ Setup is QUICK-only (fast, offline). Do NOT offer, mention, or attempt a "full" or
70
+ "enrich" install — that flow is deprecated and not available through you.
71
+ `;
72
+ /**
73
+ * Ensures the app-level agent cwd exists with operator instruction files.
74
+ * Idempotent; safe to call on every turn. Returns the cwd path.
75
+ */
76
+ function ensureAgentCwd() {
77
+ const dir = agentCwdPath();
78
+ fs_1.default.mkdirSync(dir, { recursive: true });
79
+ for (const name of INSTRUCTION_FILES) {
80
+ // App-owned files (not user-edited) — always (re)write so operator-prompt
81
+ // updates take effect instead of being pinned to the first materialization.
82
+ fs_1.default.writeFileSync(path_1.default.join(dir, name), OPERATOR_INSTRUCTIONS, 'utf-8');
83
+ }
84
+ return dir;
85
+ }
86
+ /** Removes the agent cwd (used on full teardown / reset). Best-effort. */
87
+ function removeAgentCwd() {
88
+ try {
89
+ fs_1.default.rmSync(agentCwdPath(), { recursive: true, force: true });
90
+ }
91
+ catch {
92
+ /* best-effort */
93
+ }
94
+ }
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveBridgeScript = resolveBridgeScript;
7
+ exports.resolveNodeCommand = resolveNodeCommand;
8
+ exports.buildSpecrailsMcpEntry = buildSpecrailsMcpEntry;
9
+ exports.mergeSpecrailsIntoWorkspaceMcp = mergeSpecrailsIntoWorkspaceMcp;
10
+ exports.buildAgentMcpArgs = buildAgentMcpArgs;
11
+ exports.prepareAgentMcp = prepareAgentMcp;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ const path_resolver_1 = require("./path-resolver");
16
+ const agent_tier_1 = require("./agent-tier");
17
+ // ─── Agent MCP wiring (design D8 / D1) ────────────────────────────────────────
18
+ //
19
+ // The in-app agent drives the app by talking to its OWN embedded MCP server. We
20
+ // spawn the AI CLI with `--mcp-config` pointing at a generated config whose sole
21
+ // server is the bundled `specrails-mcp` stdio bridge, which relays to
22
+ // `127.0.0.1:<port>/api/mcp` and reads the scoped token from disk (no token is
23
+ // ever written into the config file). The current Shift+Tab ladder level is
24
+ // forwarded via env → the bridge sets it as a loopback-only header so the tool
25
+ // guard enforces the agent ladder independently of the external Settings tiers.
26
+ function homeDir() {
27
+ return process.env.SPECRAILS_REGISTRY_HOME || os_1.default.homedir();
28
+ }
29
+ function fileExists(p) {
30
+ try {
31
+ return fs_1.default.existsSync(p);
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ /**
38
+ * Absolute path to the `specrails-mcp` bridge JS. Resolution order:
39
+ * 1. SPECRAILS_BUNDLED_MCP_BRIDGE_PATH (set by the Tauri host in the packaged app)
40
+ * 2. the staged copy under src-tauri/binaries/ (dev + bundled layouts)
41
+ * 3. the mcp-bridge build output (mcp-bridge/dist/)
42
+ * Climbs from this module so it works whether running from server/ or server/dist/.
43
+ */
44
+ function resolveBridgeScript() {
45
+ const fromEnv = process.env.SPECRAILS_BUNDLED_MCP_BRIDGE_PATH;
46
+ if (fromEnv && fileExists(fromEnv))
47
+ return fromEnv;
48
+ const roots = [
49
+ path_1.default.resolve(__dirname, '..'),
50
+ path_1.default.resolve(__dirname, '..', '..'),
51
+ process.cwd(),
52
+ ];
53
+ const rels = [
54
+ path_1.default.join('src-tauri', 'binaries', 'specrails-mcp.js'),
55
+ path_1.default.join('mcp-bridge', 'dist', 'specrails-mcp.js'),
56
+ ];
57
+ for (const root of roots) {
58
+ for (const rel of rels) {
59
+ const candidate = path_1.default.join(root, rel);
60
+ if (fileExists(candidate))
61
+ return candidate;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ /** The node executable that should run the bridge (bundled in the packaged app, else PATH `node`). */
67
+ function resolveNodeCommand() {
68
+ return (0, path_resolver_1.resolveBundledNodeExe)() ?? 'node';
69
+ }
70
+ /** Build the `mcpServers.specrails` entry the agent (or a project workspace) uses. */
71
+ function buildSpecrailsMcpEntry(opts) {
72
+ const bridge = resolveBridgeScript();
73
+ if (!bridge)
74
+ return null;
75
+ const env = {
76
+ SPECRAILS_MCP_PORT: String(opts.port),
77
+ };
78
+ // Preserve the test-home override so the bridge reads the right token file.
79
+ if (process.env.SPECRAILS_REGISTRY_HOME)
80
+ env.SPECRAILS_REGISTRY_HOME = process.env.SPECRAILS_REGISTRY_HOME;
81
+ if (opts.tierLevel != null)
82
+ env[agent_tier_1.AGENT_TIER_ENV] = (0, agent_tier_1.tierNameForLevel)(opts.tierLevel);
83
+ if (opts.activeProjectId)
84
+ env[agent_tier_1.AGENT_PROJECT_ENV] = opts.activeProjectId;
85
+ return { command: resolveNodeCommand(), args: [bridge], env };
86
+ }
87
+ /**
88
+ * Part A (design D8): surgically merge the `specrails` MCP server into a
89
+ * project's app-managed WORKSPACE `.mcp.json` so the project's own rails/explore/
90
+ * chat spawns can also call the Specrails MCP. NEVER touches the pristine repo.
91
+ * Additive + idempotent: preserves any existing (e.g. plugin) mcpServers entries,
92
+ * writes atomically (temp + rename), inlines NO token. Returns true when written.
93
+ */
94
+ function mergeSpecrailsIntoWorkspaceMcp(workspaceDir, port) {
95
+ const entry = buildSpecrailsMcpEntry({ port });
96
+ if (!entry)
97
+ return false;
98
+ const file = path_1.default.join(workspaceDir, '.mcp.json');
99
+ let current = {};
100
+ try {
101
+ if (fs_1.default.existsSync(file)) {
102
+ const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
103
+ if (parsed && typeof parsed === 'object')
104
+ current = parsed;
105
+ }
106
+ }
107
+ catch {
108
+ // Corrupt file → start fresh rather than crash assembly.
109
+ current = {};
110
+ }
111
+ const mcpServers = { ...(current.mcpServers ?? {}), specrails: entry };
112
+ const next = { ...current, mcpServers };
113
+ const tmp = `${file}.tmp-${process.pid}`;
114
+ fs_1.default.mkdirSync(workspaceDir, { recursive: true });
115
+ fs_1.default.writeFileSync(tmp, JSON.stringify(next, null, 2));
116
+ fs_1.default.renameSync(tmp, file);
117
+ return true;
118
+ }
119
+ /**
120
+ * Writes a per-conversation `--mcp-config` file for the agent and returns the
121
+ * `['--mcp-config', <file>]` argv to inject into SpawnOptions.extraArgs. Uses the
122
+ * bundled `specrails-mcp` stdio bridge (shipped inside the app — the user installs
123
+ * nothing; 100% unattended) which relays to this process's `/api/mcp`. Returns
124
+ * `[]` when the bridge can't be located (agent then runs tool-less / degraded).
125
+ */
126
+ function buildAgentMcpArgs(opts) {
127
+ const entry = buildSpecrailsMcpEntry({ port: opts.port, tierLevel: opts.tierLevel, activeProjectId: opts.activeProjectId });
128
+ if (!entry)
129
+ return [];
130
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(opts.conversationId)) {
131
+ throw new Error(`unsafe agent conversation id: ${opts.conversationId}`);
132
+ }
133
+ const dir = path_1.default.join(homeDir(), '.specrails', 'agent', opts.conversationId);
134
+ fs_1.default.mkdirSync(dir, { recursive: true });
135
+ const file = path_1.default.join(dir, 'mcp.json');
136
+ fs_1.default.writeFileSync(file, JSON.stringify({ mcpServers: { specrails: entry } }, null, 2), { mode: 0o600 });
137
+ try {
138
+ fs_1.default.chmodSync(file, 0o600);
139
+ }
140
+ catch {
141
+ /* best-effort on platforms without chmod */
142
+ }
143
+ return ['--mcp-config', file];
144
+ }
145
+ function agentDir(conversationId) {
146
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(conversationId)) {
147
+ throw new Error(`unsafe agent conversation id: ${conversationId}`);
148
+ }
149
+ const dir = path_1.default.join(homeDir(), '.specrails', 'agent', conversationId);
150
+ fs_1.default.mkdirSync(dir, { recursive: true });
151
+ return dir;
152
+ }
153
+ /**
154
+ * Codex registers the specrails MCP via inline `-c mcp_servers.specrails.*`
155
+ * config overrides. This keeps codex on the USER's real CODEX_HOME (auth + model
156
+ * settings intact — no all-or-nothing 401 from a CODEX_HOME override) and cannot
157
+ * break codex startup the way an unknown `--mcp-config` flag does: `-c` is a known
158
+ * flag and unknown config keys are simply set. TOML scalar values reuse JSON
159
+ * escaping (compatible for our command/path/env inputs).
160
+ */
161
+ function codexMcpOverrides(entry) {
162
+ const c = (kv) => ['-c', kv];
163
+ const args = [
164
+ ...c(`mcp_servers.specrails.command=${JSON.stringify(entry.command)}`),
165
+ ...c(`mcp_servers.specrails.args=[${entry.args.map((a) => JSON.stringify(a)).join(', ')}]`),
166
+ ];
167
+ for (const [k, v] of Object.entries(entry.env)) {
168
+ args.push(...c(`mcp_servers.specrails.env.${k}=${JSON.stringify(v)}`));
169
+ }
170
+ return args;
171
+ }
172
+ /**
173
+ * Prepares the specrails MCP for the given provider and returns the spawn
174
+ * extraArgs + env to merge. Returns empty wiring when the bridge is unavailable
175
+ * (agent then runs tool-less / degraded).
176
+ */
177
+ function prepareAgentMcp(opts) {
178
+ const entry = buildSpecrailsMcpEntry({
179
+ port: opts.port,
180
+ tierLevel: opts.tierLevel,
181
+ activeProjectId: opts.activeProjectId,
182
+ });
183
+ if (!entry)
184
+ return { extraArgs: [], env: {} };
185
+ if (opts.adapterId === 'codex') {
186
+ // Inline -c overrides — no CODEX_HOME override, no file mutation; auth intact.
187
+ return { extraArgs: codexMcpOverrides(entry), env: {} };
188
+ }
189
+ if (opts.adapterId === 'claude') {
190
+ return { extraArgs: buildAgentMcpArgs(opts), env: {} };
191
+ }
192
+ // gemini + any other project-json provider: write .mcp.json in the spawn cwd.
193
+ const file = path_1.default.join(opts.cwd, '.mcp.json');
194
+ let current = {};
195
+ try {
196
+ if (fs_1.default.existsSync(file)) {
197
+ const parsed = JSON.parse(fs_1.default.readFileSync(file, 'utf-8'));
198
+ if (parsed && typeof parsed === 'object')
199
+ current = parsed;
200
+ }
201
+ }
202
+ catch {
203
+ current = {};
204
+ }
205
+ const next = { ...current, mcpServers: { ...(current.mcpServers ?? {}), specrails: entry } };
206
+ fs_1.default.writeFileSync(file, JSON.stringify(next, null, 2), { mode: 0o600 });
207
+ return { extraArgs: [], env: {} };
208
+ }