termdock 1.4.307 → 1.4.308
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 +56 -12
- 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 +81 -11
- package/dist/server/agent/collaborationTaskService.js +2 -0
- package/dist/server/agent/integrationCli.js +15 -4
- package/dist/server/agent/integrationServer.js +85 -8
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/cli.js +1 -1
- package/docs/integrations.md +89 -4
- 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
|
@@ -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) {
|
|
@@ -318,6 +318,8 @@ export class CollaborationStore {
|
|
|
318
318
|
throw new Error('消息类型无效');
|
|
319
319
|
const extras = validateExtras(input);
|
|
320
320
|
const content = input.content;
|
|
321
|
+
if (typeof content !== 'string')
|
|
322
|
+
throw new CollaborationError('INVALID_MESSAGE', 'Message content must be text');
|
|
321
323
|
if (Buffer.byteLength(content) > COLLAB_LIMITS.message_bytes)
|
|
322
324
|
throw new CollaborationError('MESSAGE_TOO_LARGE', `Message exceeds ${COLLAB_LIMITS.message_bytes} UTF-8 bytes`, 413);
|
|
323
325
|
if (!content.trim())
|
|
@@ -326,8 +328,14 @@ export class CollaborationStore {
|
|
|
326
328
|
if (recipients.length === 0)
|
|
327
329
|
throw new Error('没有有效的接收会话');
|
|
328
330
|
const now = Date.now();
|
|
329
|
-
const key = input.idempotencyKey ? JSON.stringify([input.groupId, input.fromSessionId, input.idempotencyKey]) : null;
|
|
330
|
-
const
|
|
331
|
+
const key = input.idempotencyKey ? JSON.stringify([input.groupId, input.integrationOrigin ? `principal:${input.integrationOrigin.integrationId}` : input.fromSessionId, input.idempotencyKey]) : null;
|
|
332
|
+
const payload = [recipients.slice().sort(), input.kind, content, input.threadId ?? null, input.replyTo ?? null, extras.responseKind, extras.metadata, extras.task, extras.expiresAt];
|
|
333
|
+
if (input.integrationOrigin) {
|
|
334
|
+
if (input.fromSessionId !== null || group.federated || !/^[a-zA-Z0-9._-]{1,80}$/.test(input.integrationOrigin.integrationId))
|
|
335
|
+
throw new CollaborationError('INVALID_INTEGRATION_MESSAGE', 'Integration messages require an authenticated local nonterminal source');
|
|
336
|
+
payload.push(input.integrationOrigin);
|
|
337
|
+
}
|
|
338
|
+
const hash = crypto.createHash('sha256').update(stableJson(payload)).digest('hex');
|
|
331
339
|
const previous = key ? this.document.idempotency?.[key] : null;
|
|
332
340
|
if (previous && previous.expiresAt > now) {
|
|
333
341
|
if (previous.hash !== hash)
|
|
@@ -340,6 +348,7 @@ export class CollaborationStore {
|
|
|
340
348
|
const taken = this.takenIds();
|
|
341
349
|
const threadId = input.threadId?.trim() || newCollaborationId(taken, this.drawId);
|
|
342
350
|
const messages = recipients.map((toSessionId) => ({
|
|
351
|
+
...(input.integrationOrigin ? { integrationOrigin: structuredClone(input.integrationOrigin) } : {}),
|
|
343
352
|
idempotencyKey: input.idempotencyKey, responseKind: extras.responseKind, metadata: extras.metadata, task: extras.task, expiresAt: extras.expiresAt,
|
|
344
353
|
sequence: this.nextSequence(),
|
|
345
354
|
id: newCollaborationId(taken, this.drawId), groupId: group.id, fromSessionId: input.fromSessionId,
|
|
@@ -353,10 +362,51 @@ export class CollaborationStore {
|
|
|
353
362
|
this.document.messages.push(...messages);
|
|
354
363
|
if (key)
|
|
355
364
|
(this.document.idempotency ??= {})[key] = { hash, ids: messages.map((message) => message.id), expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
|
|
356
|
-
this.persist();
|
|
365
|
+
this.persist(!!input.integrationOrigin);
|
|
357
366
|
queueMicrotask(() => this.onMessageQueued?.());
|
|
358
367
|
return messages;
|
|
359
368
|
}
|
|
369
|
+
/** An original recipient can persist a reply to a nonterminal integration.
|
|
370
|
+
* This is a stored fact, not a fake terminal write or a new task. */
|
|
371
|
+
storeIntegrationReply(originalId, sessionId, content, input) {
|
|
372
|
+
const original = this.getMessage(originalId), group = original && this.getGroup(original.groupId);
|
|
373
|
+
if (!original?.integrationOrigin || original.fromSessionId !== null || original.toSessionId !== sessionId
|
|
374
|
+
|| !group || group.deleted || group.federated || !group.sessionIds.includes(sessionId))
|
|
375
|
+
throw new CollaborationError('NOT_MESSAGE_RECIPIENT', 'Only the original local recipient can reply to this integration message', 403);
|
|
376
|
+
const extras = validateExtras(input);
|
|
377
|
+
if (extras.task || extras.metadata?.termdockTask)
|
|
378
|
+
throw new CollaborationError('INVALID_INTEGRATION_REPLY', 'Ordinary integration replies cannot carry task envelopes');
|
|
379
|
+
if (!extras.idempotencyKey)
|
|
380
|
+
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', 'Integration replies require an idempotency key');
|
|
381
|
+
if (typeof content !== 'string' || !content.trim())
|
|
382
|
+
throw new CollaborationError('INVALID_MESSAGE', 'Reply content must not be empty');
|
|
383
|
+
if (Buffer.byteLength(content) > COLLAB_LIMITS.message_bytes)
|
|
384
|
+
throw new CollaborationError('MESSAGE_TOO_LARGE', 'Reply exceeds 1 MiB', 413);
|
|
385
|
+
if (extras.expiresAt !== undefined && extras.expiresAt !== null)
|
|
386
|
+
throw new CollaborationError('STORED_REPLY_CANNOT_EXPIRE', 'A persisted reply is not a pending delivery and cannot expire');
|
|
387
|
+
const principalId = original.integrationOrigin.integrationId;
|
|
388
|
+
const key = JSON.stringify([original.groupId, `integration-reply:${principalId}:${sessionId}`, extras.idempotencyKey]);
|
|
389
|
+
const hash = crypto.createHash('sha256').update(stableJson([original.id, content, extras.responseKind, extras.metadata, extras.task])).digest('hex');
|
|
390
|
+
const previous = this.document.idempotency?.[key];
|
|
391
|
+
if (previous && previous.expiresAt > Date.now()) {
|
|
392
|
+
if (previous.hash !== hash)
|
|
393
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', 'Reply key was already used with different content', 409);
|
|
394
|
+
const reply = this.getMessage(previous.ids[0]);
|
|
395
|
+
if (!reply)
|
|
396
|
+
throw new CollaborationError('IDEMPOTENCY_RECORD_GONE', 'Original reply is no longer retained', 409);
|
|
397
|
+
return reply;
|
|
398
|
+
}
|
|
399
|
+
const now = Date.now();
|
|
400
|
+
const reply = { ...extras, id: newCollaborationId(this.takenIds(), this.drawId), sequence: this.nextSequence(),
|
|
401
|
+
groupId: original.groupId, fromSessionId: sessionId, toSessionId: `principal:${principalId}`, toPrincipalId: principalId,
|
|
402
|
+
kind: 'reply', content, threadId: original.threadId, replyTo: original.id, status: 'stored', createdAt: now, storedAt: now, deliveredAt: null, readAt: null };
|
|
403
|
+
if (Buffer.byteLength(JSON.stringify(reply)) > COLLAB_LIMITS.wire_bytes)
|
|
404
|
+
throw new CollaborationError('MESSAGE_WIRE_TOO_LARGE', 'Encoded reply exceeds the wire limit', 413);
|
|
405
|
+
this.document.messages.push(reply);
|
|
406
|
+
(this.document.idempotency ??= {})[key] = { hash, ids: [reply.id], expiresAt: now + COLLAB_LIMITS.idempotency_retention_ms };
|
|
407
|
+
this.persist(true);
|
|
408
|
+
return reply;
|
|
409
|
+
}
|
|
360
410
|
listMessages(groupId, limit = 200) {
|
|
361
411
|
return this.document.messages.filter((message) => message.groupId === groupId).slice(-Math.max(1, Math.min(limit, 500)));
|
|
362
412
|
}
|
|
@@ -414,7 +464,7 @@ export class CollaborationStore {
|
|
|
414
464
|
const now = Date.now();
|
|
415
465
|
const changed = [];
|
|
416
466
|
this.document.messages = this.document.messages.map((message) => {
|
|
417
|
-
if (!ids.has(message.id) || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
|
|
467
|
+
if (!ids.has(message.id) || message.status === 'stored' || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
|
|
418
468
|
return message;
|
|
419
469
|
const updated = status === 'read'
|
|
420
470
|
? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource, deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
|
|
@@ -499,7 +549,7 @@ export class CollaborationStore {
|
|
|
499
549
|
if (!message || typeof message !== 'object')
|
|
500
550
|
continue;
|
|
501
551
|
const group = this.getGroup(message.groupId);
|
|
502
|
-
if (!group?.federated || group.deleted || !group.sessionIds.includes(message.toSessionId)
|
|
552
|
+
if (!group?.federated || group.deleted || message.status === 'stored' || message.integrationOrigin || message.toPrincipalId || !group.sessionIds.includes(message.toSessionId)
|
|
503
553
|
|| (message.fromSessionId !== null && !group.sessionIds.includes(message.fromSessionId))
|
|
504
554
|
|| typeof message.id !== 'string' || typeof message.threadId !== 'string' || !Number.isFinite(message.createdAt)
|
|
505
555
|
|| typeof message.content !== 'string'
|
|
@@ -581,15 +631,18 @@ export class CollaborationStore {
|
|
|
581
631
|
const message = this.getMessage(id);
|
|
582
632
|
if (!message)
|
|
583
633
|
throw new CollaborationError('MESSAGE_NOT_FOUND', 'Message not found or no longer retained', 404);
|
|
584
|
-
const replies = this.document.messages.filter(
|
|
634
|
+
const replies = this.document.messages.filter(reply => reply.replyTo === id
|
|
635
|
+
&& (message.toPrincipalId ? reply.integrationOrigin?.integrationId === message.toPrincipalId : reply.fromSessionId === message.toSessionId)
|
|
636
|
+
&& (message.integrationOrigin ? reply.toPrincipalId === message.integrationOrigin.integrationId : reply.toSessionId === message.fromSessionId));
|
|
585
637
|
const diagnostic = this.diagnostic(id);
|
|
586
638
|
const facts = terminalMessage(message, raw);
|
|
587
|
-
return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.createdAt,
|
|
639
|
+
return { message_id: id, thread_id: message.threadId, status: facts.status, queued_at: message.status === 'stored' ? null : message.createdAt, stored_at: message.storedAt ?? null,
|
|
640
|
+
from_principal_id: message.integrationOrigin?.integrationId ?? null, to_principal_id: message.toPrincipalId ?? null,
|
|
588
641
|
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'),
|
|
642
|
+
failure_reason: message.failureReason ?? null, delivery_semantics: message.status === 'stored' ? 'durable_storage' : facts.deliverySource ?? (facts.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'),
|
|
590
643
|
snapshot: message.snapshot ? (raw ? message.snapshot : plainCollaborationSnapshot(message.snapshot)) : null,
|
|
591
644
|
delivery: { status: facts.status,
|
|
592
|
-
stage: facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
|
|
645
|
+
stage: message.status === 'stored' ? 'stored' : facts.deliveredAt !== null ? 'terminal_written' : diagnostic?.remote_received_at ? 'remote_received' : 'queued',
|
|
593
646
|
remote_received_at: diagnostic?.remote_received_at ?? null, delivered_at: facts.deliveredAt, error: message.failureReason ?? diagnostic?.last_error ?? null },
|
|
594
647
|
reply: { status: replies.length ? 'received' : 'pending', ack_at: replies.find(reply => reply.responseKind === 'ack')?.createdAt ?? null, reply_ids: replies.map(reply => reply.id) },
|
|
595
648
|
idempotency_key: message.idempotencyKey ?? null,
|
|
@@ -725,7 +778,7 @@ export class CollaborationStore {
|
|
|
725
778
|
this.persist();
|
|
726
779
|
return { message_id: fragment.message_id, received, total: state.total, complete: received === state.total };
|
|
727
780
|
}
|
|
728
|
-
persist() {
|
|
781
|
+
persist(durable = false) {
|
|
729
782
|
const now = Date.now();
|
|
730
783
|
for (const [key, record] of Object.entries(this.document.idempotency ?? {}))
|
|
731
784
|
if (record.expiresAt <= now)
|
|
@@ -744,9 +797,26 @@ export class CollaborationStore {
|
|
|
744
797
|
try {
|
|
745
798
|
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
746
799
|
const serialized = JSON.stringify(this.document);
|
|
747
|
-
fs.
|
|
800
|
+
const descriptor = fs.openSync(temporaryPath, 'w', 0o600);
|
|
801
|
+
try {
|
|
802
|
+
fs.writeFileSync(descriptor, serialized);
|
|
803
|
+
if (durable)
|
|
804
|
+
fs.fsyncSync(descriptor);
|
|
805
|
+
}
|
|
806
|
+
finally {
|
|
807
|
+
fs.closeSync(descriptor);
|
|
808
|
+
}
|
|
748
809
|
fs.renameSync(temporaryPath, this.filePath);
|
|
749
810
|
this.persistedDocument = serialized;
|
|
811
|
+
if (durable) {
|
|
812
|
+
const directory = fs.openSync(path.dirname(this.filePath), 'r');
|
|
813
|
+
try {
|
|
814
|
+
fs.fsyncSync(directory);
|
|
815
|
+
}
|
|
816
|
+
finally {
|
|
817
|
+
fs.closeSync(directory);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
750
820
|
for (const listener of this.changeListeners) {
|
|
751
821
|
try {
|
|
752
822
|
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,7 +4,7 @@ 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
|
|
@@ -14,6 +14,9 @@ export const INTEGRATION_HELP = `td integration — local scoped integration adm
|
|
|
14
14
|
revoke <principal-id>
|
|
15
15
|
Credentials are saved as a private file and never printed. Workers set
|
|
16
16
|
TERMDOCK_INTEGRATION_CREDENTIAL_FILE, then use td collab --principal <id> …
|
|
17
|
+
Ordinary messages: permissions message.send, message.read, events.read;
|
|
18
|
+
td collab --principal <id> --help lists send/reply/history and event examples.
|
|
19
|
+
No task or user acceptance is required for a message-driven integration.
|
|
17
20
|
Only the local administrator can provision/revoke grants. See docs/integrations.md.`;
|
|
18
21
|
export function readIntegrationCredential(file, principalId) {
|
|
19
22
|
const info = fs.lstatSync(file);
|
|
@@ -55,7 +58,7 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
55
58
|
let reserved;
|
|
56
59
|
try {
|
|
57
60
|
const action = argv[0] ?? 'help', options = {};
|
|
58
|
-
if (action === 'help' ||
|
|
61
|
+
if (action === 'help' || argv.includes('--help')) {
|
|
59
62
|
write(INTEGRATION_HELP);
|
|
60
63
|
return 0;
|
|
61
64
|
}
|
|
@@ -131,6 +134,10 @@ export async function revokeIntegration(id, port, adminToken, write) {
|
|
|
131
134
|
}
|
|
132
135
|
export async function runIntegrationCollab(command, port, io) {
|
|
133
136
|
try {
|
|
137
|
+
if (command.action === 'help') {
|
|
138
|
+
io.write(COLLAB_HELP);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
134
141
|
const principalId = String(command.options.principal), file = process.env.TERMDOCK_INTEGRATION_CREDENTIAL_FILE;
|
|
135
142
|
if (!file)
|
|
136
143
|
throw new CollaborationError('INTEGRATION_CREDENTIAL_REQUIRED', 'Set TERMDOCK_INTEGRATION_CREDENTIAL_FILE to a private credential file', 401);
|
|
@@ -160,8 +167,12 @@ export async function runIntegrationCollab(command, port, io) {
|
|
|
160
167
|
io.write(JSON.stringify(parsed(await request(socketPath, command.operation === 'get' ? 'GET' : 'POST', route, body, headers(credential)))));
|
|
161
168
|
return 0;
|
|
162
169
|
}
|
|
163
|
-
|
|
164
|
-
|
|
170
|
+
const messageCapability = command.action === 'send' ? 'integration_message_send' : command.action === 'reply' ? 'integration_message_reply'
|
|
171
|
+
: command.action === 'message' && command.operation === 'list' ? 'integration_message_history' : null;
|
|
172
|
+
if (messageCapability && capability[messageCapability] !== true)
|
|
173
|
+
throw new CollaborationError('INTEGRATION_MESSAGING_UNSUPPORTED', 'CLI and running service must support integration messaging (minimum 1.4.308); upgrade both and read capabilities', 409);
|
|
174
|
+
if (!['task', 'message', 'send', 'reply'].includes(command.action) || command.action === 'message' && !['get', 'list'].includes(command.operation))
|
|
175
|
+
throw new CollaborationError('INTEGRATION_OPERATION_DENIED', 'Command is not exposed to integration identities; see td collab --principal <id> --help', 403);
|
|
165
176
|
return executeCollaborationCommand(command, {}, { ...io, request: async (method, endpoint, body, timeout) => {
|
|
166
177
|
const origin = { source: command.options.source, externalActor: command.options['external-actor'] ? JSON.parse(String(command.options['external-actor'])) : undefined,
|
|
167
178
|
externalMessageId: command.options['external-message-id'], metadata: command.options.metadata ? JSON.parse(String(command.options.metadata)) : undefined };
|
|
@@ -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 {
|
|
@@ -35,7 +43,7 @@ export class IntegrationRuntime {
|
|
|
35
43
|
} };
|
|
36
44
|
this.stops.push(options.messages.subscribe(changed), options.taskStore.subscribe(changed), this.sessions.subscribe(changed));
|
|
37
45
|
const app = express();
|
|
38
|
-
app.use(express.json({ limit: '
|
|
46
|
+
app.use(express.json({ limit: '2mb' }));
|
|
39
47
|
app.use((req, res, next) => {
|
|
40
48
|
if (req.headers['x-termdock-integration-protocol'] !== String(INTEGRATION_PROTOCOL)) {
|
|
41
49
|
res.status(409).json({ ...this.error(new CollaborationError('INCOMPATIBLE_INTEGRATION_PROTOCOL', 'Unsupported integration protocol', 409)), protocol: INTEGRATION_PROTOCOL });
|
|
@@ -103,11 +111,62 @@ export class IntegrationRuntime {
|
|
|
103
111
|
const event = options.taskStore.integrationRequestEvent(p.id, input.idempotencyKey, task.id);
|
|
104
112
|
return { task, event_id: event?.id ?? null, attempt_id: event?.attemptId ?? null, delivery_id: event?.deliveryId ?? null };
|
|
105
113
|
}));
|
|
114
|
+
app.post('/send', run(req => {
|
|
115
|
+
const p = principal(req);
|
|
116
|
+
this.authorize(p, 'message.send', req.body?.group_id);
|
|
117
|
+
const group = this.group(p.groupId), raw = req.body ?? {};
|
|
118
|
+
if (raw.kind && raw.kind !== 'message')
|
|
119
|
+
integrationError('INTEGRATION_OPERATION_DENIED', 'Integration send uses ordinary messages only', 403);
|
|
120
|
+
const targets = Array.isArray(raw.toSessionIds) ? raw.toSessionIds : [raw.targetSessionId];
|
|
121
|
+
if (!targets.length || targets.some((id) => typeof id !== 'string'))
|
|
122
|
+
integrationError('INVALID_MESSAGE_TARGET', 'Specify a local group member');
|
|
123
|
+
const recipients = targets.map((id) => scopedId(id, group.sessionIds, 'SESSION'));
|
|
124
|
+
let thread;
|
|
125
|
+
if (raw.thread_id)
|
|
126
|
+
thread = scopedId(raw.thread_id, options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).map(m => m.threadId), 'THREAD');
|
|
127
|
+
const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
|
|
128
|
+
integrationOrigin: this.origin(p, raw.origin), toSessionIds: recipients, kind: 'message', content: raw.message, threadId: thread });
|
|
129
|
+
this.sync();
|
|
130
|
+
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) };
|
|
131
|
+
}));
|
|
132
|
+
app.post('/reply', run(req => {
|
|
133
|
+
const p = principal(req);
|
|
134
|
+
this.authorize(p, 'message.send', req.body?.group_id);
|
|
135
|
+
const raw = req.body ?? {}, original = this.message(p, raw.messageId);
|
|
136
|
+
const target = original.toPrincipalId === p.id ? original.fromSessionId
|
|
137
|
+
: original.integrationOrigin?.integrationId === p.id ? original.toSessionId : null;
|
|
138
|
+
if (!target || !this.group(p.groupId).sessionIds.includes(target))
|
|
139
|
+
integrationError('NO_REPLY_TARGET', 'Reply requires your own message and its original live group member', 409);
|
|
140
|
+
const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
|
|
141
|
+
integrationOrigin: this.origin(p, raw.origin), toSessionIds: [target], kind: 'reply', content: raw.content, threadId: original.threadId, replyTo: original.id });
|
|
142
|
+
this.sync();
|
|
143
|
+
return { ok: true, ...options.messages.receipt(messages[0].id) };
|
|
144
|
+
}));
|
|
145
|
+
app.get('/message', run(req => {
|
|
146
|
+
const p = principal(req);
|
|
147
|
+
this.authorize(p, 'message.read', req.query.group);
|
|
148
|
+
const messages = options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).sort((a, b) => a.sequence - b.sequence);
|
|
149
|
+
const after = req.query['after-id'] ? this.message(p, String(req.query['after-id'])) : null;
|
|
150
|
+
const limit = req.query.limit === undefined ? 50 : Number(req.query.limit);
|
|
151
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200)
|
|
152
|
+
integrationError('INVALID_MESSAGE_LIMIT', 'limit must be 1..200');
|
|
153
|
+
const thread = req.query.thread ? scopedId(String(req.query.thread), messages.map(m => m.threadId), 'THREAD') : null;
|
|
154
|
+
const filtered = messages.filter(m => (!after || m.sequence > after.sequence) && (!thread || m.threadId === thread));
|
|
155
|
+
const page = [];
|
|
156
|
+
let bytes = 0;
|
|
157
|
+
for (const message of filtered.slice(0, limit)) {
|
|
158
|
+
const size = Buffer.byteLength(JSON.stringify(message));
|
|
159
|
+
if (page.length && bytes + size > 2 * 1024 * 1024)
|
|
160
|
+
break;
|
|
161
|
+
page.push(message);
|
|
162
|
+
bytes += size;
|
|
163
|
+
}
|
|
164
|
+
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' };
|
|
165
|
+
}));
|
|
106
166
|
app.get('/message/:id', run(req => {
|
|
107
|
-
const p = principal(req)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
this.authorize(p, 'task.read', message.groupId);
|
|
167
|
+
const p = principal(req);
|
|
168
|
+
this.authorize(p, p.permissions.includes('message.read') ? 'message.read' : 'task.read');
|
|
169
|
+
const message = this.message(p, req.params.id);
|
|
111
170
|
const receipt = options.messages.receipt(message.id);
|
|
112
171
|
const { snapshot: _, ...diagnostic } = receipt;
|
|
113
172
|
return { ok: true, ...diagnostic, ...(req.query.receipt_only === 'true' ? {} : { message: terminalMessage(message) }) };
|
|
@@ -149,6 +208,7 @@ export class IntegrationRuntime {
|
|
|
149
208
|
session_restore_diagnostics: true,
|
|
150
209
|
native_identity_linux_flock_owner: process.platform === 'linux',
|
|
151
210
|
background_task_records: true, task_purpose_configuration: true, explicit_execution_state: true,
|
|
211
|
+
integration_message_send: true, integration_message_reply: true, integration_message_history: true,
|
|
152
212
|
permissions: INTEGRATION_OPERATIONS };
|
|
153
213
|
}
|
|
154
214
|
error(error) {
|
|
@@ -170,6 +230,18 @@ export class IntegrationRuntime {
|
|
|
170
230
|
integrationError('TASK_NOT_FOUND', 'Task does not exist within this integration grant', 404);
|
|
171
231
|
this.authorize(p, operation, task.groupId);
|
|
172
232
|
}
|
|
233
|
+
message(p, id) {
|
|
234
|
+
const scoped = this.options.messages.snapshotMessages().filter(m => m.groupId === p.groupId);
|
|
235
|
+
const resolved = scopedId(id, scoped.map(m => m.id), 'MESSAGE');
|
|
236
|
+
return this.options.messages.getMessage(resolved);
|
|
237
|
+
}
|
|
238
|
+
messageExtras(raw) {
|
|
239
|
+
if (typeof raw.idempotency_key !== 'string' || !raw.idempotency_key.trim())
|
|
240
|
+
integrationError('IDEMPOTENCY_KEY_REQUIRED', 'Integration message mutations require an explicit idempotency key');
|
|
241
|
+
if (raw.task || raw.task_envelope || raw.metadata?.termdockTask)
|
|
242
|
+
integrationError('INTEGRATION_OPERATION_DENIED', 'Ordinary messages cannot forge task envelopes', 403);
|
|
243
|
+
return { idempotencyKey: raw.idempotency_key, responseKind: raw.response_kind, metadata: raw.metadata, expiresAt: raw.expires_at };
|
|
244
|
+
}
|
|
173
245
|
origin(p, input) {
|
|
174
246
|
const value = input && typeof input === 'object' ? input : {};
|
|
175
247
|
if (Buffer.byteLength(JSON.stringify(value)) > 16384)
|
|
@@ -215,11 +287,16 @@ export class IntegrationRuntime {
|
|
|
215
287
|
if (groups.has(message.groupId)) {
|
|
216
288
|
const link = message.metadata?.termdockTask;
|
|
217
289
|
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'
|
|
290
|
+
reply_to_event_id: link?.replyToEventId ?? null, message_id: message.id, source: message.integrationOrigin ? 'integration' : message.fromSessionId ? 'member' : 'system',
|
|
291
|
+
actor: message.integrationOrigin ? { integration_id: message.integrationOrigin.integrationId } : message.fromSessionId ? { session_id: message.fromSessionId } : null,
|
|
292
|
+
external_actor: message.integrationOrigin?.externalActor ?? null };
|
|
219
293
|
updates.push({ sourceKey: `message:${message.id}`, sourceVersion: '1', event: { ...context,
|
|
220
294
|
kind: message.kind === 'reply' ? 'message.reply' : 'message.queued', created_at: message.createdAt,
|
|
221
295
|
payload: { message_id: message.id, thread_id: message.threadId, reply_to: message.replyTo ?? null,
|
|
222
|
-
from_session_id: message.fromSessionId, to_session_id: message.
|
|
296
|
+
from_session_id: message.fromSessionId, to_session_id: message.toPrincipalId ? null : message.toSessionId,
|
|
297
|
+
to_principal_id: message.toPrincipalId ?? null, integration_origin: message.integrationOrigin ?? null,
|
|
298
|
+
response_kind: message.responseKind ?? null, stored_at: message.storedAt ?? null,
|
|
299
|
+
content: message.content, metadata: message.metadata ?? null } } });
|
|
223
300
|
const { snapshot: _, ...receipt } = this.options.messages.receipt(message.id);
|
|
224
301
|
updates.push({ sourceKey: `message-status:${message.id}`, sourceVersion: JSON.stringify(receipt), event: {
|
|
225
302
|
...context, kind: 'message.delivery', created_at: message.deliveredAt ?? message.createdAt, payload: { receipt }
|
|
@@ -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'];
|
|
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 });
|
package/dist/server/cli.js
CHANGED
|
@@ -3489,7 +3489,7 @@ async function main() {
|
|
|
3489
3489
|
}
|
|
3490
3490
|
if (options.integration) {
|
|
3491
3491
|
const argv = options.integration;
|
|
3492
|
-
if (!argv.length ||
|
|
3492
|
+
if (!argv.length || argv.includes('--help') || argv[0] === 'help') {
|
|
3493
3493
|
console.log(INTEGRATION_HELP);
|
|
3494
3494
|
return;
|
|
3495
3495
|
}
|
package/docs/integrations.md
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
**实际运行服务至少 1.4.304**。管理员恢复诊断要求 **CLI / 实际服务均至少 1.4.305**;
|
|
9
9
|
直接 exec 为 pane PID 的原生 argv 核验及可选 Linux 持锁身份要求 **实际服务至少 1.4.306**;
|
|
10
10
|
后台执行记录与用途迁移要求 **CLI / 实际服务均至少 1.4.307**;
|
|
11
|
-
|
|
11
|
+
普通消息身份发送、持久回复及消息历史要求 **CLI / 实际服务均至少 1.4.308**;
|
|
12
|
+
建议 CLI 与服务同步安装 1.4.308(既有接入命令的 CLI 最低仍为 1.4.301,诊断 CLI 最低为 1.4.305)。
|
|
12
13
|
现有 `--session`、消息投递、任务报告与自动协作入口继续兼容;旧的
|
|
13
14
|
`task create --integration` 仍表示代码集成子任务,新身份使用 `--principal`。
|
|
14
15
|
|
|
@@ -17,6 +18,90 @@ TD 提供受限身份、持久事件推送、关联咨询回复,以及会话
|
|
|
17
18
|
`complete` 是执行成员的明确交付,`accepted` 是用户明确验收;投递成功、终端快照、
|
|
18
19
|
事件 ACK 都不表示已读、任务完成或业务校验通过。
|
|
19
20
|
|
|
21
|
+
## 普通消息接入(1.4.308,推荐用于机器人与报表)
|
|
22
|
+
|
|
23
|
+
触发 Agent、追问和回帖可使用普通消息:**无需创建 task,不产生任务看板或逐期用户验收**。
|
|
24
|
+
消息负责可靠投递与关联回复;业务周期、结果版本、机器校验、外部发布状态由接入方记录。
|
|
25
|
+
只有需要 TD 任务分派、交付物、评审或人工验收时才使用后文 task;1.4.307 的 automation
|
|
26
|
+
purpose 是可选任务能力,不是机器人接入的前置要求。已有 task/attempt/session 和外部根帖
|
|
27
|
+
保持原记录,不自动迁移、重建、重投或重跑。
|
|
28
|
+
|
|
29
|
+
管理员为现有身份补权限后用 `td integration update --file /private/policy.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
"permissions": ["message.send", "message.read", "events.read", "session.create", "session.read", "session.restore"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
更新保留同一身份、组和凭据;保留实际需要的既有 task 权限以及原 launchProfiles。
|
|
36
|
+
`message.send` 授权 send/reply,`message.read` 授权组内 message get/list;它们不授予任务操作
|
|
37
|
+
或用户验收。events.read 可订阅授权组内事件。只允许该身份授权的一个本机组,拒绝跨服务目标、
|
|
38
|
+
组外成员、未授权操作与 task envelope。组中其他 principal 的消息可以按组授权读取,但不能
|
|
39
|
+
借其原消息 reply 或冒充其身份;只能向本组成员 send 新消息。
|
|
40
|
+
|
|
41
|
+
先回读实际运行服务,再检查三个能力均为 true:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
export TERMDOCK_INTEGRATION_CREDENTIAL_FILE=/private/bridge-credential.json
|
|
45
|
+
td collab --principal report-bridge capabilities
|
|
46
|
+
# cli_version / server_version >= 1.4.308, integration_protocol = 1
|
|
47
|
+
# integration_message_send / integration_message_reply / integration_message_history = true
|
|
48
|
+
|
|
49
|
+
td collab --principal report-bridge send <TD成员ID> --file /private/prompt.txt --idempotency-key run:2026-10-10
|
|
50
|
+
# 保存输出 message_id、thread_id;status=pending 表示已持久入队
|
|
51
|
+
# 可附 --source feishu --external-actor '{"id":"external-user"}' --external-message-id <external-id>
|
|
52
|
+
|
|
53
|
+
td collab --principal report-bridge events subscribe --consumer bridge-inbox --jsonl
|
|
54
|
+
# 按 event_id 去重;先将事件落盘,再 ACK 已连续持久接收的位置
|
|
55
|
+
td collab --principal report-bridge events ack <cursor> --consumer bridge-inbox
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
CLI 在发送前检查运行服务 capability;旧服务返回 `INTEGRATION_MESSAGING_UNSUPPORTED`,
|
|
59
|
+
不会降级成用户或终端成员发送。凭据只通过私有文件注入;`--principal <id> --help` 不需要
|
|
60
|
+
凭据或运行服务。建议 CLI 与实际服务保持同一版本,版本号本身不能替代能力检测。
|
|
61
|
+
|
|
62
|
+
接收 Agent 在**原终端**使用明确回复(投递提示每次都附原消息 ID 与此用法):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
td collab --session <自己的TD成员ID> reply <原消息ID> "收到" --response-kind ack --idempotency-key run:ack
|
|
66
|
+
td collab --session <自己的TD成员ID> reply <原消息ID> --file /private/result.txt --response-kind result --idempotency-key run:result
|
|
67
|
+
# 进展也可 --response-kind progress;不推断 Agent 内部进度或整个会话忙闲
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
回复保存到同一消息历史,`status=stored`、`stored_at`、`delivery.stage=stored`、
|
|
71
|
+
`delivery_semantics=durable_storage`、`delivered_at=null`、`attempt_count=0`;
|
|
72
|
+
不进终端投递队列,不伪造“写入终端”。原 message get/list 记录的 toSessionId 使用内部保留
|
|
73
|
+
地址 principal:<id> 以兼容历史结构,实际接收方由 toPrincipalId 标识;不要把这个地址当作成员 ID。Agent CLI 立即返回这个保存事实,不等待不存在的接收终端。
|
|
74
|
+
原发送的 receipt.result_ids/reply_ids/ack_at 来自明确的关联回复,原消息投递状态独立保留。
|
|
75
|
+
原生会话、结果正文及外部校验仍由实际 Agent/业务方负责;result 不表示用户验收或外部发布成功。
|
|
76
|
+
|
|
77
|
+
`message.reply` 的 payload 包含 `message_id`、`thread_id`、`reply_to`(原消息 ID)、
|
|
78
|
+
`from_session_id`、`to_session_id`(后台回复为 null)、`to_principal_id`、
|
|
79
|
+
`integration_origin`、`response_kind`、`stored_at`、`content`、`metadata`。
|
|
80
|
+
普通消息没有 task_id/attempt_id;不要套用 task 的 to-event/respond。外部话题和结果版本
|
|
81
|
+
用 message_id/thread_id 与 reply_to 关联。外部追问可以回复自己的原发送或收到的 Agent 回复:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
td collab --principal report-bridge reply <原发送ID或Agent回复ID> "解释一下" --idempotency-key followup:external-id
|
|
85
|
+
td collab --principal report-bridge message get <消息ID> --text
|
|
86
|
+
td collab --principal report-bridge message list --thread <话题ID> --limit 50
|
|
87
|
+
td collab --principal report-bridge message list --thread <话题ID> --after-id <上一页next_after_id> --limit 50
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
追问固定回原成员及原 thread,原成员不在授权组时返回 NO_REPLY_TARGET,不悄悄新建会话。
|
|
91
|
+
需要恢复时显式 session restore,仍保留原 native UUID、argv/cwd/包装器与 startupInput 条件。
|
|
92
|
+
命令变更必须给稳定 idempotency-key;同身份/组/key 且相同正文与参数返回同一消息 ID,
|
|
93
|
+
不同 payload 返回 IDEMPOTENCY_CONFLICT。原 Agent 回复另按 principal/成员/key 隔离。
|
|
94
|
+
|
|
95
|
+
消息/回复在成功返回前完成文件 fsync、原子替换和目录 fsync,事件从这些源记录持久生成。
|
|
96
|
+
订阅自动断线重连,从同 consumer 已 ACK 的连续位置重放;收到事件不自动 ACK,也不表示业务
|
|
97
|
+
已经处理。业务 inbox/outbox 按自身持久处理状态重试。事件保留缺口返回 EVENT_RETENTION_GAP
|
|
98
|
+
并停止;需结合仍保留的消息与业务数据库对账后建立新 consumer,不能声称任意历史均可重放。
|
|
99
|
+
message list 按本组 sequence 升序分页,返回 next_after_id/has_more,页还受 2 MiB 字节预算限制;
|
|
100
|
+
只覆盖仍保留的消息(常规历史最多保留 2000 条,pending 和未过期幂等记录另外保护,幂等保留 7 天)。
|
|
101
|
+
after-id 不在本组保留记录中返回 MESSAGE_NOT_FOUND;前缀不唯一返回 MESSAGE_ID_AMBIGUOUS。
|
|
102
|
+
鉴权撤销与权限更新沿用下文错误和重连契约。普通消息 delivered 仍只表示写入终端,不能从快照
|
|
103
|
+
缺少正文推断未消费,更不能因此自动重投已 delivered 消息。
|
|
104
|
+
|
|
20
105
|
## 管理员配置身份
|
|
21
106
|
|
|
22
107
|
接口只监听本机 Unix socket `~/.termdock/integration-<port>.sock`(0600),
|
|
@@ -49,7 +134,7 @@ td integration list
|
|
|
49
134
|
}
|
|
50
135
|
```
|
|
51
136
|
|
|
52
|
-
`launchProfiles`
|
|
137
|
+
`launchProfiles` 可为空(只操作已有消息或任务)。创建会话时只能选择预配 profile 和
|
|
53
138
|
目录,不能传任意命令/参数。`cwd` 必须已存在,真实路径须位于 `cwdRoots` 内;
|
|
54
139
|
符号链接不能逃出目录边界。参数按独立 token 引号转义,控制字符禁止。
|
|
55
140
|
`{cwd}`、`{sessionId}`、`{launchArgs}` 必须是独立参数;恢复模板必须各包含一次
|
|
@@ -302,8 +387,8 @@ task 事件 `event_id` 与任务内事件 ID 一致,`respond.reply_to_event_id
|
|
|
302
387
|
| `task.close/reopen/coordinate/pause/resume/retry/child-update/automation-blocked/review-passed/review-blocked` | `{event}`:保留服务事件的 source 与原文 |
|
|
303
388
|
| `task.result` | `{artifact}`:结果 id、attemptId、actor、content、summary、evidence、createdAt |
|
|
304
389
|
| `task.delivery` | `{delivery}`:任务投递 id、attemptId、messageId、status、deliveredAt、error |
|
|
305
|
-
| `message.queued/message.reply` | `{message_id,thread_id,reply_to,from_session_id,to_session_id,content,metadata}` |
|
|
306
|
-
| `message.delivery` | `{receipt}
|
|
390
|
+
| `message.queued/message.reply` | `{message_id,thread_id,reply_to,from_session_id,to_session_id,to_principal_id,integration_origin,response_kind,stored_at,content,metadata}` |
|
|
391
|
+
| `message.delivery` | `{receipt}`:实际入队/远端接收/终端写入/后台回复保存阶段、失败、重试计数与时间,及明确回复 ID;不含快照或已读字段 |
|
|
307
392
|
| `session.starting/binding_pending/ready/restoring/failed` | `{session}`:下节公开会话记录,失败包含 error_code |
|
|
308
393
|
|
|
309
394
|
TD 在源记录提交后触发 journal 及订阅,journal 中的同一事件 ID 在重连重放时保持不变。
|
package/package.json
CHANGED
package/runtime-manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.308",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
8
|
"dependencyHash": "sha256-E9GDa4nrCXADRXdyPKufusZt6IcCr4m2mGdx5h34KMQ=",
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
10
|
-
"clientBundleHash": "sha256-
|
|
9
|
+
"serverBundleHash": "sha256-KCZQLf7sIa26nzVvLZBZhHNcitDKANSYJdK523lF3qg=",
|
|
10
|
+
"clientBundleHash": "sha256-Yh0fz4oTrM6A2pNHLYx+Y67/R7Wfa84cFlIPMrwAsfE=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|
|
13
13
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{bv as r}from"./app-CXvK8WZg.js";import{b as m}from"./dnd-BCFAPC0L.js";import{b as n}from"./RightSidebar-CnnU7Ffp.js";import"./react-XDCjal2T.js";import"./terminal-C7X98Amf.js";import"./App-cagV-flM.js";import"./gestureArbiter-Cy5v_ZUL.js";import"./triangle-alert-BFCgLlxS.js";import"./referenceLongPress-xNXQRqYn.js";import"./DiffReview-Cn_PmN-9.js";import"./sparkles-BBYDWqCu.js";import"./DiffViewer-DRnuph7U.js";import"./ChangeWalkthroughPanel-DOj40Srm.js";function v({content:o}){const e=m.useMemo(()=>n(o.split(`
|
|
2
|
-
`),null,null),[o]);return r.jsx("div",{className:"min-w-0 space-y-3 break-words text-sm leading-6 text-foreground [&_h1]:text-lg [&_h2]:text-base [&_h3]:text-sm [&_pre]:max-w-full [&_table]:text-xs",children:e.map((t,i)=>r.jsx("div",{className:"min-w-0",children:typeof t.content=="function"?t.content(null):t.content},i))})}export{v as default};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|