specrails-desktop 2.32.0 → 2.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/client/dist/assets/{ActivityFeedPage-DvnFkjVG.js → ActivityFeedPage-DIDxNsDj.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-CBjcliD7.js → AgentBrowserCapture-7pJ2G5VY.js} +1 -1
  3. package/client/dist/assets/AgentModeAnalyticsPane-BSSyTnTM.js +2 -0
  4. package/client/dist/assets/{AgentModeCodePane-CokuFVdV.js → AgentModeCodePane-DzW6gvDw.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-rf_DlWNo.js → AgentModeJobsPane-9wJff15f.js} +1 -1
  6. package/client/dist/assets/AgentsPage-CIA67psb.js +87 -0
  7. package/client/dist/assets/AnalyticsPage-C0LwYELz.js +1 -0
  8. package/client/dist/assets/CodePage-RtKvHmAO.js +2 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-gBRVqiw6.js → DesktopAnalyticsPage-BU8Y_Ts1.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-BzO5dg2B.js → DocsDialog-DIpoaGlQ.js} +1 -1
  11. package/client/dist/assets/{DocsPage-Db_uxr1j.js → DocsPage-ME47KZpK.js} +1 -1
  12. package/client/dist/assets/InteractiveJobComposer-BGjIe9R6.js +19 -0
  13. package/client/dist/assets/{JobDetailModal-DPHh2WOS.js → JobDetailModal-1UZkqH4I.js} +1 -1
  14. package/client/dist/assets/JobDetailPage-CN65sAf1.js +1 -0
  15. package/client/dist/assets/JobsPage-f-4dqys7.js +1 -0
  16. package/client/dist/assets/LoopBuilderPage-WeiofeL6.js +7 -0
  17. package/client/dist/assets/LoopPreviewModal-DDTnTyH0.js +1 -0
  18. package/client/dist/assets/LoopsPage-BVNchIex.js +1 -0
  19. package/client/dist/assets/MinimizedChatsContext-CHsu3NCX.js +1 -0
  20. package/client/dist/assets/PluginsPage-CdFzGEfQ.js +2 -0
  21. package/client/dist/assets/ProjectSettingsDialog-BkCXBQWk.js +1 -0
  22. package/client/dist/assets/TemplatePreviewModal-RDi0khFG.js +1 -0
  23. package/client/dist/assets/TicketDetailModal-MhJzrqbi.js +14 -0
  24. package/client/dist/assets/{Trans-B2i5HVcM.js → Trans-CTqw6rr9.js} +1 -1
  25. package/client/dist/assets/{addspec-DhmnXER8.js → addspec-A1lDQa4M.js} +1 -1
  26. package/client/dist/assets/{addspec-BRlk8jON.js → addspec-BHZd0N5F.js} +1 -1
  27. package/client/dist/assets/{addspec-CHW9xf_X.js → addspec-BPpZ_olp.js} +1 -1
  28. package/client/dist/assets/{addspec-Di41DpYV.js → addspec-B_TPJt-R.js} +1 -1
  29. package/client/dist/assets/{addspec-4WmLJmBG.js → addspec-CLYLvaVP.js} +1 -1
  30. package/client/dist/assets/{addspec-C7XRejU4.js → addspec-CQBjzt24.js} +1 -1
  31. package/client/dist/assets/{addspec-x3mizDxc.js → addspec-D2YIQ0GU.js} +1 -1
  32. package/client/dist/assets/{addspec-DTmRrWrF.js → addspec-iFdQ6D99.js} +1 -1
  33. package/client/dist/assets/agent-Bd-q3Wqp.js +1 -0
  34. package/client/dist/assets/agent-BlvdW4Hv.js +1 -0
  35. package/client/dist/assets/agent-C-KQhxeM.js +1 -0
  36. package/client/dist/assets/agent-Ca15w5eu.js +1 -0
  37. package/client/dist/assets/agent-D6fKaoMd.js +1 -0
  38. package/client/dist/assets/agent-HFMYMTbB.js +1 -0
  39. package/client/dist/assets/agent-QIEbkBjg.js +1 -0
  40. package/client/dist/assets/agent-mTCsAbCl.js +1 -0
  41. package/client/dist/assets/agentstudio-BNUth0uj.js +1 -0
  42. package/client/dist/assets/agentstudio-BNdB5hkr.js +1 -0
  43. package/client/dist/assets/agentstudio-C7-bt8FO.js +1 -0
  44. package/client/dist/assets/agentstudio-C8uNchpV.js +1 -0
  45. package/client/dist/assets/agentstudio-CKabyMlg.js +1 -0
  46. package/client/dist/assets/agentstudio-CN2pokQu.js +1 -0
  47. package/client/dist/assets/agentstudio-DR7doNBV.js +1 -0
  48. package/client/dist/assets/agentstudio-y2dVDXeM.js +1 -0
  49. package/client/dist/assets/{analytics-C9RaQcE3.js → analytics-47pYygtV.js} +1 -1
  50. package/client/dist/assets/{analytics-B8Qtptbl.js → analytics-B0vt1g5U.js} +1 -1
  51. package/client/dist/assets/analytics-BMzCvrCU.js +1 -0
  52. package/client/dist/assets/{analytics-BPptRY4f.js → analytics-BlFN3Zwx.js} +1 -1
  53. package/client/dist/assets/{analytics-BxDGOIwf.js → analytics-CZYzL-nR.js} +1 -1
  54. package/client/dist/assets/{analytics-D_OZdQ9O.js → analytics-DAsb9MJ8.js} +1 -1
  55. package/client/dist/assets/{analytics-CJIwqrEl.js → analytics-FXQlQ2Yt.js} +1 -1
  56. package/client/dist/assets/{analytics-We6lUMI8.js → analytics-RnFCNkah.js} +1 -1
  57. package/client/dist/assets/{builder-CWYPw7Cw.js → builder-0IWtosUm.js} +1 -1
  58. package/client/dist/assets/{builder-CX1437hF.js → builder-Bf2N6XWn.js} +1 -1
  59. package/client/dist/assets/{builder-BiUG9Z5K.js → builder-BnqaaBkT.js} +1 -1
  60. package/client/dist/assets/{builder-CGRz_-zP.js → builder-BxxfGgAO.js} +1 -1
  61. package/client/dist/assets/{builder-DwDexYRE.js → builder-C5IIgWsq.js} +1 -1
  62. package/client/dist/assets/builder-CHuahItu.js +1 -0
  63. package/client/dist/assets/{builder-Cowk9jtL.js → builder-CjWosXL_.js} +1 -1
  64. package/client/dist/assets/{builder-3bygQtZi.js → builder-CpCIOoKj.js} +1 -1
  65. package/client/dist/assets/dashboard-BHRqsGCV.js +1 -0
  66. package/client/dist/assets/dashboard-Bfr90vOl.js +1 -0
  67. package/client/dist/assets/dashboard-CTsM8x3m.js +1 -0
  68. package/client/dist/assets/dashboard-CqC6wX0h.js +1 -0
  69. package/client/dist/assets/dashboard-CuZtAszc.js +1 -0
  70. package/client/dist/assets/dashboard-DAcqIc-n.js +1 -0
  71. package/client/dist/assets/dashboard-DekiV-86.js +1 -0
  72. package/client/dist/assets/dashboard-DoDczc5S.js +1 -0
  73. package/client/dist/assets/{dist-js-CkTZKBF9.js → dist-js-BmsFWRly.js} +1 -1
  74. package/client/dist/assets/{dist-js-ClShHcGa.js → dist-js-J5W_N5ng.js} +1 -1
  75. package/client/dist/assets/{formatDistanceToNow-BjGwPf6I.js → formatDistanceToNow-BOuXgebD.js} +1 -1
  76. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-CSCbUI4W.js → getTimezoneOffsetInMilliseconds-EjGOpn4b.js} +1 -1
  77. package/client/dist/assets/{i18n-Ch-Db_x2.js → i18n-nJQvD5zJ.js} +2 -2
  78. package/client/dist/assets/index-C_sdsSZt.js +63 -0
  79. package/client/dist/assets/index-DCN5QbDm.css +2 -0
  80. package/client/dist/assets/{jira-BySfUuQZ.js → jira-BNZu8f9O.js} +1 -1
  81. package/client/dist/assets/{jira-gAvOyije.js → jira-BOoaUXbb.js} +1 -1
  82. package/client/dist/assets/{jira-pRaL7JQo.js → jira-BYVN4L2u.js} +1 -1
  83. package/client/dist/assets/jira-CKJg3HZd.js +1 -0
  84. package/client/dist/assets/{jira-Byw2C0F8.js → jira-DDDsZ4ub.js} +1 -1
  85. package/client/dist/assets/{jira-D38Gj7AT.js → jira-DXwYjVE4.js} +1 -1
  86. package/client/dist/assets/{jira-D11gPhxz.js → jira-Dn6U2nRW.js} +1 -1
  87. package/client/dist/assets/{jira-api-BgFIr8eD.js → jira-api-Bhixwn-d.js} +1 -1
  88. package/client/dist/assets/{jira-CNeYi06w.js → jira-j7hVolR5.js} +1 -1
  89. package/client/dist/assets/nav-ApJVg3xu.js +1 -0
  90. package/client/dist/assets/nav-BKNky1Bg.js +1 -0
  91. package/client/dist/assets/nav-C6vWWKYx.js +1 -0
  92. package/client/dist/assets/nav-CXJ6Aw89.js +1 -0
  93. package/client/dist/assets/nav-CsmFBIDf.js +1 -0
  94. package/client/dist/assets/nav-DjkmQJjd.js +1 -0
  95. package/client/dist/assets/nav-iwjCg5FR.js +1 -0
  96. package/client/dist/assets/nav-pYuXq7sf.js +1 -0
  97. package/client/dist/assets/{provider-capabilities-BvoExILy.js → provider-capabilities-Doej9lfa.js} +1 -1
  98. package/client/dist/assets/setup-3jWaoVUO.js +1 -0
  99. package/client/dist/assets/setup-BjgRneb2.js +1 -0
  100. package/client/dist/assets/setup-BzFIjv-f.js +1 -0
  101. package/client/dist/assets/setup-C4VfQsxE.js +1 -0
  102. package/client/dist/assets/setup-DfUkpDbN.js +1 -0
  103. package/client/dist/assets/setup-ECe90Pmu.js +1 -0
  104. package/client/dist/assets/setup-dlqC4Jmm.js +1 -0
  105. package/client/dist/assets/setup-jwfLMiM2.js +1 -0
  106. package/client/dist/assets/{spending-Dq43JYmD.js → spending-BpYPsevQ.js} +1 -1
  107. package/client/dist/assets/tickets-B4rjuSYi.js +1 -0
  108. package/client/dist/assets/tickets-Bc71ce1C.js +1 -0
  109. package/client/dist/assets/tickets-CLg7VTJ8.js +1 -0
  110. package/client/dist/assets/tickets-CP9U1GJt.js +1 -0
  111. package/client/dist/assets/tickets-CZNBiHbq.js +1 -0
  112. package/client/dist/assets/tickets-DlX2mEkD.js +1 -0
  113. package/client/dist/assets/tickets-W6pBmbrX.js +1 -0
  114. package/client/dist/assets/tickets-_yKvCt4Y.js +1 -0
  115. package/client/dist/assets/{useBrowserCaptureSession-TTO8vHtd.js → useBrowserCaptureSession-BBXBN0Na.js} +1 -1
  116. package/client/dist/assets/useDesktop-B6Du2N83.js +1 -0
  117. package/client/dist/assets/{useSharedWebSocket-DaPh7QBL.js → useSharedWebSocket-DK96ctb7.js} +1 -1
  118. package/client/dist/index.html +24 -24
  119. package/docs/guide/de/getting-started/3-adding-your-first-project.md +13 -32
  120. package/docs/guide/de/integrations/1-ai-providers.md +27 -16
  121. package/docs/guide/en/getting-started/3-adding-your-first-project.md +8 -27
  122. package/docs/guide/en/integrations/1-ai-providers.md +21 -18
  123. package/docs/guide/es/getting-started/3-adding-your-first-project.md +12 -31
  124. package/docs/guide/es/integrations/1-ai-providers.md +24 -16
  125. package/docs/guide/fr/getting-started/3-adding-your-first-project.md +15 -34
  126. package/docs/guide/fr/integrations/1-ai-providers.md +25 -16
  127. package/docs/guide/it/getting-started/3-adding-your-first-project.md +16 -35
  128. package/docs/guide/it/integrations/1-ai-providers.md +25 -16
  129. package/docs/guide/ja/getting-started/3-adding-your-first-project.md +16 -35
  130. package/docs/guide/ja/integrations/1-ai-providers.md +7 -12
  131. package/docs/guide/pt/getting-started/3-adding-your-first-project.md +15 -34
  132. package/docs/guide/pt/integrations/1-ai-providers.md +24 -16
  133. package/docs/guide/zh/getting-started/3-adding-your-first-project.md +18 -37
  134. package/docs/guide/zh/integrations/1-ai-providers.md +7 -12
  135. package/docs/internals/project-builder.md +11 -6
  136. package/package.json +1 -1
  137. package/server/dist/ai-invocations.js +35 -0
  138. package/server/dist/blueprint-commit.js +1 -1
  139. package/server/dist/desktop-db.js +22 -1
  140. package/server/dist/desktop-router.js +132 -43
  141. package/server/dist/framework-reseed.js +144 -0
  142. package/server/dist/index.js +41 -2
  143. package/server/dist/jira/jira-sync-manager.js +49 -2
  144. package/server/dist/jira-router.js +21 -0
  145. package/server/dist/job-phase-breakdown.js +132 -0
  146. package/server/dist/legacy-migration.js +395 -0
  147. package/server/dist/loop-executors.js +5 -1
  148. package/server/dist/offline-assemble.js +32 -4
  149. package/server/dist/pr-body.js +13 -0
  150. package/server/dist/project-router-jobs.js +16 -0
  151. package/server/dist/project-router-spending.js +11 -1
  152. package/server/dist/provider-detection.js +222 -0
  153. package/server/dist/provider-selection.js +71 -19
  154. package/server/dist/queue-manager.js +4 -0
  155. package/server/dist/rail-pr-decision.js +10 -0
  156. package/server/dist/rails-router.js +11 -0
  157. package/server/dist/rails-store.js +5 -1
  158. package/server/dist/repo-map.js +293 -0
  159. package/server/dist/setup-manager.js +57 -0
  160. package/server/dist/spending.js +45 -0
  161. package/client/dist/assets/AgentsPage-B648IzGI.js +0 -87
  162. package/client/dist/assets/AnalyticsPage-0rBoULPX.js +0 -1
  163. package/client/dist/assets/CodePage-ju302Vl7.js +0 -2
  164. package/client/dist/assets/InteractiveJobComposer-_jmsE_Az.js +0 -19
  165. package/client/dist/assets/JobDetailPage-7nwHyHGV.js +0 -1
  166. package/client/dist/assets/JobsPage-C_6YrcmE.js +0 -1
  167. package/client/dist/assets/LoopBuilderPage-DJ1UPDwL.js +0 -7
  168. package/client/dist/assets/LoopPreviewModal-12cUnljK.js +0 -1
  169. package/client/dist/assets/LoopsPage-CHYK0Xjr.js +0 -1
  170. package/client/dist/assets/MinimizedChatsContext-Gt6KQySM.js +0 -1
  171. package/client/dist/assets/PluginsPage-E59oQmwc.js +0 -2
  172. package/client/dist/assets/ProjectSettingsDialog-DQTLQ7ie.js +0 -1
  173. package/client/dist/assets/TemplatePreviewModal-nXUcDgfk.js +0 -1
  174. package/client/dist/assets/TicketDetailModal-C-tHW-2s.js +0 -14
  175. package/client/dist/assets/agent-BKsuIgIT.js +0 -1
  176. package/client/dist/assets/agent-Bs9NilfI.js +0 -1
  177. package/client/dist/assets/agent-C23h684q.js +0 -1
  178. package/client/dist/assets/agent-CqEjy3Z2.js +0 -1
  179. package/client/dist/assets/agent-D5DZTks1.js +0 -1
  180. package/client/dist/assets/agent-DE8hUYPl.js +0 -1
  181. package/client/dist/assets/agent-DsTuJGSc.js +0 -1
  182. package/client/dist/assets/agent-DwZM05-x.js +0 -1
  183. package/client/dist/assets/agentstudio-8TQqBenW.js +0 -1
  184. package/client/dist/assets/agentstudio-B8Z_mmEX.js +0 -1
  185. package/client/dist/assets/agentstudio-BZR9YmLs.js +0 -1
  186. package/client/dist/assets/agentstudio-Bge9pAWb.js +0 -1
  187. package/client/dist/assets/agentstudio-Bz169Vzt.js +0 -1
  188. package/client/dist/assets/agentstudio-D6L2jpNi.js +0 -1
  189. package/client/dist/assets/agentstudio-K2-Z7CL0.js +0 -1
  190. package/client/dist/assets/agentstudio-Zmgzthv8.js +0 -1
  191. package/client/dist/assets/analytics-BHqUazhP.js +0 -1
  192. package/client/dist/assets/builder-skfnZ1uC.js +0 -1
  193. package/client/dist/assets/dashboard-BVq6kRJe.js +0 -1
  194. package/client/dist/assets/dashboard-BaKPPmLH.js +0 -1
  195. package/client/dist/assets/dashboard-Bm8e4zYG.js +0 -1
  196. package/client/dist/assets/dashboard-Bt6IesnM.js +0 -1
  197. package/client/dist/assets/dashboard-C_NQZle_.js +0 -1
  198. package/client/dist/assets/dashboard-CyZKoGoI.js +0 -1
  199. package/client/dist/assets/dashboard-HIUdUBQ0.js +0 -1
  200. package/client/dist/assets/dashboard-pm4Mr229.js +0 -1
  201. package/client/dist/assets/index-Bz0TeGRs.css +0 -2
  202. package/client/dist/assets/index-kCUcnT8G.js +0 -77
  203. package/client/dist/assets/jira-CmKWERE0.js +0 -1
  204. package/client/dist/assets/nav-BHDiSIVu.js +0 -1
  205. package/client/dist/assets/nav-CMiYAq4i.js +0 -1
  206. package/client/dist/assets/nav-CqS7NrDW.js +0 -1
  207. package/client/dist/assets/nav-D-0M7xfv.js +0 -1
  208. package/client/dist/assets/nav-D65O6u9I.js +0 -1
  209. package/client/dist/assets/nav-D_LKYvBr.js +0 -1
  210. package/client/dist/assets/nav-FDTJmU79.js +0 -1
  211. package/client/dist/assets/nav-VaB-C4k5.js +0 -1
  212. package/client/dist/assets/setup-BBiHEO7h.js +0 -1
  213. package/client/dist/assets/setup-BlTWhNIQ.js +0 -1
  214. package/client/dist/assets/setup-Bp2L_oiP.js +0 -1
  215. package/client/dist/assets/setup-CInYXp1-.js +0 -1
  216. package/client/dist/assets/setup-CYH3bP6K.js +0 -1
  217. package/client/dist/assets/setup-DwcQ921P.js +0 -1
  218. package/client/dist/assets/setup-PBLjuDSs.js +0 -1
  219. package/client/dist/assets/setup-bx6FtoBp.js +0 -1
  220. package/client/dist/assets/tickets-BL9r1G0e.js +0 -1
  221. package/client/dist/assets/tickets-B_NyHJ8f.js +0 -1
  222. package/client/dist/assets/tickets-BarF1wuG.js +0 -1
  223. package/client/dist/assets/tickets-CclVhpQA.js +0 -1
  224. package/client/dist/assets/tickets-CqqOHbli.js +0 -1
  225. package/client/dist/assets/tickets-DCkFSiqt.js +0 -1
  226. package/client/dist/assets/tickets-Djn3ccfT.js +0 -1
  227. package/client/dist/assets/tickets-MiHBPsy_.js +0 -1
  228. package/client/dist/assets/useDesktop-DUvS4fuP.js +0 -1
