termdock 1.4.307 → 1.4.309

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 (352) hide show
  1. package/dist/client/assets/{App-cagV-flM.js → App-ggIA_xqR.js} +6 -6
  2. package/dist/client/assets/App-ggIA_xqR.js.br +0 -0
  3. package/dist/client/assets/App-ggIA_xqR.js.gz +0 -0
  4. package/dist/client/assets/{ArchitecturePanel-TdKPja7L.js → ArchitecturePanel-CoBspW_C.js} +1 -1
  5. package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.br +0 -0
  6. package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.gz +0 -0
  7. package/dist/client/assets/{ChangeWalkthroughPanel-DOj40Srm.js → ChangeWalkthroughPanel-D1Puc7B3.js} +1 -1
  8. package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.br +0 -0
  9. package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.gz +0 -0
  10. package/dist/client/assets/{CollaborationMainWorkspace-C5Tcos5l.js → CollaborationMainWorkspace-BPyK5Xs8.js} +1 -1
  11. package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.br +0 -0
  12. package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.gz +0 -0
  13. package/dist/client/assets/CollaborationTaskContent-CTSJJqze.js +2 -0
  14. package/dist/client/assets/{ComputerControlView-C2sOFyuA.js → ComputerControlView-D5Lw9sNA.js} +2 -2
  15. package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.br +0 -0
  16. package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.gz +0 -0
  17. package/dist/client/assets/{DagPlayground-DFfVJW00.js → DagPlayground-DYJrGfiG.js} +1 -1
  18. package/dist/client/assets/DagPlayground-DYJrGfiG.js.br +0 -0
  19. package/dist/client/assets/DagPlayground-DYJrGfiG.js.gz +0 -0
  20. package/dist/client/assets/{DiffLab-hoj8gwUi.js → DiffLab-5zdcFW3z.js} +1 -1
  21. package/dist/client/assets/DiffLab-5zdcFW3z.js.br +0 -0
  22. package/dist/client/assets/DiffLab-5zdcFW3z.js.gz +0 -0
  23. package/dist/client/assets/{DiffReview-Cn_PmN-9.js → DiffReview-BBBC_NjV.js} +1 -1
  24. package/dist/client/assets/DiffReview-BBBC_NjV.js.br +0 -0
  25. package/dist/client/assets/DiffReview-BBBC_NjV.js.gz +0 -0
  26. package/dist/client/assets/{DiffReviewLab-DycgD40E.js → DiffReviewLab-CbhosFSB.js} +1 -1
  27. package/dist/client/assets/DiffReviewLab-CbhosFSB.js.br +0 -0
  28. package/dist/client/assets/DiffReviewLab-CbhosFSB.js.gz +0 -0
  29. package/dist/client/assets/{DiffViewer-DRnuph7U.js → DiffViewer-BuQwFQ4V.js} +1 -1
  30. package/dist/client/assets/DiffViewer-BuQwFQ4V.js.br +0 -0
  31. package/dist/client/assets/DiffViewer-BuQwFQ4V.js.gz +0 -0
  32. package/dist/client/assets/{ModelPreview-Dvw-UtQQ.js → ModelPreview-nO2VmqdP.js} +1 -1
  33. package/dist/client/assets/ModelPreview-nO2VmqdP.js.br +0 -0
  34. package/dist/client/assets/ModelPreview-nO2VmqdP.js.gz +0 -0
  35. package/dist/client/assets/{RightSidebar-CnnU7Ffp.js → RightSidebar-BoNzpS2K.js} +3 -3
  36. package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
  37. package/dist/client/assets/{RightSidebar-CnnU7Ffp.js.gz → RightSidebar-BoNzpS2K.js.gz} +0 -0
  38. package/dist/client/assets/{abnfDiagram-VCTEODGH-BQVIT5gD.js → abnfDiagram-VCTEODGH-yi0K8GDG.js} +1 -1
  39. package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.br +0 -0
  40. package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.gz +0 -0
  41. package/dist/client/assets/{app-CXvK8WZg.js → app-B6FdOF6K.js} +3 -3
  42. package/dist/client/assets/app-B6FdOF6K.js.br +0 -0
  43. package/dist/client/assets/app-B6FdOF6K.js.gz +0 -0
  44. package/dist/client/assets/{arc-DZG1NzUc.js → arc-_nLI_-XZ.js} +1 -1
  45. package/dist/client/assets/arc-_nLI_-XZ.js.br +0 -0
  46. package/dist/client/assets/arc-_nLI_-XZ.js.gz +0 -0
  47. package/dist/client/assets/{architectureDiagram-5GKGNRK7-Bv15lJGE.js → architectureDiagram-5GKGNRK7-CikrA78O.js} +1 -1
  48. package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.br +0 -0
  49. package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.gz +0 -0
  50. package/dist/client/assets/{blockDiagram-I7D4REHJ-DShw-lH7.js → blockDiagram-I7D4REHJ-CWJq6qJ7.js} +1 -1
  51. package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.br +0 -0
  52. package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.gz +0 -0
  53. package/dist/client/assets/{c4Diagram-7LVT6UL2-BN6SsyOM.js → c4Diagram-7LVT6UL2-gzPzLGvs.js} +1 -1
  54. package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.br +0 -0
  55. package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.gz +0 -0
  56. package/dist/client/assets/channel-BYsFmOmF.js +1 -0
  57. package/dist/client/assets/{chunk-2Q5K7J3B-BkBkNjv0.js → chunk-2Q5K7J3B-CfX9l18E.js} +1 -1
  58. package/dist/client/assets/{chunk-5VM5RSS4-LrpNvv_o.js → chunk-5VM5RSS4-JSV1yrXM.js} +1 -1
  59. package/dist/client/assets/{chunk-F27PBJKO-BcpMMU4P.js → chunk-F27PBJKO-CQ0LXeap.js} +1 -1
  60. package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.br +0 -0
  61. package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.gz +0 -0
  62. package/dist/client/assets/{chunk-IMKFNOWR-cWvd5gJk.js → chunk-IMKFNOWR-CJOEPkK9.js} +1 -1
  63. package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.br +0 -0
  64. package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.gz +0 -0
  65. package/dist/client/assets/{chunk-JWPE2WC7-Bf4ORht5.js → chunk-JWPE2WC7-Dpp7Xu_E.js} +1 -1
  66. package/dist/client/assets/{chunk-POPQ4Y6H-M5MSHOUM.js → chunk-POPQ4Y6H-9hDa1b8z.js} +1 -1
  67. package/dist/client/assets/{chunk-SVP7TREG-Bj8gUFc2.js → chunk-SVP7TREG-BskkG9gg.js} +1 -1
  68. package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.br +0 -0
  69. package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
  70. package/dist/client/assets/{chunk-TICWLB2K-W1cGkC4s.js → chunk-TICWLB2K-DyzuP-9I.js} +1 -1
  71. package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.br +0 -0
  72. package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.gz +0 -0
  73. package/dist/client/assets/{chunk-XXDRQBXY-mXLNbLH8.js → chunk-XXDRQBXY-C31_1i6z.js} +1 -1
  74. package/dist/client/assets/classDiagram-ZZMXUADV-BnmZv6Ns.js +1 -0
  75. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BnmZv6Ns.js +1 -0
  76. package/dist/client/assets/{cose-bilkent-JH36ORCC-CK0EaMQY.js → cose-bilkent-JH36ORCC-Bm-ESDOc.js} +1 -1
  77. package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.br +0 -0
  78. package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.gz +0 -0
  79. package/dist/client/assets/{cynefin-OW5HDTMX-C-7tsMpt.js → cynefin-OW5HDTMX-DbFWAPvX.js} +1 -1
  80. package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.br +0 -0
  81. package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.gz +0 -0
  82. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CijqM5Xa.js → cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js} +1 -1
  83. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.br +0 -0
  84. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.gz +0 -0
  85. package/dist/client/assets/{dagre-GXQ25YYZ-DCLINHGD.js → dagre-GXQ25YYZ-D_5uEMjJ.js} +1 -1
  86. package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.br +0 -0
  87. package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.gz +0 -0
  88. package/dist/client/assets/{diagram-S7CK7UJ4-D-JvTWtL.js → diagram-S7CK7UJ4-DWVpndRP.js} +1 -1
  89. package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.br +0 -0
  90. package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.gz +0 -0
  91. package/dist/client/assets/{diagram-UQ7AKVKN-BT01Oee8.js → diagram-UQ7AKVKN-DmYTya3w.js} +1 -1
  92. package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.br +0 -0
  93. package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.gz +0 -0
  94. package/dist/client/assets/{diagram-VSXAHHWV-C_j4_Ldm.js → diagram-VSXAHHWV-BEA6kqNj.js} +1 -1
  95. package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.br +0 -0
  96. package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.gz +0 -0
  97. package/dist/client/assets/{diagram-VX7I27RA-DY62ObO9.js → diagram-VX7I27RA-CU9vP0P0.js} +1 -1
  98. package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.br +0 -0
  99. package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.gz +0 -0
  100. package/dist/client/assets/{diagram-Z3DM3KII-TnINooIy.js → diagram-Z3DM3KII-B_GvE2g-.js} +1 -1
  101. package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.br +0 -0
  102. package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.gz +0 -0
  103. package/dist/client/assets/{ebnfDiagram-PWID7BFC-BahaUYTh.js → ebnfDiagram-PWID7BFC-BLrPQuLE.js} +1 -1
  104. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.br +0 -0
  105. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.gz +0 -0
  106. package/dist/client/assets/{erDiagram-RLTQ6QDP-DNiKhK5j.js → erDiagram-RLTQ6QDP-DlXgoyZ-.js} +1 -1
  107. package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.br +0 -0
  108. package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
  109. package/dist/client/assets/{flowDiagram-HODETNUW-x9c9CGXD.js → flowDiagram-HODETNUW-fPHWxEDZ.js} +1 -1
  110. package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.br +0 -0
  111. package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.gz +0 -0
  112. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BofL5Lxs.js → ganttDiagram-EL5Y4UJY-B8MOv6ug.js} +1 -1
  113. package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.br +0 -0
  114. package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.gz +0 -0
  115. package/dist/client/assets/{gestureArbiter-Cy5v_ZUL.js → gestureArbiter-JW8vcVRm.js} +1 -1
  116. package/dist/client/assets/gestureArbiter-JW8vcVRm.js.br +0 -0
  117. package/dist/client/assets/gestureArbiter-JW8vcVRm.js.gz +0 -0
  118. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CHYuiGg1.js → gitGraphDiagram-WWUBYQGX-lH9gSoFx.js} +1 -1
  119. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.br +0 -0
  120. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.gz +0 -0
  121. package/dist/client/assets/{infoDiagram-27XIBGKW-mfFgE6OE.js → infoDiagram-27XIBGKW-CSG29Ty_.js} +1 -1
  122. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CiCmFa95.js → ishikawaDiagram-5VMMS53U-CcwSE5qV.js} +1 -1
  123. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.br +0 -0
  124. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.gz +0 -0
  125. package/dist/client/assets/{journeyDiagram-3NMN7TZE-CVnBzo7n.js → journeyDiagram-3NMN7TZE-CXFUeP2c.js} +1 -1
  126. package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.br +0 -0
  127. package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.gz +0 -0
  128. package/dist/client/assets/{kanban-definition-UXKFOSKX-3-zdGxI0.js → kanban-definition-UXKFOSKX-KEHwljd2.js} +1 -1
  129. package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.br +0 -0
  130. package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.gz +0 -0
  131. package/dist/client/assets/{layout-DnB4RHWA.js → layout-DqYx0MnO.js} +1 -1
  132. package/dist/client/assets/layout-DqYx0MnO.js.br +0 -0
  133. package/dist/client/assets/layout-DqYx0MnO.js.gz +0 -0
  134. package/dist/client/assets/{linear-B_lli4BL.js → linear-BnG3XaJW.js} +1 -1
  135. package/dist/client/assets/linear-BnG3XaJW.js.br +0 -0
  136. package/dist/client/assets/linear-BnG3XaJW.js.gz +0 -0
  137. package/dist/client/assets/{mermaid.core-ClemZ-kc.js → mermaid.core-Ce00rdM6.js} +5 -5
  138. package/dist/client/assets/mermaid.core-Ce00rdM6.js.br +0 -0
  139. package/dist/client/assets/mermaid.core-Ce00rdM6.js.gz +0 -0
  140. package/dist/client/assets/{mindmap-definition-YA3MSWOX-hXDufapN.js → mindmap-definition-YA3MSWOX-D0hOW4SY.js} +1 -1
  141. package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.br +0 -0
  142. package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.gz +0 -0
  143. package/dist/client/assets/{passwordBootstrap-c-hmAGCQ.js → passwordBootstrap-Bncb8qF_.js} +1 -1
  144. package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.br +0 -0
  145. package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.gz +0 -0
  146. package/dist/client/assets/{pegDiagram-XKGWAZYB-yb1lAfHR.js → pegDiagram-XKGWAZYB-UKp9t91x.js} +1 -1
  147. package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.br +0 -0
  148. package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.gz +0 -0
  149. package/dist/client/assets/{pieDiagram-E7YTZNPT-lVC2DWY_.js → pieDiagram-E7YTZNPT-Dif97wbH.js} +1 -1
  150. package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.br +0 -0
  151. package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.gz +0 -0
  152. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CeqeNP5C.js → quadrantDiagram-AXDQQJYC-BPyHtH91.js} +1 -1
  153. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.br +0 -0
  154. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
  155. package/dist/client/assets/{railroadDiagram-O6MQD6OU-BMEBmjF2.js → railroadDiagram-O6MQD6OU-Wleq9LmS.js} +1 -1
  156. package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.br +0 -0
  157. package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.gz +0 -0
  158. package/dist/client/assets/{rdpSession-D77Fs4-L.js → rdpSession-CobFMSUs.js} +1 -1
  159. package/dist/client/assets/rdpSession-CobFMSUs.js.br +0 -0
  160. package/dist/client/assets/rdpSession-CobFMSUs.js.gz +0 -0
  161. package/dist/client/assets/{referenceLongPress-xNXQRqYn.js → referenceLongPress-bZ0xp-v2.js} +1 -1
  162. package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.br +0 -0
  163. package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.gz +0 -0
  164. package/dist/client/assets/{requirementDiagram-BXWQKSXE-CRhmIdDq.js → requirementDiagram-BXWQKSXE-BL4pp0Ta.js} +1 -1
  165. package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.br +0 -0
  166. package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.gz +0 -0
  167. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CZZPxGRu.js → sankeyDiagram-P5KCCOFB-CV1cKx37.js} +1 -1
  168. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.br +0 -0
  169. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.gz +0 -0
  170. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CvKYTCaL.js → sequenceDiagram-WJ2MYXX4-BF2InLwV.js} +1 -1
  171. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.br +0 -0
  172. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.gz +0 -0
  173. package/dist/client/assets/{sizeCapture-INFHLROL-DpMFgVd8.js → sizeCapture-INFHLROL-CldUyStR.js} +1 -1
  174. package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.br +0 -0
  175. package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.gz +0 -0
  176. package/dist/client/assets/{sparkles-BBYDWqCu.js → sparkles-Cg-yPdE2.js} +1 -1
  177. package/dist/client/assets/{stateDiagram-D77RDMKH-BUCKWh69.js → stateDiagram-D77RDMKH-CByehbEd.js} +1 -1
  178. package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.br +0 -0
  179. package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.gz +0 -0
  180. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTcrJ-w0.js +1 -0
  181. package/dist/client/assets/{swimlanes-42K2YHIH-Du84aO1l.js → swimlanes-42K2YHIH--1_7YTL4.js} +1 -1
  182. package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.br +0 -0
  183. package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.gz +0 -0
  184. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RSMV9AhO.js +8 -0
  185. package/dist/client/assets/{timeline-definition-24CTP7MA-94wGyYVM.js → timeline-definition-24CTP7MA-CXfXJFK6.js} +1 -1
  186. package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.br +0 -0
  187. package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.gz +0 -0
  188. package/dist/client/assets/{triangle-alert-BFCgLlxS.js → triangle-alert-CbMRJ6sx.js} +1 -1
  189. package/dist/client/assets/{vennDiagram-4TSXK5OY-C74llwIj.js → vennDiagram-4TSXK5OY-byr6UQpL.js} +1 -1
  190. package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.br +0 -0
  191. package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.gz +0 -0
  192. package/dist/client/assets/{videoCompression-b96RKMFE.js → videoCompression-ivJAy00d.js} +1 -1
  193. package/dist/client/assets/videoCompression-ivJAy00d.js.br +0 -0
  194. package/dist/client/assets/videoCompression-ivJAy00d.js.gz +0 -0
  195. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DVdgFaBs.js → wardleyDiagram-VM6X3IG4-FJduk-7s.js} +1 -1
  196. package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.br +0 -0
  197. package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.gz +0 -0
  198. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BmUxPbqk.js → xychartDiagram-S5SC5T6Z-Ct1fBPFS.js} +1 -1
  199. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.br +0 -0
  200. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.gz +0 -0
  201. package/dist/client/index.html +1 -1
  202. package/dist/client/index.html.br +0 -0
  203. package/dist/client/index.html.gz +0 -0
  204. package/dist/client/sw.js +1 -1
  205. package/dist/client/sw.js.br +0 -0
  206. package/dist/client/sw.js.gz +0 -0
  207. package/dist/server/agent/collaborationCli.js +77 -17
  208. package/dist/server/agent/collaborationPrompt.js +3 -1
  209. package/dist/server/agent/collaborationProtocol.js +1 -1
  210. package/dist/server/agent/collaborationRoutes.js +7 -1
  211. package/dist/server/agent/collaborationStore.js +86 -11
  212. package/dist/server/agent/collaborationTaskService.js +2 -0
  213. package/dist/server/agent/integrationCli.js +52 -6
  214. package/dist/server/agent/integrationServer.js +99 -9
  215. package/dist/server/agent/integrationSessions.js +226 -18
  216. package/dist/server/agent/integrationStore.js +1 -1
  217. package/dist/server/agent/sessionLifecycle.js +81 -0
  218. package/dist/server/agent/sessionResumeConfiguration.js +32 -0
  219. package/dist/server/cli.js +1 -1
  220. package/dist/server/routes/terminal.js +75 -1
  221. package/docs/integrations.md +226 -6
  222. package/package.json +1 -1
  223. package/runtime-manifest.json +3 -3
  224. package/dist/client/assets/App-cagV-flM.js.br +0 -0
  225. package/dist/client/assets/App-cagV-flM.js.gz +0 -0
  226. package/dist/client/assets/ArchitecturePanel-TdKPja7L.js.br +0 -0
  227. package/dist/client/assets/ArchitecturePanel-TdKPja7L.js.gz +0 -0
  228. package/dist/client/assets/ChangeWalkthroughPanel-DOj40Srm.js.br +0 -0
  229. package/dist/client/assets/ChangeWalkthroughPanel-DOj40Srm.js.gz +0 -0
  230. package/dist/client/assets/CollaborationMainWorkspace-C5Tcos5l.js.br +0 -0
  231. package/dist/client/assets/CollaborationMainWorkspace-C5Tcos5l.js.gz +0 -0
  232. package/dist/client/assets/CollaborationTaskContent-DQpH2HIj.js +0 -2
  233. package/dist/client/assets/ComputerControlView-C2sOFyuA.js.br +0 -0
  234. package/dist/client/assets/ComputerControlView-C2sOFyuA.js.gz +0 -0
  235. package/dist/client/assets/DagPlayground-DFfVJW00.js.br +0 -0
  236. package/dist/client/assets/DagPlayground-DFfVJW00.js.gz +0 -0
  237. package/dist/client/assets/DiffLab-hoj8gwUi.js.br +0 -0
  238. package/dist/client/assets/DiffLab-hoj8gwUi.js.gz +0 -0
  239. package/dist/client/assets/DiffReview-Cn_PmN-9.js.br +0 -0
  240. package/dist/client/assets/DiffReview-Cn_PmN-9.js.gz +0 -0
  241. package/dist/client/assets/DiffReviewLab-DycgD40E.js.br +0 -0
  242. package/dist/client/assets/DiffReviewLab-DycgD40E.js.gz +0 -0
  243. package/dist/client/assets/DiffViewer-DRnuph7U.js.br +0 -0
  244. package/dist/client/assets/DiffViewer-DRnuph7U.js.gz +0 -0
  245. package/dist/client/assets/ModelPreview-Dvw-UtQQ.js.br +0 -0
  246. package/dist/client/assets/ModelPreview-Dvw-UtQQ.js.gz +0 -0
  247. package/dist/client/assets/RightSidebar-CnnU7Ffp.js.br +0 -0
  248. package/dist/client/assets/abnfDiagram-VCTEODGH-BQVIT5gD.js.br +0 -0
  249. package/dist/client/assets/abnfDiagram-VCTEODGH-BQVIT5gD.js.gz +0 -0
  250. package/dist/client/assets/app-CXvK8WZg.js.br +0 -0
  251. package/dist/client/assets/app-CXvK8WZg.js.gz +0 -0
  252. package/dist/client/assets/arc-DZG1NzUc.js.br +0 -0
  253. package/dist/client/assets/arc-DZG1NzUc.js.gz +0 -0
  254. package/dist/client/assets/architectureDiagram-5GKGNRK7-Bv15lJGE.js.br +0 -0
  255. package/dist/client/assets/architectureDiagram-5GKGNRK7-Bv15lJGE.js.gz +0 -0
  256. package/dist/client/assets/blockDiagram-I7D4REHJ-DShw-lH7.js.br +0 -0
  257. package/dist/client/assets/blockDiagram-I7D4REHJ-DShw-lH7.js.gz +0 -0
  258. package/dist/client/assets/c4Diagram-7LVT6UL2-BN6SsyOM.js.br +0 -0
  259. package/dist/client/assets/c4Diagram-7LVT6UL2-BN6SsyOM.js.gz +0 -0
  260. package/dist/client/assets/channel-YDx-Ycsw.js +0 -1
  261. package/dist/client/assets/chunk-F27PBJKO-BcpMMU4P.js.br +0 -0
  262. package/dist/client/assets/chunk-F27PBJKO-BcpMMU4P.js.gz +0 -0
  263. package/dist/client/assets/chunk-IMKFNOWR-cWvd5gJk.js.br +0 -0
  264. package/dist/client/assets/chunk-IMKFNOWR-cWvd5gJk.js.gz +0 -0
  265. package/dist/client/assets/chunk-SVP7TREG-Bj8gUFc2.js.br +0 -0
  266. package/dist/client/assets/chunk-SVP7TREG-Bj8gUFc2.js.gz +0 -0
  267. package/dist/client/assets/chunk-TICWLB2K-W1cGkC4s.js.br +0 -0
  268. package/dist/client/assets/chunk-TICWLB2K-W1cGkC4s.js.gz +0 -0
  269. package/dist/client/assets/classDiagram-ZZMXUADV-qeb16N1g.js +0 -1
  270. package/dist/client/assets/classDiagram-v2-VYDZK3BY-qeb16N1g.js +0 -1
  271. package/dist/client/assets/cose-bilkent-JH36ORCC-CK0EaMQY.js.br +0 -0
  272. package/dist/client/assets/cose-bilkent-JH36ORCC-CK0EaMQY.js.gz +0 -0
  273. package/dist/client/assets/cynefin-OW5HDTMX-C-7tsMpt.js.br +0 -0
  274. package/dist/client/assets/cynefin-OW5HDTMX-C-7tsMpt.js.gz +0 -0
  275. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CijqM5Xa.js.br +0 -0
  276. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CijqM5Xa.js.gz +0 -0
  277. package/dist/client/assets/dagre-GXQ25YYZ-DCLINHGD.js.br +0 -0
  278. package/dist/client/assets/dagre-GXQ25YYZ-DCLINHGD.js.gz +0 -0
  279. package/dist/client/assets/diagram-S7CK7UJ4-D-JvTWtL.js.br +0 -0
  280. package/dist/client/assets/diagram-S7CK7UJ4-D-JvTWtL.js.gz +0 -0
  281. package/dist/client/assets/diagram-UQ7AKVKN-BT01Oee8.js.br +0 -0
  282. package/dist/client/assets/diagram-UQ7AKVKN-BT01Oee8.js.gz +0 -0
  283. package/dist/client/assets/diagram-VSXAHHWV-C_j4_Ldm.js.br +0 -0
  284. package/dist/client/assets/diagram-VSXAHHWV-C_j4_Ldm.js.gz +0 -0
  285. package/dist/client/assets/diagram-VX7I27RA-DY62ObO9.js.br +0 -0
  286. package/dist/client/assets/diagram-VX7I27RA-DY62ObO9.js.gz +0 -0
  287. package/dist/client/assets/diagram-Z3DM3KII-TnINooIy.js.br +0 -0
  288. package/dist/client/assets/diagram-Z3DM3KII-TnINooIy.js.gz +0 -0
  289. package/dist/client/assets/ebnfDiagram-PWID7BFC-BahaUYTh.js.br +0 -0
  290. package/dist/client/assets/ebnfDiagram-PWID7BFC-BahaUYTh.js.gz +0 -0
  291. package/dist/client/assets/erDiagram-RLTQ6QDP-DNiKhK5j.js.br +0 -0
  292. package/dist/client/assets/erDiagram-RLTQ6QDP-DNiKhK5j.js.gz +0 -0
  293. package/dist/client/assets/flowDiagram-HODETNUW-x9c9CGXD.js.br +0 -0
  294. package/dist/client/assets/flowDiagram-HODETNUW-x9c9CGXD.js.gz +0 -0
  295. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BofL5Lxs.js.br +0 -0
  296. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BofL5Lxs.js.gz +0 -0
  297. package/dist/client/assets/gestureArbiter-Cy5v_ZUL.js.br +0 -0
  298. package/dist/client/assets/gestureArbiter-Cy5v_ZUL.js.gz +0 -0
  299. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CHYuiGg1.js.br +0 -0
  300. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CHYuiGg1.js.gz +0 -0
  301. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CiCmFa95.js.br +0 -0
  302. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CiCmFa95.js.gz +0 -0
  303. package/dist/client/assets/journeyDiagram-3NMN7TZE-CVnBzo7n.js.br +0 -0
  304. package/dist/client/assets/journeyDiagram-3NMN7TZE-CVnBzo7n.js.gz +0 -0
  305. package/dist/client/assets/kanban-definition-UXKFOSKX-3-zdGxI0.js.br +0 -0
  306. package/dist/client/assets/kanban-definition-UXKFOSKX-3-zdGxI0.js.gz +0 -0
  307. package/dist/client/assets/layout-DnB4RHWA.js.br +0 -0
  308. package/dist/client/assets/layout-DnB4RHWA.js.gz +0 -0
  309. package/dist/client/assets/linear-B_lli4BL.js.br +0 -0
  310. package/dist/client/assets/linear-B_lli4BL.js.gz +0 -0
  311. package/dist/client/assets/mermaid.core-ClemZ-kc.js.br +0 -0
  312. package/dist/client/assets/mermaid.core-ClemZ-kc.js.gz +0 -0
  313. package/dist/client/assets/mindmap-definition-YA3MSWOX-hXDufapN.js.br +0 -0
  314. package/dist/client/assets/mindmap-definition-YA3MSWOX-hXDufapN.js.gz +0 -0
  315. package/dist/client/assets/passwordBootstrap-c-hmAGCQ.js.br +0 -0
  316. package/dist/client/assets/passwordBootstrap-c-hmAGCQ.js.gz +0 -0
  317. package/dist/client/assets/pegDiagram-XKGWAZYB-yb1lAfHR.js.br +0 -0
  318. package/dist/client/assets/pegDiagram-XKGWAZYB-yb1lAfHR.js.gz +0 -0
  319. package/dist/client/assets/pieDiagram-E7YTZNPT-lVC2DWY_.js.br +0 -0
  320. package/dist/client/assets/pieDiagram-E7YTZNPT-lVC2DWY_.js.gz +0 -0
  321. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CeqeNP5C.js.br +0 -0
  322. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CeqeNP5C.js.gz +0 -0
  323. package/dist/client/assets/railroadDiagram-O6MQD6OU-BMEBmjF2.js.br +0 -0
  324. package/dist/client/assets/railroadDiagram-O6MQD6OU-BMEBmjF2.js.gz +0 -0
  325. package/dist/client/assets/rdpSession-D77Fs4-L.js.br +0 -0
  326. package/dist/client/assets/rdpSession-D77Fs4-L.js.gz +0 -0
  327. package/dist/client/assets/referenceLongPress-xNXQRqYn.js.br +0 -0
  328. package/dist/client/assets/referenceLongPress-xNXQRqYn.js.gz +0 -0
  329. package/dist/client/assets/requirementDiagram-BXWQKSXE-CRhmIdDq.js.br +0 -0
  330. package/dist/client/assets/requirementDiagram-BXWQKSXE-CRhmIdDq.js.gz +0 -0
  331. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZZPxGRu.js.br +0 -0
  332. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZZPxGRu.js.gz +0 -0
  333. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CvKYTCaL.js.br +0 -0
  334. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CvKYTCaL.js.gz +0 -0
  335. package/dist/client/assets/sizeCapture-INFHLROL-DpMFgVd8.js.br +0 -0
  336. package/dist/client/assets/sizeCapture-INFHLROL-DpMFgVd8.js.gz +0 -0
  337. package/dist/client/assets/stateDiagram-D77RDMKH-BUCKWh69.js.br +0 -0
  338. package/dist/client/assets/stateDiagram-D77RDMKH-BUCKWh69.js.gz +0 -0
  339. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CrhJVriY.js +0 -1
  340. package/dist/client/assets/swimlanes-42K2YHIH-Du84aO1l.js.br +0 -0
  341. package/dist/client/assets/swimlanes-42K2YHIH-Du84aO1l.js.gz +0 -0
  342. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-bsc39ya3.js +0 -8
  343. package/dist/client/assets/timeline-definition-24CTP7MA-94wGyYVM.js.br +0 -0
  344. package/dist/client/assets/timeline-definition-24CTP7MA-94wGyYVM.js.gz +0 -0
  345. package/dist/client/assets/vennDiagram-4TSXK5OY-C74llwIj.js.br +0 -0
  346. package/dist/client/assets/vennDiagram-4TSXK5OY-C74llwIj.js.gz +0 -0
  347. package/dist/client/assets/videoCompression-b96RKMFE.js.br +0 -0
  348. package/dist/client/assets/videoCompression-b96RKMFE.js.gz +0 -0
  349. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DVdgFaBs.js.br +0 -0
  350. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DVdgFaBs.js.gz +0 -0
  351. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.js.br +0 -0
  352. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.js.gz +0 -0
