factory-factory 0.4.3 → 0.4.5

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 (357) hide show
  1. package/dist/client/assets/{abnfDiagram-VRR7QNED-Bu9-2m-t.js → abnfDiagram-VRR7QNED-DygwVN25.js} +1 -1
  2. package/dist/client/assets/{arc-Dh_ay4bc.js → arc-CcnMw5av.js} +1 -1
  3. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-V4AJKU23.js → architectureDiagram-ZJ3FMSHR-BZgeaNrI.js} +1 -1
  4. package/dist/client/assets/{blockDiagram-677ZJIJ3-unJzynY2.js → blockDiagram-677ZJIJ3-DQ77xvqH.js} +1 -1
  5. package/dist/client/assets/{c4Diagram-LMCZKHZV-De_-3fVv.js → c4Diagram-LMCZKHZV-B-9QZJ1e.js} +1 -1
  6. package/dist/client/assets/channel-zAL33ya-.js +1 -0
  7. package/dist/client/assets/{chunk-2Q5K7J3B-CdZ0ANE_.js → chunk-2Q5K7J3B-DhfBY-Wd.js} +1 -1
  8. package/dist/client/assets/{chunk-32BRIVSS-BzFpxDmT.js → chunk-32BRIVSS-CV9UmSkI.js} +1 -1
  9. package/dist/client/assets/{chunk-5VM5RSS4-8_Qj_VeY.js → chunk-5VM5RSS4-BUNj3iDY.js} +1 -1
  10. package/dist/client/assets/{chunk-EX3LRPZG-Chc6HGsz.js → chunk-EX3LRPZG-Dj63JdET.js} +1 -1
  11. package/dist/client/assets/{chunk-JWPE2WC7-CFeGNH2a.js → chunk-JWPE2WC7-i6Ud3VAu.js} +1 -1
  12. package/dist/client/assets/{chunk-MOJQB5TN-DY39i_O8.js → chunk-MOJQB5TN-PY5GLOxr.js} +1 -1
  13. package/dist/client/assets/{chunk-RYQCIY6F-C3bbEdeB.js → chunk-RYQCIY6F-pIEuQy1L.js} +1 -1
  14. package/dist/client/assets/{chunk-V7JOEXUC-BpNgC7Ah.js → chunk-V7JOEXUC--zp7Z0xS.js} +1 -1
  15. package/dist/client/assets/{chunk-VR4S4FIN-BKsk7moo.js → chunk-VR4S4FIN-JstmsazP.js} +1 -1
  16. package/dist/client/assets/{chunk-XXDRQBXY-D7Pgr1uy.js → chunk-XXDRQBXY-C66lsFmy.js} +1 -1
  17. package/dist/client/assets/classDiagram-OUVF2IWQ-Dksz7rAA.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-EOCWNBFH-Dksz7rAA.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-JH36ORCC-DvmyUiBK.js → cose-bilkent-JH36ORCC-D3jSuAHn.js} +1 -1
  20. package/dist/client/assets/{cynefin-VYW2F7L2-DyPz67uy.js → cynefin-VYW2F7L2-Dx3xfGS7.js} +1 -1
  21. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-CxZzWly_.js → cynefinDiagram-TSTJHNR4-BcGYZcSJ.js} +1 -1
  22. package/dist/client/assets/{dagre-VKFMJZFB-BkNT_0vs.js → dagre-VKFMJZFB-CLZSDf9x.js} +1 -1
  23. package/dist/client/assets/{diagram-FQU43EPY-C9c41Tpz.js → diagram-FQU43EPY-Nosv2FJf.js} +1 -1
  24. package/dist/client/assets/{diagram-G47NLZAW-CogAwMLq.js → diagram-G47NLZAW-CPjqONaO.js} +1 -1
  25. package/dist/client/assets/{diagram-NH7WQ7WH-jtYr2rjE.js → diagram-NH7WQ7WH-BmRiUIAJ.js} +1 -1
  26. package/dist/client/assets/{diagram-OA4YK3LP-CfCfCJfY.js → diagram-OA4YK3LP-C7WEtC2B.js} +1 -1
  27. package/dist/client/assets/{diagram-WEI45ONY-C3m0dfDy.js → diagram-WEI45ONY-up5rQ6My.js} +1 -1
  28. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-Dr4BkxJz.js → ebnfDiagram-CCIWWBDH-D2Ye87k4.js} +1 -1
  29. package/dist/client/assets/{erDiagram-Q63AITRT-BbDab_v6.js → erDiagram-Q63AITRT-CgZF8eG-.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-23GEKE2U-DOeJX8aU.js → flowDiagram-23GEKE2U-bxoO3Eep.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-NO4QXBWP-BpgtL3Q5.js → ganttDiagram-NO4QXBWP-BAtqPw6P.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-DTDPlnSN.js → gitGraphDiagram-IHSO6WYX-DYQqeJ8w.js} +1 -1
  33. package/dist/client/assets/{index-DuSjh2zd.js → index-BZQlu-dZ.js} +158 -153
  34. package/dist/client/assets/index-CwfNzqG5.css +1 -0
  35. package/dist/client/assets/{infoDiagram-FWYZ7A6U-CHKlHAG9.js → infoDiagram-FWYZ7A6U-CZQCerFQ.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-B7EQbtqY.js → ishikawaDiagram-FXEZZL3T-Bc7wE25N.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-5HDEW3XC-DTUiiqrL.js → journeyDiagram-5HDEW3XC-D5qqjKJz.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-HUTT4EX6-eObIHBDx.js → kanban-definition-HUTT4EX6-42kkaHBo.js} +1 -1
  39. package/dist/client/assets/{linear-DQu4w17X.js → linear-Dg3NQEM3.js} +1 -1
  40. package/dist/client/assets/{mindmap-definition-LN4V7U3C-XqlKfmJa.js → mindmap-definition-LN4V7U3C-CRANKUcD.js} +1 -1
  41. package/dist/client/assets/{mobile-baseline-v5lnTluf.js → mobile-baseline-D6bQ8WTB.js} +1 -1
  42. package/dist/client/assets/{pegDiagram-2B236MQR-By2J0vHR.js → pegDiagram-2B236MQR-qJQPOd8H.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-ENE6RG2P-DWliRlHi.js → pieDiagram-ENE6RG2P-B5wB7Ccf.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DxxsQgGe.js → quadrantDiagram-ABIIQ3AL-CCZfXF8m.js} +1 -1
  45. package/dist/client/assets/{railroadDiagram-RFXS5EU6-CRjYLslW.js → railroadDiagram-RFXS5EU6-BuIrSJUW.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-TGXJPOKE-CuZgujay.js → requirementDiagram-TGXJPOKE-DfIr0zZ3.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-DhxT75Go.js → sankeyDiagram-HTMAVEWB-CJoUXRWq.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-Tz8RVPsp.js → sequenceDiagram-DBY2YBRQ-Cq1lOU1b.js} +1 -1
  49. package/dist/client/assets/{sizeCapture-X5ZJPWSS-BME47q1I.js → sizeCapture-X5ZJPWSS-XHOsOkor.js} +1 -1
  50. package/dist/client/assets/{stateDiagram-2N3HPSRC-DQoKLjSj.js → stateDiagram-2N3HPSRC-BLVzzete.js} +1 -1
  51. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-DA02HYvU.js +1 -0
  52. package/dist/client/assets/{swimlanes-5IMT3BWC-DZ78RZi9.js → swimlanes-5IMT3BWC-C20mYIPR.js} +2 -2
  53. package/dist/client/assets/swimlanesDiagram-G3AALYLV-CbfcpXpQ.js +8 -0
  54. package/dist/client/assets/{terminal-instance-CbXOfRoa.js → terminal-instance-Dw2gcPib.js} +1 -1
  55. package/dist/client/assets/{timeline-definition-FHXFAJF6-DS_K1N-M.js → timeline-definition-FHXFAJF6-DlceJAxJ.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-L72KCM5P-D9evf7U9.js → vennDiagram-L72KCM5P-CFl-Eo1Y.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-EHGQE667-8-WkkQfu.js → wardleyDiagram-EHGQE667-eBpeLOoN.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-FW5EYKEG-gEXjpSMB.js → xychartDiagram-FW5EYKEG-B8RBkKiX.js} +1 -1
  59. package/dist/client/index.html +2 -2
  60. package/dist/prisma/generated/browser.d.ts +1 -0
  61. package/dist/prisma/generated/browser.d.ts.map +1 -1
  62. package/dist/prisma/generated/client.d.ts +1 -0
  63. package/dist/prisma/generated/client.d.ts.map +1 -1
  64. package/dist/prisma/generated/commonInputTypes.d.ts +60 -0
  65. package/dist/prisma/generated/commonInputTypes.d.ts.map +1 -1
  66. package/dist/prisma/generated/enums.d.ts +15 -0
  67. package/dist/prisma/generated/enums.d.ts.map +1 -1
  68. package/dist/prisma/generated/enums.js +13 -0
  69. package/dist/prisma/generated/enums.js.map +1 -1
  70. package/dist/prisma/generated/internal/class.d.ts +3 -0
  71. package/dist/prisma/generated/internal/class.d.ts.map +1 -1
  72. package/dist/prisma/generated/internal/class.js +4 -4
  73. package/dist/prisma/generated/internal/class.js.map +1 -1
  74. package/dist/prisma/generated/internal/prismaNamespace.d.ts +90 -1
  75. package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
  76. package/dist/prisma/generated/internal/prismaNamespace.js +11 -0
  77. package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
  78. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +12 -0
  79. package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
  80. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +11 -0
  81. package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  82. package/dist/prisma/generated/models/SessionLifecycleEvent.d.ts +657 -0
  83. package/dist/prisma/generated/models/SessionLifecycleEvent.d.ts.map +1 -0
  84. package/dist/prisma/generated/models/SessionLifecycleEvent.js +2 -0
  85. package/dist/prisma/generated/models/SessionLifecycleEvent.js.map +1 -0
  86. package/dist/prisma/generated/models/Workspace.d.ts +264 -0
  87. package/dist/prisma/generated/models/Workspace.d.ts.map +1 -1
  88. package/dist/prisma/generated/models.d.ts +1 -0
  89. package/dist/prisma/generated/models.d.ts.map +1 -1
  90. package/dist/src/backend/app-context.d.ts.map +1 -1
  91. package/dist/src/backend/app-context.js +2 -1
  92. package/dist/src/backend/app-context.js.map +1 -1
  93. package/dist/src/backend/interceptors/branch-naming.interceptor.d.ts.map +1 -1
  94. package/dist/src/backend/interceptors/branch-naming.interceptor.js +0 -4
  95. package/dist/src/backend/interceptors/branch-naming.interceptor.js.map +1 -1
  96. package/dist/src/backend/interceptors/branch-naming.interceptor.test.js +26 -0
  97. package/dist/src/backend/interceptors/branch-naming.interceptor.test.js.map +1 -1
  98. package/dist/src/backend/orchestration/domain-bridges.orchestrator.d.ts +2 -1
  99. package/dist/src/backend/orchestration/domain-bridges.orchestrator.d.ts.map +1 -1
  100. package/dist/src/backend/orchestration/domain-bridges.orchestrator.js +8 -7
  101. package/dist/src/backend/orchestration/domain-bridges.orchestrator.js.map +1 -1
  102. package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js +18 -8
  103. package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js.map +1 -1
  104. package/dist/src/backend/orchestration/event-collector.orchestrator.d.ts.map +1 -1
  105. package/dist/src/backend/orchestration/event-collector.orchestrator.js +3 -1
  106. package/dist/src/backend/orchestration/event-collector.orchestrator.js.map +1 -1
  107. package/dist/src/backend/orchestration/event-collector.orchestrator.test.js +3 -1
  108. package/dist/src/backend/orchestration/event-collector.orchestrator.test.js.map +1 -1
  109. package/dist/src/backend/orchestration/reconciliation.service.d.ts +11 -8
  110. package/dist/src/backend/orchestration/reconciliation.service.d.ts.map +1 -1
  111. package/dist/src/backend/orchestration/reconciliation.service.js +41 -33
  112. package/dist/src/backend/orchestration/reconciliation.service.js.map +1 -1
  113. package/dist/src/backend/orchestration/reconciliation.service.test.js +9 -42
  114. package/dist/src/backend/orchestration/reconciliation.service.test.js.map +1 -1
  115. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts +4 -1
  116. package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts.map +1 -1
  117. package/dist/src/backend/orchestration/workspace-archive.orchestrator.js +4 -1
  118. package/dist/src/backend/orchestration/workspace-archive.orchestrator.js.map +1 -1
  119. package/dist/src/backend/orchestration/workspace-archive.orchestrator.test.js +15 -2
  120. package/dist/src/backend/orchestration/workspace-archive.orchestrator.test.js.map +1 -1
  121. package/dist/src/backend/orchestration/workspace-init-issue-prompts.d.ts.map +1 -1
  122. package/dist/src/backend/orchestration/workspace-init-issue-prompts.js +3 -1
  123. package/dist/src/backend/orchestration/workspace-init-issue-prompts.js.map +1 -1
  124. package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.d.ts +2 -0
  125. package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.d.ts.map +1 -0
  126. package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.js +75 -0
  127. package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.js.map +1 -0
  128. package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts +7 -4
  129. package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts.map +1 -1
  130. package/dist/src/backend/orchestration/workspace-init.orchestrator.js +36 -1
  131. package/dist/src/backend/orchestration/workspace-init.orchestrator.js.map +1 -1
  132. package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js +117 -8
  133. package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js.map +1 -1
  134. package/dist/src/backend/routers/websocket/chat.handler.js +1 -1
  135. package/dist/src/backend/routers/websocket/chat.handler.js.map +1 -1
  136. package/dist/src/backend/routers/websocket/chat.handler.test.js +6 -2
  137. package/dist/src/backend/routers/websocket/chat.handler.test.js.map +1 -1
  138. package/dist/src/backend/server.d.ts.map +1 -1
  139. package/dist/src/backend/server.js +2 -1
  140. package/dist/src/backend/server.js.map +1 -1
  141. package/dist/src/backend/server.upgrade.test.js +20 -0
  142. package/dist/src/backend/server.upgrade.test.js.map +1 -1
  143. package/dist/src/backend/services/github/service/bridges.d.ts +1 -0
  144. package/dist/src/backend/services/github/service/bridges.d.ts.map +1 -1
  145. package/dist/src/backend/services/github/service/pr-snapshot.service.d.ts +1 -0
  146. package/dist/src/backend/services/github/service/pr-snapshot.service.d.ts.map +1 -1
  147. package/dist/src/backend/services/github/service/pr-snapshot.service.js +1 -0
  148. package/dist/src/backend/services/github/service/pr-snapshot.service.js.map +1 -1
  149. package/dist/src/backend/services/github/service/pr-snapshot.service.test.js +11 -0
  150. package/dist/src/backend/services/github/service/pr-snapshot.service.test.js.map +1 -1
  151. package/dist/src/backend/services/linear/service/linear-client.service.d.ts +1 -1
  152. package/dist/src/backend/services/linear/service/linear-client.service.d.ts.map +1 -1
  153. package/dist/src/backend/services/linear/service/linear-client.service.js +4 -4
  154. package/dist/src/backend/services/linear/service/linear-client.service.js.map +1 -1
  155. package/dist/src/backend/services/linear/service/linear-client.service.test.js +10 -10
  156. package/dist/src/backend/services/linear/service/linear-client.service.test.js.map +1 -1
  157. package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.d.ts.map +1 -1
  158. package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.js +35 -11
  159. package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.js.map +1 -1
  160. package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.test.js +89 -0
  161. package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.test.js.map +1 -1
  162. package/dist/src/backend/services/port.service.d.ts.map +1 -1
  163. package/dist/src/backend/services/port.service.js +4 -0
  164. package/dist/src/backend/services/port.service.js.map +1 -1
  165. package/dist/src/backend/services/port.service.test.js +8 -0
  166. package/dist/src/backend/services/port.service.test.js.map +1 -1
  167. package/dist/src/backend/services/ratchet/service/bridges.d.ts +1 -0
  168. package/dist/src/backend/services/ratchet/service/bridges.d.ts.map +1 -1
  169. package/dist/src/backend/services/ratchet/service/ratchet-active-session.helpers.d.ts.map +1 -1
  170. package/dist/src/backend/services/ratchet/service/ratchet-active-session.helpers.js +10 -2
  171. package/dist/src/backend/services/ratchet/service/ratchet-active-session.helpers.js.map +1 -1
  172. package/dist/src/backend/services/ratchet/service/ratchet.service.d.ts.map +1 -1
  173. package/dist/src/backend/services/ratchet/service/ratchet.service.js +1 -0
  174. package/dist/src/backend/services/ratchet/service/ratchet.service.js.map +1 -1
  175. package/dist/src/backend/services/ratchet/service/ratchet.service.test.js +31 -1
  176. package/dist/src/backend/services/ratchet/service/ratchet.service.test.js.map +1 -1
  177. package/dist/src/backend/services/registry.d.ts +2 -2
  178. package/dist/src/backend/services/registry.d.ts.map +1 -1
  179. package/dist/src/backend/services/registry.js +2 -1
  180. package/dist/src/backend/services/registry.js.map +1 -1
  181. package/dist/src/backend/services/run-script/service/run-script.service.d.ts +1 -0
  182. package/dist/src/backend/services/run-script/service/run-script.service.d.ts.map +1 -1
  183. package/dist/src/backend/services/run-script/service/run-script.service.js +26 -17
  184. package/dist/src/backend/services/run-script/service/run-script.service.js.map +1 -1
  185. package/dist/src/backend/services/run-script/service/run-script.service.test.js +23 -0
  186. package/dist/src/backend/services/run-script/service/run-script.service.test.js.map +1 -1
  187. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.d.ts +23 -0
  188. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.d.ts.map +1 -0
  189. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.js +23 -0
  190. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.js.map +1 -0
  191. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.d.ts +2 -0
  192. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.d.ts.map +1 -0
  193. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.js +62 -0
  194. package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.js.map +1 -0
  195. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.d.ts +2 -1
  196. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.d.ts.map +1 -1
  197. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.js +14 -7
  198. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.js.map +1 -1
  199. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.test.js +33 -0
  200. package/dist/src/backend/services/session/service/acp/acp-runtime-manager.test.js.map +1 -1
  201. package/dist/src/backend/services/session/service/chat/chat-message-handlers/attachment-processing.test.js +21 -0
  202. package/dist/src/backend/services/session/service/chat/chat-message-handlers/attachment-processing.test.js.map +1 -1
  203. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
  204. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.js +3 -279
  205. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
  206. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.test.js +154 -7
  207. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.test.js.map +1 -1
  208. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.js +1 -1
  209. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.js.map +1 -1
  210. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.test.js +1 -1
  211. package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.test.js.map +1 -1
  212. package/dist/src/backend/services/session/service/chat/chat-message-handlers/image-format-validation.js +1 -1
  213. package/dist/src/backend/services/session/service/chat/chat-message-handlers/image-format-validation.js.map +1 -1
  214. package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.d.ts.map +1 -1
  215. package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.js +4 -1
  216. package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.js.map +1 -1
  217. package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.test.js +49 -0
  218. package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.test.js.map +1 -1
  219. package/dist/src/backend/services/session/service/index.d.ts +1 -1
  220. package/dist/src/backend/services/session/service/index.d.ts.map +1 -1
  221. package/dist/src/backend/services/session/service/index.js +1 -1
  222. package/dist/src/backend/services/session/service/index.js.map +1 -1
  223. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.d.ts +5 -1
  224. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.d.ts.map +1 -1
  225. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.js +23 -4
  226. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.js.map +1 -1
  227. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.text-streaming.test.js +28 -0
  228. package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.text-streaming.test.js.map +1 -1
  229. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.d.ts +26 -0
  230. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.d.ts.map +1 -0
  231. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.js +84 -0
  232. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.js.map +1 -0
  233. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.d.ts +2 -0
  234. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.d.ts.map +1 -0
  235. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.js +283 -0
  236. package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.js.map +1 -0
  237. package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.d.ts +10 -0
  238. package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.d.ts.map +1 -0
  239. package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.js +299 -0
  240. package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.js.map +1 -0
  241. package/dist/src/backend/services/session/service/lifecycle/session-services.d.ts +2 -0
  242. package/dist/src/backend/services/session/service/lifecycle/session-services.d.ts.map +1 -1
  243. package/dist/src/backend/services/session/service/lifecycle/session-services.js +11 -0
  244. package/dist/src/backend/services/session/service/lifecycle/session-services.js.map +1 -1
  245. package/dist/src/backend/services/session/service/lifecycle/session.config.service.d.ts +1 -0
  246. package/dist/src/backend/services/session/service/lifecycle/session.config.service.d.ts.map +1 -1
  247. package/dist/src/backend/services/session/service/lifecycle/session.config.service.js +17 -0
  248. package/dist/src/backend/services/session/service/lifecycle/session.config.service.js.map +1 -1
  249. package/dist/src/backend/services/session/service/lifecycle/session.config.service.test.js +42 -0
  250. package/dist/src/backend/services/session/service/lifecycle/session.config.service.test.js.map +1 -1
  251. package/dist/src/backend/services/session/service/lifecycle/session.error-message.d.ts +2 -0
  252. package/dist/src/backend/services/session/service/lifecycle/session.error-message.d.ts.map +1 -1
  253. package/dist/src/backend/services/session/service/lifecycle/session.error-message.js +21 -0
  254. package/dist/src/backend/services/session/service/lifecycle/session.error-message.js.map +1 -1
  255. package/dist/src/backend/services/session/service/lifecycle/session.error-message.test.js +26 -1
  256. package/dist/src/backend/services/session/service/lifecycle/session.error-message.test.js.map +1 -1
  257. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.d.ts +31 -2
  258. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.d.ts.map +1 -1
  259. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.js +281 -109
  260. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.js.map +1 -1
  261. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.test.js +465 -8
  262. package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.test.js.map +1 -1
  263. package/dist/src/backend/services/session/service/lifecycle/session.prompt.service.test.js +206 -5
  264. package/dist/src/backend/services/session/service/lifecycle/session.prompt.service.test.js.map +1 -1
  265. package/dist/src/backend/services/session/service/lifecycle/session.service.d.ts +15 -2
  266. package/dist/src/backend/services/session/service/lifecycle/session.service.d.ts.map +1 -1
  267. package/dist/src/backend/services/session/service/lifecycle/session.service.js +87 -14
  268. package/dist/src/backend/services/session/service/lifecycle/session.service.js.map +1 -1
  269. package/dist/src/backend/services/session/service/lifecycle/session.service.test.js +105 -1
  270. package/dist/src/backend/services/session/service/lifecycle/session.service.test.js.map +1 -1
  271. package/dist/src/backend/services/session/service/session-domain.service.d.ts +4 -1
  272. package/dist/src/backend/services/session/service/session-domain.service.d.ts.map +1 -1
  273. package/dist/src/backend/services/session/service/session-domain.service.js +34 -25
  274. package/dist/src/backend/services/session/service/session-domain.service.js.map +1 -1
  275. package/dist/src/backend/services/session/service/session-domain.service.test.js +119 -0
  276. package/dist/src/backend/services/session/service/session-domain.service.test.js.map +1 -1
  277. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.d.ts +6 -0
  278. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.d.ts.map +1 -0
  279. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.js +114 -0
  280. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.js.map +1 -0
  281. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.d.ts +2 -0
  282. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.d.ts.map +1 -0
  283. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.js +129 -0
  284. package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.js.map +1 -0
  285. package/dist/src/backend/services/session/service/store/session-store-registry.d.ts +8 -1
  286. package/dist/src/backend/services/session/service/store/session-store-registry.d.ts.map +1 -1
  287. package/dist/src/backend/services/session/service/store/session-store-registry.js +58 -1
  288. package/dist/src/backend/services/session/service/store/session-store-registry.js.map +1 -1
  289. package/dist/src/backend/services/session/service/store/session-store-registry.test.js +120 -0
  290. package/dist/src/backend/services/session/service/store/session-store-registry.test.js.map +1 -1
  291. package/dist/src/backend/services/session/service/store/session-transcript.d.ts.map +1 -1
  292. package/dist/src/backend/services/session/service/store/session-transcript.js +7 -4
  293. package/dist/src/backend/services/session/service/store/session-transcript.js.map +1 -1
  294. package/dist/src/backend/services/workspace/resources/workspace.accessor.d.ts +1 -0
  295. package/dist/src/backend/services/workspace/resources/workspace.accessor.d.ts.map +1 -1
  296. package/dist/src/backend/services/workspace/resources/workspace.accessor.js +16 -6
  297. package/dist/src/backend/services/workspace/resources/workspace.accessor.js.map +1 -1
  298. package/dist/src/backend/services/workspace/resources/workspace.accessor.test.js +31 -0
  299. package/dist/src/backend/services/workspace/resources/workspace.accessor.test.js.map +1 -1
  300. package/dist/src/backend/services/workspace/service/lifecycle/creation.service.d.ts.map +1 -1
  301. package/dist/src/backend/services/workspace/service/lifecycle/creation.service.js +1 -0
  302. package/dist/src/backend/services/workspace/service/lifecycle/creation.service.js.map +1 -1
  303. package/dist/src/backend/services/workspace/service/lifecycle/creation.service.test.js +12 -0
  304. package/dist/src/backend/services/workspace/service/lifecycle/creation.service.test.js.map +1 -1
  305. package/dist/src/backend/services/workspace/service/lifecycle/workspace-pr-snapshot.service.test.js +1 -0
  306. package/dist/src/backend/services/workspace/service/lifecycle/workspace-pr-snapshot.service.test.js.map +1 -1
  307. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.d.ts +1 -0
  308. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.d.ts.map +1 -1
  309. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.js +14 -0
  310. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.js.map +1 -1
  311. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.test.js +54 -0
  312. package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.test.js.map +1 -1
  313. package/dist/src/backend/trpc/project.router.test.js +19 -0
  314. package/dist/src/backend/trpc/project.router.test.js.map +1 -1
  315. package/dist/src/backend/trpc/project.trpc.js +1 -1
  316. package/dist/src/backend/trpc/project.trpc.js.map +1 -1
  317. package/dist/src/backend/trpc/session.router.test.js +11 -0
  318. package/dist/src/backend/trpc/session.router.test.js.map +1 -1
  319. package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
  320. package/dist/src/backend/trpc/session.trpc.js +4 -0
  321. package/dist/src/backend/trpc/session.trpc.js.map +1 -1
  322. package/dist/src/cli/runtime-utils.d.ts.map +1 -1
  323. package/dist/src/cli/runtime-utils.js +4 -0
  324. package/dist/src/cli/runtime-utils.js.map +1 -1
  325. package/dist/src/cli/runtime-utils.test.js +7 -0
  326. package/dist/src/cli/runtime-utils.test.js.map +1 -1
  327. package/dist/src/shared/acp-protocol/protocol/messages.d.ts +18 -2
  328. package/dist/src/shared/acp-protocol/protocol/messages.d.ts.map +1 -1
  329. package/dist/src/shared/acp-protocol/protocol/messages.js +1 -0
  330. package/dist/src/shared/acp-protocol/protocol/messages.js.map +1 -1
  331. package/dist/src/shared/acp-protocol/protocol/renderer-window.d.ts.map +1 -1
  332. package/dist/src/shared/acp-protocol/protocol/renderer-window.js +3 -0
  333. package/dist/src/shared/acp-protocol/protocol/renderer-window.js.map +1 -1
  334. package/dist/src/shared/core/enums.d.ts +15 -0
  335. package/dist/src/shared/core/enums.d.ts.map +1 -1
  336. package/dist/src/shared/core/enums.js +13 -0
  337. package/dist/src/shared/core/enums.js.map +1 -1
  338. package/dist/src/shared/core/index.d.ts +1 -1
  339. package/dist/src/shared/core/index.d.ts.map +1 -1
  340. package/dist/src/shared/core/index.js +1 -1
  341. package/dist/src/shared/core/index.js.map +1 -1
  342. package/dist/src/shared/issue-start-prompt.d.ts.map +1 -1
  343. package/dist/src/shared/issue-start-prompt.js +8 -3
  344. package/dist/src/shared/issue-start-prompt.js.map +1 -1
  345. package/dist/src/test-utils/application-graph.d.ts.map +1 -1
  346. package/dist/src/test-utils/application-graph.js +3 -1
  347. package/dist/src/test-utils/application-graph.js.map +1 -1
  348. package/dist/tsconfig.backend.tsbuildinfo +1 -1
  349. package/package.json +1 -1
  350. package/prisma/migrations/20260730140000_add_session_lifecycle_events/migration.sql +20 -0
  351. package/prisma/schema.prisma +32 -0
  352. package/dist/client/assets/channel-8o5vp5Nl.js +0 -1
  353. package/dist/client/assets/classDiagram-OUVF2IWQ-BNBqP_D4.js +0 -1
  354. package/dist/client/assets/classDiagram-v2-EOCWNBFH-BNBqP_D4.js +0 -1
  355. package/dist/client/assets/index-dP7LJD2L.css +0 -1
  356. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BnQzcOjM.js +0 -1
  357. package/dist/client/assets/swimlanesDiagram-G3AALYLV-mI62K_37.js +0 -8
