specrails-desktop 2.18.2 → 2.19.1

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 (346) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{ActivityFeedPage-mGnv5SOe.js → ActivityFeedPage-C0_k6n5T.js} +1 -1
  3. package/client/dist/assets/AgentBrowserCapture-NHUCEnTh.js +1 -0
  4. package/client/dist/assets/AgentModeCodePane-jddXrhPY.js +2 -0
  5. package/client/dist/assets/AgentsPage-BPjf6bId.js +86 -0
  6. package/client/dist/assets/AnalyticsPage-BaJjj5mO.js +1 -0
  7. package/client/dist/assets/{BarChart-BqHUOnl9.js → BarChart-HPiao--v.js} +1 -1
  8. package/client/dist/assets/CodePage-CAD4KfkC.js +2 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-CR-m97Ui.js → DesktopAnalyticsPage-B2RBxIvj.js} +1 -1
  10. package/client/dist/assets/DocsDialog-BJDvUEGj.js +11 -0
  11. package/client/dist/assets/DocsPage-B6H-DBs8.js +11 -0
  12. package/client/dist/assets/{ExportDropdown-C-o1Q4ef.js → ExportDropdown-CYMcbLTD.js} +1 -1
  13. package/client/dist/assets/IntegrationsPage-Dx_UpQVk.js +3 -0
  14. package/client/dist/assets/JobDetailPage-BlTnsRAN.js +16 -0
  15. package/client/dist/assets/JobsPage-EB0PbXOk.js +1 -0
  16. package/client/dist/assets/LoopBuilderPage-DfWfhhJY.js +7 -0
  17. package/client/dist/assets/LoopsPage-DCVATTv5.js +1 -0
  18. package/client/dist/assets/{addspec-B9dY_F5T.js → addspec-BXV6sx5l.js} +1 -1
  19. package/client/dist/assets/{addspec-tbxr3sHa.js → addspec-CS895X6A.js} +1 -1
  20. package/client/dist/assets/{addspec-W0r16BOC2.js → addspec-CXfEOrD42.js} +1 -1
  21. package/client/dist/assets/{addspec-D43TlMVp2.js → addspec-D28kw9ow2.js} +1 -1
  22. package/client/dist/assets/{addspec-B9nhMikb.js → addspec-D39EEg02.js} +1 -1
  23. package/client/dist/assets/{addspec-DfgIZ5lO.js → addspec-DC1tgsFG.js} +1 -1
  24. package/client/dist/assets/{addspec-BtNaJHu2.js → addspec-DcHALPEp.js} +1 -1
  25. package/client/dist/assets/{addspec-BloGn_ey.js → addspec-DxW6oPsk.js} +1 -1
  26. package/client/dist/assets/{agent-Bm2tPrdr.js → agent-B5Ycau5M.js} +1 -1
  27. package/client/dist/assets/{agent-DMUrz9ZS2.js → agent-BEz08l4_2.js} +1 -1
  28. package/client/dist/assets/{agent-C4bO1l_v.js → agent-BIX51CEA.js} +1 -1
  29. package/client/dist/assets/{agent-9WdSxPU5.js → agent-Bc9Kwf65.js} +1 -1
  30. package/client/dist/assets/{agent-pO8x4jbR.js → agent-BiM7puyn.js} +1 -1
  31. package/client/dist/assets/{agent-CgKsh88Z2.js → agent-C0kYSv3m2.js} +1 -1
  32. package/client/dist/assets/agent-CCzWlfqs.js +1 -0
  33. package/client/dist/assets/{agent-ASIyVcg0.js → agent-DiCSo31C.js} +1 -1
  34. package/client/dist/assets/{agents-DfGvYtpq2.js → agents-BFrHy5d-2.js} +1 -1
  35. package/client/dist/assets/{agents-Dli56gr82.js → agents-BFy2Kq_k2.js} +1 -1
  36. package/client/dist/assets/{agents-C07al8rI.js → agents-BXDCFJQa.js} +1 -1
  37. package/client/dist/assets/{agents-BAxAwHy-.js → agents-BySH0Gvw.js} +1 -1
  38. package/client/dist/assets/{agents-Eqw_AQ6H.js → agents-CTdwacTO.js} +1 -1
  39. package/client/dist/assets/{agents-BjT_xLjv.js → agents-Cev2oFfX.js} +1 -1
  40. package/client/dist/assets/{agents-BUYBb825.js → agents-CsDEv4Fj.js} +1 -1
  41. package/client/dist/assets/{agents-52t8NVpS.js → agents-z0kA-y0Q.js} +1 -1
  42. package/client/dist/assets/dashboard-Bl2_Yjs6.js +1 -0
  43. package/client/dist/assets/dashboard-C_vS9RaM2.js +1 -0
  44. package/client/dist/assets/dashboard-CcEpALV_.js +1 -0
  45. package/client/dist/assets/dashboard-CfltY66K.js +1 -0
  46. package/client/dist/assets/dashboard-CoED_41r.js +1 -0
  47. package/client/dist/assets/dashboard-DLeC5ziO.js +1 -0
  48. package/client/dist/assets/dashboard-DxEPkALo2.js +1 -0
  49. package/client/dist/assets/dashboard-MgDVmaYS2.js +1 -0
  50. package/client/dist/assets/{dist-js-CUegfF6k.js → dist-js-ClEfL6Bo.js} +1 -1
  51. package/client/dist/assets/{dist-js-BHhtSerX.js → dist-js-b2_z7zAi.js} +1 -1
  52. package/client/dist/assets/index-9yNUBwEi.css +2 -0
  53. package/client/dist/assets/{index-B7vB20Jw.js → index-BeujshsU.js} +57 -57
  54. package/client/dist/assets/{lib-PQoHlR5u.js → lib-CfphudAm.js} +1 -1
  55. package/client/dist/assets/loops-BEUiCzEI.js +1 -0
  56. package/client/dist/assets/loops-BlrQiK122.js +1 -0
  57. package/client/dist/assets/loops-Bs0ectZ32.js +1 -0
  58. package/client/dist/assets/loops-CLZV8HHY2.js +1 -0
  59. package/client/dist/assets/loops-CQuuMlyZ.js +1 -0
  60. package/client/dist/assets/loops-DuuWQWTI.js +1 -0
  61. package/client/dist/assets/loops-KOTryzk5.js +1 -0
  62. package/client/dist/assets/loops-t1mUZtf0.js +1 -0
  63. package/client/dist/assets/nav--1ErXZrS.js +1 -0
  64. package/client/dist/assets/nav-18nqSJ8h.js +1 -0
  65. package/client/dist/assets/nav-BuonHb17.js +1 -0
  66. package/client/dist/assets/nav-C-sCoOLY2.js +1 -0
  67. package/client/dist/assets/nav-ClXz_3Ic2.js +1 -0
  68. package/client/dist/assets/nav-CtqgSQeE2.js +1 -0
  69. package/client/dist/assets/nav-S3YvCaVv.js +1 -0
  70. package/client/dist/assets/nav-czg8NkN-.js +1 -0
  71. package/client/dist/assets/settings-BOZBX-Vq.js +1 -0
  72. package/client/dist/assets/settings-CX29QcQW.js +1 -0
  73. package/client/dist/assets/settings-CfOWHPzc2.js +1 -0
  74. package/client/dist/assets/settings-DaXhxNug2.js +1 -0
  75. package/client/dist/assets/settings-DmyDpuzu.js +1 -0
  76. package/client/dist/assets/settings-DwPRLcxr2.js +1 -0
  77. package/client/dist/assets/settings-MjYVJvDE.js +1 -0
  78. package/client/dist/assets/settings-zJFdTzKE.js +1 -0
  79. package/client/dist/assets/{setup-BYoDYoPY2.js → setup-BmXfxlzA2.js} +1 -1
  80. package/client/dist/assets/{setup-faMApo0U2.js → setup-BxVydxbl2.js} +1 -1
  81. package/client/dist/assets/{setup-C5UbM0RP.js → setup-DIRZ1Lf-.js} +1 -1
  82. package/client/dist/assets/{setup-Bf9eLKxU.js → setup-DwbKSnKQ.js} +1 -1
  83. package/client/dist/assets/{setup-JFkWWZnR.js → setup-Dx13pESZ.js} +1 -1
  84. package/client/dist/assets/{setup-CqGrWb6G.js → setup-G4ZvDKxX.js} +1 -1
  85. package/client/dist/assets/{setup-tloaTobG2.js → setup-SLAi4xxQ2.js} +1 -1
  86. package/client/dist/assets/{setup-4us-Bz3H.js → setup-Z01j3st4.js} +1 -1
  87. package/client/dist/assets/{upload-PnJDPHZH.js → upload-Ox9jUq1X.js} +1 -1
  88. package/client/dist/assets/{value-BTDefcOV.js → value-BeQVX5gN.js} +1 -1
  89. package/client/dist/index.html +10 -10
  90. package/docs/codex.md +5 -5
  91. package/docs/customizing.md +4 -4
  92. package/docs/gemini.md +2 -2
  93. package/docs/getting-started.md +1 -1
  94. package/docs/guide/de/integrations/1-ai-providers.md +2 -2
  95. package/docs/guide/de/integrations/5-mcp-server.md +20 -1
  96. package/docs/guide/de/pipeline/1-rails-and-jobs.md +4 -4
  97. package/docs/guide/de/pipeline/2-the-job-detail-view.md +1 -1
  98. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +2 -2
  99. package/docs/guide/de/pipeline/5-the-loop-builder.md +2 -2
  100. package/docs/guide/en/integrations/1-ai-providers.md +2 -2
  101. package/docs/guide/en/integrations/5-mcp-server.md +20 -1
  102. package/docs/guide/en/pipeline/1-rails-and-jobs.md +4 -4
  103. package/docs/guide/en/pipeline/2-the-job-detail-view.md +1 -1
  104. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +2 -2
  105. package/docs/guide/en/pipeline/5-the-loop-builder.md +2 -2
  106. package/docs/guide/es/integrations/1-ai-providers.md +2 -2
  107. package/docs/guide/es/integrations/5-mcp-server.md +20 -1
  108. package/docs/guide/es/pipeline/1-rails-and-jobs.md +4 -4
  109. package/docs/guide/es/pipeline/2-the-job-detail-view.md +1 -1
  110. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +2 -2
  111. package/docs/guide/es/pipeline/5-the-loop-builder.md +2 -2
  112. package/docs/guide/fr/integrations/1-ai-providers.md +2 -2
  113. package/docs/guide/fr/integrations/5-mcp-server.md +20 -1
  114. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +4 -4
  115. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +1 -1
  116. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +2 -2
  117. package/docs/guide/fr/pipeline/5-the-loop-builder.md +2 -2
  118. package/docs/guide/it/integrations/1-ai-providers.md +2 -2
  119. package/docs/guide/it/integrations/5-mcp-server.md +20 -1
  120. package/docs/guide/it/pipeline/1-rails-and-jobs.md +4 -4
  121. package/docs/guide/it/pipeline/2-the-job-detail-view.md +1 -1
  122. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +2 -2
  123. package/docs/guide/it/pipeline/5-the-loop-builder.md +2 -2
  124. package/docs/guide/ja/integrations/1-ai-providers.md +2 -2
  125. package/docs/guide/ja/integrations/5-mcp-server.md +20 -1
  126. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +4 -4
  127. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +1 -1
  128. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +2 -2
  129. package/docs/guide/ja/pipeline/5-the-loop-builder.md +2 -2
  130. package/docs/guide/pt/integrations/1-ai-providers.md +2 -2
  131. package/docs/guide/pt/integrations/5-mcp-server.md +20 -1
  132. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +4 -4
  133. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +1 -1
  134. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +2 -2
  135. package/docs/guide/pt/pipeline/5-the-loop-builder.md +2 -2
  136. package/docs/guide/zh/integrations/1-ai-providers.md +2 -2
  137. package/docs/guide/zh/integrations/5-mcp-server.md +20 -1
  138. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +4 -4
  139. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +1 -1
  140. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +2 -2
  141. package/docs/guide/zh/pipeline/5-the-loop-builder.md +2 -2
  142. package/docs/internals/switch-to-agent-mode-design.md +301 -0
  143. package/docs/internals/visual-design-audit.md +184 -0
  144. package/docs/mcp.md +26 -1
  145. package/docs/running-pipelines.md +10 -10
  146. package/docs/tracking-cost.md +1 -1
  147. package/package.json +1 -1
  148. package/server/dist/agent-chat-manager.js +107 -33
  149. package/server/dist/agent-chat-router.js +170 -8
  150. package/server/dist/agent-cwd-manager.js +2 -47
  151. package/server/dist/agent-operator-prompt.js +277 -0
  152. package/server/dist/agent-store.js +27 -5
  153. package/server/dist/attachment-manager.js +138 -0
  154. package/server/dist/desktop-db.js +15 -0
  155. package/server/dist/loop-factory.js +12 -2
  156. package/server/dist/mcp/guide.js +186 -51
  157. package/server/dist/mcp/tools/analytics.js +1 -1
  158. package/server/dist/mcp/tools/app-settings.js +45 -5
  159. package/server/dist/mcp/tools/chat.js +9 -2
  160. package/server/dist/mcp/tools/code.js +3 -2
  161. package/server/dist/mcp/tools/jobs.js +18 -6
  162. package/server/dist/mcp/tools/loops.js +57 -5
  163. package/server/dist/mcp/tools/meta.js +43 -3
  164. package/server/dist/mcp/tools/rails.js +6 -6
  165. package/server/dist/mcp/tools/setup.js +2 -1
  166. package/server/dist/mcp/tools/specs.js +60 -38
  167. package/server/dist/mcp/tools/types.js +17 -8
  168. package/server/dist/mcp/tools/watch.js +71 -8
  169. package/server/dist/project-router-helpers.js +1 -1
  170. package/server/dist/project-router-tickets.js +43 -4
  171. package/server/dist/providers/claude-adapter.js +6 -0
  172. package/server/dist/providers/codex-adapter.js +10 -0
  173. package/server/dist/providers/gemini-adapter.js +3 -0
  174. package/server/dist/rails-router.js +8 -17
  175. package/server/dist/spec-models.js +1 -0
  176. package/client/dist/assets/AgentsPage-BH1isg5j.js +0 -86
  177. package/client/dist/assets/AnalyticsPage-ByyshYRI.js +0 -1
  178. package/client/dist/assets/CodePage-CU7T_HYs.js +0 -2
  179. package/client/dist/assets/DocsDialog-CEmZp_8V.js +0 -11
  180. package/client/dist/assets/DocsPage-DYbAQ1AT.js +0 -11
  181. package/client/dist/assets/IntegrationsPage-C6fKdwVH.js +0 -3
  182. package/client/dist/assets/JobDetailPage-CKfIfBgE.js +0 -16
  183. package/client/dist/assets/JobsPage-nBng0riZ.js +0 -1
  184. package/client/dist/assets/LoopBuilderPage-Co_TYqYk.js +0 -7
  185. package/client/dist/assets/LoopsPage-a2hd37Ya.js +0 -1
  186. package/client/dist/assets/agent-Dwkz5mPS.js +0 -1
  187. package/client/dist/assets/dashboard-BTiH361F.js +0 -1
  188. package/client/dist/assets/dashboard-COSm_14E.js +0 -1
  189. package/client/dist/assets/dashboard-CfOs70FN2.js +0 -1
  190. package/client/dist/assets/dashboard-Cyzp4ox9.js +0 -1
  191. package/client/dist/assets/dashboard-CzHDp14_2.js +0 -1
  192. package/client/dist/assets/dashboard-D6Woy2B5.js +0 -1
  193. package/client/dist/assets/dashboard-DwbH4oVH.js +0 -1
  194. package/client/dist/assets/dashboard-cWhU3krA2.js +0 -1
  195. package/client/dist/assets/index-B0FxQALR.css +0 -2
  196. package/client/dist/assets/loops-BDhEfnx82.js +0 -1
  197. package/client/dist/assets/loops-BM5QggwH.js +0 -1
  198. package/client/dist/assets/loops-CPjLAAxj.js +0 -1
  199. package/client/dist/assets/loops-D8Iu6Jss.js +0 -1
  200. package/client/dist/assets/loops-DKnk79TB2.js +0 -1
  201. package/client/dist/assets/loops-DLW0aFMm.js +0 -1
  202. package/client/dist/assets/loops-Dcv3dFy_2.js +0 -1
  203. package/client/dist/assets/loops-y7wFRx6M.js +0 -1
  204. package/client/dist/assets/nav-1hj2izqB2.js +0 -1
  205. package/client/dist/assets/nav-BBUKMAu-2.js +0 -1
  206. package/client/dist/assets/nav-CLqkP-26.js +0 -1
  207. package/client/dist/assets/nav-Cv46L837.js +0 -1
  208. package/client/dist/assets/nav-DE3yWQw6.js +0 -1
  209. package/client/dist/assets/nav-DgA-rjLk2.js +0 -1
  210. package/client/dist/assets/nav-EG2pr6vN.js +0 -1
  211. package/client/dist/assets/nav-r3mIJVfU.js +0 -1
  212. package/client/dist/assets/settings-BKmORxlB2.js +0 -1
  213. package/client/dist/assets/settings-BVv2JgQ8.js +0 -1
  214. package/client/dist/assets/settings-Bghm3pAV.js +0 -1
  215. package/client/dist/assets/settings-BuC_aplX.js +0 -1
  216. package/client/dist/assets/settings-Bw_Qk8E32.js +0 -1
  217. package/client/dist/assets/settings-CAKgJv7p.js +0 -1
  218. package/client/dist/assets/settings-CEUgk1Ck.js +0 -1
  219. package/client/dist/assets/settings-C_PDE20s2.js +0 -1
  220. /package/client/dist/assets/{activity-Uj576ckT.js → activity-BlvN5rhP.js} +0 -0
  221. /package/client/dist/assets/{activity-w1qMFwxh.js → activity-C5SeKbom.js} +0 -0
  222. /package/client/dist/assets/{activity-BySXFT2J2.js → activity-DIJexk6v2.js} +0 -0
  223. /package/client/dist/assets/{activity-B82z3Pna.js → activity-DLdwUYv7.js} +0 -0
  224. /package/client/dist/assets/{activity-BVpZrs0J.js → activity-DOKV_Wyf.js} +0 -0
  225. /package/client/dist/assets/{activity-BId0pwuS2.js → activity-DVuiy8TI2.js} +0 -0
  226. /package/client/dist/assets/{activity-DIWnV0Pl.js → activity-Dsf3J7iW.js} +0 -0
  227. /package/client/dist/assets/{agentstudio-C7xxqVWn.js → agentstudio-B8H2H4Db2.js} +0 -0
  228. /package/client/dist/assets/{agentstudio-D-UoNAIA.js → agentstudio-B9zthaTJ.js} +0 -0
  229. /package/client/dist/assets/{agentstudio-0o3XKrcy.js → agentstudio-BDMpU01s.js} +0 -0
  230. /package/client/dist/assets/{agentstudio-Bu8m5jDw.js → agentstudio-CSZo7p8K.js} +0 -0
  231. /package/client/dist/assets/{agentstudio-ObTpVbBP.js → agentstudio-CdD6J23W.js} +0 -0
  232. /package/client/dist/assets/{agentstudio-PzFB3vfh2.js → agentstudio-D80fZu_M2.js} +0 -0
  233. /package/client/dist/assets/{agentstudio-OJjjz6Uf2.js → agentstudio-EuKglkaF2.js} +0 -0
  234. /package/client/dist/assets/{aiedit-DRrNiaB4.js → aiedit-B_VOTUo42.js} +0 -0
  235. /package/client/dist/assets/{aiedit-sP_iao2j.js → aiedit-Cxoe_0wv.js} +0 -0
  236. /package/client/dist/assets/{aiedit-DAM_WRAl2.js → aiedit-D59sPapq2.js} +0 -0
  237. /package/client/dist/assets/{aiedit-BaLZalLa.js → aiedit-DX3BBpXF.js} +0 -0
  238. /package/client/dist/assets/{aiedit-BIQqv2XM2.js → aiedit-GYOYWCHJ2.js} +0 -0
  239. /package/client/dist/assets/{aiedit-BjlUzasF.js → aiedit-TON-4N9S.js} +0 -0
  240. /package/client/dist/assets/{aiedit-BmDufyGU.js → aiedit-ybQmdsST.js} +0 -0
  241. /package/client/dist/assets/{analytics-MVJRjcY52.js → analytics-B2jjXLCP2.js} +0 -0
  242. /package/client/dist/assets/{analytics-C4z_NgJZ.js → analytics-BTkXLTfa.js} +0 -0
  243. /package/client/dist/assets/{analytics-DL2vJCbq.js → analytics-BotqRU_R.js} +0 -0
  244. /package/client/dist/assets/{analytics-e1mkNTgy2.js → analytics-Ck3vl2Qo2.js} +0 -0
  245. /package/client/dist/assets/{analytics-Ds_PNxtO2.js → analytics-CqMJQb7N.js} +0 -0
  246. /package/client/dist/assets/{analytics-B_rxNC9X.js → analytics-DGOoMkaZ.js} +0 -0
  247. /package/client/dist/assets/{analytics-BDqqCzN9.js → analytics-DL7uwNAI2.js} +0 -0
  248. /package/client/dist/assets/{attachments-BP0Ac1yt.js → attachments-7w-BR2V2.js} +0 -0
  249. /package/client/dist/assets/{attachments-CPBVeHX72.js → attachments-B5QX1Xfa2.js} +0 -0
  250. /package/client/dist/assets/{attachments-Db7JfA8a.js → attachments-BcE7kZG-2.js} +0 -0
  251. /package/client/dist/assets/{attachments-CJaO1vCH.js → attachments-BsJJXFzT.js} +0 -0
  252. /package/client/dist/assets/{attachments-8QCKHCEF2.js → attachments-Cbh4AwjP.js} +0 -0
  253. /package/client/dist/assets/{attachments-CV2uaTGE.js → attachments-D0fGlFve.js} +0 -0
  254. /package/client/dist/assets/{attachments-CaOjdN332.js → attachments-D3DJpd3e2.js} +0 -0
  255. /package/client/dist/assets/{browser-C9rUUxo4.js → browser-9GR11iTi.js} +0 -0
  256. /package/client/dist/assets/{browser-CEqx11iy.js → browser-BOxg0p3n.js} +0 -0
  257. /package/client/dist/assets/{browser-543jbxE4.js → browser-BSh8xUWV.js} +0 -0
  258. /package/client/dist/assets/{browser-CLC0etB_2.js → browser-CaV3BJNY2.js} +0 -0
  259. /package/client/dist/assets/{browser-0xeW8fUa2.js → browser-CmWGMRGf2.js} +0 -0
  260. /package/client/dist/assets/{browser-ExmELH702.js → browser-D6wbpBOL2.js} +0 -0
  261. /package/client/dist/assets/{browser-DyG2l9Wa.js → browser-dsgDU1HB.js} +0 -0
  262. /package/client/dist/assets/{chat-DagUyoVA.js → chat-B90iLDI4.js} +0 -0
  263. /package/client/dist/assets/{chat-Cu6F3d6g2.js → chat-CAo0jCnj2.js} +0 -0
  264. /package/client/dist/assets/{chat-CN8BMAMc.js → chat-CW54IP3u.js} +0 -0
  265. /package/client/dist/assets/{chat-NQPCBQo72.js → chat-CYHUUW0p2.js} +0 -0
  266. /package/client/dist/assets/{chat-CCytD3P8.js → chat-D1PXX_p2.js} +0 -0
  267. /package/client/dist/assets/{chat-IjutwlBg.js → chat-DhpcWjOf.js} +0 -0
  268. /package/client/dist/assets/{chat-vVpwkOp52.js → chat-cFG-UfRA2.js} +0 -0
  269. /package/client/dist/assets/{code-Dhb6jbAU.js → code--qUk_yn-.js} +0 -0
  270. /package/client/dist/assets/{code-Ce-nBIwj.js → code-B1aOMCVV.js} +0 -0
  271. /package/client/dist/assets/{code-BaG05zK62.js → code-B5UrmjaE2.js} +0 -0
  272. /package/client/dist/assets/{code-Dap54vOb.js → code-CjBHU0nw.js} +0 -0
  273. /package/client/dist/assets/{code-_NOCo-Ol2.js → code-Cjv4_HkK2.js} +0 -0
  274. /package/client/dist/assets/{code-32l6Naxu2.js → code-D7GLAjrz2.js} +0 -0
  275. /package/client/dist/assets/{code-C5cibF2F.js → code-DZKjfzBN.js} +0 -0
  276. /package/client/dist/assets/{commands-Cc2WpuXa.js → commands-B9XaopQC.js} +0 -0
  277. /package/client/dist/assets/{commands-k9wbNYoi.js → commands-BCx1uDIE.js} +0 -0
  278. /package/client/dist/assets/{commands-CM99Upsz2.js → commands-Bhw9Hqsa2.js} +0 -0
  279. /package/client/dist/assets/{commands-Bc8y9eNo2.js → commands-D0RnooQF2.js} +0 -0
  280. /package/client/dist/assets/{commands-Bl3-KVfj.js → commands-DuaQT-it.js} +0 -0
  281. /package/client/dist/assets/{commands-DASXJD-l.js → commands-MqNuGGO4.js} +0 -0
  282. /package/client/dist/assets/{commands-C5WQ9blx2.js → commands-oB9q2vy_2.js} +0 -0
  283. /package/client/dist/assets/{common-CHARW6MZ2.js → common-BXzqeuiC2.js} +0 -0
  284. /package/client/dist/assets/{common-0aY9HQBP.js → common-BaJ6MF7T.js} +0 -0
  285. /package/client/dist/assets/{common-CQCJzAQq2.js → common-C-jaOdxz2.js} +0 -0
  286. /package/client/dist/assets/{common-DNhhqjlJ.js → common-CKjy-R8-.js} +0 -0
  287. /package/client/dist/assets/{common-YdyZtmaI2.js → common-CRd-Ge5u2.js} +0 -0
  288. /package/client/dist/assets/{common-5jScBJqt.js → common-DPGR3Tfo.js} +0 -0
  289. /package/client/dist/assets/{common-BdaRciTu.js → common-wEB9RBdf.js} +0 -0
  290. /package/client/dist/assets/{explore-ChtxIiSM.js → explore-B_79NLR5.js} +0 -0
  291. /package/client/dist/assets/{explore-BzdMEpuf.js → explore-D-htKul_.js} +0 -0
  292. /package/client/dist/assets/{explore-B82VMUJ_.js → explore-D5b2jgvR.js} +0 -0
  293. /package/client/dist/assets/{explore-DfcUfiPw2.js → explore-Dvp-5qOw2.js} +0 -0
  294. /package/client/dist/assets/{explore-CbW0381i2.js → explore-FaKfWCr72.js} +0 -0
  295. /package/client/dist/assets/{explore-BGz1rvQE.js → explore-IS8VyaSZ.js} +0 -0
  296. /package/client/dist/assets/{explore-yRF7wFSp2.js → explore-OZhWV0Eb2.js} +0 -0
  297. /package/client/dist/assets/{format-command-D96da4fZ.js → format-command-CHImQv6W.js} +0 -0
  298. /package/client/dist/assets/{integrations-CnrX7vqw2.js → integrations-B97cGWfo2.js} +0 -0
  299. /package/client/dist/assets/{integrations-BJvrlcc7.js → integrations-Bp3leAh3.js} +0 -0
  300. /package/client/dist/assets/{integrations-CPKYkG36.js → integrations-C3Ma3GdM.js} +0 -0
  301. /package/client/dist/assets/{integrations-BA5pM05t.js → integrations-C5MUA-0-.js} +0 -0
  302. /package/client/dist/assets/{integrations-B0OBPU9_.js → integrations-CiQbCn_W.js} +0 -0
  303. /package/client/dist/assets/{integrations-DeJFRyXz2.js → integrations-Du_0rDfi2.js} +0 -0
  304. /package/client/dist/assets/{integrations-DBou159M2.js → integrations-t4X5UVcx2.js} +0 -0
  305. /package/client/dist/assets/{jira-mi37H4Kt.js → jira-B1XaR6fj.js} +0 -0
  306. /package/client/dist/assets/{jira-Srx2P5zS.js → jira-BCHx9zUF.js} +0 -0
  307. /package/client/dist/assets/{jira-DeotPx6P2.js → jira-BqXyykbt2.js} +0 -0
  308. /package/client/dist/assets/{jira-BwLBgmfI2.js → jira-BzFvFn7t2.js} +0 -0
  309. /package/client/dist/assets/{jira-BDgxAFTF2.js → jira-D6IDTIsc2.js} +0 -0
  310. /package/client/dist/assets/{jira-NwlUCcQD.js → jira-DSZ5xFRl.js} +0 -0
  311. /package/client/dist/assets/{jira-D3GGzXOI.js → jira-DdEKW9Pu.js} +0 -0
  312. /package/client/dist/assets/{jobs-D8adm2BX.js → jobs-BX9dP3Qn.js} +0 -0
  313. /package/client/dist/assets/{jobs-B56vQEkh2.js → jobs-BgHEgCWk2.js} +0 -0
  314. /package/client/dist/assets/{jobs-K_Y8KAZ2.js → jobs-CU-khJTt.js} +0 -0
  315. /package/client/dist/assets/{jobs-BYIk6Tg_.js → jobs-Ce0SE81t.js} +0 -0
  316. /package/client/dist/assets/{jobs-C-lM-iax2.js → jobs-CeOcGHd_2.js} +0 -0
  317. /package/client/dist/assets/{jobs-CYdevGgT.js → jobs-Ct5v2p6T.js} +0 -0
  318. /package/client/dist/assets/{jobs-OwM0ZWhI2.js → jobs-m7L0K9uC2.js} +0 -0
  319. /package/client/dist/assets/{mcp-D3E4_Mmk2.js → mcp-4QbfLPa4.js} +0 -0
  320. /package/client/dist/assets/{mcp-B6nwJJmR.js → mcp-CXy61khH.js} +0 -0
  321. /package/client/dist/assets/{mcp-CNgFiTEh.js → mcp-CmOBPDjE.js} +0 -0
  322. /package/client/dist/assets/{mcp-mupPuPDO2.js → mcp-NlLc9T8u2.js} +0 -0
  323. /package/client/dist/assets/{mcp-B4irfLJ2.js → mcp-_DUUXTHB2.js} +0 -0
  324. /package/client/dist/assets/{mcp-BP7PpbDr2.js → mcp-dlOyUQQy2.js} +0 -0
  325. /package/client/dist/assets/{mcp-BOoEGTSQ.js → mcp-zqSVP7nA.js} +0 -0
  326. /package/client/dist/assets/{specs-D4z5d8uq.js → specs-57pue7UQ.js} +0 -0
  327. /package/client/dist/assets/{specs-CWNKAScX2.js → specs-BWQ-0EXt2.js} +0 -0
  328. /package/client/dist/assets/{specs-B8mwScoM2.js → specs-BYSOlQRw2.js} +0 -0
  329. /package/client/dist/assets/{specs-B5XOoH-02.js → specs-CkP0B28v2.js} +0 -0
  330. /package/client/dist/assets/{specs-DgvIRZtM.js → specs-RSYuLgF4.js} +0 -0
  331. /package/client/dist/assets/{specs-DuRzE5wE.js → specs-d-zU7_3Q.js} +0 -0
  332. /package/client/dist/assets/{specs-Bj_3fDrQ.js → specs-nM5Cpcqe.js} +0 -0
  333. /package/client/dist/assets/{terminal-DoMTHedv2.js → terminal-Adi3W-GZ2.js} +0 -0
  334. /package/client/dist/assets/{terminal-DOwfkxqa2.js → terminal-BA6FCZna2.js} +0 -0
  335. /package/client/dist/assets/{terminal-CH0fdHvP.js → terminal-BCIUSkrg.js} +0 -0
  336. /package/client/dist/assets/{terminal-B2sExUis.js → terminal-C8KlD61D.js} +0 -0
  337. /package/client/dist/assets/{terminal-DjSSa5qP.js → terminal-D3RTuBwy.js} +0 -0
  338. /package/client/dist/assets/{terminal-w0i-OCp32.js → terminal-DCkOEjSj2.js} +0 -0
  339. /package/client/dist/assets/{terminal-CPMigUX7.js → terminal-DOuNm2BI.js} +0 -0
  340. /package/client/dist/assets/{tickets-D00mSt832.js → tickets-B2q36GlQ2.js} +0 -0
  341. /package/client/dist/assets/{tickets-CQTana1u.js → tickets-Bsyv_znj.js} +0 -0
  342. /package/client/dist/assets/{tickets-D7CSuWjO.js → tickets-C_UPra_1.js} +0 -0
  343. /package/client/dist/assets/{tickets-heDAaj8C2.js → tickets-D72LbUtU2.js} +0 -0
  344. /package/client/dist/assets/{tickets-DvbFg_-c2.js → tickets-DDjU7UfE2.js} +0 -0
  345. /package/client/dist/assets/{tickets-onKZz6Dd.js → tickets-DR4ZrPMf.js} +0 -0
  346. /package/client/dist/assets/{tickets-Dq7HDy8n.js → tickets-DiUA3rXq.js} +0 -0
