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
@@ -3,14 +3,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AGENT_SEARCH_INDEX_DDL = void 0;
6
7
  exports.setProjectProvidersMirror = setProjectProvidersMirror;
7
8
  exports.getDesktopDbPath = getDesktopDbPath;
9
+ exports.rebuildAgentSearchIndex = rebuildAgentSearchIndex;
8
10
  exports.initDesktopDb = initDesktopDb;
9
11
  exports.listProjects = listProjects;
10
12
  exports.getProject = getProject;
11
13
  exports.getProjectBySlug = getProjectBySlug;
12
14
  exports.getProjectByPath = getProjectByPath;
13
15
  exports.addProject = addProject;
16
+ exports.listProjectRepositories = listProjectRepositories;
17
+ exports.addProjectRepository = addProjectRepository;
18
+ exports.updateProjectRepository = updateProjectRepository;
19
+ exports.removeProjectRepository = removeProjectRepository;
14
20
  exports.removeProject = removeProject;
15
21
  exports.touchProject = touchProject;
16
22
  exports.getDesktopSetting = getDesktopSetting;
@@ -38,6 +44,9 @@ const path_1 = __importDefault(require("path"));
38
44
  const os_1 = __importDefault(require("os"));
39
45
  const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
40
46
  const secure_fs_1 = require("./util/secure-fs");
