specrails-desktop 2.61.0 → 2.63.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 (230) hide show
  1. package/cli/dist/specrails-desktop.js +39 -5
  2. package/client/dist/assets/ActivityFeedPage-Bn2MpG5M.js +1 -0
  3. package/client/dist/assets/{AgentBrowserCapture-CkNWDZT3.js → AgentBrowserCapture-CHlXWdXq.js} +1 -1
  4. package/client/dist/assets/AgentModeAnalyticsPane-yj-YCgrK.js +2 -0
  5. package/client/dist/assets/AgentModeCodePane-Xczcyghb.js +2 -0
  6. package/client/dist/assets/{AgentModeJobsPane-U1J6PaBo.js → AgentModeJobsPane-DtOW01ln.js} +1 -1
  7. package/client/dist/assets/AgentsPage-BMfJ4mzU.js +118 -0
  8. package/client/dist/assets/AnalyticsPage-CGn5cgtc.js +1 -0
  9. package/client/dist/assets/BarChart-BDMqVFJy.js +55 -0
  10. package/client/dist/assets/CodePage-C8NIginr.js +3 -0
  11. package/client/dist/assets/DesktopAnalyticsPage-C87r4y26.js +1 -0
  12. package/client/dist/assets/{DocsDialog-CSlMPemn.js → DocsDialog-Z50a_KPv.js} +1 -1
  13. package/client/dist/assets/{DocsPage-BKlYAvWq.js → DocsPage-DIArbEPl.js} +1 -1
  14. package/client/dist/assets/{ExportDropdown-CweP_1GP.js → ExportDropdown-CJdCqYd7.js} +1 -1
  15. package/client/dist/assets/IntegrationsPage-EAipiDWm.js +1 -0
  16. package/client/dist/assets/InteractiveJobComposer-BxLh4me-.js +19 -0
  17. package/client/dist/assets/{JobDetailModal-DYyGq1Vp.js → JobDetailModal-C7dWoRu6.js} +1 -1
  18. package/client/dist/assets/JobDetailPage-Ilu294WS.js +1 -0
  19. package/client/dist/assets/JobsPage-fPq1hefb.js +1 -0
  20. package/client/dist/assets/LoopBuilderPage-5M7IypEP.js +1 -0
  21. package/client/dist/assets/{LoopPreviewModal-DkLRfsYX.js → LoopPreviewModal-ChI3NQC7.js} +1 -1
  22. package/client/dist/assets/{LoopWindowSurface-CuACkXFV.js → LoopWindowSurface-D1WB9i-9.js} +1 -1
  23. package/client/dist/assets/LoopsPage-CCwc0Biu.js +2 -0
  24. package/client/dist/assets/MinimizedChatsContext-eH_KaAZ1.js +1 -0
  25. package/client/dist/assets/PluginsPage-DfDfEfbW.js +2 -0
  26. package/client/dist/assets/ProjectSettingsDialog-9NHnG8JQ.js +1 -0
  27. package/client/dist/assets/RepositoryDeliveries-DgzNnJmn.js +5 -0
  28. package/client/dist/assets/{RepositoryScopeSelector-CeCr6Jz-.js → RepositoryScopeSelector-BSgUqk_u.js} +1 -1
  29. package/client/dist/assets/ReviewPacketPage-D34bGpSA.js +4 -0
  30. package/client/dist/assets/{TemplatePreviewModal-B8yd1AGU.js → TemplatePreviewModal-CcfSxWQ7.js} +1 -1
  31. package/client/dist/assets/TicketDetailModalContext-BoafEcm4.js +16 -0
  32. package/client/dist/assets/Trans-CVSryAL1.js +1 -0
  33. package/client/dist/assets/agent-5bSixVzz.js +1 -0
  34. package/client/dist/assets/agent-BR4EHfhq.js +1 -0
  35. package/client/dist/assets/agent-C0IKkJP8.js +1 -0
  36. package/client/dist/assets/agent-C7li9ayC.js +1 -0
  37. package/client/dist/assets/agent-D0dRmEen.js +1 -0
  38. package/client/dist/assets/{agent-BjttZLIl.js → agent-DZVGHm7H.js} +1 -1
  39. package/client/dist/assets/agent-Dip4h2YE.js +1 -0
  40. package/client/dist/assets/agent-Pp-P-n1p.js +1 -0
  41. package/client/dist/assets/ban-wqfdzoXt.js +1 -0
  42. package/client/dist/assets/{button-D1xExSTF.js → button-4FS1ZDTp.js} +1 -1
  43. package/client/dist/assets/chevron-up-6EFqEHBj.js +1 -0
  44. package/client/dist/assets/chunk-OB3PAWPO-B1EMk45A.js +3 -0
  45. package/client/dist/assets/circle-x-D2ttKotB.js +1 -0
  46. package/client/dist/assets/createLucideIcon-DtODIU64.js +1 -0
  47. package/client/dist/assets/{dialog-CMgps2MY.js → dialog-DtdR05v1.js} +1 -1
  48. package/client/dist/assets/download-C9wP_42h.js +1 -0
  49. package/client/dist/assets/external-link-CZ6Q-v1-.js +1 -0
  50. package/client/dist/assets/file-text-C0blZHLh.js +1 -0
  51. package/client/dist/assets/{formatDistanceToNow-DZ3VWsHq.js → formatDistanceToNow-NVM6rK3i.js} +1 -1
  52. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-9JiGlFrx.js → getTimezoneOffsetInMilliseconds-ZHg7WpdH.js} +1 -1
  53. package/client/dist/assets/git-branch-yf6LE1Bw.js +1 -0
  54. package/client/dist/assets/index-Culan6y7.js +83 -0
  55. package/client/dist/assets/index-D1v33giW.css +2 -0
  56. package/client/dist/assets/jira-api-Bl8xEIpk.js +1 -0
  57. package/client/dist/assets/jsx-runtime-BbYvbtY8.js +1 -0
  58. package/client/dist/assets/layers-BCLTgHXO.js +1 -0
  59. package/client/dist/assets/{lib-C7Mhm-gT.js → lib-CQJT8S6Q.js} +1 -1
  60. package/client/dist/assets/{lib-CGdyxIWg.js → lib-DWJfei6y.js} +1 -1
  61. package/client/dist/assets/loader-circle-BqV9xYGf.js +1 -0
  62. package/client/dist/assets/{loop-layout-BF8xMiNv.js → loop-layout-CyXj9223.js} +1 -1
  63. package/client/dist/assets/pencil-CxrRbDwb.js +1 -0
  64. package/client/dist/assets/plus-fHDdjBk1.js +1 -0
  65. package/client/dist/assets/{project-repositories-CadLZOnr.js → project-repositories-CvJf1a6H.js} +1 -1
  66. package/client/dist/assets/react-dom-DDD8FYaU.js +1 -0
  67. package/client/dist/assets/rotate-ccw-DalJAQQX.js +1 -0
  68. package/client/dist/assets/save-LmPSfsvZ.js +1 -0
  69. package/client/dist/assets/search-BDVp3fkH.js +1 -0
  70. package/client/dist/assets/{settings-Dn-pXukr.js → settings-BO2N-oPf.js} +1 -1
  71. package/client/dist/assets/{settings-ByVE7jdZ.js → settings-BQChAs_w.js} +1 -1
  72. package/client/dist/assets/{settings-BBj_kZaD.js → settings-BzmYlcQB.js} +1 -1
  73. package/client/dist/assets/{settings-COlH_P33.js → settings-CGjXb6TX.js} +1 -1
  74. package/client/dist/assets/{settings-D77mC_Lb.js → settings-CMxEWY46.js} +1 -1
  75. package/client/dist/assets/{settings-DZKy7MqN.js → settings-C_dRSgI2.js} +1 -1
  76. package/client/dist/assets/{settings-B5Hy_Kol.js → settings-Hw7av-B1.js} +1 -1
  77. package/client/dist/assets/settings-nryN6wuS.js +1 -0
  78. package/client/dist/assets/sparkles-CCfUdFGY.js +1 -0
  79. package/client/dist/assets/spending-BfjSJjBd.js +1 -0
  80. package/client/dist/assets/square-D0JTbRSR.js +1 -0
  81. package/client/dist/assets/trash-D8zYUmTg.js +1 -0
  82. package/client/dist/assets/triangle-alert-Cxpn8bqr.js +1 -0
  83. package/client/dist/assets/upload-CCHvUkRs.js +1 -0
  84. package/client/dist/assets/{useDesktop-DKNbOF0u.js → useDesktop-C5JVBeur.js} +1 -1
  85. package/client/dist/assets/{useRuntimeRuns-D3YMw9qx.js → useRuntimeRuns-DohBmeg8.js} +1 -1
  86. package/client/dist/assets/useSharedWebSocket-CNOvmgTr.js +2 -0
  87. package/client/dist/assets/{value-s-gzBeyF.js → value-DtOOK0wQ.js} +1 -1
  88. package/client/dist/assets/x-Bu7X_eR2.js +1 -0
  89. package/client/dist/assets/zap-bugHyWzQ.js +1 -0
  90. package/client/dist/index.html +44 -41
  91. package/docs/ci-cd.md +8 -1
  92. package/docs/customizing.md +1 -0
  93. package/docs/getting-started.md +1 -1
  94. package/docs/guide/de/integrations/6-agent-chat.md +11 -0
  95. package/docs/guide/de/settings/5-project-environment.md +27 -0
  96. package/docs/guide/en/integrations/6-agent-chat.md +11 -0
  97. package/docs/guide/en/settings/5-project-environment.md +27 -0
  98. package/docs/guide/es/integrations/6-agent-chat.md +11 -0
  99. package/docs/guide/es/settings/5-project-environment.md +27 -0
  100. package/docs/guide/fr/integrations/6-agent-chat.md +11 -0
  101. package/docs/guide/fr/settings/5-project-environment.md +27 -0
  102. package/docs/guide/it/integrations/6-agent-chat.md +11 -0
  103. package/docs/guide/it/settings/5-project-environment.md +27 -0
  104. package/docs/guide/ja/integrations/6-agent-chat.md +11 -0
  105. package/docs/guide/ja/settings/5-project-environment.md +27 -0
  106. package/docs/guide/pt/integrations/6-agent-chat.md +11 -0
  107. package/docs/guide/pt/settings/5-project-environment.md +27 -0
  108. package/docs/guide/zh/integrations/6-agent-chat.md +11 -0
  109. package/docs/guide/zh/settings/5-project-environment.md +27 -0
  110. package/docs/internals/README.md +1 -0
  111. package/docs/internals/agent-sessions.md +157 -0
  112. package/docs/internals/api-reference.md +35 -4
  113. package/docs/internals/configuration.md +80 -5
  114. package/docs/internals/core-runtime-updates.md +10 -3
  115. package/docs/internals/operations-runbook.md +11 -0
  116. package/docs/internals/programmatic-agent-runtime.md +1 -1
  117. package/docs/internals/safe-pr-review-flow.md +15 -0
  118. package/docs/internals/source-map.md +42 -1
  119. package/docs/mcp.md +1 -1
  120. package/docs/platforms/macos.md +2 -0
  121. package/docs/platforms/windows-parity.md +4 -4
  122. package/docs/platforms/windows.md +9 -9
  123. package/local-runner/dist/specrails-local-runner.js +1 -1
  124. package/mcp-bridge/dist/specrails-mcp.js +82 -15
  125. package/package.json +8 -9
  126. package/server/dist/agent-mcp-config.js +41 -0
  127. package/server/dist/core-compat.js +2 -0
  128. package/server/dist/desktop-db.js +101 -4
  129. package/server/dist/desktop-router.js +31 -2
  130. package/server/dist/index.js +58 -0
  131. package/server/dist/mcp/tools/app-settings.js +11 -0
  132. package/server/dist/mcp/tools/env.js +38 -3
  133. package/server/dist/mcp/tools/mission.js +45 -2
  134. package/server/dist/mcp/tools/specs.js +2 -2
  135. package/server/dist/modules/accounting/runtime/result-event.js +9 -1
  136. package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +25 -0
  137. package/server/dist/modules/agent-sessions/adapters/host-process.js +73 -0
  138. package/server/dist/modules/agent-sessions/adapters/http.js +31 -0
  139. package/server/dist/modules/agent-sessions/adapters/rpc-client.js +147 -0
  140. package/server/dist/modules/agent-sessions/application/session-event-pump.js +99 -0
  141. package/server/dist/modules/agent-sessions/domain/availability.js +25 -0
  142. package/server/dist/modules/agent-sessions/domain/errors.js +23 -0
  143. package/server/dist/modules/agent-sessions/domain/host-state.js +80 -0
  144. package/server/dist/modules/agent-sessions/domain/projection.js +147 -0
  145. package/server/dist/modules/agent-sessions/domain/protocol.js +59 -0
  146. package/server/dist/modules/agent-sessions/domain/subagent-policy.js +34 -0
  147. package/server/dist/modules/agent-sessions/index.js +31 -0
  148. package/server/dist/modules/agent-sessions/ports.js +2 -0
  149. package/server/dist/modules/agent-sessions/runtime/core-sessions-availability.js +43 -0
  150. package/server/dist/modules/agent-sessions/runtime/session-host-registry.js +226 -0
  151. package/server/dist/modules/agents/runtime/agent-store.js +3 -1
  152. package/server/dist/modules/execution/runtime/queue-manager.js +8 -0
  153. package/server/dist/modules/loops/runtime/loop-executors.js +1 -0
  154. package/server/dist/modules/loops/runtime/loop-implement-recipe.js +15 -4
  155. package/server/dist/modules/loops/runtime/loop-run-manager.js +11 -0
  156. package/server/dist/modules/missions/runtime/agent-chat-manager.js +137 -40
  157. package/server/dist/modules/missions/runtime/agent-chat-router.js +68 -0
  158. package/server/dist/modules/missions/runtime/agent-operator-prompt.js +8 -9
  159. package/server/dist/modules/missions/runtime/agent-session-store.js +133 -0
  160. package/server/dist/modules/missions/runtime/core-session-runner.js +204 -0
  161. package/server/dist/modules/missions/runtime/mission-core-sessions.js +286 -0
  162. package/server/dist/modules/missions/runtime/mission-session-projector.js +168 -0
  163. package/server/dist/modules/project-settings/adapters/http.js +38 -2
  164. package/server/dist/modules/project-settings/adapters/sqlite.js +30 -2
  165. package/server/dist/modules/project-settings/domain.js +24 -0
  166. package/server/dist/modules/project-settings/index.js +2 -1
  167. package/server/dist/modules/specs/runtime/spec-models.js +1 -1
  168. package/server/dist/path-resolver.js +135 -20
  169. package/server/dist/plugins/codex-spawn.js +15 -7
  170. package/server/dist/project-env.js +265 -17
  171. package/server/dist/project-registry.js +46 -0
  172. package/server/dist/project-router-settings.js +24 -1
  173. package/server/dist/providers/claude-adapter.js +6 -1
  174. package/server/dist/worktree-node-modules.js +95 -1
  175. package/client/dist/assets/ActivityFeedPage-Wi5XNgoG.js +0 -1
  176. package/client/dist/assets/AgentModeAnalyticsPane-CXz2j-Bt.js +0 -2
  177. package/client/dist/assets/AgentModeCodePane-DnsBPtpS.js +0 -2
  178. package/client/dist/assets/AgentsPage-CXrqu5vo.js +0 -118
  179. package/client/dist/assets/AnalyticsPage-Cvtt-LOO.js +0 -1
  180. package/client/dist/assets/BarChart-nBOMNfiy.js +0 -33
  181. package/client/dist/assets/CodePage-D35o-MFO.js +0 -3
  182. package/client/dist/assets/DesktopAnalyticsPage-CsDFy5rp.js +0 -1
  183. package/client/dist/assets/IntegrationsPage-Bhy58lnQ.js +0 -1
  184. package/client/dist/assets/InteractiveJobComposer-CFKvbQeJ.js +0 -19
  185. package/client/dist/assets/JobDetailPage-C6gpdYvj.js +0 -1
  186. package/client/dist/assets/JobsPage-CgS0pnKd.js +0 -1
  187. package/client/dist/assets/LoopBuilderPage-jUp20DSZ.js +0 -1
  188. package/client/dist/assets/LoopsPage-ClSrWzDd.js +0 -2
  189. package/client/dist/assets/MinimizedChatsContext-CroAs6Kh.js +0 -1
  190. package/client/dist/assets/PluginsPage-nCKQOwRL.js +0 -2
  191. package/client/dist/assets/ProjectSettingsDialog-BVDYwllU.js +0 -1
  192. package/client/dist/assets/RepositoryDeliveries-DJCm1pZr.js +0 -5
  193. package/client/dist/assets/ReviewPacketPage-kTErrEjx.js +0 -4
  194. package/client/dist/assets/TicketDetailModalContext-YBJX3RmP.js +0 -16
  195. package/client/dist/assets/Trans-D5eOjfvG.js +0 -1
  196. package/client/dist/assets/agent--8QAfoXL.js +0 -1
  197. package/client/dist/assets/agent-3HxXkIWX.js +0 -1
  198. package/client/dist/assets/agent-CpFCz32T.js +0 -1
  199. package/client/dist/assets/agent-DVnw6ybO.js +0 -1
  200. package/client/dist/assets/agent-Z5s_GTP0.js +0 -1
  201. package/client/dist/assets/agent-hv57-zyG.js +0 -1
  202. package/client/dist/assets/agent-xGrZ_aN1.js +0 -1
  203. package/client/dist/assets/chunk-KS7C4IRE-DEKu087k.js +0 -3
  204. package/client/dist/assets/circle-x-CMYsN4ZH.js +0 -1
  205. package/client/dist/assets/createLucideIcon-Bjbbp_KS.js +0 -1
  206. package/client/dist/assets/download-CFHyyHcw.js +0 -1
  207. package/client/dist/assets/external-link-D6XxC_Cu.js +0 -1
  208. package/client/dist/assets/file-text-BcLCfcWZ.js +0 -1
  209. package/client/dist/assets/git-branch-BRM0F5GN.js +0 -1
  210. package/client/dist/assets/index-Bfunvi6w.css +0 -2
  211. package/client/dist/assets/index-lTGJJTuk.js +0 -83
  212. package/client/dist/assets/jira-api-Bp0zXwjg.js +0 -1
  213. package/client/dist/assets/jsx-runtime-zu2_FqZY.js +0 -1
  214. package/client/dist/assets/loader-circle-DcygOQpC.js +0 -1
  215. package/client/dist/assets/pencil-C9RkrIPZ.js +0 -1
  216. package/client/dist/assets/plus-CsnjfkmX.js +0 -1
  217. package/client/dist/assets/react-dom-DiWzBDP9.js +0 -1
  218. package/client/dist/assets/rotate-ccw-Wh4id13q.js +0 -1
  219. package/client/dist/assets/save-OsUqVn99.js +0 -1
  220. package/client/dist/assets/search-Cq_gE3Ik.js +0 -1
  221. package/client/dist/assets/settings-Dbyd6gmj.js +0 -1
  222. package/client/dist/assets/sparkles-DrY8hb0X.js +0 -1
  223. package/client/dist/assets/spending-DQDNtM8q.js +0 -1
  224. package/client/dist/assets/square-Ci-PFcFe.js +0 -1
  225. package/client/dist/assets/trash-2-BOU3WdZZ.js +0 -1
  226. package/client/dist/assets/triangle-alert-H9T4jAmh.js +0 -1
  227. package/client/dist/assets/upload-DYMjAQ6M.js +0 -1
  228. package/client/dist/assets/useSharedWebSocket-DaWuiO0S.js +0 -2
  229. package/client/dist/assets/x-CBMI9ejn.js +0 -1
  230. package/client/dist/assets/zap-CUOlvtT1.js +0 -1