@@ -64,7 +64,17 @@ function registerSpendingRoutes(deps) {
64
64
  return;
65
65
  }
66
66
  try {
67
- res.json((0, spending_1.getSpending)(ctx(req).db, ctx(req).project.id, filters, makeTitleResolver(req)));
67
+ const spending = (0, spending_1.getSpending)(ctx(req).db, ctx(req).project.id, filters, makeTitleResolver(req));
68
+ // Agent-mission spend (app-level desktop.sqlite ledger, pinned to this
69
+ // project) folds in additively — a failure never breaks the dashboard.
70
+ let agentMissions;
71
+ try {
72
+ agentMissions = (0, spending_1.getAgentMissionSpending)(registry.desktopDb, ctx(req).project.id, filters);
73
+ }
74
+ catch (err) {
75
+ console.error('[project-router] agent-mission spending error (non-fatal):', err);
76
+ }
77
+ res.json({ ...spending, ...(agentMissions ? { agentMissions } : {}) });
68
78
  }
69
79
  catch (err) {
70
80
  console.error('[project-router] spending error:', err);
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ // App-level provider detection singleton.
3
+ //
4
+ // Provider availability is a property of the MACHINE, not of any project: this
5
+ // module probes every registered adapter (binary presence, version, auth state),
6
+ // applies the beta-flag vetoes at the source, and caches the result for 60s.
7
+ // Consumers (provider-selection, routers, setup) read the same snapshot, so the
8
+ // whole app agrees on which providers exist at any moment.
9
+ //
10
+ // Spec: openspec/changes/global-core-zero-friction/specs/provider-auto-detection/spec.md
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.AUTH_PROBE_TIMEOUT_MS = void 0;
46
+ exports.isCodexBetaDisabled = isCodexBetaDisabled;
47
+ exports.isGeminiBetaDisabled = isGeminiBetaDisabled;
48
+ exports.getDetectionSnapshot = getDetectionSnapshot;
49
+ exports.getDetectedIdsSync = getDetectedIdsSync;
50
+ exports.getSnapshotSync = getSnapshotSync;
51
+ exports.refreshDetection = refreshDetection;
52
+ exports._resetDetectionForTests = _resetDetectionForTests;
53
+ const fs = __importStar(require("fs"));
54
+ const os = __importStar(require("os"));
55
+ const path = __importStar(require("path"));
56
+ const providers_1 = require("./providers");
57
+ const core_compat_1 = require("./core-compat");
58
+ const CACHE_TTL_MS = 60_000;
59
+ /** Auth probes are offline file checks today, but keep the contract bounded. */
60
+ exports.AUTH_PROBE_TIMEOUT_MS = 1500;
61
+ // Emergency rollback for the codex provider: SPECRAILS_CODEX_BETA=0 forces
62
+ // codex unavailable without redeploying. The pre-rebrand SPECRAILS_HUB_CODEX_BETA
63
+ // name is read as a legacy fallback when the new var is unset.
64
+ function isCodexBetaDisabled() {
65
+ const v = process.env.SPECRAILS_CODEX_BETA ?? process.env.SPECRAILS_HUB_CODEX_BETA;
66
+ return v === '0';
67
+ }
68
+ // Gemini parity kill switch: only the exact string '0' disables.
69
+ function isGeminiBetaDisabled() {
70
+ return process.env.SPECRAILS_GEMINI_BETA === '0';
71
+ }
72
+ function isVetoed(id) {
73
+ if (id === 'codex')
74
+ return isCodexBetaDisabled();
75
+ if (id === 'gemini')
76
+ return isGeminiBetaDisabled();
77
+ return false;
78
+ }
79
+ /**
80
+ * Offline, provider-specific auth probe. Never throws; degrades to 'unknown'.
81
+ * These are file-presence heuristics — cheap, offline, and bounded — chosen
82
+ * over spawning login-status subcommands (slow, sometimes network-bound).
83
+ */
84
+ function probeAuthState(id, home) {
85
+ try {
86
+ switch (id) {
87
+ case 'claude':
88
+ // ~/.claude.json exists once the CLI has run/logged in. Credentials may
89
+ // also live in the OS keychain, so absence is 'unknown', never a hard
90
+ // 'unauthenticated'.
91
+ return fs.existsSync(path.join(home, '.claude.json')) ? 'authenticated' : 'unknown';
92
+ case 'codex':
93
+ // Codex persists auth at ~/.codex/auth.json; its absence reliably means
94
+ // no login has happened on this machine.
95
+ return fs.existsSync(path.join(home, '.codex', 'auth.json'))
96
+ ? 'authenticated'
97
+ : 'unauthenticated';
98
+ case 'gemini': {
99
+ // OAuth creds land under ~/.gemini; an API key via env also counts.
100
+ if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)
101
+ return 'authenticated';
102
+ return fs.existsSync(path.join(home, '.gemini', 'oauth_creds.json'))
103
+ ? 'authenticated'
104
+ : 'unknown';
105
+ }
106
+ default:
107
+ // Kimi (and future providers) have no safe offline probe today.
108
+ return 'unknown';
109
+ }
110
+ }
111
+ catch {
112
+ return 'unknown';
113
+ }
114
+ }
115
+ function usabilityError(adapter, d) {
116
+ if (!d.installed)
117
+ return d.error ?? `${adapter.displayName} CLI was not found on PATH.`;
118
+ if (!d.executable)
119
+ return d.error ?? `${adapter.displayName} was found but its readiness probe failed.`;
120
+ if (adapter.minCliVersion && d.meetsMinimum !== true) {
121
+ return d.error
122
+ ?? `${adapter.displayName} ${d.version ?? '(unknown version)'} does not satisfy the required minimum ${adapter.minCliVersion}.`;
123
+ }
124
+ return undefined;
125
+ }
126
+ async function probeAdapter(adapter, home) {
127
+ const vetoed = isVetoed(adapter.id);
128
+ let detection;
129
+ try {
130
+ detection = await adapter.detectInstalled();
131
+ }
132
+ catch (err) {
133
+ detection = {
134
+ installed: false,
135
+ executable: false,
136
+ error: err instanceof Error ? err.message : String(err),
137
+ };
138
+ }
139
+ const error = usabilityError(adapter, detection);
140
+ let usable = !vetoed && error === undefined;
141
+ // Kimi selection additionally requires a Core build that can render its
142
+ // skills — same gate /available-providers applies.
143
+ if (usable && adapter.id === 'kimi') {
144
+ try {
145
+ const core = await (0, core_compat_1.checkCoreCompat)();
146
+ if (!(0, core_compat_1.coreCompatSupportsProvider)(core, 'kimi'))
147
+ usable = false;
148
+ }
149
+ catch {
150
+ usable = false;
151
+ }
152
+ }
153
+ return {
154
+ id: adapter.id,
155
+ displayName: adapter.displayName,
156
+ installed: detection.installed,
157
+ executable: detection.executable,
158
+ version: detection.version,
159
+ meetsMinimum: detection.meetsMinimum,
160
+ authState: detection.installed ? probeAuthState(adapter.id, home) : 'unknown',
161
+ usable,
162
+ error,
163
+ ...(vetoed ? { vetoed: true } : {}),
164
+ };
165
+ }
166
+ let _snapshot = null;
167
+ let _inflight = null;
168
+ async function runDetection() {
169
+ const home = os.homedir();
170
+ const adapters = (0, providers_1.listAdapters)();
171
+ const results = await Promise.all(adapters.map((a) => probeAdapter(a, home)));
172
+ const providers = {};
173
+ for (const r of results)
174
+ providers[r.id] = r;
175
+ const snapshot = {
176
+ providers,
177
+ detected: results.filter((r) => r.usable).map((r) => r.id),
178
+ at: Date.now(),
179
+ };
180
+ _snapshot = snapshot;
181
+ return snapshot;
182
+ }
183
+ /**
184
+ * Read the detection snapshot. Cached for 60s; `refresh: true` bypasses the
185
+ * cache (still coalescing concurrent refreshes into one probe run).
186
+ */
187
+ async function getDetectionSnapshot(opts) {
188
+ const fresh = _snapshot && Date.now() - _snapshot.at < CACHE_TTL_MS;
189
+ if (fresh && !opts?.refresh)
190
+ return _snapshot;
191
+ if (_inflight)
192
+ return _inflight;
193
+ _inflight = runDetection().finally(() => {
194
+ _inflight = null;
195
+ });
196
+ return _inflight;
197
+ }
198
+ /**
199
+ * Synchronous accessor for the last snapshot's usable ids. Returns null when no
200
+ * detection has completed yet — callers fall back to legacy project-row data.
201
+ */
202
+ function getDetectedIdsSync() {
203
+ return _snapshot ? _snapshot.detected : null;
204
+ }
205
+ function getSnapshotSync() {
206
+ return _snapshot;
207
+ }
208
+ /**
209
+ * Refresh and report whether the usable set changed vs the previous snapshot.
210
+ * Callers use the flag to decide whether to broadcast `providers.detected_changed`.
211
+ */
212
+ async function refreshDetection() {
213
+ const before = _snapshot?.detected.join(',') ?? null;
214
+ const snapshot = await getDetectionSnapshot({ refresh: true });
215
+ const after = snapshot.detected.join(',');
216
+ return { snapshot, changed: before !== null && before !== after };
217
+ }
218
+ /** Test-only: clear module state. */
219
+ function _resetDetectionForTests() {
220
+ _snapshot = null;
221
+ _inflight = null;
222
+ }
@@ -1,60 +1,112 @@
1
1
  "use strict";