@@ -4,12 +4,20 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { timingSafeEqual } from 'node:crypto';
7
- import { CollaborationError } from './collaborationProtocol.js';
7
+ import { CollaborationError, resolveIdPrefix } from './collaborationProtocol.js';
8
8
  import { terminalMessage } from './collaborationStore.js';
9
9
  import { IntegrationStore, INTEGRATION_PROTOCOL, INTEGRATION_OPERATIONS, integrationError } from './integrationStore.js';
10
10
  import { IntegrationSessions, publicIntegrationSession } from './integrationSessions.js';
11
11
  import { getTermdockVersion } from '../utils/version.js';
12
12
  export function integrationSocketPath(port) { return path.join(homedir(), '.termdock', `integration-${port}.sock`); }
13
+ function scopedId(input, ids, type) {
14
+ if (typeof input !== 'string')
15
+ integrationError(`INVALID_${type}_ID`, 'A message or member id is required');
16
+ const result = resolveIdPrefix([...new Set(ids)], input);
17
+ if (result.status !== 'ok')
18
+ integrationError(`${type}_${result.status === 'ambiguous' ? 'ID_AMBIGUOUS' : 'NOT_FOUND'}`, `No unique ${type.toLowerCase()} in this integration group`, result.status === 'ambiguous' ? 409 : 404);
19
+ return result.id;
20
+ }
13
21
  /** A local authenticated API and push stream. It is never mounted on the
14
22
  * public HTTP application and does not reuse an integration's admin token. */
