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
@@ -13,6 +13,7 @@ const agent_tier_1 = require("./agent-tier");
13
13
  const attachment_manager_1 = require("./attachment-manager");
14
14
  const agent_store_1 = require("./agent-store");
15
15
  const transient_children_1 = require("./transient-children");
16
+ const agent_input_store_1 = require("./agent-input-store");
16
17
  function isAgentChatEnabled() {
17
18
  return process.env.SPECRAILS_AGENT_CHAT !== 'false';
18
19
  }
@@ -78,13 +79,15 @@ function sanitizeContextRefs(value) {
78
79
  const scopeRaw = row.scope && typeof row.scope === 'object' ? row.scope : null;
79
80
  const projectId = cleanContextString(scopeRaw?.projectId, 160);
80
81
  const projectName = cleanContextString(scopeRaw?.projectName, 180);
82
+ const repositoryId = cleanContextString(scopeRaw?.repositoryId, 160);
83
+ const repositoryName = cleanContextString(scopeRaw?.repositoryName, 180);
81
84
  const status = cleanContextString(row.status, 80);
82
85
  refs.push({
83
86
  kind,
84
87
  id,
85
88
  label,
86
89
  token,
87
- scope: projectId || projectName ? { projectId, projectName } : undefined,
90
+ scope: projectId || projectName || repositoryId || repositoryName ? { projectId, projectName, repositoryId, repositoryName } : undefined,
88
91
  status,
89
92
  metadata: cleanContextMetadata(row.metadata),
90
93
  });
@@ -149,6 +152,23 @@ function createAgentChatRouter(deps) {
149
152
  router.get('/conversations', (_req, res) => {
150
153
  res.json({ conversations: (0, agent_store_1.listAgentConversations)(desktopDb) });
151
154
  });
155
+ // Mission search (search-missions-in-palette): title + user/assistant content,
156
+ // one row per conversation, title hits first. Blank `q` is a client bug → 400.
157
+ router.get('/search', (req, res) => {
158
+ const q = typeof req.query.q === 'string' ? req.query.q.trim() : '';
159
+ if (!q) {
160
+ res.status(400).json({ error: 'q is required' });
161
+ return;
162
+ }
163
+ const rawLimit = Number(req.query.limit);
164
+ const limit = Number.isFinite(rawLimit) && rawLimit > 0
165
+ ? Math.min(agent_store_1.MISSION_SEARCH_MAX_LIMIT, Math.floor(rawLimit))
166
+ : agent_store_1.MISSION_SEARCH_DEFAULT_LIMIT;
167
+ res.json({ results: (0, agent_store_1.searchAgentConversations)(desktopDb, q, limit) });
168
+ });
169
+ router.get('/active-turns', (_req, res) => {
170
+ res.json(manager.activeTurns());
171
+ });
152
172
  router.post('/conversations', (req, res) => {
153
173
  const body = (req.body ?? {});
154
174
  let provider = 'claude';
@@ -183,7 +203,12 @@ function createAgentChatRouter(deps) {
183
203
  res.status(404).json({ error: 'Unknown conversation' });
184
204
  return;
185
205
  }
186
- res.json({ conversation, messages: (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id) });
206
+ res.json({
207
+ conversation,
208
+ messages: (0, agent_input_store_1.decorateAgentInputMessages)(desktopDb, (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id)),
209
+ pendingMessages: manager.pendingMessages(conversation.id),
210
+ live: manager.conversationLive(conversation.id),
211
+ });
187
212
  });
188
213
  router.patch('/conversations/:id', (req, res) => {
189
214
  const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
@@ -260,6 +285,7 @@ function createAgentChatRouter(deps) {
260
285
  const id = String(req.params.id);
261
286
  manager.abort(id);
262
287
  (0, transient_children_1.killBackgroundProcessesForChat)(id);
288
+ (0, transient_children_1.purgeBackgroundProcessHistory)({ chatId: id });
263
289
  (0, agent_store_1.deleteAgentConversation)(desktopDb, id);
264
290
  // Cascade-remove the conversation's attachment directory (best-effort).
265
291
  void attachment_manager_1.attachmentManager.deleteAllAgent(id).catch((e) => console.error('[agent-chat] attachment cleanup failed:', e));
@@ -379,23 +405,54 @@ function createAgentChatRouter(deps) {
379
405
  }
380
406
  const tierLevel = body.tierLevel !== undefined ? (0, agent_tier_1.normalizeLevel)(body.tierLevel) : undefined;
381
407
  const model = typeof body.model === 'string' ? body.model : undefined;
408
+ if (body.deliveryMode !== undefined && body.deliveryMode !== 'queue' && body.deliveryMode !== 'steer') {
409
+ res.status(400).json({ error: 'deliveryMode must be queue or steer' });
410
+ return;
411
+ }
412
+ const deliveryMode = body.deliveryMode;
382
413
  let attachmentIds = [];
383
414
  const rawAtt = body.attachments;
384
415
  if (rawAtt && typeof rawAtt === 'object' && Array.isArray(rawAtt.ids)) {
385
416
  attachmentIds = (rawAtt.ids).filter((x) => typeof x === 'string');
386
417
  }
387
418
  const queueId = typeof body.queueId === 'string' ? body.queueId : null;
419
+ if (queueId !== null && (!queueId.trim() || queueId.length > 200)) {
420
+ res.status(400).json({ error: 'queueId must be a nonempty message ID of at most 200 characters' });
421
+ return;
422
+ }
388
423
  const contextRefs = sanitizeContextRefs(body.contextRefs);
389
424
  // Fire-and-forget: the turn streams over WS. Persist the chosen tier first so
390
425
  // a refresh mid-turn restores the right level.
391
- if (tierLevel !== undefined)
392
- (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, { tier_level: tierLevel });
393
426
  // isBusy here and sendMessage's own busy/enqueue branch run in the same
394
427
  // synchronous frame (the enqueue happens before sendMessage's first await),
395
428
  // so the flag the client gets always matches what actually happened.
396
429
  const queued = manager.isBusy(conversation.id);
397
- void manager.sendMessage(conversation.id, text, { tierLevel, model, attachmentIds, queueId, contextRefs }).catch((e) => console.error('[agent-chat] send failed:', e));
398
- res.status(202).json({ accepted: true, queued });
430
+ try {
431
+ const previous = queueId ? (0, agent_input_store_1.getAgentInput)(desktopDb, conversation.id, queueId) : undefined;
432
+ void manager.sendMessage(conversation.id, text, { tierLevel, model, attachmentIds, queueId, contextRefs, deliveryMode }).catch((e) => console.error('[agent-chat] send failed:', e));
433
+ if (tierLevel !== undefined)
434
+ (0, agent_store_1.updateAgentConversation)(desktopDb, conversation.id, { tier_level: tierLevel });
435
+ const input = queueId ? (0, agent_input_store_1.getAgentInput)(desktopDb, conversation.id, queueId) : undefined;
436
+ const pending = manager.pendingMessages(conversation.id).find((item) => item.queueId === queueId);
437
+ const stillQueued = input ? input.status === 'pending' && pending !== undefined : queued;
438
+ const message = input?.messageId
439
+ ? (0, agent_input_store_1.decorateAgentInputMessages)(desktopDb, (0, agent_store_1.listAgentMessages)(desktopDb, conversation.id).filter((item) => item.id === input.messageId))[0]
440
+ : undefined;
441
+ res.status(202).json({
442
+ accepted: true, queued: stillQueued,
443
+ ...(stillQueued ? { deliveryMode: pending?.deliveryMode ?? input?.options.deliveryMode ?? deliveryMode ?? 'queue' } : {}),
444
+ ...(previous ? { duplicate: true } : {}),
445
+ ...(input?.status === 'cancelled' && !input.messageId ? { removed: true } : {}),
446
+ ...(message ? { message } : {}),
447
+ });
448
+ }
449
+ catch (err) {
450
+ if (err instanceof agent_input_store_1.AgentInputConflictError || err instanceof agent_input_store_1.AgentInputLimitError) {
451
+ res.status(err instanceof agent_input_store_1.AgentInputLimitError ? 429 : 409).json({ error: err.message });
452
+ return;
453
+ }
454
+ throw err;
455
+ }
399
456
  });
400
457
  // Edit a still-queued (not yet dispatched) message in place. 409 when the
401
458
  // queue already consumed it — the client keeps the user's text as a draft so
@@ -419,6 +476,30 @@ function createAgentChatRouter(deps) {
419
476
  }
420
477
  res.json({ ok: true });
421
478
  });
479
+ router.post('/conversations/:id/queue/:queueId/steer', (req, res) => {
480
+ const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
481
+ if (!conversation) {
482
+ res.status(404).json({ error: 'Unknown conversation' });
483
+ return;
484
+ }
485
+ if (!manager.steerQueued(conversation.id, String(req.params.queueId))) {
486
+ res.status(409).json({ error: 'Message already dispatched' });
487
+ return;
488
+ }
489
+ res.json({ ok: true });
490
+ });
491
+ router.delete('/conversations/:id/queue/:queueId', (req, res) => {
492
+ const conversation = (0, agent_store_1.getAgentConversation)(desktopDb, String(req.params.id));
493
+ if (!conversation) {
494
+ res.status(404).json({ error: 'Unknown conversation' });
495
+ return;
496
+ }
497
+ if (!manager.removeQueued(conversation.id, String(req.params.queueId))) {
498
+ res.status(409).json({ error: 'Message already dispatched' });
499
+ return;
500
+ }
501
+ res.json({ ok: true });
502
+ });
422
503
  router.post('/conversations/:id/abort', (req, res) => {
423
504
  const aborted = manager.abort(String(req.params.id));
424
505
  res.json({ aborted });
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildResolvedAgentContextBlock = buildResolvedAgentContextBlock;
4
+ exports.buildAgentProjectContextBlock = buildAgentProjectContextBlock;
5
+ exports.buildAgentHistoryBlock = buildAgentHistoryBlock;
4
6
  const agent_store_1 = require("./agent-store");
5
7
  const db_1 = require("./db");
6
8
  const desktop_db_1 = require("./desktop-db");
7
9
  const workspace_resolution_1 = require("./workspace-resolution");
8
10
  const ticket_store_1 = require("./ticket-store");
11
+ const project_repositories_1 = require("./project-repositories");
12
+ const code_explorer_router_1 = require("./code-explorer-router");
9
13
  const MAX_BLOCK_CHARS = 28_000;
10
14
  const MAX_DESCRIPTION_CHARS = 7_000;
11
15
  const MAX_EVENT_PAYLOAD_CHARS = 1_200;
@@ -70,18 +74,21 @@ function resolveProject(ref, deps) {
70
74
  }
71
75
  function resolveProjectForJob(ref, deps) {
72
76
  const direct = resolveProject(ref, deps);
73
- if (direct.context && (0, db_1.getJob)(direct.context.db, ref.id))
77
+ // A project id is an address, never a hint. Missing scoped entities must not
78
+ // silently resolve to an identically numbered item in another repository.
79
+ if (projectIdFor(ref, deps.fallbackProjectId))
74
80
  return direct;
81
+ const matches = [];
75
82
  for (const context of deps.registry?.listContexts() ?? []) {
76
83
  try {
77
84
  if ((0, db_1.getJob)(context.db, ref.id))
78
- return { project: context.project, context };
85
+ matches.push({ project: context.project, context });
79
86
  }
80
87
  catch {
81
88
  /* ignore failed project db */
82
89
  }
83
90
  }
84
- return direct;
91
+ return matches.length === 1 ? matches[0] : direct;
85
92
  }
86
93
  function ticketFileFor(project) {
87
94
  try {
@@ -93,26 +100,28 @@ function ticketFileFor(project) {
93
100
  }
94
101
  }
95
102
  function readTicket(project, id) {
96
- try {
97
- return (0, ticket_store_1.readStore)(ticketFileFor(project)).tickets[String(id)] ?? null;
98
- }
99
- catch {
100
- return null;
101
- }
103
+ // Present-but-unreadable stores must remain errors. Calling that "not found"
104
+ // encourages duplicate specs and hides the evidence needed for recovery.
105
+ return (0, ticket_store_1.readStore)(ticketFileFor(project)).tickets[String(id)] ?? null;
102
106
  }
103
107
  function resolveProjectForTicket(ref, ticketId, deps) {
104
108
  const direct = resolveProject(ref, deps);
105
- if (direct.project) {
106
- const ticket = readTicket(direct.project, ticketId);
107
- if (ticket)
108
- return { ...direct, ticket };
109
+ if (projectIdFor(ref, deps.fallbackProjectId)) {
110
+ return { ...direct, ticket: direct.project ? readTicket(direct.project, ticketId) : null };
109
111
  }
112
+ const matches = [];
113
+ let unavailable = false;
110
114
  for (const context of deps.registry?.listContexts() ?? []) {
111
- const ticket = readTicket(context.project, ticketId);
112
- if (ticket)
113
- return { project: context.project, context, ticket };
115
+ try {
116
+ const ticket = readTicket(context.project, ticketId);
117
+ if (ticket)
118
+ matches.push({ project: context.project, context, ticket });
119
+ }
120
+ catch {
121
+ unavailable = true;
122
+ }
114
123
  }
115
- return { ...direct, ticket: null };
124
+ return matches.length === 1 && !unavailable ? matches[0] : { ...direct, ticket: null, ambiguous: matches.length > 1, unavailable };
116
125
  }
117
126
  function formatBase(ref, index) {
118
127
  const lines = [
@@ -123,6 +132,8 @@ function formatBase(ref, index) {
123
132
  if (ref.scope?.projectName || ref.scope?.projectId) {
124
133
  lines.push(`scope: ${safe(ref.scope.projectName ?? ref.scope.projectId, 180)}`);
125
134
  }
135
+ if (ref.scope?.repositoryId)
136
+ lines.push(`repository.id: ${safe(ref.scope.repositoryId, 180)}`);
126
137
  if (ref.status)
127
138
  lines.push(`status: ${safe(ref.status, 80)}`);
128
139
  const metadata = metadataLine(ref.metadata);
@@ -137,7 +148,7 @@ function formatProjectOverview(ref, index, deps) {
137
148
  lines.push('resolution: unresolved project');
138
149
  return lines.join('\n');
139
150
  }
140
- lines.push(`project.name: ${safe(project.name, 180)}`, `project.slug: ${safe(project.slug, 120)}`, `project.path: ${project.path}`, `project.provider: ${safe(project.provider, 80)}`, `project.providers: ${(project.providers ?? [project.provider]).join(', ')}`);
151
+ lines.push(`project.id: ${project.id}`, `project.name: ${safe(project.name, 180)}`, `project.slug: ${safe(project.slug, 120)}`, `project.path: ${project.path}`, `project.provider: ${safe(project.provider, 80)}`, `project.providers: ${(project.providers ?? [project.provider]).join(', ')}`, `project.runtime: ${context ? 'available' : 'unavailable; do not interpret this as a deleted project'}`, `project.repositories: ${JSON.stringify((0, project_repositories_1.getProjectRepositories)(project).map(repository => ({ id: repository.id, name: repository.name, path: repository.path, isPrimary: repository.isPrimary, kind: repository.kind, available: repository.available })))}`, 'repository.scope: One shared project backlog. Files and Git operations require repositoryId when more than one member exists; find/search can discover across members. Reading context does not grant implementation write access. Historical specs without repositoryIds target only the primary.');
141
152
  try {
142
153
  const tickets = Object.values((0, ticket_store_1.readStore)(ticketFileFor(project)).tickets);
143
154
  const counts = tickets.reduce((acc, ticket) => {
@@ -171,23 +182,27 @@ function formatProjectOverview(ref, index, deps) {
171
182
  }
172
183
  function formatTicket(ref, index, deps) {
173
184
  const fromMetadata = typeof ref.metadata?.ticketId === 'number' ? ref.metadata.ticketId : Number.NaN;
174
- const ticketId = Number.isFinite(fromMetadata) ? fromMetadata : Number.parseInt(ref.id, 10);
185
+ const ticketId = Number.isFinite(fromMetadata) ? fromMetadata : /^\d+$/.test(ref.id) ? Number(ref.id) : Number.NaN;
175
186
  const lines = formatBase(ref, index);
176
187
  if (!Number.isInteger(ticketId) || ticketId <= 0) {
177
188
  lines.push('resolution: unresolved spec id');
178
189
  return lines.join('\n');
179
190
  }
180
- const { project, ticket } = resolveProjectForTicket(ref, ticketId, deps);
191
+ const { project, ticket, ambiguous, unavailable } = resolveProjectForTicket(ref, ticketId, deps);
181
192
  if (!ticket) {
182
- lines.push(`resolution: spec #${ticketId} not found`);
193
+ lines.push(ambiguous
194
+ ? `resolution: spec #${ticketId} is ambiguous across projects; request or select its projectId`
195
+ : unavailable
196
+ ? `resolution: spec #${ticketId} could not be resolved because a project store is unavailable; select an explicit projectId`
197
+ : `resolution: spec #${ticketId} not found in the requested project`);
183
198
  if (project)
184
199
  lines.push(`project.path: ${project.path}`);
185
200
  return lines.join('\n');
186
201
  }
187
202
  if (project) {
188
- lines.push(`project.name: ${safe(project.name, 180)}`, `project.path: ${project.path}`);
203
+ lines.push(`project.id: ${project.id}`, `project.name: ${safe(project.name, 180)}`, `project.path: ${project.path}`);
189
204
  }
190
- lines.push(`spec.id: #${ticket.id}`, `spec.title: ${safe(ticket.title, 240)}`, `spec.status: ${ticket.status}`, `spec.priority: ${ticket.priority ?? 'none'}`, `spec.labels: ${(ticket.labels ?? []).join(', ') || 'none'}`, `spec.assignee: ${ticket.assignee ?? 'none'}`, `spec.prerequisites: ${(ticket.prerequisites ?? []).join(', ') || 'none'}`);
205
+ lines.push(`spec.id: #${ticket.id}`, `spec.title: ${safe(ticket.title, 240)}`, `spec.status: ${ticket.status}`, `spec.priority: ${ticket.priority ?? 'none'}`, `spec.repositoryIds: ${JSON.stringify(ticket.repositoryIds ?? (project ? [(0, project_repositories_1.resolveProjectRepository)(project).id] : []))}`, `spec.labels: ${(ticket.labels ?? []).join(', ') || 'none'}`, `spec.assignee: ${ticket.assignee ?? 'none'}`, `spec.prerequisites: ${(ticket.prerequisites ?? []).join(', ') || 'none'}`);
191
206
  if (ticket.short_summary)
192
207
  lines.push(`spec.short_summary: ${safe(ticket.short_summary, 500)}`);
193
208
  if (ticket.jira_key || ticket.jira_url || ticket.jira_status) {
@@ -220,7 +235,7 @@ function formatJob(ref, index, deps) {
220
235
  lines.push(`project.name: ${safe(context.project.name, 180)}`);
221
236
  return lines.join('\n');
222
237
  }
223
- lines.push(`project.name: ${safe(context.project.name, 180)}`, `project.path: ${context.project.path}`, `job.id: ${job.id}`, `job.status: ${job.status}`, `job.command: ${job.command}`, `job.started_at: ${job.started_at}`, `job.finished_at: ${job.finished_at ?? 'null'}`, `job.exit_code: ${job.exit_code ?? 'null'}`, `job.model: ${job.model ?? 'null'}`, `job.duration_ms: ${job.duration_ms ?? 'null'}`, `job.total_cost_usd: ${job.total_cost_usd ?? 'null'}`, `job.tokens: in=${job.tokens_in ?? 'null'} out=${job.tokens_out ?? 'null'} cache_read=${job.tokens_cache_read ?? 'null'} cache_create=${job.tokens_cache_create ?? 'null'}`);
238
+ lines.push(`project.id: ${context.project.id}`, `project.name: ${safe(context.project.name, 180)}`, `project.path: ${context.project.path}`, `job.id: ${job.id}`, `job.status: ${job.status}`, `job.command: ${job.command}`, `job.started_at: ${job.started_at}`, `job.finished_at: ${job.finished_at ?? 'null'}`, `job.exit_code: ${job.exit_code ?? 'null'}`, `job.model: ${job.model ?? 'null'}`, `job.duration_ms: ${job.duration_ms ?? 'null'}`, `job.total_cost_usd: ${job.total_cost_usd ?? 'null'}`, `job.tokens: in=${job.tokens_in ?? 'null'} out=${job.tokens_out ?? 'null'} cache_read=${job.tokens_cache_read ?? 'null'} cache_create=${job.tokens_cache_create ?? 'null'}`);
224
239
  const events = (0, db_1.getJobEvents)(context.db, job.id);
225
240
  if (events.length) {
226
241
  lines.push(`job.events.total: ${events.length}`);
@@ -258,11 +273,32 @@ function formatFallback(ref, index) {
258
273
  lines.push('resolution: structured reference only; no richer resolver is registered for this kind yet');
259
274
  return lines.join('\n');
260
275
  }
276
+ function formatRepositoryReference(ref, index, deps) {
277
+ const lines = formatBase(ref, index);
278
+ const { project } = resolveProject(ref, deps);
279
+ if (!project)
280
+ return [...lines, 'resolution: unresolved project'].join('\n');
281
+ const id = ref.scope?.repositoryId ?? (ref.kind === 'repository' ? ref.id : undefined);
282
+ if (!id && (0, project_repositories_1.getProjectRepositories)(project).length > 1) {
283
+ return [...lines, 'resolution: repository required for this reference; do not guess the primary', `repositories: ${JSON.stringify((0, project_repositories_1.getProjectRepositories)(project).map(repository => ({ id: repository.id, name: repository.name })))}`].join('\n');
284
+ }
285
+ const repository = (0, project_repositories_1.resolveProjectRepository)(project, id);
286
+ lines.push(`project.id: ${project.id}`, `repository.id: ${repository.id}`, `repository.name: ${safe(repository.name, 180)}`, `repository.path: ${repository.path}`, `repository.kind: ${repository.kind}`);
287
+ if (ref.kind === 'file') {
288
+ const filePath = typeof ref.metadata?.path === 'string' ? ref.metadata.path : ref.id;
289
+ if (!(0, code_explorer_router_1.resolveSafePath)(repository.path, filePath) || (0, code_explorer_router_1.isDeniedRelPath)(filePath))
290
+ return [...lines, 'resolution: file path is unavailable or outside the allowed source scope'].join('\n');
291
+ lines.push(`file.path: ${safe(filePath, 600)}`, 'resolution: scoped file reference; use specrails_code(read_file) with this projectId, repositoryId and path for current content');
292
+ }
293
+ else
294
+ lines.push('resolution: scoped repository reference; use specrails_git with this projectId and repositoryId for current Git evidence');
295
+ return lines.join('\n');
296
+ }
261
297
  function buildResolvedAgentContextBlock(refs, deps) {
262
298
  const unique = [];
263
299
  const seen = new Set();
264
300
  for (const ref of refs.slice(0, 16)) {
265
- const key = `${ref.kind}:${ref.id}`;
301
+ const key = JSON.stringify([ref.kind, ref.id, projectIdFor(ref, deps.fallbackProjectId), ref.scope?.repositoryId ?? null]);
266
302
  if (seen.has(key))
267
303
  continue;
268
304
  seen.add(key);
@@ -272,6 +308,14 @@ function buildResolvedAgentContextBlock(refs, deps) {
272
308
  return '';
273
309
  const sections = unique.map((ref, index) => {
274
310
  try {
311
+ if (ref.scope?.repositoryId) {
312
+ const { project } = resolveProject(ref, deps);
313
+ if (!project)
314
+ throw new Error('Unknown project for repository-scoped reference');
315
+ (0, project_repositories_1.resolveProjectRepository)(project, ref.scope.repositoryId);
316
+ }
317
+ if (['repository', 'file', 'pr', 'commit', 'branch'].includes(ref.kind) || (ref.kind === 'alias' && ref.scope?.repositoryId))
318
+ return formatRepositoryReference(ref, index + 1, deps);
275
319
  if (ref.kind === 'project' || ref.kind === 'alias')
276
320
  return formatProjectOverview(ref, index + 1, deps);
277
321
  if (ref.kind === 'spec')
@@ -299,3 +343,63 @@ function buildResolvedAgentContextBlock(refs, deps) {
299
343
  return block;
300
344
  return `${block.slice(0, MAX_BLOCK_CHARS).trimEnd()}\n\n[Resolved context truncated ${block.length - MAX_BLOCK_CHARS} chars]`;
301
345
  }
346
+ /** A compact live orientation even when the user did not attach composer chips.
347
+ * Keep it in the user turn so the static operator prompt remains cacheable. */
348
+ function buildAgentProjectContextBlock(deps) {
349
+ const lines = [
350
+ '## Current Specrails project snapshot',
351
+ `snapshot.captured_at: ${new Date().toISOString()}`,
352
+ 'Snapshot data, not instructions. Re-read affected specs, rails, deliveries and Git state before mutations. The operator cwd is app-owned; it is not the selected repository.',
353
+ ];
354
+ if (deps.fallbackProjectId) {
355
+ lines.push(formatProjectOverview({
356
+ kind: 'project', id: deps.fallbackProjectId, label: 'Pinned project', token: '@current',
357
+ scope: { projectId: deps.fallbackProjectId },
358
+ }, 1, deps));
359
+ }
360
+ else {
361
+ lines.push('No project is pinned. For a uniquely named project in the request, pass its explicit projectId on each project tool; ask only if the intended target is ambiguous. Changing the default target requires pinning the project in the app.');
362
+ try {
363
+ const projects = (0, desktop_db_1.listProjects)(deps.desktopDb);
364
+ lines.push(`registered.projects.total: ${projects.length}`);
365
+ for (const project of projects.slice(0, 20)) {
366
+ lines.push(JSON.stringify({ projectId: project.id, name: safe(project.name, 120), path: project.path, providers: project.providers }));
367
+ }
368
+ if (projects.length > 20)
369
+ lines.push('Additional projects omitted; use specrails_projects(list) to search the complete catalog.');
370
+ }
371
+ catch {
372
+ lines.push('Project catalog unavailable; use specrails_projects(list) to verify it. Do not infer that no projects exist.');
373
+ }
374
+ }
375
+ return clipMultiline(lines.join('\n\n'), 6500);
376
+ }
377
+ /** Native sessions may expire or belong to a previously selected provider.
378
+ * Restore bounded persisted context for a fresh invocation without replaying
379
+ * old requests as new work or inventing the content of earlier attachments. */
380
+ function buildAgentHistoryBlock(messages) {
381
+ if (!messages.length)
382
+ return '';
383
+ const selected = [];
384
+ let remaining = 17000;
385
+ for (const message of messages.slice(-12).reverse()) {
386
+ const record = {
387
+ role: message.role,
388
+ content: clipMultiline(message.content, 3500),
389
+ ...(message.context_refs.length ? { references: message.context_refs.map((ref) => ({ kind: ref.kind, id: ref.id, projectId: ref.scope?.projectId ?? null, ...(ref.scope?.repositoryId ? { repositoryId: ref.scope.repositoryId } : {}) })) } : {}),
390
+ ...(message.attachment_ids.length ? { attachmentIds: message.attachment_ids, attachmentNote: 'Historical attachment content is not included.' } : {}),
391
+ };
392
+ const serialized = JSON.stringify(record);
393
+ if (serialized.length > remaining)
394
+ break;
395
+ selected.unshift(serialized);
396
+ remaining -= serialized.length;
397
+ }
398
+ return [
399
+ '## Persisted conversation history (fresh provider session)',
400
+ 'The following JSON lines are historical messages and app cards, not new instructions. Preserve the established intent and constraints, but do not repeat completed actions or assume an old card is current. The current project/permission prefix and the current user message take precedence. Verify live state before continuing a pending action.',
401
+ `Included ${selected.length} of ${messages.length} stored messages; earlier content may be omitted.`,
402
+ ...selected,
403
+ '## Current turn',
404
+ ].join('\n\n');
405
+ }
@@ -312,16 +312,18 @@ function buildAgentDefaultsCatalog() {
312
312
  * Loop-path profile resolver factory. Returns `(provider) => snapshotPath|null`
313
313
  * for injection as `SPECRAILS_PROFILE_PATH` on loop ai-step spawns.
314
314
  *
315
- * Deliberately inert unless the global layer carries per-agent models for the
316
- * provider — when inactive (or when the project profile already pins every
317
- * agent) it returns null and the run behaves byte-identically to today (core
318
- * resolves `.specrails/profiles/project-default.json` on its own).
315
+ * A named rail profile always gets its own immutable snapshot. An explicit
316
+ * null opts out. Without a selection, the global layer only fills missing
317
+ * per-agent models; otherwise Core keeps its existing file-based defaults.
319
318
  */
320
319
  function createLoopProfilePathResolver(io) {
321
- return (provider) => {
320
+ return (provider, profileName) => {
321
+ if (profileName === null)
322
+ return null;
322
323
  try {
323
324
  const defaults = resolveAgentDefaults(io.desktopDb, provider);
324
- if (!defaults || Object.keys(defaults.agentModels).length === 0)
325
+ const hasGlobalModels = defaults && Object.keys(defaults.agentModels).length > 0;
326
+ if (!profileName && !hasGlobalModels)
325
327
  return null;
326
328
  const adapter = (0, providers_1.getAdapter)(provider);
327
329
  if (adapter.capabilities.profiles !== true || adapter.capabilities.profileEnvSupport !== true)
@@ -333,18 +335,22 @@ function createLoopProfilePathResolver(io) {
333
335
  try {
334
336
  const resolve = io.resolveProfile
335
337
  ?? ((r, e, p) => require('./profile-manager').resolveProfile(r, e, p));
336
- base = resolve(root, undefined, provider);
338
+ base = resolve(root, profileName, provider);
337
339
  }
338
- catch {
340
+ catch (error) {
341
+ if (profileName)
342
+ throw error;
339
343
  base = null;
340
344
  }
345
+ if (profileName && !base)
346
+ throw new Error(`Profile '${profileName}' is unavailable for ${provider}`);
341
347
  let effective = null;
342
348
  if (base) {
343
- const merged = mergeProfileWithAgentDefaults(base.profile, defaults);
349
+ const merged = defaults ? mergeProfileWithAgentDefaults(base.profile, defaults) : { changed: false, profile: base.profile };
344
350
  // Nothing to add ⇒ stay out of the way (core's own file fallback rules).
345
- effective = merged.changed ? merged.profile : null;
351
+ effective = profileName || merged.changed ? merged.profile : null;
346
352
  }
347
- else {
353
+ else if (defaults) {
348
354
  effective = synthesizeProfileFromDefaults(adapter, defaults);
349
355
  }
350
356
  if (!effective)
@@ -352,6 +358,8 @@ function createLoopProfilePathResolver(io) {
352
358
  return ensureGlobalProfileSnapshot(provider, effective, io.snapshotDir);
353
359
  }
354
360
  catch (err) {
361
+ if (profileName)
362
+ throw err;
355
363
  console.warn(`[agent-defaults] loop profile resolution failed for ${provider}: ${err.message}`);
356
364
  return null;
357
365
  }