47
+ const sqlite_migrations_1 = require("./util/sqlite-migrations");
48
+ const crypto_1 = require("crypto");
49
+ const project_repositories_1 = require("./project-repositories");
41
50
  function parseProviders(raw, primary) {
42
51
  if (raw) {
43
52
  try {
@@ -63,9 +72,11 @@ function setProjectProvidersMirror(fn) {
63
72
  _detectedMirrorSupplier = fn;
64
73
  }
65
74
  const PRIMARY_PREFERENCE = ['claude', 'codex', 'gemini', 'kimi'];
66
- function mapProjectRow(raw) {
75
+ function mapProjectRow(db, raw) {
67
76
  if (!raw)
68
77
  return undefined;
78
+ const repositories = listProjectRepositories(db, raw.id);
79
+ const membership = { repositories, primaryRepositoryId: repositories.find((repository) => repository.isPrimary)?.id };
69
80
  const provider = (raw.provider ?? 'claude');
70
81
  const stored = parseProviders(raw.providers, provider);
71
82
  const detected = _detectedMirrorSupplier?.();
@@ -74,9 +85,9 @@ function mapProjectRow(raw) {
74
85
  let primary = mirrored.includes(provider) ? provider : undefined;
75
86
  if (!primary)
76
87
  primary = PRIMARY_PREFERENCE.find((p) => mirrored.includes(p)) ?? mirrored[0];
77
- return { ...raw, provider: primary, providers: [...mirrored] };
88
+ return { ...raw, ...membership, provider: primary, providers: [...mirrored] };
78
89
  }
79
- return { ...raw, provider, providers: stored };
90
+ return { ...raw, ...membership, provider, providers: stored };
80
91
  }
81
92
  // ─── Desktop DB path ──────────────────────────────────────────────────────────
82
93
  function getDesktopDbPath() {
@@ -102,9 +113,8 @@ function migrateLegacyDbFile(dbPath) {
102
113
  // is renamed first, a crash in the (sub-ms) window between renames leaves
103
114
  // `desktop.sqlite` with no adjacent WAL -> un-checkpointed commits in the
104
115
  // orphaned `hub.sqlite-wal` are silently discarded on next open. Moving the
105
- // sidecars first means an interrupted migration leaves the WAL/SHM still
106
- // matched to the legacy main file (the new file does not exist yet, so the
107
- // guard above re-runs the whole migration on the next launch).
116
+ // sidecars first keeps the new main file from being opened without its
117
+ // WAL. An interrupted migration resumes before opening either main file.
108
118
  for (const suffix of ['-wal', '-shm']) {
109
119
  if (fs_1.default.existsSync(legacyPath + suffix)) {
110
120
  fs_1.default.renameSync(legacyPath + suffix, dbPath + suffix);
@@ -114,12 +124,62 @@ function migrateLegacyDbFile(dbPath) {
114
124
  }
115
125
  }
116
126
  catch (err) {
117
- console.warn('[desktop-db] could not migrate legacy hub.sqlite:', err);
127
+ // A failed rename must never fall through into creating an empty catalog.
128
+ // Tolerate only a second startup that already completed the same rename.
129
+ if (!fs_1.default.existsSync(legacyPath) && fs_1.default.existsSync(dbPath))
130
+ return;
131
+ const detail = err instanceof Error ? err.message : String(err);
132
+ throw new Error(`Could not migrate the existing project database from hub.sqlite to desktop.sqlite: ${detail}`, { cause: err });
118
133
  }
119
134
  }
120
135
  function getProjectDbPath(slug) {
121
136
  return path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug, 'jobs.sqlite');
122
137
  }
138
+ // ─── Mission search index ─────────────────────────────────────────────────────
139
+ /**
140
+ * DDL for the mission full-text index (migration 24). Kept as a constant so
141
+ * `rebuildAgentSearchIndex` and tests can reason about the same schema. The
142
+ * column order matters: `content` is column 3, which is what `snippet()` and
143
+ * `highlight()` address in `agent-store.ts`.
144
+ */
145
+ exports.AGENT_SEARCH_INDEX_DDL = `
146
+ CREATE VIRTUAL TABLE IF NOT EXISTS agent_messages_fts USING fts5(
147
+ conversation_id UNINDEXED,
148
+ message_id UNINDEXED,
149
+ role UNINDEXED,
150
+ content,
151
+ tokenize='trigram remove_diacritics 1'
152
+ );
153
+ CREATE TRIGGER IF NOT EXISTS agent_messages_fts_ai AFTER INSERT ON agent_messages BEGIN
154
+ INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
155
+ VALUES (new.conversation_id, new.id, new.role, new.content);
156
+ END;
157
+ CREATE TRIGGER IF NOT EXISTS agent_messages_fts_ad AFTER DELETE ON agent_messages BEGIN
158
+ DELETE FROM agent_messages_fts WHERE message_id = old.id;
159
+ END;
160
+ CREATE TRIGGER IF NOT EXISTS agent_messages_fts_au AFTER UPDATE OF content, role, conversation_id ON agent_messages BEGIN
161
+ DELETE FROM agent_messages_fts WHERE message_id = old.id;
162
+ INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
163
+ VALUES (new.conversation_id, new.id, new.role, new.content);
164
+ END;
165
+ `;
166
+ /**
167
+ * Re-derive the whole mission search index from `agent_messages`. Idempotent
168
+ * and cheap (the corpus is a few MB at most); the migration calls it once, and
169
+ * any future bulk writer that bypasses SQL triggers (an import, a repair tool)
170
+ * must call it afterwards. Runs in one transaction so a reader never observes
171
+ * a half-empty index.
172
+ */
173
+ function rebuildAgentSearchIndex(db) {
174
+ const tx = db.transaction(() => {
175
+ db.exec('DELETE FROM agent_messages_fts');
176
+ db.exec(`
177
+ INSERT INTO agent_messages_fts (conversation_id, message_id, role, content)
178
+ SELECT conversation_id, id, role, content FROM agent_messages
179
+ `);
180
+ });
181
+ tx();
182
+ }
123
183
  // ─── Schema migrations ────────────────────────────────────────────────────────
124
184
  // NOTE: existing migrations below intentionally keep the legacy `hub_settings`
125
185
  // table name — they are append-only history and must stay byte-identical for
@@ -133,8 +193,6 @@ function applyDesktopMigrations(db) {
133
193
  applied_at TEXT NOT NULL DEFAULT (datetime('now'))
134
194
  );
135
195
  `);
136
- const appliedVersions = new Set(db.prepare('SELECT version FROM schema_migrations').all()
137
- .map((r) => r.version));
138
196
  const migrations = [
139
197
  // Migration 1: projects and hub_settings tables
140
198
  () => {
@@ -484,23 +542,102 @@ function applyDesktopMigrations(db) {
484
542
  CREATE INDEX IF NOT EXISTS idx_blueprint_messages_conv ON blueprint_messages(conversation_id, created_at);
485
543
  `);
486
544
  },
545
+ // 23: durable Builder snapshots (harden-project-builder-snapshots). The
546
+ // blueprint used to live ONLY in client memory — the persisted assistant
547
+ // rows were stripped of the blueprint-draft JSON — so a closed panel, a
548
+ // refresh, or a silently rejected block lost everything the model had
549
+ // produced (and billed). Now every ACCEPTED snapshot is stored on the
550
+ // conversation (normalized + exact raw payload), the model's unstripped
551
+ // reply is kept per message for forensics/re-parsing, the last rejection
552
+ // is recorded so a manual repair can resume after a restart, and a commit
553
+ // links the conversation to the project it created (so the resume list
554
+ // only offers unfinished blueprints).
555
+ () => {
556
+ db.exec(`
557
+ ALTER TABLE blueprint_conversations ADD COLUMN blueprint_json TEXT;
558
+ ALTER TABLE blueprint_conversations ADD COLUMN raw_blueprint_json TEXT;
559
+ ALTER TABLE blueprint_conversations ADD COLUMN snapshot_updated_at TEXT;
560
+ ALTER TABLE blueprint_conversations ADD COLUMN snapshot_issue_json TEXT;
561
+ ALTER TABLE blueprint_conversations ADD COLUMN committed_project_id TEXT;
562
+ ALTER TABLE blueprint_messages ADD COLUMN raw_content TEXT;
563
+ `);
564
+ },
565
+ // 24: mission search (search-missions-in-palette). A standard FTS5 table
566
+ // (NOT external-content: `agent_messages` has a TEXT primary key, so its
567
+ // rowid is implicit and a VACUUM may renumber it — an external-content
568
+ // index keyed on that rowid would silently desync) carrying its own copy of
569
+ // every message body plus UNINDEXED conversation/message/role columns so a
570
+ // search never has to join back. The trigram tokenizer gives substring
571
+ // semantics ("etris" finds "Tetris") with an index and `snippet()`;
572
+ // `remove_diacritics 1` makes "mision" find "misión". Three triggers keep
573
+ // it in sync so every existing writer stays untouched; the one-off rebuild
574
+ // indexes the history already on disk.
575
+ () => {
576
+ db.exec(exports.AGENT_SEARCH_INDEX_DDL);
577
+ rebuildAgentSearchIndex(db);
578
+ },
579
+ // 25: Builder decision cards (premium-milestone-progress follow-up): a user
580
+ // turn sent from a one-click card ("surprise me", "approve") keeps its
581
+ // intent so the thread renders the settled decision card in place of the
582
+ // prompt bubble — across resumes and locale switches.
583
+ () => {
584
+ db.exec(`ALTER TABLE blueprint_messages ADD COLUMN intent TEXT;`);
585
+ },
586
+ // 26: logical projects retain their primary identity and one shared backlog.
587
+ () => {
588
+ db.exec(`
589
+ CREATE TABLE project_repositories (
590
+ id TEXT PRIMARY KEY,
591
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
592
+ name TEXT NOT NULL,
593
+ path TEXT NOT NULL,
594
+ canonical_key TEXT NOT NULL,
595
+ git_identity TEXT,
596
+ is_primary INTEGER NOT NULL DEFAULT 0 CHECK (is_primary IN (0, 1)),
597
+ kind TEXT NOT NULL CHECK (kind IN ('git', 'folder')),
598
+ integration_branch TEXT,
599
+ added_at TEXT NOT NULL DEFAULT (datetime('now')),
600
+ UNIQUE(project_id, canonical_key)
601
+ );
602
+ CREATE UNIQUE INDEX project_repositories_primary ON project_repositories(project_id) WHERE is_primary = 1;
603
+ CREATE UNIQUE INDEX project_repositories_git_identity ON project_repositories(project_id, git_identity) WHERE git_identity IS NOT NULL;
604
+ `);
605
+ const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, is_primary, kind, added_at) VALUES (?, ?, ?, ?, ?, 1, ?, ?)`);
606
+ for (const project of db.prepare('SELECT id, name, path, added_at FROM projects').all()) {
607
+ insert.run(`primary-${project.id}`, project.id, project.name, project.path, (0, project_repositories_1.repositoryPathKey)((0, project_repositories_1.canonicalRepositoryPath)(project.path)), (0, project_repositories_1.legacyRepositoryKind)(project.path), project.added_at);
608
+ }
609
+ },
610
+ // 27: durable mission inputs. Pending corrections survive a process restart
611
+ // as visible interrupted user messages, without silently replaying actions.
612
+ () => {
613
+ db.exec(`
614
+ CREATE TABLE agent_inputs (
615
+ id TEXT PRIMARY KEY,
616
+ conversation_id TEXT NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE,
617
+ queue_id TEXT NOT NULL,
618
+ text TEXT NOT NULL,
619
+ options_json TEXT NOT NULL DEFAULT '{}',
620
+ enqueue_payload TEXT NOT NULL,
621
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'delivered', 'cancelled', 'interrupted')),
622
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
623
+ message_id TEXT REFERENCES agent_messages(id) ON DELETE SET NULL,
624
+ UNIQUE(conversation_id, queue_id)
625
+ );
626
+ CREATE INDEX idx_agent_inputs_pending ON agent_inputs(conversation_id, created_at) WHERE status = 'pending';
627
+ CREATE UNIQUE INDEX idx_agent_inputs_message ON agent_inputs(message_id) WHERE message_id IS NOT NULL;
628
+ `);
629
+ },
630
+ // 28: provider receipt is independent of transcript settlement. Existing
631
+ // delivered inputs were accepted, but have no evidence of being read.
632
+ () => {
633
+ db.exec(`
634
+ ALTER TABLE agent_inputs ADD COLUMN receipt TEXT NOT NULL DEFAULT 'sent'
635
+ CHECK (receipt IN ('sent', 'received', 'read'));
636
+ UPDATE agent_inputs SET receipt = 'received' WHERE status = 'delivered';
637
+ `);
638
+ },
487
639
  ];
488
- for (let i = 0; i < migrations.length; i++) {
489
- const version = i + 1;
490
- if (!appliedVersions.has(version)) {
491
- // Run each migration body and its version INSERT atomically (mirrors
492
- // db.ts applyMigrations). SQLite DDL is transactional, so a crash/failure
493
- // mid-migration rolls back the whole body instead of leaving a half-applied
494
- // schema with the version unrecorded — which previously re-ran a bare
495
- // `ALTER TABLE ADD COLUMN` on next startup and bricked app launch forever
496
- // with 'duplicate column name'.
497
- const tx = db.transaction(() => {
498
- migrations[i]();
499
- db.prepare('INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)').run(version);
500
- });
501
- tx();
502
- }
503
- }
640
+ (0, sqlite_migrations_1.applyNumberedMigrations)(db, migrations);
504
641
  }
505
642
  // ─── Public API ───────────────────────────────────────────────────────────────
506
643
  function initDesktopDb(dbPath = getDesktopDbPath()) {
@@ -511,29 +648,35 @@ function initDesktopDb(dbPath = getDesktopDbPath()) {
511
648
  // Rebrand compat: pick up a pre-rename hub.sqlite before opening.
512
649
  migrateLegacyDbFile(dbPath);
513
650
  const db = new better_sqlite3_1.default(dbPath);
514
- db.pragma('journal_mode = WAL');
515
- db.pragma('foreign_keys = ON');
516
- // Wait up to 5s on a lock instead of throwing SQLITE_BUSY, and cap the WAL.
517
- db.pragma('busy_timeout = 5000');
518
- db.pragma('journal_size_limit = 10000000'); // ~10 MB
519
- applyDesktopMigrations(db);
520
- // H-13: desktop.sqlite stores webhook HMAC secrets in plaintext — restrict it
521
- // (and its WAL sidecars) to owner read/write.
522
- if (dbPath !== ':memory:')
523
- (0, secure_fs_1.secureDbFile)(dbPath);
651
+ try {
652
+ db.pragma('journal_mode = WAL');
653
+ db.pragma('foreign_keys = ON');
654
+ // Wait up to 5s on a lock instead of throwing SQLITE_BUSY, and cap the WAL.
655
+ db.pragma('busy_timeout = 5000');
656
+ db.pragma('journal_size_limit = 10000000'); // ~10 MB
657
+ applyDesktopMigrations(db);
658
+ // H-13: desktop.sqlite stores webhook HMAC secrets in plaintext — restrict it
659
+ // (and its WAL sidecars) to owner read/write.
660
+ if (dbPath !== ':memory:')
661
+ (0, secure_fs_1.secureDbFile)(dbPath);
662
+ }
663
+ catch (err) {
664
+ db.close();
665
+ throw err;
666
+ }
524
667
  return db;
525
668
  }
526
669
  function listProjects(db) {
527
- return db.prepare('SELECT * FROM projects ORDER BY added_at ASC').all().map((r) => mapProjectRow(r));
670
+ return db.prepare('SELECT * FROM projects ORDER BY added_at ASC').all().map((r) => mapProjectRow(db, r));
528
671
  }
529
672
  function getProject(db, id) {
530
- return mapProjectRow(db.prepare('SELECT * FROM projects WHERE id = ?').get(id));
673
+ return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE id = ?').get(id));
531
674
  }
532
675
  function getProjectBySlug(db, slug) {
533
- return mapProjectRow(db.prepare('SELECT * FROM projects WHERE slug = ?').get(slug));
676
+ return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE slug = ?').get(slug));
534
677
  }
535
678
  function getProjectByPath(db, projectPath) {
536
- return mapProjectRow(db.prepare('SELECT * FROM projects WHERE path = ?').get(projectPath));
679
+ return mapProjectRow(db, db.prepare('SELECT * FROM projects WHERE path = ?').get(projectPath));
537
680
  }
538
681
  function addProject(db, project) {
539
682
  const dbPath = getProjectDbPath(project.slug);
@@ -542,12 +685,65 @@ function addProject(db, project) {
542
685
  : [project.provider ?? 'claude'];
543
686
  // Primary provider = explicit override, else the first selected provider.
544
687
  const provider = project.provider ?? providers[0];
545
- db.prepare(`
546
- INSERT INTO projects (id, slug, name, path, db_path, provider, providers)
547
- VALUES (?, ?, ?, ?, ?, ?, ?)
548
- `).run(project.id, project.slug, project.name, project.path, dbPath, provider, JSON.stringify(providers));
688
+ // Low-level callers may register unavailable legacy roots. HTTP validates all
689
+ // roots first; membership and project rows are still committed atomically here.
690
+ const roots = [(0, project_repositories_1.inspectRepositoryPath)({ path: project.path, name: project.name }, false), ...(project.repositories ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))];
691
+ (0, project_repositories_1.assertDistinctRepositories)(roots);
692
+ db.transaction(() => {
693
+ db.prepare(`
694
+ INSERT INTO projects (id, slug, name, path, db_path, provider, providers)
695
+ VALUES (?, ?, ?, ?, ?, ?, ?)
696
+ `).run(project.id, project.slug, project.name, project.path, dbPath, provider, JSON.stringify(providers));
697
+ const insert = db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, is_primary, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
698
+ roots.forEach((root, index) => insert.run(index === 0 ? `primary-${project.id}` : (0, crypto_1.randomUUID)(), project.id, root.name ?? path_1.default.basename(root.path), index === 0 ? project.path : root.path, root.canonicalKey, root.gitIdentity, index === 0 ? 1 : 0, root.kind, root.integrationBranch ?? null));
699
+ })();
549
700
  return getProject(db, project.id);
550
701
  }
702
+ function listProjectRepositories(db, projectId) {
703
+ return db.prepare('SELECT * FROM project_repositories WHERE project_id = ? ORDER BY is_primary DESC, added_at, rowid').all(projectId).map((row) => ({
704
+ id: row.id, projectId: row.project_id, name: row.name, path: row.path,
705
+ isPrimary: row.is_primary === 1, kind: row.kind, integrationBranch: row.integration_branch,
706
+ addedAt: row.added_at, available: (0, project_repositories_1.repositoryAvailable)(row.path),
707
+ }));
708
+ }
709
+ function addProjectRepository(db, projectId, input) {
710
+ if (!getProject(db, projectId))
711
+ throw new project_repositories_1.RepositoryValidationError('Project not found', 'project_not_found', 404);
712
+ const root = (0, project_repositories_1.inspectRepositoryPath)(input);
713
+ const existing = listProjectRepositories(db, projectId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false));
714
+ (0, project_repositories_1.assertDistinctRepositories)([...existing, root]);
715
+ const id = (0, crypto_1.randomUUID)();
716
+ db.prepare(`INSERT INTO project_repositories (id, project_id, name, path, canonical_key, git_identity, kind, integration_branch) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
717
+ .run(id, projectId, root.name ?? path_1.default.basename(root.path), root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null);
718
+ return listProjectRepositories(db, projectId).find((repo) => repo.id === id);
719
+ }
720
+ function updateProjectRepository(db, projectId, repositoryId, input) {
721
+ const repositories = listProjectRepositories(db, projectId);
722
+ const current = repositories.find((repo) => repo.id === repositoryId);
723
+ if (!current)
724
+ throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
725
+ if (current.isPrimary && input.path !== undefined)
726
+ throw new project_repositories_1.RepositoryValidationError('The primary project path cannot be relocated here', 'primary_repository_protected', 409);
727
+ const next = { ...current, ...input };
728
+ const root = (0, project_repositories_1.inspectRepositoryPath)(next, input.path !== undefined);
729
+ if (input.path === undefined && !(0, project_repositories_1.repositoryAvailable)(current.path)) {
730
+ root.kind = current.kind;
731
+ root.gitIdentity = db.prepare('SELECT git_identity FROM project_repositories WHERE id = ?').get(repositoryId).git_identity;
732
+ }
733
+ (0, project_repositories_1.assertDistinctRepositories)([...repositories.filter((repo) => repo.id !== repositoryId).map((repo) => (0, project_repositories_1.inspectRepositoryPath)(repo, false)), root]);
734
+ db.prepare('UPDATE project_repositories SET name = ?, path = ?, canonical_key = ?, git_identity = ?, kind = ?, integration_branch = ? WHERE project_id = ? AND id = ?')
735
+ .run(root.name ?? current.name, input.path === undefined ? current.path : root.path, root.canonicalKey, root.gitIdentity, root.kind, root.integrationBranch ?? null, projectId, repositoryId);
736
+ return listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
737
+ }
738
+ /** Removes the association only. Operational reference guards belong to ProjectRegistry. */
739
+ function removeProjectRepository(db, projectId, repositoryId) {
740
+ const current = listProjectRepositories(db, projectId).find((repo) => repo.id === repositoryId);
741
+ if (!current)
742
+ throw new project_repositories_1.RepositoryValidationError('Repository does not belong to this project', 'repository_not_found', 404);
743
+ if (current.isPrimary)
744
+ throw new project_repositories_1.RepositoryValidationError('The primary repository cannot be removed', 'primary_repository_protected', 409);
745
+ db.prepare('DELETE FROM project_repositories WHERE project_id = ? AND id = ?').run(projectId, repositoryId);
746
+ }
551
747
  function removeProject(db, id) {
552
748
  db.prepare('DELETE FROM projects WHERE id = ?').run(id);
553
749
  // No FK on agent_conversations.pinned_project_id — unpin so orphaned agent
@@ -10,9 +10,11 @@ const path_1 = __importDefault(require("path"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const net_1 = __importDefault(require("net"));
12
12
  const dns_1 = __importDefault(require("dns"));
13
+ const project_repositories_1 = require("./project-repositories");
13
14
  const desktop_db_1 = require("./desktop-db");
14
15
  const webhook_manager_1 = require("./webhook-manager");
15
16
  const core_update_manager_1 = require("./core-update-manager");
17
+ const framework_reseed_1 = require("./framework-reseed");
16
18
  const specrails_tech_client_1 = require("./specrails-tech-client");
17
19
  const core_compat_1 = require("./core-compat");
18
20
  const providers_1 = require("./providers");
@@ -453,7 +455,7 @@ function createDesktopRouter(registry, broadcast) {
453
455
  });
454
456
  // POST /api/projects — register a new project by path
455
457
  router.post('/projects', async (req, res) => {
456
- const { path: projectPath, name, provider, providers: providersRaw } = req.body ?? {};
458
+ const { path: projectPath, name, provider, providers: providersRaw, repositories: repositoriesRaw } = req.body ?? {};
457
459
  if (!projectPath || typeof projectPath !== 'string') {
458
460
  res.status(400).json({ error: 'path is required' });
459
461
  return;
@@ -545,6 +547,23 @@ function createDesktopRouter(registry, broadcast) {
545
547
  return;
546
548
  }
547
549
  const canonicalPath = canonicalizePath(resolvedPath);
550
+ let repositories = [];
551
+ try {
552
+ if (repositoriesRaw !== undefined && !Array.isArray(repositoriesRaw))
553
+ throw new project_repositories_1.RepositoryValidationError('repositories must be an array');
554
+ repositories = (repositoriesRaw ?? []).map((input) => (0, project_repositories_1.inspectRepositoryPath)(input));
555
+ const primary = (0, project_repositories_1.inspectRepositoryPath)({ path: canonicalPath });
556
+ (0, project_repositories_1.assertDistinctRepositories)([primary, ...repositories.map((input) => (0, project_repositories_1.inspectRepositoryPath)(input))]);
557
+ if (repositories.some((repository) => !isPathSafe(repository.path)))
558
+ throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
559
+ }
560
+ catch (err) {
561
+ if (err instanceof project_repositories_1.RepositoryValidationError) {
562
+ res.status(err.status).json({ error: err.message, code: err.code });
563
+ return;
564
+ }
565
+ throw err;
566
+ }
548
567
  // LOW-04: Reject registration of system-critical directories
549
568
  if (!isPathSafe(canonicalPath)) {
550
569
  res.status(400).json({ error: 'Registering system directories is not allowed' });
@@ -568,6 +587,7 @@ function createDesktopRouter(registry, broadcast) {
568
587
  path: canonicalPath,
569
588
  provider: providers[0],
570
589
  providers,
590
+ repositories,
571
591
  });
572
592
  broadcast({
573
593
  type: 'desktop.project_added',
@@ -593,7 +613,10 @@ function createDesktopRouter(registry, broadcast) {
593
613
  catch (err) {
594
614
  const message = err.message ?? '';
595
615
  // SQLite UNIQUE constraint violation means path or slug already registered
596
- if (message.includes('UNIQUE')) {
616
+ if (err instanceof project_repositories_1.RepositoryValidationError) {
617
+ res.status(err.status).json({ error: err.message, code: err.code, details: err.details });
618
+ }
619
+ else if (message.includes('UNIQUE')) {
597
620
  res.status(409).json({ error: 'A project with this path is already registered' });
598
621
  }
599
622
  else {
@@ -602,6 +625,68 @@ function createDesktopRouter(registry, broadcast) {
602
625
  }
603
626
  }
604
627
  });
628
+ // The logical project keeps one backlog; these routes only edit membership.
629
+ const sendRepositoryError = (err, res) => {
630
+ if (err instanceof project_repositories_1.RepositoryValidationError)
631
+ res.status(err.status).json({ error: err.message, code: err.code, details: err.details });
632
+ else {
633
+ console.error('[desktop] repository operation failed:', err);
634
+ res.status(500).json({ error: 'Could not update project repositories' });
635
+ }
636
+ };
637
+ const broadcastRepositories = () => broadcast({ type: 'desktop.projects', projects: (0, desktop_db_1.listProjects)(registry.desktopDb), timestamp: new Date().toISOString() });
638
+ router.get('/projects/:projectId/repositories', (req, res) => {
639
+ const project = (0, desktop_db_1.listProjects)(registry.desktopDb).find((item) => item.id === req.params.projectId);
640
+ if (!project) {
641
+ res.status(404).json({ error: 'Project not found' });
642
+ return;
643
+ }
644
+ res.json({ repositories: (0, project_repositories_1.getProjectRepositories)(project), primaryRepositoryId: project.primaryRepositoryId });
645
+ });
646
+ router.post('/projects/:projectId/repositories', (req, res) => {
647
+ try {
648
+ const input = (0, project_repositories_1.inspectRepositoryPath)(req.body);
649
+ if (!isPathSafe(input.path))
650
+ throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
651
+ const repository = registry.addRepository(req.params.projectId, input);
652
+ broadcastRepositories();
653
+ res.status(201).json({ repository, project: registry.getProjectRow(req.params.projectId) });
654
+ }
655
+ catch (err) {
656
+ sendRepositoryError(err, res);
657
+ }
658
+ });
659
+ router.patch('/projects/:projectId/repositories/:repositoryId', (req, res) => {
660
+ try {
661
+ const body = req.body ?? {};
662
+ const input = {};
663
+ for (const key of ['path', 'name', 'integrationBranch'])
664
+ if (Object.prototype.hasOwnProperty.call(body, key))
665
+ Object.assign(input, { [key]: body[key] });
666
+ if (input.path !== undefined) {
667
+ const inspected = (0, project_repositories_1.inspectRepositoryPath)(input);
668
+ if (!isPathSafe(inspected.path))
669
+ throw new project_repositories_1.RepositoryValidationError('Registering system directories is not allowed');
670
+ input.path = inspected.path;
671
+ }
672
+ const repository = registry.updateRepository(req.params.projectId, req.params.repositoryId, input);
673
+ broadcastRepositories();
674
+ res.json({ repository, project: registry.getProjectRow(req.params.projectId) });
675
+ }
676
+ catch (err) {
677
+ sendRepositoryError(err, res);
678
+ }
679
+ });
680
+ router.delete('/projects/:projectId/repositories/:repositoryId', (req, res) => {
681
+ try {
682
+ registry.removeRepository(req.params.projectId, req.params.repositoryId);
683
+ broadcastRepositories();
684
+ res.json({ ok: true, project: registry.getProjectRow(req.params.projectId) });
685
+ }
686
+ catch (err) {
687
+ sendRepositoryError(err, res);
688
+ }
689
+ });
605
690
  // POST /api/projects/:id/assemble-retry — re-run the silent workspace
606
691
  // assemble for the providers that failed (or every detected provider when
607
692
  // nothing is recorded as failed). 202; progress rides project.assemble_progress.
@@ -679,14 +764,24 @@ function createDesktopRouter(registry, broadcast) {
679
764
  res.status(400).json({ error: 'path query parameter is required' });
680
765
  return;
681
766
  }
682
- const resolvedPath = canonicalizePath(path_1.default.resolve(queryPath));
683
- const ctx = registry.getContextByPath(resolvedPath);
684
- if (!ctx) {
685
- res.status(404).json({ error: 'No project registered for this path' });
686
- return;
767
+ try {
768
+ const projectId = typeof req.query.projectId === 'string' ? req.query.projectId : undefined;
769
+ const project = (0, project_repositories_1.resolveRepositoryProject)((0, desktop_db_1.listProjects)(registry.desktopDb), queryPath, projectId);
770
+ if (!project) {
771
+ res.status(404).json({ error: 'No project registered for this path' });
772
+ return;
773
+ }
774
+ const context = registry.getContext(project.id);
775
+ if (!context) {
776
+ res.status(503).json({ error: 'Project is registered but unavailable', code: 'project_unavailable', project });
777
+ return;
778
+ }
779
+ registry.touchProject(project.id);
780
+ res.json({ project: context.project });
781
+ }
782
+ catch (err) {
783
+ sendRepositoryError(err, res);
687
784
  }
688
- registry.touchProject(ctx.project.id);
689
- res.json({ project: ctx.project });
690
785
  });
691
786
  // GET /api/settings — get app-level settings
692
787
  router.get('/settings', (_req, res) => {
@@ -1184,6 +1279,11 @@ function createDesktopRouter(registry, broadcast) {
1184
1279
  // and not members of the WsMessage union; cast at this single boundary.
1185
1280
  broadcast: (msg) => broadcast(msg),
1186
1281
  providers: () => registry.installedProvidersUnion(),
1282
+ reseed: version => (0, framework_reseed_1.reseedStaleWorkspaces)(registry.listContexts().map(context => ({
1283
+ id: context.project.id,
1284
+ slug: context.project.slug,
1285
+ path: context.project.path,
1286
+ })), version),
1187
1287
  });
1188
1288
  // GET /api/core-update/status — current/bundled/latest versions, no network.
1189
1289
  router.get('/core-update/status', (_req, res) => {
@@ -24,12 +24,17 @@ function resolveServerPort(argv, env = process.env) {
24
24
  return port;
25
25
  }
26
26
  function resolveWebDevPorts(env = process.env) {
27
- const serverPort = parseDevPort(env.SPECRAILS_DEV_SERVER_PORT, exports.DEFAULT_DEV_SERVER_PORT);
27
+ const serverPort = resolveServerPort([], env);
28
28
  const clientPort = parseDevPort(env.SPECRAILS_DEV_CLIENT_PORT, exports.DEFAULT_DEV_CLIENT_PORT);
29
+ if (serverPort === clientPort) {
30
+ throw new Error('SPECRAILS_DEV_CLIENT_PORT must differ from the Specrails API port (SPECRAILS_DEV_SERVER_PORT or SPECRAILS_PORT).');
31
+ }
29
32
  return {
30
33
  serverPort,
31
34
  clientPort,
32
- serverOrigin: `http://localhost:${serverPort}`,
33
- wsUrl: `ws://localhost:${serverPort}`,
35
+ // index.ts binds IPv4 loopback. localhost can resolve to another app on ::1
36
+ // using the same numeric port, including a frontend returning HTML for /api.
37
+ serverOrigin: `http://127.0.0.1:${serverPort}`,
38
+ wsUrl: `ws://127.0.0.1:${serverPort}`,
34
39
  };
35
40
  }