15
23
  export class IntegrationRuntime {
@@ -26,6 +34,7 @@ export class IntegrationRuntime {
26
34
  this.options = options;
27
35
  this.store = new IntegrationStore(path.join(options.directory, 'integrations.json'));
28
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)));
29
38
  const changed = () => { try {
30
39
  this.sync();
31
40
  }
@@ -35,7 +44,7 @@ export class IntegrationRuntime {
35
44
  } };
36
45
  this.stops.push(options.messages.subscribe(changed), options.taskStore.subscribe(changed), this.sessions.subscribe(changed));
37
46
  const app = express();
38
- app.use(express.json({ limit: '1mb' }));
47
+ app.use(express.json({ limit: '2mb' }));
39
48
  app.use((req, res, next) => {
40
49
  if (req.headers['x-termdock-integration-protocol'] !== String(INTEGRATION_PROTOCOL)) {
41
50
  res.status(409).json({ ...this.error(new CollaborationError('INCOMPATIBLE_INTEGRATION_PROTOCOL', 'Unsupported integration protocol', 409)), protocol: INTEGRATION_PROTOCOL });
@@ -62,6 +71,8 @@ export class IntegrationRuntime {
62
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 })) }; }));
63
72
  app.get('/admin/capabilities', run(req => { admin(req); return this.capabilities(); }));