2
- // Per-invocation provider selection for multi-provider projects.
2
+ // Per-invocation provider selection.
3
3
  //
4
- // A project carries a primary `provider` (single source for single-provider
5
- // projects, and the fallback default) plus a `providers` list of every
6
- // installed provider. Add Spec, rails and the terminal can request a specific
7
- // engine per invocation; these helpers resolve and validate that request
8
- // against what the project actually has installed.
4
+ // Provider availability is a MACHINE property (see provider-detection.ts): every
5
+ // project offers every detected, non-vetoed provider. These helpers resolve and
6
+ // validate per-invocation engine requests against that detected set, with the
7
+ // project row's `provider`/`providers` columns as the legacy fallback when no
8
+ // detection snapshot exists yet (startup, unit tests).
9
9
  //
10
- // Invariant: when `providers.length === 1` every helper collapses to the single
11
- // provider, so single-provider projects behave exactly as before.
10
+ // The detected set is injected via `setDetectedProvidersSupplier` so this module
11
+ // stays synchronous and unit-testable with no detection singleton import cycle.
12
+ //
13
+ // Invariant: when the effective list has length 1 every helper collapses to the
14
+ // single provider, so single-provider machines behave exactly as before.
15
+ //
16
+ // Spec: openspec/changes/global-core-zero-friction/specs/provider-auto-detection/spec.md
12
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.PROVIDER_PREFERENCE_ORDER = void 0;
19
+ exports.setDetectedProvidersSupplier = setDetectedProvidersSupplier;
20
+ exports.installedProviders = installedProviders;
13
21
  exports.isProviderEnabled = isProviderEnabled;
