termdock 1.4.308 → 1.4.310

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 (375) hide show
  1. package/README.en.md +3 -1
  2. package/README.md +3 -1
  3. package/THIRD_PARTY_NOTICES.md +30 -0
  4. package/dist/client/assets/{App-ggIA_xqR.js → App-CytIMrFr.js} +62 -55
  5. package/dist/client/assets/App-CytIMrFr.js.br +0 -0
  6. package/dist/client/assets/App-CytIMrFr.js.gz +0 -0
  7. package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js +9 -0
  8. package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js.br +0 -0
  9. package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js.gz +0 -0
  10. package/dist/client/assets/{ArchitecturePanel-Cmpmp6FS.css → ArchitecturePanel-B_BDrrfR.css} +1 -1
  11. package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.br +0 -0
  12. package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.gz +0 -0
  13. package/dist/client/assets/{ChangeWalkthroughPanel-D1Puc7B3.js → ChangeWalkthroughPanel-Bm9Tvd6H.js} +2 -2
  14. package/dist/client/assets/ChangeWalkthroughPanel-Bm9Tvd6H.js.br +0 -0
  15. package/dist/client/assets/ChangeWalkthroughPanel-Bm9Tvd6H.js.gz +0 -0
  16. package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js +1 -0
  17. package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js.br +0 -0
  18. package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js.gz +0 -0
  19. package/dist/client/assets/CollaborationTaskContent-D7LXylWr.js +2 -0
  20. package/dist/client/assets/{ComputerControlView-D5Lw9sNA.js → ComputerControlView-CC6r2cD3.js} +2 -2
  21. package/dist/client/assets/ComputerControlView-CC6r2cD3.js.br +0 -0
  22. package/dist/client/assets/ComputerControlView-CC6r2cD3.js.gz +0 -0
  23. package/dist/client/assets/{DagPlayground-DYJrGfiG.js → DagPlayground-BqAadqBT.js} +1 -1
  24. package/dist/client/assets/DagPlayground-BqAadqBT.js.br +0 -0
  25. package/dist/client/assets/DagPlayground-BqAadqBT.js.gz +0 -0
  26. package/dist/client/assets/{DiffLab-5zdcFW3z.js → DiffLab-CrEVLy-j.js} +1 -1
  27. package/dist/client/assets/DiffLab-CrEVLy-j.js.br +0 -0
  28. package/dist/client/assets/DiffLab-CrEVLy-j.js.gz +0 -0
  29. package/dist/client/assets/{DiffReview-BBBC_NjV.js → DiffReview-C8e2GQxb.js} +1 -1
  30. package/dist/client/assets/DiffReview-C8e2GQxb.js.br +0 -0
  31. package/dist/client/assets/DiffReview-C8e2GQxb.js.gz +0 -0
  32. package/dist/client/assets/{DiffReviewLab-CbhosFSB.js → DiffReviewLab-RQ1NmpZE.js} +2 -2
  33. package/dist/client/assets/DiffReviewLab-RQ1NmpZE.js.br +0 -0
  34. package/dist/client/assets/DiffReviewLab-RQ1NmpZE.js.gz +0 -0
  35. package/dist/client/assets/{DiffViewer-BuQwFQ4V.js → DiffViewer-KljgMa9h.js} +8 -8
  36. package/dist/client/assets/DiffViewer-KljgMa9h.js.br +0 -0
  37. package/dist/client/assets/DiffViewer-KljgMa9h.js.gz +0 -0
  38. package/dist/client/assets/{ModelPreview-nO2VmqdP.js → ModelPreview-B9UR1TR3.js} +1 -1
  39. package/dist/client/assets/ModelPreview-B9UR1TR3.js.br +0 -0
  40. package/dist/client/assets/ModelPreview-B9UR1TR3.js.gz +0 -0
  41. package/dist/client/assets/RightSidebar-DSH93gN5.js +1677 -0
  42. package/dist/client/assets/RightSidebar-DSH93gN5.js.br +0 -0
  43. package/dist/client/assets/RightSidebar-DSH93gN5.js.gz +0 -0
  44. package/dist/client/assets/{abnfDiagram-VCTEODGH-yi0K8GDG.js → abnfDiagram-VCTEODGH-WFUf9n1A.js} +1 -1
  45. package/dist/client/assets/abnfDiagram-VCTEODGH-WFUf9n1A.js.br +0 -0
  46. package/dist/client/assets/abnfDiagram-VCTEODGH-WFUf9n1A.js.gz +0 -0
  47. package/dist/client/assets/{app-B6FdOF6K.js → app-Bey8S6UU.js} +4 -4
  48. package/dist/client/assets/app-Bey8S6UU.js.br +0 -0
  49. package/dist/client/assets/app-Bey8S6UU.js.gz +0 -0
  50. package/dist/client/assets/{arc-_nLI_-XZ.js → arc-BXKxIsA3.js} +1 -1
  51. package/dist/client/assets/arc-BXKxIsA3.js.br +0 -0
  52. package/dist/client/assets/arc-BXKxIsA3.js.gz +0 -0
  53. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CikrA78O.js → architectureDiagram-5GKGNRK7-DAfRrSxr.js} +1 -1
  54. package/dist/client/assets/architectureDiagram-5GKGNRK7-DAfRrSxr.js.br +0 -0
  55. package/dist/client/assets/architectureDiagram-5GKGNRK7-DAfRrSxr.js.gz +0 -0
  56. package/dist/client/assets/{blockDiagram-I7D4REHJ-CWJq6qJ7.js → blockDiagram-I7D4REHJ-1HbSy7rb.js} +1 -1
  57. package/dist/client/assets/blockDiagram-I7D4REHJ-1HbSy7rb.js.br +0 -0
  58. package/dist/client/assets/blockDiagram-I7D4REHJ-1HbSy7rb.js.gz +0 -0
  59. package/dist/client/assets/{c4Diagram-7LVT6UL2-gzPzLGvs.js → c4Diagram-7LVT6UL2-Dxwui-M_.js} +1 -1
  60. package/dist/client/assets/c4Diagram-7LVT6UL2-Dxwui-M_.js.br +0 -0
  61. package/dist/client/assets/c4Diagram-7LVT6UL2-Dxwui-M_.js.gz +0 -0
  62. package/dist/client/assets/channel-Dkh0SMv-.js +1 -0
  63. package/dist/client/assets/{chunk-2Q5K7J3B-CfX9l18E.js → chunk-2Q5K7J3B-CJSdJ_A1.js} +1 -1
  64. package/dist/client/assets/{chunk-5VM5RSS4-JSV1yrXM.js → chunk-5VM5RSS4-BNEXn4bI.js} +1 -1
  65. package/dist/client/assets/{chunk-F27PBJKO-CQ0LXeap.js → chunk-F27PBJKO-BaM6iLMb.js} +1 -1
  66. package/dist/client/assets/chunk-F27PBJKO-BaM6iLMb.js.br +0 -0
  67. package/dist/client/assets/chunk-F27PBJKO-BaM6iLMb.js.gz +0 -0
  68. package/dist/client/assets/{chunk-IMKFNOWR-CJOEPkK9.js → chunk-IMKFNOWR-DxPJBdLq.js} +1 -1
  69. package/dist/client/assets/chunk-IMKFNOWR-DxPJBdLq.js.br +0 -0
  70. package/dist/client/assets/chunk-IMKFNOWR-DxPJBdLq.js.gz +0 -0
  71. package/dist/client/assets/{chunk-JWPE2WC7-Dpp7Xu_E.js → chunk-JWPE2WC7-BwEOSoiu.js} +1 -1
  72. package/dist/client/assets/{chunk-POPQ4Y6H-9hDa1b8z.js → chunk-POPQ4Y6H-CbYcBz8G.js} +1 -1
  73. package/dist/client/assets/{chunk-SVP7TREG-BskkG9gg.js → chunk-SVP7TREG-CpnycZ3K.js} +1 -1
  74. package/dist/client/assets/chunk-SVP7TREG-CpnycZ3K.js.br +0 -0
  75. package/dist/client/assets/chunk-SVP7TREG-CpnycZ3K.js.gz +0 -0
  76. package/dist/client/assets/{chunk-TICWLB2K-DyzuP-9I.js → chunk-TICWLB2K-5oWwjAOF.js} +1 -1
  77. package/dist/client/assets/chunk-TICWLB2K-5oWwjAOF.js.br +0 -0
  78. package/dist/client/assets/chunk-TICWLB2K-5oWwjAOF.js.gz +0 -0
  79. package/dist/client/assets/{chunk-XXDRQBXY-C31_1i6z.js → chunk-XXDRQBXY-VWUm4Sz5.js} +1 -1
  80. package/dist/client/assets/classDiagram-ZZMXUADV-DqvmSdwt.js +1 -0
  81. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DqvmSdwt.js +1 -0
  82. package/dist/client/assets/{cose-bilkent-JH36ORCC-Bm-ESDOc.js → cose-bilkent-JH36ORCC-gpF5Tw44.js} +1 -1
  83. package/dist/client/assets/cose-bilkent-JH36ORCC-gpF5Tw44.js.br +0 -0
  84. package/dist/client/assets/cose-bilkent-JH36ORCC-gpF5Tw44.js.gz +0 -0
  85. package/dist/client/assets/{cynefin-OW5HDTMX-DbFWAPvX.js → cynefin-OW5HDTMX-HFr7zRQ0.js} +1 -1
  86. package/dist/client/assets/cynefin-OW5HDTMX-HFr7zRQ0.js.br +0 -0
  87. package/dist/client/assets/cynefin-OW5HDTMX-HFr7zRQ0.js.gz +0 -0
  88. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js → cynefinDiagram-5FMLGOSQ-LccKgJj0.js} +1 -1
  89. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-LccKgJj0.js.br +0 -0
  90. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-LccKgJj0.js.gz +0 -0
  91. package/dist/client/assets/{dagre-GXQ25YYZ-D_5uEMjJ.js → dagre-GXQ25YYZ-C7eLk1r1.js} +1 -1
  92. package/dist/client/assets/dagre-GXQ25YYZ-C7eLk1r1.js.br +0 -0
  93. package/dist/client/assets/dagre-GXQ25YYZ-C7eLk1r1.js.gz +0 -0
  94. package/dist/client/assets/{diagram-S7CK7UJ4-DWVpndRP.js → diagram-S7CK7UJ4-DkWPZYAg.js} +1 -1
  95. package/dist/client/assets/diagram-S7CK7UJ4-DkWPZYAg.js.br +0 -0
  96. package/dist/client/assets/diagram-S7CK7UJ4-DkWPZYAg.js.gz +0 -0
  97. package/dist/client/assets/{diagram-UQ7AKVKN-DmYTya3w.js → diagram-UQ7AKVKN-DluTsMXz.js} +1 -1
  98. package/dist/client/assets/diagram-UQ7AKVKN-DluTsMXz.js.br +0 -0
  99. package/dist/client/assets/diagram-UQ7AKVKN-DluTsMXz.js.gz +0 -0
  100. package/dist/client/assets/{diagram-VSXAHHWV-BEA6kqNj.js → diagram-VSXAHHWV-BLXEmXmu.js} +1 -1
  101. package/dist/client/assets/diagram-VSXAHHWV-BLXEmXmu.js.br +0 -0
  102. package/dist/client/assets/diagram-VSXAHHWV-BLXEmXmu.js.gz +0 -0
  103. package/dist/client/assets/{diagram-VX7I27RA-CU9vP0P0.js → diagram-VX7I27RA-DO3J99WU.js} +1 -1
  104. package/dist/client/assets/diagram-VX7I27RA-DO3J99WU.js.br +0 -0
  105. package/dist/client/assets/diagram-VX7I27RA-DO3J99WU.js.gz +0 -0
  106. package/dist/client/assets/{diagram-Z3DM3KII-B_GvE2g-.js → diagram-Z3DM3KII-kvkSusTy.js} +1 -1
  107. package/dist/client/assets/diagram-Z3DM3KII-kvkSusTy.js.br +0 -0
  108. package/dist/client/assets/diagram-Z3DM3KII-kvkSusTy.js.gz +0 -0
  109. package/dist/client/assets/{ebnfDiagram-PWID7BFC-BLrPQuLE.js → ebnfDiagram-PWID7BFC-Bxd9hatc.js} +1 -1
  110. package/dist/client/assets/ebnfDiagram-PWID7BFC-Bxd9hatc.js.br +0 -0
  111. package/dist/client/assets/ebnfDiagram-PWID7BFC-Bxd9hatc.js.gz +0 -0
  112. package/dist/client/assets/{erDiagram-RLTQ6QDP-DlXgoyZ-.js → erDiagram-RLTQ6QDP-BWc3uM23.js} +1 -1
  113. package/dist/client/assets/erDiagram-RLTQ6QDP-BWc3uM23.js.br +0 -0
  114. package/dist/client/assets/erDiagram-RLTQ6QDP-BWc3uM23.js.gz +0 -0
  115. package/dist/client/assets/{flowDiagram-HODETNUW-fPHWxEDZ.js → flowDiagram-HODETNUW-BQhPtc9z.js} +1 -1
  116. package/dist/client/assets/flowDiagram-HODETNUW-BQhPtc9z.js.br +0 -0
  117. package/dist/client/assets/flowDiagram-HODETNUW-BQhPtc9z.js.gz +0 -0
  118. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-B8MOv6ug.js → ganttDiagram-EL5Y4UJY-DzmTLgVr.js} +1 -1
  119. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DzmTLgVr.js.br +0 -0
  120. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DzmTLgVr.js.gz +0 -0
  121. package/dist/client/assets/{gestureArbiter-JW8vcVRm.js → gestureArbiter-BuNZMZ7A.js} +1 -1
  122. package/dist/client/assets/gestureArbiter-BuNZMZ7A.js.br +0 -0
  123. package/dist/client/assets/gestureArbiter-BuNZMZ7A.js.gz +0 -0
  124. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-lH9gSoFx.js → gitGraphDiagram-WWUBYQGX-r0Onfl7K.js} +1 -1
  125. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-r0Onfl7K.js.br +0 -0
  126. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-r0Onfl7K.js.gz +0 -0
  127. package/dist/client/assets/{index-CNY7CTVa.css → index-C6D5a-dg.css} +1 -1
  128. package/dist/client/assets/index-C6D5a-dg.css.br +0 -0
  129. package/dist/client/assets/{index-CNY7CTVa.css.gz → index-C6D5a-dg.css.gz} +0 -0
  130. package/dist/client/assets/{infoDiagram-27XIBGKW-CSG29Ty_.js → infoDiagram-27XIBGKW-C52jpDzv.js} +1 -1
  131. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CcwSE5qV.js → ishikawaDiagram-5VMMS53U-C-l0eHmO.js} +1 -1
  132. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C-l0eHmO.js.br +0 -0
  133. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C-l0eHmO.js.gz +0 -0
  134. package/dist/client/assets/{journeyDiagram-3NMN7TZE-CXFUeP2c.js → journeyDiagram-3NMN7TZE-DmhgEOjX.js} +1 -1
  135. package/dist/client/assets/journeyDiagram-3NMN7TZE-DmhgEOjX.js.br +0 -0
  136. package/dist/client/assets/journeyDiagram-3NMN7TZE-DmhgEOjX.js.gz +0 -0
  137. package/dist/client/assets/{kanban-definition-UXKFOSKX-KEHwljd2.js → kanban-definition-UXKFOSKX-BnZWlxIT.js} +1 -1
  138. package/dist/client/assets/kanban-definition-UXKFOSKX-BnZWlxIT.js.br +0 -0
  139. package/dist/client/assets/kanban-definition-UXKFOSKX-BnZWlxIT.js.gz +0 -0
  140. package/dist/client/assets/{layout-DqYx0MnO.js → layout-BrxPpC7u.js} +1 -1
  141. package/dist/client/assets/layout-BrxPpC7u.js.br +0 -0
  142. package/dist/client/assets/layout-BrxPpC7u.js.gz +0 -0
  143. package/dist/client/assets/{linear-BnG3XaJW.js → linear-DaEpqVcH.js} +1 -1
  144. package/dist/client/assets/linear-DaEpqVcH.js.br +0 -0
  145. package/dist/client/assets/linear-DaEpqVcH.js.gz +0 -0
  146. package/dist/client/assets/{mermaid.core-Ce00rdM6.js → mermaid.core-CidoOS12.js} +5 -5
  147. package/dist/client/assets/mermaid.core-CidoOS12.js.br +0 -0
  148. package/dist/client/assets/mermaid.core-CidoOS12.js.gz +0 -0
  149. package/dist/client/assets/{mindmap-definition-YA3MSWOX-D0hOW4SY.js → mindmap-definition-YA3MSWOX-Bpr3CVLy.js} +1 -1
  150. package/dist/client/assets/mindmap-definition-YA3MSWOX-Bpr3CVLy.js.br +0 -0
  151. package/dist/client/assets/mindmap-definition-YA3MSWOX-Bpr3CVLy.js.gz +0 -0
  152. package/dist/client/assets/passwordBootstrap-D7hFrc0r.js +2 -0
  153. package/dist/client/assets/passwordBootstrap-D7hFrc0r.js.br +0 -0
  154. package/dist/client/assets/passwordBootstrap-D7hFrc0r.js.gz +0 -0
  155. package/dist/client/assets/{pegDiagram-XKGWAZYB-UKp9t91x.js → pegDiagram-XKGWAZYB-Bw2JpSYn.js} +1 -1
  156. package/dist/client/assets/pegDiagram-XKGWAZYB-Bw2JpSYn.js.br +0 -0
  157. package/dist/client/assets/pegDiagram-XKGWAZYB-Bw2JpSYn.js.gz +0 -0
  158. package/dist/client/assets/{pieDiagram-E7YTZNPT-Dif97wbH.js → pieDiagram-E7YTZNPT-BaKc3ifQ.js} +1 -1
  159. package/dist/client/assets/pieDiagram-E7YTZNPT-BaKc3ifQ.js.br +0 -0
  160. package/dist/client/assets/pieDiagram-E7YTZNPT-BaKc3ifQ.js.gz +0 -0
  161. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BPyHtH91.js → quadrantDiagram-AXDQQJYC-B0Yi-dl9.js} +1 -1
  162. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0Yi-dl9.js.br +0 -0
  163. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0Yi-dl9.js.gz +0 -0
  164. package/dist/client/assets/{railroadDiagram-O6MQD6OU-Wleq9LmS.js → railroadDiagram-O6MQD6OU-DCcCQofU.js} +1 -1
  165. package/dist/client/assets/railroadDiagram-O6MQD6OU-DCcCQofU.js.br +0 -0
  166. package/dist/client/assets/railroadDiagram-O6MQD6OU-DCcCQofU.js.gz +0 -0
  167. package/dist/client/assets/{rdpSession-CobFMSUs.js → rdpSession-DY81W5BY.js} +1 -1
  168. package/dist/client/assets/rdpSession-DY81W5BY.js.br +0 -0
  169. package/dist/client/assets/rdpSession-DY81W5BY.js.gz +0 -0
  170. package/dist/client/assets/{referenceLongPress-bZ0xp-v2.js → referenceLongPress-CAzWqXsY.js} +1 -1
  171. package/dist/client/assets/referenceLongPress-CAzWqXsY.js.br +0 -0
  172. package/dist/client/assets/referenceLongPress-CAzWqXsY.js.gz +0 -0
  173. package/dist/client/assets/{requirementDiagram-BXWQKSXE-BL4pp0Ta.js → requirementDiagram-BXWQKSXE-CEJFRPWH.js} +1 -1
  174. package/dist/client/assets/requirementDiagram-BXWQKSXE-CEJFRPWH.js.br +0 -0
  175. package/dist/client/assets/requirementDiagram-BXWQKSXE-CEJFRPWH.js.gz +0 -0
  176. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CV1cKx37.js → sankeyDiagram-P5KCCOFB-B3Wk45FA.js} +1 -1
  177. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B3Wk45FA.js.br +0 -0
  178. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B3Wk45FA.js.gz +0 -0
  179. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BF2InLwV.js → sequenceDiagram-WJ2MYXX4-BejEsITb.js} +1 -1
  180. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BejEsITb.js.br +0 -0
  181. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BejEsITb.js.gz +0 -0
  182. package/dist/client/assets/{sizeCapture-INFHLROL-CldUyStR.js → sizeCapture-INFHLROL-DZ5NJZnM.js} +1 -1
  183. package/dist/client/assets/sizeCapture-INFHLROL-DZ5NJZnM.js.br +0 -0
  184. package/dist/client/assets/sizeCapture-INFHLROL-DZ5NJZnM.js.gz +0 -0
  185. package/dist/client/assets/{sparkles-Cg-yPdE2.js → sparkles-DQGLdN2b.js} +1 -1
  186. package/dist/client/assets/{stateDiagram-D77RDMKH-CByehbEd.js → stateDiagram-D77RDMKH-D8SSEwHa.js} +1 -1
  187. package/dist/client/assets/stateDiagram-D77RDMKH-D8SSEwHa.js.br +0 -0
  188. package/dist/client/assets/stateDiagram-D77RDMKH-D8SSEwHa.js.gz +0 -0
  189. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-D1KQOetv.js +1 -0
  190. package/dist/client/assets/{swimlanes-42K2YHIH--1_7YTL4.js → swimlanes-42K2YHIH-Dow-EKMG.js} +1 -1
  191. package/dist/client/assets/swimlanes-42K2YHIH-Dow-EKMG.js.br +0 -0
  192. package/dist/client/assets/swimlanes-42K2YHIH-Dow-EKMG.js.gz +0 -0
  193. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DQbZCzq9.js +8 -0
  194. package/dist/client/assets/{timeline-definition-24CTP7MA-CXfXJFK6.js → timeline-definition-24CTP7MA-DpQ9D5f6.js} +1 -1
  195. package/dist/client/assets/timeline-definition-24CTP7MA-DpQ9D5f6.js.br +0 -0
  196. package/dist/client/assets/timeline-definition-24CTP7MA-DpQ9D5f6.js.gz +0 -0
  197. package/dist/client/assets/{triangle-alert-CbMRJ6sx.js → triangle-alert-Bdf_dBch.js} +1 -1
  198. package/dist/client/assets/{vennDiagram-4TSXK5OY-byr6UQpL.js → vennDiagram-4TSXK5OY-DoemAmM2.js} +1 -1
  199. package/dist/client/assets/vennDiagram-4TSXK5OY-DoemAmM2.js.br +0 -0
  200. package/dist/client/assets/vennDiagram-4TSXK5OY-DoemAmM2.js.gz +0 -0
  201. package/dist/client/assets/{videoCompression-ivJAy00d.js → videoCompression-Bg4gUQUf.js} +1 -1
  202. package/dist/client/assets/videoCompression-Bg4gUQUf.js.br +0 -0
  203. package/dist/client/assets/videoCompression-Bg4gUQUf.js.gz +0 -0
  204. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-FJduk-7s.js → wardleyDiagram-VM6X3IG4-MernEZ54.js} +1 -1
  205. package/dist/client/assets/wardleyDiagram-VM6X3IG4-MernEZ54.js.br +0 -0
  206. package/dist/client/assets/wardleyDiagram-VM6X3IG4-MernEZ54.js.gz +0 -0
  207. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ct1fBPFS.js → xychartDiagram-S5SC5T6Z-CzO3HEHD.js} +1 -1
  208. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CzO3HEHD.js.br +0 -0
  209. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CzO3HEHD.js.gz +0 -0
  210. package/dist/client/index.html +2 -2
  211. package/dist/client/index.html.br +0 -0
  212. package/dist/client/index.html.gz +0 -0
  213. package/dist/client/sw.js +1 -1
  214. package/dist/client/sw.js.br +0 -0
  215. package/dist/client/sw.js.gz +0 -0
  216. package/dist/server/agent/collaborationCli.js +21 -5
  217. package/dist/server/agent/collaborationStore.js +5 -0
  218. package/dist/server/agent/integrationCli.js +60 -2
  219. package/dist/server/agent/integrationServer.js +22 -2
  220. package/dist/server/agent/integrationSessions.js +250 -19
  221. package/dist/server/agent/integrationStore.js +1 -1
  222. package/dist/server/agent/sessionLifecycle.js +81 -0
  223. package/dist/server/agent/sessionResumeConfiguration.js +32 -0
  224. package/dist/server/architecture/cli.js +295 -0
  225. package/dist/server/architecture/document.js +328 -0
  226. package/dist/server/architecture/documentDiff.js +34 -0
  227. package/dist/server/architecture/fieldMutation.js +52 -0
  228. package/dist/server/architecture/flowchart.js +62 -0
  229. package/dist/server/architecture/mermaidDiff.js +85 -0
  230. package/dist/server/architecture/routes.js +117 -0
  231. package/dist/server/architecture/store.js +502 -0
  232. package/dist/server/cli.js +7 -1
  233. package/dist/server/entry.js +2 -0
  234. package/dist/server/federation/accessPolicy.js +9 -1
  235. package/dist/server/routes/terminal.js +75 -1
  236. package/docs/integrations.md +178 -3
  237. package/package.json +3 -1
  238. package/runtime-manifest.json +4 -4
  239. package/dist/client/assets/App-ggIA_xqR.js.br +0 -0
  240. package/dist/client/assets/App-ggIA_xqR.js.gz +0 -0
  241. package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.br +0 -0
  242. package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.gz +0 -0
  243. package/dist/client/assets/ArchitecturePanel-CoBspW_C.js +0 -12
  244. package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.br +0 -0
  245. package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.gz +0 -0
  246. package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.br +0 -0
  247. package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.gz +0 -0
  248. package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js +0 -1
  249. package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.br +0 -0
  250. package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.gz +0 -0
  251. package/dist/client/assets/CollaborationTaskContent-CTSJJqze.js +0 -2
  252. package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.br +0 -0
  253. package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.gz +0 -0
  254. package/dist/client/assets/DagPlayground-DYJrGfiG.js.br +0 -0
  255. package/dist/client/assets/DagPlayground-DYJrGfiG.js.gz +0 -0
  256. package/dist/client/assets/DiffLab-5zdcFW3z.js.br +0 -0
  257. package/dist/client/assets/DiffLab-5zdcFW3z.js.gz +0 -0
  258. package/dist/client/assets/DiffReview-BBBC_NjV.js.br +0 -0
  259. package/dist/client/assets/DiffReview-BBBC_NjV.js.gz +0 -0
  260. package/dist/client/assets/DiffReviewLab-CbhosFSB.js.br +0 -0
  261. package/dist/client/assets/DiffReviewLab-CbhosFSB.js.gz +0 -0
  262. package/dist/client/assets/DiffViewer-BuQwFQ4V.js.br +0 -0
  263. package/dist/client/assets/DiffViewer-BuQwFQ4V.js.gz +0 -0
  264. package/dist/client/assets/ModelPreview-nO2VmqdP.js.br +0 -0
  265. package/dist/client/assets/ModelPreview-nO2VmqdP.js.gz +0 -0
  266. package/dist/client/assets/RightSidebar-BoNzpS2K.js +0 -1677
  267. package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
  268. package/dist/client/assets/RightSidebar-BoNzpS2K.js.gz +0 -0
  269. package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.br +0 -0
  270. package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.gz +0 -0
  271. package/dist/client/assets/app-B6FdOF6K.js.br +0 -0
  272. package/dist/client/assets/app-B6FdOF6K.js.gz +0 -0
  273. package/dist/client/assets/arc-_nLI_-XZ.js.br +0 -0
  274. package/dist/client/assets/arc-_nLI_-XZ.js.gz +0 -0
  275. package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.br +0 -0
  276. package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.gz +0 -0
  277. package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.br +0 -0
  278. package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.gz +0 -0
  279. package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.br +0 -0
  280. package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.gz +0 -0
  281. package/dist/client/assets/channel-BYsFmOmF.js +0 -1
  282. package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.br +0 -0
  283. package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.gz +0 -0
  284. package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.br +0 -0
  285. package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.gz +0 -0
  286. package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.br +0 -0
  287. package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
  288. package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.br +0 -0
  289. package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.gz +0 -0
  290. package/dist/client/assets/classDiagram-ZZMXUADV-BnmZv6Ns.js +0 -1
  291. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BnmZv6Ns.js +0 -1
  292. package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.br +0 -0
  293. package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.gz +0 -0
  294. package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.br +0 -0
  295. package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.gz +0 -0
  296. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.br +0 -0
  297. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.gz +0 -0
  298. package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.br +0 -0
  299. package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.gz +0 -0
  300. package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.br +0 -0
  301. package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.gz +0 -0
  302. package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.br +0 -0
  303. package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.gz +0 -0
  304. package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.br +0 -0
  305. package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.gz +0 -0
  306. package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.br +0 -0
  307. package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.gz +0 -0
  308. package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.br +0 -0
  309. package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.gz +0 -0
  310. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.br +0 -0
  311. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.gz +0 -0
  312. package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.br +0 -0
  313. package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
  314. package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.br +0 -0
  315. package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.gz +0 -0
  316. package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.br +0 -0
  317. package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.gz +0 -0
  318. package/dist/client/assets/gestureArbiter-JW8vcVRm.js.br +0 -0
  319. package/dist/client/assets/gestureArbiter-JW8vcVRm.js.gz +0 -0
  320. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.br +0 -0
  321. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.gz +0 -0
  322. package/dist/client/assets/index-CNY7CTVa.css.br +0 -0
  323. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.br +0 -0
  324. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.gz +0 -0
  325. package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.br +0 -0
  326. package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.gz +0 -0
  327. package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.br +0 -0
  328. package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.gz +0 -0
  329. package/dist/client/assets/layout-DqYx0MnO.js.br +0 -0
  330. package/dist/client/assets/layout-DqYx0MnO.js.gz +0 -0
  331. package/dist/client/assets/linear-BnG3XaJW.js.br +0 -0
  332. package/dist/client/assets/linear-BnG3XaJW.js.gz +0 -0
  333. package/dist/client/assets/mermaid.core-Ce00rdM6.js.br +0 -0
  334. package/dist/client/assets/mermaid.core-Ce00rdM6.js.gz +0 -0
  335. package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.br +0 -0
  336. package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.gz +0 -0
  337. package/dist/client/assets/passwordBootstrap-Bncb8qF_.js +0 -2
  338. package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.br +0 -0
  339. package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.gz +0 -0
  340. package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.br +0 -0
  341. package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.gz +0 -0
  342. package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.br +0 -0
  343. package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.gz +0 -0
  344. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.br +0 -0
  345. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
  346. package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.br +0 -0
  347. package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.gz +0 -0
  348. package/dist/client/assets/rdpSession-CobFMSUs.js.br +0 -0
  349. package/dist/client/assets/rdpSession-CobFMSUs.js.gz +0 -0
  350. package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.br +0 -0
  351. package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.gz +0 -0
  352. package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.br +0 -0
  353. package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.gz +0 -0
  354. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.br +0 -0
  355. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.gz +0 -0
  356. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.br +0 -0
  357. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.gz +0 -0
  358. package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.br +0 -0
  359. package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.gz +0 -0
  360. package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.br +0 -0
  361. package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.gz +0 -0
  362. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTcrJ-w0.js +0 -1
  363. package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.br +0 -0
  364. package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.gz +0 -0
  365. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RSMV9AhO.js +0 -8
  366. package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.br +0 -0
  367. package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.gz +0 -0
  368. package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.br +0 -0
  369. package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.gz +0 -0
  370. package/dist/client/assets/videoCompression-ivJAy00d.js.br +0 -0
  371. package/dist/client/assets/videoCompression-ivJAy00d.js.gz +0 -0
  372. package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.br +0 -0
  373. package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.gz +0 -0
  374. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.br +0 -0
  375. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.gz +0 -0