64
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)) }; }));
65
76
  app.get('/admin/principals', run(req => { admin(req); return { integrations: this.store.policies() }; }));
66
77
  app.post('/admin/principals', run(req => { admin(req); this.group(req.body?.groupId); const provisioned = this.store.provision(req.body); try {
67
78
  this.sync();
@@ -103,11 +114,70 @@ export class IntegrationRuntime {
103
114
  const event = options.taskStore.integrationRequestEvent(p.id, input.idempotencyKey, task.id);
104
115
  return { task, event_id: event?.id ?? null, attempt_id: event?.attemptId ?? null, delivery_id: event?.deliveryId ?? null };
105
116
  }));
117
+ app.post('/send', run(req => {
118
+ const p = principal(req);
119
+ this.authorize(p, 'message.send', req.body?.group_id);
120
+ const group = this.group(p.groupId), raw = req.body ?? {};
121
+ if (raw.kind && raw.kind !== 'message')
122
+ integrationError('INTEGRATION_OPERATION_DENIED', 'Integration send uses ordinary messages only', 403);
123
+ const targets = Array.isArray(raw.toSessionIds) ? raw.toSessionIds : [raw.targetSessionId];
124
+ if (!targets.length || targets.some((id) => typeof id !== 'string'))
125
+ integrationError('INVALID_MESSAGE_TARGET', 'Specify a local group member');
126
+ const descriptors = this.sessions.snapshot().filter(s => s.group_id === p.groupId);
127
+ const recipients = targets.map((id) => scopedId(id, [...group.sessionIds, ...descriptors.map(s => s.session_id)], 'SESSION'));
128
+ for (const id of recipients) {
129
+ this.sessions.assertMessageTarget(p, id);
130
+ if (!group.sessionIds.includes(id))
131
+ integrationError('SESSION_NOT_FOUND', 'Restore the original runtime and membership before sending', 404);
132
+ }
133
+ let thread;
134
+ if (raw.thread_id)
135
+ thread = scopedId(raw.thread_id, options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).map(m => m.threadId), 'THREAD');
136
+ const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
137
+ integrationOrigin: this.origin(p, raw.origin), toSessionIds: recipients, kind: 'message', content: raw.message, threadId: thread });
138
+ this.sync();
139
+ return { ok: true, message_ids: messages.map(m => m.id), receipts: messages.map(m => options.messages.receipt(m.id)), ...options.messages.receipt(messages[0].id) };
140
+ }));
141
+ app.post('/reply', run(req => {
142
+ const p = principal(req);
143
+ this.authorize(p, 'message.send', req.body?.group_id);
144
+ const raw = req.body ?? {}, original = this.message(p, raw.messageId);
145
+ const target = original.toPrincipalId === p.id ? original.fromSessionId
146
+ : original.integrationOrigin?.integrationId === p.id ? original.toSessionId : null;
147
+ if (target)
148
+ this.sessions.assertMessageTarget(p, target);
149
+ if (!target || !this.group(p.groupId).sessionIds.includes(target))
150
+ integrationError('NO_REPLY_TARGET', 'Reply requires your own message and its original live group member', 409);
151
+ const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
152
+ integrationOrigin: this.origin(p, raw.origin), toSessionIds: [target], kind: 'reply', content: raw.content, threadId: original.threadId, replyTo: original.id });
153
+ this.sync();
154
+ return { ok: true, ...options.messages.receipt(messages[0].id) };
155
+ }));
156
+ app.get('/message', run(req => {
157
+ const p = principal(req);
158
+ this.authorize(p, 'message.read', req.query.group);
159
+ const messages = options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).sort((a, b) => a.sequence - b.sequence);
160
+ const after = req.query['after-id'] ? this.message(p, String(req.query['after-id'])) : null;
161
+ const limit = req.query.limit === undefined ? 50 : Number(req.query.limit);
162
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200)
163
+ integrationError('INVALID_MESSAGE_LIMIT', 'limit must be 1..200');
164
+ const thread = req.query.thread ? scopedId(String(req.query.thread), messages.map(m => m.threadId), 'THREAD') : null;
165
+ const filtered = messages.filter(m => (!after || m.sequence > after.sequence) && (!thread || m.threadId === thread));
166
+ const page = [];
167
+ let bytes = 0;
168
+ for (const message of filtered.slice(0, limit)) {
169
+ const size = Buffer.byteLength(JSON.stringify(message));
170
+ if (page.length && bytes + size > 2 * 1024 * 1024)
171
+ break;
172
+ page.push(message);
173
+ bytes += size;
174
+ }
175
+ return { messages: page.map(m => terminalMessage(m)), next_after_id: page.at(-1)?.id ?? after?.id ?? null, has_more: filtered.length > page.length, retention: 'retained_messages_only' };
176
+ }));
106
177
  app.get('/message/:id', run(req => {
107
- const p = principal(req), message = options.messages.getMessage(req.params.id);
108
- if (!message)
109
- integrationError('MESSAGE_NOT_FOUND', 'Message does not exist', 404);
110
- this.authorize(p, 'task.read', message.groupId);
178
+ const p = principal(req);
179
+ this.authorize(p, p.permissions.includes('message.read') ? 'message.read' : 'task.read');
180
+ const message = this.message(p, req.params.id);
111
181
  const receipt = options.messages.receipt(message.id);
112
182
  const { snapshot: _, ...diagnostic } = receipt;
113
183
  return { ok: true, ...diagnostic, ...(req.query.receipt_only === 'true' ? {} : { message: terminalMessage(message) }) };
@@ -121,6 +191,7 @@ export class IntegrationRuntime {
121
191
  }));
122
192
  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)) }; }));
