factory-factory 0.4.4 → 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.
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CtUj9J56.js → abnfDiagram-VRR7QNED-DygwVN25.js} +1 -1
- package/dist/client/assets/{arc-2QeawLbF.js → arc-CcnMw5av.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-Cz-V9xRh.js → architectureDiagram-ZJ3FMSHR-BZgeaNrI.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-BN6MDejN.js → blockDiagram-677ZJIJ3-DQ77xvqH.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-ZxcmUt9u.js → c4Diagram-LMCZKHZV-B-9QZJ1e.js} +1 -1
- package/dist/client/assets/channel-zAL33ya-.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CyvdNBvs.js → chunk-2Q5K7J3B-DhfBY-Wd.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-BXA35oY0.js → chunk-32BRIVSS-CV9UmSkI.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-6lY9D3Us.js → chunk-5VM5RSS4-BUNj3iDY.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BRYEhwoK.js → chunk-EX3LRPZG-Dj63JdET.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-DBeDDTf3.js → chunk-JWPE2WC7-i6Ud3VAu.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-MIX8YUEz.js → chunk-MOJQB5TN-PY5GLOxr.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CT_f1sbt.js → chunk-RYQCIY6F-pIEuQy1L.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-DpMacC1A.js → chunk-V7JOEXUC--zp7Z0xS.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-Bd6MGh78.js → chunk-VR4S4FIN-JstmsazP.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-BYg-oILv.js → chunk-XXDRQBXY-C66lsFmy.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-Dksz7rAA.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-Dksz7rAA.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-BGhjYi6z.js → cose-bilkent-JH36ORCC-D3jSuAHn.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-BouObN4T.js → cynefin-VYW2F7L2-Dx3xfGS7.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DrbscNyk.js → cynefinDiagram-TSTJHNR4-BcGYZcSJ.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CpHBhtab.js → dagre-VKFMJZFB-CLZSDf9x.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-C_pXymmj.js → diagram-FQU43EPY-Nosv2FJf.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-DNPz9Qrh.js → diagram-G47NLZAW-CPjqONaO.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-DWHcerSm.js → diagram-NH7WQ7WH-BmRiUIAJ.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-C6Jnb8nm.js → diagram-OA4YK3LP-C7WEtC2B.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-DF667tXW.js → diagram-WEI45ONY-up5rQ6My.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-_Wt4C-Lm.js → ebnfDiagram-CCIWWBDH-D2Ye87k4.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-D9F9IIZ7.js → erDiagram-Q63AITRT-CgZF8eG-.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-BzTUtnLY.js → flowDiagram-23GEKE2U-bxoO3Eep.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-lmFnKYE5.js → ganttDiagram-NO4QXBWP-BAtqPw6P.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-DiIQa_m6.js → gitGraphDiagram-IHSO6WYX-DYQqeJ8w.js} +1 -1
- package/dist/client/assets/{index-CpvT2M5m.js → index-BZQlu-dZ.js} +155 -150
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BXtmiN5g.js → infoDiagram-FWYZ7A6U-CZQCerFQ.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-Ce8fzasD.js → ishikawaDiagram-FXEZZL3T-Bc7wE25N.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-CAhJE-OU.js → journeyDiagram-5HDEW3XC-D5qqjKJz.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-B8br5xew.js → kanban-definition-HUTT4EX6-42kkaHBo.js} +1 -1
- package/dist/client/assets/{linear-BPVVEns_.js → linear-Dg3NQEM3.js} +1 -1
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-lX6h9H-N.js → mindmap-definition-LN4V7U3C-CRANKUcD.js} +1 -1
- package/dist/client/assets/{mobile-baseline-C45NSkzw.js → mobile-baseline-D6bQ8WTB.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-CiFWwnk4.js → pegDiagram-2B236MQR-qJQPOd8H.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-CvkwWtzQ.js → pieDiagram-ENE6RG2P-B5wB7Ccf.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-B40anMyK.js → quadrantDiagram-ABIIQ3AL-CCZfXF8m.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-WVYsdkDC.js → railroadDiagram-RFXS5EU6-BuIrSJUW.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-DBTIsXpA.js → requirementDiagram-TGXJPOKE-DfIr0zZ3.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-Dx8y47pZ.js → sankeyDiagram-HTMAVEWB-CJoUXRWq.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-C6ZZMZJm.js → sequenceDiagram-DBY2YBRQ-Cq1lOU1b.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-DLy8loNR.js → sizeCapture-X5ZJPWSS-XHOsOkor.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-RoTcqZkX.js → stateDiagram-2N3HPSRC-BLVzzete.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-DA02HYvU.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-CFmoSHiT.js → swimlanes-5IMT3BWC-C20mYIPR.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CbfcpXpQ.js +8 -0
- package/dist/client/assets/{terminal-instance-Br0jXsXE.js → terminal-instance-Dw2gcPib.js} +1 -1
- package/dist/client/assets/{timeline-definition-FHXFAJF6-Bh5XA09y.js → timeline-definition-FHXFAJF6-DlceJAxJ.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BC754zGO.js → vennDiagram-L72KCM5P-CFl-Eo1Y.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-xJdUzWCg.js → wardleyDiagram-EHGQE667-eBpeLOoN.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-wxQH1S7i.js → xychartDiagram-FW5EYKEG-B8RBkKiX.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/prisma/generated/browser.d.ts +1 -0
- package/dist/prisma/generated/browser.d.ts.map +1 -1
- package/dist/prisma/generated/client.d.ts +1 -0
- package/dist/prisma/generated/client.d.ts.map +1 -1
- package/dist/prisma/generated/commonInputTypes.d.ts +60 -0
- package/dist/prisma/generated/commonInputTypes.d.ts.map +1 -1
- package/dist/prisma/generated/enums.d.ts +15 -0
- package/dist/prisma/generated/enums.d.ts.map +1 -1
- package/dist/prisma/generated/enums.js +13 -0
- package/dist/prisma/generated/enums.js.map +1 -1
- package/dist/prisma/generated/internal/class.d.ts +3 -0
- package/dist/prisma/generated/internal/class.d.ts.map +1 -1
- package/dist/prisma/generated/internal/class.js +4 -4
- package/dist/prisma/generated/internal/class.js.map +1 -1
- package/dist/prisma/generated/internal/prismaNamespace.d.ts +90 -1
- package/dist/prisma/generated/internal/prismaNamespace.d.ts.map +1 -1
- package/dist/prisma/generated/internal/prismaNamespace.js +11 -0
- package/dist/prisma/generated/internal/prismaNamespace.js.map +1 -1
- package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts +12 -0
- package/dist/prisma/generated/internal/prismaNamespaceBrowser.d.ts.map +1 -1
- package/dist/prisma/generated/internal/prismaNamespaceBrowser.js +11 -0
- package/dist/prisma/generated/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/prisma/generated/models/SessionLifecycleEvent.d.ts +657 -0
- package/dist/prisma/generated/models/SessionLifecycleEvent.d.ts.map +1 -0
- package/dist/prisma/generated/models/SessionLifecycleEvent.js +2 -0
- package/dist/prisma/generated/models/SessionLifecycleEvent.js.map +1 -0
- package/dist/prisma/generated/models/Workspace.d.ts +264 -0
- package/dist/prisma/generated/models/Workspace.d.ts.map +1 -1
- package/dist/prisma/generated/models.d.ts +1 -0
- package/dist/prisma/generated/models.d.ts.map +1 -1
- package/dist/src/backend/app-context.d.ts.map +1 -1
- package/dist/src/backend/app-context.js +2 -1
- package/dist/src/backend/app-context.js.map +1 -1
- package/dist/src/backend/interceptors/branch-naming.interceptor.d.ts.map +1 -1
- package/dist/src/backend/interceptors/branch-naming.interceptor.js +0 -4
- package/dist/src/backend/interceptors/branch-naming.interceptor.js.map +1 -1
- package/dist/src/backend/interceptors/branch-naming.interceptor.test.js +26 -0
- package/dist/src/backend/interceptors/branch-naming.interceptor.test.js.map +1 -1
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.d.ts +2 -1
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.d.ts.map +1 -1
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.js +6 -6
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.js.map +1 -1
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js +18 -8
- package/dist/src/backend/orchestration/domain-bridges.orchestrator.test.js.map +1 -1
- package/dist/src/backend/orchestration/event-collector.orchestrator.d.ts.map +1 -1
- package/dist/src/backend/orchestration/event-collector.orchestrator.js +3 -1
- package/dist/src/backend/orchestration/event-collector.orchestrator.js.map +1 -1
- package/dist/src/backend/orchestration/event-collector.orchestrator.test.js +3 -1
- package/dist/src/backend/orchestration/event-collector.orchestrator.test.js.map +1 -1
- package/dist/src/backend/orchestration/reconciliation.service.d.ts +11 -8
- package/dist/src/backend/orchestration/reconciliation.service.d.ts.map +1 -1
- package/dist/src/backend/orchestration/reconciliation.service.js +41 -33
- package/dist/src/backend/orchestration/reconciliation.service.js.map +1 -1
- package/dist/src/backend/orchestration/reconciliation.service.test.js +9 -42
- package/dist/src/backend/orchestration/reconciliation.service.test.js.map +1 -1
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts +4 -1
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.d.ts.map +1 -1
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.js +4 -1
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.js.map +1 -1
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.test.js +15 -2
- package/dist/src/backend/orchestration/workspace-archive.orchestrator.test.js.map +1 -1
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.d.ts.map +1 -1
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.js +3 -1
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.js.map +1 -1
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.d.ts +2 -0
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.d.ts.map +1 -0
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.js +75 -0
- package/dist/src/backend/orchestration/workspace-init-issue-prompts.test.js.map +1 -0
- package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts +7 -4
- package/dist/src/backend/orchestration/workspace-init.orchestrator.d.ts.map +1 -1
- package/dist/src/backend/orchestration/workspace-init.orchestrator.js +36 -1
- package/dist/src/backend/orchestration/workspace-init.orchestrator.js.map +1 -1
- package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js +117 -8
- package/dist/src/backend/orchestration/workspace-init.orchestrator.test.js.map +1 -1
- package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.d.ts.map +1 -1
- package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.js +27 -6
- package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.js.map +1 -1
- package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.test.js +7 -0
- package/dist/src/backend/services/periodic-task/resources/periodic-task.accessor.test.js.map +1 -1
- package/dist/src/backend/services/port.service.d.ts.map +1 -1
- package/dist/src/backend/services/port.service.js +4 -0
- package/dist/src/backend/services/port.service.js.map +1 -1
- package/dist/src/backend/services/port.service.test.js +8 -0
- package/dist/src/backend/services/port.service.test.js.map +1 -1
- package/dist/src/backend/services/registry.d.ts +2 -2
- package/dist/src/backend/services/registry.d.ts.map +1 -1
- package/dist/src/backend/services/registry.js +2 -1
- package/dist/src/backend/services/registry.js.map +1 -1
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.d.ts +23 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.d.ts.map +1 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.js +23 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.js.map +1 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.d.ts +2 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.d.ts.map +1 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.js +62 -0
- package/dist/src/backend/services/session/resources/session-lifecycle-event.accessor.test.js.map +1 -0
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.d.ts +2 -1
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.js +14 -7
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.js.map +1 -1
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.test.js +33 -0
- package/dist/src/backend/services/session/service/acp/acp-runtime-manager.test.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/attachment-processing.test.js +21 -0
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/attachment-processing.test.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.js +3 -279
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.test.js +154 -7
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/load-session.handler.test.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.js +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.test.js +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/handlers/stop.handler.test.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/image-format-validation.js +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers/image-format-validation.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.js +4 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.js.map +1 -1
- package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.test.js +49 -0
- package/dist/src/backend/services/session/service/chat/chat-message-handlers.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/index.d.ts +1 -1
- package/dist/src/backend/services/session/service/index.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/index.js +1 -1
- package/dist/src/backend/services/session/service/index.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.d.ts +5 -1
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.js +23 -4
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.text-streaming.test.js +28 -0
- package/dist/src/backend/services/session/service/lifecycle/acp-event-processor.text-streaming.test.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.d.ts +26 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.d.ts.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.js +84 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.js.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.d.ts +2 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.d.ts.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.js +283 -0
- package/dist/src/backend/services/session/service/lifecycle/session-lifecycle-event.service.test.js.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.d.ts +10 -0
- package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.d.ts.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.js +299 -0
- package/dist/src/backend/services/session/service/lifecycle/session-provider-history-hydrator.js.map +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session-services.d.ts +2 -0
- package/dist/src/backend/services/session/service/lifecycle/session-services.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session-services.js +11 -0
- package/dist/src/backend/services/session/service/lifecycle/session-services.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.d.ts +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.js +17 -0
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.test.js +42 -0
- package/dist/src/backend/services/session/service/lifecycle/session.config.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.d.ts +2 -0
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.js +21 -0
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.test.js +26 -1
- package/dist/src/backend/services/session/service/lifecycle/session.error-message.test.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.d.ts +31 -2
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.js +279 -109
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.test.js +465 -8
- package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.prompt.service.test.js +206 -5
- package/dist/src/backend/services/session/service/lifecycle/session.prompt.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.service.d.ts +15 -2
- package/dist/src/backend/services/session/service/lifecycle/session.service.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.service.js +87 -14
- package/dist/src/backend/services/session/service/lifecycle/session.service.js.map +1 -1
- package/dist/src/backend/services/session/service/lifecycle/session.service.test.js +1 -0
- package/dist/src/backend/services/session/service/lifecycle/session.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/session-domain.service.d.ts +1 -0
- package/dist/src/backend/services/session/service/session-domain.service.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/session-domain.service.js +9 -0
- package/dist/src/backend/services/session/service/session-domain.service.js.map +1 -1
- package/dist/src/backend/services/session/service/session-domain.service.test.js +58 -0
- package/dist/src/backend/services/session/service/session-domain.service.test.js.map +1 -1
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.d.ts +6 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.d.ts.map +1 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.js +114 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.js.map +1 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.d.ts +2 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.d.ts.map +1 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.js +129 -0
- package/dist/src/backend/services/session/service/store/session-lifecycle-transcript.test.js.map +1 -0
- package/dist/src/backend/services/session/service/store/session-transcript.d.ts.map +1 -1
- package/dist/src/backend/services/session/service/store/session-transcript.js +7 -4
- package/dist/src/backend/services/session/service/store/session-transcript.js.map +1 -1
- package/dist/src/backend/services/workspace/service/lifecycle/creation.service.d.ts.map +1 -1
- package/dist/src/backend/services/workspace/service/lifecycle/creation.service.js +1 -0
- package/dist/src/backend/services/workspace/service/lifecycle/creation.service.js.map +1 -1
- package/dist/src/backend/services/workspace/service/lifecycle/creation.service.test.js +12 -0
- package/dist/src/backend/services/workspace/service/lifecycle/creation.service.test.js.map +1 -1
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.d.ts +1 -0
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.d.ts.map +1 -1
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.js +14 -0
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.js.map +1 -1
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.test.js +54 -0
- package/dist/src/backend/services/workspace/service/worktree/worktree-lifecycle.service.test.js.map +1 -1
- package/dist/src/backend/trpc/project.router.test.js +19 -0
- package/dist/src/backend/trpc/project.router.test.js.map +1 -1
- package/dist/src/backend/trpc/project.trpc.js +1 -1
- package/dist/src/backend/trpc/project.trpc.js.map +1 -1
- package/dist/src/backend/trpc/session.router.test.js +11 -0
- package/dist/src/backend/trpc/session.router.test.js.map +1 -1
- package/dist/src/backend/trpc/session.trpc.d.ts.map +1 -1
- package/dist/src/backend/trpc/session.trpc.js +4 -0
- package/dist/src/backend/trpc/session.trpc.js.map +1 -1
- package/dist/src/cli/runtime-utils.d.ts.map +1 -1
- package/dist/src/cli/runtime-utils.js +4 -0
- package/dist/src/cli/runtime-utils.js.map +1 -1
- package/dist/src/cli/runtime-utils.test.js +7 -0
- package/dist/src/cli/runtime-utils.test.js.map +1 -1
- package/dist/src/shared/acp-protocol/protocol/messages.d.ts +18 -2
- package/dist/src/shared/acp-protocol/protocol/messages.d.ts.map +1 -1
- package/dist/src/shared/acp-protocol/protocol/messages.js +1 -0
- package/dist/src/shared/acp-protocol/protocol/messages.js.map +1 -1
- package/dist/src/shared/acp-protocol/protocol/renderer-window.d.ts.map +1 -1
- package/dist/src/shared/acp-protocol/protocol/renderer-window.js +3 -0
- package/dist/src/shared/acp-protocol/protocol/renderer-window.js.map +1 -1
- package/dist/src/shared/core/enums.d.ts +15 -0
- package/dist/src/shared/core/enums.d.ts.map +1 -1
- package/dist/src/shared/core/enums.js +13 -0
- package/dist/src/shared/core/enums.js.map +1 -1
- package/dist/src/shared/core/index.d.ts +1 -1
- package/dist/src/shared/core/index.d.ts.map +1 -1
- package/dist/src/shared/core/index.js +1 -1
- package/dist/src/shared/core/index.js.map +1 -1
- package/dist/src/shared/issue-start-prompt.d.ts.map +1 -1
- package/dist/src/shared/issue-start-prompt.js +8 -3
- package/dist/src/shared/issue-start-prompt.js.map +1 -1
- package/dist/src/test-utils/application-graph.d.ts.map +1 -1
- package/dist/src/test-utils/application-graph.js +3 -1
- package/dist/src/test-utils/application-graph.js.map +1 -1
- package/dist/tsconfig.backend.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/prisma/migrations/20260730140000_add_session_lifecycle_events/migration.sql +20 -0
- package/prisma/schema.prisma +32 -0
- package/dist/client/assets/channel-DVMObwOq.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-wHGXcbfd.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-wHGXcbfd.js +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-CGxDgHho.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-1u_1g3u4.js +0 -8
package/dist/src/backend/services/session/service/lifecycle/session.lifecycle.service.test.js
CHANGED
|
@@ -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.
|
|
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 () =>
|
|
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();
|