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,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionRequestError = void 0;
4
+ exports.isSessionRequestError = isSessionRequestError;
5
+ /** A session protocol request failed; `code` is Core's stable error code. */
6
+ class SessionRequestError extends Error {
7
+ rpcCode;
8
+ code;
9
+ retryable;
10
+ detail;
11
+ constructor(message, data, rpcCode) {
12
+ super(message);
13
+ this.rpcCode = rpcCode;
14
+ this.name = 'SessionRequestError';
15
+ this.code = data.code;
16
+ this.retryable = data.retryable;
17
+ this.detail = data.detail;
18
+ }
19
+ }
20
+ exports.SessionRequestError = SessionRequestError;
21
+ function isSessionRequestError(error, code) {
22
+ return error instanceof SessionRequestError && (code === undefined || error.code === code);
23
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INITIAL_HOST_STATE = exports.DEFAULT_SUPERVISION = void 0;
4
+ exports.canTransition = canTransition;
5
+ exports.onDemand = onDemand;
6
+ exports.onReady = onReady;
7
+ exports.onFailure = onFailure;
8
+ exports.onFatal = onFatal;
9
+ exports.onRestartDue = onRestartDue;
10
+ exports.onRetry = onRetry;
11
+ exports.onStop = onStop;
12
+ exports.acceptsSessions = acceptsSessions;
13
+ exports.DEFAULT_SUPERVISION = Object.freeze({
14
+ baseDelayMs: 500,
15
+ maxDelayMs: 30_000,
16
+ maxFailures: 5,
17
+ failureWindowMs: 5 * 60_000,
18
+ });
19
+ exports.INITIAL_HOST_STATE = Object.freeze({ status: 'absent', failures: [] });
20
+ const TRANSITIONS = Object.freeze({
21
+ absent: ['starting', 'stopped'],
22
+ starting: ['ready', 'restarting', 'degraded', 'stopped'],
23
+ ready: ['restarting', 'degraded', 'stopped'],
24
+ restarting: ['starting', 'degraded', 'stopped'],
25
+ degraded: ['starting', 'stopped'],
26
+ stopped: [],
27
+ });
28
+ function canTransition(from, to) {
29
+ return from === to || TRANSITIONS[from].includes(to);
30
+ }
31
+ function transition(state, to, failures = state.failures) {
32
+ if (!canTransition(state.status, to))
33
+ throw new Error(`Session host: ${state.status} → ${to} is not allowed`);
34
+ return { status: to, failures };
35
+ }
36
+ /** A session is needed in this scope. */
37
+ function onDemand(state) {
38
+ if (state.status === 'absent')
39
+ return { state: transition(state, 'starting'), decision: { action: 'start' } };
40
+ return { state, decision: { action: 'none' } };
41
+ }
42
+ function onReady(state) {
43
+ return transition(state, 'ready');
44
+ }
45
+ /** The host exited unexpectedly or failed to start. */
46
+ function onFailure(state, now, policy = exports.DEFAULT_SUPERVISION) {
47
+ if (state.status === 'stopped')
48
+ return { state, decision: { action: 'none' } };
49
+ const failures = [...state.failures.filter((at) => now - at < policy.failureWindowMs), now];
50
+ if (failures.length >= policy.maxFailures) {
51
+ return { state: transition(state, 'degraded', failures), decision: { action: 'degrade', reason: `${failures.length} host failures within ${Math.round(policy.failureWindowMs / 1000)}s` } };
52
+ }
53
+ const delayMs = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (failures.length - 1));
54
+ return { state: transition(state, 'restarting', failures), decision: { action: 'restart', delayMs } };
55
+ }
56
+ /** A failure no restart can fix (e.g. another host owns the scope's journal): degrade at once. */
57
+ function onFatal(state, reason) {
58
+ if (state.status === 'stopped' || state.status === 'degraded')
59
+ return { state, decision: { action: 'none' } };
60
+ return { state: transition(state, 'degraded'), decision: { action: 'degrade', reason } };
61
+ }
62
+ /** The restart delay elapsed. */
63
+ function onRestartDue(state) {
64
+ if (state.status !== 'restarting')
65
+ return { state, decision: { action: 'none' } };
66
+ return { state: transition(state, 'starting'), decision: { action: 'start' } };
67
+ }
68
+ /** A user explicitly retries a degraded scope (e.g. after fixing Core). */
69
+ function onRetry(state) {
70
+ if (state.status !== 'degraded')
71
+ return { state, decision: { action: 'none' } };
72
+ return { state: transition(state, 'starting', []), decision: { action: 'start' } };
73
+ }
74
+ function onStop(state) {
75
+ return state.status === 'stopped' ? state : transition(state, 'stopped');
76
+ }
77
+ /** Whether new turns in this scope should use Core sessions right now. */
78
+ function acceptsSessions(state) {
79
+ return state.status !== 'degraded' && state.status !== 'stopped';
80
+ }
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initialProjection = initialProjection;
4
+ exports.reduceEnvelope = reduceEnvelope;
5
+ exports.liveSubagentCount = liveSubagentCount;
6
+ const protocol_1 = require("./protocol");
7
+ function initialProjection(sessionId, lastSeq = 0) {
8
+ return { sessionId, lastSeq, phase: 'idle', processAlive: false, openTurn: null, liveSubagents: 0, subagents: {} };
9
+ }
10
+ const KNOWN = new Set(protocol_1.KNOWN_EVENT_TYPES);
11
+ function phaseOp(state) {
12
+ return { kind: 'resident.phase', phase: state.phase, liveSubagents: state.liveSubagents, processAlive: state.processAlive };
13
+ }
14
+ function reduceEnvelope(state, envelope) {
15
+ if (envelope.sessionId !== state.sessionId)
16
+ throw new Error(`Event for ${envelope.sessionId} applied to ${state.sessionId}`);
17
+ if (envelope.seq <= state.lastSeq)
18
+ return { status: 'duplicate', state, ops: [] };
19
+ if (envelope.seq !== state.lastSeq + 1)
20
+ return { status: 'gap', state, ops: [], expected: state.lastSeq + 1 };
21
+ const next = { ...state, lastSeq: envelope.seq };
22
+ const event = envelope.event;
23
+ if (!KNOWN.has(event.type))
24
+ return { status: 'applied', state: next, ops: [] };
25
+ const ops = [];
26
+ switch (event.type) {
27
+ case 'session.phase':
28
+ next.phase = event.phase;
29
+ ops.push(phaseOp(next));
30
+ break;
31
+ case 'session.process':
32
+ next.processAlive = event.state === 'started';
33
+ ops.push(phaseOp(next));
34
+ break;
35
+ case 'session.provider-ref':
36
+ ops.push({ kind: 'provider.ref', providerSessionRef: event.providerSessionRef });
37
+ break;
38
+ case 'session.updated':
39
+ ops.push({ kind: 'session.updated', changes: event.changes, outcome: event.outcome });
40
+ break;
41
+ case 'session.closed':
42
+ ops.push({ kind: 'session.closed', reason: event.reason });
43
+ break;
44
+ case 'input.accepted':
45
+ ops.push({ kind: 'input.state', inputId: event.inputId, state: 'accepted', turnId: null, reason: null });
46
+ break;
47
+ case 'input.state':
48
+ ops.push({ kind: 'input.state', inputId: event.inputId, state: event.state, turnId: event.turnId ?? null, reason: event.reason ?? null });
49
+ break;
50
+ case 'turn.started':
51
+ next.openTurn = { turnId: event.turnId, origin: event.origin, startedAt: event.at };
52
+ ops.push({ kind: 'turn.opened', turnId: event.turnId, origin: event.origin, inputIds: [...event.inputIds], triggeredBy: [...(event.trigger?.subagentIds ?? [])], at: event.at });
53
+ break;
54
+ case 'turn.output':
55
+ ops.push({ kind: 'turn.delta', turnId: event.turnId, channel: event.channel, delta: event.delta });
56
+ break;
57
+ case 'turn.tool': {
58
+ const { type: _type, at: _at, turnId, ...tool } = event;
59
+ ops.push({ kind: 'turn.tool', turnId, tool });
60
+ break;
61
+ }
62
+ case 'turn.completed': {
63
+ const open = next.openTurn;
64
+ next.openTurn = null;
65
+ ops.push({ kind: 'turn.closed', turnId: event.turnId, origin: open?.origin ?? 'user', status: event.status, text: event.text, error: event.error ?? null, usage: event.usage, startedAt: open?.startedAt ?? event.at, at: event.at });
66
+ break;
67
+ }
68
+ case 'subagent.started': {
69
+ const existing = next.subagents[event.subagentId];
70
+ const node = existing
71
+ ? { ...existing, phase: 'running', reason: null, endedAt: null, restarts: existing.restarts + (existing.phase === 'idle' ? 1 : 0) }
72
+ : {
73
+ subagentId: event.subagentId,
74
+ parentId: event.parentId,
75
+ kind: event.kind,
76
+ agentType: event.agentType ?? null,
77
+ description: event.description,
78
+ phase: 'running',
79
+ reason: null,
80
+ restarts: 0,
81
+ startedAt: event.at,
82
+ endedAt: null,
83
+ usage: null,
84
+ toolUses: null,
85
+ durationMs: null,
86
+ resultSummary: null,
87
+ launchedInTurnId: next.openTurn?.turnId ?? null,
88
+ delegated: event.delegated ?? null,
89
+ };
90
+ next.subagents = { ...next.subagents, [node.subagentId]: node };
91
+ ops.push({ kind: 'subagent.upsert', subagent: node });
92
+ break;
93
+ }
94
+ case 'subagent.phase': {
95
+ const node = next.subagents[event.subagentId];
96
+ if (!node)
97
+ break;
98
+ const live = event.phase === 'running';
99
+ const updated = { ...node, phase: event.phase, reason: event.reason ?? (live ? null : node.reason), endedAt: live ? null : event.at };
100
+ next.subagents = { ...next.subagents, [node.subagentId]: updated };
101
+ ops.push({ kind: 'subagent.upsert', subagent: updated });
102
+ break;
103
+ }
104
+ case 'subagent.usage': {
105
+ const node = next.subagents[event.subagentId];
106
+ if (!node)
107
+ break;
108
+ const updated = { ...node, usage: event.usage, toolUses: event.toolUses ?? node.toolUses, durationMs: event.durationMs ?? node.durationMs };
109
+ next.subagents = { ...next.subagents, [node.subagentId]: updated };
110
+ ops.push({ kind: 'subagent.upsert', subagent: updated });
111
+ if (event.billing === 'separate' && node.delegated)
112
+ ops.push({ kind: 'subagent.billed', subagentId: node.subagentId, driver: node.delegated.driver, model: event.usage.model ?? node.delegated.model, usage: event.usage, seq: envelope.seq, at: event.at });
113
+ break;
114
+ }
115
+ case 'subagent.result': {
116
+ const node = next.subagents[event.subagentId];
117
+ if (!node)
118
+ break;
119
+ const updated = { ...node, resultSummary: event.summary };
120
+ next.subagents = { ...next.subagents, [node.subagentId]: updated };
121
+ ops.push({ kind: 'subagent.upsert', subagent: updated });
122
+ break;
123
+ }
124
+ case 'subagent.output':
125
+ ops.push({ kind: 'subagent.output', subagentId: event.subagentId, channel: event.channel, delta: event.delta ?? null, tool: event.tool ?? null, seq: envelope.seq });
126
+ break;
127
+ case 'subagents.settled':
128
+ next.liveSubagents = event.live;
129
+ ops.push(phaseOp(next));
130
+ break;
131
+ case 'output.truncated':
132
+ ops.push({ kind: 'notice', level: 'info', code: 'output_truncated', message: `${event.droppedEvents} output chunks (${event.droppedBytes} bytes) were not stored` });
133
+ break;
134
+ case 'provider.diagnostic':
135
+ if (event.level === 'warning')
136
+ ops.push({ kind: 'notice', level: 'warning', code: event.code, message: event.message });
137
+ break;
138
+ case 'session.opened':
139
+ case 'notice.interruption':
140
+ break;
141
+ }
142
+ return { status: 'applied', state: next, ops };
143
+ }
144
+ /** Number of sub-agents the provider is still running. */
145
+ function liveSubagentCount(state) {
146
+ return Object.values(state.subagents).filter((node) => node.phase === 'running').length;
147
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Session protocol v1 wire types, as Desktop consumes them from the Core
4
+ * session host (`specrails-core runtime host --stdio`). This is Desktop's
5
+ * anti-corruption layer for the protocol: Core's internals never leak in.
6
+ * Drift is checked against the resolved Core's integration contract
7
+ * (`agentRuntime.sessions`) by `checkSessionContract` and check-core-compat.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.KNOWN_EVENT_TYPES = exports.SUPPORTED_PROTOCOL_VERSIONS = void 0;
11
+ exports.checkSessionContract = checkSessionContract;
12
+ exports.SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([1]);
13
+ /** Event types this Desktop understands; newer Cores may send more (ignored). */
14
+ exports.KNOWN_EVENT_TYPES = Object.freeze([
15
+ 'session.opened',
16
+ 'session.phase',
17
+ 'session.process',
18
+ 'session.provider-ref',
19
+ 'session.updated',
20
+ 'session.closed',
21
+ 'input.accepted',
22
+ 'input.state',
23
+ 'turn.started',
24
+ 'turn.output',
25
+ 'turn.tool',
26
+ 'turn.completed',
27
+ 'subagent.started',
28
+ 'subagent.phase',
29
+ 'subagent.output',
30
+ 'subagent.usage',
31
+ 'subagent.result',
32
+ 'subagents.settled',
33
+ 'output.truncated',
34
+ 'notice.interruption',
35
+ 'provider.diagnostic',
36
+ ]);
37
+ /** Compare a Core contract block with what this Desktop speaks. Pure. */
38
+ function checkSessionContract(block) {
39
+ if (!block)
40
+ return { compatible: false, protocolVersion: null, unknownEventTypes: [], missingEventTypes: [], reasons: ['Core does not declare agentRuntime.sessions'] };
41
+ const common = block.protocolVersions.filter((version) => exports.SUPPORTED_PROTOCOL_VERSIONS.includes(version));
42
+ const declared = new Set(block.eventTypes);
43
+ const known = new Set(exports.KNOWN_EVENT_TYPES);
44
+ const missingEventTypes = exports.KNOWN_EVENT_TYPES.filter((type) => !declared.has(type));
45
+ const reasons = [];
46
+ if (common.length === 0)
47
+ reasons.push(`No common protocol version (Core ${block.protocolVersions.join(', ')}; Desktop ${exports.SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`);
48
+ if (block.cliOperation !== 'host')
49
+ reasons.push(`Unexpected session CLI operation "${block.cliOperation}"`);
50
+ if (missingEventTypes.length)
51
+ reasons.push(`Core no longer declares: ${missingEventTypes.join(', ')}`);
52
+ return {
53
+ compatible: reasons.length === 0,
54
+ protocolVersion: common.length ? Math.max(...common) : null,
55
+ unknownEventTypes: block.eventTypes.filter((type) => !known.has(type)),
56
+ missingEventTypes,
57
+ reasons,
58
+ };
59
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSubagentPolicy = resolveSubagentPolicy;
4
+ exports.resolveSubagentRuntime = resolveSubagentRuntime;
5
+ /** The project setting decides; conversations without a project use the global one. */
6
+ function resolveSubagentPolicy(input) {
7
+ const allowed = input.projectAllows ?? input.globalAllows;
8
+ return allowed ? 'enabled' : 'disabled';
9
+ }
10
+ /**
11
+ * Hybrid runtime (core-agent-sessions-host D9): native unless the user chose a
12
+ * provider other than the mission's; then Core launches them. Overrides a
13
+ * driver cannot apply are dropped (the UI never offers them).
14
+ */
15
+ function resolveSubagentRuntime(input) {
16
+ const { choice } = input;
17
+ if (!choice)
18
+ return { runtime: { mode: 'native' } };
19
+ if (choice.provider === input.missionProvider) {
20
+ const caps = input.drivers.find((driver) => driver.id === input.missionProvider)?.capabilities;
21
+ return {
22
+ runtime: {
23
+ mode: 'native',
24
+ ...(caps?.subagentModel ? { model: choice.model } : {}),
25
+ ...(caps?.subagentEffort && choice.effort ? { effort: choice.effort } : {}),
26
+ },
27
+ };
28
+ }
29
+ if (!input.delegation)
30
+ return { runtime: { mode: 'native' }, unavailable: 'delegation_unsupported' };
31
+ if (!input.drivers.some((driver) => driver.id === choice.provider))
32
+ return { runtime: { mode: 'native' }, unavailable: 'driver_unavailable' };
33
+ return { runtime: { mode: 'delegated', driver: choice.provider, model: choice.model, ...(choice.effort ? { effort: choice.effort } : {}) } };
34
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionEventPump = exports.resolveCoreSessionsAvailability = exports.parseCoreSessionsFlag = exports.DEFAULT_CORE_SESSIONS_FLAG = exports.acceptsSessions = exports.DEFAULT_SUPERVISION = exports.resolveSubagentRuntime = exports.resolveSubagentPolicy = exports.reduceEnvelope = exports.liveSubagentCount = exports.initialProjection = exports.isSessionRequestError = exports.SessionRequestError = exports.checkSessionContract = exports.KNOWN_EVENT_TYPES = exports.SUPPORTED_PROTOCOL_VERSIONS = void 0;
4
+ /**
5
+ * Agent sessions: Desktop as the host of Core's agent session runtime.
6
+ * Public API for conversational features (missions first). Adapters and the
7
+ * runtime registry are exposed through focused subpaths, not this barrel.
8
+ */
9
+ var protocol_1 = require("./domain/protocol");
10
+ Object.defineProperty(exports, "SUPPORTED_PROTOCOL_VERSIONS", { enumerable: true, get: function () { return protocol_1.SUPPORTED_PROTOCOL_VERSIONS; } });
11
+ Object.defineProperty(exports, "KNOWN_EVENT_TYPES", { enumerable: true, get: function () { return protocol_1.KNOWN_EVENT_TYPES; } });
12
+ Object.defineProperty(exports, "checkSessionContract", { enumerable: true, get: function () { return protocol_1.checkSessionContract; } });
13
+ var errors_1 = require("./domain/errors");
14
+ Object.defineProperty(exports, "SessionRequestError", { enumerable: true, get: function () { return errors_1.SessionRequestError; } });
15
+ Object.defineProperty(exports, "isSessionRequestError", { enumerable: true, get: function () { return errors_1.isSessionRequestError; } });
16
+ var projection_1 = require("./domain/projection");
17
+ Object.defineProperty(exports, "initialProjection", { enumerable: true, get: function () { return projection_1.initialProjection; } });
18
+ Object.defineProperty(exports, "liveSubagentCount", { enumerable: true, get: function () { return projection_1.liveSubagentCount; } });
19
+ Object.defineProperty(exports, "reduceEnvelope", { enumerable: true, get: function () { return projection_1.reduceEnvelope; } });
20
+ var subagent_policy_1 = require("./domain/subagent-policy");
21
+ Object.defineProperty(exports, "resolveSubagentPolicy", { enumerable: true, get: function () { return subagent_policy_1.resolveSubagentPolicy; } });
22
+ Object.defineProperty(exports, "resolveSubagentRuntime", { enumerable: true, get: function () { return subagent_policy_1.resolveSubagentRuntime; } });
23
+ var host_state_1 = require("./domain/host-state");
24
+ Object.defineProperty(exports, "DEFAULT_SUPERVISION", { enumerable: true, get: function () { return host_state_1.DEFAULT_SUPERVISION; } });
25
+ Object.defineProperty(exports, "acceptsSessions", { enumerable: true, get: function () { return host_state_1.acceptsSessions; } });
26
+ var availability_1 = require("./domain/availability");
27
+ Object.defineProperty(exports, "DEFAULT_CORE_SESSIONS_FLAG", { enumerable: true, get: function () { return availability_1.DEFAULT_CORE_SESSIONS_FLAG; } });
28
+ Object.defineProperty(exports, "parseCoreSessionsFlag", { enumerable: true, get: function () { return availability_1.parseCoreSessionsFlag; } });
29
+ Object.defineProperty(exports, "resolveCoreSessionsAvailability", { enumerable: true, get: function () { return availability_1.resolveCoreSessionsAvailability; } });
30
+ var session_event_pump_1 = require("./application/session-event-pump");
31
+ Object.defineProperty(exports, "SessionEventPump", { enumerable: true, get: function () { return session_event_pump_1.SessionEventPump; } });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readSelectedSessionContract = readSelectedSessionContract;
7
+ exports.coreSessionsAvailability = coreSessionsAvailability;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const agent_runtime_loader_1 = require("../../agent-runtime/runtime/agent-runtime-loader");
11
+ const availability_1 = require("../domain/availability");
12
+ const protocol_1 = require("../domain/protocol");
13
+ /** Read `agentRuntime.sessions` from the selected Core package next to its runtime CLI. */
14
+ function readSelectedSessionContract(cli = (0, agent_runtime_loader_1.findCoreAgentRuntimeCli)()) {
15
+ if (!cli)
16
+ return null;
17
+ const contract = node_path_1.default.resolve(node_path_1.default.dirname(cli), '..', '..', 'integration-contract.json');
18
+ if (!node_fs_1.default.existsSync(contract))
19
+ return null;
20
+ try {
21
+ return JSON.parse(node_fs_1.default.readFileSync(contract, 'utf8')).agentRuntime?.sessions ?? null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ /**
28
+ * Decide once per call whether Core sessions are usable now. Any failure to
29
+ * probe Core degrades to legacy transports with an explicit reason.
30
+ */
31
+ async function coreSessionsAvailability(env = process.env) {
32
+ const flag = (0, availability_1.parseCoreSessionsFlag)(env.SPECRAILS_CORE_SESSIONS);
33
+ if (flag === 'off')
34
+ return (0, availability_1.resolveCoreSessionsAvailability)(flag, null);
35
+ try {
36
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
37
+ const contract = (0, protocol_1.checkSessionContract)(readSelectedSessionContract());
38
+ return (0, availability_1.resolveCoreSessionsAvailability)(flag, runtime.api?.capabilities, contract.compatible);
39
+ }
40
+ catch (error) {
41
+ return { enabled: false, flag, reason: `Core runtime probe failed: ${error.message}` };
42
+ }
43
+ }
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionHostRegistry = void 0;
4
+ const session_event_pump_1 = require("../application/session-event-pump");
5
+ const errors_1 = require("../domain/errors");
6
+ const host_state_1 = require("../domain/host-state");
7
+ /** Failures a restart cannot fix: the scope degrades at once instead of looping. */
8
+ const FATAL_CODES = new Set(['journal_locked', 'protocol_mismatch', 'driver_unavailable']);
9
+ /**
10
+ * One supervised Core session host per scope (project key or `global`).
11
+ * Hosts start lazily, restart with backoff after unexpected exits, re-open and
12
+ * replay tracked sessions, and become degraded after repeated failures so
13
+ * callers can fall back to legacy transports for new turns.
14
+ */
15
+ class SessionHostRegistry {
16
+ options;
17
+ scopes = new Map();
18
+ stopped = false;
19
+ constructor(options) {
20
+ this.options = options;
21
+ }
22
+ status(scope) {
23
+ return this.scopes.get(scope)?.state.status ?? 'absent';
24
+ }
25
+ /** Every known scope with its status (operator view, UI notices). */
26
+ hosts() {
27
+ return [...this.scopes].map(([scope, entry]) => ({ scope, status: entry.state.status, detail: entry.degradedReason, code: entry.degradedCode }));
28
+ }
29
+ /** Why a degraded scope is unavailable, when known. */
30
+ degradedCode(scope) {
31
+ return this.scopes.get(scope)?.degradedCode ?? null;
32
+ }
33
+ /** Whether new turns in this scope should use Core sessions now. */
34
+ available(scope) {
35
+ return !this.stopped && (0, host_state_1.acceptsSessions)(this.scopes.get(scope)?.state ?? host_state_1.INITIAL_HOST_STATE);
36
+ }
37
+ /** A ready client for the scope, starting the host when needed. */
38
+ async acquire(scope) {
39
+ if (this.stopped)
40
+ throw new errors_1.SessionRequestError('Session hosts are shutting down', { code: 'busy', retryable: true });
41
+ const entry = this.entry(scope);
42
+ if (entry.client && !this.isClosed(entry.client))
43
+ return entry.client;
44
+ if (entry.state.status === 'degraded') {
45
+ throw new errors_1.SessionRequestError(`Core sessions are unavailable for ${scope}: ${entry.degradedReason ?? 'repeated host failures'}`, { code: 'host_degraded', retryable: false });
46
+ }
47
+ if (entry.starting)
48
+ return entry.starting;
49
+ if (entry.state.status === 'ready') {
50
+ // The client closed before its close event was processed: count it as a failure.
51
+ this.fail(scope, entry, 'Session host connection closed');
52
+ if (this.status(scope) === 'degraded')
53
+ return this.acquire(scope);
54
+ }
55
+ if (entry.state.status === 'restarting') {
56
+ // A caller needs the host now: start immediately instead of waiting for the backoff timer.
57
+ entry.restartTimer?.cancel();
58
+ entry.restartTimer = null;
59
+ entry.state = (0, host_state_1.onRestartDue)(entry.state).state;
60
+ }
61
+ else if (entry.state.status === 'absent') {
62
+ entry.state = (0, host_state_1.onDemand)(entry.state).state;
63
+ }
64
+ return this.start(scope, entry);
65
+ }
66
+ /**
67
+ * Follow a session: its events are applied to `sink` and reported to
68
+ * `listener`. Survives host restarts (resume + replay from the sink cursor).
69
+ */
70
+ async track(scope, sessionId, sink, listener) {
71
+ const client = await this.acquire(scope);
72
+ const entry = this.entry(scope);
73
+ const tracked = entry.tracked.get(sessionId) ?? { sessionId, sink, listener, pump: null };
74
+ entry.tracked.set(sessionId, tracked);
75
+ tracked.pump?.detach();
76
+ tracked.pump = new session_event_pump_1.SessionEventPump(sessionId, client, sink, listener);
77
+ await tracked.pump.attach();
78
+ return tracked.pump;
79
+ }
80
+ untrack(scope, sessionId) {
81
+ const entry = this.scopes.get(scope);
82
+ const tracked = entry?.tracked.get(sessionId);
83
+ tracked?.pump?.detach();
84
+ entry?.tracked.delete(sessionId);
85
+ }
86
+ /** Manual retry of a degraded scope. */
87
+ async retry(scope) {
88
+ const entry = this.entry(scope);
89
+ const result = (0, host_state_1.onRetry)(entry.state);
90
+ if (result.decision.action !== 'start')
91
+ return;
92
+ entry.state = result.state;
93
+ entry.degradedReason = null;
94
+ entry.degradedCode = null;
95
+ await this.start(scope, entry);
96
+ }
97
+ /** Graceful stop of one scope (project removed). */
98
+ async stop(scope) {
99
+ const entry = this.scopes.get(scope);
100
+ if (!entry)
101
+ return;
102
+ entry.restartTimer?.cancel();
103
+ entry.state = (0, host_state_1.onStop)(entry.state);
104
+ for (const tracked of entry.tracked.values())
105
+ tracked.pump?.detach();
106
+ for (const unsubscribe of entry.unsubscribe.splice(0))
107
+ unsubscribe();
108
+ const client = entry.client ?? (entry.starting ? await entry.starting.catch(() => null) : null);
109
+ entry.client = null;
110
+ this.options.onStatus?.(scope, 'stopped');
111
+ if (client)
112
+ await client.close();
113
+ this.scopes.delete(scope);
114
+ }
115
+ /** App shutdown. */
116
+ async stopAll() {
117
+ this.stopped = true;
118
+ await Promise.allSettled([...this.scopes.keys()].map((scope) => this.stop(scope)));
119
+ }
120
+ // ── internals ─────────────────────────────────────────────────────────────
121
+ entry(scope) {
122
+ let entry = this.scopes.get(scope);
123
+ if (!entry) {
124
+ entry = { state: host_state_1.INITIAL_HOST_STATE, client: null, starting: null, restartTimer: null, tracked: new Map(), unsubscribe: [], degradedReason: null, degradedCode: null };
125
+ this.scopes.set(scope, entry);
126
+ }
127
+ return entry;
128
+ }
129
+ isClosed(client) {
130
+ return client.closed === true;
131
+ }
132
+ start(scope, entry) {
133
+ this.options.onStatus?.(scope, 'starting');
134
+ entry.starting = (async () => {
135
+ try {
136
+ const client = await this.options.launcher.launch(scope);
137
+ if (entry.state.status === 'stopped') {
138
+ await client.close();
139
+ throw new errors_1.SessionRequestError('Scope stopped', { code: 'busy', retryable: false });
140
+ }
141
+ entry.client = client;
142
+ entry.state = (0, host_state_1.onReady)(entry.state);
143
+ this.wire(scope, entry, client);
144
+ this.options.onStatus?.(scope, 'ready');
145
+ await this.reattach(entry, client);
146
+ return client;
147
+ }
148
+ catch (error) {
149
+ if (entry.state.status !== 'stopped') {
150
+ if ((0, errors_1.isSessionRequestError)(error) && FATAL_CODES.has(error.code))
151
+ this.fatal(scope, entry, error.code, error.message);
152
+ else
153
+ this.fail(scope, entry, error.message);
154
+ }
155
+ throw error;
156
+ }
157
+ finally {
158
+ entry.starting = null;
159
+ }
160
+ })();
161
+ return entry.starting;
162
+ }
163
+ wire(scope, entry, client) {
164
+ for (const unsubscribe of entry.unsubscribe.splice(0))
165
+ unsubscribe();
166
+ entry.unsubscribe.push(client.onEvent((envelope) => { void entry.tracked.get(envelope.sessionId)?.pump?.push(envelope).catch((error) => this.options.onStatus?.(scope, entry.state.status, `projection failed: ${error.message}`)); }), client.onLagged((sessionId) => { void entry.tracked.get(sessionId)?.pump?.catchUp().catch(() => undefined); }), client.onClose((reason, code) => {
167
+ if (entry.client !== client || entry.state.status === 'stopped')
168
+ return;
169
+ entry.client = null;
170
+ if (code && FATAL_CODES.has(code))
171
+ this.fatal(scope, entry, code, reason);
172
+ else
173
+ this.fail(scope, entry, reason);
174
+ }));
175
+ }
176
+ /** After a (re)start: resume every tracked session and replay from its cursor. */
177
+ async reattach(entry, client) {
178
+ for (const tracked of entry.tracked.values()) {
179
+ tracked.pump?.detach();
180
+ try {
181
+ await client.request('session.open', { resume: { sessionId: tracked.sessionId } });
182
+ }
183
+ catch (error) {
184
+ // Closed sessions replay as history; a session Core never opened has nothing to resume.
185
+ if (!(error instanceof errors_1.SessionRequestError) || (error.code !== 'session_closed' && error.code !== 'session_not_found'))
186
+ throw error;
187
+ if (error.code === 'session_not_found')
188
+ continue;
189
+ }
190
+ tracked.pump = new session_event_pump_1.SessionEventPump(tracked.sessionId, client, tracked.sink, tracked.listener);
191
+ await tracked.pump.attach();
192
+ }
193
+ }
194
+ fatal(scope, entry, code, reason) {
195
+ const result = (0, host_state_1.onFatal)(entry.state, reason);
196
+ entry.state = result.state;
197
+ if (result.decision.action !== 'degrade')
198
+ return;
199
+ entry.restartTimer?.cancel();
200
+ entry.restartTimer = null;
201
+ entry.degradedReason = reason;
202
+ entry.degradedCode = code;
203
+ this.options.onStatus?.(scope, 'degraded', reason, code);
204
+ }
205
+ fail(scope, entry, reason) {
206
+ const result = (0, host_state_1.onFailure)(entry.state, this.options.clock.now(), this.options.policy ?? host_state_1.DEFAULT_SUPERVISION);
207
+ entry.state = result.state;
208
+ if (result.decision.action === 'degrade') {
209
+ entry.degradedReason = `${result.decision.reason}; last: ${reason}`;
210
+ this.options.onStatus?.(scope, 'degraded', entry.degradedReason);
211
+ return;
212
+ }
213
+ if (result.decision.action === 'restart') {
214
+ this.options.onStatus?.(scope, 'restarting', reason);
215
+ entry.restartTimer?.cancel();
216
+ entry.restartTimer = this.options.clock.after(result.decision.delayMs, () => {
217
+ entry.restartTimer = null;
218
+ const due = (0, host_state_1.onRestartDue)(entry.state);
219
+ entry.state = due.state;
220
+ if (due.decision.action === 'start')
221
+ void this.start(scope, entry).catch(() => undefined);
222
+ });
223
+ }
224
+ }
225
+ }
226
+ exports.SessionHostRegistry = SessionHostRegistry;