@@ -0,0 +1,157 @@
1
+ # Core agent sessions in Desktop
2
+
3
+ Specrails Core runs conversational agent work as **resident sessions**: one
4
+ provider process per conversation that survives between turns, owns its
5
+ sub-agents and records everything in a durable journal. Desktop hosts that
6
+ runtime, projects its events into its own tables and renders them. This page is
7
+ the map for that split. The OpenSpec change
8
+ [`core-agent-sessions-host`](../../openspec/changes/core-agent-sessions-host/design.md)
9
+ holds the decisions; Core's side is documented in Core's `docs/agent-sessions/`.
10
+
11
+ ## Who owns what
12
+
13
+ | Concern | Owner |
14
+ | --- | --- |
15
+ | Provider processes, provider frames, sub-agent lifecycle, steering, interrupt | Core drivers (Claude stream-json, Codex app-server) |
16
+ | Usage normalisation (per-turn deltas, billed vs estimated cost) | Core |
17
+ | Durable journal `~/.specrails/sessions/<scope>/sessions.sqlite` | Core (single writer per scope) |
18
+ | Host supervision (one host per scope), protocol client, generic projection | Desktop [`server/modules/agent-sessions`](../../server/modules/agent-sessions/README.md) |
19
+ | Surface tables, accounting rows, WebSocket events, UI | Desktop surfaces (missions today) |
20
+ | Queue, receipts, MCP capability, tiers, titles, PR/rail cards | Desktop product rules (unchanged) |
21
+
22
+ Desktop never interprets provider frames and never branches on provider ids:
23
+ it relies on the driver capabilities Core declares in `initialize`.
24
+
25
+ ## Scopes and hosts
26
+
27
+ A scope is a project slug, or `global` for missions without a pinned project.
28
+ Desktop starts `specrails-core runtime host --stdio --scope <scope>` lazily on
29
+ the first session of that scope ([`CoreHostLauncher`](../../server/modules/agent-sessions/adapters/host-process.ts))
30
+ and supervises it in the [`SessionHostRegistry`](../../server/modules/agent-sessions/runtime/session-host-registry.ts):
31
+
32
+ - pings with a timeout; unexpected exits restart with capped backoff;
33
+ - after a restart, tracked sessions are resumed and replayed from their cursors;
34
+ - repeated failures mark the scope `degraded`: new turns use the legacy
35
+ transport and the mission shows a notice with Retry;
36
+ - causes a restart cannot fix degrade at once: another host owns the journal
37
+ (`journal_locked`), the protocol does not match, or Core lacks the host;
38
+ - removing a project stops its host; app shutdown sends `host.shutdown`, then
39
+ tree-kills.
40
+
41
+ ## Turns: the runner seam
42
+
43
+ Missions run turns through `nativeLiveSessionRunner(adapter) ?? runAiCliInvocation`.
44
+ [`createCoreSessionRunner`](../../server/modules/missions/runtime/core-session-runner.ts)
45
+ is a third implementation of that contract. [`MissionCoreSessions.prepareTurn`](../../server/modules/missions/runtime/mission-core-sessions.ts)
46
+ selects it when the scope is available and Core lists a driver for the
47
+ conversation's provider. The runner opens or resumes the session, sends the
48
+ prompt as one input, maps user-turn events to `AdapterEvent`s, delivers steer
49
+ through `session.send`, and returns Core's normalised usage. Stop goes through a
50
+ `TurnHandle` (`session.interrupt`); deleting a mission calls `session.close`.
51
+
52
+ MCP: each turn mints a capability and rewrites the conversation's capability
53
+ file; the bridge re-reads it per request, so a resident provider always presents
54
+ the current turn's capability. The capability outlives the turn while
55
+ background work may still call Specrails tools, until the next turn rotates it
56
+ or Core retires the process. Structured MCP specs include the Specrails bridge,
57
+ external servers and app-installed plugin servers.
58
+
59
+ ## Projection
60
+
61
+ ```
62
+ Core journal ──session.event──▶ SessionEventPump ──ProjectionOp[]──▶ ProjectionSink (surface)
63
+ (seq, dedupe, (domain/projection.ts, one transaction:
64
+ gap fill) pure reducer) rows + cursor, then WS
65
+ ```
66
+
67
+ - [`SessionEventPump`](../../server/modules/agent-sessions/application/session-event-pump.ts)
68
+ applies each `(sessionId, seq)` once and in order; gaps and `session.lagged`
69
+ are filled from `session.events`. On attach it rebuilds in-memory state up to
70
+ the sink cursor without re-applying.
71
+ - [`MissionSessionProjector`](../../server/modules/missions/runtime/mission-session-projector.ts)
72
+ is the missions sink. It persists what happens outside user turns: sub-agents
73
+ (`agent_subagents`) and their output (`agent_subagent_events`), resident phase
74
+ and cursor (`agent_session_cursors`), and continuation turns as messages with
75
+ `turn_origin` `subagent`/`system` plus invocations with `origin`. User turns
76
+ are settled by the mission manager.
77
+ - Continuation turns get a deterministic invocation id, so replay never records
78
+ a turn twice. `POST /api/agent/conversations/:id/session/rebuild` resets the
79
+ derived rows and replays the journal silently (see the
80
+ [API reference](api-reference.md)).
81
+
82
+ ## Sub-agent policy
83
+
84
+ [`resolveSubagentPolicy`](../../server/modules/agent-sessions/domain/subagent-policy.ts)
85
+ maps `(surface, project setting, app setting)` to `policy.subagents`. A
86
+ conversation with a project follows the project's "Allow sub-agents" setting.
87
+ A conversation without a project follows the app setting. Both default to off.
88
+ Implement pipelines are not a surface, so they never resolve a policy. The
89
+ composition root wires the resolver into `MissionCoreSessions`. Settings
90
+ routes call `ProjectRegistry.notifySettingsChanged`, and
91
+ `MissionCoreSessions.refreshSubagentPolicy` then sends the complete policy to
92
+ each open session of that scope. Core's `session.update` replaces the policy as
93
+ a whole and ignores unchanged configurations, so the resident process is not
94
+ restarted for nothing. A session with running sub-agents reports the change as
95
+ deferred, and the mission offers "Stop agents and apply now". If Core rejects
96
+ a policy with `policy_unenforceable`, the mission shows a localized error that
97
+ names the provider and the setting.
98
+
99
+ ## Sub-agent runtime (who launches them)
100
+
101
+ The hybrid runtime decides who launches sub-agents once the policy allows them.
102
+ [`resolveSubagentRuntime`](../../server/modules/agent-sessions/domain/subagent-policy.ts)
103
+ maps the "Run sub-agents with" choice (project setting `subagentRuntime`, or the
104
+ app setting for missions without a project), the mission's provider and the
105
+ Core capabilities to `policy.subagentRuntime`:
106
+
107
+ | Choice | Mission provider | Runtime sent to Core |
108
+ | --- | --- | --- |
109
+ | `null` (same as the mission agent) | any | `{ mode: 'native' }` |
110
+ | provider P, model M, effort E | P | `{ mode: 'native', model?, effort? }`. Only overrides the driver declares through `capabilities.subagentModel` / `subagentEffort` are sent (Claude: model only, Codex: both) |
111
+ | provider P, model M, effort E | not P | `{ mode: 'delegated', driver: P, model: M, effort? }` |
112
+
113
+ When Core lacks `capabilities.delegation`, or P is not one of its drivers, the
114
+ mission stays native and receives an `agent_session_notice` with code
115
+ `subagents.delegation_unsupported` or `subagents.driver_unavailable` and the
116
+ `provider`. A setting change refreshes open sessions like the policy does.
117
+
118
+ In a delegated session Core disables the provider's own sub-agent tool and the
119
+ mission agent launches sub-agents through the Specrails MCP: `specrails_mission`
120
+ actions `subagent_start`, `subagent_wait`, `subagent_list` and `subagent_stop`
121
+ call `AgentChatManager.delegateSubagent` / `waitSubagents` / `stopSubagents`,
122
+ which map to Core's `session.delegate`, `session.waitSubagents` and
123
+ `session.stopSubagents`. `MissionCoreSessions` adds a short system prompt
124
+ addendum that tells the agent how to delegate. Each delegated sub-agent is a
125
+ Core child session (its own process, no shared prompt cache), mirrored into the
126
+ mission's tree with `delegated: { driver, model }`; results nobody waited for
127
+ arrive as a system continuation turn. Its usage is billed separately
128
+ (`subagent.usage` with `billing: 'separate'`): the projector records it as its
129
+ own invocation (`core-subagent:<conversation>:<subagent>:<seq>`, origin
130
+ `subagent`, the child's provider) on top of the parent's cost. Migration 34
131
+ adds `delegated_driver` / `delegated_model` to `agent_subagents`.
132
+
133
+ ## UI
134
+
135
+ The client keeps session state in [`MissionSessionsContext`](../../client/src/features/missions/context/MissionSessionsContext.tsx)
136
+ over a pure reducer ([`mission-sessions.ts`](../../client/src/features/missions/lib/mission-sessions.ts)).
137
+ Each launch renders as a compact line under the message whose `core_turn_id`
138
+ started it. `agent_done` carries `coreTurnId`, so the anchor exists without a
139
+ reload. A launch whose reply has no text sits after the message that preceded
140
+ it. The line is expanded while agents work and folds once they finish. The
141
+ sidebar keeps a live dot for missions with background work. Details are in the
142
+ [missions feature guide](../../client/src/features/missions/README.md).
143
+
144
+ ## Rollout
145
+
146
+ `SPECRAILS_CORE_SESSIONS` (`auto` by default, `on` or `off`, see
147
+ [configuration](configuration.md)) gates the Core path. It only takes effect
148
+ when the selected Core advertises `sessions` and its session contract matches
149
+ ([`core-compat.ts`](../../server/core-compat.ts)). Otherwise, missions keep
150
+ their legacy transports.
151
+
152
+ ## Verify
153
+
154
+ ```bash
155
+ npx vitest run server/modules/agent-sessions server/modules/missions
156
+ npm run test --prefix client -- src/features/missions
157
+ ```
@@ -93,8 +93,8 @@ Example — register a project that uses both Claude and Gemini:
93
93
 
94
94
  | Method | Path | Notes |
95
95
  |--------|------|-------|
96
- | `GET` | `/settings` | App-wide settings |
97
- | `PUT` | `/settings` | Update any subset of `{ port?, specrailsTechUrl?, costAlertThresholdUsd? }` |
96
+ | `GET` | `/settings` | App-wide settings, including `allowSubagents` (default `false`) and `subagentRuntime` (default `null`) |
97
+ | `PUT` | `/settings` | Update any subset of `{ port?, specrailsTechUrl?, costAlertThresholdUsd?, allowSubagents?, subagentRuntime? }`. `subagentRuntime` is `null` (same as the mission agent) or `{ provider, model, effort: string \| null }` for missions without a project; an invalid value returns `400`. `allowSubagents` is the app-wide "Allow sub-agents" setting for missions without a project. It must be a boolean (otherwise `400` and nothing is written), and a change refreshes the policy of open sessions |
98
98
  | `GET` | `/budget` | App-wide budget config |
99
99
  | `PATCH` | `/budget` | Update the app-wide daily budget |
100
100
  | `GET` | `/theme` | Current UI theme (defaults to `specrails`) |
@@ -149,6 +149,35 @@ Proxies the external specrails-tech agents service (base URL from `desktop_setti
149
149
  | `POST` | `/agents` | Create an agent entry |
150
150
  | `PATCH` | `/agents/:id` | Update |
151
151
 
152
+ ### Missions: Core agent session sub-agents (`/api/agent/*`)
153
+
154
+ These routes are available when a mission ran in a Core agent session (`SPECRAILS_CORE_SESSIONS`, see [agent sessions](../../server/modules/agent-sessions/README.md)). Rows are Desktop's rebuildable projection of Core's session journal.
155
+
156
+ | Method | Path | Notes |
157
+ |--------|------|-------|
158
+ | `GET` | `/agent/active-turns` | Reconnect snapshot. Besides `turns`, the response includes `sessions[]`: `{ conversationId, residentPhase, processAlive, liveSubagents, subagents }` for missions with background work. |
159
+ | `GET` | `/agent/conversations/:id` | Includes `session`: `{ residentPhase, processAlive, liveSubagents, subagents }`, or `null` when the mission never ran in Core. |
160
+ | `GET` | `/agent/conversations/:id/subagents` | `{ subagents, session }`. Each sub-agent has id, parent, kind, agent type, description, phase (`running`/`idle`/`failed`/`stopped`/`killed`/`interrupted`), reason, restarts, launching Core turn, timestamps, usage, tool uses, duration and result summary. Sub-agents Specrails launched on another provider carry `delegated: { driver, model }`; their usage is recorded as separate `subagent` invocations. |
161
+ | `GET` | `/agent/conversations/:id/subagents/:subagentId/events?after=&limit=` | Paged sub-agent transcript `{ events: [{ seq, channel, delta?, tool?, createdAt }], hasMore }`. `limit` is 1–1000. |
162
+ | `POST` | `/agent/conversations/:id/subagents/stop` | Body `{ subagentIds? }` (all when omitted). Returns `{ stopped }`; `502` when the session host is unavailable. |
163
+ | `POST` | `/agent/conversations/:id/session/rebuild` | Operator repair: replays the mission's Core journal into its sub-agent rows, events, resident state and cursor. Turns already recorded as messages/invocations are not duplicated; the replay is silent, then clients receive `agent_resident_state` and one `agent_subagent` per row. Returns `{ lastSeq, subagents }`; `409` while a turn runs or when the mission has no Core session; `502` when the host is unavailable. |
164
+
165
+ | `GET` | `/agent/session-hosts` | `{ hosts: [{ scope, status, detail, code }] }`: one entry per Core session host scope (project slug or `global`). `code` explains a degraded scope (`journal_locked`, `protocol_mismatch`, `driver_unavailable`). |
166
+ | `POST` | `/agent/session-hosts/:scope/retry` | Starts a degraded scope again. Returns `{ host }`, or `502` with `{ error, host }` when it is still unavailable. |
167
+
168
+ WebSocket events for these missions:
169
+
170
+ - `agent_resident_state`, `agent_subagent`, `agent_subagent_event`;
171
+ - `agent_turn_started` / `agent_turn_done` for turns Core starts after sub-agents finish (origin `subagent` or `system`);
172
+ - `agent_session_updated` (`applied` / `deferred`) and `agent_session_notice` (`level`, `code`, `message`, optional `scope`). The notice covers provider warnings such as `policy.subagent_blocked`, and a turn that fell back to the legacy transport (`host_degraded`, `journal_locked`);
173
+ - `agent_sessions_host` (app-level, no `projectId`): `{ scope, status, detail, code }` on every host status change.
174
+
175
+ Background turns also stream `agent_stream`, `agent_tool` and `agent_tool_result` with a `turnId`.
176
+
177
+ `agent_session_notice` with code `subagents.delegation_unsupported` or `subagents.driver_unavailable` (and `provider`) says a "Run sub-agents with" choice cannot be honoured, so the mission stays native. In delegated missions the agent uses the `specrails_mission` MCP actions `subagent_start`, `subagent_wait`, `subagent_list` and `subagent_stop`.
178
+
179
+ `agent_error` may carry `code` and `provider` for failures the client explains in the user's language. One example is `policy_unenforceable`, sent when a provider cannot honour "Allow sub-agents: off".
180
+
152
181
  ### Webhooks
153
182
 
154
183
  | Method | Path | Notes |
@@ -405,8 +434,10 @@ Gated by `requireBrowserCaptureEnabled` — returns 404 when the feature is disa
405
434
 
406
435
  | Method | Path | Notes |
407
436
  |--------|------|-------|
408
- | `GET` | `/settings` | Project settings |
409
- | `PATCH` | `/settings` | Update |
437
+ | `GET` | `/settings` | Project settings, including `allowSubagents` (default `false`) and `subagentRuntime` (default `null`) |
438
+ | `PATCH` | `/settings` | Update. `allowSubagents` must be a boolean; `subagentRuntime` is `null` or `{ provider, model, effort: string \| null }` (strictly validated). A change to either refreshes the sub-agent policy of the project's open agent sessions. A change to `worktreeEnvPassthrough` (names only) re-warms the project's login-shell recovery |
439
+ | `GET` | `/env-passthrough/status` | Value-free resolution status of every configured `worktreeEnvPassthrough` name: `{ loginShellRecovery, timeoutMs, checking, names: [{ name, status, shell, checkedAt, exitCode }] }`. `status` is `inherited`, `recovered`, `not-defined`, `probe-timeout`, `probe-failed`, or `pending` for a name never checked. Probes first only when a missing name has no valid cached result. Never returns values |
440
+ | `POST` | `/env-passthrough/recheck` | Probe the login shell now (asynchronously, `SPECRAILS_LOGIN_SHELL_TIMEOUT_MS`, default 10 s), then return the same payload as the status route |
410
441
  | `GET` | `/terminal-settings` | Per-project terminal overrides |
411
442
  | `PATCH` | `/terminal-settings` | Update |
412
443
  | `GET` | `/agent-models` | Per-agent model overrides |
@@ -175,17 +175,58 @@ shell when a GUI launch does not supply it. A zsh login shell reads `~/.zprofile
175
175
  adding it to tracked repository files. Windows uses its inherited environment.
176
176
 
177
177
  Recovered values stay in a project-owned in-memory overlay, not the server's global
178
- environment, SQLite or runtime snapshots. Each project's lookup cache expires after
179
- 30 seconds, including failed/empty results, so a repaired profile or rotated shell
180
- credential can be picked up without restarting Desktop. Removing a configured name
181
- drops its recovered value immediately. A missing value remains absent: Desktop never
182
- substitutes a dummy credential or treats a blocked package-manager command as passed.
178
+ environment, SQLite or runtime snapshots. Removing a configured name drops its
179
+ recovered value and status immediately. A missing value remains absent: Desktop
180
+ never substitutes a dummy credential or treats a blocked package-manager command as passed.
181
+
182
+ #### Login-shell recovery and resolution status
183
+
184
+ The login-shell probe runs `$SHELL -l -i -c 'printf …'` and reads only the requested
185
+ names from a block bracketed by sentinels. Text a profile prints outside that block
186
+ (banners, instant prompts) is ignored. A complete block is accepted **whatever the
187
+ shell's exit status**; a non-zero exit is kept only as a diagnostic.
188
+
189
+ - **Asynchronous warm cache.** Desktop probes in the background when a project opens,
190
+ when its configured names change, and about a minute before a successful result
191
+ expires. The probe never blocks the server's event loop. Its budget is 10 seconds by
192
+ default; set `SPECRAILS_LOGIN_SHELL_TIMEOUT_MS` (milliseconds, capped at 120000) to
193
+ change it. The refresh stops when the project is removed.
194
+ - **Spawn-time reads.** Rail jobs, loop steps and retained runtime controls read the
195
+ warm cache synchronously. Only a name that was never checked (or whose success
196
+ expired) gets the short synchronous fallback, bounded at 1.5 seconds. When that
197
+ fallback fails, the asynchronous probe is started immediately so the next spawn can
198
+ use its result.
199
+ - **Lifetimes.** Recovered values stay valid for 10 minutes and are refreshed before
200
+ then; a failed refresh keeps a still-valid value. Failed or empty lookups are retried
201
+ after 30 seconds. Changing `SHELL`, `HOME` or `ZDOTDIR` discards cached results.
202
+
203
+ Each configured name has a value-free status, kept in the owning project's memory
204
+ together with the shell used and the time of the check:
205
+
206
+ | Status | Meaning |
207
+ |--------|---------|
208
+ | `inherited` | Present in the environment Desktop started with. |
209
+ | `recovered` | Read from the login shell. |
210
+ | `not-defined` | The probe succeeded but the shell does not export the name (also used on Windows, where there is no login-shell recovery). |
211
+ | `probe-timeout` | The shell did not print the block within the budget. |
212
+ | `probe-failed` | The shell could not be started, or its output had no complete sentinel block. |
213
+
214
+ `GET /api/projects/:projectId/env-passthrough/status` returns these statuses
215
+ (`pending` appears only for a name that has never been checked), and
216
+ `POST /api/projects/:projectId/env-passthrough/recheck` probes immediately. The
217
+ Project Settings environment card shows them as chips with a **Check again** action,
218
+ and the MCP `specrails_env` tool exposes them through `get`, `status` and `recheck`.
219
+ When a rail, loop or queued job starts with a configured name unresolved, its run log
220
+ receives one aggregated warning, for example
221
+ `[environment] NODE_AUTH_TOKEN not available (probe-timeout); configure it in the login shell or launch from a terminal`.
222
+ Statuses, logs and MCP output contain names and states only, never values.
183
223
 
184
224
  A few env vars are read directly by the app. The two most useful operational ones:
185
225
 
186
226
  | Variable | Description |
187
227
  |----------|-------------|
188
228
  | `SPECRAILS_TECH_URL` | Fallback specrails-tech base URL. Resolution order is the `specrails_tech_url` app setting **first**, then this env var, then `http://localhost:3000` — so a stored app setting takes precedence over this var. |
229
+ | `SPECRAILS_LOGIN_SHELL_TIMEOUT_MS` | Budget in milliseconds for the asynchronous login-shell probe that recovers configured project environment names (default `10000`, capped at `120000`). The synchronous cold fallback keeps its 1.5 s bound. See [Login-shell recovery](#login-shell-recovery-and-resolution-status). |
189
230
  | `WM_ZOMBIE_TIMEOUT_MS` | Zombie-job detection timeout in milliseconds (default `1800000` = 30 min). This is a stuck-job watchdog, **not** a hard "kill after N minutes" cap. |
190
231
 
191
232
  ### Provider rollback gates
@@ -205,6 +246,40 @@ its native key/config, and Kimi via `kimi login` or user-managed
205
246
  `~/.kimi-code/config.toml`. See [Kimi](../kimi.md), [Codex](../codex.md), and
206
247
  [Gemini](../gemini.md).
207
248
 
249
+ ### Core agent sessions (rollout)
250
+
251
+ | Variable | Effect |
252
+ |----------|--------|
253
+ | `SPECRAILS_CORE_SESSIONS` | `auto` (default) uses Core's agent session host (`specrails-core runtime host`) when the selected Core advertises the `sessions` capability and its session contract is compatible. `on` behaves like `auto` but is reported as forced. `off` keeps conversational features on their legacy transports. It cannot enable sessions on a Core that lacks them, and Desktop logs the reason. |
254
+
255
+ With sessions enabled, Desktop runs one Core host per project, plus one for
256
+ project-less missions. Session journals live in
257
+ `~/.specrails/sessions/<project>/sessions.sqlite`, owned by Core. Desktop
258
+ stores only a rebuildable projection. See
259
+ [agent sessions](../../server/modules/agent-sessions/README.md).
260
+
261
+ **Allow sub-agents** is a setting, not a flag, and it is off by default. The
262
+ project setting `allowSubagents` (`config.allow_subagents`) applies to that
263
+ project's conversational agents (missions, explore, refinements). The app
264
+ setting (`agent_allow_subagents` in `desktop_settings`) covers missions without
265
+ a project. Implement pipelines never read it. Desktop sends the resolved value
266
+ to Core as `policy.subagents`. Core enforces it through the provider switch, and
267
+ it also stops any sub-agent a provider starts anyway. A change reaches open
268
+ sessions immediately: idle sessions apply it, and sessions with running
269
+ sub-agents report it as deferred. The setting is read-only over the Specrails
270
+ MCP, so an agent cannot grant itself sub-agents.
271
+
272
+ **Run sub-agents with** chooses who launches them. The project setting
273
+ `subagentRuntime` (`config.subagent_runtime`, JSON) and the app setting
274
+ (`agent_subagent_runtime` in `desktop_settings`) hold `null` (the default: the
275
+ mission agent's own provider launches native sub-agents) or
276
+ `{ provider, model, effort }`. Missions on that provider keep native sub-agents
277
+ with that model and effort; missions on another provider get sub-agents that
278
+ Core launches on the chosen provider (delegated), at the cost of a cold start
279
+ and no shared prompt cache. The UI asks for confirmation before leaving the
280
+ default. The setting is read-only over the Specrails MCP. See
281
+ [agent sessions](agent-sessions.md#sub-agent-runtime-who-launches-them).
282
+
208
283
  For the full catalogue of feature flags and kill switches (`SPECRAILS_*` server gates, `VITE_FEATURE_*` client flags, `SPECRAILS_CODEX_BETA`, `SPECRAILS_GEMINI_BETA`, `SPECRAILS_EXPLORE_*`), see [../customizing.md](../customizing.md#environment-variables).
209
284
 
210
285
  ---
@@ -13,15 +13,22 @@ framework is rejected rather than silently replacing an update.
13
13
 
14
14
  ## Persistence and publication
15
15
 
16
- The release bundle pins Core 6.4.0 in `desktop-release.yml` and
16
+ The release bundle pins Core 6.5.0 in `desktop-release.yml` and
17
17
  `scripts/assemble-bundled-core.lock.json`. Core 6 is the Desktop-only engine: it
18
18
  publishes integration contract 5.1 (no standalone `update`) and ships only the
19
19
  implement and retry workflows (Batch was folded into implement; Desktop no
20
20
  longer requires `batch-implement` but stays compatible with Cores that still ship it). Update both pins together and check compatibility against
21
21
  the staged published package; retained runs still use their original runtime.
22
22
 
23
- The paired CI checkout uses the immutable Core 6.4.0 release commit
24
- `aa00781e962a30c22bbcdecc632bc2abcf3c97d2`. Bundle smoke checks use Node 22.22.3,
23
+ `scripts/assemble-bundled-core.mjs` relocates any pnpm virtual store a
24
+ dependency leaks into its tarball (today `@langchain/langgraph-sdk` ships
25
+ `dist/node_modules/.pnpm/…` and imports it by relative path) to
26
+ `<dir>/_pnpm/<entry>`, rewriting the relative imports that cross it, so the
27
+ module still loads while every staged path stays inside the MSI budget
28
+ (`MAX_STAGED_RELATIVE_PATH`, 120 characters).
29
+
30
+ The paired CI checkout uses the immutable Core 6.5.0 release commit
31
+ `187f9d24533a99fb1f37c7805b3c5b1375040d25`. Bundle smoke checks use Node 22.22.3,
25
32
  matching Core's minimum supported Node version and the Desktop release runtime.
26
33
 
27
34
  Desktop updates retain the complete npm installation, including dependencies,
@@ -140,6 +140,17 @@ If a single provider or feature is breaking the app, you can turn it off with an
140
140
 
141
141
  These are the most common ops escape hatches; see [Configuration → Environment variables](configuration.md#environment-variables) for the complete list of gates and flags.
142
142
 
143
+ ### Missions on Core agent sessions
144
+
145
+ With `SPECRAILS_CORE_SESSIONS` at `auto` (the default) or `on`, missions run in a Core host
146
+ process per project scope (`specrails-core runtime host --stdio --scope …`).
147
+ The server log prefix is `[agent-sessions]`.
148
+
149
+ - **Status.** The server logs each scope's transitions: `starting`, `ready`, `restarting`, `degraded`. `GET /api/agent/session-hosts` lists them. A degraded scope sends new mission turns through the legacy transport, and the mission shows why, with a **Retry** button (`POST /api/agent/session-hosts/:scope/retry`). Crashes degrade a scope after repeated failures. Some causes degrade it at once, without restart attempts: another Desktop instance holding the journal (`journal_locked`), an incompatible Core (`protocol_mismatch`), or a Core without the host (`driver_unavailable`). Close the other instance or fix Core, then retry.
150
+ - **Session data.** Core owns the journals in `~/.specrails/sessions/<project>/sessions.sqlite`. Desktop's tables (`agent_session_cursors`, `agent_subagents`, `agent_subagent_events`) are a projection. If they look wrong, rebuild them for one mission with `POST /api/agent/conversations/:id/session/rebuild`. This never duplicates messages or spend.
151
+ - **Turn it off.** `SPECRAILS_CORE_SESSIONS=off` returns every mission to the legacy transports. Missions keep their history; sub-agent cards remain for past turns.
152
+ - **Live check (paid, opt-in).** `SPECRAILS_LIVE_PROVIDER_SMOKE=1 npx vitest run server/modules/missions/runtime/mission-core-sessions.live.test.ts` runs real Claude missions against the local `../specrails-core` build. Add `SPECRAILS_LIVE_PROVIDERS=claude,codex` to include Codex, and `SPECRAILS_LIVE_<PROVIDER>_MODEL` to pick a model the account can use.
153
+
143
154
  ### Per-project reset (keeps the registration)
144
155
 
145
156
  To clear one project's job history, invocations, and telemetry pointers while keeping it registered, delete just that project's `jobs.sqlite` with the app stopped:
@@ -119,7 +119,7 @@ All routes are under `/api/projects/:projectId`:
119
119
  | `POST /agent-runtime/runs/:runId/resume` | Accept `{}`, `{ "approve": ["archive"] }`, `{ "recover": ["developer"] }`, explicit `invalidate` phase IDs, or `{ "answer": "…" }` for a pending question (required while one is open) |
120
120
  | `POST /agent-runtime/runs/:runId/cancel` | Cancel a continuation owned by this controller |
121
121
 
122
- Host blockers (Core `hostBlockers: 1` / `setupCommands: 1`): the Implement recipe opts its `verify` nodes into host-owned environment repair and the `blocked` outcome. Core emits `[environment] <command> (<repository>)` and `Environment: installed …` lines on the `verification-output` channel, so they reach the readable job log through the same projection as checks. A precondition Core cannot satisfy (no network for a browser download, registry credentials, a failing `setup` command, a missing toolchain) ends the run at `host-blocked` with `completion.blocker = { kind, reason, command, args, cwd, requiredAction, evidenceId? }`; a fixer-declared `blocker` ends it at `fixer-blocked`. The bridge maps that completion (`ok: false`) to `runtimeStatus: 'blocked'`, the run row shows the blocked badge and the loop log renders "Blocked by the host environment" with the localized kind, the failing command and cwd, the required action and a copy button. Older Core packages receive the pre-blocker definition and no `setup` list.
122
+ Host blockers (Core `hostBlockers: 1` / `setupCommands: 1`): the Implement recipe opts its `verify` nodes into host-owned environment repair and the `blocked` outcome. Core emits `[environment] <command> (<repository>)` and `Environment: installed …` lines on the `verification-output` channel, so they reach the readable job log through the same projection as checks. A precondition Core cannot satisfy (no network for a browser download, registry credentials, a failing `setup` command, a missing toolchain) ends the run at `host-blocked` with `completion.blocker = { kind, reason, command, args, cwd, requiredAction, evidenceId? }`; a fixer-declared `blocker` ends it at `fixer-blocked`. A developer-declared `blocker` ends the run at `developer-blocked` only for kinds the person must provide (`credential`, `network`, `environment-variable`); any other kind goes to host verification first: a host precondition ends at `host-blocked`, a reproduced failure goes to the fixer as a correction, and passing checks reach the reviewer, whose rejection of unfinished tasks is corrected within the same budget. The developer loop is bounded like corrections: when `tasks` finds approved tasks still unchecked, the developer runs again only while its last turn changed the candidate (`implementation-progress`, otherwise `implementation-stalled`) and within `implementationLimit` = 3 attempts (`implementation-exhausted`); both ends report the developer's `incomplete` list and summary. The bridge maps that completion (`ok: false`) to `runtimeStatus: 'blocked'`, the run row shows the blocked badge and the loop log renders "Blocked by the host environment" with the localized kind, the failing command and cwd, the required action and a copy button. Older Core packages receive the pre-blocker definition and no `setup` list.
123
123
 
124
124
  Resume responds `202` after admission and continues asynchronously. Legacy phase IDs are `architect`, `developer`, `fixer`, `verify`, `reviewer` and `archive`. For engine v2, use `POST /loop-runs/:runId/resume` instead. `recover` contains exact attempt IDs from `GET /loop-runs/:runId/recovery`; approval and answer controls use pending interrupt IDs. Node paths are display context, not recovery identities. The saved-execution panel and job header expose explicit per-attempt selection. Core's lease and Desktop's execution/mount claim jointly prevent competing writers. Resident human pauses keep their original settlement callback; restart recovery reconnects to the frozen delivery allocation and terminal outbox.
125
125
 
@@ -133,6 +133,21 @@ Both temporary checkouts use non-force cleanup, including after a Git runner exc
133
133
  including proofs for historical misplaced links; symlinked destination ancestors confer no
134
134
  authority. Registration paths, frozen manifests and Core scope remain unchanged. A failed run
135
135
  keeps its old checkout; after updating Desktop, a fresh Relaunch receives the corrected layout.
136
+ - Warm reuse is gated by dependency freshness. Before preparing a new link for a package
137
+ directory, Desktop compares its dependency inputs in the base checkout and in the worktree: the
138
+ nearest lockfile found walking up to the Git top level (first of `pnpm-lock.yaml`, `yarn.lock`,
139
+ `package-lock.json`, `npm-shrinkwrap.json`, `bun.lock`, at the same relative position) and a
140
+ canonical digest of the `package.json` dependency, devDependency, optionalDependency and
141
+ peerDependency fields (scripts and other metadata are ignored). If either input differs, or one
142
+ cannot be read, the directory gets no `node_modules` and the run reports, on the
143
+ `rail.overlay_degraded` channel, for example
144
+ `apps/catalog: yarn.lock differs from the base checkout; dependencies will be installed in the worktree`.
145
+ Core's environment preparation then installs that directory cold from the worktree's own lockfile.
146
+ This prevents a base checkout on another branch, or behind the remote, from supplying packages
147
+ the run's code does not declare. Desktop never checks out, pulls or installs anything in the
148
+ user's checkout to make warm reuse possible. Links already present for a resumed run keep their
149
+ authentication; the guard only applies when a link is prepared. `SPECRAILS_WORKTREE_NODE_MODULES=false`
150
+ still disables warm reuse entirely.
136
151
  - Per-unit settlement returns structured execution + delivery results. `onLoopRunFinished` receives
137
152
  the engine outcome only; commit/status/ref/provenance/push failures cannot rewrite it.
138
153
  - Before staging, settlement blocks active OpenSpec change directories introduced relative to
@@ -5,7 +5,7 @@ architectural dependency declaration. Search a heading or filename and read only
5
5
  the relevant source and tests. Feature prefixes group the legacy server files;
6
6
  new bounded modules live under `server/modules/`.
7
7
 
8
- Includes 1018 source/build files. Nearby tests are linked where names
8
+ Includes 1044 source/build files. Nearby tests are linked where names
9
9
  match; integration suites may live elsewhere. Use `rg` to find other consumers.
10
10
 
11
11
  ## cli
@@ -457,7 +457,9 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
457
457
  - [AgentRefChip.tsx](../../client/src/features/missions/components/AgentRefChip.tsx)
458
458
  - [AgentRunFailureMarker.tsx](../../client/src/features/missions/components/AgentRunFailureMarker.tsx)
459
459
  - [AgentRuntimeSelector.tsx](../../client/src/features/missions/components/AgentRuntimeSelector.tsx)
460
+ - [AgentSessionIndicators.tsx](../../client/src/features/missions/components/AgentSessionIndicators.tsx)
460
461
  - [AgentSpecDraftCard.tsx](../../client/src/features/missions/components/AgentSpecDraftCard.tsx)
462
+ - [AgentSubagentsCard.tsx](../../client/src/features/missions/components/AgentSubagentsCard.tsx)
461
463
  - [AgentThinkingHalo.tsx](../../client/src/features/missions/components/AgentThinkingHalo.tsx) · [test](../../client/src/features/missions/components/__tests__/AgentThinkingHalo.test.tsx)
462
464
  - [AgentTierChip.tsx](../../client/src/features/missions/components/AgentTierChip.tsx)
463
465
  - [AgentToolbarSelector.tsx](../../client/src/features/missions/components/AgentToolbarSelector.tsx)
@@ -486,6 +488,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
486
488
  - [AgentChatContext.tsx](../../client/src/features/missions/context/AgentChatContext.tsx) · [test](../../client/src/features/missions/context/AgentChatContext.test.tsx)
487
489
  - [AgentWorkspaceContext.tsx](../../client/src/features/missions/context/AgentWorkspaceContext.tsx) · [test](../../client/src/features/missions/context/__tests__/AgentWorkspaceContext.test.tsx)
488
490
  - [MinimizedChatsContext.tsx](../../client/src/features/missions/context/MinimizedChatsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MinimizedChatsContext.test.tsx)
491
+ - [MissionSessionsContext.tsx](../../client/src/features/missions/context/MissionSessionsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MissionSessionsContext.test.tsx)
489
492
  - [MissionSplitViewsContext.tsx](../../client/src/features/missions/context/MissionSplitViewsContext.tsx)
490
493
  - [MissionWindowsContext.tsx](../../client/src/features/missions/context/MissionWindowsContext.tsx) · [test](../../client/src/features/missions/context/__tests__/MissionWindowsContext.test.tsx)
491
494
 
@@ -500,10 +503,12 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
500
503
  - [agent-context-palette.ts](../../client/src/features/missions/lib/agent-context-palette.ts) · [test](../../client/src/features/missions/lib/__tests__/agent-context-palette.test.ts)
501
504
  - [agent-refs.ts](../../client/src/features/missions/lib/agent-refs.ts) · [test](../../client/src/features/missions/lib/__tests__/agent-refs.test.ts)
502
505
  - [mission-search.ts](../../client/src/features/missions/lib/mission-search.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-search.test.ts)
506
+ - [mission-sessions.ts](../../client/src/features/missions/lib/mission-sessions.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-sessions.test.ts)
503
507
  - [mission-split-layout.ts](../../client/src/features/missions/lib/mission-split-layout.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-split-layout.test.ts)
504
508
  - [mission-view-state.ts](../../client/src/features/missions/lib/mission-view-state.ts)
505
509
  - [mission-window-controller.ts](../../client/src/features/missions/lib/mission-window-controller.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-window-controller.test.ts)
506
510
  - [mission-windows.ts](../../client/src/features/missions/lib/mission-windows.ts) · [test](../../client/src/features/missions/lib/__tests__/mission-windows.test.ts)
511
+ - [subagent-result.ts](../../client/src/features/missions/lib/subagent-result.ts) · [test](../../client/src/features/missions/lib/__tests__/subagent-result.test.ts)
507
512
 
508
513
  ## client/src/features/plugins/lib
509
514
 
@@ -600,6 +605,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
600
605
  - [RuntimeRetention.tsx](../../client/src/features/settings/components/RuntimeRetention.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeRetention.test.tsx)
601
606
  - [RuntimeRolePrompts.tsx](../../client/src/features/settings/components/RuntimeRolePrompts.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeRolePrompts.test.tsx)
602
607
  - [RuntimeSteering.tsx](../../client/src/features/settings/components/RuntimeSteering.tsx) · [test](../../client/src/features/settings/components/__tests__/RuntimeSteering.test.tsx)
608
+ - [SubagentsSettingsSection.tsx](../../client/src/features/settings/components/SubagentsSettingsSection.tsx) · [test](../../client/src/features/settings/components/__tests__/SubagentsSettingsSection.test.tsx)
603
609
  - [TerminalSettingsSection.tsx](../../client/src/features/settings/components/TerminalSettingsSection.tsx)
604
610
 
605
611
  ## client/src/features/settings/components/pickers
@@ -842,7 +848,9 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
842
848
  - [generate-icons.mjs](../../scripts/generate-icons.mjs)
843
849
  - [generate-mobile-types.mjs](../../scripts/generate-mobile-types.mjs)
844
850
  - [generate-source-map.mjs](../../scripts/generate-source-map.mjs) · [test](../../scripts/generate-source-map.test.mjs)
851
+ - [module-references.mjs](../../scripts/module-references.mjs) · [test](../../scripts/module-references.test.mjs)
845
852
  - [obfuscate-chromium.mjs](../../scripts/obfuscate-chromium.mjs)
853
+ - [paired-core-shards.mjs](../../scripts/paired-core-shards.mjs) · [test](../../scripts/paired-core-shards.test.mjs)
846
854
  - [patch-node-pty.mjs](../../scripts/patch-node-pty.mjs) · [test](../../scripts/patch-node-pty.test.mjs)
847
855
  - [recover-desktop-channel.mjs](../../scripts/recover-desktop-channel.mjs) · [test](../../scripts/recover-desktop-channel.test.mjs)
848
856
  - [release-policy.mjs](../../scripts/release-policy.mjs) · [test](../../scripts/release-policy.test.mjs)
@@ -1098,6 +1106,35 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
1098
1106
  - [agent-runtime-verification-log.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-log.ts)
1099
1107
  - [agent-runtime-verification-suggestions.ts](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.ts) · [test](../../server/modules/agent-runtime/runtime/agent-runtime-verification-suggestions.test.ts)
1100
1108
 
1109
+ ## server/modules/agent-sessions
1110
+
1111
+ - [index.ts](../../server/modules/agent-sessions/index.ts)
1112
+ - [ports.ts](../../server/modules/agent-sessions/ports.ts)
1113
+
1114
+ ## server/modules/agent-sessions/adapters
1115
+
1116
+ - [host-process.ts](../../server/modules/agent-sessions/adapters/host-process.ts)
1117
+ - [http.ts](../../server/modules/agent-sessions/adapters/http.ts)
1118
+ - [rpc-client.ts](../../server/modules/agent-sessions/adapters/rpc-client.ts)
1119
+
1120
+ ## server/modules/agent-sessions/application
1121
+
1122
+ - [session-event-pump.ts](../../server/modules/agent-sessions/application/session-event-pump.ts)
1123
+
1124
+ ## server/modules/agent-sessions/domain
1125
+
1126
+ - [availability.ts](../../server/modules/agent-sessions/domain/availability.ts)
1127
+ - [errors.ts](../../server/modules/agent-sessions/domain/errors.ts)
1128
+ - [host-state.ts](../../server/modules/agent-sessions/domain/host-state.ts)
1129
+ - [projection.ts](../../server/modules/agent-sessions/domain/projection.ts)
1130
+ - [protocol.ts](../../server/modules/agent-sessions/domain/protocol.ts)
1131
+ - [subagent-policy.ts](../../server/modules/agent-sessions/domain/subagent-policy.ts)
1132
+
1133
+ ## server/modules/agent-sessions/runtime
1134
+
1135
+ - [core-sessions-availability.ts](../../server/modules/agent-sessions/runtime/core-sessions-availability.ts)
1136
+ - [session-host-registry.ts](../../server/modules/agent-sessions/runtime/session-host-registry.ts)
1137
+
1101
1138
  ## server/modules/agents/runtime
1102
1139
 
1103
1140
  - [agent-catalog.ts](../../server/modules/agents/runtime/agent-catalog.ts)
@@ -1309,10 +1346,14 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
1309
1346
  - [agent-fence-promotion.ts](../../server/modules/missions/runtime/agent-fence-promotion.ts)
1310
1347
  - [agent-input-store.ts](../../server/modules/missions/runtime/agent-input-store.ts) · [test](../../server/modules/missions/runtime/agent-input-store.test.ts)
1311
1348
  - [agent-operator-prompt.ts](../../server/modules/missions/runtime/agent-operator-prompt.ts) · [test](../../server/modules/missions/runtime/agent-operator-prompt.test.ts)
1349
+ - [agent-session-store.ts](../../server/modules/missions/runtime/agent-session-store.ts)
1312
1350
  - [agent-spec-framing.ts](../../server/modules/missions/runtime/agent-spec-framing.ts) · [test](../../server/modules/missions/runtime/agent-spec-framing.test.ts)
1313
1351
  - [agent-steering.ts](../../server/modules/missions/runtime/agent-steering.ts) · [test](../../server/modules/missions/runtime/agent-steering.test.ts)
1314
1352
  - [agent-tier.ts](../../server/modules/missions/runtime/agent-tier.ts)
1353
+ - [core-session-runner.ts](../../server/modules/missions/runtime/core-session-runner.ts) · [test](../../server/modules/missions/runtime/core-session-runner.test.ts)
1354
+ - [mission-core-sessions.ts](../../server/modules/missions/runtime/mission-core-sessions.ts) · [test](../../server/modules/missions/runtime/mission-core-sessions.test.ts)
1315
1355
  - [mission-run-notify.ts](../../server/modules/missions/runtime/mission-run-notify.ts) · [test](../../server/modules/missions/runtime/mission-run-notify.test.ts)
1356
+ - [mission-session-projector.ts](../../server/modules/missions/runtime/mission-session-projector.ts) · [test](../../server/modules/missions/runtime/mission-session-projector.test.ts)
1316
1357
 
1317
1358
  ## server/modules/project-settings
1318
1359
 
package/docs/mcp.md CHANGED
@@ -48,7 +48,7 @@ reading one description. The tools are:
48
48
  | `specrails_loops` | Saved loop workflows |
49
49
  | `specrails_code` | File discovery, literal content search, bounded line reads, summaries and provenance; AI summary regeneration is capability-gated |
50
50
  | `specrails_git` | Repository/branch/worktree information, status, diffs and PR lookup |
51
- | `specrails_env` | Project environment readiness and environment-file management |
51
+ | `specrails_env` | Project environment readiness and environment-file management; `get`/`status`/`recheck` report each configured name's value-free resolution status |
52
52
  | `specrails_support` | Support triage, local diagnostics and specrails-core update workflows |
53
53
  | `specrails_setup` | Add-project setup wizard surface |
54
54
  | `specrails_analytics` | Per-project spending analytics + budget |
@@ -77,6 +77,8 @@ When the app runs without an active bundle (a runtimes-less build, or `npm run d
77
77
 
78
78
  2. **Login-shell merge (async)** — right after the HTTP server starts listening, `augmentPathFromLoginShell()` spawns `$SHELL -l -i` once (1500 ms timeout) and merges whatever `PATH` that login + interactive shell exposes. This recovers the segments that Volta, nvm, fnm, asdf, etc. add only inside your interactive shell — whatever your `$SHELL` produces, no specific rc file is assumed. On timeout or non-zero exit, the fast-path `PATH` stays in effect and a single warning is logged.
79
79
 
80
+ Project environment names configured in Project Settings (for example `NODE_AUTH_TOKEN`) use a separate, per-project login-shell probe with a 10 s budget (`SPECRAILS_LOGIN_SHELL_TIMEOUT_MS`). It runs in the background, never writes to `process.env`, and reports a status per name. See [configuration](../internals/configuration.md#login-shell-recovery-and-resolution-status).
81
+
80
82
  The resolved `PATH` is stored on `process.env.PATH`, so every downstream spawn inherits it (`QueueManager` → provider CLI, `SetupManager` → `npx specrails-core`, `terminalManager` PTYs, etc.).
81
83
 
82
84
  ## Broken-symlink detection
@@ -6,8 +6,8 @@ This audit covers the `codex/multi-repo-projects` branch, including mission stee
6
6
 
7
7
  | Feature | Windows implementation and checks |
8
8
  | --- | --- |
9
- | Installation, native runtimes | Native x64/ARM64 NSIS and MSI builds; embedded WebView2 offline provisioning; bundled Node/Git/core/OpenSpec; staged SQLite and full ConPTY/WinPTY dependency validation. |
10
- | Desktop updates | Installer-specific Tauri targets keep NSIS and MSI separate. Missing, empty or ambiguous artifact/signature pairs fail manifest generation. Generic NSIS entries remain for older clients. Publication is serialized, and previous download installers are retained until the remote manifest matches the new release. |
9
+ | Installation, native runtimes | Native x64/ARM64 NSIS builds; WebView2 provisioning through the embedded bootstrapper; bundled Node/Git/core/OpenSpec; staged SQLite and full ConPTY/WinPTY dependency validation. |
10
+ | Desktop updates | NSIS-specific and generic Tauri targets both reference the NSIS installer; earlier MSI installs fall back to the generic entry and the NSIS installer removes the WiX installation. Missing, empty or ambiguous artifact/signature pairs fail manifest generation. Publication is serialized, and previous download installers are retained until the remote manifest matches the new release. |
11
11
  | Startup and project catalog | Recover the actual Windows profile rather than inventing `C:\Users\Default`; SQLite catalog/repository IDs reopen under the same Unicode/spaced profile. |
12
12
  | Projects and multi-repo | Repository membership and canonical identity use real paths; junction and case aliases cannot add the same repository twice. Shared backlog and per-repository jobs retain their existing regression coverage. |
13
13
  | Core setup and updates | Offline assembly from the shipped core; preserve and restore the previous active framework if Windows junction replacement fails. Installed-package smoke waits for the real workspace marker. |
@@ -28,7 +28,7 @@ This audit covers the `codex/multi-repo-projects` branch, including mission stee
28
28
 
29
29
  `.github/workflows/windows-parity.yml` runs a `windows-parity` job on `windows-latest` (x64) for every pull request and push to `main`, adding `windows-11-arm` on the `main` pushes. It installs dependencies, checks TypeScript, runs release/PTY helper regression tests, native filesystem/process tests (retried once, they drive real processes), the application build, native host build/tests, and four real native fixtures: WebView2 capture/selection, authentication popups, mission window handoff, and browser multiwindow transfer/parking. It is deliberately a separate workflow from `ci.yml`: `release.yml` waits for CI only, so a slow or flaky Windows run never delays the release PR, while Desktop Release still builds and installs on Windows before publication. The jsdom client suite is not repeated on Windows; it runs on Linux in CI. The bundled-core matrix in `ci.yml` exercises Windows junction/copy relocation for providers.
30
30
 
31
- `.github/workflows/desktop-release.yml` builds the real installers and runs `scripts/smoke-windows-installers.ps1` before publishing artifacts. For each NSIS and MSI package, the script installs into a temporary path containing spaces and drives `scripts/smoke-installed-windows.mjs` with the installed Node runtime. The driver uses an isolated user profile and tests:
31
+ `.github/workflows/desktop-release.yml` builds the real installers and runs `scripts/smoke-windows-installers.ps1` before publishing artifacts. The script fails on any MSI bundle, then installs the NSIS package into a temporary path containing spaces and drives `scripts/smoke-installed-windows.mjs` with the installed Node runtime. The driver uses an isolated user profile and tests:
32
32
 
33
33
  1. The installed pkg sidecar boots with bundled resources and authenticates API access.
34
34
  2. A temporary Git repository is registered and core assembly completes offline.
@@ -53,7 +53,7 @@ The detachable-window implementation has passed native macOS fixtures locally, i
53
53
 
54
54
  Before claiming complete Windows parity, record successful x64 and ARM64 CI/release runs and exercise the actual installed UI on supported Windows versions:
55
55
 
56
- - Clean NSIS and MSI installs, in-place upgrades from the previous release, relaunch, tray quit and uninstall. Verify one installation entry and preserved projects/logs.
56
+ - Clean NSIS installs, in-place upgrades from the previous release (including an MSI-installed release migrating through the updater), relaunch, tray quit and uninstall. Verify one installation entry and preserved projects/logs.
57
57
  - Native browsing and capture at 100%, 150% and 200% display scaling; resize, maximize, multi-monitor movement, keyboard/clipboard, popup/self-close, and the relevant Okta/SSO tenant. Detach two active missions, minimize main independently, move them between monitors, and reattach into a window with its own open browser; verify both sessions and pending inputs remain intact.
58
58
  - Provider authentication and one real mission/rail per enabled provider; steering during a tool call; cancellation; multi-repo integration/checkout with clean and conflicting working trees.
59
59
  - Long-running frontend/backend processes, immediate startup failure, stopping nested processes, app quit/update during execution, and persisted logs after restart.