14
22
  exports.isMultiProvider = isMultiProvider;
23
+ exports.derivePrimaryProvider = derivePrimaryProvider;
15
24
  exports.resolveProvider = resolveProvider;
16
25
  exports.validateRequestedProvider = validateRequestedProvider;
26
+ /** Fixed preference order for primary derivation when the stored primary is gone. */
27
+ exports.PROVIDER_PREFERENCE_ORDER = ['claude', 'codex', 'gemini', 'kimi'];
28
+ let _detectedSupplier = null;
29
+ /**
30
+ * Wire the app-level detection singleton in (done once at startup in index.ts).
31
+ * A supplier returning null (no snapshot yet) falls back to project-row data.
32
+ */
33
+ function setDetectedProvidersSupplier(fn) {
34
+ _detectedSupplier = fn;
35
+ }
36
+ function detectedProviders() {
37
+ if (!_detectedSupplier)
38
+ return null;
39
+ const list = _detectedSupplier();
40
+ if (!Array.isArray(list) || list.length === 0)
41
+ return null;
42
+ return list;
43
+ }
17
44
  /**
18
- * Normalised installed-provider list. `getProject` always populates
19
- * `providers`, but this tolerates a row where it is missing/empty (legacy or
20
- * hand-built rows) by falling back to the primary provider.
45
+ * The effective provider list for a project: the detected set when available,
46
+ * else the row's `providers`, else the primary. Never empty.
21
47
  */
