termdock 1.4.306 → 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-BziVLy1b.js → App-ggIA_xqR.js} +7 -7
- 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-CARNBEqQ.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-00oa0z6w.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-DuEeKQKZ.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-C9v1Ooc9.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-DfUpMALj.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-Cf_URhQt.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-DjG9vCpH.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-BKOvHdJY.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-6n3tgsdc.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-BNs2sRct.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-CcMSU1v8.js → RightSidebar-BoNzpS2K.js} +3 -3
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DogjvRKp.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-D6vN8PW-.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-Cqel4Ted.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-DgvPtgNb.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-BxbWO4x4.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-gsA37gjL.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-ChC9yYOd.js → chunk-2Q5K7J3B-CfX9l18E.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Bi4Hc8yL.js → chunk-5VM5RSS4-JSV1yrXM.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DvVWvQEk.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-BnwQLR5S.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-BkNnaDiR.js → chunk-JWPE2WC7-Dpp7Xu_E.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BRzm6Uzi.js → chunk-POPQ4Y6H-9hDa1b8z.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CDYm-dzk.js → chunk-SVP7TREG-BskkG9gg.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CDYm-dzk.js.br → chunk-SVP7TREG-BskkG9gg.js.br} +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-BKaW9Z9i.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-DcHZ_FHb.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-DCbbTNc6.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-i23hD0Z2.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-CbyLYcqc.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-iJ4VKzZm.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-BNVZcdsO.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-1ghTvPiu.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-Ce_0nMd3.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-CLHEkhUt.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-iyb5DkP6.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-Dxh2TzXm.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-Cr8IkdCC.js → erDiagram-RLTQ6QDP-DlXgoyZ-.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-Cr8IkdCC.js.br → erDiagram-RLTQ6QDP-DlXgoyZ-.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-B8ZF1bwr.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-zhdL10eM.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-CXYHfU__.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-BQf2fHBo.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-BT8xey7S.js → infoDiagram-27XIBGKW-CSG29Ty_.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DunPdwW9.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-B0y75iIg.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-BfgA5WLo.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-BvjABLff.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-DgeFRliF.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-DM6Rlal9.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-DhEiABv6.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-Dxv0I4wM.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-LTlVaJfm.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-9bnryna3.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-ZBmgIpsJ.js → quadrantDiagram-AXDQQJYC-BPyHtH91.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-ZBmgIpsJ.js.br → quadrantDiagram-AXDQQJYC-BPyHtH91.js.br} +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-DTtXvKOu.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-Ggo3N5s5.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-Df8W2cyP.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-BNia9zI0.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-uvg5npfK.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-DpJNf2MD.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-CkWB2AnF.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-DOuEtn62.js → sparkles-Cg-yPdE2.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CeMzMQq-.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-Bo9GejWo.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-cvWXYEZ4.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-DzV0gQXz.js → triangle-alert-CbMRJ6sx.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CzleWcmk.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-DCnzjEfU.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-Cir1iutR.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-pNPcVJ1C.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 +62 -16
- package/dist/server/agent/collaborationPrompt.js +3 -1
- package/dist/server/agent/collaborationProtocol.js +1 -1
- package/dist/server/agent/collaborationRoutes.js +8 -1
- package/dist/server/agent/collaborationStore.js +81 -11
- package/dist/server/agent/collaborationTaskCli.js +25 -7
- package/dist/server/agent/collaborationTaskPurpose.js +20 -0
- package/dist/server/agent/collaborationTaskRoutes.js +1 -1
- package/dist/server/agent/collaborationTaskService.js +15 -6
- package/dist/server/agent/collaborationTaskStore.js +35 -6
- package/dist/server/agent/integrationCli.js +15 -4
- package/dist/server/agent/integrationServer.js +92 -14
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/cli.js +1 -1
- package/docs/integrations.md +177 -5
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-BziVLy1b.js.br +0 -0
- package/dist/client/assets/App-BziVLy1b.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CARNBEqQ.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CARNBEqQ.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-00oa0z6w.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-00oa0z6w.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DuEeKQKZ.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DuEeKQKZ.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-B_mzpNuw.js +0 -2
- package/dist/client/assets/ComputerControlView-C9v1Ooc9.js.br +0 -0
- package/dist/client/assets/ComputerControlView-C9v1Ooc9.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DfUpMALj.js.br +0 -0
- package/dist/client/assets/DagPlayground-DfUpMALj.js.gz +0 -0
- package/dist/client/assets/DiffLab-Cf_URhQt.js.br +0 -0
- package/dist/client/assets/DiffLab-Cf_URhQt.js.gz +0 -0
- package/dist/client/assets/DiffReview-DjG9vCpH.js.br +0 -0
- package/dist/client/assets/DiffReview-DjG9vCpH.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-BKOvHdJY.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-BKOvHdJY.js.gz +0 -0
- package/dist/client/assets/DiffViewer-6n3tgsdc.js.br +0 -0
- package/dist/client/assets/DiffViewer-6n3tgsdc.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BNs2sRct.js.br +0 -0
- package/dist/client/assets/ModelPreview-BNs2sRct.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CcMSU1v8.js.br +0 -0
- package/dist/client/assets/RightSidebar-CcMSU1v8.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DogjvRKp.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DogjvRKp.js.gz +0 -0
- package/dist/client/assets/app-D6vN8PW-.js.br +0 -0
- package/dist/client/assets/app-D6vN8PW-.js.gz +0 -0
- package/dist/client/assets/arc-Cqel4Ted.js.br +0 -0
- package/dist/client/assets/arc-Cqel4Ted.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DgvPtgNb.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DgvPtgNb.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BxbWO4x4.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BxbWO4x4.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gsA37gjL.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gsA37gjL.js.gz +0 -0
- package/dist/client/assets/channel-CBc8-n7y.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DvVWvQEk.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DvVWvQEk.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BnwQLR5S.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BnwQLR5S.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CDYm-dzk.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BKaW9Z9i.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BKaW9Z9i.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CNFc2dOc.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CNFc2dOc.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DCbbTNc6.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DCbbTNc6.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-i23hD0Z2.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-i23hD0Z2.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CbyLYcqc.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CbyLYcqc.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-iJ4VKzZm.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-iJ4VKzZm.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BNVZcdsO.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BNVZcdsO.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-1ghTvPiu.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-1ghTvPiu.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Ce_0nMd3.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Ce_0nMd3.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CLHEkhUt.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CLHEkhUt.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-iyb5DkP6.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-iyb5DkP6.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dxh2TzXm.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dxh2TzXm.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cr8IkdCC.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B8ZF1bwr.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B8ZF1bwr.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-zhdL10eM.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-zhdL10eM.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-CXYHfU__.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CXYHfU__.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BQf2fHBo.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BQf2fHBo.js.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DunPdwW9.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DunPdwW9.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B0y75iIg.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B0y75iIg.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BfgA5WLo.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BfgA5WLo.js.gz +0 -0
- package/dist/client/assets/layout-BvjABLff.js.br +0 -0
- package/dist/client/assets/layout-BvjABLff.js.gz +0 -0
- package/dist/client/assets/linear-DgeFRliF.js.br +0 -0
- package/dist/client/assets/linear-DgeFRliF.js.gz +0 -0
- package/dist/client/assets/mermaid.core-DM6Rlal9.js.br +0 -0
- package/dist/client/assets/mermaid.core-DM6Rlal9.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DhEiABv6.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DhEiABv6.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Dxv0I4wM.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Dxv0I4wM.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-LTlVaJfm.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-LTlVaJfm.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-9bnryna3.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-9bnryna3.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-ZBmgIpsJ.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DTtXvKOu.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DTtXvKOu.js.gz +0 -0
- package/dist/client/assets/rdpSession-Ggo3N5s5.js.br +0 -0
- package/dist/client/assets/rdpSession-Ggo3N5s5.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-Df8W2cyP.js.br +0 -0
- package/dist/client/assets/referenceLongPress-Df8W2cyP.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BNia9zI0.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BNia9zI0.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uvg5npfK.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uvg5npfK.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DpJNf2MD.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DpJNf2MD.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CkWB2AnF.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CkWB2AnF.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CeMzMQq-.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CeMzMQq-.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CNYUpLxt.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Bo9GejWo.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Bo9GejWo.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-EB7zBRFR.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-cvWXYEZ4.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-cvWXYEZ4.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CzleWcmk.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CzleWcmk.js.gz +0 -0
- package/dist/client/assets/videoCompression-DCnzjEfU.js.br +0 -0
- package/dist/client/assets/videoCompression-DCnzjEfU.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cir1iutR.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cir1iutR.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pNPcVJ1C.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pNPcVJ1C.js.gz +0 -0
|
@@ -5,10 +5,12 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { COLLAB_LIMITS, CollaborationError, canonicalShortId } from './collaborationProtocol.js';
|
|
6
6
|
export const COLLAB_HELP = `td collab — durable messages; no agent-specific hooks required
|
|
7
7
|
── 任务闭环 ──
|
|
8
|
-
task list [--group <组id>] | task get <任务id>
|
|
8
|
+
task list [--group <组id>] [--purpose interactive|automation|all] | task get <任务id>
|
|
9
|
+
task configure <任务id> --purpose interactive|automation --revision <当前版本>
|
|
9
10
|
task create --group <组id> --title <标题> --content <正文> [--assignee <成员id>]
|
|
10
11
|
[--constraints <约束>] [--acceptance <验收标准>] [--coordinator <成员id>]
|
|
11
12
|
[--parent <任务id>] [--depends-on <任务id,任务id>] [--integration]
|
|
13
|
+
[--purpose interactive|automation](默认 interactive;automation 是独立后台记录,无需用户验收)
|
|
12
14
|
task assign <任务id> --assignee <成员id> --revision <当前版本>
|
|
13
15
|
task report <任务id> --attempt <尝试id> --status ack|working|blocked|complete|failed --content <正文>
|
|
14
16
|
task ask <任务id> --attempt <尝试id> --content <问题> [--options '["选项一","选项二"]']
|
|
@@ -50,12 +52,27 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
|
|
|
50
52
|
── 后台集成(独立凭据;本机 Unix socket) ──
|
|
51
53
|
td integration help(管理授权与启动配置)
|
|
52
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 标记回复类型。
|
|
53
64
|
events subscribe --consumer <name> --jsonl(主动推送,默认持续运行)
|
|
54
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,不能据此声称历史完整。
|
|
55
69
|
session create --group <id> --launch-profile <id> --cwd <absolute-path> --idempotency-key <key>
|
|
56
70
|
session get <id> | session restore <id> --idempotency-key <key>
|
|
57
71
|
task respond <task-id> --attempt <attempt-id> --to-event <comment-event-id> --content <答复>
|
|
58
72
|
凭据通过 TERMDOCK_INTEGRATION_CREDENTIAL_FILE 注入,不通过命令行传 token。
|
|
73
|
+
消息接入最低 CLI/实际服务均为 1.4.308;capabilities 回读双方版本与 integration_message_send/reply/history。
|
|
74
|
+
principal 只操作获授权本机组;不能冒充用户/成员或用户验收。普通消息不需要 task/accepted。
|
|
75
|
+
任务命令适用于需要任务闭环的流程;message.send/read 与 task 权限独立。
|
|
59
76
|
已验收任务可追加纯咨询;respond 保留当前结果与验收,revise 是修改要求。
|
|
60
77
|
── 常用 ──
|
|
61
78
|
status (who is in my groups, their names, roles, observed activity.
|
|
@@ -143,11 +160,11 @@ Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LI
|
|
|
143
160
|
Idempotency retention: 7 days. Terminal delivery, ACK and result never imply each other.
|
|
144
161
|
Delivery semantics: delivered = written to the terminal — never proof of
|
|
145
162
|
reading or task completion; a timeout stops waiting, it does not cancel delivery.
|
|
146
|
-
The service cannot see whether the recipient's agent consumed a message.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
163
|
+
The service cannot see whether the recipient's agent consumed a message.
|
|
164
|
+
Use explicit replies as evidence. A screen snapshot is only an observation;
|
|
165
|
+
do not automatically resend a delivered message because it is absent from a snapshot.
|
|
166
|
+
stored = durable reply to a nonterminal principal; terminal delivery waits do not apply.
|
|
167
|
+
Idempotent retries reuse the same key and payload; a fresh key creates a new message.`;
|
|
151
168
|
/** One roster row: prefer the member's human name, keep the full session id
|
|
152
169
|
* reachable for role set/unset targeting, mark unset members explicitly. */
|
|
153
170
|
function roleLine(member, role) {
|
|
@@ -155,7 +172,7 @@ function roleLine(member, role) {
|
|
|
155
172
|
return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
|
|
156
173
|
}
|
|
157
174
|
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help', 'no-rules', 'managed', 'shared-directory', 'integration']);
|
|
158
|
-
const VALUE_OPTIONS = new Set(['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']);
|
|
175
|
+
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']);
|
|
159
176
|
export function parseCollaborationCommand(argv) {
|
|
160
177
|
const options = {};
|
|
161
178
|
const positional = [];
|
|
@@ -234,7 +251,7 @@ export function parseCollaborationCommand(argv) {
|
|
|
234
251
|
else if (action === 'task') {
|
|
235
252
|
command.operation = positional.shift();
|
|
236
253
|
command.target = positional.shift();
|
|
237
|
-
if (positional.length || !['list', 'get', 'create', 'assign', 'report', 'respond', 'answer', 'ask', 'plan', 'review', 'request-review', 'comment', 'revise', 'close', 'reopen', 'coordinate', 'pause', 'resume', 'retry'].includes(command.operation ?? ''))
|
|
254
|
+
if (positional.length || !['list', 'get', 'create', 'configure', 'assign', 'report', 'respond', 'answer', 'ask', 'plan', 'review', 'request-review', 'comment', 'revise', 'close', 'reopen', 'coordinate', 'pause', 'resume', 'retry'].includes(command.operation ?? ''))
|
|
238
255
|
throw new Error('Usage: td collab task list|get|create|assign|report|ask|plan|review|request-review|comment|revise|close|reopen');
|
|
239
256
|
if (['list', 'create'].includes(command.operation) ? Boolean(command.target) : !command.target)
|
|
240
257
|
throw new Error('task command requires a full task id except list/create');
|
|
@@ -248,6 +265,8 @@ export function parseCollaborationCommand(argv) {
|
|
|
248
265
|
throw new Error(`${action} requires a target and message`);
|
|
249
266
|
if ([Boolean(command.message), Boolean(options.file), Boolean(options.stdin)].filter(Boolean).length !== 1)
|
|
250
267
|
throw new Error('Choose inline body, --file, or --stdin');
|
|
268
|
+
if (options.principal && !options['idempotency-key'])
|
|
269
|
+
throw new Error('Integration message mutation requires an explicit --idempotency-key');
|
|
251
270
|
}
|
|
252
271
|
else if (action === 'rules') {
|
|
253
272
|
command.operation = positional.shift();
|
|
@@ -284,8 +303,15 @@ export function parseCollaborationCommand(argv) {
|
|
|
284
303
|
else if (action === 'message') {
|
|
285
304
|
command.operation = positional.shift();
|
|
286
305
|
command.target = positional.shift();
|
|
287
|
-
if (!['get', 'watch', 'confirm-shell'].includes(command.operation ?? '') || !command.target || positional.length)
|
|
288
|
-
throw new Error('Usage: td collab message get|watch|confirm-shell <id>');
|
|
306
|
+
if (!['get', 'watch', 'confirm-shell', 'list'].includes(command.operation ?? '') || (command.operation === 'list' ? !options.principal || Boolean(command.target) : !command.target) || positional.length)
|
|
307
|
+
throw new Error('Usage: td collab message get|watch|confirm-shell <id>; --principal <id> message list');
|
|
308
|
+
if (command.operation === 'list') {
|
|
309
|
+
for (const flag of Object.keys(options))
|
|
310
|
+
if (!['principal', 'json', 'jsonl', 'text', 'group', 'thread', 'after-id', 'limit'].includes(flag))
|
|
311
|
+
throw new Error(`--${flag} is not supported by message list`);
|
|
312
|
+
}
|
|
313
|
+
else if (['group', 'thread', 'after-id', 'limit'].some(flag => options[flag] !== undefined))
|
|
314
|
+
throw new Error('History filters apply to --principal message list only');
|
|
289
315
|
}
|
|
290
316
|
else if (action === 'cursor') {
|
|
291
317
|
command.operation = positional.shift();
|
|
@@ -356,17 +382,20 @@ export function parseCollaborationCommand(argv) {
|
|
|
356
382
|
const allowed = new Set(['json', 'jsonl', 'text', 'help', 'session', 'principal']);
|
|
357
383
|
const byAction = {
|
|
358
384
|
events: ['consumer', 'group', 'timeout'], session: ['group', 'launch-profile', 'cwd', 'idempotency-key'],
|
|
359
|
-
task: ['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'],
|
|
385
|
+
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'],
|
|
360
386
|
rules: ['file', 'stdin', 'if-version'], transport: ['file', 'origin'], group: ['file'], status: [], capabilities: [], rebind: ['pane'], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
|
|
361
387
|
send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at', 'kind'],
|
|
362
388
|
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
|
|
363
389
|
reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
364
390
|
inbox: ['raw', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
|
|
365
|
-
message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
|
|
391
|
+
message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply', 'group', 'thread', 'after-id', 'limit'], cursor: ['consumer'],
|
|
366
392
|
add: [], remove: [], spawn: ['launch-profile', 'name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: ['lines', 'raw'],
|
|
367
393
|
};
|
|
368
394
|
for (const option of byAction[action])
|
|
369
395
|
allowed.add(option);
|
|
396
|
+
if (options.principal && ['send', 'reply'].includes(action))
|
|
397
|
+
for (const option of ['source', 'external-actor', 'external-message-id'])
|
|
398
|
+
allowed.add(option);
|
|
370
399
|
for (const option of Object.keys(options))
|
|
371
400
|
if (!allowed.has(option))
|
|
372
401
|
throw new Error(`--${option} is not supported by ${action}`);
|
|
@@ -392,7 +421,7 @@ export function waitSatisfied(receipt, stage, reply) {
|
|
|
392
421
|
* machine reasons onto what the sender should do next, so a settled-but-
|
|
393
422
|
* unconsumed message is not mistaken for a lost one (or a good one). */
|
|
394
423
|
const DELIVERY_DIAGNOSTICS = {
|
|
395
|
-
AGENT_CONSUME_UNCONFIRMED: '
|
|
424
|
+
AGENT_CONSUME_UNCONFIRMED: '消息已写入对方终端,但未确认被对方消费;等待明确回复,不从终端快照推断消费或自动重发',
|
|
396
425
|
SHELL_CONFIRMATION_REQUIRED: '目标当前是 shell,消息可能被当命令执行;确认请运行 message confirm-shell <id>',
|
|
397
426
|
DELIVERY_IN_PROGRESS: '投递进行中(写入前的中间标记,正常会在数秒内推进为 delivered 或带原因的重试)',
|
|
398
427
|
TERMINAL_WRITE_FAILED: '写入对方终端失败,会按重试间隔继续尝试',
|
|
@@ -428,17 +457,22 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
428
457
|
const output = (value) => {
|
|
429
458
|
if (o.text) {
|
|
430
459
|
let diagnosed = false;
|
|
431
|
-
if (Array.isArray(value.messages))
|
|
460
|
+
if (Array.isArray(value.messages)) {
|
|
432
461
|
for (const message of value.messages) {
|
|
433
462
|
const fanIds = Array.isArray(message.fanOutIds) ? message.fanOutIds : [];
|
|
434
463
|
const names = value.names ?? {};
|
|
435
|
-
io.write(`[${message.responseKind ?? message.kind}] ${canonicalShortId(message.id)} from ${message.fromSessionId ?? 'user'}${fanIds.length ? ' · 群发' : ''}`);
|
|
464
|
+
io.write(`[${message.responseKind ?? message.kind}] ${canonicalShortId(message.id)} from ${message.fromSessionId ?? (message.integrationOrigin ? `principal:${message.integrationOrigin.integrationId}` : 'user')}${fanIds.length ? ' · 群发' : ''}`);
|
|
436
465
|
// A fan-out dispatch names its sibling recipients so a broadcast is
|
|
437
466
|
// never read as a one-to-one assignment; unknown ids stay raw.
|
|
438
467
|
if (fanIds.length)
|
|
439
468
|
io.write(`同时发给了:${fanIds.map((id) => names[id] ?? id).join('、')}`);
|
|
440
469
|
io.write(message.content);
|
|
470
|
+
if (message.status === 'stored')
|
|
471
|
+
io.write('答复已持久保存;接收方是后台程序,不是终端。');
|
|
441
472
|
}
|
|
473
|
+
if (value.next_after_id !== undefined)
|
|
474
|
+
io.write(`next_after_id=${value.next_after_id ?? ''} has_more=${value.has_more === true}(仅保留记录)`);
|
|
475
|
+
}
|
|
442
476
|
else if (value.message) {
|
|
443
477
|
// One line instead of the full rules text — the CLI is stateless so it
|
|
444
478
|
// cannot diff versions across calls; --no-rules silences it entirely.
|
|
@@ -643,12 +677,24 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
643
677
|
return 0;
|
|
644
678
|
}
|
|
645
679
|
if (command.action === 'message') {
|
|
680
|
+
if (command.operation === 'list') {
|
|
681
|
+
const params = new URLSearchParams();
|
|
682
|
+
for (const key of ['group', 'thread', 'after-id', 'limit'])
|
|
683
|
+
if (o[key])
|
|
684
|
+
params.set(key, String(o[key]));
|
|
685
|
+
output(await request('GET', `/message?${params}`));
|
|
686
|
+
return 0;
|
|
687
|
+
}
|
|
646
688
|
const route = `/message/${encodeURIComponent(command.target)}`;
|
|
647
689
|
if (command.operation === 'confirm-shell') {
|
|
648
690
|
output(await request('POST', `${route}/${command.operation}`));
|
|
649
691
|
return 0;
|
|
650
692
|
}
|
|
651
693
|
receipt = await request('GET', `${route}?receipt_only=${Boolean(o['receipt-only'] || command.operation === 'watch')}&raw=${Boolean(o.raw)}`);
|
|
694
|
+
if (receipt.status === 'stored') {
|
|
695
|
+
output(receipt);
|
|
696
|
+
return 0;
|
|
697
|
+
}
|
|
652
698
|
if (command.operation === 'get' && !o.follow && !o['wait-until'] && !o['expect-reply']) {
|
|
653
699
|
output(receipt);
|
|
654
700
|
return 0;
|
|
@@ -690,7 +736,7 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
690
736
|
// CLI receipts stay small even when the message is a large evidence package.
|
|
691
737
|
delete receipt.messages;
|
|
692
738
|
receipt.idempotency_key = idempotencyKey;
|
|
693
|
-
if (receipt.task_recorded) {
|
|
739
|
+
if (receipt.task_recorded || receipt.integration_recorded) {
|
|
694
740
|
output(receipt);
|
|
695
741
|
return 0;
|
|
696
742
|
}
|
|
@@ -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 };
|
|
@@ -156,10 +156,12 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind, re
|
|
|
156
156
|
managed_goals: true, automatic_review: true, dependency_dispatch: true, isolated_worktrees: true, encrypted_commit_transfer: true,
|
|
157
157
|
durable_questions: true, user_acceptance: true, versioned_artifacts: true, independent_reviews: true,
|
|
158
158
|
correlated_responses: true, accepted_comments: true,
|
|
159
|
+
background_task_records: true, task_purpose_configuration: true, explicit_execution_state: true,
|
|
159
160
|
task_reply: 'Replies to task-linked user messages are stored in the task; status=stored is not a terminal delivery receipt' },
|
|
160
161
|
routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
|
|
161
|
-
statuses: ['pending', 'delivered', 'failed', 'expired'], queued_status: 'pending',
|
|
162
|
+
statuses: ['pending', 'delivered', 'failed', 'expired', 'stored'], queued_status: 'pending',
|
|
162
163
|
semantics: { delivered: 'written to terminal; no application acknowledgement implied',
|
|
164
|
+
stored: 'durable reply to a nonterminal integration principal; never a terminal write',
|
|
163
165
|
ack: 'explicit response_kind=ack', result: 'explicit response_kind=result; inspect task.status and evidence',
|
|
164
166
|
snapshot: 'terminal observation, never proof of reading or task completion',
|
|
165
167
|
timeout: 'stops waiting, does not cancel delivery',
|
|
@@ -207,6 +209,11 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind, re
|
|
|
207
209
|
}));
|
|
208
210
|
router.post('/reply', run(async (req, res, sessionId) => {
|
|
209
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
|
+
}
|
|
210
217
|
if (!original.fromSessionId) {
|
|
211
218
|
const link = original.metadata?.termdockTask;
|
|
212
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();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
4
|
+
import { taskPurpose, taskPurposeFilter } from './collaborationTaskPurpose.js';
|
|
3
5
|
export async function executeTaskCommand(command, request, io) {
|
|
4
6
|
const o = command.options, operation = command.operation;
|
|
5
7
|
const bodyOptions = ['content', 'file', 'stdin', 'idempotency-key'];
|
|
@@ -7,7 +9,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
7
9
|
if (originOptions.some(key => o[key]) && !o.principal)
|
|
8
10
|
throw new Error('External source metadata requires --principal');
|
|
9
11
|
const allowed = {
|
|
10
|
-
list: ['group'], get: [], create: [...bodyOptions, 'group', 'title', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'managed', 'reviewers', 'shared-directory', 'integration', 'work-type'],
|
|
12
|
+
list: ['group', 'purpose'], get: [], configure: [...bodyOptions, 'purpose', 'revision'], create: [...bodyOptions, 'purpose', 'group', 'title', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'managed', 'reviewers', 'shared-directory', 'integration', 'work-type'],
|
|
11
13
|
answer: [...bodyOptions, 'decision'], respond: [...bodyOptions, 'attempt', 'to-event', 'evidence'],
|
|
12
14
|
assign: [...bodyOptions, 'assignee', 'revision'], report: [...bodyOptions, 'attempt', 'status', 'evidence', 'summary'], ask: [...bodyOptions, 'attempt', 'options'],
|
|
13
15
|
plan: [...bodyOptions, 'attempt', 'evidence'], review: [...bodyOptions, 'artifact', 'evidence', 'verdict'], comment: bodyOptions,
|
|
@@ -18,21 +20,37 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
18
20
|
for (const flag of Object.keys(o))
|
|
19
21
|
if (![...allowed[operation], ...(o.principal ? originOptions : []), 'principal', 'session', 'json', 'jsonl', 'text', 'help'].includes(flag))
|
|
20
22
|
throw new Error(`--${flag} is not supported by task ${operation}`);
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
+
if (o.purpose !== undefined)
|
|
24
|
+
(operation === 'list' ? taskPurposeFilter : taskPurpose)(o.purpose);
|
|
25
|
+
if (operation === 'configure' && o.purpose === undefined)
|
|
26
|
+
throw new Error('configure requires --purpose interactive|automation');
|
|
27
|
+
if (o.purpose !== undefined || operation === 'configure') {
|
|
28
|
+
const capabilities = await request('GET', '/capabilities');
|
|
29
|
+
const features = o.principal ? capabilities : capabilities.task_workflow;
|
|
30
|
+
if (features?.background_task_records !== true || features.task_purpose_configuration !== true || features.explicit_execution_state !== true)
|
|
31
|
+
throw new CollaborationError('BACKGROUND_TASK_RECORDS_UNSUPPORTED', 'Running service does not support background task records', 409);
|
|
32
|
+
}
|
|
33
|
+
if (operation === 'list') {
|
|
34
|
+
const query = new URLSearchParams();
|
|
35
|
+
if (o.group)
|
|
36
|
+
query.set('group', String(o.group));
|
|
37
|
+
if (o.purpose)
|
|
38
|
+
query.set('purpose', String(o.purpose));
|
|
39
|
+
return request('GET', `/tasks${query.size ? `?${query}` : ''}`);
|
|
40
|
+
}
|
|
23
41
|
if (operation === 'get')
|
|
24
42
|
return request('GET', `/tasks/${encodeURIComponent(command.target)}`);
|
|
25
43
|
const contentSources = [o.content, o.file, o.stdin].filter(Boolean).length;
|
|
26
44
|
if (contentSources > 1)
|
|
27
45
|
throw new Error('Choose --content, --file or --stdin');
|
|
28
46
|
const content = o.file ? fs.readFileSync(String(o.file), 'utf8') : o.stdin ? await io.stdin?.() : o.content;
|
|
29
|
-
if (!['assign', 'close', 'reopen', 'request-review', 'pause', 'resume', 'retry'].includes(operation) && (typeof content !== 'string' || !content.trim()))
|
|
47
|
+
if (!['configure', 'assign', 'close', 'reopen', 'request-review', 'pause', 'resume', 'retry'].includes(operation) && (typeof content !== 'string' || !content.trim()))
|
|
30
48
|
throw new Error('This task operation requires --content, --file or --stdin');
|
|
31
49
|
const idempotencyKey = String(o['idempotency-key'] ?? randomUUID());
|
|
32
50
|
const expectedRevision = o.revision === undefined ? undefined : Number(o.revision);
|
|
33
51
|
if (expectedRevision !== undefined && (!Number.isSafeInteger(expectedRevision) || expectedRevision < 1))
|
|
34
52
|
throw new Error('--revision must be a positive integer');
|
|
35
|
-
if (['assign', 'revise', 'close', 'reopen', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(operation) && expectedRevision === undefined)
|
|
53
|
+
if (['configure', 'assign', 'revise', 'close', 'reopen', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(operation) && expectedRevision === undefined)
|
|
36
54
|
throw new Error('Read task get first, then supply --revision to avoid overwriting another decision');
|
|
37
55
|
if (['assign', 'request-review'].includes(operation) && !o.assignee)
|
|
38
56
|
throw new Error('This task operation requires --assignee');
|
|
@@ -46,7 +64,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
46
64
|
if (!o.title || !o.group)
|
|
47
65
|
throw new Error('task create requires --title and --group');
|
|
48
66
|
return request('POST', '/tasks', { input: { idempotencyKey, groupId: o.group, title: o.title, spec: content,
|
|
49
|
-
constraints: o.constraints, acceptance: o.acceptance, assigneeSessionId: o.assignee, coordinatorSessionId: o.coordinator,
|
|
67
|
+
purpose: o.purpose, constraints: o.constraints, acceptance: o.acceptance, assigneeSessionId: o.assignee, coordinatorSessionId: o.coordinator,
|
|
50
68
|
parentTaskId: o.parent, dependsOn: o['depends-on'] ? String(o['depends-on']).split(',').filter(Boolean) : undefined,
|
|
51
69
|
integration: o.integration === true, managed: o.managed === true, isolated: o['shared-directory'] !== true, workType: o['work-type'],
|
|
52
70
|
reviewerSessionIds: o.reviewers ? String(o.reviewers).split(',').filter(Boolean) : undefined } });
|
|
@@ -55,7 +73,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
55
73
|
if (options !== undefined && (!Array.isArray(options) || options.some(v => typeof v !== 'string')))
|
|
56
74
|
throw new Error('--options must be a JSON array of strings');
|
|
57
75
|
return request('POST', `/tasks/${encodeURIComponent(command.target)}`, { input: {
|
|
58
|
-
replyToEventId: o['to-event'], decisionId: o.decision, kind: operation === 'plan' ? 'submit-plan' : operation, idempotencyKey, expectedRevision, content,
|
|
76
|
+
purpose: o.purpose, replyToEventId: o['to-event'], decisionId: o.decision, kind: operation === 'plan' ? 'submit-plan' : operation, idempotencyKey, expectedRevision, content,
|
|
59
77
|
assigneeSessionId: o.assignee, attemptId: o.attempt, status: o.status, artifactId: o.artifact, options, verdict: o.verdict,
|
|
60
78
|
evidence: o.evidence ? JSON.parse(String(o.evidence)) : undefined, summary: o.summary,
|
|
61
79
|
} });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
2
|
+
export function taskPurpose(value = 'interactive') {
|
|
3
|
+
if (value !== 'interactive' && value !== 'automation')
|
|
4
|
+
throw new CollaborationError('INVALID_TASK_PURPOSE', 'purpose must be interactive or automation');
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
export function taskPurposeFilter(value = 'all') {
|
|
8
|
+
return value === 'all' ? value : taskPurpose(value);
|
|
9
|
+
}
|
|
10
|
+
/** Only ordered, explicit reports count. Revisions invalidate the previous round;
|
|
11
|
+
* comments and responses leave it intact. Delivery never implies completion. */
|
|
12
|
+
export function taskExecution(task) {
|
|
13
|
+
const report = task.events.filter(e => e.kind === 'report' && e.attemptId === task.activeAttemptId).at(-1);
|
|
14
|
+
const revision = task.events.filter(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId).at(-1);
|
|
15
|
+
if (!task.activeAttemptId || !report?.reportStatus || revision && revision.sequence > report.sequence) {
|
|
16
|
+
return { status: task.activeAttemptId ? 'awaiting_report' : 'unassigned', attemptId: task.activeAttemptId, reportEventId: null, artifactId: null, reportedAt: null };
|
|
17
|
+
}
|
|
18
|
+
return { status: report.reportStatus, attemptId: task.activeAttemptId, reportEventId: report.id,
|
|
19
|
+
artifactId: report.reportStatus === 'complete' ? report.artifactId ?? null : null, reportedAt: report.createdAt };
|
|
20
|
+
}
|
|
@@ -40,7 +40,7 @@ export function collaborationTaskRoutes(options) {
|
|
|
40
40
|
router.get('/', run((req, { tasks, actor }) => {
|
|
41
41
|
const requested = typeof req.query.group === 'string' ? groupId(req.query.group) : null;
|
|
42
42
|
const groups = requested ? [requested] : actor ? options.store.groupsForSession(actor.sessionId).map(g => g.id) : options.store.list().map(g => g.id);
|
|
43
|
-
return { tasks: groups.flatMap(id => tasks.list(id, actor)) };
|
|
43
|
+
return { tasks: groups.flatMap(id => tasks.list(id, actor, req.query.purpose ?? (options.agent ? 'all' : 'interactive'))) };
|
|
44
44
|
}));
|
|
45
45
|
router.get('/:taskId', run(async (req, { tasks, actor }) => ({ task: await tasks.get(req.params.taskId, actor) })));
|
|
46
46
|
const input = (req, peers) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
2
2
|
import { captureTaskCommit } from './collaborationTaskWorkspace.js';
|
|
3
3
|
import { taskBundleChunk } from './collaborationTaskBundles.js';
|
|
4
|
+
import { taskExecution, taskPurpose, taskPurposeFilter } from './collaborationTaskPurpose.js';
|
|
4
5
|
import { taskMemberKey } from './collaborationTaskTypes.js';
|
|
5
6
|
/** One authority per task; replicas are records, never competing decision writers. */
|
|
6
7
|
export class CollaborationTaskService {
|
|
@@ -56,12 +57,13 @@ export class CollaborationTaskService {
|
|
|
56
57
|
const template = task.workflow?.reviewers.some(m => taskMemberKey(m) === taskMemberKey(member));
|
|
57
58
|
return { member, sessionId, launchProfile: group.memberLaunchProfiles?.[sessionId], role: `${template ? '此目标的执行成员模板;' : ''}${group.roles?.[sessionId] ?? ''}` };
|
|
58
59
|
});
|
|
59
|
-
return { ...task, children, roster, launchProfiles: summary ? undefined : group?.launchProfiles, defaultLaunchProfileId: summary ? undefined : group?.defaultLaunchProfileId, memberSessions, replica: task.ownerServiceId !== this.self, summaryOnly: summary, outbox: (summary ? [] : this.tasks.pending(task.id))
|
|
60
|
+
return { ...task, purpose: taskPurpose(task.purpose), ...(task.purpose === 'automation' ? { execution: task.execution ?? taskExecution(task) } : {}), children, roster, launchProfiles: summary ? undefined : group?.launchProfiles, defaultLaunchProfileId: summary ? undefined : group?.defaultLaunchProfileId, memberSessions, replica: task.ownerServiceId !== this.self, summaryOnly: summary, outbox: (summary ? [] : this.tasks.pending(task.id))
|
|
60
61
|
.map(({ id, attemptId, messageId, lastError }) => ({ id, attemptId, messageId, lastError })) };
|
|
61
62
|
}
|
|
62
|
-
list(groupId, actor) {
|
|
63
|
+
list(groupId, actor, purpose = 'all') {
|
|
63
64
|
this.group(groupId, actor);
|
|
64
|
-
|
|
65
|
+
const filter = taskPurposeFilter(purpose);
|
|
66
|
+
return this.tasks.list([groupId], true).filter(task => filter === 'all' || taskPurpose(task.purpose) === filter).map(task => this.view(task, true));
|
|
65
67
|
}
|
|
66
68
|
async get(taskId, actor) {
|
|
67
69
|
const task = this.tasks.get(taskId);
|
|
@@ -71,8 +73,10 @@ export class CollaborationTaskService {
|
|
|
71
73
|
return this.view(task);
|
|
72
74
|
}
|
|
73
75
|
async create(input, actor, origin) {
|
|
74
|
-
this.group(input.groupId, actor);
|
|
76
|
+
const group = this.group(input.groupId, actor);
|
|
75
77
|
this.validateMembers(input.groupId, input);
|
|
78
|
+
if (input.purpose === 'automation' && group.federated)
|
|
79
|
+
throw new CollaborationError('BACKGROUND_TASK_LOCAL_ONLY', '首版后台自动化记录使用本机协作组', 409);
|
|
76
80
|
const dependencies = [...(input.dependsOn ?? []), ...(input.parentTaskId ? [input.parentTaskId] : [])];
|
|
77
81
|
const authority = dependencies.length ? this.tasks.get(dependencies[0])?.ownerServiceId : this.self;
|
|
78
82
|
if (!authority)
|
|
@@ -97,8 +101,10 @@ export class CollaborationTaskService {
|
|
|
97
101
|
const task = this.tasks.get(taskId);
|
|
98
102
|
if (!task)
|
|
99
103
|
throw new CollaborationError('TASK_NOT_FOUND', '任务不存在', 404);
|
|
100
|
-
this.group(task.groupId, actor);
|
|
104
|
+
const group = this.group(task.groupId, actor);
|
|
101
105
|
this.validateMembers(task.groupId, input);
|
|
106
|
+
if (input.kind === 'configure' && input.purpose === 'automation' && group.federated)
|
|
107
|
+
throw new CollaborationError('BACKGROUND_TASK_LOCAL_ONLY', '首版后台自动化记录使用本机协作组', 409);
|
|
102
108
|
if (origin && task.ownerServiceId !== this.self)
|
|
103
109
|
throw new CollaborationError('INTEGRATION_LOCAL_ONLY', '首版集成任务须由本机服务保存', 409);
|
|
104
110
|
if (task.ownerServiceId !== this.self) {
|
|
@@ -131,7 +137,8 @@ export class CollaborationTaskService {
|
|
|
131
137
|
commit: child.artifacts.find(a => a.id === child.acceptedArtifactId)?.evidence?.commit })) } };
|
|
132
138
|
}
|
|
133
139
|
const updated = this.tasks.apply(taskId, input, actor, origin);
|
|
134
|
-
|
|
140
|
+
if (input.kind !== 'configure')
|
|
141
|
+
void this.flush();
|
|
135
142
|
return this.view(updated);
|
|
136
143
|
}
|
|
137
144
|
heads(groupIds) { return this.tasks.heads(groupIds, this.self); }
|
|
@@ -261,6 +268,8 @@ export class CollaborationTaskService {
|
|
|
261
268
|
}
|
|
262
269
|
receipt(messageId) {
|
|
263
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');
|
|
264
273
|
return { messageId, status: receipt.status, deliveredAt: receipt.delivered_at, error: receipt.delivery.error };
|
|
265
274
|
}
|
|
266
275
|
start() { this.timer = setInterval(() => void this.flush(), 1500); this.timer.unref(); void this.flush(); }
|