@@ -4,6 +4,45 @@ exports.metaTools = metaTools;
4
4
  const zod_1 = require("zod");
5
5
  const guide_1 = require("../guide");
6
6
  const types_1 = require("./types");
7
+ /**
8
+ * Serialize a raw-shape zod field into `{ name, description, type, enumValues?,
9
+ * optional }`. Unwraps optional/default/nullable wrappers (keeping the OUTERMOST
10
+ * `.describe()` string, the convention across the tool files) down to the base
11
+ * type so the model sees the real type + enum values instead of a bare key name.
12
+ */
13
+ function describeZodField(name, field) {
14
+ let current = field;
15
+ let optional = false;
16
+ let nullable = false;
17
+ let description;
18
+ for (;;) {
19
+ if (description === undefined && current.description !== undefined)
20
+ description = current.description;
21
+ const def = current._def;
22
+ const typeName = def?.typeName;
23
+ if ((typeName === 'ZodOptional' || typeName === 'ZodDefault') && def?.innerType) {
24
+ optional = true;
25
+ current = def.innerType;
26
+ continue;
27
+ }
28
+ if (typeName === 'ZodNullable' && def?.innerType) {
29
+ nullable = true;
30
+ current = def.innerType;
31
+ continue;
32
+ }
33
+ break;
34
+ }
35
+ const baseName = (current._def?.typeName ?? 'ZodUnknown')
36
+ .replace(/^Zod/, '')
37
+ .toLowerCase();
38
+ const type = nullable ? `${baseName} | null` : baseName;
39
+ const out = { name, type, optional };
40
+ if (description !== undefined)
41
+ out.description = description;
42
+ if (current instanceof zod_1.z.ZodEnum)
43
+ out.enumValues = [...current.options];
44
+ return out;
45
+ }
7
46
  /**
8
47
  * Meta tools that teach the platform and aid discovery.
9
48
  * `getSpecs` returns the full catalog (passed lazily to avoid a cycle).
@@ -13,7 +52,8 @@ function metaTools(getSpecs) {
13
52
  {
14
53
  name: 'specrails_guide',
15
54
  title: 'Platform guide',
16
- description: 'Returns a self-contained guide to Specrails: concepts, workflow, invariants, and how to use these tools. Read this first.',
55
+ description: 'Returns a self-contained guide to Specrails: concepts, workflow, invariants, and how to use these tools. ' +
56
+ 'Read this first — call once per session before your first domain call.',
17
57
  tier: 'read',
18
58
  inputSchema: {},
19
59
  handler: () => guide_1.SPECRAILS_GUIDE,
@@ -45,7 +85,7 @@ function metaTools(getSpecs) {
45
85
  {
46
86
  name: 'specrails_describe',
47
87
  title: 'Describe a tool',
48
- description: 'Return the full description and input schema summary for a named Specrails tool.',
88
+ description: 'Return the full description and per-field input schema (name, description, type, enum values, optionality) for a named Specrails tool.',
49
89
  tier: 'read',
50
90
  inputSchema: {
51
91
  name: zod_1.z.string().describe('Tool name, e.g. "specrails_rails"'),
@@ -59,7 +99,7 @@ function metaTools(getSpecs) {
59
99
  name: spec.name,
60
100
  title: spec.title,
61
101
  description: spec.description,
62
- inputFields: Object.keys(spec.inputSchema),
102
+ inputFields: Object.entries(spec.inputSchema).map(([fieldName, field]) => describeZodField(fieldName, field)),
63
103
  };
64
104
  },
65
105
  },
@@ -72,19 +72,19 @@ function railsTools() {
72
72
  mode: zod_1.z
73
73
  .enum(['implement', 'batch-implement', 'ultracode', 'loop'])
74
74
  .optional()
75
- .describe('Launch mode (launch; default "implement"). ultracode is Claude-only and runs one job per ticket; loop runs an app-driven loop per ticket.'),
75
+ .describe('Launch mode (launch; default "implement"). "ultracode" is the wire value for Freestyle mode — hands the spec straight to the model, Claude-only, one job per ticket; loop runs an app-driven loop per ticket.'),
76
76
  model: zod_1.z
77
- .enum(['haiku', 'sonnet', 'opus'])
77
+ .string()
78
78
  .optional()
79
- .describe('Ultracode Claude model (launch, ultracode mode only). For loop mode, a provider-valid model string.'),
79
+ .describe('Model for launch. Freestyle mode (wire value \'ultracode\') is Claude-only and takes a Claude alias (haiku | sonnet | opus | fable). For loop mode, any model string valid for the rail\'s provider (claude/codex/gemini).'),
80
80
  interactive: zod_1.z
81
81
  .boolean()
82
82
  .optional()
83
- .describe('Interactive ultracode session (launch, ultracode + Claude only; requires the interactive-jobs feature)'),
83
+ .describe('DEPRECATED — accepted and ignored: Freestyle jobs are interactive by default whenever the interactive-jobs feature is enabled'),
84
84
  loopId: zod_1.z
85
85
  .string()
86
86
  .optional()
87
- .describe('Loop id to run (launch). Factory ids (e.g. "factory:implement") map to a legacy mode; a custom published loop id keeps mode="loop".'),
87
+ .describe('Loop id to run (launch). Factory ids (e.g. "factory:implement") map to a legacy mode; a custom published loop id keeps mode="loop". Browse/author loops with specrails_loops; the loop must be Published.'),
88
88
  reasoning_effort: zod_1.z
89
89
  .enum(['low', 'medium', 'high'])
90
90
  .optional()
@@ -165,7 +165,7 @@ function railsTools() {
165
165
  const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/${railIndex}/launch`, body);
166
166
  return {
167
167
  ...r,
168
- hint: 'Launch accepted (202). Use specrails_watch with the returned jobId/jobIds (or loopRunIds for loop mode) to await completion. Live output streams over the job WS.',
168
+ hint: 'Launch accepted (202). Use specrails_watch with the returned jobId/jobIds (or loopRunIds for loop mode) to await completion. Live output streams over the job WS. Rails run for minutes; pass untilMs up to 600000 and re-watch on timeout.',
169
169
  };
170
170
  }
171
171
  case 'stop': {
@@ -36,7 +36,8 @@ function setupTools() {
36
36
  'Per-project actions: install_config (persist install-config.yaml), install (spawn offline framework scaffold — async/202), ' +
37
37
  'checkpoints (poll live setup/install state — the canonical completion signal when not streaming WS), ' +
38
38
  'abort (SIGTERM/SIGKILL the in-progress install child), integration_contract (read the ticket-provider contract). ' +
39
- 'Install is QUICK-only and provisions every registered provider in one shot; the legacy full/enrich AI flow is not exposed here.',
39
+ 'Install is QUICK-only and provisions every registered provider in one shot; the legacy full/enrich AI flow is not exposed here. ' +
40
+ 'Lifecycle: 1) prerequisites 2) add_project(path, providers) 3) install_config per provider 4) ONE install (provisions ALL chosen providers; quick-only, offline) 5) poll checkpoints until isInstalling === false.',
40
41
  hintTier: 'read',
41
42
  tier: (a) => {
42
43
  const action = a.action;
@@ -25,7 +25,12 @@ function resolveSpecsTier(action) {
25
25
  }
26
26
  const WATCH_HINT = 'Returns 202 immediately; the actual result streams over the WebSocket. ' +
27
27
  'Use specrails_watch with the returned requestId/scheduled ref to await completion ' +
28
- '(WS events: spec_gen_*, ticket_ai_edit_*, explore.contract_refine_*, smash.*).';
28
+ '(WS events: spec_gen_*, ticket_ai_edit_*, smash.*).';
29
+ // Contract Refine settles asymmetrically: only FAILURE emits a watch-terminal
30
+ // event (explore.contract_refine_failed); success lands as a plain
31
+ // ticket_updated broadcast, which watch does not treat as terminal.
32
+ const CONTRACT_REFINE_HINT = 'Returns 202 immediately. Failure settles via specrails_watch (explore.contract_refine_failed); ' +
33
+ "success arrives as ticket_updated — poll specrails_specs(get) for the '## Contract Layer' section.";
29
34
  function specsTools() {
30
35
  return [
31
36
  {
@@ -35,9 +40,16 @@ function specsTools() {
35
40
  'Actions: list, get, ' +
36
41
  'create (ai-spawn — THE way to add a spec: routes through Quick Add Spec, so the specrails agent generates a structured spec with AI from the user request, exactly matching the app\'s "Add Spec → Quick" flow; pass the request as title/description, it becomes the idea; async), ' +
37
42
  'update, delete (destructive), ' +
38
- 'from_prompt (verbatim insert, no AI — use ONLY when you already have a complete finished spec to store as-is), ' +
39
- 'save_draft (persist an Explore conversation as a draft), commit_draft (flip a draft to a real spec), ' +
40
- 'generate (alias of create — Quick AI spec generation), ai_edit (ai-spawn, AI-edit a ticket title+description), ' +
43
+ 'from_prompt (verbatim insert, no AI — use ONLY when you already have a complete finished spec to store as-is; ' +
44
+ 'cannot set acceptanceCriteria or shortSummary — prefer commit_draft for structured specs), ' +
45
+ 'save_draft (persist an Explore conversation as a draft), ' +
46
+ 'commit_draft (write, no AI — TWO uses: (a) flip an Explore draft to a real spec, passing conversationId from ' +
47
+ 'specrails_chat and/or draftTicketId; (b) with NO conversationId/draftTicketId, insert a COMPLETE spec you ' +
48
+ 'authored in ONE call: title (required), description, acceptanceCriteria, priority, labels, shortSummary, ' +
49
+ 'assignee, prerequisites, metadata. THE canonical way to persist a spec refined in conversation — do NOT route ' +
50
+ 'a finished spec through create/generate, which re-generate the content with AI and lossily rewrite it), ' +
51
+ 'generate (same as create but takes a pre-formed idea string; both accept contextScope/attachments/createLocal), ' +
52
+ 'ai_edit (ai-spawn, AI-edit a ticket title+description), ' +
41
53
  'cancel_ai_edit (abort an in-flight ai-edit), contract_refine (ai-spawn, append a Contract Layer — Claude-only), ' +
42
54
  'smash (ai-spawn, decompose a spec into N child sub-specs under an epic — Claude-only), ' +
43
55
  'smash_undo (destructive, reverse a prior SMASH), delete_epic_children (destructive, delete all children of an epic), ' +
@@ -81,7 +93,7 @@ function specsTools() {
81
93
  status: zod_1.z
82
94
  .string()
83
95
  .optional()
84
- .describe('list: filter by status (CSV: draft|todo|in_progress|done|cancelled). create/update: set ticket status'),
96
+ .describe('list: CSV status filter (draft|todo|in_progress|done|cancelled). update: set ticket status — note job outcomes set in_progress/done automatically; do not fight the pipeline.'),
85
97
  label: zod_1.z.string().optional().describe('list: filter by label (CSV)'),
86
98
  q: zod_1.z.string().optional().describe('list: free-text search over title + description'),
87
99
  // ── create / update / from_prompt / commit_draft fields ──────────
@@ -92,13 +104,13 @@ function specsTools() {
92
104
  .describe('Ticket description. For ai_edit this is the REQUIRED current baseline description'),
93
105
  priority: zod_1.z
94
106
  .enum(['critical', 'high', 'medium', 'low'])
107
+ .nullable()
95
108
  .optional()
96
- .describe('Ticket priority (defaults to medium; may be null only for drafts)'),
97
- labels: zod_1.z.array(zod_1.z.string()).optional().describe('Labels array'),
98
- assignee: zod_1.z.string().optional().describe('create/update: assignee'),
99
- prerequisites: zod_1.z.array(zod_1.z.number().int()).optional().describe('create/update: prerequisite ticket ids'),
100
- metadata: zod_1.z.record(zod_1.z.unknown()).optional().describe('create/update: free-form metadata object'),
101
- source: zod_1.z.string().optional().describe('create: source tag'),
109
+ .describe('update/from_prompt/commit_draft: ticket priority. update: null allowed only for drafts'),
110
+ labels: zod_1.z.array(zod_1.z.string()).optional().describe('update/from_prompt/save_draft/commit_draft: labels array'),
111
+ assignee: zod_1.z.string().optional().describe('update/commit_draft: assignee'),
112
+ prerequisites: zod_1.z.array(zod_1.z.number().int()).optional().describe('update/commit_draft: prerequisite ticket ids'),
113
+ metadata: zod_1.z.record(zod_1.z.unknown()).optional().describe('update/commit_draft: free-form metadata object (update merges keys)'),
102
114
  acceptanceCriteria: zod_1.z
103
115
  .array(zod_1.z.string())
104
116
  .optional()
@@ -106,31 +118,31 @@ function specsTools() {
106
118
  shortSummary: zod_1.z.string().optional().describe('commit_draft: explicit short summary (camelCase key)'),
107
119
  short_summary: zod_1.z.string().optional().describe('update: explicit short summary (snake_case key; null clears)'),
108
120
  structured: zod_1.z.boolean().optional().describe('from_prompt: lightly structure the verbatim prompt'),
109
- createLocal: zod_1.z.boolean().optional().describe('commit_draft/generate: keep the spec local (skip Jira promote)'),
121
+ createLocal: zod_1.z.boolean().optional().describe('create/generate/commit_draft: keep the spec local (skip Jira promote)'),
110
122
  // ── draft linkage ────────────────────────────────────────────────
111
123
  conversationId: zod_1.z
112
124
  .string()
113
125
  .optional()
114
- .describe('save_draft (required) / commit_draft: origin Explore conversation id'),
126
+ .describe("save_draft (required) / commit_draft: origin Explore conversation id — obtained from specrails_chat(create kind:'explore'); NEVER an agent/desktop conversation id"),
115
127
  draftTicketId: zod_1.z.number().int().optional().describe('commit_draft: explicit draft ticket id to flip in place'),
116
128
  editTicketId: zod_1.z.number().int().optional().describe('save_draft: existing ticket id to demote to a draft in place'),
117
129
  pendingSpecId: zod_1.z
118
130
  .string()
119
131
  .optional()
120
- .describe('generate/from_prompt/commit_draft: pending-spec UUID owning pre-creation attachments'),
121
- // ── generate (Quick AI spec) ─────────────────────────────────────
132
+ .describe('create/generate/from_prompt/commit_draft: pending-spec UUID owning pre-creation attachments'),
133
+ // ── create / generate (Quick AI spec) ────────────────────────────
122
134
  idea: zod_1.z.string().optional().describe('generate: the idea/prompt to turn into a spec (required)'),
123
- model: zod_1.z.string().optional().describe('generate/smash: model override (validated against the provider catalog)'),
135
+ model: zod_1.z.string().optional().describe('create/generate/smash: model override (validated against the provider catalog)'),
124
136
  aiEngine: zod_1.z
125
137
  .string()
126
138
  .optional()
127
- .describe('generate: provider/engine override (must be an installed provider; defaults to primary)'),
139
+ .describe('create/generate: provider/engine override (must be an installed provider; defaults to primary)'),
128
140
  contextScope: zod_1.z
129
141
  .record(zod_1.z.unknown())
130
142
  .optional()
131
- .describe('generate: context-awareness flags {specrails,openspec,full,mcp,userMcp,contractRefine}'),
132
- contractRefine: zod_1.z.boolean().optional().describe('generate: enrich the spec with a Contract Layer (Claude-only)'),
133
- attachmentIds: zod_1.z.array(zod_1.z.string()).optional().describe('generate: attachment ids (requires pendingSpecId)'),
143
+ .describe('create/generate: context-awareness flags {specrails,openspec,full,mcp,userMcp,contractRefine}'),
144
+ contractRefine: zod_1.z.boolean().optional().describe('create/generate: enrich the spec with a Contract Layer (Claude-only)'),
145
+ attachmentIds: zod_1.z.array(zod_1.z.string()).optional().describe('create/generate/ai_edit: attachment ids (create/generate require pendingSpecId)'),
134
146
  // ── ai_edit ──────────────────────────────────────────────────────
135
147
  instructions: zod_1.z.string().optional().describe('ai_edit: natural-language editing instructions (required)'),
136
148
  priorInstructions: zod_1.z.array(zod_1.z.string()).optional().describe('ai_edit: prior refinement turns for an iterative edit'),
@@ -156,6 +168,20 @@ function specsTools() {
156
168
  }
157
169
  return id;
158
170
  };
171
+ // Shared body builder for the two Quick Add Spec entry points
172
+ // (POST /tickets/generate-spec): `create` derives the idea from
173
+ // title/description, `generate` takes it pre-formed. Both forward the
174
+ // full option set (undefined fields are dropped by JSON.stringify).
175
+ const buildGenerateSpecBody = (idea) => ({
176
+ idea,
177
+ model: args.model,
178
+ aiEngine: args.aiEngine,
179
+ contextScope: args.contextScope,
180
+ contractRefine: args.contractRefine,
181
+ attachmentIds: args.attachmentIds,
182
+ pendingSpecId: args.pendingSpecId,
183
+ createLocal: args.createLocal,
184
+ });
159
185
  switch (action) {
160
186
  // ── reads ──────────────────────────────────────────────────────
161
187
  case 'list': {
@@ -198,19 +224,21 @@ function specsTools() {
198
224
  // following the project's conventions, instead of a raw insert of
199
225
  // whatever the external LLM wrote. The user's request becomes the
200
226
  // `idea`. For a verbatim insert with no AI, use `from_prompt`.
227
+ // generate-spec accepts none of labels/priority/status/assignee —
228
+ // throwing turns the likeliest silent misuse into a self-correcting error.
229
+ if (args.labels !== undefined ||
230
+ args.priority !== undefined ||
231
+ args.status !== undefined ||
232
+ args.assignee !== undefined) {
233
+ throw new Error('create generates the spec with AI and cannot set labels/priority directly — use commit_draft (you control everything) or update after generation.');
234
+ }
201
235
  const idea = [args.title, args.description]
202
236
  .filter((v) => typeof v === 'string' && v.trim().length > 0)
203
237
  .join('\n\n')
204
238
  .trim();
205
239
  if (!idea)
206
240
  throw new Error('create requires a "title" and/or "description" to use as the spec idea.');
207
- const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, {
208
- idea,
209
- model: args.model,
210
- aiEngine: args.aiEngine,
211
- provider: args.provider,
212
- contractRefine: args.contractRefine,
213
- });
241
+ const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, buildGenerateSpecBody(idea));
214
242
  return { ...r, hint: WATCH_HINT };
215
243
  }
216
244
  case 'update':
@@ -257,6 +285,9 @@ function specsTools() {
257
285
  acceptanceCriteria: args.acceptanceCriteria,
258
286
  priority: args.priority,
259
287
  shortSummary: args.shortSummary,
288
+ assignee: args.assignee,
289
+ prerequisites: args.prerequisites,
290
+ metadata: args.metadata,
260
291
  createLocal: args.createLocal,
261
292
  });
262
293
  case 'cancel_ai_edit': {
@@ -280,16 +311,7 @@ function specsTools() {
280
311
  case 'generate': {
281
312
  if (!args.idea)
282
313
  throw new Error('generate requires an "idea".');
283
- const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, {
284
- idea: args.idea,
285
- model: args.model,
286
- aiEngine: args.aiEngine,
287
- contextScope: args.contextScope,
288
- contractRefine: args.contractRefine,
289
- attachmentIds: args.attachmentIds,
290
- pendingSpecId: args.pendingSpecId,
291
- createLocal: args.createLocal,
292
- });
314
+ const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/generate-spec`, buildGenerateSpecBody(args.idea));
293
315
  return { ...r, hint: WATCH_HINT };
294
316
  }
295
317
  case 'ai_edit': {
@@ -309,7 +331,7 @@ function specsTools() {
309
331
  }
310
332
  case 'contract_refine': {
311
333
  const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/contract-refine`);
312
- return { ...r, hint: WATCH_HINT };
334
+ return { ...r, hint: CONTRACT_REFINE_HINT };
313
335
  }
314
336
  case 'smash': {
315
337
  const r = await (0, types_1.apiCall)(ctx, 'POST', `${base}/tickets/${requireId()}/smash`, {
@@ -56,13 +56,17 @@ function requireProject(ctx, projectId) {
56
56
  return pc;
57
57
  }
58
58
  // ── Active-project stickiness (per server process) ────────────────────────────
59
- // The active project is a soft default; an explicit projectId always wins.
59
+ // The active project is a soft default; an explicit projectId always wins, and
60
+ // a per-request pin (ctx.requestProjectId, from the in-app agent's loopback
61
+ // header) wins over the process-wide selection. `setActiveProject` remains for
62
+ // `specrails_select_project` (and, fallback-only, the agent-chat manager's
63
+ // per-turn pin) — request headers must never mutate this global.
60
64
  let _activeProjectId = null;
61
65
  function setActiveProject(id) {
62
66
  _activeProjectId = id;
63
67
  }
64
- function getActiveProject(_ctx) {
65
- return _activeProjectId;
68
+ function getActiveProject(ctx) {
69
+ return ctx.requestProjectId ?? _activeProjectId;
66
70
  }
67
71
  function tierAnnotations(tier) {
68
72
  switch (tier) {
@@ -93,7 +97,9 @@ function errorResult(message) {
93
97
  */