22
48
  function installedProviders(project) {
49
+ const detected = detectedProviders();
50
+ if (detected)
51
+ return detected;
23
52
  if (Array.isArray(project.providers) && project.providers.length > 0)
24
53
  return project.providers;
25
54
  return [project.provider ?? 'claude'];
26
55
  }
27
- /** True when `id` is one of the providers installed on the project. */
56
+ /** True when `id` is one of the providers effectively available. */
28
57
  function isProviderEnabled(project, id) {
29
58
  if (!id)
30
59
  return false;
31
60
  return installedProviders(project).includes(id);
32
61
  }
33
- /** True when the project offers a choice of engines (more than one installed). */
62
+ /** True when the project offers a choice of engines (more than one available). */
34
63
  function isMultiProvider(project) {
35
64
  return installedProviders(project).length > 1;
36
65
  }
66
+ /**
67
+ * Primary derivation (spec: "Primary provider is derived with stability"):
68
+ * the stored primary while it is still available; otherwise claude if
69
+ * available; otherwise the first available provider in the fixed preference
70
+ * order; otherwise the stored primary (legacy, no detection snapshot).
71
+ */
72
+ function derivePrimaryProvider(project) {
73
+ const available = installedProviders(project);
74
+ const stored = project.provider;
75
+ if (stored && available.includes(stored))
76
+ return stored;
77
+ for (const p of exports.PROVIDER_PREFERENCE_ORDER) {
78
+ if (available.includes(p))
79
+ return p;
80
+ }
81
+ return stored ?? available[0];
82
+ }
37
83
  /**
38
84
  * Resolve the effective provider for a per-invocation request.
39
- * Returns the requested provider when it is installed on the project; otherwise
40
- * falls back to the project's primary provider. Never throws — callers that
41
- * want strict validation should use `validateRequestedProvider` first.
85
+ * Returns the requested provider when it is available; otherwise falls back to
86
+ * the derived primary. Never throws — callers that want strict validation
87
+ * should use `validateRequestedProvider` first.
42
88
  */
