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.
- package/dist/client/assets/{App-cagV-flM.js → App-ggIA_xqR.js} +6 -6
- package/dist/client/assets/App-ggIA_xqR.js.br +0 -0
- package/dist/client/assets/App-ggIA_xqR.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-TdKPja7L.js → ArchitecturePanel-CoBspW_C.js} +1 -1
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-DOj40Srm.js → ChangeWalkthroughPanel-D1Puc7B3.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.gz +0 -0
- package/dist/client/assets/{CollaborationMainWorkspace-C5Tcos5l.js → CollaborationMainWorkspace-BPyK5Xs8.js} +1 -1
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-CTSJJqze.js +2 -0
- package/dist/client/assets/{ComputerControlView-C2sOFyuA.js → ComputerControlView-D5Lw9sNA.js} +2 -2
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.br +0 -0
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DFfVJW00.js → DagPlayground-DYJrGfiG.js} +1 -1
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.br +0 -0
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.gz +0 -0
- package/dist/client/assets/{DiffLab-hoj8gwUi.js → DiffLab-5zdcFW3z.js} +1 -1
- package/dist/client/assets/DiffLab-5zdcFW3z.js.br +0 -0
- package/dist/client/assets/DiffLab-5zdcFW3z.js.gz +0 -0
- package/dist/client/assets/{DiffReview-Cn_PmN-9.js → DiffReview-BBBC_NjV.js} +1 -1
- package/dist/client/assets/DiffReview-BBBC_NjV.js.br +0 -0
- package/dist/client/assets/DiffReview-BBBC_NjV.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-DycgD40E.js → DiffReviewLab-CbhosFSB.js} +1 -1
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-DRnuph7U.js → DiffViewer-BuQwFQ4V.js} +1 -1
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.br +0 -0
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-Dvw-UtQQ.js → ModelPreview-nO2VmqdP.js} +1 -1
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.br +0 -0
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-CnnU7Ffp.js → RightSidebar-BoNzpS2K.js} +3 -3
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
- package/dist/client/assets/{RightSidebar-CnnU7Ffp.js.gz → RightSidebar-BoNzpS2K.js.gz} +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-BQVIT5gD.js → abnfDiagram-VCTEODGH-yi0K8GDG.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.gz +0 -0
- package/dist/client/assets/{app-CXvK8WZg.js → app-B6FdOF6K.js} +3 -3
- package/dist/client/assets/app-B6FdOF6K.js.br +0 -0
- package/dist/client/assets/app-B6FdOF6K.js.gz +0 -0
- package/dist/client/assets/{arc-DZG1NzUc.js → arc-_nLI_-XZ.js} +1 -1
- package/dist/client/assets/arc-_nLI_-XZ.js.br +0 -0
- package/dist/client/assets/arc-_nLI_-XZ.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-Bv15lJGE.js → architectureDiagram-5GKGNRK7-CikrA78O.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-DShw-lH7.js → blockDiagram-I7D4REHJ-CWJq6qJ7.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-BN6SsyOM.js → c4Diagram-7LVT6UL2-gzPzLGvs.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.gz +0 -0
- package/dist/client/assets/channel-BYsFmOmF.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BkBkNjv0.js → chunk-2Q5K7J3B-CfX9l18E.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-LrpNvv_o.js → chunk-5VM5RSS4-JSV1yrXM.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-BcpMMU4P.js → chunk-F27PBJKO-CQ0LXeap.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-cWvd5gJk.js → chunk-IMKFNOWR-CJOEPkK9.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-Bf4ORht5.js → chunk-JWPE2WC7-Dpp7Xu_E.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-M5MSHOUM.js → chunk-POPQ4Y6H-9hDa1b8z.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-Bj8gUFc2.js → chunk-SVP7TREG-BskkG9gg.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-W1cGkC4s.js → chunk-TICWLB2K-DyzuP-9I.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-mXLNbLH8.js → chunk-XXDRQBXY-C31_1i6z.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-BnmZv6Ns.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BnmZv6Ns.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CK0EaMQY.js → cose-bilkent-JH36ORCC-Bm-ESDOc.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-C-7tsMpt.js → cynefin-OW5HDTMX-DbFWAPvX.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CijqM5Xa.js → cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-DCLINHGD.js → dagre-GXQ25YYZ-D_5uEMjJ.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-D-JvTWtL.js → diagram-S7CK7UJ4-DWVpndRP.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BT01Oee8.js → diagram-UQ7AKVKN-DmYTya3w.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-C_j4_Ldm.js → diagram-VSXAHHWV-BEA6kqNj.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-DY62ObO9.js → diagram-VX7I27RA-CU9vP0P0.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-TnINooIy.js → diagram-Z3DM3KII-B_GvE2g-.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BahaUYTh.js → ebnfDiagram-PWID7BFC-BLrPQuLE.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DNiKhK5j.js → erDiagram-RLTQ6QDP-DlXgoyZ-.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-x9c9CGXD.js → flowDiagram-HODETNUW-fPHWxEDZ.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BofL5Lxs.js → ganttDiagram-EL5Y4UJY-B8MOv6ug.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-Cy5v_ZUL.js → gestureArbiter-JW8vcVRm.js} +1 -1
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.br +0 -0
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CHYuiGg1.js → gitGraphDiagram-WWUBYQGX-lH9gSoFx.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-mfFgE6OE.js → infoDiagram-27XIBGKW-CSG29Ty_.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CiCmFa95.js → ishikawaDiagram-5VMMS53U-CcwSE5qV.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CVnBzo7n.js → journeyDiagram-3NMN7TZE-CXFUeP2c.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-3-zdGxI0.js → kanban-definition-UXKFOSKX-KEHwljd2.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.gz +0 -0
- package/dist/client/assets/{layout-DnB4RHWA.js → layout-DqYx0MnO.js} +1 -1
- package/dist/client/assets/layout-DqYx0MnO.js.br +0 -0
- package/dist/client/assets/layout-DqYx0MnO.js.gz +0 -0
- package/dist/client/assets/{linear-B_lli4BL.js → linear-BnG3XaJW.js} +1 -1
- package/dist/client/assets/linear-BnG3XaJW.js.br +0 -0
- package/dist/client/assets/linear-BnG3XaJW.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-ClemZ-kc.js → mermaid.core-Ce00rdM6.js} +5 -5
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.br +0 -0
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-hXDufapN.js → mindmap-definition-YA3MSWOX-D0hOW4SY.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-c-hmAGCQ.js → passwordBootstrap-Bncb8qF_.js} +1 -1
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-yb1lAfHR.js → pegDiagram-XKGWAZYB-UKp9t91x.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-lVC2DWY_.js → pieDiagram-E7YTZNPT-Dif97wbH.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CeqeNP5C.js → quadrantDiagram-AXDQQJYC-BPyHtH91.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BMEBmjF2.js → railroadDiagram-O6MQD6OU-Wleq9LmS.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.gz +0 -0
- package/dist/client/assets/{rdpSession-D77Fs4-L.js → rdpSession-CobFMSUs.js} +1 -1
- package/dist/client/assets/rdpSession-CobFMSUs.js.br +0 -0
- package/dist/client/assets/rdpSession-CobFMSUs.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-xNXQRqYn.js → referenceLongPress-bZ0xp-v2.js} +1 -1
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.br +0 -0
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CRhmIdDq.js → requirementDiagram-BXWQKSXE-BL4pp0Ta.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CZZPxGRu.js → sankeyDiagram-P5KCCOFB-CV1cKx37.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CvKYTCaL.js → sequenceDiagram-WJ2MYXX4-BF2InLwV.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-DpMFgVd8.js → sizeCapture-INFHLROL-CldUyStR.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.gz +0 -0
- package/dist/client/assets/{sparkles-BBYDWqCu.js → sparkles-Cg-yPdE2.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-BUCKWh69.js → stateDiagram-D77RDMKH-CByehbEd.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTcrJ-w0.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Du84aO1l.js → swimlanes-42K2YHIH--1_7YTL4.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RSMV9AhO.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-94wGyYVM.js → timeline-definition-24CTP7MA-CXfXJFK6.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-BFCgLlxS.js → triangle-alert-CbMRJ6sx.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-C74llwIj.js → vennDiagram-4TSXK5OY-byr6UQpL.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.gz +0 -0
- package/dist/client/assets/{videoCompression-b96RKMFE.js → videoCompression-ivJAy00d.js} +1 -1
- package/dist/client/assets/videoCompression-ivJAy00d.js.br +0 -0
- package/dist/client/assets/videoCompression-ivJAy00d.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DVdgFaBs.js → wardleyDiagram-VM6X3IG4-FJduk-7s.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BmUxPbqk.js → xychartDiagram-S5SC5T6Z-Ct1fBPFS.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.gz +0 -0
- package/dist/client/index.html +1 -1
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationCli.js +77 -17
- package/dist/server/agent/collaborationPrompt.js +3 -1
- package/dist/server/agent/collaborationProtocol.js +1 -1
- package/dist/server/agent/collaborationRoutes.js +7 -1
- package/dist/server/agent/collaborationStore.js +86 -11
- package/dist/server/agent/collaborationTaskService.js +2 -0
- package/dist/server/agent/integrationCli.js +52 -6
- package/dist/server/agent/integrationServer.js +99 -9
- package/dist/server/agent/integrationSessions.js +226 -18
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/agent/sessionLifecycle.js +81 -0
- package/dist/server/agent/sessionResumeConfiguration.js +32 -0
- package/dist/server/cli.js +1 -1
- package/dist/server/routes/terminal.js +75 -1
- package/docs/integrations.md +226 -6
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-cagV-flM.js.br +0 -0
- package/dist/client/assets/App-cagV-flM.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-TdKPja7L.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-TdKPja7L.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DOj40Srm.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DOj40Srm.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-C5Tcos5l.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-C5Tcos5l.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-DQpH2HIj.js +0 -2
- package/dist/client/assets/ComputerControlView-C2sOFyuA.js.br +0 -0
- package/dist/client/assets/ComputerControlView-C2sOFyuA.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DFfVJW00.js.br +0 -0
- package/dist/client/assets/DagPlayground-DFfVJW00.js.gz +0 -0
- package/dist/client/assets/DiffLab-hoj8gwUi.js.br +0 -0
- package/dist/client/assets/DiffLab-hoj8gwUi.js.gz +0 -0
- package/dist/client/assets/DiffReview-Cn_PmN-9.js.br +0 -0
- package/dist/client/assets/DiffReview-Cn_PmN-9.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DycgD40E.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DycgD40E.js.gz +0 -0
- package/dist/client/assets/DiffViewer-DRnuph7U.js.br +0 -0
- package/dist/client/assets/DiffViewer-DRnuph7U.js.gz +0 -0
- package/dist/client/assets/ModelPreview-Dvw-UtQQ.js.br +0 -0
- package/dist/client/assets/ModelPreview-Dvw-UtQQ.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CnnU7Ffp.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BQVIT5gD.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BQVIT5gD.js.gz +0 -0
- package/dist/client/assets/app-CXvK8WZg.js.br +0 -0
- package/dist/client/assets/app-CXvK8WZg.js.gz +0 -0
- package/dist/client/assets/arc-DZG1NzUc.js.br +0 -0
- package/dist/client/assets/arc-DZG1NzUc.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bv15lJGE.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bv15lJGE.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DShw-lH7.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DShw-lH7.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BN6SsyOM.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BN6SsyOM.js.gz +0 -0
- package/dist/client/assets/channel-YDx-Ycsw.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-BcpMMU4P.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BcpMMU4P.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-cWvd5gJk.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-cWvd5gJk.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Bj8gUFc2.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Bj8gUFc2.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-W1cGkC4s.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-W1cGkC4s.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-qeb16N1g.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-qeb16N1g.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CK0EaMQY.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CK0EaMQY.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C-7tsMpt.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C-7tsMpt.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CijqM5Xa.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CijqM5Xa.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DCLINHGD.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DCLINHGD.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-JvTWtL.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-JvTWtL.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BT01Oee8.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BT01Oee8.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-C_j4_Ldm.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-C_j4_Ldm.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DY62ObO9.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DY62ObO9.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-TnINooIy.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-TnINooIy.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BahaUYTh.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BahaUYTh.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DNiKhK5j.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DNiKhK5j.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-x9c9CGXD.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-x9c9CGXD.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BofL5Lxs.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BofL5Lxs.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-Cy5v_ZUL.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Cy5v_ZUL.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CHYuiGg1.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CHYuiGg1.js.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CiCmFa95.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CiCmFa95.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CVnBzo7n.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CVnBzo7n.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-3-zdGxI0.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-3-zdGxI0.js.gz +0 -0
- package/dist/client/assets/layout-DnB4RHWA.js.br +0 -0
- package/dist/client/assets/layout-DnB4RHWA.js.gz +0 -0
- package/dist/client/assets/linear-B_lli4BL.js.br +0 -0
- package/dist/client/assets/linear-B_lli4BL.js.gz +0 -0
- package/dist/client/assets/mermaid.core-ClemZ-kc.js.br +0 -0
- package/dist/client/assets/mermaid.core-ClemZ-kc.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-hXDufapN.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-hXDufapN.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-c-hmAGCQ.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-c-hmAGCQ.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-yb1lAfHR.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-yb1lAfHR.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-lVC2DWY_.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-lVC2DWY_.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CeqeNP5C.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CeqeNP5C.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BMEBmjF2.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BMEBmjF2.js.gz +0 -0
- package/dist/client/assets/rdpSession-D77Fs4-L.js.br +0 -0
- package/dist/client/assets/rdpSession-D77Fs4-L.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-xNXQRqYn.js.br +0 -0
- package/dist/client/assets/referenceLongPress-xNXQRqYn.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CRhmIdDq.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CRhmIdDq.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZZPxGRu.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZZPxGRu.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CvKYTCaL.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CvKYTCaL.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DpMFgVd8.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DpMFgVd8.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BUCKWh69.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BUCKWh69.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CrhJVriY.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Du84aO1l.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Du84aO1l.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-bsc39ya3.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-94wGyYVM.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-94wGyYVM.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C74llwIj.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C74llwIj.js.gz +0 -0
- package/dist/client/assets/videoCompression-b96RKMFE.js.br +0 -0
- package/dist/client/assets/videoCompression-b96RKMFE.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DVdgFaBs.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DVdgFaBs.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.js.gz +0 -0
|
@@ -52,12 +52,41 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
|
|
|
52
52
|
── 后台集成(独立凭据;本机 Unix socket) ──
|
|
53
53
|
td integration help(管理授权与启动配置)
|
|
54
54
|
td collab --principal <id> capabilities
|
|
55
|
+
send <成员id> "执行正文" --idempotency-key <key>(权限 message.send;普通消息,不创建看板任务)
|
|
56
|
+
reply <原消息id> "追问正文" --idempotency-key <key>(回到原成员、原话题)
|
|
57
|
+
message get <id> | message list [--thread <id>] [--after-id <id>] [--limit 1..200]
|
|
58
|
+
(权限 message.read;list 返回保留记录,next_after_id/has_more 用于分页)
|
|
59
|
+
以上命令均需前缀 td collab --principal <id>;send/reply 支持 --file/--stdin。
|
|
60
|
+
来源可附 --source <name> --external-actor '<JSON object>' --external-message-id <id>。
|
|
61
|
+
Agent 在终端收到消息后:td collab --session <自己的TD成员id> reply <原消息id> "答复" --response-kind result --idempotency-key <key>
|
|
62
|
+
ack=明确回复收到;progress=原文进展;result=原文结果。后台回复 status=stored,仅表示持久保存。
|
|
63
|
+
回复通过 message.reply 事件主动推送;payload.reply_to 关联原消息,response_kind 标记回复类型。
|
|
55
64
|
events subscribe --consumer <name> --jsonl(主动推送,默认持续运行)
|
|
56
65
|
events ack <cursor> --consumer <name>(仅声明已持久接收)
|
|
66
|
+
events 需权限 events.read;按 event_id 去重,落盘后 ACK 连续前缀;订阅读到不等于 ACK。
|
|
67
|
+
断线自动重连,同 consumer 从已 ACK 处重放;保留缺口 EVENT_RETENTION_GAP 停止,需对账后换 consumer。
|
|
68
|
+
消息历史只覆盖仍保留记录;不存在的 after-id 返回 MESSAGE_NOT_FOUND,不能据此声称历史完整。
|
|
57
69
|
session create --group <id> --launch-profile <id> --cwd <absolute-path> --idempotency-key <key>
|
|
58
70
|
session get <id> | session restore <id> --idempotency-key <key>
|
|
71
|
+
session release <id> --generation <session-get-generation> --idempotency-key <key>
|
|
72
|
+
权限 session.release;仅原 Agent 已退出、精确空 shell、无 pending 消息时释放实际运行承载。
|
|
73
|
+
released: runtime_present=false、terminal_binding=null;保留逻辑 ID/原生 UUID/原消息,restore 后可继续 reply。
|
|
74
|
+
released 上 send/reply 返回 SESSION_RELEASED,不入队、不隐式恢复;业务先持久接收,再明确 restore。
|
|
75
|
+
释放/恢复最低 CLI/实际服务 1.4.309;检查 session_runtime_release/stable_session_identity/session_lifecycle_generation。
|
|
76
|
+
generation 每次实际 restore/release/管理员兼容确认递增;旧 generation 拒绝,运行中精确 restore 不递增。
|
|
77
|
+
重用同幂等键回读原操作结果,不能用于新一轮恢复;用 session get 查询当前状态。
|
|
78
|
+
旧配置须管理员 td integration resume-configuration <id> 查看,再 confirm-resume 显式确认当前指纹。
|
|
79
|
+
状态 starting/restoring/binding_pending/ready/failed/releasing/released;runtime_present=null 表示无法确认。
|
|
80
|
+
失败码:INVALID_SESSION_GENERATION、SESSION_GENERATION_MISMATCH、SESSION_RELEASE_UNSUPPORTED;
|
|
81
|
+
SESSION_PENDING_MESSAGES/SESSION_STILL_RUNNING/SESSION_OPERATION_IN_PROGRESS 表示应先处理原现场;
|
|
82
|
+
SESSION_NATIVE_OPERATION_IN_PROGRESS、SESSION_SCOPE_CONFLICT、SESSION_IDENTITY_MISMATCH 拒绝并发或身份/归属冲突;
|
|
83
|
+
SESSION_RELEASE_FAILED、SESSION_STORAGE_FULL 保留原记录,不表示回收成功;
|
|
84
|
+
RESUME_CONFIGURATION_CHANGED/RESUME_CONFIGURATION_UNCONFIRMED 拒绝静默替换配置;OPERATION_OUTCOME_UNCONFIRMED 保留记录待核对。
|
|
59
85
|
task respond <task-id> --attempt <attempt-id> --to-event <comment-event-id> --content <答复>
|
|
60
86
|
凭据通过 TERMDOCK_INTEGRATION_CREDENTIAL_FILE 注入,不通过命令行传 token。
|
|
87
|
+
消息接入最低 CLI/实际服务均为 1.4.308;capabilities 回读双方版本与 integration_message_send/reply/history。
|
|
88
|
+
principal 只操作获授权本机组;不能冒充用户/成员或用户验收。普通消息不需要 task/accepted。
|
|
89
|
+
任务命令适用于需要任务闭环的流程;message.send/read 与 task 权限独立。
|
|
61
90
|
已验收任务可追加纯咨询;respond 保留当前结果与验收,revise 是修改要求。
|
|
62
91
|
── 常用 ──
|
|
63
92
|
status (who is in my groups, their names, roles, observed activity.
|
|
@@ -145,11 +174,11 @@ Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LI
|
|
|
145
174
|
Idempotency retention: 7 days. Terminal delivery, ACK and result never imply each other.
|
|
146
175
|
Delivery semantics: delivered = written to the terminal — never proof of
|
|
147
176
|
reading or task completion; a timeout stops waiting, it does not cancel delivery.
|
|
148
|
-
The service cannot see whether the recipient's agent consumed a message.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
177
|
+
The service cannot see whether the recipient's agent consumed a message.
|
|
178
|
+
Use explicit replies as evidence. A screen snapshot is only an observation;
|
|
179
|
+
do not automatically resend a delivered message because it is absent from a snapshot.
|
|
180
|
+
stored = durable reply to a nonterminal principal; terminal delivery waits do not apply.
|
|
181
|
+
Idempotent retries reuse the same key and payload; a fresh key creates a new message.`;
|
|
153
182
|
/** One roster row: prefer the member's human name, keep the full session id
|
|
154
183
|
* reachable for role set/unset targeting, mark unset members explicitly. */
|
|
155
184
|
function roleLine(member, role) {
|
|
@@ -157,7 +186,7 @@ function roleLine(member, role) {
|
|
|
157
186
|
return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
|
|
158
187
|
}
|
|
159
188
|
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help', 'no-rules', 'managed', 'shared-directory', 'integration']);
|
|
160
|
-
const VALUE_OPTIONS = new Set(['purpose', 'principal', 'source', 'external-actor', 'external-message-id', 'launch-profile', 'to-event', 'decision', 'work-type', 'summary', 'verdict', 'reviewers', 'title', 'content', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'attempt', 'status', 'artifact', 'options', 'revision', 'evidence', 'session', 'group', 'thread', 'idempotency-key', 'file', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'kind', 'name', 'cwd', 'task', 'pane', 'lines', 'if-version', 'origin']);
|
|
189
|
+
const VALUE_OPTIONS = new Set(['generation', 'purpose', 'principal', 'source', 'external-actor', 'external-message-id', 'launch-profile', 'to-event', 'decision', 'work-type', 'summary', 'verdict', 'reviewers', 'title', 'content', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'attempt', 'status', 'artifact', 'options', 'revision', 'evidence', 'session', 'group', 'thread', 'idempotency-key', 'file', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'kind', 'name', 'cwd', 'task', 'pane', 'lines', 'if-version', 'origin']);
|
|
161
190
|
export function parseCollaborationCommand(argv) {
|
|
162
191
|
const options = {};
|
|
163
192
|
const positional = [];
|
|
@@ -223,12 +252,14 @@ export function parseCollaborationCommand(argv) {
|
|
|
223
252
|
throw new Error('Unexpected integration arguments');
|
|
224
253
|
if (action === 'events' && (!['subscribe', 'ack'].includes(command.operation ?? '') || (command.operation === 'ack' ? !command.target : Boolean(command.target))))
|
|
225
254
|
throw new Error('Usage: events subscribe | events ack <cursor>');
|
|
226
|
-
if (action === 'session' && (!['create', 'get', 'restore'].includes(command.operation ?? '') || (command.operation === 'create' ? Boolean(command.target) : !command.target)))
|
|
227
|
-
throw new Error('Usage: session create|get|restore');
|
|
255
|
+
if (action === 'session' && (!['create', 'get', 'restore', 'release'].includes(command.operation ?? '') || (command.operation === 'create' ? Boolean(command.target) : !command.target)))
|
|
256
|
+
throw new Error('Usage: session create|get|restore|release');
|
|
228
257
|
if (action === 'session' && command.operation !== 'get' && !options['idempotency-key'])
|
|
229
258
|
throw new Error('Session mutation requires an explicit --idempotency-key');
|
|
259
|
+
if (action === 'session' && command.operation === 'release' && (!/^[1-9][0-9]*$/.test(String(options.generation ?? '')) || !Number.isSafeInteger(Number(options.generation))))
|
|
260
|
+
throw new Error('session release requires --generation from session get');
|
|
230
261
|
const integrationFlags = action === 'events' ? command.operation === 'ack' ? ['consumer'] : ['consumer', 'group', 'timeout']
|
|
231
|
-
: command.operation === 'create' ? ['group', 'launch-profile', 'cwd', 'idempotency-key'] : command.operation === 'restore' ? ['idempotency-key'] : [];
|
|
262
|
+
: command.operation === 'create' ? ['group', 'launch-profile', 'cwd', 'idempotency-key'] : command.operation === 'restore' ? ['idempotency-key'] : command.operation === 'release' ? ['idempotency-key', 'generation'] : [];
|
|
232
263
|
for (const flag of Object.keys(options))
|
|
233
264
|
if (!['principal', 'json', 'jsonl', 'text', 'help', ...integrationFlags].includes(flag))
|
|
234
265
|
throw new Error(`--${flag} is not supported by ${action} ${command.operation}`);
|
|
@@ -250,6 +281,8 @@ export function parseCollaborationCommand(argv) {
|
|
|
250
281
|
throw new Error(`${action} requires a target and message`);
|
|
251
282
|
if ([Boolean(command.message), Boolean(options.file), Boolean(options.stdin)].filter(Boolean).length !== 1)
|
|
252
283
|
throw new Error('Choose inline body, --file, or --stdin');
|
|
284
|
+
if (options.principal && !options['idempotency-key'])
|
|
285
|
+
throw new Error('Integration message mutation requires an explicit --idempotency-key');
|
|
253
286
|
}
|
|
254
287
|
else if (action === 'rules') {
|
|
255
288
|
command.operation = positional.shift();
|
|
@@ -286,8 +319,15 @@ export function parseCollaborationCommand(argv) {
|
|
|
286
319
|
else if (action === 'message') {
|
|
287
320
|
command.operation = positional.shift();
|
|
288
321
|
command.target = positional.shift();
|
|
289
|
-
if (!['get', 'watch', 'confirm-shell'].includes(command.operation ?? '') || !command.target || positional.length)
|
|
290
|
-
throw new Error('Usage: td collab message get|watch|confirm-shell <id>');
|
|
322
|
+
if (!['get', 'watch', 'confirm-shell', 'list'].includes(command.operation ?? '') || (command.operation === 'list' ? !options.principal || Boolean(command.target) : !command.target) || positional.length)
|
|
323
|
+
throw new Error('Usage: td collab message get|watch|confirm-shell <id>; --principal <id> message list');
|
|
324
|
+
if (command.operation === 'list') {
|
|
325
|
+
for (const flag of Object.keys(options))
|
|
326
|
+
if (!['principal', 'json', 'jsonl', 'text', 'group', 'thread', 'after-id', 'limit'].includes(flag))
|
|
327
|
+
throw new Error(`--${flag} is not supported by message list`);
|
|
328
|
+
}
|
|
329
|
+
else if (['group', 'thread', 'after-id', 'limit'].some(flag => options[flag] !== undefined))
|
|
330
|
+
throw new Error('History filters apply to --principal message list only');
|
|
291
331
|
}
|
|
292
332
|
else if (action === 'cursor') {
|
|
293
333
|
command.operation = positional.shift();
|
|
@@ -357,18 +397,21 @@ export function parseCollaborationCommand(argv) {
|
|
|
357
397
|
throw new Error(`Unexpected arguments for ${action}`);
|
|
358
398
|
const allowed = new Set(['json', 'jsonl', 'text', 'help', 'session', 'principal']);
|
|
359
399
|
const byAction = {
|
|
360
|
-
events: ['consumer', 'group', 'timeout'], session: ['group', 'launch-profile', 'cwd', 'idempotency-key'],
|
|
400
|
+
events: ['consumer', 'group', 'timeout'], session: ['group', 'launch-profile', 'cwd', 'idempotency-key', 'generation'],
|
|
361
401
|
task: ['purpose', 'to-event', 'decision', 'source', 'external-actor', 'external-message-id', 'metadata', 'work-type', 'summary', 'integration', 'managed', 'shared-directory', 'reviewers', 'verdict', 'group', 'title', 'content', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'attempt', 'status', 'artifact', 'options', 'revision', 'evidence', 'idempotency-key', 'file', 'stdin'],
|
|
362
402
|
rules: ['file', 'stdin', 'if-version'], transport: ['file', 'origin'], group: ['file'], status: [], capabilities: [], rebind: ['pane'], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
|
|
363
403
|
send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at', 'kind'],
|
|
364
404
|
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
|
|
365
405
|
reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
366
406
|
inbox: ['raw', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
|
|
367
|
-
message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
|
|
407
|
+
message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply', 'group', 'thread', 'after-id', 'limit'], cursor: ['consumer'],
|
|
368
408
|
add: [], remove: [], spawn: ['launch-profile', 'name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: ['lines', 'raw'],
|
|
369
409
|
};
|
|
370
410
|
for (const option of byAction[action])
|
|
371
411
|
allowed.add(option);
|
|
412
|
+
if (options.principal && ['send', 'reply'].includes(action))
|
|
413
|
+
for (const option of ['source', 'external-actor', 'external-message-id'])
|
|
414
|
+
allowed.add(option);
|
|
372
415
|
for (const option of Object.keys(options))
|
|
373
416
|
if (!allowed.has(option))
|
|
374
417
|
throw new Error(`--${option} is not supported by ${action}`);
|
|
@@ -394,7 +437,7 @@ export function waitSatisfied(receipt, stage, reply) {
|
|
|
394
437
|
* machine reasons onto what the sender should do next, so a settled-but-
|
|
395
438
|
* unconsumed message is not mistaken for a lost one (or a good one). */
|
|
396
439
|
const DELIVERY_DIAGNOSTICS = {
|
|
397
|
-
AGENT_CONSUME_UNCONFIRMED: '
|
|
440
|
+
AGENT_CONSUME_UNCONFIRMED: '消息已写入对方终端,但未确认被对方消费;等待明确回复,不从终端快照推断消费或自动重发',
|
|
398
441
|
SHELL_CONFIRMATION_REQUIRED: '目标当前是 shell,消息可能被当命令执行;确认请运行 message confirm-shell <id>',
|
|
399
442
|
DELIVERY_IN_PROGRESS: '投递进行中(写入前的中间标记,正常会在数秒内推进为 delivered 或带原因的重试)',
|
|
400
443
|
TERMINAL_WRITE_FAILED: '写入对方终端失败,会按重试间隔继续尝试',
|
|
@@ -430,17 +473,22 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
430
473
|
const output = (value) => {
|
|
431
474
|
if (o.text) {
|
|
432
475
|
let diagnosed = false;
|
|
433
|
-
if (Array.isArray(value.messages))
|
|
476
|
+
if (Array.isArray(value.messages)) {
|
|
434
477
|
for (const message of value.messages) {
|
|
435
478
|
const fanIds = Array.isArray(message.fanOutIds) ? message.fanOutIds : [];
|
|
436
479
|
const names = value.names ?? {};
|
|
437
|
-
io.write(`[${message.responseKind ?? message.kind}] ${canonicalShortId(message.id)} from ${message.fromSessionId ?? 'user'}${fanIds.length ? ' · 群发' : ''}`);
|
|
480
|
+
io.write(`[${message.responseKind ?? message.kind}] ${canonicalShortId(message.id)} from ${message.fromSessionId ?? (message.integrationOrigin ? `principal:${message.integrationOrigin.integrationId}` : 'user')}${fanIds.length ? ' · 群发' : ''}`);
|
|
438
481
|
// A fan-out dispatch names its sibling recipients so a broadcast is
|
|
439
482
|
// never read as a one-to-one assignment; unknown ids stay raw.
|
|
440
483
|
if (fanIds.length)
|
|
441
484
|
io.write(`同时发给了:${fanIds.map((id) => names[id] ?? id).join('、')}`);
|
|
442
485
|
io.write(message.content);
|
|
486
|
+
if (message.status === 'stored')
|
|
487
|
+
io.write('答复已持久保存;接收方是后台程序,不是终端。');
|
|
443
488
|
}
|
|
489
|
+
if (value.next_after_id !== undefined)
|
|
490
|
+
io.write(`next_after_id=${value.next_after_id ?? ''} has_more=${value.has_more === true}(仅保留记录)`);
|
|
491
|
+
}
|
|
444
492
|
else if (value.message) {
|
|
445
493
|
// One line instead of the full rules text — the CLI is stateless so it
|
|
446
494
|
// cannot diff versions across calls; --no-rules silences it entirely.
|
|
@@ -645,12 +693,24 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
645
693
|
return 0;
|
|
646
694
|
}
|
|
647
695
|
if (command.action === 'message') {
|
|
696
|
+
if (command.operation === 'list') {
|
|
697
|
+
const params = new URLSearchParams();
|
|
698
|
+
for (const key of ['group', 'thread', 'after-id', 'limit'])
|
|
699
|
+
if (o[key])
|
|
700
|
+
params.set(key, String(o[key]));
|
|
701
|
+
output(await request('GET', `/message?${params}`));
|
|
702
|
+
return 0;
|
|
703
|
+
}
|
|
648
704
|
const route = `/message/${encodeURIComponent(command.target)}`;
|
|
649
705
|
if (command.operation === 'confirm-shell') {
|
|
650
706
|
output(await request('POST', `${route}/${command.operation}`));
|
|
651
707
|
return 0;
|
|
652
708
|
}
|
|
653
709
|
receipt = await request('GET', `${route}?receipt_only=${Boolean(o['receipt-only'] || command.operation === 'watch')}&raw=${Boolean(o.raw)}`);
|
|
710
|
+
if (receipt.status === 'stored') {
|
|
711
|
+
output(receipt);
|
|
712
|
+
return 0;
|
|
713
|
+
}
|
|
654
714
|
if (command.operation === 'get' && !o.follow && !o['wait-until'] && !o['expect-reply']) {
|
|
655
715
|
output(receipt);
|
|
656
716
|
return 0;
|
|
@@ -692,7 +752,7 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
692
752
|
// CLI receipts stay small even when the message is a large evidence package.
|
|
693
753
|
delete receipt.messages;
|
|
694
754
|
receipt.idempotency_key = idempotencyKey;
|
|
695
|
-
if (receipt.task_recorded) {
|
|
755
|
+
if (receipt.task_recorded || receipt.integration_recorded) {
|
|
696
756
|
output(receipt);
|
|
697
757
|
return 0;
|
|
698
758
|
}
|
|
@@ -89,7 +89,7 @@ export function formatCollaborationDelivery(input) {
|
|
|
89
89
|
const sourceSession = message.fromSessionId ? sessionsById.get(message.fromSessionId) : null;
|
|
90
90
|
const source = message.fromSessionId
|
|
91
91
|
? sanitizeCollaborationName(sourceSession?.name ?? message.fromSessionId)
|
|
92
|
-
: '用户';
|
|
92
|
+
: message.integrationOrigin ? `后台接入 ${sanitizeCollaborationName(message.integrationOrigin.integrationId, 80)}` : '用户';
|
|
93
93
|
const token = tokens.get(message.id) ?? message.id;
|
|
94
94
|
const bytes = Buffer.byteLength(message.content);
|
|
95
95
|
// A remote recipient's CLI cannot detect its identity (the session is not
|
|
@@ -103,6 +103,8 @@ export function formatCollaborationDelivery(input) {
|
|
|
103
103
|
const fannedNames = (message.fanOutIds ?? [])
|
|
104
104
|
.map((sessionId) => sanitizeCollaborationName(sessionsById.get(sessionId)?.name ?? sessionId));
|
|
105
105
|
blocks.push(formatCollaborationMessage({ ...message, content: body }, source, fannedNames, fence, token));
|
|
106
|
+
if (message.integrationOrigin)
|
|
107
|
+
blocks.push(`此消息来自后台程序,使用原消息 id 回复:td collab --session ${input.targetSessionId} reply ${token} "答复正文" --response-kind result --idempotency-key <稳定唯一key> --text\n需要时用 --response-kind ack|progress 明确回复收到或进展;result 提交结果。回复持久保存为 stored 并主动推送给后台,不创建看板任务,不代表用户验收。`);
|
|
106
108
|
}
|
|
107
109
|
// The shell header names the group only when every block belongs to one;
|
|
108
110
|
// a mixed batch drops the header entirely (no single-group claim to make).
|
|
@@ -143,4 +143,4 @@ export function extrasFromBody(body) {
|
|
|
143
143
|
responseKind: body.response_kind, metadata: body.metadata,
|
|
144
144
|
task: body.task, expiresAt: body.expires_at });
|
|
145
145
|
}
|
|
146
|
-
export const STATUS_RANK = { pending: 0, failed: 1, expired: 1, delivered: 2, read: 3 };
|
|
146
|
+
export const STATUS_RANK = { pending: 0, failed: 1, expired: 1, delivered: 2, read: 3, stored: 4 };
|
|
@@ -159,8 +159,9 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind, re
|
|
|
159
159
|
background_task_records: true, task_purpose_configuration: true, explicit_execution_state: true,
|
|
160
160
|
task_reply: 'Replies to task-linked user messages are stored in the task; status=stored is not a terminal delivery receipt' },
|
|
161
161
|
routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
|
|
162
|
-
statuses: ['pending', 'delivered', 'failed', 'expired'], queued_status: 'pending',
|
|
162
|
+
statuses: ['pending', 'delivered', 'failed', 'expired', 'stored'], queued_status: 'pending',
|
|
163
163
|
semantics: { delivered: 'written to terminal; no application acknowledgement implied',
|
|
164
|
+
stored: 'durable reply to a nonterminal integration principal; never a terminal write',
|
|
164
165
|
ack: 'explicit response_kind=ack', result: 'explicit response_kind=result; inspect task.status and evidence',
|
|
165
166
|
snapshot: 'terminal observation, never proof of reading or task completion',
|
|
166
167
|
timeout: 'stops waiting, does not cancel delivery',
|
|
@@ -208,6 +209,11 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind, re
|
|
|
208
209
|
}));
|
|
209
210
|
router.post('/reply', run(async (req, res, sessionId) => {
|
|
210
211
|
const original = ownMessage(String(req.body.messageId ?? ''), sessionId, true);
|
|
212
|
+
if (original.integrationOrigin) {
|
|
213
|
+
const reply = store.storeIntegrationReply(original.id, sessionId, req.body.content, extrasFromBody(req.body));
|
|
214
|
+
res.json({ ok: true, ...store.receipt(reply.id), integration_recorded: true });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
211
217
|
if (!original.fromSessionId) {
|
|
212
218
|
const link = original.metadata?.termdockTask;
|
|
213
219
|
if (link?.taskId && req.app.locals.collaborationTasks) {
|
|
@@ -45,6 +45,8 @@ export class CollaborationStore {
|
|
|
45
45
|
drawId;
|
|
46
46
|
onMessageQueued;
|
|
47
47
|
changeListeners = new Set();
|
|
48
|
+
runtimeGuards = new Set();
|
|
49
|
+
registerRuntimeGuard(guard) { this.runtimeGuards.add(guard); return () => { this.runtimeGuards.delete(guard); }; }
|
|
48
50
|
subscribe(listener) { this.changeListeners.add(listener); return () => { this.changeListeners.delete(listener); }; }
|
|
49
51
|
snapshotMessages() { return structuredClone(this.document.messages); }
|
|
50
52
|
peerActivity = new Map();
|
|
@@ -318,16 +320,27 @@ export class CollaborationStore {
|
|
|
318
320
|
throw new Error('消息类型无效');
|
|
319
321
|
const extras = validateExtras(input);
|
|
320
322
|
const content = input.content;
|
|
323
|
+
if (typeof content !== 'string')
|
|
324
|
+
throw new CollaborationError('INVALID_MESSAGE', 'Message content must be text');
|
|
321
325
|
if (Buffer.byteLength(content) > COLLAB_LIMITS.message_bytes)
|
|
322
326
|
throw new CollaborationError('MESSAGE_TOO_LARGE', `Message exceeds ${COLLAB_LIMITS.message_bytes} UTF-8 bytes`, 413);
|
|
323
327
|
if (!content.trim())
|
|
324
328
|
throw new Error('消息不能为空');
|
|
329
|
+
for (const id of input.toSessionIds)
|
|
330
|
+
for (const guard of this.runtimeGuards)
|
|
331
|
+
guard(id);
|
|
325
332
|
const recipients = Array.from(new Set(input.toSessionIds)).filter((id) => group.sessionIds.includes(id) && id !== input.fromSessionId);
|
|
326
333
|
if (recipients.length === 0)
|
|
327
334
|
throw new Error('没有有效的接收会话');
|
|
328
335
|
const now = Date.now();
|
|
329
|
-
const key = input.idempotencyKey ? JSON.stringify([input.groupId, input.fromSessionId, input.idempotencyKey]) : null;
|
|
330
|
-
const
|
|
336
|
+
const key = input.idempotencyKey ? JSON.stringify([input.groupId, input.integrationOrigin ? `principal:${input.integrationOrigin.integrationId}` : input.fromSessionId, input.idempotencyKey]) : null;
|
|
337
|
+
const payload = [recipients.slice().sort(), input.kind, content, input.threadId ?? null, input.replyTo ?? null, extras.responseKind, extras.metadata, extras.task, extras.expiresAt];
|
|
338
|
+
if (input.integrationOrigin) {
|
|
339
|
+
if (input.fromSessionId !== null || group.federated || !/^[a-zA-Z0-9._-]{1,80}$/.test(input.integrationOrigin.integrationId))
|
|
340
|
+
throw new CollaborationError('INVALID_INTEGRATION_MESSAGE', 'Integration messages require an authenticated local nonterminal source');
|
|
341
|
+
payload.push(input.integrationOrigin);
|
|
342
|
+
}
|
|
343
|
+
const hash = crypto.createHash('sha256').update(stableJson(payload)).digest('hex');
|
|
331
344
|
const previous = key ? this.document.idempotency?.[key] : null;
|
|
332
345
|
if (previous && previous.expiresAt > now) {
|
|
333
346
|
if (previous.hash !== hash)
|
|
@@ -340,6 +353,7 @@ export class CollaborationStore {
|
|
|
340
353
|
const taken = this.takenIds();
|
|
341
354
|
const threadId = input.threadId?.trim() || newCollaborationId(taken, this.drawId);
|
|
342
355
|
const messages = recipients.map((toSessionId) => ({
|
|
356
|
+
...(input.integrationOrigin ? { integrationOrigin: structuredClone(input.integrationOrigin) } : {}),
|
|
343
357
|
idempotencyKey: input.idempotencyKey, responseKind: extras.responseKind, metadata: extras.metadata, task: extras.task, expiresAt: extras.expiresAt,
|
|
344
358
|
sequence: this.nextSequence(),
|
|
345
359
|
id: newCollaborationId(taken, this.drawId), groupId: group.id, fromSessionId: input.fromSessionId,
|
|
@@ -353,10 +367,51 @@ export class CollaborationStore {
|
|
|
353
367
|
this.document.messages.push(...messages);
|
|
354
368
|
if (key)
|
|
355
369
|
(this.document.idempotency ??= {})[key] = { hash, ids: messages.map((message) => message.id), expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
|
|
356
|
-
this.persist();
|
|
370
|
+
this.persist(!!input.integrationOrigin);
|
|
357
371
|
queueMicrotask(() => this.onMessageQueued?.());
|
|
358
372
|
return messages;
|
|
359
373
|
}
|
|
374
|
+
/** An original recipient can persist a reply to a nonterminal integration.
|
|
375
|
+
* This is a stored fact, not a fake terminal write or a new task. */
|
|
376
|
+
storeIntegrationReply(originalId, sessionId, content, input) {
|
|
377
|
+
const original = this.getMessage(originalId), group = original && this.getGroup(original.groupId);
|
|
378
|
+
if (!original?.integrationOrigin || original.fromSessionId !== null || original.toSessionId !== sessionId
|
|
379
|
+
|| !group || group.deleted || group.federated || !group.sessionIds.includes(sessionId))
|
|
380
|
+
throw new CollaborationError('NOT_MESSAGE_RECIPIENT', 'Only the original local recipient can reply to this integration message', 403);
|
|
381
|
+
const extras = validateExtras(input);
|
|
382
|
+
if (extras.task || extras.metadata?.termdockTask)
|
|
383
|
+
throw new CollaborationError('INVALID_INTEGRATION_REPLY', 'Ordinary integration replies cannot carry task envelopes');
|
|
384
|
+
if (!extras.idempotencyKey)
|
|
385
|
+
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', 'Integration replies require an idempotency key');
|
|
386
|
+
if (typeof content !== 'string' || !content.trim())
|
|
387
|
+
throw new CollaborationError('INVALID_MESSAGE', 'Reply content must not be empty');
|
|
388
|
+
if (Buffer.byteLength(content) > COLLAB_LIMITS.message_bytes)
|
|
389
|
+
throw new CollaborationError('MESSAGE_TOO_LARGE', 'Reply exceeds 1 MiB', 413);
|
|
390
|
+
if (extras.expiresAt !== undefined && extras.expiresAt !== null)
|
|
391
|
+
throw new CollaborationError('STORED_REPLY_CANNOT_EXPIRE', 'A persisted reply is not a pending delivery and cannot expire');
|
|
392
|
+
const principalId = original.integrationOrigin.integrationId;
|
|
393
|
+
const key = JSON.stringify([original.groupId, `integration-reply:${principalId}:${sessionId}`, extras.idempotencyKey]);
|
|
394
|
+
const hash = crypto.createHash('sha256').update(stableJson([original.id, content, extras.responseKind, extras.metadata, extras.task])).digest('hex');
|
|
395
|
+
const previous = this.document.idempotency?.[key];
|
|
396
|
+
if (previous && previous.expiresAt > Date.now()) {
|
|
397
|
+
if (previous.hash !== hash)
|
|
398
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', 'Reply key was already used with different content', 409);
|
|
399
|
+
const reply = this.getMessage(previous.ids[0]);
|
|
400
|
+
if (!reply)
|
|
401
|
+
throw new CollaborationError('IDEMPOTENCY_RECORD_GONE', 'Original reply is no longer retained', 409);
|
|
402
|
+
return reply;
|
|
403
|
+
}
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
const reply = { ...extras, id: newCollaborationId(this.takenIds(), this.drawId), sequence: this.nextSequence(),
|
|
406
|
+
groupId: original.groupId, fromSessionId: sessionId, toSessionId: `principal:${principalId}`, toPrincipalId: principalId,
|
|
407
|
+
kind: 'reply', content, threadId: original.threadId, replyTo: original.id, status: 'stored', createdAt: now, storedAt: now, deliveredAt: null, readAt: null };
|
|
408
|
+
if (Buffer.byteLength(JSON.stringify(reply)) > COLLAB_LIMITS.wire_bytes)
|
|
409
|
+
throw new CollaborationError('MESSAGE_WIRE_TOO_LARGE', 'Encoded reply exceeds the wire limit', 413);
|
|
410
|
+
this.document.messages.push(reply);
|
|
411
|
+
(this.document.idempotency ??= {})[key] = { hash, ids: [reply.id], expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
|
|
412
|
+
this.persist(true);
|
|
413
|
+
return reply;
|
|
414
|
+
}
|
|
360
415
|
listMessages(groupId, limit = 200) {
|
|
361
416
|
return this.document.messages.filter((message) => message.groupId === groupId).slice(-Math.max(1, Math.min(limit, 500)));
|
|
362
417
|
}
|
|
@@ -414,7 +469,7 @@ export class CollaborationStore {
|
|
|
414
469
|
const now = Date.now();
|
|
415
470
|
const changed = [];
|
|
416
471
|
this.document.messages = this.document.messages.map((message) => {
|
|
417
|
-
if (!ids.has(message.id) || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
|
|
472
|
+
if (!ids.has(message.id) || message.status === 'stored' || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
|
|
418
473
|
return message;
|
|
419
474
|
const updated = status === 'read'
|
|
420
475
|
? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource, deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
|
|
@@ -499,7 +554,7 @@ export class CollaborationStore {
|
|
|
499
554
|
if (!message || typeof message !== 'object')
|
|
500
555
|
continue;
|
|
501
556
|
const group = this.getGroup(message.groupId);
|
|
502
|
-
if (!group?.federated || group.deleted || !group.sessionIds.includes(message.toSessionId)
|
|
557
|
+
if (!group?.federated || group.deleted || message.status === 'stored' || message.integrationOrigin || message.toPrincipalId || !group.sessionIds.includes(message.toSessionId)
|
|
503
558
|
|| (message.fromSessionId !== null && !group.sessionIds.includes(message.fromSessionId))
|
|
504
559
|
|| typeof message.id !== 'string' || typeof message.threadId !== 'string' || !Number.isFinite(message.createdAt)
|
|
505
560
|
|| typeof message.content !== 'string'
|
|
@@ -581,15 +636,18 @@ export class CollaborationStore {
|
|
|
581
636
|
const message = this.getMessage(id);
|
|
582
637
|
if (!message)
|
|
583
638
|
throw new CollaborationError('MESSAGE_NOT_FOUND', 'Message not found or no longer retained', 404);
|
|
584
|
-
const replies = this.document.messages.filter(
|
|
639
|
+
const replies = this.document.messages.filter(reply => reply.replyTo === id
|
|
640
|
+
&& (message.toPrincipalId ? reply.integrationOrigin?.integrationId === message.toPrincipalId : reply.fromSessionId === message.toSessionId)
|
|
641
|
+
&& (message.integrationOrigin ? reply.toPrincipalId === message.integrationOrigin.integrationId : reply.toSessionId === message.fromSessionId));
|
|
585
642
|
const diagnostic = this.diagnostic(id);
|
|
586
643
|
const facts = terminalMessage(message, raw);
|
|
587
|
-
return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.createdAt,
|
|
644
|
+
return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.status === 'stored' ? null : message.createdAt, stored_at: message.storedAt ?? null,
|
|
645
|
+
from_principal_id: message.integrationOrigin?.integrationId ?? null, to_principal_id: message.toPrincipalId ?? null,
|
|
588
646
|
delivered_at: facts.deliveredAt, expires_at: message.expiresAt ?? null,
|
|
589
|
-
failure_reason: message.failureReason ?? null, delivery_semantics: facts.deliverySource ?? (facts.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'),
|
|
647
|
+
failure_reason: message.failureReason ?? null, delivery_semantics: message.status === 'stored' ? 'durable_storage' : facts.deliverySource ?? (facts.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'),
|
|
590
648
|
snapshot: message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null,
|
|
591
649
|
delivery: { status: facts.status,
|
|
592
|
-
stage: facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
|
|
650
|
+
stage: message.status === 'stored' ? 'stored' : facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
|
|
593
651
|
remote_received_at: diagnostic?.remote_received_at ?? null, delivered_at: facts.deliveredAt, error: message.failureReason ?? diagnostic?.last_error ?? null },
|
|
594
652
|
reply: { status: replies.length ? 'received' : 'pending', ack_at: replies.find(reply => reply.responseKind === 'ack')?.createdAt ?? null, reply_ids: replies.map(reply => reply.id) },
|
|
595
653
|
idempotency_key: message.idempotencyKey ?? null,
|
|
@@ -725,7 +783,7 @@ export class CollaborationStore {
|
|
|
725
783
|
this.persist();
|
|
726
784
|
return { message_id: fragment.message_id, received, total: state.total, complete: received === state.total };
|
|
727
785
|
}
|
|
728
|
-
persist() {
|
|
786
|
+
persist(durable = false) {
|
|
729
787
|
const now = Date.now();
|
|
730
788
|
for (const [key, record] of Object.entries(this.document.idempotency ?? {}))
|
|
731
789
|
if (record.expiresAt <= now)
|
|
@@ -744,9 +802,26 @@ export class CollaborationStore {
|
|
|
744
802
|
try {
|
|
745
803
|
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
746
804
|
const serialized = JSON.stringify(this.document);
|
|
747
|
-
fs.
|
|
805
|
+
const descriptor = fs.openSync(temporaryPath, 'w', 0o600);
|
|
806
|
+
try {
|
|
807
|
+
fs.writeFileSync(descriptor, serialized);
|
|
808
|
+
if (durable)
|
|
809
|
+
fs.fsyncSync(descriptor);
|
|
810
|
+
}
|
|
811
|
+
finally {
|
|
812
|
+
fs.closeSync(descriptor);
|
|
813
|
+
}
|
|
748
814
|
fs.renameSync(temporaryPath, this.filePath);
|
|
749
815
|
this.persistedDocument = serialized;
|
|
816
|
+
if (durable) {
|
|
817
|
+
const directory = fs.openSync(path.dirname(this.filePath), 'r');
|
|
818
|
+
try {
|
|
819
|
+
fs.fsyncSync(directory);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
fs.closeSync(directory);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
750
825
|
for (const listener of this.changeListeners) {
|
|
751
826
|
try {
|
|
752
827
|
listener();
|
|
@@ -268,6 +268,8 @@ export class CollaborationTaskService {
|
|
|
268
268
|
}
|
|
269
269
|
receipt(messageId) {
|
|
270
270
|
const receipt = this.messages.receipt(messageId);
|
|
271
|
+
if (receipt.status === 'stored')
|
|
272
|
+
throw new CollaborationError('INVALID_TASK_DELIVERY', 'A nonterminal reply cannot be a task delivery');
|
|
271
273
|
return { messageId, status: receipt.status, deliveredAt: receipt.delivered_at, error: receipt.delivery.error };
|
|
272
274
|
}
|
|
273
275
|
start() { this.timer = setInterval(() => void this.flush(), 1500); this.timer.unref(); void this.flush(); }
|
|
@@ -4,16 +4,28 @@ import http from 'node:http';
|
|
|
4
4
|
import { INTEGRATION_PROTOCOL } from './integrationStore.js';
|
|
5
5
|
import { integrationSocketPath } from './integrationServer.js';
|
|
6
6
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
7
|
-
import { executeCollaborationCommand, duration } from './collaborationCli.js';
|
|
7
|
+
import { COLLAB_HELP, executeCollaborationCommand, duration } from './collaborationCli.js';
|
|
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
|
+
resume-configuration <TD-session-id> (read current/recorded fingerprints; no mutation)
|
|
12
|
+
confirm-resume <TD-session-id> --generation <N> --fingerprint <sha256>
|
|
13
|
+
Administrator explicitly authorizes the current launcher/plugin recovery semantics.
|
|
14
|
+
This never proves unknown historical file contents or modifies Agent configuration.
|
|
15
|
+
Old records need this confirmation before release; new records capture a fingerprint.
|
|
16
|
+
Later changes fail closed. Grant session.release separately for runtime reclamation.
|
|
17
|
+
Minimum CLI/running service 1.4.309; capabilities.session_resume_configuration=true.
|
|
18
|
+
Errors: RESUME_CONFIGURATION_CHANGED, EXACT_RESUME_UNSUPPORTED,
|
|
19
|
+
SESSION_GENERATION_MISMATCH, SESSION_OPERATION_IN_PROGRESS, LAUNCHER_UNAVAILABLE.
|
|
11
20
|
diagnostics <TD-session-id> (read the last restore check; administrator only)
|
|
12
21
|
create --file <policy.json> --credential-file <private-output.json>
|
|
13
22
|
update --file <policy.json> (same identity/group; preserves the credential)
|
|
14
23
|
revoke <principal-id>
|
|
15
24
|
Credentials are saved as a private file and never printed. Workers set
|
|
16
25
|
TERMDOCK_INTEGRATION_CREDENTIAL_FILE, then use td collab --principal <id> …
|
|
26
|
+
Ordinary messages: permissions message.send, message.read, events.read;
|
|
27
|
+
td collab --principal <id> --help lists send/reply/history and event examples.
|
|
28
|
+
No task or user acceptance is required for a message-driven integration.
|
|
17
29
|
Only the local administrator can provision/revoke grants. See docs/integrations.md.`;
|
|
18
30
|
export function readIntegrationCredential(file, principalId) {
|
|
19
31
|
const info = fs.lstatSync(file);
|
|
@@ -55,11 +67,35 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
55
67
|
let reserved;
|
|
56
68
|
try {
|
|
57
69
|
const action = argv[0] ?? 'help', options = {};
|
|
58
|
-
if (action === 'help' ||
|
|
70
|
+
if (action === 'help' || argv.includes('--help')) {
|
|
59
71
|
write(INTEGRATION_HELP);
|
|
60
72
|
return 0;
|
|
61
73
|
}
|
|
62
74
|
const socketPath = integrationSocketPath(port);
|
|
75
|
+
if (action === 'resume-configuration' || action === 'confirm-resume') {
|
|
76
|
+
const id = argv[1];
|
|
77
|
+
if (!id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id))
|
|
78
|
+
throw new Error('A full TD session id is required');
|
|
79
|
+
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
80
|
+
if (capabilities.session_resume_configuration !== true)
|
|
81
|
+
throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must be at least 1.4.309', 409);
|
|
82
|
+
let body;
|
|
83
|
+
if (action === 'confirm-resume') {
|
|
84
|
+
const args = {};
|
|
85
|
+
for (let i = 2; i < argv.length; i += 2) {
|
|
86
|
+
if (!['--generation', '--fingerprint'].includes(argv[i]) || !argv[i + 1] || args[argv[i]])
|
|
87
|
+
throw new Error('confirm-resume requires --generation and --fingerprint');
|
|
88
|
+
args[argv[i]] = argv[i + 1];
|
|
89
|
+
}
|
|
90
|
+
if (!/^[1-9][0-9]*$/.test(args['--generation'] ?? '') || !Number.isSafeInteger(Number(args['--generation'])) || !/^[a-f0-9]{64}$/.test(args['--fingerprint'] ?? ''))
|
|
91
|
+
throw new Error('Invalid generation or fingerprint');
|
|
92
|
+
body = { generation: Number(args['--generation']), fingerprint: args['--fingerprint'] };
|
|
93
|
+
}
|
|
94
|
+
else if (argv.length !== 2)
|
|
95
|
+
throw new Error('resume-configuration requires one TD session id');
|
|
96
|
+
write(JSON.stringify(parsed(await request(socketPath, body ? 'POST' : 'GET', `/admin/sessions/${encodeURIComponent(id)}/resume-configuration`, body, headers(undefined, adminToken)))));
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
63
99
|
if (action === 'diagnostics') {
|
|
64
100
|
if (argv.length !== 2 || !/^[a-zA-Z0-9._-]{1,80}$/.test(argv[1]))
|
|
65
101
|
throw new Error('diagnostics requires one TD session id');
|
|
@@ -131,6 +167,10 @@ export async function revokeIntegration(id, port, adminToken, write) {
|
|
|
131
167
|
}
|
|
132
168
|
export async function runIntegrationCollab(command, port, io) {
|
|
133
169
|
try {
|
|
170
|
+
if (command.action === 'help') {
|
|
171
|
+
io.write(COLLAB_HELP);
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
134
174
|
const principalId = String(command.options.principal), file = process.env.TERMDOCK_INTEGRATION_CREDENTIAL_FILE;
|
|
135
175
|
if (!file)
|
|
136
176
|
throw new CollaborationError('INTEGRATION_CREDENTIAL_REQUIRED', 'Set TERMDOCK_INTEGRATION_CREDENTIAL_FILE to a private credential file', 401);
|
|
@@ -154,14 +194,20 @@ export async function runIntegrationCollab(command, port, io) {
|
|
|
154
194
|
return subscribe(socketPath, `/events?${params}`, credential, command.options.timeout ? duration(String(command.options.timeout)) : undefined, io.write);
|
|
155
195
|
}
|
|
156
196
|
if (command.action === 'session') {
|
|
157
|
-
|
|
197
|
+
if (command.operation === 'release' && (capability.session_runtime_release !== true || capability.session_lifecycle_generation !== true || capability.stable_session_identity !== true))
|
|
198
|
+
throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must support the lifecycle contract (minimum 1.4.309)', 409);
|
|
199
|
+
const route = command.operation === 'create' ? '/sessions' : `/sessions/${encodeURIComponent(command.target)}${['restore', 'release'].includes(command.operation) ? `/${command.operation}` : ''}`;
|
|
158
200
|
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'] }
|
|
159
|
-
: command.operation === '
|
|
201
|
+
: ['restore', 'release'].includes(command.operation) ? { ...(command.operation === 'release' ? { generation: Number(command.options.generation) } : {}), idempotency_key: command.options['idempotency-key'] } : undefined;
|
|
160
202
|
io.write(JSON.stringify(parsed(await request(socketPath, command.operation === 'get' ? 'GET' : 'POST', route, body, headers(credential)))));
|
|
161
203
|
return 0;
|
|
162
204
|
}
|
|
163
|
-
|
|
164
|
-
|
|
205
|
+
const messageCapability = command.action === 'send' ? 'integration_message_send' : command.action === 'reply' ? 'integration_message_reply'
|
|
206
|
+
: command.action === 'message' && command.operation === 'list' ? 'integration_message_history' : null;
|
|
207
|
+
if (messageCapability && capability[messageCapability] !== true)
|
|
208
|
+
throw new CollaborationError('INTEGRATION_MESSAGING_UNSUPPORTED', 'CLI and running service must support integration messaging (minimum 1.4.308); upgrade both and read capabilities', 409);
|
|
209
|
+
if (!['task', 'message', 'send', 'reply'].includes(command.action) || command.action === 'message' && !['get', 'list'].includes(command.operation))
|
|
210
|
+
throw new CollaborationError('INTEGRATION_OPERATION_DENIED', 'Command is not exposed to integration identities; see td collab --principal <id> --help', 403);
|
|
165
211
|
return executeCollaborationCommand(command, {}, { ...io, request: async (method, endpoint, body, timeout) => {
|
|
166
212
|
const origin = { source: command.options.source, externalActor: command.options['external-actor'] ? JSON.parse(String(command.options['external-actor'])) : undefined,
|
|
167
213
|
externalMessageId: command.options['external-message-id'], metadata: command.options.metadata ? JSON.parse(String(command.options.metadata)) : undefined };
|