specrails-desktop 2.36.0 → 2.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. package/README.md +94 -247
  2. package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
  3. package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
  4. package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
  6. package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
  7. package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
  8. package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
  9. package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
  10. package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
  11. package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
  12. package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
  13. package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
  14. package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
  15. package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
  16. package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
  17. package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
  18. package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
  19. package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
  20. package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
  21. package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
  22. package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
  23. package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
  24. package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
  25. package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
  26. package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
  27. package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
  28. package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
  29. package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
  30. package/client/dist/assets/agent-BdT0SB1D.js +1 -0
  31. package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
  32. package/client/dist/assets/agent-C4ibdFk9.js +1 -0
  33. package/client/dist/assets/agent-CSFY22Zo.js +1 -0
  34. package/client/dist/assets/agent-DAnqxUn8.js +1 -0
  35. package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
  36. package/client/dist/assets/agent-T54WuR_Y.js +1 -0
  37. package/client/dist/assets/agent-i75WDZOA.js +1 -0
  38. package/client/dist/assets/browser-05vRDg_y.js +1 -0
  39. package/client/dist/assets/browser-Bulb-cB1.js +1 -0
  40. package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
  41. package/client/dist/assets/browser-CG0PE1j_.js +1 -0
  42. package/client/dist/assets/browser-CMzaqAjo.js +1 -0
  43. package/client/dist/assets/browser-DMajYdNX.js +1 -0
  44. package/client/dist/assets/browser-DxcOHrdP.js +1 -0
  45. package/client/dist/assets/browser-ObE7xVo3.js +1 -0
  46. package/client/dist/assets/builder-765NeYTp.js +1 -0
  47. package/client/dist/assets/builder-8MG3V6ea.js +1 -0
  48. package/client/dist/assets/builder-B-1UBs5K.js +1 -0
  49. package/client/dist/assets/builder-BTiGnPT0.js +1 -0
  50. package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
  51. package/client/dist/assets/builder-DKFMELBl.js +1 -0
  52. package/client/dist/assets/builder-OcmKrC0h.js +1 -0
  53. package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
  54. package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
  55. package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
  56. package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
  57. package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
  58. package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
  59. package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
  60. package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
  61. package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
  62. package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
  63. package/client/dist/assets/commands-0XjAftla.js +1 -0
  64. package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
  65. package/client/dist/assets/commands-BBwowgiE.js +1 -0
  66. package/client/dist/assets/commands-BacjIZLN.js +1 -0
  67. package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
  68. package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
  69. package/client/dist/assets/commands-DlbFzgrp.js +1 -0
  70. package/client/dist/assets/commands-LjPSsi5F.js +1 -0
  71. package/client/dist/assets/common-B5AZNYMH.js +1 -0
  72. package/client/dist/assets/common-BFZ0c_vN.js +1 -0
  73. package/client/dist/assets/common-BrlwEnL7.js +1 -0
  74. package/client/dist/assets/common-BttQx3z4.js +1 -0
  75. package/client/dist/assets/common-CM65k_-e.js +1 -0
  76. package/client/dist/assets/common-D4mvFFlu.js +1 -0
  77. package/client/dist/assets/common-TZ9Ny96S.js +1 -0
  78. package/client/dist/assets/common-nFpSF4GC.js +1 -0
  79. package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
  80. package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
  81. package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
  82. package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
  83. package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
  84. package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
  85. package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
  86. package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
  87. package/client/dist/assets/dialog-SRw01_QU.js +45 -0
  88. package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
  89. package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
  90. package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
  91. package/client/dist/assets/index-DwAmL7m5.css +2 -0
  92. package/client/dist/assets/index-EzrkUb8B.js +76 -0
  93. package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
  94. package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
  95. package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
  96. package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
  97. package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
  98. package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
  99. package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
  100. package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
  101. package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
  102. package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
  103. package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
  104. package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
  105. package/client/dist/assets/mcp-CCpSelOh.js +1 -0
  106. package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
  107. package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
  108. package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
  109. package/client/dist/assets/mcp-DUVQamDz.js +1 -0
  110. package/client/dist/assets/narration-B-ynWTpn.js +1 -0
  111. package/client/dist/assets/narration-Bj_laZwg.js +1 -0
  112. package/client/dist/assets/narration-BxHpmMv_.js +1 -0
  113. package/client/dist/assets/narration-C1Px0-0j.js +1 -0
  114. package/client/dist/assets/narration-CAXhGbzM.js +1 -0
  115. package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
  116. package/client/dist/assets/narration-XISXwqZd.js +1 -0
  117. package/client/dist/assets/narration-kwzfglQo.js +1 -0
  118. package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
  119. package/client/dist/assets/nav-B34QkESm.js +1 -0
  120. package/client/dist/assets/nav-Bap_UXAq.js +1 -0
  121. package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
  122. package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
  123. package/client/dist/assets/nav-CygC_WN-.js +1 -0
  124. package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
  125. package/client/dist/assets/nav-m00uxYUG.js +1 -0
  126. package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
  127. package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
  128. package/client/dist/assets/packet-C3Ievb9R.js +1 -0
  129. package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
  130. package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
  131. package/client/dist/assets/packet-DUTGX52M.js +1 -0
  132. package/client/dist/assets/packet-DhsKtUPB.js +1 -0
  133. package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
  134. package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
  135. package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
  136. package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
  137. package/client/dist/assets/settings-BJB9RKJj.js +1 -0
  138. package/client/dist/assets/settings-CHwQR2rB.js +1 -0
  139. package/client/dist/assets/settings-CrMi3kSi.js +1 -0
  140. package/client/dist/assets/settings-DKyhDGD1.js +1 -0
  141. package/client/dist/assets/settings-Deupji9c.js +1 -0
  142. package/client/dist/assets/settings-DqRMmwAM.js +1 -0
  143. package/client/dist/assets/settings-J4rj6vo-.js +1 -0
  144. package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
  145. package/client/dist/assets/setup-Ba11l2JA.js +1 -0
  146. package/client/dist/assets/setup-BzW4kMok.js +1 -0
  147. package/client/dist/assets/setup-COzwYhkJ.js +1 -0
  148. package/client/dist/assets/setup-CRVULhtF.js +1 -0
  149. package/client/dist/assets/setup-DgvlxwpE.js +1 -0
  150. package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
  151. package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
  152. package/client/dist/assets/spending-BqbdPm_4.js +1 -0
  153. package/client/dist/assets/square-Ci-PFcFe.js +1 -0
  154. package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
  155. package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
  156. package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
  157. package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
  158. package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
  159. package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
  160. package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
  161. package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
  162. package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
  163. package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
  164. package/client/dist/index.html +34 -35
  165. package/docs/README.md +3 -0
  166. package/docs/agent-live-steering.md +57 -0
  167. package/docs/ci-cd.md +57 -0
  168. package/docs/code-explorer.md +48 -0
  169. package/docs/codex.md +12 -1
  170. package/docs/features/detachable-mission-windows.md +59 -0
  171. package/docs/guide/de/integrations/5-mcp-server.md +13 -9
  172. package/docs/guide/de/integrations/6-agent-chat.md +1 -0
  173. package/docs/guide/en/integrations/5-mcp-server.md +13 -9
  174. package/docs/guide/en/integrations/6-agent-chat.md +1 -0
  175. package/docs/guide/es/integrations/5-mcp-server.md +13 -9
  176. package/docs/guide/es/integrations/6-agent-chat.md +1 -0
  177. package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
  178. package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
  179. package/docs/guide/it/integrations/5-mcp-server.md +13 -9
  180. package/docs/guide/it/integrations/6-agent-chat.md +1 -0
  181. package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
  182. package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
  183. package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
  184. package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
  185. package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
  186. package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
  187. package/docs/internals/README.md +1 -0
  188. package/docs/internals/browser-capture-performance.md +55 -3
  189. package/docs/internals/browser-login-popups.md +45 -0
  190. package/docs/internals/browser-native-retina-audit.md +39 -0
  191. package/docs/internals/ci-cd-audit.md +39 -0
  192. package/docs/internals/core-runtime-updates.md +68 -0
  193. package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
  194. package/docs/internals/implementation-reliability-audit.md +93 -0
  195. package/docs/internals/interactive-jobs.md +110 -6
  196. package/docs/internals/mcp-mission-audit.md +76 -0
  197. package/docs/internals/project-builder.md +404 -42
  198. package/docs/internals/safe-pr-review-flow.md +52 -10
  199. package/docs/internals/startup-recovery-audit.md +58 -0
  200. package/docs/mcp.md +74 -41
  201. package/docs/mission-processes.md +59 -0
  202. package/docs/multi-repo-projects.md +51 -0
  203. package/docs/platforms/windows-parity.md +70 -0
  204. package/docs/platforms/windows.md +13 -11
  205. package/docs/running-pipelines.md +14 -0
  206. package/mcp-bridge/dist/specrails-mcp.js +7186 -0
  207. package/package.json +9 -4
  208. package/server/dist/agent-chat-manager.js +537 -68
  209. package/server/dist/agent-chat-router.js +87 -6
  210. package/server/dist/agent-context-resolver.js +129 -25
  211. package/server/dist/agent-defaults.js +19 -11
  212. package/server/dist/agent-input-store.js +206 -0
  213. package/server/dist/agent-mcp-config.js +54 -15
  214. package/server/dist/agent-operator-prompt.js +255 -65
  215. package/server/dist/agent-spec-framing.js +202 -0
  216. package/server/dist/agent-steering.js +218 -0
  217. package/server/dist/agent-store.js +153 -0
  218. package/server/dist/api-not-found.js +8 -0
  219. package/server/dist/background-process-control.js +164 -0
  220. package/server/dist/background-process-service.js +61 -0
  221. package/server/dist/background-process-store.js +242 -0
  222. package/server/dist/background-windows-bootstrap.js +238 -0
  223. package/server/dist/blueprint-chat-manager.js +476 -62
  224. package/server/dist/blueprint-commit.js +21 -1
  225. package/server/dist/blueprint-draft-parser.js +122 -17
  226. package/server/dist/blueprint-generation.js +238 -0
  227. package/server/dist/blueprint-operator-prompt.js +91 -63
  228. package/server/dist/blueprint-router.js +55 -3
  229. package/server/dist/blueprint-spec-fixtures.js +85 -0
  230. package/server/dist/blueprint-spec-quality.js +57 -16
  231. package/server/dist/blueprint-store.js +92 -1
  232. package/server/dist/browser-capture-manager.js +86 -30
  233. package/server/dist/browser-playwright.js +168 -45
  234. package/server/dist/browser-viewport.js +26 -0
  235. package/server/dist/chat-manager.js +23 -15
  236. package/server/dist/chromium-archive.cjs +83 -0
  237. package/server/dist/chromium-resolver.js +72 -64
  238. package/server/dist/code-activity.js +145 -0
  239. package/server/dist/code-explorer-router.js +714 -114
  240. package/server/dist/core-compat.js +28 -1
  241. package/server/dist/core-execution.js +111 -0
  242. package/server/dist/core-package.js +4 -16
  243. package/server/dist/core-runtime.js +202 -0
  244. package/server/dist/core-update-manager.js +161 -60
  245. package/server/dist/core-update-state.js +29 -0
  246. package/server/dist/db.js +66 -35
  247. package/server/dist/delivery-evidence.js +142 -39
  248. package/server/dist/desktop-db.js +239 -43
  249. package/server/dist/desktop-router.js +109 -9
  250. package/server/dist/dev-ports.js +8 -3
  251. package/server/dist/file-provenance.js +34 -22
  252. package/server/dist/file-story-manager.js +62 -64
  253. package/server/dist/file-story.js +118 -35
  254. package/server/dist/file-summary-generator.js +46 -5
  255. package/server/dist/file-summary-manager.js +355 -106
  256. package/server/dist/framework-manager.js +98 -110
  257. package/server/dist/framework-reseed.js +76 -10
  258. package/server/dist/git-diagnostics.js +2 -2
  259. package/server/dist/headroom-manager.js +38 -3
  260. package/server/dist/host-control.js +52 -0
  261. package/server/dist/index.js +43 -2
  262. package/server/dist/interactive-job-session.js +149 -16
  263. package/server/dist/internal-api.js +48 -0
  264. package/server/dist/jira/jira-materializer.js +1 -0
  265. package/server/dist/json-tolerant.js +174 -0
  266. package/server/dist/legacy-migration.js +4 -2
  267. package/server/dist/loop-command-catalog.js +83 -21
  268. package/server/dist/loop-constants.js +6 -1
  269. package/server/dist/loop-decider.js +11 -1
  270. package/server/dist/loop-executors.js +194 -45
  271. package/server/dist/loop-factory.js +11 -4
  272. package/server/dist/loop-graph.js +90 -4
  273. package/server/dist/loop-run-manager.js +301 -69
  274. package/server/dist/loop-shell-invocation.js +21 -0
  275. package/server/dist/loop-step-idle.js +55 -0
  276. package/server/dist/loop-templates.js +31 -7
  277. package/server/dist/mcp/agent-capability.js +37 -4
  278. package/server/dist/mcp/guide.js +149 -36
  279. package/server/dist/mcp/mcp-admin-router.js +20 -4
  280. package/server/dist/mcp/mcp-server.js +89 -43
  281. package/server/dist/mcp/mcp-tiers.js +6 -5
  282. package/server/dist/mcp/mcp-token.js +3 -1
  283. package/server/dist/mcp/resources.js +5 -4
  284. package/server/dist/mcp/tools/catalog.js +4 -0
  285. package/server/dist/mcp/tools/code.js +125 -14
  286. package/server/dist/mcp/tools/context.js +153 -0
  287. package/server/dist/mcp/tools/git.js +15 -5
  288. package/server/dist/mcp/tools/jobs.js +84 -71
  289. package/server/dist/mcp/tools/loops.js +17 -6
  290. package/server/dist/mcp/tools/meta.js +74 -27
  291. package/server/dist/mcp/tools/mission.js +29 -0
  292. package/server/dist/mcp/tools/projects.js +22 -6
  293. package/server/dist/mcp/tools/rails.js +48 -11
  294. package/server/dist/mcp/tools/specs.js +86 -14
  295. package/server/dist/mcp/tools/support.js +1 -1
  296. package/server/dist/mcp/tools/types.js +117 -43
  297. package/server/dist/mcp/tools/watch.js +144 -51
  298. package/server/dist/milestone-chain-store.js +164 -0
  299. package/server/dist/milestone-chain.js +487 -0
  300. package/server/dist/milestone-progress.js +348 -0
  301. package/server/dist/mobile/mobile-devices.js +10 -7
  302. package/server/dist/mobile/mobile-gateway.js +2 -0
  303. package/server/dist/mobile/mobile-missions.js +214 -0
  304. package/server/dist/mobile/mobile-redact.js +17 -3
  305. package/server/dist/mobile/mobile-router.js +46 -4
  306. package/server/dist/mobile/mobile-ws.js +49 -4
  307. package/server/dist/multi-repo-bases.js +56 -0
  308. package/server/dist/multi-repo-checkout.js +101 -0
  309. package/server/dist/multi-repo-delivery.js +100 -0
  310. package/server/dist/multi-repo-execution-store.js +65 -0
  311. package/server/dist/multi-repo-execution.js +453 -0
  312. package/server/dist/offline-assemble.js +10 -8
  313. package/server/dist/openspec-runtime-plugin-commands.json +18 -0
  314. package/server/dist/openspec-runtime-plugin.js +120 -0
  315. package/server/dist/plugins/codex-spawn.js +49 -0
  316. package/server/dist/plugins/serena/manifest.js +2 -2
  317. package/server/dist/plugins/serena/templates/instructions.md +16 -0
  318. package/server/dist/pr-publisher.js +1 -1
  319. package/server/dist/profiles-router.js +3 -18
  320. package/server/dist/project-code-discovery.js +142 -0
  321. package/server/dist/project-git.js +19 -21
  322. package/server/dist/project-registry.js +272 -20
  323. package/server/dist/project-repositories.js +157 -0
  324. package/server/dist/project-repository-provenance.js +31 -0
  325. package/server/dist/project-router-background-processes.js +105 -0
  326. package/server/dist/project-router-git.js +75 -16
  327. package/server/dist/project-router-jobs.js +2 -126
  328. package/server/dist/project-router-loop-runs.js +72 -2
  329. package/server/dist/project-router-tickets.js +53 -8
  330. package/server/dist/project-router.js +239 -38
  331. package/server/dist/provider-detection.js +18 -6
  332. package/server/dist/provider-limit.js +64 -0
  333. package/server/dist/providers/claude-adapter.js +26 -1
  334. package/server/dist/providers/claude-background-tasks.js +18 -0
  335. package/server/dist/providers/claude-live-session.js +383 -0
  336. package/server/dist/providers/codex-adapter.js +19 -0
  337. package/server/dist/providers/codex-live-session.js +545 -0
  338. package/server/dist/providers/gemini-adapter.js +1 -0
  339. package/server/dist/providers/live-session-types.js +12 -0
  340. package/server/dist/providers/live-session.js +13 -0
  341. package/server/dist/providers/terminal-result.js +24 -0
  342. package/server/dist/queue-manager.js +2 -0
  343. package/server/dist/rail-isolated-launch.js +141 -16
  344. package/server/dist/rail-pr-decision.js +364 -99
  345. package/server/dist/rail-pr-store.js +24 -10
  346. package/server/dist/rail-worktree-release.js +34 -2
  347. package/server/dist/rail-worktrees-store.js +4 -4
  348. package/server/dist/rails-router.js +249 -55
  349. package/server/dist/repo-lock.js +92 -17
  350. package/server/dist/review-packet.js +23 -13
  351. package/server/dist/schemas/file-summary.v1.json +59 -59
  352. package/server/dist/schemas/profile.v1.json +137 -137
  353. package/server/dist/setup-manager.js +52 -8
  354. package/server/dist/shell-integration/bash-shim.bash +45 -0
  355. package/server/dist/shell-integration/fish-shim.fish +36 -0
  356. package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
  357. package/server/dist/shell-integration/zsh-shim.zsh +47 -0
  358. package/server/dist/smash-runner.js +1 -0
  359. package/server/dist/spawn-lifecycle.js +44 -27
  360. package/server/dist/spec-contract-prompt.js +143 -0
  361. package/server/dist/spec-draft-parser.js +6 -1
  362. package/server/dist/spec-models.js +1 -0
  363. package/server/dist/stuck-run-detector.js +1 -0
  364. package/server/dist/terminal-manager.js +21 -1
  365. package/server/dist/terminal-shell-integration.js +4 -1
  366. package/server/dist/transient-children.js +619 -230
  367. package/server/dist/util/cli-prompt.js +78 -5
  368. package/server/dist/util/sqlite-migrations.js +20 -0
  369. package/server/dist/util/win-spawn.js +18 -3
  370. package/server/dist/util/windows-provider-shell.js +29 -0
  371. package/server/dist/verification-sentinel.js +14 -0
  372. package/server/dist/vitest-setup.js +2 -0
  373. package/server/dist/windows-job-supervisor.js +133 -0
  374. package/server/dist/workspace-manager.js +9 -30
  375. package/server/dist/worktree-manager.js +17 -2
  376. package/server/dist/worktree-overlay.js +3 -1
  377. package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
  378. package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
  379. package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
  380. package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
  381. package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
  382. package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
  383. package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
  384. package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
  385. package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
  386. package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
  387. package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
  388. package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
  389. package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
  390. package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
  391. package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
  392. package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
  393. package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
  394. package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
  395. package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
  396. package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
  397. package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
  398. package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
  399. package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
  400. package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
  401. package/client/dist/assets/agent-BQgKv_2F.js +0 -1
  402. package/client/dist/assets/agent-C1NRDJqL.js +0 -1
  403. package/client/dist/assets/agent-CLNleiy6.js +0 -1
  404. package/client/dist/assets/agent-CmceArkQ.js +0 -1
  405. package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
  406. package/client/dist/assets/agent-DB1JeE64.js +0 -1
  407. package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
  408. package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
  409. package/client/dist/assets/auth-D_5bQv5W.js +0 -1
  410. package/client/dist/assets/ban-E4EaZe3a.js +0 -1
  411. package/client/dist/assets/bot-DzuuuC5R.js +0 -1
  412. package/client/dist/assets/browser-BgqHDMXP.js +0 -1
  413. package/client/dist/assets/browser-CsKJtrpv.js +0 -1
  414. package/client/dist/assets/browser-Cu58EpZU.js +0 -1
  415. package/client/dist/assets/browser-CvXOJm-j.js +0 -1
  416. package/client/dist/assets/browser-DOrFh1c7.js +0 -1
  417. package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
  418. package/client/dist/assets/browser-LPgxvJu2.js +0 -1
  419. package/client/dist/assets/browser-eL-3MIEI.js +0 -1
  420. package/client/dist/assets/builder-0IWtosUm.js +0 -1
  421. package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
  422. package/client/dist/assets/builder-BnqaaBkT.js +0 -1
  423. package/client/dist/assets/builder-BxxfGgAO.js +0 -1
  424. package/client/dist/assets/builder-C5IIgWsq.js +0 -1
  425. package/client/dist/assets/builder-CHuahItu.js +0 -1
  426. package/client/dist/assets/builder-CjWosXL_.js +0 -1
  427. package/client/dist/assets/builder-CpCIOoKj.js +0 -1
  428. package/client/dist/assets/code-CHPkOkDS.js +0 -1
  429. package/client/dist/assets/commands-6tYQih5m.js +0 -1
  430. package/client/dist/assets/commands-BK2QinXN.js +0 -1
  431. package/client/dist/assets/commands-C2Nca76h.js +0 -1
  432. package/client/dist/assets/commands-CKdLihNX.js +0 -1
  433. package/client/dist/assets/commands-CYIBTAlc.js +0 -1
  434. package/client/dist/assets/commands-CjUtYJIM.js +0 -1
  435. package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
  436. package/client/dist/assets/commands-D_ZHYQug.js +0 -1
  437. package/client/dist/assets/common-6MLbD-jM.js +0 -1
  438. package/client/dist/assets/common-BKGEu4rm.js +0 -1
  439. package/client/dist/assets/common-BNbnv8cE.js +0 -1
  440. package/client/dist/assets/common-BqGWtDbt.js +0 -1
  441. package/client/dist/assets/common-DBtTzeYm.js +0 -1
  442. package/client/dist/assets/common-DEW3Nr8n.js +0 -1
  443. package/client/dist/assets/common-DHsv9N5d.js +0 -1
  444. package/client/dist/assets/common-mkrEcW-Y.js +0 -1
  445. package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
  446. package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
  447. package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
  448. package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
  449. package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
  450. package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
  451. package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
  452. package/client/dist/assets/dialog-CdWCb02j.js +0 -45
  453. package/client/dist/assets/dist-bVqfYmby.js +0 -1
  454. package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
  455. package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
  456. package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
  457. package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
  458. package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
  459. package/client/dist/assets/index-DIhMEAVw.js +0 -66
  460. package/client/dist/assets/index-DwygcU7s.css +0 -2
  461. package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
  462. package/client/dist/assets/mcp-C2AojZZN.js +0 -1
  463. package/client/dist/assets/mcp-D8BT78_L.js +0 -1
  464. package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
  465. package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
  466. package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
  467. package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
  468. package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
  469. package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
  470. package/client/dist/assets/narration-BGuiEDj3.js +0 -1
  471. package/client/dist/assets/narration-BuSoqRmv.js +0 -1
  472. package/client/dist/assets/narration-CB9t29fy.js +0 -1
  473. package/client/dist/assets/narration-CU3x8FHD.js +0 -1
  474. package/client/dist/assets/narration-CWeXhfP5.js +0 -1
  475. package/client/dist/assets/narration-DRaDnhT5.js +0 -1
  476. package/client/dist/assets/narration-DtEbQgCw.js +0 -1
  477. package/client/dist/assets/nav-ApJVg3xu.js +0 -1
  478. package/client/dist/assets/nav-BKNky1Bg.js +0 -1
  479. package/client/dist/assets/nav-C6vWWKYx.js +0 -1
  480. package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
  481. package/client/dist/assets/nav-CsmFBIDf.js +0 -1
  482. package/client/dist/assets/nav-DjkmQJjd.js +0 -1
  483. package/client/dist/assets/packet-B1IxaL5f.js +0 -1
  484. package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
  485. package/client/dist/assets/packet-By8fqYxW.js +0 -1
  486. package/client/dist/assets/packet-CZdvA44E.js +0 -1
  487. package/client/dist/assets/packet-CrjHRQCP.js +0 -1
  488. package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
  489. package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
  490. package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
  491. package/client/dist/assets/play-DNlrawbS.js +0 -1
  492. package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
  493. package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
  494. package/client/dist/assets/settings-C1-7cXzC.js +0 -1
  495. package/client/dist/assets/settings-CAFaT38b.js +0 -1
  496. package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
  497. package/client/dist/assets/settings-DHh7_IF5.js +0 -1
  498. package/client/dist/assets/settings-DQXAmdcv.js +0 -1
  499. package/client/dist/assets/settings-guzlhpup.js +0 -1
  500. package/client/dist/assets/settings-sZfTPMEH.js +0 -1
  501. package/client/dist/assets/setup-3jWaoVUO.js +0 -1
  502. package/client/dist/assets/setup-BjgRneb2.js +0 -1
  503. package/client/dist/assets/setup-BzFIjv-f.js +0 -1
  504. package/client/dist/assets/setup-C4VfQsxE.js +0 -1
  505. package/client/dist/assets/setup-DfUkpDbN.js +0 -1
  506. package/client/dist/assets/setup-ECe90Pmu.js +0 -1
  507. package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
  508. package/client/dist/assets/setup-jwfLMiM2.js +0 -1
  509. package/client/dist/assets/spending-EC9xqio-.js +0 -1
  510. package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