43
89
  function resolveProvider(project, requested) {
44
90
  if (isProviderEnabled(project, requested))
45
91
  return requested;
46
- return project.provider ?? installedProviders(project)[0];
92
+ const primary = derivePrimaryProvider(project);
93
+ if (typeof requested === 'string' && requested !== '') {
94
+ // A stored engine reference (rail ai_engine, conversation provider) naming
95
+ // a provider that is no longer detected — substitute, never block.
96
+ console.warn(`[provider-selection] provider '${requested}' unavailable; falling back to '${primary}'`);
97
+ }
98
+ return primary;
47
99
  }
48
100
  /**
49
101
  * Strict validation for route handlers. Returns the resolved provider when the
50
102
  * request is acceptable, or an `error` string when an explicit, non-empty
51
- * provider was requested that the project does not have installed. Omitting the
103
+ * provider was requested that is not currently available. Omitting the
52
104
  * provider (undefined/null/empty) is always acceptable and resolves to primary.
53
105
  */
54
106
  function validateRequestedProvider(project, requested) {
55
107
  const installed = installedProviders(project);
56
108
  if (requested === undefined || requested === null || requested === '') {
57
- return { ok: true, provider: project.provider ?? installed[0] };
109
+ return { ok: true, provider: derivePrimaryProvider(project) };
58
110
  }
59
111
  if (typeof requested !== 'string') {
60
112
  return { ok: false, error: 'provider must be a string' };
@@ -15,6 +15,7 @@ const win_spawn_1 = require("./util/win-spawn");
15
15
  const types_1 = require("./types");
16
16
  const command_resolver_1 = require("./command-resolver");
17
17
  const rail_isolation_1 = require("./rail-isolation");
18
+ const repo_map_1 = require("./repo-map");
18
19
  const cli_prompt_1 = require("./util/cli-prompt");
19
20
  const stream_display_1 = require("./util/stream-display");
20
21
  const hooks_1 = require("./hooks");
@@ -2368,6 +2369,9 @@ class QueueManager {
2368
2369
  if (profileSnapshotPath) {
2369
2370
  spawnEnv = { ...spawnEnv, SPECRAILS_PROFILE_PATH: profileSnapshotPath };
2370
2371
  }
2372
+ // Deterministic repo map (zero-AI, best-effort) — orients the pipeline's
2373
+ // exploration phase so it doesn't spend its first turns on `ls`/`find`.
2374
+ spawnEnv = (0, repo_map_1.injectRepoMapEnv)(spawnEnv, execution.repoDir);
2371
2375
  // ─── Plugin resolution + snapshot ──────────────────────────────────────
2372
2376
  // Active = installed + verify ok; degraded = installed but verify failed
2373
2377
  // or timed out. Degraded does NOT block spawn — rail proceeds, UI gets
@@ -73,6 +73,7 @@ const rail_pr_delivery_1 = require("./rail-pr-delivery");
73
73
  const rail_worktree_release_1 = require("./rail-worktree-release");
74
74
  const pr_naming_1 = require("./pr-naming");
75
75
  const pr_body_1 = require("./pr-body");
76
+ const ai_invocations_1 = require("./ai-invocations");
76
77
  const jira_db_1 = require("./jira/jira-db");
77
78
  const rail_pr_store_1 = require("./rail-pr-store");
78
79
  const rail_worktrees_store_1 = require("./rail-worktrees-store");
@@ -1292,12 +1293,21 @@ async function runCreatePr(deps, row) {
1292
1293
  catch {
1293
1294
  changes = null;
1294
1295
  }
1296
+ // Cost footer is best-effort: a lookup failure omits the line, never blocks.
1297
+ let costUsd = null;
1298
+ try {
1299
+ costUsd = (0, ai_invocations_1.sumInvocationCostForRuns)(deps.db, snap.runIds);
1300
+ }
1301
+ catch {
1302
+ costUsd = null;
1303
+ }
1295
1304
  const title = (0, pr_naming_1.buildPrTitle)(ticketData, { loopName: row.loop_name });
1296
1305
  const body = (0, pr_body_1.buildCanonicalPrBody)({
1297
1306
  loopName: row.loop_name,
1298
1307
  baseBranch: row.base_branch,
1299
1308
  tickets: ticketData.map((t) => ({ ...t, branch: branchFor(t.ticketId) })),
1300
1309
  changes,
1310
+ costUsd,
1301
1311
  });
1302
1312
  let next;
1303
1313
  // Underlying git/gh failure detail for a degraded or failed delivery —
@@ -460,6 +460,17 @@ function createRailsRouter() {
460
460
  res.status(400).json({ error: 'Rail has no tickets assigned' });
461
461
  return;
462
462
  }
463
+ // Per-rail spec cap: a single launch may carry at most
464
+ // MAX_TICKETS_PER_RAIL_LAUNCH specs — bigger batches must be chunked
465
+ // across rails (the milestone launcher does this automatically).
466
+ if (rail.ticketIds.length > rails_store_1.MAX_TICKETS_PER_RAIL_LAUNCH) {
467
+ res.status(400).json({
468
+ error: 'rail_ticket_cap_exceeded',
469
+ max: rails_store_1.MAX_TICKETS_PER_RAIL_LAUNCH,
470
+ ticketCount: rail.ticketIds.length,
471
+ });
472
+ return;
473
+ }
463
474
  // Concurrent-launch safety (parallel rails): a ticket already worked by an
464
475
  // ACTIVE run — this rail relaunched mid-run, or the same ticket sitting on
465
476
  // two rails — must not spawn a second concurrent writer. The per-ticket
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_RAILS = exports.BASE_RAIL_COUNT = void 0;
3
+ exports.MAX_TICKETS_PER_RAIL_LAUNCH = exports.MAX_RAILS = exports.BASE_RAIL_COUNT = void 0;
4
4
  exports.ensureRailMeta = ensureRailMeta;
5
5
  exports.listRailIndices = listRailIndices;
6
6
  exports.railCount = railCount;
@@ -22,6 +22,10 @@ exports.BASE_RAIL_COUNT = 3;
22
22
  /** Hard cap on rails per project — keeps railIndex-keyed maps (metrics,
23
23
  * pr-deliveries, worktree progress) bounded and the board renderable. */
24
24
  exports.MAX_RAILS = 12;
25
+ /** Hard cap on specs a single rail may LAUNCH with — a bigger batch must be
26
+ * chunked across several rails (the milestone launcher does this client-side;
27
+ * the launch route rejects oversized rails with `rail_ticket_cap_exceeded`). */
28
+ exports.MAX_TICKETS_PER_RAIL_LAUNCH = 3;
25
29
  /** Read the per-rail display names from rail_meta into a lookup map. */
26
30
  function railNames(db) {
27
31
  const rows = db.prepare('SELECT rail_index, name FROM rail_meta').all();