123
193
  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) }; }));
194
+ 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) }; }));
124
195
  app.get('/events', run((req, res) => {
125
196
  const p = principal(req);
126
197
  this.authorize(p, 'events.read', req.query.group);
@@ -144,11 +215,13 @@ export class IntegrationRuntime {
144
215
  integration_principal: true, group_scoped_permissions: true, durable_event_push: true,
145
216
  event_ack_semantics: 'durable_received', correlated_task_responses: true, accepted_task_comments: true,
146
217
  idempotent_session_create: true, exact_session_restore: true, external_validation: false,
147
- startup_input_conditions: true,
218
+ startup_input_conditions: true, session_runtime_release: true, stable_session_identity: true, session_lifecycle_generation: true,
219
+ session_resume_configuration: true, session_lifecycle_min_version: '1.4.309',
148
220
  integration_policy_update: true,
149
221
  session_restore_diagnostics: true,
150
222
  native_identity_linux_flock_owner: process.platform === 'linux',
151
223
  background_task_records: true, task_purpose_configuration: true, explicit_execution_state: true,
224
+ integration_message_send: true, integration_message_reply: true, integration_message_history: true,
152
225
  permissions: INTEGRATION_OPERATIONS };
153
226
  }
154
227
  error(error) {
@@ -170,6 +243,18 @@ export class IntegrationRuntime {
170
243
  integrationError('TASK_NOT_FOUND', 'Task does not exist within this integration grant', 404);
171
244
  this.authorize(p, operation, task.groupId);
172
245
  }
246
+ message(p, id) {
247
+ const scoped = this.options.messages.snapshotMessages().filter(m => m.groupId === p.groupId);
248
+ const resolved = scopedId(id, scoped.map(m => m.id), 'MESSAGE');
249
+ return this.options.messages.getMessage(resolved);
250
+ }
251
+ messageExtras(raw) {
252
+ if (typeof raw.idempotency_key !== 'string' || !raw.idempotency_key.trim())
253
+ integrationError('IDEMPOTENCY_KEY_REQUIRED', 'Integration message mutations require an explicit idempotency key');
254
+ if (raw.task || raw.task_envelope || raw.metadata?.termdockTask)
255
+ integrationError('INTEGRATION_OPERATION_DENIED', 'Ordinary messages cannot forge task envelopes', 403);
256
+ return { idempotencyKey: raw.idempotency_key, responseKind: raw.response_kind, metadata: raw.metadata, expiresAt: raw.expires_at };
257
+ }
173
258
  origin(p, input) {
174
259
  const value = input && typeof input === 'object' ? input : {};
175
260
  if (Buffer.byteLength(JSON.stringify(value)) > 16384)
@@ -215,11 +300,16 @@ export class IntegrationRuntime {
215
300
  if (groups.has(message.groupId)) {
216
301
  const link = message.metadata?.termdockTask;
217
302
  const context = { group_id: message.groupId, task_id: link?.taskId ?? null, attempt_id: link?.attemptId ?? null,
218
- reply_to_event_id: link?.replyToEventId ?? null, message_id: message.id, source: message.fromSessionId ? 'member' : 'system' };
303
+ reply_to_event_id: link?.replyToEventId ?? null, message_id: message.id, source: message.integrationOrigin ? 'integration' : message.fromSessionId ? 'member' : 'system',
304
+ actor: message.integrationOrigin ? { integration_id: message.integrationOrigin.integrationId } : message.fromSessionId ? { session_id: message.fromSessionId } : null,
305
+ external_actor: message.integrationOrigin?.externalActor ?? null };
219
306
  updates.push({ sourceKey: `message:${message.id}`, sourceVersion: '1', event: { ...context,
220
307
  kind: message.kind === 'reply' ? 'message.reply' : 'message.queued', created_at: message.createdAt,
221
308
  payload: { message_id: message.id, thread_id: message.threadId, reply_to: message.replyTo ?? null,
222
- from_session_id: message.fromSessionId, to_session_id: message.toSessionId, content: message.content, metadata: message.metadata ?? null } } });
309
+ from_session_id: message.fromSessionId, to_session_id: message.toPrincipalId ? null : message.toSessionId,
310
+ to_principal_id: message.toPrincipalId ?? null, integration_origin: message.integrationOrigin ?? null,
311
+ response_kind: message.responseKind ?? null, stored_at: message.storedAt ?? null,
312
+ content: message.content, metadata: message.metadata ?? null } } });
223
313
  const { snapshot: _, ...receipt } = this.options.messages.receipt(message.id);
224
314
  updates.push({ sourceKey: `message-status:${message.id}`, sourceVersion: JSON.stringify(receipt), event: {
225
315
  ...context, kind: 'message.delivery', created_at: message.deliveredAt ?? message.createdAt, payload: { receipt }
@@ -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) {
@@ -50,7 +52,8 @@ export class IntegrationSessions {
50
52
  doc = { version: 1, sessions: [], requests: {} };
51
53
  pending = new Map();
52
54
  listeners = new Set();
53
- restoring = new Set();
55
+ lifecycle = new SessionLifecycleCoordinator();
56
+ mutations = new Map();
54
57
  timers = new Set();
55
58
  stopObservation;
56
59
  stopDeliveryGuard;
@@ -70,8 +73,12 @@ export class IntegrationSessions {
70
73
  if (error.code !== 'ENOENT')
71
74
  throw error;
72
75
  }
76
+ for (const record of this.doc.sessions) {
77
+ record.generation ??= 1;
78
+ record.runtime_present ??= null;
79
+ }
73
80
  for (const record of this.doc.sessions)
74
- if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.startup_input?.state === 'pending')
81
+ if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.state === 'releasing' || record.startup_input?.state === 'pending')
75
82
  this.scheduleRefresh(record);
76
83
  // A pre-restart partial match cannot prove continuity of the live screen.
77
84
  for (const record of this.doc.sessions)
@@ -89,12 +96,17 @@ export class IntegrationSessions {
89
96
  }
90
97
  subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
91
98
  snapshot() { return structuredClone(this.doc.sessions); }
92
- save() { atomicJson(this.file, this.doc); for (const listener of this.listeners) {
93
- try {
94
- listener();
99
+ save() {
100
+ if (Buffer.byteLength(JSON.stringify(this.doc)) > 64 * 1024 * 1024)
101
+ integrationError('SESSION_STORAGE_FULL', 'Recovery descriptor journal is full; no new operation was committed', 503);
102
+ atomicJson(this.file, this.doc);
103
+ for (const listener of this.listeners) {
104
+ try {
105
+ listener();
106
+ }
107
+ catch { /* Durable session state is reconciled on restart. */ }
95
108
  }
96
- catch { /* Durable session state is reconciled on restart. */ }
97
- } }
109
+ }
98
110
  saveRecord(record) {
99
111
  const before = this.doc.sessions;
100
112
  this.doc.sessions = [...before.filter(s => s.session_id !== record.session_id), structuredClone(record)];
@@ -139,7 +151,7 @@ export class IntegrationSessions {
139
151
  integrationError('INTEGRATION_SESSION_LIMIT', 'Integration session storage is full', 409);
140
152
  const now = Date.now(), record = { operation_id: randomUUID(), session_id: randomBytes(6).toString('hex'),
141
153
  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),
154
+ state: 'starting', generation: 1, runtime_present: null, error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
143
155
  created_at: now, updated_at: now, deadline: now + this.bindingTimeout, launch_submitted: false };
144
156
  record.startup_input = startupInput(record, now);
145
157
  this.doc.requests[request.scoped] = { hash: request.hash, sessionId: record.session_id };
@@ -156,6 +168,21 @@ export class IntegrationSessions {
156
168
  const promise = (async () => {
157
169
  try {
158
170
  // Store intent before launching. A failed persistence cannot execute.
171
+ if (this.adapter.resumeConfiguration) {
172
+ try {
173
+ const fingerprint = await resumeConfigurationFingerprint(record, this.adapter);
174
+ if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== fingerprint)
175
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
176
+ if (!restoring)
177
+ record.resume_configuration_fingerprint = fingerprint;
178
+ }
179
+ catch (error) {
180
+ // Existing creation supports plugins without an exact resume path.
181
+ // They remain launchable, but cannot gain a releasable descriptor.
182
+ if (restoring || error.code !== 'EXACT_RESUME_UNSUPPORTED')
183
+ throw error;
184
+ }
185
+ }
159
186
  record.launch_submitted = true;
160
187
  this.saveRecord(record);
161
188
  const prepared = (binding) => { record.terminal_binding = binding; this.saveRecord(record); };
@@ -187,27 +214,74 @@ export class IntegrationSessions {
187
214
  return structuredClone({ session_id: record.session_id, operation_id: record.operation_id,
188
215
  state: record.state, error_code: record.error_code, diagnostics: record.restore_diagnostics ?? null });
189
216
  }
190
- async restore(principal, id, idempotencyKey) {
217
+ assertRuntimeMessage(id) { assertMessageRuntime(this.doc.sessions.find(s => s.session_id === id)); }
218
+ assertMessageTarget(principal, id) {
219
+ const record = this.doc.sessions.find(s => s.session_id === id && s.group_id === principal.groupId);
220
+ assertMessageRuntime(record);
221
+ }
222
+ async mutate(principal, key, input, action) {
223
+ const request = this.request(principal, key, input);
224
+ if (request.previous?.result) {
225
+ this.lookup(principal, request.previous.sessionId);
226
+ return structuredClone(request.previous.result);
227
+ }
228
+ const active = this.mutations.get(request.scoped);
229
+ if (active) {
230
+ if (active.hash !== request.hash)
231
+ integrationError('IDEMPOTENCY_CONFLICT', 'Operation key parameters changed', 409);
232
+ return active.promise;
233
+ }
234
+ const promise = action().then(result => {
235
+ if (this.doc.requests[request.scoped]) {
236
+ const before = this.doc.requests[request.scoped];
237
+ this.doc.requests[request.scoped] = { ...before, result: structuredClone(result) };
238
+ try {
239
+ this.save();
240
+ }
241
+ catch (error) {
242
+ this.doc.requests[request.scoped] = before;
243
+ throw error;
244
+ }
245
+ }
246
+ return result;
247
+ }).finally(() => this.mutations.delete(request.scoped));
248
+ this.mutations.set(request.scoped, { hash: request.hash, promise });
249
+ return promise;
250
+ }
251
+ async replayRequest(principal, id, request) {
252
+ const record = await (this.pending.get(id) ?? this.get(principal, id));
253
+ if (request.operationId && (record.operation_id !== request.operationId || record.generation !== request.generation))
254
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Original operation result was not committed and a later generation exists; read session get, do not reuse this key', 409);
255
+ return record;
256
+ }
257
+ async restore(principal, id, key) {
258
+ return this.mutate(principal, key, ['restore', id], () => this.lifecycle.run(id, () => this.restoreOnce(principal, id, key)));
259
+ }
260
+ async restoreOnce(principal, id, idempotencyKey) {
191
261
  const request = this.request(principal, idempotencyKey, ['restore', id]);
192
262
  if (request.previous)
193
- return this.pending.get(id) ?? this.get(principal, id);
194
- if (this.pending.has(id) || this.restoring.has(id))
263
+ return this.replayRequest(principal, id, request.previous);
264
+ if (this.pending.has(id))
195
265
  integrationError('SESSION_OPERATION_IN_PROGRESS', 'Another session operation is in progress', 409);
196
- this.restoring.add(id);
266
+ let releaseNative;
197
267
  try {
198
268
  const record = await this.get(principal, id);
199
269
  if (!record.agent_native_session_id)
200
270
  integrationError('NATIVE_SESSION_ID_MISSING', 'An exact native session binding is required', 409);
201
271
  // Recheck the current grant and actual directories; restore uses the
202
272
  // original profile snapshot, never changed model or wrapper defaults.
203
- if (!principal.launchProfiles.some(p => p.id === record.launch_profile))
273
+ const authorizedProfile = principal.launchProfiles.find(p => p.id === record.launch_profile);
274
+ if (!authorizedProfile)
204
275
  integrationError('LAUNCH_PROFILE_DENIED', 'Original launch profile is no longer authorized', 403);
276
+ permittedCwd(authorizedProfile, record.cwd);
205
277
  permittedCwd(record.profile, record.cwd);
278
+ if (record.operation_uncertain || record.state === 'releasing')
279
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile prior release using session get before restore', 409);
206
280
  const observed = await this.adapter.inspect(record);
207
281
  if (observed.running) {
208
282
  if (observed.agentSlug !== record.agent_slug || observed.nativeId !== record.agent_native_session_id)
209
283
  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 };
284
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
211
285
  try {
212
286
  this.save();
213
287
  }
@@ -221,6 +295,13 @@ export class IntegrationSessions {
221
295
  integrationError('SESSION_OPERATION_IN_PROGRESS', 'Prior launch outcome is not yet resolved; inspect the original session', 409);
222
296
  if (observed.exists && !observed.shell)
223
297
  integrationError('SESSION_TARGET_NOT_SHELL', 'Original pane is not a verified shell', 409);
298
+ if (record.state === 'released' && !record.resume_configuration_fingerprint)
299
+ integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must explicitly confirm the current recovery configuration', 409);
300
+ releaseNative = this.lifecycle.reserveNative(record, this.doc.sessions);
301
+ if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
302
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
303
+ record.generation = (record.generation ?? 1) + 1;
304
+ record.runtime_present = observed.exists;
224
305
  record.state = 'restoring';
225
306
  record.error_code = null;
226
307
  record.operation_id = randomUUID();
@@ -230,7 +311,7 @@ export class IntegrationSessions {
230
311
  if (!record.profile.startupInput && !record.startup_condition)
231
312
  record.startup_condition = structuredClone(principal.launchProfiles.find(profile => profile.id === record.launch_profile)?.startupInput);
232
313
  record.startup_input = startupInput(record, Date.now());
233
- this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
314
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
234
315
  try {
235
316
  this.saveRecord(record);
236
317
  }
@@ -241,13 +322,135 @@ export class IntegrationSessions {
241
322
  return await this.launch(record, true);
242
323
  }
243
324
  finally {
244
- this.restoring.delete(id);
325
+ releaseNative?.();
245
326
  }
246
327
  }
328
+ async resumeConfiguration(id) {
329
+ const record = this.doc.sessions.find(s => s.session_id === id);
330
+ if (!record)
331
+ return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
332
+ return { session_id: id, generation: record.generation ?? 1, recorded_fingerprint: record.resume_configuration_fingerprint ?? null,
333
+ current_fingerprint: await resumeConfigurationFingerprint(record, this.adapter), confirmation_required: !record.resume_configuration_fingerprint };
334
+ }
335
+ async confirmResumeConfiguration(id, generation, fingerprint) {
336
+ return this.lifecycle.run(id, async () => {
337
+ const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
338
+ if (!record)
339
+ return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
340
+ assertReleaseGeneration(record, generation);
341
+ if (this.pending.has(id) || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
342
+ integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current operation before confirming compatibility', 409);
343
+ if (!/^[a-f0-9]{64}$/.test(fingerprint) || fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
344
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Current fingerprint no longer matches the administrator confirmation', 409);
345
+ record.resume_configuration_fingerprint = fingerprint;
346
+ record.generation = generation + 1;
347
+ record.updated_at = Date.now();
348
+ this.saveRecord(record);
349
+ return record;
350
+ });
351
+ }
352
+ async release(principal, id, generation, key) {
353
+ return this.mutate(principal, key, ['release', id, generation], () => this.lifecycle.run(id, async () => {
354
+ const request = this.request(principal, key, ['release', id, generation]);
355
+ if (request.previous)
356
+ return this.replayRequest(principal, id, request.previous);
357
+ if (!this.adapter.release || !this.adapter.runtimePresent)
358
+ integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime adapter does not support release', 409);
359
+ if (this.pending.has(id))
360
+ integrationError('SESSION_OPERATION_IN_PROGRESS', 'Launch is in progress', 409);
361
+ const record = await this.get(principal, id);
362
+ assertReleaseGeneration(record, generation);
363
+ if (record.state !== 'released') {
364
+ assertReleaseFacts(record, await this.adapter.inspect(record));
365
+ if (this.adapter.pendingMessages?.(id))
366
+ integrationError('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before release', 409);
367
+ if (!record.resume_configuration_fingerprint)
368
+ integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must inspect and confirm the recovery configuration before release', 409);
369
+ if (record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
370
+ integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
371
+ }
372
+ if (record.state === 'released') {
373
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
374
+ try {
375
+ this.save();
376
+ }
377
+ catch (error) {
378
+ delete this.doc.requests[request.scoped];
379
+ throw error;
380
+ }
381
+ return record;
382
+ }
383
+ if (this.adapter.prepareRelease)
384
+ record.group_role = (await this.adapter.prepareRelease(record)).group_role;
385
+ record.state = 'releasing';
386
+ record.operation_id = randomUUID();
387
+ record.generation = generation + 1;
388
+ record.operation_uncertain = false;
389
+ record.error_code = null;
390
+ record.updated_at = Date.now();
391
+ this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
392
+ try {
393
+ this.saveRecord(record);
394
+ }
395
+ catch (error) {
396
+ delete this.doc.requests[request.scoped];
397
+ throw error;
398
+ }
399
+ try {
400
+ await this.adapter.release(record, false);
401
+ if (await this.adapter.runtimePresent(record))
402
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime removal could not be confirmed', 409);
403
+ record.state = 'released';
404
+ record.runtime_present = false;
405
+ record.terminal_binding = null;
406
+ record.startup_input = undefined;
407
+ }
408
+ catch (error) {
409
+ record.state = 'failed';
410
+ record.operation_uncertain = true;
411
+ record.runtime_present = null;
412
+ record.error_code = typeof error.code === 'string' ? error.code : 'SESSION_RELEASE_FAILED';
413
+ }
414
+ record.updated_at = Date.now();
415
+ this.saveRecord(record);
416
+ return record;
417
+ }));
418
+ }
247
419
  async refresh(id) {
248
420
  const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
249
421
  if (!record)
250
422
  return;
423
+ if (record.state === 'released' || record.state === 'releasing' || record.operation_uncertain) {
424
+ if (this.lifecycle.active(id))
425
+ return;
426
+ try {
427
+ if (record.state !== 'released')
428
+ await this.adapter.release?.(record, true);
429
+ const present = await this.adapter.runtimePresent?.(record);
430
+ if (present === undefined)
431
+ integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime presence is unavailable', 409);
432
+ if (record.state !== 'released') {
433
+ if (present)
434
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Prior release still has a runtime; explicit release retry is required', 409);
435
+ // Absent runtime metadata has been reconciled without killing a live runtime.
436
+ }
437
+ else if (present)
438
+ integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'A runtime exists for a released descriptor', 409);
439
+ const next = { ...record, state: 'released', runtime_present: false, terminal_binding: null, startup_input: undefined, operation_uncertain: false, error_code: null };
440
+ if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
441
+ return;
442
+ if (JSON.stringify(next) !== JSON.stringify(record))
443
+ this.saveRecord({ ...next, updated_at: Date.now() });
444
+ }
445
+ catch (error) {
446
+ if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
447
+ return;
448
+ const next = { ...record, state: 'failed', runtime_present: null, operation_uncertain: true, error_code: typeof error.code === 'string' ? error.code : 'OPERATION_OUTCOME_UNCONFIRMED' };
449
+ if (JSON.stringify(next) !== JSON.stringify(record))
450
+ this.saveRecord({ ...next, updated_at: Date.now() });
451
+ }
452
+ return;
453
+ }
251
454
  let observed;
252
455
  try {
253
456
  observed = await this.adapter.inspect(record);
@@ -261,7 +464,7 @@ export class IntegrationSessions {
261
464
  this.saveRecord({ ...record, state: 'failed', error_code: typeof code === 'string' ? code : 'SESSION_OBSERVATION_FAILED', updated_at: Date.now() });
262
465
  return;
263
466
  }
264
- const next = { ...record };
467
+ const next = { ...record, runtime_present: this.adapter.runtimePresent ? await this.adapter.runtimePresent(record) : observed.exists };
265
468
  if (observed.nativeId && (typeof observed.nativeId !== 'string' || observed.nativeId.length > 256 || /[\x00-\x1f\x7f]/.test(observed.nativeId))) {
266
469
  next.state = 'failed';
267
470
  next.error_code = 'NATIVE_SESSION_ID_INVALID';
@@ -325,7 +528,12 @@ export class IntegrationSessions {
325
528
  }
326
529
  /** Only gates configured launches. Native UUID and Agent turn state are not input prerequisites. */
327
530
  async deliveryReadiness(id) {
328
- if (!this.doc.sessions.find(record => record.session_id === id)?.startup_input)
531
+ const existing = this.doc.sessions.find(record => record.session_id === id);
532
+ if (!existing)
533
+ return null;
534
+ if (existing.state === 'released' || existing.state === 'releasing' || existing.operation_uncertain)
535
+ return { allowed: false, reason: existing.state === 'released' ? 'SESSION_RELEASED' : 'SESSION_OPERATION_IN_PROGRESS' };
536
+ if (!existing.startup_input)
329
537
  return null;
330
538
  await this.refresh(id);
331
539
  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 = ['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', 'release 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 releasing');
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
+ }