94
98
  function emitMcpActivity(ctx, spec, args, tier) {
95
99
  try {
96
- const affectedProjectId = (typeof args.projectId === 'string' ? args.projectId : null) ?? getActiveProject(ctx);
100
+ const affectedProjectId = (typeof args.projectId === 'string' ? args.projectId : null) ??
101
+ ctx.requestProjectId ??
102
+ getActiveProject(ctx);
97
103
  ctx.broadcast({
98
104
  type: 'mcp.activity',
99
105
  tool: spec.name,
@@ -123,12 +129,15 @@ function registerTieredTool(server, ctx, spec) {
123
129
  }, async (args, extra) => {
124
130
  // In-app agent: a per-request active-project header (loopback-only, set by
125
131
  // the agent's bridge from the Cursor-style selector) pins the project for
126
- // this call so project-scoped tools resolve without specrails_select_project.
132
+ // THIS CALL ONLY via a per-call ctx copy — it must never mutate the
133
+ // process-wide active project, or a concurrent external MCP client and the
134
+ // in-app agent would clobber each other (wrong-project delete/launch).
127
135
  const projectHeader = extra?.requestInfo?.headers?.[agent_tier_1.AGENT_PROJECT_HEADER];
136
+ let callCtx = ctx;
128
137
  if (projectHeader != null) {
129
138
  const pid = Array.isArray(projectHeader) ? projectHeader[0] : projectHeader;
130
139
  if (typeof pid === 'string' && pid.trim())
131
- setActiveProject(pid.trim());
140
+ callCtx = { ...ctx, requestProjectId: pid.trim() };
132
141
  }
133
142
  const tier = typeof spec.tier === 'function' ? spec.tier(args ?? {}) : spec.tier;
134
143
  // In-app agent: when the request carries the loopback-only agent-tier header,
@@ -144,9 +153,9 @@ function registerTieredTool(server, ctx, spec) {
144
153
  return errorResult((0, mcp_tiers_1.tierRefusalMessage)(tier));
145
154
  }
146
155
  try {
147
- const data = await spec.handler(ctx, args ?? {});
156
+ const data = await spec.handler(callCtx, args ?? {});
148
157
  if (tier !== 'read')
149
- emitMcpActivity(ctx, spec, args ?? {}, tier);
158
+ emitMcpActivity(callCtx, spec, args ?? {}, tier);
150
159
  return toResult(data);
151
160
  }
152
161
  catch (err) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.watchTool = watchTool;
4
4
  const zod_1 = require("zod");
5
+ const types_1 = require("./types");
5
6
  // Event `type` suffixes/strings that mean an async operation has settled.
6
7
  const TERMINAL_PATTERNS = [
7
8
  'job.finalized',
@@ -17,12 +18,25 @@ const TERMINAL_PATTERNS = [
17
18
  'agent_refine_error',
18
19
  'smash.completed',
19
20
  'smash.failed',
21
+ 'smash.undone',
20
22
  'loop.run_completed',
21
23
  'loop.run_stopped',
22
24
  'explore.contract_refine_failed',
23
25
  'file.summary_updated',
24
26
  'file.summary_failed',
27
+ 'setup_install_done',
28
+ 'setup_error',
29
+ 'plugin.installed',
30
+ 'plugin.degraded',
25
31
  ];
32
+ // Job statuses that mean a polled job has settled (see project-router-jobs.ts).
33
+ const TERMINAL_JOB_STATUSES = new Set(['completed', 'failed', 'canceled']);
34
+ // Jobs are identified by UUIDs (server/ids.ts newId → randomUUID). When the ref
35
+ // looks like one, the wait loop also polls the job read as a fallback for jobs
36
+ // that never emit a watch-terminal event (e.g. rail-bypass `spawn` jobs).
37
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
38
+ const JOB_POLL_INTERVAL_MS = 5000;
39
+ const TIMEOUT_SUGGESTION = 'timeout ≠ failure — the operation may still be running. Re-watch with the same ref, or poll specrails_jobs(get)/specrails_specs(get) for current state.';
26
40
  function isTerminal(type) {
27
41
  return TERMINAL_PATTERNS.some((p) => type === p || type.endsWith(p));
28
42
  }
@@ -40,19 +54,35 @@ function watchTool() {
40
54
  return {
41
55
  name: 'specrails_watch',
42
56
  title: 'Watch async result',
43
- description: 'Wait for an asynchronous operation (rail launch, spec generation, chat turn, agent refine, SMASH, loop run) to settle ' +
44
- 'and return its events. Pass the projectId and the reference (jobId / requestId) returned by the action. ' +
45
- 'Resolves when a terminal event for that reference is seen, or after untilMs.',
57
+ description: 'Wait for an asynchronous operation (rail launch, spec generation, chat turn, agent refine, SMASH, loop run, plugin install, setup install) to settle ' +
58
+ 'and return its events. Pass the reference (jobId / requestId / conversationId / loopRunId) returned by the action; projectId is optional ' +
59
+ '(defaults to the active project; app-level operations need none). ' +
60
+ 'Resolves when a terminal event for that reference is seen, or after untilMs. ' +
61
+ 'When the ref is a jobId (or kind:"job" is passed), the job status is also polled every 5s so jobs settle even without a watch-terminal event. ' +
62
+ 'Default untilMs 120000 is often shorter than a rail run (max 600000). ' +
63
+ 'settled:false means TIMEOUT, not failure — re-watch or poll the domain read (specrails_jobs(get) / specrails_specs(get) / specrails_plugins(health)).',
46
64
  tier: 'read',
47
65
  inputSchema: {
48
- projectId: zod_1.z.string().describe('Project id the operation runs in'),
49
- ref: zod_1.z.string().describe('jobId / requestId / loopRunId returned by the async action'),
66
+ projectId: zod_1.z
67
+ .string()
68
+ .optional()
69
+ .describe('Project id the operation runs in (defaults to the active project; omit for app-level operations)'),
70
+ ref: zod_1.z.string().describe('jobId / requestId / conversationId / loopRunId returned by the async action'),
50
71
  untilMs: zod_1.z.number().int().min(1000).max(600000).default(120000).describe('Max time to wait (ms)'),
72
+ kind: zod_1.z
73
+ .enum(['job'])
74
+ .optional()
75
+ .describe('Force the ref to be treated as a jobId (enables the 5s job-status poll fallback even for non-UUID ids)'),
51
76
  },
52
77
  handler: (ctx, args) => {
53
- const projectId = args.projectId;
78
+ // Optional + active-project resolution: an explicit projectId wins, else
79
+ // the active project. When neither exists, project filtering is skipped —
80
+ // the ref-substring match is discriminating enough, and app-level agent
81
+ // operations carry no projectId at all.
82
+ const projectId = args.projectId ?? (0, types_1.getActiveProject)(ctx) ?? undefined;
54
83
  const ref = args.ref;
55
84
  const untilMs = args.untilMs ?? 120000;
85
+ const pollAsJob = args.kind === 'job' || UUID_RE.test(ref);
56
86
  return new Promise((resolve) => {
57
87
  const events = [];
58
88
  let done = false;
@@ -61,20 +91,23 @@ function watchTool() {
61
91
  return;
62
92
  done = true;
63
93
  clearTimeout(timer);
94
+ if (poller)
95
+ clearInterval(poller);
64
96
  unsub();
65
97
  resolve({
66
98
  settled,
67
99
  reason,
68
100
  ref,
69
- projectId,
101
+ projectId: projectId ?? null,
70
102
  eventCount: events.length,
71
103
  terminalEvent: settled ? events[events.length - 1] : null,
72
104
  events: events.slice(-50),
105
+ ...(settled ? {} : { suggestion: TIMEOUT_SUGGESTION }),
73
106
  });
74
107
  };
75
108
  const unsub = ctx.eventBus.onMessage((msg) => {
76
109
  const msgProjectId = msg.projectId;
77
- if (msgProjectId && msgProjectId !== projectId)
110
+ if (projectId && msgProjectId && msgProjectId !== projectId)
78
111
  return;
79
112
  if (!mentionsRef(msg, ref))
80
113
  return;
@@ -83,6 +116,36 @@ function watchTool() {
83
116
  if (isTerminal(type))
84
117
  finish(true, `terminal:${type}`);
85
118
  });
119
+ // Job-status poll fallback: some jobs (rail-bypass spawn) never emit a
120
+ // watch-terminal event; poll the job read every 5s and settle when its
121
+ // status is terminal. Polling needs a resolvable project; errors (e.g.
122
+ // the ref is not a job in this project) are ignored and the wait
123
+ // continues on the event path.
124
+ let poller;
125
+ if (pollAsJob && projectId) {
126
+ let polling = false;
127
+ poller = setInterval(() => {
128
+ if (done || polling)
129
+ return;
130
+ polling = true;
131
+ (0, types_1.apiCall)(ctx, 'GET', `/projects/${encodeURIComponent(projectId)}/jobs/${encodeURIComponent(ref)}`)
132
+ .then((r) => {
133
+ const status = r?.job?.status;
134
+ if (typeof status === 'string' && TERMINAL_JOB_STATUSES.has(status)) {
135
+ events.push({ type: 'job.poll_settled', projectId, jobId: ref, status });
136
+ finish(true, `poll:job:${status}`);
137
+ }
138
+ })
139
+ .catch(() => {
140
+ /* not a job / transient error — keep waiting on events */
141
+ })
142
+ .finally(() => {
143
+ polling = false;
144
+ });
145
+ }, JOB_POLL_INTERVAL_MS);
146
+ if (typeof poller.unref === 'function')
147
+ poller.unref();
148
+ }
86
149
  const timer = setTimeout(() => finish(false, 'timeout'), untilMs);
87
150
  // Do not keep the event loop alive solely for this watch.
88
151
  if (typeof timer.unref === 'function')
@@ -55,7 +55,7 @@ function serializeInstallConfigYaml(config) {
55
55
  return lines.join('\n');
56
56
  }
57
57
  // ─── Agent model helpers ──────────────────────────────────────────────────────
58
- const VALID_MODEL_ALIASES = ['sonnet', 'opus', 'haiku'];
58
+ const VALID_MODEL_ALIASES = ['sonnet', 'fable', 'opus', 'haiku'];
59
59
  exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
60
60
  /**
61
61
  * Relocate-artifacts: where the agent `.claude/agents/*.md` files live. When the
@@ -802,6 +802,27 @@ function registerTicketsRoutes(deps) {
802
802
  .filter((c) => c.length > 0)
803
803
  : [];
804
804
  const priority = (0, ticket_store_1.isValidPriority)(body.priority) ? body.priority : 'medium';
805
+ // Optional assignee/prerequisites/metadata (same normalization as the
806
+ // PATCH /tickets/:id route) so an MCP commit_draft is a true one-shot
807
+ // insert — no follow-up update call needed for these fields.
808
+ const hasAssignee = body.assignee !== undefined;
809
+ const assignee = typeof body.assignee === 'string' ? body.assignee : null;
810
+ const hasPrerequisites = Array.isArray(body.prerequisites);
811
+ const prerequisites = hasPrerequisites
812
+ ? body.prerequisites.filter((p) => typeof p === 'number')
813
+ : [];
814
+ const hasMetadata = typeof body.metadata === 'object' && body.metadata !== null;
815
+ const metadata = hasMetadata ? body.metadata : {};
816
+ // A supplied conversationId must reference a REAL chat conversation in this
817
+ // project (an Explore row in chat_conversations). Agent/desktop conversation
818
+ // ids are NOT valid origins — stamping one would corrupt the ticket's origin
819
+ // linkage ("Continue Explore") and mis-schedule Contract Refine.
820
+ if (conversationId && !(0, db_1.getConversation)(ctx(req).db, conversationId)) {
821
+ res.status(400).json({
822
+ error: "conversationId not found in this project's chat conversations — agent conversations are not valid origins; omit conversationId",
823
+ });
824
+ return;
825
+ }
805
826
  // Compose the final ticket body. The title is already its own ticket
806
827
  // field, so we deliberately do NOT echo it as a `## Spec Title` heading
807
828
  // inside the description. The body is just the structured sections from
@@ -865,6 +886,14 @@ function registerTicketsRoutes(deps) {
865
886
  flipTarget.description = descriptionForStore;
866
887
  if (labels.length > 0)
867
888
  flipTarget.labels = labels;
889
+ // PATCH-route semantics: apply only when the field was supplied;
890
+ // metadata merges keys instead of replacing the object.
891
+ if (hasAssignee)
892
+ flipTarget.assignee = assignee;
893
+ if (hasPrerequisites)
894
+ flipTarget.prerequisites = prerequisites;
895
+ if (hasMetadata)
896
+ flipTarget.metadata = { ...flipTarget.metadata, ...metadata };
868
897
  flipTarget.updated_at = now;
869
898
  // Preserve prior short_summary on flip when the model/body omits one;
870
899
  // overwrite only when a non-null value is provided.
@@ -898,9 +927,9 @@ function registerTicketsRoutes(deps) {
898
927
  status: 'todo',
899
928
  priority,
900
929
  labels,
901
- assignee: null,
902
- prerequisites: [],
903
- metadata: {},
930
+ assignee,
931
+ prerequisites,
932
+ metadata,
904
933
  comments: [],
905
934
  origin_conversation_id: conversationId,
906
935
  is_epic: false,
@@ -1884,7 +1913,17 @@ function registerTicketsRoutes(deps) {
1884
1913
  // non-ASCII names survive.
1885
1914
  const asciiName = meta.filename.replace(/[\r\n"]/g, '_');
1886
1915
  res.setHeader('Content-Disposition', `inline; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(meta.filename)}`);
1887
- fs_1.default.createReadStream(abs).pipe(res);
1916
+ // The exists check above is not atomic with the stream's async open — a
1917
+ // concurrent attachment DELETE can rm the file in between, and an unhandled
1918
+ // ReadStream 'error' would take down the sidecar.
1919
+ const stream = fs_1.default.createReadStream(abs);
1920
+ stream.on('error', () => {
1921
+ if (!res.headersSent)
1922
+ res.status(404).json({ error: 'Attachment not found' });
1923
+ else
1924
+ res.destroy();
1925
+ });
1926
+ stream.pipe(res);
1888
1927
  });
1889
1928
  router.delete('/:projectId/tickets/:ticketId/attachments/:attachmentId', async (req, res) => {
1890
1929
  const parsed = parseTicketKey(req.params.ticketId);
@@ -13,6 +13,7 @@ const child_process_1 = require("child_process");
13
13
  const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
14
14
  const CLAUDE_MODELS = [
15
15
  { value: 'sonnet', label: 'Claude Sonnet', default: true },
16
+ { value: 'fable', label: 'Claude Fable' },
16
17
  { value: 'opus', label: 'Claude Opus' },
17
18
  { value: 'haiku', label: 'Claude Haiku' },
18
19
  ];
@@ -20,11 +21,14 @@ const CLAUDE_MODELS = [
20
21
  * CLI accepts collapse to the short alias. */
21
22
  function normaliseModel(model) {
22
23
  switch (model) {
24
+ case 'claude-sonnet-5':
23
25
  case 'claude-sonnet-4-6':
24
26
  case 'claude-sonnet-4-5':
25
27
  case 'claude-sonnet-4-0':
26
28
  case 'claude-sonnet-4-20250514':
27
29
  return 'sonnet';
30
+ case 'claude-fable-5':
31
+ return 'fable';
28
32
  case 'claude-opus-4-8':
29
33
  case 'claude-opus-4-5':
30
34
  case 'claude-opus-4-1-20250805':
@@ -317,6 +321,8 @@ exports.claudeAdapter = {
317
321
  systemPromptArg: true,
318
322
  persistentStdin: true,
319
323
  supportsReasoningEffort: true,
324
+ reasoningEfforts: ['low', 'medium', 'high', 'xhigh'], // claude-code --effort tiers
325
+ supportsImageInput: true, // images ride as `@<abs-path>` prompt refs
320
326
  },
321
327
  modelCatalog: () => CLAUDE_MODELS,
322
328
  defaultModel: () => 'sonnet',