@@ -16,16 +16,12 @@ const agent_capability_1 = require("./agent-capability");
16
16
  function jsonRpcError(message, code = -32000) {
17
17
  return { jsonrpc: '2.0', error: { code, message }, id: null };
18
18
  }
19
- /**
20
- * Embedded MCP server manager. Mirrors the MobileGateway lifecycle shape
21
- * (isEnabledSetting / setEnabled / start / stop / status) but mounts on the
22
- * EXISTING Express app rather than binding a second port. Uses the SDK's
23
- * stateful streamable-HTTP transport (a transport per MCP session).
24
- */
19
+ const SESSION_IDLE_MS = 30 * 60 * 1000;
25
20
  class McpServerManager {
26
21
  _ctx;
27
22
  _version;
28
- _transports = {};
23
+ _sessions = new Map();
24
+ _pending = new Set();
29
25
  _toolCount;
30
26
  constructor(deps) {
31
27
  this._ctx = {
@@ -45,7 +41,7 @@ class McpServerManager {
45
41
  async setEnabled(enabled) {
46
42
  (0, desktop_db_1.setDesktopSetting)(this._ctx.desktopDb, mcp_tiers_1.MCP_ENABLED_KEY, enabled ? 'true' : 'false');
47
43
  if (!enabled)
48
- await this.stop();
44
+ await this.stop(true);
49
45
  this._ctx.broadcast({ type: 'mcp.state', enabled, running: enabled });
50
46
  return this.status();
51
47
  }
@@ -55,33 +51,53 @@ class McpServerManager {
55
51
  async start() {
56
52
  /* no-op: served on the shared app, gated by isEnabledSetting() */
57
53
  }
58
- /** Close all open MCP sessions. */
59
- async stop() {
60
- for (const sid of Object.keys(this._transports)) {
61
- try {
62
- await this._transports[sid].close();
63
- }
64
- catch {
65
- /* ignore */
66
- }
67
- delete this._transports[sid];
54
+ /** Shutdown/rotation closes everything; the external Settings toggle must
55
+ * preserve first-party turns governed by their independent permission tier. */
56
+ async stop(externalOnly = false) {
57
+ const sessions = [...new Set([...this._sessions.values(), ...this._pending])]
58
+ .filter((session) => !externalOnly || session.capabilityHash === null);
59
+ await Promise.allSettled(sessions.map((session) => this.closeSession(session)));
60
+ }
61
+ async closeSession(session) {
62
+ if (session.closed)
63
+ return;
64
+ this.forgetSession(session);
65
+ try {
66
+ await session.server.close();
68
67
  }
68
+ catch { /* best-effort teardown */ }
69
+ }
70
+ forgetSession(session) {
71
+ session.closed = true;
72
+ if (session.timer)
73
+ clearTimeout(session.timer);
74
+ session.unsubscribe?.();
75
+ this._pending.delete(session);
76
+ if (session.transport.sessionId)
77
+ this._sessions.delete(session.transport.sessionId);
78
+ }
79
+ touchSession(session, expiresAt) {
80
+ if (session.timer)
81
+ clearTimeout(session.timer);
82
+ session.timer = setTimeout(() => { void this.closeSession(session); }, Math.max(1, Math.min(SESSION_IDLE_MS, (expiresAt ?? Infinity) - Date.now())));
83
+ session.timer.unref?.();
69
84
  }
70
85
  status() {
71
86
  return {
72
87
  enabled: this.isEnabledSetting(),
73
88
  running: this.isEnabledSetting(),
74
- activeSessions: Object.keys(this._transports).length,
89
+ activeSessions: this._sessions.size,
75
90
  toolCount: this._toolCount,
76
91
  };
77
92
  }
78
93
  /** Build a fresh McpServer with the full tool + resource catalog registered. */
79
94
  buildServer() {
80
95
  const server = new mcp_js_1.McpServer({ name: 'specrails-desktop', version: this._version }, { capabilities: { logging: {}, tools: {}, resources: {} } });
96
+ const context = { ...this._ctx, sessionState: { activeProjectId: null } };
81
97
  for (const spec of (0, catalog_1.buildToolSpecs)()) {
82
- (0, types_1.registerTieredTool)(server, this._ctx, spec);
98
+ (0, types_1.registerTieredTool)(server, context, spec);
83
99
  }
84
- (0, resources_1.registerResources)(server, this._ctx);
100
+ (0, resources_1.registerResources)(server, context);
85
101
  return server;
86
102
  }
87
103
  /**
@@ -93,53 +109,83 @@ class McpServerManager {
93
109
  // The external Settings toggle gates third-party clients. Only a live,
94
110
  // server-minted per-turn capability identifies the in-app agent and may
95
111
  // bypass it; caller-authored context headers grant no authority.
96
- const isFirstPartyAgent = (0, agent_capability_1.verifyAgentCapability)(req.headers[agent_tier_1.AGENT_CAPABILITY_HEADER]) !== null;
97
- if (!this.isEnabledSetting() && !isFirstPartyAgent) {
112
+ const suppliedCapability = req.headers[agent_tier_1.AGENT_CAPABILITY_HEADER];
113
+ const capability = (0, agent_capability_1.verifyAgentCapability)(suppliedCapability);
114
+ if (suppliedCapability !== undefined && !capability) {
115
+ res.status(401).json(jsonRpcError('Agent capability expired or was revoked. Start a new mission turn.', -32001));
116
+ return;
117
+ }
118
+ if (!this.isEnabledSetting() && !capability) {
98
119
  res.status(404).json(jsonRpcError('MCP is disabled. Enable it in the Specrails app under Settings ▸ MCP.', -32004));
99
120
  return;
100
121
  }
101
- const sessionId = req.headers['mcp-session-id'];
122
+ const suppliedSession = req.headers['mcp-session-id'];
123
+ if (suppliedSession !== undefined && (typeof suppliedSession !== 'string' || !suppliedSession.trim())) {
124
+ res.status(400).json(jsonRpcError('Invalid mcp-session-id.'));
125
+ return;
126
+ }
127
+ const sessionId = suppliedSession;
128
+ const capabilityHash = capability && typeof suppliedCapability === 'string'
129
+ ? (0, crypto_1.createHash)('sha256').update(suppliedCapability.trim()).digest('hex') : null;
130
+ let session = sessionId ? this._sessions.get(sessionId) : undefined;
131
+ if (sessionId && !session) {
132
+ // Streamable HTTP clients recognize 404 as an expired session and must
133
+ // initialize again. 400 left SDK clients stuck with the stale id forever.
134
+ res.status(404).json(jsonRpcError('Unknown or expired MCP session. Reinitialize.'));
135
+ return;
136
+ }
137
+ if (session && session.capabilityHash !== capabilityHash) {
138
+ res.status(403).json(jsonRpcError('MCP session belongs to a different agent turn or client. Reinitialize.'));
139
+ return;
140
+ }
102
141
  try {
103
142
  if (req.method === 'POST') {
104
- let transport = sessionId ? this._transports[sessionId] : undefined;
105
- if (!transport) {
106
- if (sessionId) {
107
- res.status(400).json(jsonRpcError('Unknown or expired MCP session. Reinitialize.'));
108
- return;
109
- }
143
+ if (!session) {
110
144
  if (!(0, types_js_1.isInitializeRequest)(req.body)) {
111
145
  res.status(400).json(jsonRpcError('Missing mcp-session-id and body is not an initialize request.'));
112
146
  return;
113
147
  }
114
- transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
148
+ const server = this.buildServer();
149
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
115
150
  sessionIdGenerator: () => (0, crypto_1.randomUUID)(),
116
151
  onsessioninitialized: (sid) => {
117
- this._transports[sid] = transport;
152
+ this._pending.delete(created);
153
+ this._sessions.set(sid, created);
118
154
  },
119
155
  });
120
- transport.onclose = () => {
121
- const sid = transport?.sessionId;
122
- if (sid && this._transports[sid])
123
- delete this._transports[sid];
124
- };
125
- const server = this.buildServer();
156
+ const created = { transport, server, capabilityHash, closed: false };
157
+ session = created;
158
+ this._pending.add(created);
159
+ transport.onclose = () => { this.forgetSession(created); };
160
+ if (capability && typeof suppliedCapability === 'string') {
161
+ created.unsubscribe = (0, agent_capability_1.onAgentCapabilityRevoked)(suppliedCapability, () => { void this.closeSession(created); });
162
+ }
126
163
  await server.connect(transport);
127
164
  }
128
- await transport.handleRequest(req, res, req.body);
165
+ if (session.closed) {
166
+ res.status(503).json(jsonRpcError('MCP session closed during initialization. Reconnect.'));
167
+ return;
168
+ }
169
+ this.touchSession(session, capability?.expiresAt);
170
+ await session.transport.handleRequest(req, res, req.body);
171
+ if (!session.transport.sessionId)
172
+ await this.closeSession(session);
129
173
  return;
130
174
  }
131
175
  if (req.method === 'GET' || req.method === 'DELETE') {
132
- const transport = sessionId ? this._transports[sessionId] : undefined;
133
- if (!transport) {
134
- res.status(400).json(jsonRpcError('Missing or unknown mcp-session-id.'));
176
+ if (!session) {
177
+ res.status(400).json(jsonRpcError('Missing mcp-session-id.'));
135
178
  return;
136
179
  }
137
- await transport.handleRequest(req, res);
180
+ this.touchSession(session, capability?.expiresAt);
181
+ await session.transport.handleRequest(req, res);
138
182
  return;
139
183
  }
140
184
  res.status(405).json(jsonRpcError('Method not allowed.'));
141
185
  }
142
186
  catch (err) {
187
+ if (session && !session.transport.sessionId)
188
+ await this.closeSession(session);
143
189
  if (!res.headersSent) {
144
190
  res.status(500).json(jsonRpcError(err instanceof Error ? err.message : 'Internal MCP error', -32603));
145
191
  }
@@ -7,26 +7,27 @@ exports.tierLabel = tierLabel;
7
7
  exports.tierRefusalMessage = tierRefusalMessage;
8
8
  const desktop_db_1 = require("../desktop-db");
9
9
  exports.MCP_TIERS = ['read', 'write', 'ai-spawn', 'destructive'];
10
- /** desktop_settings keys for the opt-in tiers (read is implicit). */
10
+ /** desktop_settings keys for the opt-out tiers (all on by default; read is implicit). */
11
11
  exports.TIER_SETTING_KEY = {
12
12
  write: 'mcp_tier_write',
13
13
  'ai-spawn': 'mcp_tier_ai_spawn',
14
14
  destructive: 'mcp_tier_destructive',
15
15
  };
16
16
  exports.MCP_ENABLED_KEY = 'mcp_enabled';
17
- /** True when MCP itself is enabled (default false). */
17
+ /** True when MCP itself is enabled (default TRUE — only an explicit 'false' disables). */
18
18
  function isMcpEnabled(db) {
19
- return (0, desktop_db_1.getDesktopSetting)(db, exports.MCP_ENABLED_KEY) === 'true';
19
+ return (0, desktop_db_1.getDesktopSetting)(db, exports.MCP_ENABLED_KEY) !== 'false';
20
20
  }
21
21
  /**
22
22
  * True when a tool of the given tier may run right now. Read is always allowed
23
23
  * (its availability is governed by MCP being enabled, checked at the transport
24
- * gate). The other tiers require their opt-in flag to be 'true'.
24
+ * gate). The other tiers default ON and are disabled only when the user has
25
+ * explicitly stored 'false' for them (opt-out).
25
26
  */
26
27
  function isTierEnabled(db, tier) {
27
28
  if (tier === 'read')
28
29
  return true;
29
- return (0, desktop_db_1.getDesktopSetting)(db, exports.TIER_SETTING_KEY[tier]) === 'true';
30
+ return (0, desktop_db_1.getDesktopSetting)(db, exports.TIER_SETTING_KEY[tier]) !== 'false';
30
31
  }
31
32
  /** Human-readable label for a tier, used in refusal messages. */
32
33
  function tierLabel(tier) {
@@ -84,8 +84,10 @@ function getMcpToken() {
84
84
  */
85
85
  function regenerateMcpToken() {
86
86
  const next = generateToken();
87
+ // Commit the file before switching the live credential. If persistence
88
+ // fails, bridges must still authenticate with the existing on-disk token.
89
+ (0, secure_token_file_1.writePrivateTextFile)(tokenPath(), next);
87
90
  _mcpToken = next;
88
- persist(next);
89
91
  return next;
90
92
  }
91
93
  /** Extracts a bearer token from `Authorization: Bearer <t>` or `X-Desktop-Token`. */
@@ -4,6 +4,7 @@ exports.registerResources = registerResources;
4
4
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
5
  const guide_1 = require("./guide");
6
6
  const projects_1 = require("./tools/projects");
7
+ const desktop_db_1 = require("../desktop-db");
7
8
  // Read-only state exposed as MCP resources. Read-tier only (resources are never
8
9
  // mutating). Per-project resource templates (tickets, rails, analytics) are
9
10
  // added in subsequent phases.
@@ -12,13 +13,13 @@ function registerResources(server, ctx) {
12
13
  contents: [{ uri: uri.href, text: guide_1.SPECRAILS_GUIDE }],
13
14
  }));
14
15
  server.registerResource('projects', 'specrails://projects', { title: 'Projects', description: 'All registered Specrails projects.', mimeType: 'application/json' }, async (uri) => ({
15
- contents: [{ uri: uri.href, text: JSON.stringify(ctx.registry.listContexts().map((c) => (0, projects_1.serializeProject)(c.project)), null, 2) }],
16
+ contents: [{ uri: uri.href, text: JSON.stringify((0, desktop_db_1.listProjects)(ctx.desktopDb).map(projects_1.serializeProject), null, 2) }],
16
17
  }));
17
18
  server.registerResource('project', new mcp_js_1.ResourceTemplate('specrails://projects/{projectId}', { list: undefined }), { title: 'Project', description: 'A single project by id.', mimeType: 'application/json' }, async (uri, variables) => {
18
19
  const projectId = String(variables.projectId);
19
- const pc = ctx.registry.getContext(projectId);
20
- if (!pc)
20
+ const project = (0, desktop_db_1.getProject)(ctx.desktopDb, projectId);
21
+ if (!project)
21
22
  throw new Error(`Unknown projectId "${projectId}".`);
22
- return { contents: [{ uri: uri.href, text: JSON.stringify((0, projects_1.serializeProject)(pc.project), null, 2) }] };
23
+ return { contents: [{ uri: uri.href, text: JSON.stringify((0, projects_1.serializeProject)(project), null, 2) }] };
23
24
  });
24
25
  }
@@ -19,6 +19,8 @@ const analytics_1 = require("./analytics");
19
19
  const git_1 = require("./git");
20
20
  const support_1 = require("./support");
21
21
  const env_1 = require("./env");
22
+ const context_1 = require("./context");
23
+ const mission_1 = require("./mission");
22
24
  /**
23
25
  * The full MCP tool catalog. Domain-facade tools each expose an `action` enum
24
26
  * and call the app's REST API over loopback (see apiCall). Meta tools receive a
@@ -28,6 +30,8 @@ function buildToolSpecs() {
28
30
  const specs = [];
29
31
  // App-level + meta + watch
30
32
  specs.push(...(0, projects_1.projectsTools)());
33
+ specs.push(...(0, context_1.contextTools)());
34
+ specs.push(...(0, mission_1.missionTools)());
31
35
  specs.push(...(0, app_settings_1.appTools)());
32
36
  specs.push((0, watch_1.watchTool)());
33
37
  // Per-project domains
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.codeTools = codeTools;
4
4
  const zod_1 = require("zod");
5
5
  const types_1 = require("./types");
6
+ const project_repositories_1 = require("../../project-repositories");
6
7
  /**
7
8
  * Read-only Code Explorer surface for a project: the AI-touched file tree,
8
9
  * file contents + plain-language AI summaries, "touched by AI" provenance, and
@@ -23,29 +24,47 @@ function codeTools() {
23
24
  name: 'specrails_code',
24
25
  title: 'Code Explorer',
25
26
  description: 'Read-only browse a project\'s source through the Code Explorer: list the file tree ' +
26
- '(default scoped to AI-touched files, optional provenance rollup), read a file with its ' +
27
- 'AI summary + staleness + provenance, fetch a stored summary alone, regenerate a file\'s ' +
28
- 'AI summary (cost-incurring, async), list "touched by AI" provenance rows, and fetch the ' +
29
- 'stored unified-diff a job applied to a file. ' +
30
- 'Actions: tree, read_file, summary, regenerate_summary (ai-spawn — spawns an AI CLI, ' +
27
+ '(default scoped to AI-touched files, optional provenance rollup), locate a file by name / ' +
28
+ 'path suffix, read a bounded file range with a stable content hash, fetch a stored ' +
29
+ 'summary alone, regenerate a file\'s AI summary (cost-incurring, async), list "touched by ' +
30
+ 'AI" provenance rows, and fetch the stored unified-diff a job applied to a file. ' +
31
+ 'Actions: tree, find (name / path-suffix / fragment → ranked project-relative paths; use it ' +
32
+ 'when read_file 404s — a path copied from a stack trace or import is usually relative to a ' +
33
+ 'subdirectory), search (literal source content, scoped path and explicit scan budgets), ' +
34
+ 'read_file (bounded line/column pages with hash and continuation, default 200 lines), summary, regenerate_summary (ai-spawn — spawns an AI CLI, ' +
31
35
  'incurs cost, returns 202 then completes over WS), provenance, diff. ' +
32
36
  'Requires SPECRAILS_CODE_EXPLORER (default on); the /code prefix 404s when disabled.',
33
37
  hintTier: 'read',
34
38
  tier: (a) => (a.action === 'regenerate_summary' ? 'ai-spawn' : 'read'),
35
39
  inputSchema: {
36
40
  action: zod_1.z
37
- .enum(['tree', 'read_file', 'summary', 'regenerate_summary', 'provenance', 'diff'])
41
+ .enum(['tree', 'find', 'search', 'read_file', 'summary', 'regenerate_summary', 'provenance', 'diff'])
38
42
  .describe('Operation to perform'),
39
43
  projectId: zod_1.z.string().optional().describe('Project id (defaults to the active project)'),
44
+ repositoryId: zod_1.z.string().min(1).optional().describe('Repository membership ID. Required for individual tree/file/summary/provenance/diff actions in multi-repository projects. Omit for find/search to discover across members under one shared budget.'),
40
45
  path: zod_1.z
41
46
  .string()
42
47
  .optional()
43
48
  .describe('Project-relative file path. Required for read_file/summary/regenerate_summary/diff; ' +
44
- 'optional narrowing filter for provenance.'),
49
+ 'optional directory/file prefix for search or narrowing filter for provenance.'),
50
+ file: zod_1.z
51
+ .string()
52
+ .optional()
53
+ .describe('Compatibility alias for path (accepted for read_file/summary/regenerate_summary/diff)'),
54
+ query: zod_1.z
55
+ .string()
56
+ .optional()
57
+ .describe('search: literal single-line source text (not regex). find: file name, path suffix (e.g. components/detail/LessonView.tsx) or fragment ' +
58
+ 'to locate, case-insensitive. Falls back to path/file when omitted.'),
45
59
  filter: zod_1.z
46
60
  .enum(['touched-by-ai', 'all'])
47
61
  .optional()
48
62
  .describe('tree only: scope to AI-touched files (default) or the whole repo'),
63
+ caseSensitive: zod_1.z.boolean().optional().describe('search only: match exact character case (default false)'),
64
+ startLine: zod_1.z.number().int().positive().optional().describe('read_file only: first line, 1-based (default 1)'),
65
+ endLine: zod_1.z.number().int().positive().optional().describe('read_file only: last line inclusive (default startLine + 199; at most 500 lines returned)'),
66
+ startColumn: zod_1.z.number().int().positive().optional().describe('read_file only: resume at nextColumn when a long line was split by the character budget (default 1)'),
67
+ expectedHash: zod_1.z.string().regex(/^[a-f0-9]{64}$/i).optional().describe('read_file only: hash from the previous page/search; rejects changed files so pages cannot silently mix revisions'),
49
68
  withProvenance: zod_1.z
50
69
  .boolean()
51
70
  .optional()
@@ -54,6 +73,13 @@ function codeTools() {
54
73
  .string()
55
74
  .optional()
56
75
  .describe('tree only: opaque pagination cursor from a prior response\'s nextCursor'),
76
+ limit: zod_1.z
77
+ .number()
78
+ .int()
79
+ .positive()
80
+ .max(500)
81
+ .optional()
82
+ .describe('tree: entries per page (default 200, maximum 500); find: matches (default 20, max 50); search: matching lines (default 30, max 100)'),
57
83
  ticketId: zod_1.z
58
84
  .number()
59
85
  .int()
@@ -71,14 +97,47 @@ function codeTools() {
71
97
  'incurs cost beyond the configured limit.'),
72
98
  },
73
99
  async handler(ctx, args) {
74
- const base = (0, types_1.projectPath)(ctx, args.projectId);
75
100
  const action = args.action;
101
+ const projectId = args.projectId;
102
+ const repositoryId = args.repositoryId;
103
+ if (repositoryId === undefined && ['find', 'search'].includes(action) && (0, project_repositories_1.getProjectRepositories)((0, types_1.requireProject)(ctx, projectId).project).length > 1) {
104
+ const query = (args.query ?? args.path ?? args.file);
105
+ if (!query?.trim())
106
+ throw new Error(`Action "${action}" requires a "query".`);
107
+ const qs = new URLSearchParams({ kind: action, q: query });
108
+ if (args.limit !== undefined)
109
+ qs.set('limit', String(args.limit));
110
+ if (action === 'search' && args.path)
111
+ qs.set('path', String(args.path));
112
+ if (args.caseSensitive === true)
113
+ qs.set('caseSensitive', 'true');
114
+ return (0, types_1.apiCall)(ctx, 'GET', `${(0, types_1.projectPath)(ctx, projectId)}/code/discover?${qs}`);
115
+ }
116
+ const base = (0, types_1.repositoryPath)(ctx, projectId, repositoryId);
76
117
  const requirePath = () => {
77
- const p = args.path;
118
+ const p = (args.path ?? args.file);
78
119
  if (!p || p.trim() === '')
79
120
  throw new Error(`Action "${action}" requires a "path".`);
80
121
  return p;
81
122
  };
123
+ // A bare `→ 404: {"error":"file not found"}` sent callers into a
124
+ // ToolSearch/retry loop: the path they hold is usually right, just
125
+ // relative to a subdirectory. Point them at `find` instead of leaving
126
+ // them to page through `tree`.
127
+ const withNotFoundHint = async (p, run) => {
128
+ try {
129
+ return await run();
130
+ }
131
+ catch (err) {
132
+ const msg = err instanceof Error ? err.message : String(err);
133
+ if (!/→ 404: .*file not found/.test(msg))
134
+ throw err;
135
+ const base = p.replace(/\\/g, '/').replace(/\/+$/, '').split('/').pop() || p;
136
+ throw new Error(`${msg}. Paths are relative to the project root and "${p}" may live under a subdirectory ` +
137
+ '(a path copied from a stack trace or import usually does). Locate it with ' +
138
+ `specrails_code(action: "find", query: ${JSON.stringify(base)}) and retry with the returned path.`);
139
+ }
140
+ };
82
141
  switch (action) {
83
142
  case 'tree': {
84
143
  const qs = new URLSearchParams();
@@ -88,20 +147,72 @@ function codeTools() {
88
147
  qs.set('withProvenance', '1');
89
148
  if (args.cursor)
90
149
  qs.set('cursor', args.cursor);
150
+ qs.set('limit', String(typeof args.limit === 'number' ? args.limit : 200));
91
151
  if (typeof args.ticketId === 'number')
92
152
  qs.set('ticketId', String(args.ticketId));
93
153
  if (args.jobId)
94
154
  qs.set('jobId', args.jobId);
95
155
  const suffix = qs.toString() ? `?${qs.toString()}` : '';
96
- return (0, types_1.apiCall)(ctx, 'GET', `${base}/code/tree${suffix}`);
156
+ const result = await (0, types_1.apiCall)(ctx, 'GET', `${base}/code/tree${suffix}`);
157
+ if (args.withProvenance === true || !Array.isArray(result.entries))
158
+ return result;
159
+ return {
160
+ ...result,
161
+ entries: result.entries.map((entry) => {
162
+ const item = entry;
163
+ return { path: item.path, kind: item.kind, ...(item.repositoryId ? { repositoryId: item.repositoryId } : {}) };
164
+ }),
165
+ hint: result.nextCursor
166
+ ? 'More files are available. Call tree again with this nextCursor; prefer targeted read_file calls.'
167
+ : 'Tree complete. Prefer targeted read_file calls.',
168
+ };
169
+ }
170
+ case 'find': {
171
+ const q = (args.query ?? args.path ?? args.file)?.trim();
172
+ if (!q)
173
+ throw new Error('Action "find" requires a "query" (file name, path suffix or fragment).');
174
+ const qs = new URLSearchParams({ q });
175
+ qs.set('limit', String(typeof args.limit === 'number' ? Math.min(args.limit, 50) : 20));
176
+ const r = (await (0, types_1.apiCall)(ctx, 'GET', `${base}/code/find?${qs.toString()}`));
177
+ const matches = Array.isArray(r.matches) ? r.matches : [];
178
+ return {
179
+ ...r,
180
+ hint: matches.length
181
+ ? 'Call read_file with one of these project-relative paths (best match first).'
182
+ : r.truncated
183
+ ? 'Partial scan found no matches; this does not prove absence. Narrow the query and retry or inspect the project tree.'
184
+ : 'No file matches. The scan skips build/dependency trees, dot-directories and gitignored paths; ' +
185
+ 'try a shorter fragment or the bare file name.',
186
+ };
187
+ }
188
+ case 'search': {
189
+ const query = args.query;
190
+ if (!query?.trim())
191
+ throw new Error('Action "search" requires a literal source-text "query".');
192
+ if (typeof args.limit === 'number' && args.limit > 100)
193
+ throw new Error('Action "search" limit must be at most 100.');
194
+ const qs = new URLSearchParams({ q: query, limit: String(args.limit ?? 30), caseSensitive: String(args.caseSensitive === true) });
195
+ if (args.path)
196
+ qs.set('path', args.path);
197
+ return (0, types_1.apiCall)(ctx, 'GET', `${base}/code/search?${qs.toString()}`);
97
198
  }
98
199
  case 'read_file': {
99
- const qs = new URLSearchParams({ path: requirePath() });
100
- return (0, types_1.apiCall)(ctx, 'GET', `${base}/code/file?${qs.toString()}`);
200
+ const p = requirePath();
201
+ const startLine = typeof args.startLine === 'number' ? args.startLine : 1;
202
+ const endLine = typeof args.endLine === 'number' ? args.endLine : startLine + 199;
203
+ if (endLine < startLine)
204
+ throw new Error('endLine must be greater than or equal to startLine.');
205
+ const qs = new URLSearchParams({ path: p, startLine: String(startLine), endLine: String(endLine) });
206
+ if (args.startColumn !== undefined)
207
+ qs.set('startColumn', String(args.startColumn));
208
+ if (args.expectedHash)
209
+ qs.set('expectedHash', args.expectedHash);
210
+ return withNotFoundHint(p, () => (0, types_1.apiCall)(ctx, 'GET', `${base}/code/file?${qs.toString()}`));
101
211
  }
102
212
  case 'summary': {
103
- const qs = new URLSearchParams({ path: requirePath() });
104
- return (0, types_1.apiCall)(ctx, 'GET', `${base}/code/summary?${qs.toString()}`);
213
+ const p = requirePath();
214
+ const qs = new URLSearchParams({ path: p });
215
+ return withNotFoundHint(p, () => (0, types_1.apiCall)(ctx, 'GET', `${base}/code/summary?${qs.toString()}`));
105
216
  }
106
217
  case 'regenerate_summary': {
107
218
  const qs = new URLSearchParams({ path: requirePath() });