@@ -1,9 +1,18 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { SessionDomainService } from '../../../../../backend/services/session/service/session-domain.service.js';
3
+ import { sessionEventBus } from '../../../../../backend/services/session/service/session-event-bus.js';
2
4
  import { userSettingsService } from '../../../../../backend/services/settings/index.js';
3
- import { workspaceNotificationService } from '../../../../../backend/services/workspace/index.js';
5
+ import { workspaceDataService, workspaceNotificationService } from '../../../../../backend/services/workspace/index.js';
4
6
  import { SessionStatus } from '../../../../../shared/core/index.js';
5
7
  import { unsafeCoerce } from '../../../../../test-utils/unsafe-coerce.js';
6
8
  import { SessionLifecycleService } from './session.lifecycle.service.js';
9
+ import { SessionLifecycleEventService } from './session-lifecycle-event.service.js';
10
+ vi.mock('./closed-session-persistence.service', () => ({
11
+ closedSessionPersistenceService: {
12
+ persistClosedSession: vi.fn(async () => undefined),
13
+ },
14
+ }));
15
+ import { closedSessionPersistenceService } from './closed-session-persistence.service.js';
7
16
  vi.mock('@/backend/services/logger.service', () => ({
8
17
  createLogger: () => ({
9
18
  debug: vi.fn(),
@@ -49,6 +58,7 @@ function createLifecycleService(options) {
49
58
  promptTurnCompletionService: {},
50
59
  retryService: {},
51
60
  sendSessionMessage: vi.fn(async () => undefined),
61
+ lifecycleEventService: { hydrate: vi.fn(async () => undefined) },
52
62
  });
53
63
  service.configure({
54
64
  workspace: {
@@ -64,6 +74,9 @@ function createLifecycleService(options) {
64
74
  async function deliverPendingChildNotifications(service, sessionId = 'session-1', workspaceId = 'workspace-1') {
65
75
  return await service.deliverPendingChildNotifications(sessionId, workspaceId);
66
76
  }
77
+ function getStopGenerations(service) {
78
+ return service.stopGenerations;
79
+ }
67
80
  function createStoppableLifecycleService() {
68
81
  const repository = {
69
82
  getSessionsByWorkspaceId: vi.fn(async () => [
@@ -86,11 +99,77 @@ function createStoppableLifecycleService() {
86
99
  promptTurnCompletionService: {},
87
100
  retryService: {},
88
101
  sendSessionMessage: vi.fn(async () => undefined),
102
+ lifecycleEventService: { hydrate: vi.fn(async () => undefined) },
89
103
  });
90
- const stopSession = vi.fn((_sessionId) => Promise.resolve());
104
+ const stopSession = vi.fn((_sessionId, _options) => Promise.resolve());
91
105
  service.stopSession = stopSession;
92
106
  return { service, repository, runtimeManager, stopSession };
93
107
  }
108
+ function createStopReasonLifecycleService(options) {
109
+ const session = {
110
+ id: 'session-1',
111
+ workspaceId: 'workspace-1',
112
+ workflow: 'code',
113
+ providerProcessPid: options?.providerProcessPid ?? 4242,
114
+ };
115
+ const repository = {
116
+ getSessionById: vi.fn(async () => session),
117
+ updateSessionIfStatus: vi.fn(async () => session),
118
+ updateSession: vi.fn(async () => session),
119
+ };
120
+ const runtimeManager = {
121
+ isStopInProgress: vi.fn(() => false),
122
+ isSessionRunning: vi.fn(() => false),
123
+ isSessionWorking: vi.fn(() => false),
124
+ getClient: vi.fn(() => undefined),
125
+ stopClient: vi.fn(async () => undefined),
126
+ };
127
+ const lifecycleEventService = {
128
+ record: vi.fn(async (_input) => null),
129
+ hydrate: vi.fn(),
130
+ };
131
+ const service = new SessionLifecycleService(unsafeCoerce({
132
+ repository,
133
+ promptBuilder: {},
134
+ runtimeManager,
135
+ sessionDomainService: {
136
+ clearQueuedWork: vi.fn(),
137
+ getRuntimeSnapshot: vi.fn(() => ({
138
+ phase: 'idle',
139
+ processState: 'stopped',
140
+ activity: 'IDLE',
141
+ updatedAt: '2026-07-30T00:00:00.000Z',
142
+ })),
143
+ setRuntimeSnapshot: vi.fn(),
144
+ markProcessExit: vi.fn(),
145
+ clearSession: vi.fn(),
146
+ },
147
+ sessionPermissionService: { cancelPendingRequests: vi.fn() },
148
+ sessionConfigService: {},
149
+ acpEventProcessor: {
150
+ createRuntimeEventHandler: vi.fn(() => ({})),
151
+ clearStreamingState: vi.fn(),
152
+ clearReplaySuppression: vi.fn(),
153
+ clearSessionContext: vi.fn(),
154
+ clearSessionState: vi.fn(),
155
+ finalizeOrphanedToolCalls: vi.fn(),
156
+ },
157
+ promptTurnCompletionService: { clearSession: vi.fn() },
158
+ retryService: { run: vi.fn(async (operation) => await operation()) },
159
+ lifecycleEventService,
160
+ sendSessionMessage: vi.fn(),
161
+ }));
162
+ const createRuntimeHandlers = () => service;
163
+ const runtimeHandlers = createRuntimeHandlers().setupAcpEventHandler('session-1');
164
+ return {
165
+ service,
166
+ repository,
167
+ lifecycleEventService,
168
+ runtimeManager,
169
+ runtimeHandlers,
170
+ createRuntimeHandlers: () => createRuntimeHandlers().setupAcpEventHandler('session-1'),
171
+ };
172
+ }
94
173
  describe('SessionLifecycleService stopWorkspaceSessions', () => {
95
174
  beforeEach(() => {
96
175
  vi.clearAllMocks();
@@ -101,8 +180,8 @@ describe('SessionLifecycleService stopWorkspaceSessions', () => {
101
180
  expect(repository.getSessionsByWorkspaceId).toHaveBeenCalledWith('workspace-1');
102
181
  expect(runtimeManager.isSessionRunning).toHaveBeenCalledWith('session-runtime-only');
103
182
  expect(runtimeManager.isSessionRunning).toHaveBeenCalledWith('session-idle');
104
- expect(stopSession).toHaveBeenCalledWith('session-running');
105
- expect(stopSession).toHaveBeenCalledWith('session-runtime-only');
183
+ expect(stopSession).toHaveBeenCalledWith('session-running', { reason: 'SYSTEM_STOP' });
184
+ expect(stopSession).toHaveBeenCalledWith('session-runtime-only', { reason: 'SYSTEM_STOP' });
106
185
  expect(stopSession).not.toHaveBeenCalledWith('session-idle');
107
186
  });
108
187
  it('attempts every running session and throws when any stop fails', async () => {
@@ -114,8 +193,263 @@ describe('SessionLifecycleService stopWorkspaceSessions', () => {
114
193
  return Promise.resolve();
115
194
  });
116
195
  await expect(service.stopWorkspaceSessions('workspace-1')).rejects.toThrow('Failed to stop 1 workspace session');
117
- expect(stopSession).toHaveBeenCalledWith('session-running');
118
- expect(stopSession).toHaveBeenCalledWith('session-runtime-only');
196
+ expect(stopSession).toHaveBeenCalledWith('session-running', { reason: 'SYSTEM_STOP' });
197
+ expect(stopSession).toHaveBeenCalledWith('session-runtime-only', { reason: 'SYSTEM_STOP' });
198
+ });
199
+ });
200
+ describe('SessionLifecycleService stop causes', () => {
201
+ beforeEach(() => {
202
+ vi.clearAllMocks();
203
+ });
204
+ it.each([
205
+ ['USER_STOP', 'Session stopped by you.'],
206
+ ['SESSION_CLOSED', 'Session closed by you.'],
207
+ ['WORKSPACE_ARCHIVED', 'Session stopped because the workspace was archived.'],
208
+ ['SYSTEM_STOP', 'Session stopped by the system.'],
209
+ ])('records %s before stopping the runtime', async (reason, message) => {
210
+ const { service, lifecycleEventService, runtimeManager } = createStopReasonLifecycleService();
211
+ await service.stopSession('session-1', { reason });
212
+ expect(lifecycleEventService.record).toHaveBeenCalledWith(expect.objectContaining({
213
+ workspaceId: 'workspace-1',
214
+ sessionId: 'session-1',
215
+ kind: 'SESSION_STOPPED',
216
+ reason,
217
+ message,
218
+ dedupeKey: expect.stringMatching(/^session-stop:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/),
219
+ }));
220
+ expect(lifecycleEventService.record.mock.invocationCallOrder[0]).toBeLessThan(runtimeManager.stopClient.mock.invocationCallOrder[0]);
221
+ });
222
+ it('uses a new durable stop identity after lifecycle-service reconstruction', async () => {
223
+ const first = createStopReasonLifecycleService();
224
+ const reconstructed = createStopReasonLifecycleService();
225
+ await first.service.stopSession('session-1', { reason: 'USER_STOP' });
226
+ await reconstructed.service.stopSession('session-1', { reason: 'USER_STOP' });
227
+ const firstKey = first.lifecycleEventService.record.mock.calls[0]?.[0]?.dedupeKey;
228
+ const reconstructedKey = reconstructed.lifecycleEventService.record.mock.calls[0]?.[0]?.dedupeKey;
229
+ expect(firstKey).toMatch(/^session-stop:/);
230
+ expect(reconstructedKey).toMatch(/^session-stop:/);
231
+ expect(reconstructedKey).not.toBe(firstKey);
232
+ });
233
+ it('records an unexpected process exit once with its runtime incarnation and exit code', async () => {
234
+ const { lifecycleEventService, runtimeHandlers } = createStopReasonLifecycleService();
235
+ await runtimeHandlers.onExit?.('session-1', 1);
236
+ expect(lifecycleEventService.record).toHaveBeenCalledWith(expect.objectContaining({
237
+ kind: 'SESSION_STOPPED',
238
+ reason: 'UNEXPECTED_EXIT',
239
+ message: 'Session stopped: agent process exited unexpectedly (code 1).',
240
+ dedupeKey: expect.stringMatching(/^process-exit:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}:1$/),
241
+ }));
242
+ });
243
+ it('records an unexpected process exit when the status update fails', async () => {
244
+ const { lifecycleEventService, repository, runtimeHandlers } = createStopReasonLifecycleService();
245
+ repository.updateSession.mockRejectedValueOnce(new Error('database write failed'));
246
+ await runtimeHandlers.onExit?.('session-1', 1);
247
+ expect(lifecycleEventService.record).toHaveBeenCalledWith(expect.objectContaining({
248
+ reason: 'UNEXPECTED_EXIT',
249
+ dedupeKey: expect.stringMatching(/^process-exit:.+:1$/),
250
+ }));
251
+ });
252
+ it('does not collapse repeated null-PID exits from separate runtime incarnations', async () => {
253
+ const { lifecycleEventService, createRuntimeHandlers } = createStopReasonLifecycleService({
254
+ providerProcessPid: null,
255
+ });
256
+ await createRuntimeHandlers().onExit?.('session-1', 1);
257
+ await createRuntimeHandlers().onExit?.('session-1', 1);
258
+ const dedupeKeys = lifecycleEventService.record.mock.calls.map(([recordInput]) => recordInput.dedupeKey);
259
+ expect(dedupeKeys).toHaveLength(2);
260
+ expect(new Set(dedupeKeys).size).toBe(2);
261
+ expect(dedupeKeys).toEqual([
262
+ expect.stringMatching(/^process-exit:.+:1$/),
263
+ expect.stringMatching(/^process-exit:.+:1$/),
264
+ ]);
265
+ });
266
+ it('does not label a deliberate stop as an unexpected exit', async () => {
267
+ const { lifecycleEventService, runtimeManager, runtimeHandlers } = createStopReasonLifecycleService();
268
+ runtimeManager.isStopInProgress.mockReturnValue(true);
269
+ await runtimeHandlers.onExit?.('session-1', 0);
270
+ expect(lifecycleEventService.record).not.toHaveBeenCalledWith(expect.objectContaining({ reason: 'UNEXPECTED_EXIT' }));
271
+ });
272
+ it('can clean up a failed startup without recording a stop event', async () => {
273
+ const { service, lifecycleEventService, runtimeManager } = createStopReasonLifecycleService();
274
+ await service.stopSession('session-1', { recordLifecycleEvent: false });
275
+ expect(lifecycleEventService.record).not.toHaveBeenCalled();
276
+ expect(runtimeManager.stopClient).toHaveBeenCalledWith('session-1');
277
+ });
278
+ });
279
+ describe('SessionLifecycleService closed transcript persistence', () => {
280
+ beforeEach(() => {
281
+ vi.clearAllMocks();
282
+ });
283
+ it('hydrates durable lifecycle rows before persisting a closed transcript', async () => {
284
+ const domain = new SessionDomainService();
285
+ const hydrateProviderHistory = vi.fn(() => {
286
+ domain.replaceTranscript('session-1', [
287
+ {
288
+ id: 'provider-user-1',
289
+ source: 'user',
290
+ text: 'Original provider conversation',
291
+ timestamp: '2026-07-30T12:00:00.000Z',
292
+ order: 0,
293
+ },
294
+ ]);
295
+ return Promise.resolve();
296
+ });
297
+ const lifecycleEventService = new SessionLifecycleEventService({
298
+ store: {
299
+ upsert: vi.fn(),
300
+ findBySessionId: vi.fn(async () => [
301
+ {
302
+ id: 'event-1',
303
+ workspaceId: 'workspace-1',
304
+ sessionId: 'session-1',
305
+ kind: 'TURN_INTERRUPTED',
306
+ reason: 'PROMPT_TIMEOUT',
307
+ message: 'Turn stopped: reached the 4-hour limit.',
308
+ dedupeKey: 'prompt-timeout',
309
+ createdAt: new Date('2026-07-30T12:22:23.353Z'),
310
+ },
311
+ ]),
312
+ },
313
+ sessionDomainService: domain,
314
+ });
315
+ const hydrateLifecycleEvents = vi.spyOn(lifecycleEventService, 'hydrate');
316
+ domain.clearSession('session-1');
317
+ const repository = {
318
+ getSessionById: vi.fn(async () => ({
319
+ id: 'session-1',
320
+ workspaceId: 'workspace-1',
321
+ name: 'Chat',
322
+ workflow: 'user',
323
+ provider: 'CLAUDE',
324
+ model: 'claude-sonnet',
325
+ createdAt: new Date('2026-07-30T12:00:00.000Z'),
326
+ })),
327
+ };
328
+ vi.mocked(workspaceDataService.findById).mockResolvedValue({
329
+ id: 'workspace-1',
330
+ worktreePath: '/tmp/worktree',
331
+ });
332
+ const service = new SessionLifecycleService(unsafeCoerce({
333
+ repository,
334
+ promptBuilder: {},
335
+ runtimeManager: {},
336
+ sessionDomainService: domain,
337
+ sessionPermissionService: {},
338
+ sessionConfigService: {},
339
+ acpEventProcessor: {},
340
+ promptTurnCompletionService: {},
341
+ retryService: {},
342
+ sendSessionMessage: vi.fn(),
343
+ lifecycleEventService,
344
+ hydrateProviderHistory,
345
+ }));
346
+ await service.persistClosedSession('session-1');
347
+ expect(closedSessionPersistenceService.persistClosedSession).toHaveBeenCalledWith(expect.objectContaining({
348
+ sessionId: 'session-1',
349
+ messages: expect.arrayContaining([
350
+ expect.objectContaining({ id: 'provider-user-1' }),
351
+ expect.objectContaining({ id: 'session-lifecycle:event-1' }),
352
+ ]),
353
+ }));
354
+ expect(hydrateProviderHistory).toHaveBeenCalledWith('session-1', expect.objectContaining({ provider: 'CLAUDE' }));
355
+ expect(hydrateProviderHistory.mock.invocationCallOrder[0]).toBeLessThan(hydrateLifecycleEvents.mock.invocationCallOrder[0]);
356
+ });
357
+ });
358
+ describe('SessionLifecycleService graceful shutdown', () => {
359
+ it('records SYSTEM_STOP for active and pending runtimes before bounded shutdown', async () => {
360
+ const runtimeManager = {
361
+ beginShutdown: vi.fn(() => ['session-active', 'session-pending']),
362
+ stopAllClients: vi.fn(async () => undefined),
363
+ isStopInProgress: vi.fn(() => false),
364
+ };
365
+ const repository = {
366
+ getSessionById: vi.fn(async (sessionId) => ({
367
+ id: sessionId,
368
+ workspaceId: 'workspace-1',
369
+ workflow: 'code',
370
+ })),
371
+ };
372
+ const lifecycleEventService = {
373
+ record: vi.fn(async () => null),
374
+ hydrate: vi.fn(async () => undefined),
375
+ };
376
+ const promptTurnCompletionService = {
377
+ clearAll: vi.fn(),
378
+ };
379
+ const service = new SessionLifecycleService(unsafeCoerce({
380
+ repository,
381
+ promptBuilder: {},
382
+ runtimeManager,
383
+ sessionDomainService: {},
384
+ sessionPermissionService: {},
385
+ sessionConfigService: {},
386
+ acpEventProcessor: { getWorkspaceId: vi.fn() },
387
+ promptTurnCompletionService,
388
+ retryService: {
389
+ run: vi.fn(async (operation) => await operation()),
390
+ },
391
+ lifecycleEventService,
392
+ hydrateProviderHistory: vi.fn(),
393
+ sendSessionMessage: vi.fn(),
394
+ }));
395
+ await service.stopAllClients(4321);
396
+ expect(runtimeManager.beginShutdown).toHaveBeenCalledOnce();
397
+ expect(lifecycleEventService.record).toHaveBeenCalledTimes(2);
398
+ expect(lifecycleEventService.record).toHaveBeenCalledWith(expect.objectContaining({
399
+ sessionId: 'session-active',
400
+ reason: 'SYSTEM_STOP',
401
+ }));
402
+ expect(lifecycleEventService.record).toHaveBeenCalledWith(expect.objectContaining({
403
+ sessionId: 'session-pending',
404
+ reason: 'SYSTEM_STOP',
405
+ }));
406
+ expect(runtimeManager.beginShutdown.mock.invocationCallOrder[0]).toBeLessThan(lifecycleEventService.record.mock.invocationCallOrder[0]);
407
+ expect(lifecycleEventService.record.mock.invocationCallOrder.at(-1)).toBeLessThan(runtimeManager.stopAllClients.mock.invocationCallOrder[0]);
408
+ expect(runtimeManager.stopAllClients).toHaveBeenCalledWith(4321);
409
+ });
410
+ it('continues runtime shutdown when lifecycle recording stalls', async () => {
411
+ vi.useFakeTimers();
412
+ try {
413
+ const runtimeManager = {
414
+ beginShutdown: vi.fn(() => ['session-active']),
415
+ stopAllClients: vi.fn(async () => undefined),
416
+ isStopInProgress: vi.fn(() => false),
417
+ };
418
+ const service = new SessionLifecycleService(unsafeCoerce({
419
+ repository: {
420
+ getSessionById: vi.fn(async () => ({
421
+ id: 'session-active',
422
+ workspaceId: 'workspace-1',
423
+ workflow: 'code',
424
+ })),
425
+ },
426
+ promptBuilder: {},
427
+ runtimeManager,
428
+ sessionDomainService: {},
429
+ sessionPermissionService: {},
430
+ sessionConfigService: {},
431
+ acpEventProcessor: { getWorkspaceId: vi.fn() },
432
+ promptTurnCompletionService: { clearAll: vi.fn() },
433
+ retryService: {
434
+ run: vi.fn(async (operation) => await operation()),
435
+ },
436
+ lifecycleEventService: {
437
+ record: vi.fn(() => new Promise(() => undefined)),
438
+ hydrate: vi.fn(async () => undefined),
439
+ },
440
+ hydrateProviderHistory: vi.fn(),
441
+ sendSessionMessage: vi.fn(),
442
+ }));
443
+ const shutdown = service.stopAllClients(4321);
444
+ await vi.advanceTimersByTimeAsync(999);
445
+ expect(runtimeManager.stopAllClients).not.toHaveBeenCalled();
446
+ await vi.advanceTimersByTimeAsync(1);
447
+ await shutdown;
448
+ expect(runtimeManager.stopAllClients).toHaveBeenCalledWith(4321);
449
+ }
450
+ finally {
451
+ vi.useRealTimers();
452
+ }
119
453
  });
120
454
  });
121
455
  describe('SessionLifecycleService pending workspace notifications', () => {
@@ -196,7 +530,7 @@ describe('SessionLifecycleService pending workspace notifications', () => {
196
530
  await vi.waitFor(() => {
197
531
  expect(workspaceNotificationService.listPendingForDelivery).toHaveBeenCalledWith('workspace-1');
198
532
  });
199
- service.stopGenerations.set('session-1', 1);
533
+ getStopGenerations(service).set('session-1', service.getStopGeneration('session-1') + 1);
200
534
  resolvePending([
201
535
  {
202
536
  id: 'notif-parent',
@@ -653,7 +987,7 @@ function createStartableLifecycleService(options) {
653
987
  getWorkspaceById: vi.fn(async () => workspace),
654
988
  getProjectById: vi.fn(),
655
989
  markWorkspaceHasHadSessions: vi.fn(async () => undefined),
656
- updateSession: vi.fn(async () => undefined),
990
+ updateSession: vi.fn(async () => session),
657
991
  updateSessionIfStatus: vi.fn(async () => null),
658
992
  };
659
993
  const promptBuilder = {
@@ -685,6 +1019,7 @@ function createStartableLifecycleService(options) {
685
1019
  })),
686
1020
  clearQueuedWork: vi.fn(),
687
1021
  clearSession: vi.fn(),
1022
+ markProcessExit: vi.fn(),
688
1023
  };
689
1024
  const sessionConfigService = {
690
1025
  applyConfiguredReasoningEffort: vi.fn(async () => undefined),
@@ -718,6 +1053,7 @@ function createStartableLifecycleService(options) {
718
1053
  run: vi.fn(async (operation) => await operation()),
719
1054
  },
720
1055
  sendSessionMessage,
1056
+ lifecycleEventService: { hydrate: vi.fn(async () => undefined), record: vi.fn() },
721
1057
  });
722
1058
  service.configure({
723
1059
  workspace: {
@@ -731,16 +1067,84 @@ function createStartableLifecycleService(options) {
731
1067
  service.deliverPendingChildNotifications = vi.fn(async () => options?.pendingNotificationCount ?? 0);
732
1068
  return {
733
1069
  service,
1070
+ session,
1071
+ repository,
734
1072
  sendSessionMessage,
735
1073
  tryDispatchNextMessage,
736
1074
  sessionConfigService,
737
1075
  runtimeManager,
1076
+ sessionDomainService,
738
1077
  };
739
1078
  }
740
1079
  describe('SessionLifecycleService startSession pending workspace notifications', () => {
741
1080
  beforeEach(() => {
742
1081
  vi.clearAllMocks();
743
1082
  });
1083
+ it('does not retain a stop generation for a missing session', async () => {
1084
+ const { service, repository } = createStartableLifecycleService();
1085
+ repository.getSessionById.mockResolvedValueOnce(null);
1086
+ await expect(service.startSession('missing-session')).rejects.toThrow('Session not found: missing-session');
1087
+ expect(getStopGenerations(service).has('missing-session')).toBe(false);
1088
+ });
1089
+ it('does not create a client when stop completes during the initial session lookup', async () => {
1090
+ const { service, session, repository, runtimeManager } = createStartableLifecycleService();
1091
+ let resolveSession;
1092
+ repository.getSessionById.mockReturnValueOnce(new Promise((resolve) => {
1093
+ resolveSession = resolve;
1094
+ }));
1095
+ const startResult = service.startSession('session-1').catch((error) => error);
1096
+ await vi.waitFor(() => {
1097
+ expect(repository.getSessionById).toHaveBeenCalledWith('session-1');
1098
+ });
1099
+ await service.stopSession('session-1');
1100
+ resolveSession(session);
1101
+ await expect(startResult).resolves.toEqual(expect.objectContaining({ message: 'Session is currently being stopped' }));
1102
+ expect(runtimeManager.getOrCreateClient).not.toHaveBeenCalled();
1103
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1104
+ });
1105
+ it('releases the stop generation when startup fails before creating a runtime', async () => {
1106
+ const { service, runtimeManager } = createStartableLifecycleService();
1107
+ runtimeManager.getOrCreateClient.mockRejectedValueOnce(new Error('spawn failed'));
1108
+ await expect(service.startSession('session-1')).rejects.toThrow('spawn failed');
1109
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1110
+ });
1111
+ it('does not retain a stop generation when client lookup cannot find the session', async () => {
1112
+ const { service, repository } = createStartableLifecycleService();
1113
+ repository.getSessionById.mockResolvedValueOnce(null);
1114
+ await expect(service.getOrCreateSessionClient('missing-session')).rejects.toThrow('Session not found: missing-session');
1115
+ expect(getStopGenerations(service).has('missing-session')).toBe(false);
1116
+ });
1117
+ it('releases the stop generation when record-based client creation fails', async () => {
1118
+ const { service, session, runtimeManager } = createStartableLifecycleService();
1119
+ runtimeManager.getOrCreateClient.mockRejectedValueOnce(new Error('spawn failed'));
1120
+ await expect(service.getOrCreateSessionClientFromRecord(session)).rejects.toThrow('spawn failed');
1121
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1122
+ });
1123
+ it('does not release a stop generation still owned by a concurrent startup', async () => {
1124
+ let resolveFirstSettings;
1125
+ const firstSettings = new Promise((resolve) => {
1126
+ resolveFirstSettings = resolve;
1127
+ });
1128
+ vi.mocked(userSettingsService.get)
1129
+ .mockReturnValueOnce(firstSettings)
1130
+ .mockResolvedValueOnce(unsafeCoerce({
1131
+ defaultWorkspacePermissions: 'STRICT',
1132
+ ratchetPermissions: 'YOLO',
1133
+ }));
1134
+ const { service, runtimeManager } = createStartableLifecycleService();
1135
+ const firstStart = service.startSession('session-1');
1136
+ await vi.waitFor(() => {
1137
+ expect(userSettingsService.get).toHaveBeenCalledTimes(1);
1138
+ });
1139
+ runtimeManager.getOrCreateClient.mockRejectedValueOnce(new Error('second spawn failed'));
1140
+ await expect(service.startSession('session-1')).rejects.toThrow('second spawn failed');
1141
+ resolveFirstSettings(unsafeCoerce({
1142
+ defaultWorkspacePermissions: 'STRICT',
1143
+ ratchetPermissions: 'YOLO',
1144
+ }));
1145
+ await expect(firstStart).resolves.toBeUndefined();
1146
+ expect(getStopGenerations(service).has('session-1')).toBe(true);
1147
+ });
744
1148
  it('dispatches queued notifications after startup presets and skips the default continue prompt', async () => {
745
1149
  const { service, sendSessionMessage, tryDispatchNextMessage, sessionConfigService } = createStartableLifecycleService({ pendingNotificationCount: 2 });
746
1150
  await service.startSession('session-1');
@@ -768,6 +1172,22 @@ describe('SessionLifecycleService startSession pending workspace notifications',
768
1172
  expect(sendOrder).toBeDefined();
769
1173
  expect(dispatchOrder).toBeLessThan(sendOrder);
770
1174
  });
1175
+ it('does not complete startup when stop finishes during the initial prompt', async () => {
1176
+ let resolvePrompt;
1177
+ const pendingPrompt = new Promise((resolve) => {
1178
+ resolvePrompt = resolve;
1179
+ });
1180
+ const { service, sendSessionMessage } = createStartableLifecycleService();
1181
+ sendSessionMessage.mockReturnValueOnce(pendingPrompt);
1182
+ const startResult = service.startSession('session-1').catch((error) => error);
1183
+ await vi.waitFor(() => {
1184
+ expect(sendSessionMessage).toHaveBeenCalledWith('session-1', 'Continue with the task.');
1185
+ });
1186
+ await service.stopSession('session-1');
1187
+ resolvePrompt(undefined);
1188
+ await expect(startResult).resolves.toEqual(expect.objectContaining({ message: 'Session is currently being stopped' }));
1189
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1190
+ });
771
1191
  it('does not create a client after stop completes during permission resolution', async () => {
772
1192
  let resolveSettings;
773
1193
  const pendingSettings = new Promise((resolve) => {
@@ -780,6 +1200,7 @@ describe('SessionLifecycleService startSession pending workspace notifications',
780
1200
  expect(userSettingsService.get).toHaveBeenCalled();
781
1201
  });
782
1202
  await service.stopSession('session-1');
1203
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
783
1204
  resolveSettings(unsafeCoerce({
784
1205
  defaultWorkspacePermissions: 'STRICT',
785
1206
  ratchetPermissions: 'YOLO',
@@ -787,6 +1208,42 @@ describe('SessionLifecycleService startSession pending workspace notifications',
787
1208
  await expect(startResult).resolves.toEqual(expect.objectContaining({ message: 'Session is currently being stopped' }));
788
1209
  expect(runtimeManager.getOrCreateClient).not.toHaveBeenCalled();
789
1210
  expect(sendSessionMessage).not.toHaveBeenCalled();
1211
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1212
+ });
1213
+ it('releases the stop generation after a session stops', async () => {
1214
+ const { service } = createStartableLifecycleService();
1215
+ await service.stopSession('session-1');
1216
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1217
+ });
1218
+ it('releases the stop generation when viewers retain inactive session state', async () => {
1219
+ const { service, sessionDomainService } = createStartableLifecycleService();
1220
+ sessionEventBus.registerViewerCountProvider((sessionId) => (sessionId === 'session-1' ? 1 : 0));
1221
+ try {
1222
+ await service.stopSession('session-1');
1223
+ }
1224
+ finally {
1225
+ sessionEventBus.registerViewerCountProvider(null);
1226
+ }
1227
+ expect(sessionDomainService.clearSession).not.toHaveBeenCalled();
1228
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1229
+ });
1230
+ it('does not clear a restarted generation when an old runtime exit finishes', async () => {
1231
+ const { service, session, repository } = createStartableLifecycleService();
1232
+ let resolveUpdate;
1233
+ repository.updateSession.mockReturnValueOnce(new Promise((resolve) => {
1234
+ resolveUpdate = resolve;
1235
+ }));
1236
+ const oldGeneration = service.getStopGeneration('session-1');
1237
+ const handlers = service.setupAcpEventHandler('session-1');
1238
+ const exitPromise = handlers.onExit('session-1', 0);
1239
+ expect(getStopGenerations(service).has('session-1')).toBe(false);
1240
+ await service.startSession('session-1');
1241
+ const restartedGeneration = service.getStopGeneration('session-1');
1242
+ expect(restartedGeneration).not.toBe(oldGeneration);
1243
+ resolveUpdate(session);
1244
+ await exitPromise;
1245
+ expect(service.isStopGenerationCurrent('session-1', restartedGeneration)).toBe(true);
1246
+ expect(service.isStopGenerationCurrent('session-1', oldGeneration)).toBe(false);
790
1247
  });
791
1248
  it('waits for a registered client creation and stops the resulting runtime', async () => {
792
1249
  const { service, sendSessionMessage, runtimeManager } = createStartableLifecycleService();