@@ -8,6 +8,26 @@ import { COLLAB_HELP, executeCollaborationCommand, duration } from './collaborat
8
8
  import { getTermdockVersion } from '../utils/version.js';
9
9
  export const INTEGRATION_HELP = `td integration — local scoped integration administration
10
10
  groups | list
11
+ clear-startup-input <TD-session-id> --generation <N>
12
+ Administrator removes this session's saved screen conditions and disables
13
+ inheritance on later restores; original identity/launch/history are preserved.
14
+ Pending messages may then use the ordinary delivery worker; delivered bodies
15
+ are never replayed. No Agent restart, resize, RPC or readiness claim.
16
+ The generation increments once; read session get for the new generation and
17
+ startup_input_disabled=true. Repeating with the old generation is rejected.
18
+ Minimum CLI/running service 1.4.310; session_startup_input_clear=true.
19
+ Errors: SESSION_STARTUP_INPUT_CLEAR_UNSUPPORTED, INVALID_SESSION_GENERATION,
20
+ SESSION_GENERATION_MISMATCH, SESSION_OPERATION_IN_PROGRESS,
21
+ INTEGRATION_SESSION_NOT_FOUND. Profile policy update alone keeps old snapshots.
22
+ resume-configuration <TD-session-id> (read current/recorded fingerprints; no mutation)
23
+ confirm-resume <TD-session-id> --generation <N> --fingerprint <sha256>
24
+ Administrator explicitly authorizes the current launcher/plugin recovery semantics.
25
+ This never proves unknown historical file contents or modifies Agent configuration.
26
+ Old records need this confirmation before release; new records capture a fingerprint.
27
+ Later changes fail closed. Grant session.release separately for runtime reclamation.
28
+ Minimum CLI/running service 1.4.309; capabilities.session_resume_configuration=true.
29
+ Errors: RESUME_CONFIGURATION_CHANGED, EXACT_RESUME_UNSUPPORTED,
30
+ SESSION_GENERATION_MISMATCH, SESSION_OPERATION_IN_PROGRESS, LAUNCHER_UNAVAILABLE.
11
31
  diagnostics <TD-session-id> (read the last restore check; administrator only)
12
32
  create --file <policy.json> --credential-file <private-output.json>
13
33
  update --file <policy.json> (same identity/group; preserves the credential)
@@ -63,6 +83,42 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
63
83
  return 0;
64
84
  }
65
85
  const socketPath = integrationSocketPath(port);
86
+ if (action === 'clear-startup-input') {
87
+ const id = argv[1];
88
+ if (argv.length !== 4 || !id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id) || argv[2] !== '--generation'
89
+ || !/^[1-9][0-9]*$/.test(argv[3]) || !Number.isSafeInteger(Number(argv[3]))) {
90
+ throw new Error('Usage: td integration clear-startup-input <TD-session-id> --generation <positive integer>');
91
+ }
92
+ const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
93
+ if (capabilities.session_startup_input_clear !== true)
94
+ throw new CollaborationError('SESSION_STARTUP_INPUT_CLEAR_UNSUPPORTED', 'CLI and running service must be at least 1.4.310', 409);
95
+ write(JSON.stringify(parsed(await request(socketPath, 'POST', `/admin/sessions/${encodeURIComponent(id)}/startup-input/clear`, { generation: Number(argv[3]) }, headers(undefined, adminToken)))));
96
+ return 0;
97
+ }
98
+ if (action === 'resume-configuration' || action === 'confirm-resume') {
99
+ const id = argv[1];
100
+ if (!id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id))
101
+ throw new Error('A full TD session id is required');
102
+ const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
103
+ if (capabilities.session_resume_configuration !== true)
104
+ throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must be at least 1.4.309', 409);
105
+ let body;
106
+ if (action === 'confirm-resume') {
107
+ const args = {};
108
+ for (let i = 2; i < argv.length; i += 2) {
109
+ if (!['--generation', '--fingerprint'].includes(argv[i]) || !argv[i + 1] || args[argv[i]])
110
+ throw new Error('confirm-resume requires --generation and --fingerprint');
111
+ args[argv[i]] = argv[i + 1];
112
+ }
113
+ if (!/^[1-9][0-9]*$/.test(args['--generation'] ?? '') || !Number.isSafeInteger(Number(args['--generation'])) || !/^[a-f0-9]{64}$/.test(args['--fingerprint'] ?? ''))
114
+ throw new Error('Invalid generation or fingerprint');
115
+ body = { generation: Number(args['--generation']), fingerprint: args['--fingerprint'] };
116
+ }
117
+ else if (argv.length !== 2)
118
+ throw new Error('resume-configuration requires one TD session id');
119
+ write(JSON.stringify(parsed(await request(socketPath, body ? 'POST' : 'GET', `/admin/sessions/${encodeURIComponent(id)}/resume-configuration`, body, headers(undefined, adminToken)))));
120
+ return 0;
121
+ }
66
122
  if (action === 'diagnostics') {
67
123
  if (argv.length !== 2 || !/^[a-zA-Z0-9._-]{1,80}$/.test(argv[1]))
68
124
  throw new Error('diagnostics requires one TD session id');
@@ -161,9 +217,11 @@ export async function runIntegrationCollab(command, port, io) {
161
217
  return subscribe(socketPath, `/events?${params}`, credential, command.options.timeout ? duration(String(command.options.timeout)) : undefined, io.write);
162
218
  }
163
219
  if (command.action === 'session') {
164
- const route = command.operation === 'create' ? '/sessions' : `/sessions/${encodeURIComponent(command.target)}${command.operation === 'restore' ? '/restore' : ''}`;
220
+ if (command.operation === 'release' && (capability.session_runtime_release !== true || capability.session_lifecycle_generation !== true || capability.stable_session_identity !== true))
221
+ throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must support the lifecycle contract (minimum 1.4.309)', 409);
222
+ const route = command.operation === 'create' ? '/sessions' : `/sessions/${encodeURIComponent(command.target)}${['restore', 'release'].includes(command.operation) ? `/${command.operation}` : ''}`;
165
223
  const body = command.operation === 'create' ? { group_id: command.options.group, launch_profile: command.options['launch-profile'], cwd: command.options.cwd, idempotency_key: command.options['idempotency-key'] }
166
- : command.operation === 'restore' ? { idempotency_key: command.options['idempotency-key'] } : undefined;
224
+ : ['restore', 'release'].includes(command.operation) ? { ...(command.operation === 'release' ? { generation: Number(command.options.generation) } : {}), idempotency_key: command.options['idempotency-key'] } : undefined;
167
225
  io.write(JSON.stringify(parsed(await request(socketPath, command.operation === 'get' ? 'GET' : 'POST', route, body, headers(credential)))));
168
226
  return 0;
169
227
  }
@@ -34,6 +34,7 @@ export class IntegrationRuntime {
34
34
  this.options = options;
35
35
  this.store = new IntegrationStore(path.join(options.directory, 'integrations.json'));
36
36
  this.sessions = new IntegrationSessions(path.join(options.directory, 'integration-sessions.json'), options.sessions);
37
+ this.stops.push(options.messages.registerRuntimeGuard(id => this.sessions.assertRuntimeMessage(id)));
37
38
  const changed = () => { try {
38
39
  this.sync();
39
40
  }
@@ -70,6 +71,14 @@ export class IntegrationRuntime {
70
71
  app.get('/admin/groups', run(req => { admin(req); return { groups: options.messages.list().filter(g => !g.federated).map(g => ({ id: g.id, name: g.name })) }; }));
71
72
  app.get('/admin/capabilities', run(req => { admin(req); return this.capabilities(); }));
72
73
  app.get('/admin/sessions/:id/restore-diagnostics', run(req => { admin(req); return this.sessions.restoreDiagnostics(req.params.id); }));
74
+ app.get('/admin/sessions/:id/resume-configuration', run(async (req) => { admin(req); return this.sessions.resumeConfiguration(req.params.id); }));
75
+ app.post('/admin/sessions/:id/resume-configuration', run(async (req) => { admin(req); return { session: publicIntegrationSession(await this.sessions.confirmResumeConfiguration(req.params.id, req.body?.generation, req.body?.fingerprint)) }; }));
76
+ app.post('/admin/sessions/:id/startup-input/clear', run(async (req) => {
77
+ admin(req);
78
+ const session = await this.sessions.clearStartupInput(req.params.id, req.body?.generation);
79
+ this.sync();
80
+ return { session: publicIntegrationSession(session) };
81
+ }));
73
82
  app.get('/admin/principals', run(req => { admin(req); return { integrations: this.store.policies() }; }));
74
83
  app.post('/admin/principals', run(req => { admin(req); this.group(req.body?.groupId); const provisioned = this.store.provision(req.body); try {
75
84
  this.sync();
@@ -120,7 +129,13 @@ export class IntegrationRuntime {
120
129
  const targets = Array.isArray(raw.toSessionIds) ? raw.toSessionIds : [raw.targetSessionId];
121
130
  if (!targets.length || targets.some((id) => typeof id !== 'string'))
122
131
  integrationError('INVALID_MESSAGE_TARGET', 'Specify a local group member');
123
- const recipients = targets.map((id) => scopedId(id, group.sessionIds, 'SESSION'));
132
+ const descriptors = this.sessions.snapshot().filter(s => s.group_id === p.groupId);
133
+ const recipients = targets.map((id) => scopedId(id, [...group.sessionIds, ...descriptors.map(s => s.session_id)], 'SESSION'));
134
+ for (const id of recipients) {
135
+ this.sessions.assertMessageTarget(p, id);
136
+ if (!group.sessionIds.includes(id))
137
+ integrationError('SESSION_NOT_FOUND', 'Restore the original runtime and membership before sending', 404);
138
+ }
124
139
  let thread;
125
140
  if (raw.thread_id)
126
141
  thread = scopedId(raw.thread_id, options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).map(m => m.threadId), 'THREAD');
@@ -135,6 +150,8 @@ export class IntegrationRuntime {
135
150
  const raw = req.body ?? {}, original = this.message(p, raw.messageId);
136
151
  const target = original.toPrincipalId === p.id ? original.fromSessionId
137
152
  : original.integrationOrigin?.integrationId === p.id ? original.toSessionId : null;
153
+ if (target)
154
+ this.sessions.assertMessageTarget(p, target);
138
155
  if (!target || !this.group(p.groupId).sessionIds.includes(target))
139
156
  integrationError('NO_REPLY_TARGET', 'Reply requires your own message and its original live group member', 409);
140
157
  const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
@@ -180,6 +197,7 @@ export class IntegrationRuntime {
180
197
  }));
181
198
  app.get('/sessions/:id', run(async (req) => { const p = principal(req); this.authorize(p, 'session.read'); return { session: publicIntegrationSession(await this.sessions.get(p, req.params.id)) }; }));
182
199
  app.post('/sessions/:id/restore', run(async (req) => { const p = principal(req); this.authorize(p, 'session.restore'); const session = await this.sessions.restore(p, req.params.id, req.body?.idempotency_key); this.sync(); return { session: publicIntegrationSession(session) }; }));
200
+ app.post('/sessions/:id/release', run(async (req) => { const p = principal(req); this.authorize(p, 'session.release'); const session = await this.sessions.release(p, req.params.id, req.body?.generation, req.body?.idempotency_key); this.sync(); return { session: publicIntegrationSession(session) }; }));
183
201
  app.get('/events', run((req, res) => {
184
202
  const p = principal(req);
185
203
  this.authorize(p, 'events.read', req.query.group);
@@ -203,7 +221,9 @@ export class IntegrationRuntime {
203
221
  integration_principal: true, group_scoped_permissions: true, durable_event_push: true,
204
222
  event_ack_semantics: 'durable_received', correlated_task_responses: true, accepted_task_comments: true,
205
223
  idempotent_session_create: true, exact_session_restore: true, external_validation: false,
206
- startup_input_conditions: true,
224
+ startup_input_conditions: true, session_runtime_release: true, stable_session_identity: true, session_lifecycle_generation: true,
225
+ session_resume_configuration: true, session_lifecycle_min_version: '1.4.309',
226
+ session_startup_input_clear: true, session_startup_input_clear_min_version: '1.4.310',
207
227
  integration_policy_update: true,
208
228
  session_restore_diagnostics: true,
209
229
  native_identity_linux_flock_owner: process.platform === 'linux',
@@ -2,6 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomBytes, randomUUID } from 'node:crypto';
4
4
  import { atomicJson, integrationError } from './integrationStore.js';
5
+ import { SessionLifecycleCoordinator, assertMessageRuntime, assertReleaseFacts, assertReleaseGeneration } from './sessionLifecycle.js';
6
+ import { resumeConfigurationFingerprint } from './sessionResumeConfiguration.js';
5
7
  import { NativeResumeOwnerError } from './nativeResumeOwner.js';
6
8
  /** Quote each configured argument as literal shell data, including paths. */
7
9
  export function integrationLaunchCommand(record, restore) {
@@ -12,6 +14,8 @@ export function integrationLaunchCommand(record, restore) {
12
14
  return `cd -- ${quote(record.cwd)} && ${[record.profile.executable, ...args].map(quote).join(' ')}`;
13
15
  }
14
16
  function startupInput(record, now) {
17
+ if (record.startup_input_disabled)
18
+ return undefined;
15
19
  const condition = record.startup_condition ?? record.profile.startupInput;
16
20
  return condition ? { state: 'pending', deadline: now + (condition.timeoutMs ?? 120000), matched_since: null, observed_at: null } : undefined;
17
21
  }
@@ -50,7 +54,8 @@ export class IntegrationSessions {
50
54
  doc = { version: 1, sessions: [], requests: {} };
51
55
  pending = new Map();
52
56
  listeners = new Set();
53
- restoring = new Set();
57
+ lifecycle = new SessionLifecycleCoordinator();
58
+ mutations = new Map();
54
59
  timers = new Set();
55
60
  stopObservation;
56
61
  stopDeliveryGuard;
@@ -70,8 +75,12 @@ export class IntegrationSessions {
70
75
  if (error.code !== 'ENOENT')
71
76
  throw error;
72
77
  }
78
+ for (const record of this.doc.sessions) {
79
+ record.generation ??= 1;
80
+ record.runtime_present ??= null;
81
+ }
73
82
  for (const record of this.doc.sessions)
74
- if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.startup_input?.state === 'pending')
83
+ if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.state === 'releasing' || record.startup_input?.state === 'pending')
75
84
  this.scheduleRefresh(record);
76
85
  // A pre-restart partial match cannot prove continuity of the live screen.
77
86
  for (const record of this.doc.sessions)
@@ -89,12 +98,17 @@ export class IntegrationSessions {
89
98
  }
90
99
  subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
91
100
  snapshot() { return structuredClone(this.doc.sessions); }
92
- save() { atomicJson(this.file, this.doc); for (const listener of this.listeners) {
93
- try {
94
- listener();
101
+ save() {
102
+ if (Buffer.byteLength(JSON.stringify(this.doc)) > 64 * 1024 * 1024)
103
+ integrationError('SESSION_STORAGE_FULL', 'Recovery descriptor journal is full; no new operation was committed', 503);
104
+ atomicJson(this.file, this.doc);
105
+ for (const listener of this.listeners) {
106
+ try {
107
+ listener();
108
+ }
109
+ catch { /* Durable session state is reconciled on restart. */ }
95
110
  }
96
- catch { /* Durable session state is reconciled on restart. */ }
97
- } }
111
+ }
98
112
  saveRecord(record) {
99
113
  const before = this.doc.sessions;
100
114
  this.doc.sessions = [...before.filter(s => s.session_id !== record.session_id), structuredClone(record)];
@@ -139,7 +153,7 @@ export class IntegrationSessions {
139
153
  integrationError('INTEGRATION_SESSION_LIMIT', 'Integration session storage is full', 409);
140
154
  const now = Date.now(), record = { operation_id: randomUUID(), session_id: randomBytes(6).toString('hex'),
141
155
  group_id: principal.groupId, principal_id: principal.id, agent_slug: profile.agentSlug, agent_native_session_id: null,
142
- state: 'starting', error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
156
+ state: 'starting', generation: 1, runtime_present: null, error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
143
157
  created_at: now, updated_at: now, deadline: now + this.bindingTimeout, launch_submitted: false };
144
158
  record.startup_input = startupInput(record, now);
145
159
  this.doc.requests[request.scoped] = { hash: request.hash, sessionId: record.session_id };
@@ -156,6 +170,21 @@ export class IntegrationSessions {
156
170
  const promise = (async () => {
157
171
  try {
158
172
  // Store intent before launching. A failed persistence cannot execute.
173
+ if (this.adapter.resumeConfiguration) {
174
+ try {
175
+ const fingerprint = await resumeConfigurationFingerprint(record, this.adapter);
176
+ if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== fingerprint)
177
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
178
+ if (!restoring)
179
+ record.resume_configuration_fingerprint = fingerprint;
180
+ }
181
+ catch (error) {
182
+ // Existing creation supports plugins without an exact resume path.
183
+ // They remain launchable, but cannot gain a releasable descriptor.
184
+ if (restoring || error.code !== 'EXACT_RESUME_UNSUPPORTED')
185
+ throw error;
186
+ }
187
+ }
159
188
  record.launch_submitted = true;
160
189
  this.saveRecord(record);
161
190
  const prepared = (binding) => { record.terminal_binding = binding; this.saveRecord(record); };
@@ -187,27 +216,74 @@ export class IntegrationSessions {
187
216
  return structuredClone({ session_id: record.session_id, operation_id: record.operation_id,
188
217
  state: record.state, error_code: record.error_code, diagnostics: record.restore_diagnostics ?? null });
189
218
  }
190
- async restore(principal, id, idempotencyKey) {
219
+ assertRuntimeMessage(id) { assertMessageRuntime(this.doc.sessions.find(s => s.session_id === id)); }
220
+ assertMessageTarget(principal, id) {
221
+ const record = this.doc.sessions.find(s => s.session_id === id && s.group_id === principal.groupId);
222
+ assertMessageRuntime(record);
223
+ }
224
+ async mutate(principal, key, input, action) {
225
+ const request = this.request(principal, key, input);
226
+ if (request.previous?.result) {
227
+ this.lookup(principal, request.previous.sessionId);
228
+ return structuredClone(request.previous.result);
229
+ }
230
+ const active = this.mutations.get(request.scoped);
231
+ if (active) {
232
+ if (active.hash !== request.hash)
233
+ integrationError('IDEMPOTENCY_CONFLICT', 'Operation key parameters changed', 409);
234
+ return active.promise;
235
+ }
236
+ const promise = action().then(result => {
237
+ if (this.doc.requests[request.scoped]) {
238
+ const before = this.doc.requests[request.scoped];
239
+ this.doc.requests[request.scoped] = { ...before, result: structuredClone(result) };
240
+ try {
241
+ this.save();
242
+ }
243
+ catch (error) {
244
+ this.doc.requests[request.scoped] = before;
245
+ throw error;
246
+ }
247
+ }
248
+ return result;
249
+ }).finally(() => this.mutations.delete(request.scoped));
250
+ this.mutations.set(request.scoped, { hash: request.hash, promise });
251
+ return promise;
252
+ }
253
+ async replayRequest(principal, id, request) {
254
+ const record = await (this.pending.get(id) ?? this.get(principal, id));
255
+ if (request.operationId && (record.operation_id !== request.operationId || record.generation !== request.generation))
256
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Original operation result was not committed and a later generation exists; read session get, do not reuse this key', 409);
257
+ return record;
258
+ }
259
+ async restore(principal, id, key) {
260
+ return this.mutate(principal, key, ['restore', id], () => this.lifecycle.run(id, () => this.restoreOnce(principal, id, key)));
261
+ }
262
+ async restoreOnce(principal, id, idempotencyKey) {
191
263
  const request = this.request(principal, idempotencyKey, ['restore', id]);
192
264
  if (request.previous)
193
- return this.pending.get(id) ?? this.get(principal, id);
194
- if (this.pending.has(id) || this.restoring.has(id))
265
+ return this.replayRequest(principal, id, request.previous);
266
+ if (this.pending.has(id))
195
267
  integrationError('SESSION_OPERATION_IN_PROGRESS', 'Another session operation is in progress', 409);
196
- this.restoring.add(id);
268
+ let releaseNative;
197
269
  try {
198
270
  const record = await this.get(principal, id);
199
271
  if (!record.agent_native_session_id)
200
272
  integrationError('NATIVE_SESSION_ID_MISSING', 'An exact native session binding is required', 409);
201
273
  // Recheck the current grant and actual directories; restore uses the
202
274
  // original profile snapshot, never changed model or wrapper defaults.
203
- if (!principal.launchProfiles.some(p => p.id === record.launch_profile))
275
+ const authorizedProfile = principal.launchProfiles.find(p => p.id === record.launch_profile);
276
+ if (!authorizedProfile)
204
277
  integrationError('LAUNCH_PROFILE_DENIED', 'Original launch profile is no longer authorized', 403);
278
+ permittedCwd(authorizedProfile, record.cwd);
205
279
  permittedCwd(record.profile, record.cwd);
280
+ if (record.operation_uncertain || record.state === 'releasing')
281
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile prior release using session get before restore', 409);
206
282
  const observed = await this.adapter.inspect(record);
207
283
  if (observed.running) {
208
284
  if (observed.agentSlug !== record.agent_slug || observed.nativeId !== record.agent_native_session_id)
209
285
  integrationError('SESSION_IDENTITY_MISMATCH', 'Existing process does not prove the exact native binding', 409);
210
- this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
286
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
211
287
  try {
212
288
  this.save();
213
289
  }
@@ -221,16 +297,23 @@ export class IntegrationSessions {
221
297
  integrationError('SESSION_OPERATION_IN_PROGRESS', 'Prior launch outcome is not yet resolved; inspect the original session', 409);
222
298
  if (observed.exists && !observed.shell)
223
299
  integrationError('SESSION_TARGET_NOT_SHELL', 'Original pane is not a verified shell', 409);
300
+ if (record.state === 'released' && !record.resume_configuration_fingerprint)
301
+ integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must explicitly confirm the current recovery configuration', 409);
302
+ releaseNative = this.lifecycle.reserveNative(record, this.doc.sessions);
303
+ if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
304
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
305
+ record.generation = (record.generation ?? 1) + 1;
306
+ record.runtime_present = observed.exists;
224
307
  record.state = 'restoring';
225
308
  record.error_code = null;
226
309
  record.operation_id = randomUUID();
227
310
  record.deadline = Date.now() + this.bindingTimeout;
228
311
  record.updated_at = Date.now();
229
312
  record.restore_diagnostics = undefined;
230
- if (!record.profile.startupInput && !record.startup_condition)
313
+ if (!record.startup_input_disabled && !record.profile.startupInput && !record.startup_condition)
231
314
  record.startup_condition = structuredClone(principal.launchProfiles.find(profile => profile.id === record.launch_profile)?.startupInput);
232
315
  record.startup_input = startupInput(record, Date.now());
233
- this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
316
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
234
317
  try {
235
318
  this.saveRecord(record);
236
319
  }
@@ -241,13 +324,156 @@ export class IntegrationSessions {
241
324
  return await this.launch(record, true);
242
325
  }
243
326
  finally {
244
- this.restoring.delete(id);
327
+ releaseNative?.();
245
328
  }
246
329
  }
330
+ async resumeConfiguration(id) {
331
+ const record = this.doc.sessions.find(s => s.session_id === id);
332
+ if (!record)
333
+ return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
334
+ return { session_id: id, generation: record.generation ?? 1, recorded_fingerprint: record.resume_configuration_fingerprint ?? null,
335
+ current_fingerprint: await resumeConfigurationFingerprint(record, this.adapter), confirmation_required: !record.resume_configuration_fingerprint };
336
+ }
337
+ async clearStartupInput(id, generation) {
338
+ return this.lifecycle.run(id, async () => {
339
+ const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
340
+ if (!record)
341
+ return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
342
+ assertReleaseGeneration(record, generation);
343
+ if (this.pending.has(id) || record.operation_uncertain || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state)) {
344
+ integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current lifecycle operation before clearing startup input conditions', 409);
345
+ }
346
+ if (record.startup_input_disabled && !record.profile.startupInput && !record.startup_condition && !record.startup_input)
347
+ return record;
348
+ delete record.profile.startupInput;
349
+ delete record.startup_condition;
350
+ delete record.startup_input;
351
+ record.startup_input_disabled = true;
352
+ record.generation = generation + 1;
353
+ record.updated_at = Date.now();
354
+ this.saveRecord(record);
355
+ return record;
356
+ });
357
+ }
358
+ async confirmResumeConfiguration(id, generation, fingerprint) {
359
+ return this.lifecycle.run(id, async () => {
360
+ const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
361
+ if (!record)
362
+ return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
363
+ assertReleaseGeneration(record, generation);
364
+ if (this.pending.has(id) || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
365
+ integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current operation before confirming compatibility', 409);
366
+ if (!/^[a-f0-9]{64}$/.test(fingerprint) || fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
367
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Current fingerprint no longer matches the administrator confirmation', 409);
368
+ record.resume_configuration_fingerprint = fingerprint;
369
+ record.generation = generation + 1;
370
+ record.updated_at = Date.now();
371
+ this.saveRecord(record);
372
+ return record;
373
+ });
374
+ }
375
+ async release(principal, id, generation, key) {
376
+ return this.mutate(principal, key, ['release', id, generation], () => this.lifecycle.run(id, async () => {
377
+ const request = this.request(principal, key, ['release', id, generation]);
378
+ if (request.previous)
379
+ return this.replayRequest(principal, id, request.previous);
380
+ if (!this.adapter.release || !this.adapter.runtimePresent)
381
+ integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime adapter does not support release', 409);
382
+ if (this.pending.has(id))
383
+ integrationError('SESSION_OPERATION_IN_PROGRESS', 'Launch is in progress', 409);
384
+ const record = await this.get(principal, id);
385
+ assertReleaseGeneration(record, generation);
386
+ if (record.state !== 'released') {
387
+ assertReleaseFacts(record, await this.adapter.inspect(record));
388
+ if (this.adapter.pendingMessages?.(id))
389
+ integrationError('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before release', 409);
390
+ if (!record.resume_configuration_fingerprint)
391
+ integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must inspect and confirm the recovery configuration before release', 409);
392
+ if (record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
393
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
394
+ }
395
+ if (record.state === 'released') {
396
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
397
+ try {
398
+ this.save();
399
+ }
400
+ catch (error) {
401
+ delete this.doc.requests[request.scoped];
402
+ throw error;
403
+ }
404
+ return record;
405
+ }
406
+ if (this.adapter.prepareRelease)
407
+ record.group_role = (await this.adapter.prepareRelease(record)).group_role;
408
+ record.state = 'releasing';
409
+ record.operation_id = randomUUID();
410
+ record.generation = generation + 1;
411
+ record.operation_uncertain = false;
412
+ record.error_code = null;
413
+ record.updated_at = Date.now();
414
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
415
+ try {
416
+ this.saveRecord(record);
417
+ }
418
+ catch (error) {
419
+ delete this.doc.requests[request.scoped];
420
+ throw error;
421
+ }
422
+ try {
423
+ await this.adapter.release(record, false);
424
+ if (await this.adapter.runtimePresent(record))
425
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime removal could not be confirmed', 409);
426
+ record.state = 'released';
427
+ record.runtime_present = false;
428
+ record.terminal_binding = null;
429
+ record.startup_input = undefined;
430
+ }
431
+ catch (error) {
432
+ record.state = 'failed';
433
+ record.operation_uncertain = true;
434
+ record.runtime_present = null;
435
+ record.error_code = typeof error.code === 'string' ? error.code : 'SESSION_RELEASE_FAILED';
436
+ }
437
+ record.updated_at = Date.now();
438
+ this.saveRecord(record);
439
+ return record;
440
+ }));
441
+ }
247
442
  async refresh(id) {
248
443
  const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
249
444
  if (!record)
250
445
  return;
446
+ if (record.state === 'released' || record.state === 'releasing' || record.operation_uncertain) {
447
+ if (this.lifecycle.active(id))
448
+ return;
449
+ try {
450
+ if (record.state !== 'released')
451
+ await this.adapter.release?.(record, true);
452
+ const present = await this.adapter.runtimePresent?.(record);
453
+ if (present === undefined)
454
+ integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime presence is unavailable', 409);
455
+ if (record.state !== 'released') {
456
+ if (present)
457
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Prior release still has a runtime; explicit release retry is required', 409);
458
+ // Absent runtime metadata has been reconciled without killing a live runtime.
459
+ }
460
+ else if (present)
461
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'A runtime exists for a released descriptor', 409);
462
+ const next = { ...record, state: 'released', runtime_present: false, terminal_binding: null, startup_input: undefined, operation_uncertain: false, error_code: null };
463
+ if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
464
+ return;
465
+ if (JSON.stringify(next) !== JSON.stringify(record))
466
+ this.saveRecord({ ...next, updated_at: Date.now() });
467
+ }
468
+ catch (error) {
469
+ if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
470
+ return;
471
+ const next = { ...record, state: 'failed', runtime_present: null, operation_uncertain: true, error_code: typeof error.code === 'string' ? error.code : 'OPERATION_OUTCOME_UNCONFIRMED' };
472
+ if (JSON.stringify(next) !== JSON.stringify(record))
473
+ this.saveRecord({ ...next, updated_at: Date.now() });
474
+ }
475
+ return;
476
+ }
251
477
  let observed;
252
478
  try {
253
479
  observed = await this.adapter.inspect(record);
@@ -261,7 +487,7 @@ export class IntegrationSessions {
261
487
  this.saveRecord({ ...record, state: 'failed', error_code: typeof code === 'string' ? code : 'SESSION_OBSERVATION_FAILED', updated_at: Date.now() });
262
488
  return;
263
489
  }
264
- const next = { ...record };
490
+ const next = { ...record, runtime_present: this.adapter.runtimePresent ? await this.adapter.runtimePresent(record) : observed.exists };
265
491
  if (observed.nativeId && (typeof observed.nativeId !== 'string' || observed.nativeId.length > 256 || /[\x00-\x1f\x7f]/.test(observed.nativeId))) {
266
492
  next.state = 'failed';
267
493
  next.error_code = 'NATIVE_SESSION_ID_INVALID';
@@ -325,7 +551,12 @@ export class IntegrationSessions {
325
551
  }
326
552
  /** Only gates configured launches. Native UUID and Agent turn state are not input prerequisites. */
327
553
  async deliveryReadiness(id) {
328
- if (!this.doc.sessions.find(record => record.session_id === id)?.startup_input)
554
+ const existing = this.doc.sessions.find(record => record.session_id === id);
555
+ if (!existing)
556
+ return null;
557
+ if (existing.state === 'released' || existing.state === 'releasing' || existing.operation_uncertain)
558
+ return { allowed: false, reason: existing.state === 'released' ? 'SESSION_RELEASED' : 'SESSION_OPERATION_IN_PROGRESS' };
559
+ if (!existing.startup_input)
329
560
  return null;
330
561
  await this.refresh(id);
331
562
  const record = this.doc.sessions.find(record => record.session_id === id);
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
4
4
  import { CollaborationError } from './collaborationProtocol.js';
5
5
  export const INTEGRATION_PROTOCOL = 1;
6
- export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore'];
6
+ export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore', 'session.release'];
7
7
  export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
8
8
  export function atomicJson(file, value) {
9
9
  fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
@@ -0,0 +1,81 @@
1
+ import { CollaborationError } from './collaborationProtocol.js';
2
+ const fail = (code, message) => { throw new CollaborationError(code, message, 409); };
3
+ /** One coordinator per service. A reservation covers validation and launch;
4
+ * persisted unresolved intents also block a second identity after restart.
5
+ * This does not promise atomic exclusion of independently launched programs. */
6
+ export class SessionLifecycleCoordinator {
7
+ operations = new Set();
8
+ natives = new Map();
9
+ async run(id, action) {
10
+ if (this.operations.has(id))
11
+ fail('SESSION_OPERATION_IN_PROGRESS', 'Another lifecycle operation is in progress');
12
+ this.operations.add(id);
13
+ try {
14
+ return await action();
15
+ }
16
+ finally {
17
+ this.operations.delete(id);
18
+ }
19
+ }
20
+ active(id) { return this.operations.has(id); }
21
+ reserveNative(record, descriptors) {
22
+ const key = JSON.stringify([record.agent_slug, record.agent_native_session_id]);
23
+ const owner = this.natives.get(key);
24
+ if (owner && owner !== record.session_id || descriptors.some(other => other.session_id !== record.session_id
25
+ && other.agent_slug === record.agent_slug && other.agent_native_session_id === record.agent_native_session_id
26
+ && (['starting', 'restoring', 'binding_pending'].includes(other.state) || other.operation_uncertain))) {
27
+ fail('SESSION_NATIVE_OPERATION_IN_PROGRESS', 'Another descriptor has an unresolved operation for this native identity');
28
+ }
29
+ this.natives.set(key, record.session_id);
30
+ return () => { if (this.natives.get(key) === record.session_id)
31
+ this.natives.delete(key); };
32
+ }
33
+ }
34
+ export function assertReleaseGeneration(record, generation) {
35
+ if (!Number.isSafeInteger(generation) || Number(generation) < 1)
36
+ fail('INVALID_SESSION_GENERATION', 'Operation requires the generation returned by session get');
37
+ if (generation !== (record.generation ?? 1))
38
+ fail('SESSION_GENERATION_MISMATCH', 'Session generation changed; read its current state before retrying');
39
+ }
40
+ export function assertReleaseFacts(record, facts) {
41
+ if (!record.agent_native_session_id)
42
+ fail('NATIVE_SESSION_ID_MISSING', 'Release requires a durable exact native identity');
43
+ if (['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
44
+ fail('SESSION_OPERATION_IN_PROGRESS', 'Prior operation is unresolved');
45
+ if (facts.running)
46
+ fail('SESSION_STILL_RUNNING', 'Agent is still running; explicitly exit it before releasing the runtime');
47
+ if (facts.exists && !facts.shell)
48
+ fail('SESSION_IDENTITY_MISMATCH', 'Runtime is not the verified original empty shell');
49
+ }
50
+ export function assertMessageRuntime(record) {
51
+ if (!record)
52
+ return;
53
+ if (record.state === 'released')
54
+ fail('SESSION_RELEASED', 'Runtime was released; explicitly restore this session before sending');
55
+ if (record.state === 'releasing')
56
+ fail('SESSION_OPERATION_IN_PROGRESS', 'Runtime release is in progress; message was not enqueued');
57
+ if (record.operation_uncertain)
58
+ fail('OPERATION_OUTCOME_UNCONFIRMED', 'Lifecycle outcome needs reconciliation before sending');
59
+ }
60
+ /** Shared release protocol. No Agent idle inference, task result, or UI
61
+ * visibility participates in deciding whether a process may be destroyed. */
62
+ export async function releaseSessionRuntime(adapter, reconcile) {
63
+ await adapter.barrier(async () => {
64
+ if (adapter.pendingMessages())
65
+ fail('SESSION_PENDING_MESSAGES', 'Pending messages appeared before runtime release');
66
+ if (await adapter.exists()) {
67
+ if (reconcile)
68
+ fail('OPERATION_OUTCOME_UNCONFIRMED', 'Original runtime still exists; restart reconciliation will not terminate it');
69
+ await adapter.verifyOwnership();
70
+ const facts = await adapter.inspect();
71
+ if (facts.running)
72
+ fail('SESSION_STILL_RUNNING', 'Agent restarted before release');
73
+ if (!facts.exists || !facts.shell)
74
+ fail('SESSION_IDENTITY_MISMATCH', 'Original runtime is not a verified foreground shell');
75
+ await adapter.destroy();
76
+ }
77
+ if (await adapter.exists())
78
+ fail('OPERATION_OUTCOME_UNCONFIRMED', 'Owned runtime still exists after release');
79
+ await adapter.forgetRuntime();
80
+ });
81
+ }