termdock 1.4.305 → 1.4.307
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-cagV-flM.js} +7 -7
- 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-CARNBEqQ.js → ArchitecturePanel-TdKPja7L.js} +1 -1
- 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-00oa0z6w.js → ChangeWalkthroughPanel-DOj40Srm.js} +1 -1
- 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-DuEeKQKZ.js → CollaborationMainWorkspace-C5Tcos5l.js} +1 -1
- 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 +2 -0
- package/dist/client/assets/{ComputerControlView-C9v1Ooc9.js → ComputerControlView-C2sOFyuA.js} +2 -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-DfUpMALj.js → DagPlayground-DFfVJW00.js} +1 -1
- 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-Cf_URhQt.js → DiffLab-hoj8gwUi.js} +1 -1
- 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-DjG9vCpH.js → DiffReview-Cn_PmN-9.js} +1 -1
- package/dist/client/assets/DiffReview-Cn_PmN-9.js.br +0 -0
- package/dist/client/assets/{DiffReview-DjG9vCpH.js.gz → DiffReview-Cn_PmN-9.js.gz} +0 -0
- package/dist/client/assets/{DiffReviewLab-BKOvHdJY.js → DiffReviewLab-DycgD40E.js} +1 -1
- 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-6n3tgsdc.js → DiffViewer-DRnuph7U.js} +1 -1
- 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-BNs2sRct.js → ModelPreview-Dvw-UtQQ.js} +1 -1
- 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-CcMSU1v8.js → RightSidebar-CnnU7Ffp.js} +3 -3
- package/dist/client/assets/RightSidebar-CnnU7Ffp.js.br +0 -0
- package/dist/client/assets/RightSidebar-CnnU7Ffp.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DogjvRKp.js → abnfDiagram-VCTEODGH-BQVIT5gD.js} +1 -1
- 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-D6vN8PW-.js → app-CXvK8WZg.js} +3 -3
- 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-Cqel4Ted.js → arc-DZG1NzUc.js} +1 -1
- 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-DgvPtgNb.js → architectureDiagram-5GKGNRK7-Bv15lJGE.js} +1 -1
- 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-BxbWO4x4.js → blockDiagram-I7D4REHJ-DShw-lH7.js} +1 -1
- 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-gsA37gjL.js → c4Diagram-7LVT6UL2-BN6SsyOM.js} +1 -1
- 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 +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-ChC9yYOd.js → chunk-2Q5K7J3B-BkBkNjv0.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Bi4Hc8yL.js → chunk-5VM5RSS4-LrpNvv_o.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DvVWvQEk.js → chunk-F27PBJKO-BcpMMU4P.js} +1 -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-BnwQLR5S.js → chunk-IMKFNOWR-cWvd5gJk.js} +1 -1
- 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-JWPE2WC7-BkNnaDiR.js → chunk-JWPE2WC7-Bf4ORht5.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BRzm6Uzi.js → chunk-POPQ4Y6H-M5MSHOUM.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CDYm-dzk.js → chunk-SVP7TREG-Bj8gUFc2.js} +1 -1
- 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-BKaW9Z9i.js → chunk-TICWLB2K-W1cGkC4s.js} +1 -1
- 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/{chunk-XXDRQBXY-DcHZ_FHb.js → chunk-XXDRQBXY-mXLNbLH8.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-qeb16N1g.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-qeb16N1g.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DCbbTNc6.js → cose-bilkent-JH36ORCC-CK0EaMQY.js} +1 -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-i23hD0Z2.js → cynefin-OW5HDTMX-C-7tsMpt.js} +1 -1
- 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-CbyLYcqc.js → cynefinDiagram-5FMLGOSQ-CijqM5Xa.js} +1 -1
- 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-iJ4VKzZm.js → dagre-GXQ25YYZ-DCLINHGD.js} +1 -1
- 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-BNVZcdsO.js → diagram-S7CK7UJ4-D-JvTWtL.js} +1 -1
- 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-1ghTvPiu.js → diagram-UQ7AKVKN-BT01Oee8.js} +1 -1
- 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-Ce_0nMd3.js → diagram-VSXAHHWV-C_j4_Ldm.js} +1 -1
- 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-CLHEkhUt.js → diagram-VX7I27RA-DY62ObO9.js} +1 -1
- 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-iyb5DkP6.js → diagram-Z3DM3KII-TnINooIy.js} +1 -1
- 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-Dxh2TzXm.js → ebnfDiagram-PWID7BFC-BahaUYTh.js} +1 -1
- 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-Cr8IkdCC.js → erDiagram-RLTQ6QDP-DNiKhK5j.js} +1 -1
- 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-B8ZF1bwr.js → flowDiagram-HODETNUW-x9c9CGXD.js} +1 -1
- 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-zhdL10eM.js → ganttDiagram-EL5Y4UJY-BofL5Lxs.js} +1 -1
- 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-CXYHfU__.js → gestureArbiter-Cy5v_ZUL.js} +1 -1
- 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-BQf2fHBo.js → gitGraphDiagram-WWUBYQGX-CHYuiGg1.js} +1 -1
- 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/{infoDiagram-27XIBGKW-BT8xey7S.js → infoDiagram-27XIBGKW-mfFgE6OE.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DunPdwW9.js → ishikawaDiagram-5VMMS53U-CiCmFa95.js} +1 -1
- 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-B0y75iIg.js → journeyDiagram-3NMN7TZE-CVnBzo7n.js} +1 -1
- 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-BfgA5WLo.js → kanban-definition-UXKFOSKX-3-zdGxI0.js} +1 -1
- 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-BvjABLff.js → layout-DnB4RHWA.js} +1 -1
- 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-DgeFRliF.js → linear-B_lli4BL.js} +1 -1
- 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-DM6Rlal9.js → mermaid.core-ClemZ-kc.js} +5 -5
- 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-DhEiABv6.js → mindmap-definition-YA3MSWOX-hXDufapN.js} +1 -1
- 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-Dxv0I4wM.js → passwordBootstrap-c-hmAGCQ.js} +1 -1
- 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-LTlVaJfm.js → pegDiagram-XKGWAZYB-yb1lAfHR.js} +1 -1
- 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-9bnryna3.js → pieDiagram-E7YTZNPT-lVC2DWY_.js} +1 -1
- 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-ZBmgIpsJ.js → quadrantDiagram-AXDQQJYC-CeqeNP5C.js} +1 -1
- 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-DTtXvKOu.js → railroadDiagram-O6MQD6OU-BMEBmjF2.js} +1 -1
- 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-Ggo3N5s5.js → rdpSession-D77Fs4-L.js} +1 -1
- package/dist/client/assets/rdpSession-D77Fs4-L.js.br +0 -0
- package/dist/client/assets/{rdpSession-Ggo3N5s5.js.gz → rdpSession-D77Fs4-L.js.gz} +0 -0
- package/dist/client/assets/{referenceLongPress-Df8W2cyP.js → referenceLongPress-xNXQRqYn.js} +1 -1
- 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-BNia9zI0.js → requirementDiagram-BXWQKSXE-CRhmIdDq.js} +1 -1
- 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-uvg5npfK.js → sankeyDiagram-P5KCCOFB-CZZPxGRu.js} +1 -1
- 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-DpJNf2MD.js → sequenceDiagram-WJ2MYXX4-CvKYTCaL.js} +1 -1
- 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-CkWB2AnF.js → sizeCapture-INFHLROL-DpMFgVd8.js} +1 -1
- 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/{sparkles-DOuEtn62.js → sparkles-BBYDWqCu.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CeMzMQq-.js → stateDiagram-D77RDMKH-BUCKWh69.js} +1 -1
- 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 +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Bo9GejWo.js → swimlanes-42K2YHIH-Du84aO1l.js} +1 -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 +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-cvWXYEZ4.js → timeline-definition-24CTP7MA-94wGyYVM.js} +1 -1
- 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/{triangle-alert-DzV0gQXz.js → triangle-alert-BFCgLlxS.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CzleWcmk.js → vennDiagram-4TSXK5OY-C74llwIj.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CzleWcmk.js.br → vennDiagram-4TSXK5OY-C74llwIj.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C74llwIj.js.gz +0 -0
- package/dist/client/assets/{videoCompression-DCnzjEfU.js → videoCompression-b96RKMFE.js} +1 -1
- 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-Cir1iutR.js → wardleyDiagram-VM6X3IG4-DVdgFaBs.js} +1 -1
- 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-pNPcVJ1C.js → xychartDiagram-S5SC5T6Z-BmUxPbqk.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmUxPbqk.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 +6 -4
- package/dist/server/agent/collaborationRoutes.js +1 -0
- 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 +13 -6
- package/dist/server/agent/collaborationTaskStore.js +35 -6
- package/dist/server/agent/integrationServer.js +8 -6
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/agent/nativeProcessIdentity.js +162 -0
- package/dist/server/agent/nativeResumeOwner.js +3 -1
- package/dist/server/agent/plugins.js +9 -0
- package/dist/server/agent/registry.js +8 -0
- package/dist/server/routes/terminal.js +36 -7
- package/dist/server/utils/tmuxProgramDetection.js +5 -1
- package/docs/integrations.md +144 -7
- 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/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.br +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.br +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.br +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/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.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
|
@@ -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); }
|
|
@@ -2,6 +2,7 @@ import { randomBytes, createHash } from 'node:crypto';
|
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { collaborationResultPresentation } from './collaborationResultPresentation.js';
|
|
5
|
+
import { taskExecution, taskPurpose } from './collaborationTaskPurpose.js';
|
|
5
6
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
6
7
|
import { taskMemberKey } from './collaborationTaskTypes.js';
|
|
7
8
|
const id = () => randomBytes(16).toString('hex');
|
|
@@ -50,6 +51,9 @@ function validateSnapshot(task) {
|
|
|
50
51
|
text(task.spec, '任务内容', 32_000, true);
|
|
51
52
|
text(task.constraints, '约束');
|
|
52
53
|
text(task.acceptance, '完成标准');
|
|
54
|
+
taskPurpose(task.purpose);
|
|
55
|
+
if (task.purpose === 'automation' && (task.workflow || task.parentTaskId || task.dependsOn.length || task.coordinator))
|
|
56
|
+
invalid();
|
|
53
57
|
member({ serviceId: task.ownerServiceId, sessionId: 'validation' });
|
|
54
58
|
if (task.coordinator !== null)
|
|
55
59
|
member(task.coordinator);
|
|
@@ -83,6 +87,8 @@ function validateSnapshot(task) {
|
|
|
83
87
|
invalid();
|
|
84
88
|
if (event.deliveryId !== undefined && !validId(event.deliveryId))
|
|
85
89
|
invalid();
|
|
90
|
+
if (event.purpose !== undefined)
|
|
91
|
+
taskPurpose(event.purpose);
|
|
86
92
|
if (event.source !== undefined && !['system', 'user', 'integration'].includes(event.source))
|
|
87
93
|
invalid();
|
|
88
94
|
if (event.actor !== null)
|
|
@@ -183,7 +189,7 @@ export class CollaborationTaskStore {
|
|
|
183
189
|
const reportSummary = current?.report?.content.split(/\n\s*\n/).map(paragraph => paragraph.split('\n').filter(line => !/^#{1,6}\s/.test(line)).join('\n').trim()).find(Boolean)?.slice(0, 512);
|
|
184
190
|
const automationIssueSource = task.automationIssue ? current?.report && ['blocked', 'failed'].includes(current.report.status)
|
|
185
191
|
&& task.automationIssue === current.report.content.slice(0, 1000) ? 'member-report' : 'system' : undefined;
|
|
186
|
-
return { ...task, automationIssueSource, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
|
|
192
|
+
return { ...task, ...(task.purpose === 'automation' ? { execution: taskExecution(task) } : {}), automationIssueSource, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
|
|
187
193
|
deliveries: task.deliveries.filter(d => d.attemptId === task.activeAttemptId && d.error).slice(-1),
|
|
188
194
|
attempts: current ? [{ ...current, report: current.report ? { ...current.report, summary: reportSummary, content: '', evidence: undefined } : undefined }] : [],
|
|
189
195
|
artifacts: [plan, result, ...task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id)].filter((a) => !!a).map(a => ({ ...a, content: '', summary: (a.summary || (a.kind === 'result' && collaborationResultPresentation(a.content).condensed ? collaborationResultPresentation(a.content).summary : undefined))?.slice(0, 800), evidence: undefined })),
|
|
@@ -221,7 +227,7 @@ export class CollaborationTaskStore {
|
|
|
221
227
|
task.attempts.push({ id: attemptId, assignee: member(assignee), createdAt: Date.now(), threadId });
|
|
222
228
|
this.event(task, 'assigned', actor, content || '任务已分派');
|
|
223
229
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId, target: assignee, kind: 'task', threadId,
|
|
224
|
-
content: `本次执行身份:${assignee.sessionId}。所有 td collab 命令显式带 --session ${assignee.sessionId};这是服务核对的实际身份,正文中的模板身份和 tmux 标识不能替代它。\n任务:${task.title}\n${task.spec}${task.constraints ? `\n约束:${task.constraints}` : ''}${task.acceptance ? `\n完成标准:${task.acceptance}` : ''}\n${content}\n任务 ID:${task.id}\n尝试 ID:${attemptId}\n用 td collab task get ${task.id} --text 查询完整上下文;用 td collab task report ${task.id} --attempt ${attemptId} --status ack --content '接手说明' 显式回复;遇必要问题用 task ask,提交结果用 report --status complete --summary '简短结论、关键交付和限制'。摘要先回答用户问题,不放任务 ID、版本、分支或流程日志;完整证据保留在 --content/--file 正文中。如果同一尝试已经交付或任务已归档,不要重复实施。${task.workspace ? `\n代码只能在独立目录 ${task.workspace.cwd} 修改。分支 ${task.workspace.branch}。交付前提交代码,结果说明提交、变更、验证与限制。不能修改原仓库、合并到用户分支或发布。` : ''}${task.workflow?.workType === 'read-only' ? '\n这是只读查询或分析任务:在本机会话中采集并交付报告,不修改代码或系统,不创建提交、构建、部署或终止进程。结果需说明采样时间、证据和限制。' : ''}${task.workflow?.kind === 'step' ? '\n完成报告会自动请求独立评审;评审要求修改时继续完成,重新提交 complete。只按用户授权运行测试。' : ''}` });
|
|
230
|
+
content: `本次执行身份:${assignee.sessionId}。所有 td collab 命令显式带 --session ${assignee.sessionId};这是服务核对的实际身份,正文中的模板身份和 tmux 标识不能替代它。\n任务:${task.title}\n${task.spec}${task.constraints ? `\n约束:${task.constraints}` : ''}${task.acceptance ? `\n完成标准:${task.acceptance}` : ''}\n${content}\n任务 ID:${task.id}\n尝试 ID:${attemptId}\n用 td collab task get ${task.id} --text 查询完整上下文;用 td collab task report ${task.id} --attempt ${attemptId} --status ack --content '接手说明' 显式回复;遇必要问题用 task ask,提交结果用 report --status complete --summary '简短结论、关键交付和限制'。摘要先回答用户问题,不放任务 ID、版本、分支或流程日志;完整证据保留在 --content/--file 正文中。如果同一尝试已经交付或任务已归档,不要重复实施。${task.purpose === 'automation' ? '\n这是独立后台自动化执行记录:complete 是你明确提交的执行结果,无需等待用户 accepted。机械校验和外部发布由接入系统处理;纯咨询用 respond 关联原 comment,不重复提交 complete。必要问题用 ask 保留原文,由接入系统联系用户并回答。' : ''}${task.workspace ? `\n代码只能在独立目录 ${task.workspace.cwd} 修改。分支 ${task.workspace.branch}。交付前提交代码,结果说明提交、变更、验证与限制。不能修改原仓库、合并到用户分支或发布。` : ''}${task.workflow?.workType === 'read-only' ? '\n这是只读查询或分析任务:在本机会话中采集并交付报告,不修改代码或系统,不创建提交、构建、部署或终止进程。结果需说明采样时间、证据和限制。' : ''}${task.workflow?.kind === 'step' ? '\n完成报告会自动请求独立评审;评审要求修改时继续完成,重新提交 complete。只按用户授权运行测试。' : ''}` });
|
|
225
231
|
}
|
|
226
232
|
goalInstructions(task) {
|
|
227
233
|
return `${this.baseGoalInstructions(task)}\n启动 Agent 前,读取 task get 返回的 launchProfiles、defaultLaunchProfileId 和 roster.launchProfile。根据用户填写的 notes 判断适用任务;这些是选用建议,不是能力或性能保证。需要另一种模型或参数时,用 td collab spawn ${task.groupId} <agentSlug> --launch-profile <方案id> --cwd <项目目录> --session ${task.coordinator?.sessionId} 创建对应执行成员,再把子任务分派给它。已有成员及其代码执行会话保留创建时的启动方案,不因默认方案变更而更换模型。`;
|
|
@@ -248,6 +254,9 @@ export class CollaborationTaskStore {
|
|
|
248
254
|
content: `你被指定为任务「${task.title}」的协调者。\n目标:${task.spec}\n约束:${task.constraints || '未补充'}\n验收标准:${task.acceptance || '由用户决定'}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --text 查看原始记录;用 task list --group ${task.groupId} 查看工作组。需要拆分时用 task create --group ${task.groupId} --parent ${task.id} --title '子任务标题' --content '任务内容'。分派前查询 revision,再用 task assign --revision;把必要的人类决策交给 task ask,不推断成员忙闲,不停止其他成员终端。` });
|
|
249
255
|
}
|
|
250
256
|
create(owner, input, actor, origin) {
|
|
257
|
+
const purpose = taskPurpose(input.purpose);
|
|
258
|
+
if (purpose === 'automation' && (input.managed || input.parentTaskId || input.dependsOn?.length || input.coordinator || input.reviewers?.length || input.integration))
|
|
259
|
+
throw new CollaborationError('AUTOMATION_STANDALONE_REQUIRED', '后台自动化记录不加入人工目标、依赖或评审流程');
|
|
251
260
|
const title = text(input.title, '标题', 640, true), spec = text(input.spec, '任务内容', 32_000, true);
|
|
252
261
|
const constraints = text(input.constraints, '约束'), acceptance = text(input.acceptance, '完成标准');
|
|
253
262
|
if (input.dependsOn !== undefined && (!Array.isArray(input.dependsOn) || input.dependsOn.length > 32 || input.dependsOn.some(v => typeof v !== 'string')))
|
|
@@ -276,6 +285,8 @@ export class CollaborationTaskStore {
|
|
|
276
285
|
for (const dependency of [...dependsOn, ...(input.parentTaskId ? [input.parentTaskId] : [])]) {
|
|
277
286
|
if (!doc.tasks.some(t => t.id === dependency && t.groupId === input.groupId))
|
|
278
287
|
throw new CollaborationError('TASK_DEPENDENCY_NOT_FOUND', '父任务或依赖任务不在当前协作组', 404);
|
|
288
|
+
if (doc.tasks.find(t => t.id === dependency)?.purpose === 'automation')
|
|
289
|
+
throw new CollaborationError('AUTOMATION_STANDALONE_REQUIRED', '后台自动化记录不能作为人工任务父级或验收依赖');
|
|
279
290
|
}
|
|
280
291
|
const now = Date.now();
|
|
281
292
|
const parent = doc.tasks.find(t => t.id === input.parentTaskId);
|
|
@@ -307,11 +318,11 @@ export class CollaborationTaskStore {
|
|
|
307
318
|
if (!required.length || required.some(t => !dependsOn.includes(t.id)))
|
|
308
319
|
throw new CollaborationError('INTEGRATION_DEPENDENCIES_REQUIRED', '集成任务必须依赖目标下全部实施子任务');
|
|
309
320
|
}
|
|
310
|
-
const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance,
|
|
321
|
+
const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance, purpose,
|
|
311
322
|
coordinator: parent?.workflow ? parent.coordinator : input.coordinator !== undefined ? input.coordinator : input.parentTaskId ? doc.tasks.find(t => t.id === input.parentTaskId)?.coordinator ?? actor : null,
|
|
312
323
|
parentTaskId: input.parentTaskId ?? null, dependsOn, createdAt: now, updatedAt: now, revision: 1,
|
|
313
324
|
...(origin ? { origin: structuredClone(origin) } : {}), status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
|
|
314
|
-
this.event(task, 'created', actor, spec);
|
|
325
|
+
this.event(task, 'created', actor, spec).purpose = purpose;
|
|
315
326
|
if (parent?.constraints)
|
|
316
327
|
task.constraints = text([parent.constraints, constraints].filter(Boolean).join('\n'), '继承约束');
|
|
317
328
|
if (workflow?.kind === 'goal') {
|
|
@@ -335,6 +346,12 @@ export class CollaborationTaskStore {
|
|
|
335
346
|
}, origin);
|
|
336
347
|
}
|
|
337
348
|
apply(taskId, input, actor, origin) {
|
|
349
|
+
if (input.purpose !== undefined && input.kind !== 'configure')
|
|
350
|
+
throw new CollaborationError('INVALID_TASK_PURPOSE', 'purpose can only be changed through configure');
|
|
351
|
+
if (input.kind === 'configure' && input.purpose === undefined)
|
|
352
|
+
throw new CollaborationError('INVALID_TASK_PURPOSE', 'configure requires purpose');
|
|
353
|
+
if (input.kind === 'configure')
|
|
354
|
+
taskPurpose(input.purpose);
|
|
338
355
|
const content = text(input.content, '内容');
|
|
339
356
|
const summary = text(input.summary, '结果摘要', 2400);
|
|
340
357
|
if (summary && (input.kind !== 'report' || input.status !== 'complete'))
|
|
@@ -353,6 +370,8 @@ export class CollaborationTaskStore {
|
|
|
353
370
|
if (origin && ['accept', 'approve-plan', 'report', 'ask', 'submit-plan', 'review', 'respond'].includes(input.kind))
|
|
354
371
|
throw new CollaborationError('INTEGRATION_OPERATION_DENIED', '此操作需要用户或原执行成员身份', 403);
|
|
355
372
|
const coordination = actor === null || sameMember(actor, task.coordinator);
|
|
373
|
+
if (input.kind === 'configure' && actor !== null)
|
|
374
|
+
throw new CollaborationError('USER_DECISION_REQUIRED', '用途配置需要用户管理身份或授权的集成身份', 403);
|
|
356
375
|
if (['assign', 'coordinator', 'close', 'reopen', 'revise', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(input.kind) && !coordination)
|
|
357
376
|
throw new CollaborationError('TASK_PERMISSION_DENIED', '此操作需要用户或当前协调者', 403);
|
|
358
377
|
if (['answer', 'accept', 'approve-plan'].includes(input.kind) && actor !== null)
|
|
@@ -360,7 +379,7 @@ export class CollaborationTaskStore {
|
|
|
360
379
|
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'respond', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
361
380
|
throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
|
|
362
381
|
const userFollowup = actor === null && input.kind === 'revise' && !!content;
|
|
363
|
-
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
382
|
+
if (task.status !== 'open' && !['configure', 'reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
364
383
|
throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
|
|
365
384
|
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen' && !(task.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
366
385
|
throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
|
|
@@ -390,7 +409,15 @@ export class CollaborationTaskStore {
|
|
|
390
409
|
throw new CollaborationError('ATTEMPT_SUPERSEDED', '这轮分派已被替换,不能再请求用户决定', 409);
|
|
391
410
|
if (task.workflow && ['report', 'ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
|
|
392
411
|
throw new CollaborationError('ATTEMPT_SUPERSEDED', '自动协作只能推进当前分派', 409);
|
|
393
|
-
if (input.kind === '
|
|
412
|
+
if (input.kind === 'configure') {
|
|
413
|
+
const purpose = taskPurpose(input.purpose);
|
|
414
|
+
if (purpose === 'automation' && (task.workflow || task.parentTaskId || task.dependsOn.length || task.coordinator
|
|
415
|
+
|| doc.tasks.some(t => t.parentTaskId === task.id || t.workflow?.rootTaskId === task.id || t.dependsOn.includes(task.id))))
|
|
416
|
+
throw new CollaborationError('AUTOMATION_STANDALONE_REQUIRED', '只能把独立记录改为后台自动化用途');
|
|
417
|
+
task.purpose = purpose;
|
|
418
|
+
this.event(task, 'configured', actor, content || `任务用途已配置为 ${purpose}`).purpose = purpose;
|
|
419
|
+
}
|
|
420
|
+
else if (input.kind === 'assign') {
|
|
394
421
|
if (task.workflow?.kind === 'goal' && !sameMember(input.assignee ?? null, task.coordinator))
|
|
395
422
|
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '目标由协调者负责,请在子任务中分派实施');
|
|
396
423
|
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
@@ -407,6 +434,8 @@ export class CollaborationTaskStore {
|
|
|
407
434
|
this.notifyCoordinator(doc, task);
|
|
408
435
|
}
|
|
409
436
|
else if (input.kind === 'coordinator') {
|
|
437
|
+
if (task.purpose === 'automation' && input.coordinator)
|
|
438
|
+
throw new CollaborationError('AUTOMATION_STANDALONE_REQUIRED', '后台自动化记录不加入人工协调流程');
|
|
410
439
|
if (task.workflow && !input.coordinator)
|
|
411
440
|
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '自动协作需要协调者');
|
|
412
441
|
const previousCoordinator = task.coordinator;
|
|
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { timingSafeEqual } from 'node:crypto';
|
|
7
7
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
8
8
|
import { terminalMessage } from './collaborationStore.js';
|
|
9
|
-
import { IntegrationStore, INTEGRATION_PROTOCOL, integrationError } from './integrationStore.js';
|
|
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`); }
|
|
@@ -73,7 +73,7 @@ export class IntegrationRuntime {
|
|
|
73
73
|
integrationError('INVALID_INTEGRATION_POLICY', 'Policy identity must match the requested identity'); this.group(req.body?.groupId); this.store.update(req.body); return { ok: true, id: req.params.id }; }));
|
|
74
74
|
app.post('/admin/principals/:id/revoke', run(req => { admin(req); this.store.revoke(req.params.id); return { ok: true }; }));
|
|
75
75
|
app.get('/capabilities', run(req => { const p = principal(req); return { ...this.capabilities(), principal: { id: p.id, group_id: p.groupId, permissions: p.permissions, launch_profiles: p.launchProfiles.map(profile => ({ id: profile.id, agent_slug: profile.agentSlug, cwd_roots: profile.cwdRoots, startup_input_condition: !!profile.startupInput })) } }; }));
|
|
76
|
-
app.get('/tasks', run(req => { const p = principal(req); this.authorize(p, 'task.read', req.query.group); return { tasks: options.tasks.list(p.groupId, null) }; }));
|
|
76
|
+
app.get('/tasks', run(req => { const p = principal(req); this.authorize(p, 'task.read', req.query.group); return { tasks: options.tasks.list(p.groupId, null, req.query.purpose) }; }));
|
|
77
77
|
app.get('/tasks/:id', run(async (req) => { const p = principal(req); this.taskPermission(p, req.params.id, 'task.read'); return { task: await options.tasks.get(req.params.id, null) }; }));
|
|
78
78
|
app.post('/tasks', run(async (req) => {
|
|
79
79
|
const p = principal(req), raw = req.body?.input ?? {};
|
|
@@ -81,7 +81,7 @@ export class IntegrationRuntime {
|
|
|
81
81
|
if (raw.managed || raw.parentTaskId || raw.dependsOn?.length || raw.coordinatorSessionId || raw.reviewerSessionIds?.length || raw.integration)
|
|
82
82
|
integrationError('INTEGRATION_OPERATION_DENIED', 'Integration creates ordinary standalone tasks only', 403);
|
|
83
83
|
const input = { idempotencyKey: raw.idempotencyKey, groupId: p.groupId, title: raw.title, spec: raw.spec,
|
|
84
|
-
constraints: raw.constraints, acceptance: raw.acceptance, assignee: raw.assigneeSessionId ? options.peers.taskMember(String(raw.assigneeSessionId)) : undefined };
|
|
84
|
+
purpose: raw.purpose, constraints: raw.constraints, acceptance: raw.acceptance, assignee: raw.assigneeSessionId ? options.peers.taskMember(String(raw.assigneeSessionId)) : undefined };
|
|
85
85
|
if (input.assignee)
|
|
86
86
|
this.authorize(p, 'task.assign');
|
|
87
87
|
const task = await options.tasks.create(input, null, this.origin(p, req.body?.origin));
|
|
@@ -90,13 +90,13 @@ export class IntegrationRuntime {
|
|
|
90
90
|
}));
|
|
91
91
|
app.post('/tasks/:id', run(async (req) => {
|
|
92
92
|
const p = principal(req), raw = req.body?.input ?? {};
|
|
93
|
-
const permissions = { assign: 'task.assign', comment: 'task.comment', revise: 'task.revise', answer: 'task.answer' };
|
|
93
|
+
const permissions = { configure: 'task.configure', assign: 'task.assign', comment: 'task.comment', revise: 'task.revise', answer: 'task.answer' };
|
|
94
94
|
const permission = permissions[raw.kind];
|
|
95
95
|
if (!permission)
|
|
96
96
|
integrationError('INTEGRATION_OPERATION_DENIED', 'Operation requires a user or terminal member', 403);
|
|
97
97
|
this.taskPermission(p, req.params.id, permission);
|
|
98
98
|
const input = { kind: raw.kind, idempotencyKey: raw.idempotencyKey, content: raw.content,
|
|
99
|
-
expectedRevision: raw.expectedRevision, decisionId: raw.decisionId,
|
|
99
|
+
purpose: raw.purpose, expectedRevision: raw.expectedRevision, decisionId: raw.decisionId,
|
|
100
100
|
assignee: raw.assigneeSessionId ? options.peers.taskMember(String(raw.assigneeSessionId)) : undefined };
|
|
101
101
|
const task = await options.tasks.apply(req.params.id, input, null, this.origin(p, req.body?.origin));
|
|
102
102
|
this.sync();
|
|
@@ -147,7 +147,9 @@ export class IntegrationRuntime {
|
|
|
147
147
|
startup_input_conditions: true,
|
|
148
148
|
integration_policy_update: true,
|
|
149
149
|
session_restore_diagnostics: true,
|
|
150
|
-
|
|
150
|
+
native_identity_linux_flock_owner: process.platform === 'linux',
|
|
151
|
+
background_task_records: true, task_purpose_configuration: true, explicit_execution_state: true,
|
|
152
|
+
permissions: INTEGRATION_OPERATIONS };
|
|
151
153
|
}
|
|
152
154
|
error(error) {
|
|
153
155
|
return { type: 'error', ok: false, code: error instanceof CollaborationError ? error.code : 'INTEGRATION_INTERNAL_ERROR',
|
|
@@ -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.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore'];
|
|
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'];
|
|
7
7
|
export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
|
|
8
8
|
export function atomicJson(file, value) {
|
|
9
9
|
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function nativeIdentityDirectory(config) {
|
|
5
|
+
if (!config || typeof config !== 'object' || Array.isArray(config))
|
|
6
|
+
return null;
|
|
7
|
+
const c = config;
|
|
8
|
+
if (c.kind !== 'linux-flock-owner' || Object.keys(c).some(k => !['kind', 'directory'].includes(k))
|
|
9
|
+
|| typeof c.directory !== 'string' || c.directory.length > 1024 || /[\x00-\x1f\x7f]/.test(c.directory))
|
|
10
|
+
return null;
|
|
11
|
+
const expanded = c.directory.startsWith('~/') ? path.join(os.homedir(), c.directory.slice(2)) : c.directory;
|
|
12
|
+
if (!path.isAbsolute(expanded))
|
|
13
|
+
return null;
|
|
14
|
+
const directory = path.resolve(expanded), relative = path.relative(os.homedir(), directory);
|
|
15
|
+
return relative && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) ? directory : null;
|
|
16
|
+
}
|
|
17
|
+
const unavailable = { nativeId: null, outcome: 'unavailable' };
|
|
18
|
+
function processStat(text, pid) {
|
|
19
|
+
const close = text.lastIndexOf(')'), open = text.indexOf('(');
|
|
20
|
+
if (open < 0 || close <= open || Number(text.slice(0, open).trim()) !== pid)
|
|
21
|
+
return null;
|
|
22
|
+
const fields = text.slice(close + 1).trim().split(/\s+/);
|
|
23
|
+
const pgid = Number(fields[2]), tpgid = Number(fields[5]), start = fields[19];
|
|
24
|
+
return fields[0] !== 'Z' && pgid > 0 && pgid === tpgid && /^\d+$/.test(start ?? '') ? { pgid, tpgid, start, comm: text.slice(open + 1, close) } : null;
|
|
25
|
+
}
|
|
26
|
+
function safeFile(stat, uid) {
|
|
27
|
+
return stat.isFile() && stat.uid === BigInt(uid) && (stat.mode & 18n) === 0n;
|
|
28
|
+
}
|
|
29
|
+
function sameFile(a, b) { return a.dev === b.dev && a.ino === b.ino; }
|
|
30
|
+
function kernelWriter(locks, pid, stat) {
|
|
31
|
+
// Linux dev_t encoding, matched against /proc/locks major:minor:inode.
|
|
32
|
+
const major = (stat.dev >> 8n & 0xfffn) | (stat.dev >> 32n & 0xfffff000n);
|
|
33
|
+
const minor = (stat.dev & 0xffn) | (stat.dev >> 12n & 0xffffff00n);
|
|
34
|
+
return locks.split('\n').some(line => {
|
|
35
|
+
const m = line.match(/^\d+:\s+FLOCK\s+ADVISORY\s+WRITE\s+(\d+)\s+([0-9a-f]+):([0-9a-f]+):(\d+)\s+0\s+EOF\s*$/i);
|
|
36
|
+
return !!m && Number(m[1]) === pid && BigInt(`0x${m[2]}`) === major && BigInt(`0x${m[3]}`) === minor && BigInt(m[4]) === stat.ino;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function readOwner(file, uid) {
|
|
40
|
+
const handle = await fs.promises.open(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
41
|
+
try {
|
|
42
|
+
const stat = await handle.stat({ bigint: true });
|
|
43
|
+
if (!safeFile(stat, uid) || stat.size > 4096n)
|
|
44
|
+
return null;
|
|
45
|
+
const buffer = Buffer.alloc(4097), { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
46
|
+
if (bytesRead > 4096)
|
|
47
|
+
return null;
|
|
48
|
+
const value = JSON.parse(buffer.subarray(0, bytesRead).toString('utf8'));
|
|
49
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await handle.close();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Confirm exactly one UUID using a current foreground PID, owner starttime,
|
|
56
|
+
* its real open FD/inode, and the kernel's exclusive whole-file flock. */
|
|
57
|
+
export async function readNativeProcessIdentity(pid, config, expectedComm, expectedArgs) {
|
|
58
|
+
if (process.platform !== 'linux' || !Number.isSafeInteger(pid) || pid <= 1 || !process.getuid)
|
|
59
|
+
return unavailable;
|
|
60
|
+
const directory = nativeIdentityDirectory(config), uid = process.getuid();
|
|
61
|
+
if (!directory)
|
|
62
|
+
return unavailable;
|
|
63
|
+
let hadNativeDescriptor = false;
|
|
64
|
+
try {
|
|
65
|
+
const root = await fs.promises.lstat(directory, { bigint: true });
|
|
66
|
+
if (!root.isDirectory() || root.uid !== BigInt(uid) || (root.mode & 18n) !== 0n)
|
|
67
|
+
return unavailable;
|
|
68
|
+
const canonical = await fs.promises.realpath(directory), canonicalHome = await fs.promises.realpath(os.homedir());
|
|
69
|
+
if (!canonical.startsWith(`${canonicalHome}${path.sep}`))
|
|
70
|
+
return unavailable;
|
|
71
|
+
const proc = `/proc/${pid}`, initial = processStat(await fs.promises.readFile(`${proc}/stat`, 'utf8'), pid);
|
|
72
|
+
if (!initial || expectedComm && initial.comm !== expectedComm || (await fs.promises.stat(proc)).uid !== uid)
|
|
73
|
+
return { nativeId: null, outcome: 'process_changed' };
|
|
74
|
+
const argsMatch = async () => {
|
|
75
|
+
if (expectedArgs === undefined)
|
|
76
|
+
return true;
|
|
77
|
+
const handle = await fs.promises.open(`${proc}/cmdline`, fs.constants.O_RDONLY);
|
|
78
|
+
try {
|
|
79
|
+
const buffer = Buffer.alloc(16385), { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
80
|
+
return bytesRead < buffer.length && buffer.subarray(0, bytesRead).toString('utf8').replace(/\0$/, '').split('\0').join(' ').trim() === expectedArgs.trim();
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await handle.close();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
if (!await argsMatch())
|
|
87
|
+
return { nativeId: null, outcome: 'process_changed' };
|
|
88
|
+
const lockDescriptors = async () => {
|
|
89
|
+
const descriptors = await fs.promises.readdir(`${proc}/fd`);
|
|
90
|
+
if (descriptors.length > 4096)
|
|
91
|
+
throw new Error('descriptor bound');
|
|
92
|
+
const files = [];
|
|
93
|
+
for (const descriptor of descriptors) {
|
|
94
|
+
if (!/^\d+$/.test(descriptor))
|
|
95
|
+
continue;
|
|
96
|
+
const fd = `${proc}/fd/${descriptor}`;
|
|
97
|
+
let target;
|
|
98
|
+
try {
|
|
99
|
+
target = await fs.promises.readlink(fd);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code === 'ENOENT')
|
|
103
|
+
continue;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
if (path.dirname(target) === canonical && /\.lock(?: \(deleted\))?$/.test(target))
|
|
107
|
+
files.push({ fd, target });
|
|
108
|
+
}
|
|
109
|
+
return files.sort((a, b) => a.fd.localeCompare(b.fd));
|
|
110
|
+
};
|
|
111
|
+
const descriptors = await lockDescriptors();
|
|
112
|
+
hadNativeDescriptor = descriptors.length > 0;
|
|
113
|
+
const locks = await fs.promises.readFile('/proc/locks', 'utf8');
|
|
114
|
+
const verified = new Map();
|
|
115
|
+
let unproved = false;
|
|
116
|
+
for (const { fd, target } of descriptors) {
|
|
117
|
+
if (!target.endsWith('.lock')) {
|
|
118
|
+
unproved = true;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const nativeId = path.basename(target).slice(0, -5);
|
|
122
|
+
if (!/^[A-Za-z0-9._-]{1,256}$/.test(nativeId) || nativeId === '.' || nativeId === '..') {
|
|
123
|
+
unproved = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const lock = await fs.promises.lstat(target, { bigint: true }), held = await fs.promises.stat(fd, { bigint: true });
|
|
128
|
+
const ownerFile = path.join(canonical, `${nativeId}.owner.json`), owner = await readOwner(ownerFile, uid);
|
|
129
|
+
if (!safeFile(lock, uid) || !sameFile(lock, held) || !kernelWriter(locks, pid, lock)
|
|
130
|
+
|| owner?.pid !== pid || owner.process_start_id !== `linux:${initial.start}`) {
|
|
131
|
+
unproved = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
verified.set(nativeId, { fd, stat: lock, owner: ownerFile });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
unproved = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (verified.size > 1)
|
|
141
|
+
return { nativeId: null, outcome: 'ambiguous' };
|
|
142
|
+
if (unproved)
|
|
143
|
+
return { nativeId: null, outcome: 'unconfirmed' };
|
|
144
|
+
if (verified.size !== 1)
|
|
145
|
+
return unavailable;
|
|
146
|
+
const final = processStat(await fs.promises.readFile(`${proc}/stat`, 'utf8'), pid);
|
|
147
|
+
const currentLocks = await fs.promises.readFile('/proc/locks', 'utf8');
|
|
148
|
+
const [nativeId, evidence] = [...verified][0];
|
|
149
|
+
const current = await fs.promises.lstat(path.join(canonical, `${nativeId}.lock`), { bigint: true });
|
|
150
|
+
const held = await fs.promises.stat(evidence.fd, { bigint: true }), owner = await readOwner(evidence.owner, uid);
|
|
151
|
+
if (!final || final.start !== initial.start || final.comm !== initial.comm || final.pgid !== initial.pgid || final.tpgid !== initial.tpgid
|
|
152
|
+
|| !await argsMatch()
|
|
153
|
+
|| JSON.stringify(await lockDescriptors()) !== JSON.stringify(descriptors)
|
|
154
|
+
|| !safeFile(current, uid) || !sameFile(current, evidence.stat) || !sameFile(current, held)
|
|
155
|
+
|| !kernelWriter(currentLocks, pid, current) || owner?.pid !== pid || owner.process_start_id !== `linux:${final.start}`)
|
|
156
|
+
return { nativeId: null, outcome: 'process_changed' };
|
|
157
|
+
return { nativeId, outcome: 'verified' };
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return hadNativeDescriptor ? { nativeId: null, outcome: 'unconfirmed' } : unavailable;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -46,6 +46,8 @@ function diagnostic(candidate, process, reason) {
|
|
|
46
46
|
return { session_id: text(candidate.sessionId), backend_session_id: candidate.backendSessionId.startsWith('inventory:') ? null : text(candidate.backendSessionId),
|
|
47
47
|
backend_attached: candidate.backendAttached ?? null, tmux_session_name: text(candidate.tmuxSessionName),
|
|
48
48
|
tmux_session_id: text(process?.tmuxSessionId), pane_id: text(process?.paneId), pane_pid: pid(process?.panePid),
|
|
49
|
+
process_pid: pid(process?.processPid), native_identity_source: process?.nativeIdentitySource === 'argv'
|
|
50
|
+
|| process?.nativeIdentitySource === 'linux-flock-owner' ? process.nativeIdentitySource : null,
|
|
49
51
|
program: text(process?.program), process_source: text(process?.processSource), arguments_observed: process?.argumentsObserved === true,
|
|
50
52
|
agent_slug: text(process?.agentSlug), last_known_native_id: text(candidate.cachedNativeId), observed_native_id: text(process?.nativeId),
|
|
51
53
|
pgid: Number.isInteger(process?.pgid) ? process.pgid : null,
|
|
@@ -72,7 +74,7 @@ export async function assertNativeResumeAvailable(target, excludedBackendSession
|
|
|
72
74
|
throw new NativeResumeOwnerError('NATIVE_SESSION_ALREADY_RUNNING', blockers);
|
|
73
75
|
}
|
|
74
76
|
if (!process.confirmed)
|
|
75
|
-
blockers.push(diagnostic(candidate, process, process.shellFailure
|
|
77
|
+
blockers.push(diagnostic(candidate, process, process.nativeIdentityFailure ?? process.shellFailure
|
|
76
78
|
?? (process.argumentsObserved === false ? 'PROCESS_ARGUMENTS_UNAVAILABLE' : 'FOREGROUND_PROCESS_UNCONFIRMED')));
|
|
77
79
|
else if (process.agentSlug === target.slug && !process.nativeId)
|
|
78
80
|
blockers.push(diagnostic(candidate, process, 'NATIVE_SESSION_ID_MISSING'));
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import os from 'node:os';
|
|
16
|
+
import { nativeIdentityDirectory } from './nativeProcessIdentity.js';
|
|
16
17
|
export const PLUGINS_DIR = path.join(os.homedir(), '.termdock', 'agent-plugins');
|
|
17
18
|
export const MANIFEST_FILE = 'manifest.json';
|
|
18
19
|
export const ICON_FILE = 'icon.svg';
|
|
@@ -323,6 +324,13 @@ export function validateManifest(raw, dir) {
|
|
|
323
324
|
};
|
|
324
325
|
}
|
|
325
326
|
}
|
|
327
|
+
let nativeIdentity;
|
|
328
|
+
if (m.nativeIdentity !== undefined) {
|
|
329
|
+
if (!nativeIdentityDirectory(m.nativeIdentity))
|
|
330
|
+
errors.push('nativeIdentity must declare linux-flock-owner with a directory under home');
|
|
331
|
+
else
|
|
332
|
+
nativeIdentity = { kind: 'linux-flock-owner', directory: m.nativeIdentity.directory };
|
|
333
|
+
}
|
|
326
334
|
// Optional automatic-title provider. Commands are argv arrays rather than
|
|
327
335
|
// shell snippets so a UI-installed manifest cannot gain accidental shell
|
|
328
336
|
// interpolation beyond the executable it explicitly declares.
|
|
@@ -411,6 +419,7 @@ export function validateManifest(raw, dir) {
|
|
|
411
419
|
statuses,
|
|
412
420
|
hooks,
|
|
413
421
|
resume: resumeConfig,
|
|
422
|
+
nativeIdentity,
|
|
414
423
|
titleNamer,
|
|
415
424
|
},
|
|
416
425
|
};
|
|
@@ -315,6 +315,7 @@ export function buildResumeCommand(agent, sessionId, launchArgv) {
|
|
|
315
315
|
// ---------------------------------------------------------------------------
|
|
316
316
|
/** Plugin-defined resume configs, keyed by slug. */
|
|
317
317
|
const pluginResumeConfigs = new Map();
|
|
318
|
+
const pluginNativeIdentityConfigs = new Map();
|
|
318
319
|
/**
|
|
319
320
|
* Register plugin agents into the lookup maps. Plugins whose slug or aliases
|
|
320
321
|
* collide with built-in agents are skipped. Call `clearPluginAgents()` first
|
|
@@ -362,6 +363,8 @@ export function registerPluginAgents(plugins) {
|
|
|
362
363
|
staleFlags: manifest.resume.staleFlags ?? [],
|
|
363
364
|
});
|
|
364
365
|
}
|
|
366
|
+
if (manifest.nativeIdentity)
|
|
367
|
+
pluginNativeIdentityConfigs.set(manifest.slug, { ...manifest.nativeIdentity });
|
|
365
368
|
registered++;
|
|
366
369
|
}
|
|
367
370
|
return { registered, skipped };
|
|
@@ -376,6 +379,11 @@ export function clearPluginAgents() {
|
|
|
376
379
|
BY_ALIAS.delete(alias);
|
|
377
380
|
}
|
|
378
381
|
pluginResumeConfigs.clear();
|
|
382
|
+
pluginNativeIdentityConfigs.clear();
|
|
383
|
+
}
|
|
384
|
+
export function getPluginNativeIdentityConfig(slug) {
|
|
385
|
+
const config = pluginNativeIdentityConfigs.get(slug);
|
|
386
|
+
return config ? { ...config } : undefined;
|
|
379
387
|
}
|
|
380
388
|
export function getPluginResumeConfig(slug) {
|
|
381
389
|
return pluginResumeConfigs.get(slug);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
|
|
2
2
|
import { integrationError } from '../agent/integrationStore.js';
|
|
3
3
|
import { assertNativeResumeAvailable, collectNativeResumeOwnerCandidates } from '../agent/nativeResumeOwner.js';
|
|
4
|
+
import { readNativeProcessIdentity } from '../agent/nativeProcessIdentity.js';
|
|
4
5
|
import { integrationLaunchCommand } from '../agent/integrationSessions.js';
|
|
5
6
|
import { ensureTeam } from '../agent/collaborationTeam.js';
|
|
6
7
|
import { androidRecordings } from '../android/recording.js';
|
|
@@ -38,7 +39,7 @@ import { inspectLinuxInotifyUsage, isWatchResourceExhaustion } from '../utils/fi
|
|
|
38
39
|
import { writeResponseChunk } from '../utils/httpResponse.js';
|
|
39
40
|
import { getFocusSequence, removeClientFocusState, scanFocusTrackingMode, setClientFocusState, } from '../utils/tmuxFocus.js';
|
|
40
41
|
import { extractProgramLabelFromArgs, normalizeProgramName, normalizeTmuxMetadataProgram, selectTmuxForegroundProgram, } from '../utils/tmuxProgramDetection.js';
|
|
41
|
-
import { agentBySlug, buildResumeCommand, detectAgentFromCommand, inferResumeSessionId, listAgents, } from '../agent/registry.js';
|
|
42
|
+
import { agentBySlug, buildResumeCommand, detectAgentFromCommand, inferResumeSessionId, getPluginNativeIdentityConfig, listAgents, } from '../agent/registry.js';
|
|
42
43
|
import { applyAgentEvent, agentEventMatchesCurrentAgent, defaultAgentSessionState, parseAgentEvent, } from '../agent/session.js';
|
|
43
44
|
import { canRestoreDeadAgentShell, isConfirmedAgentResumeProcess, normalizePersistedAgentResumeInfo, } from '../agent/resumePersistence.js';
|
|
44
45
|
import { AgentResumeHistoryStore } from '../agent/resumeHistory.js';
|
|
@@ -1577,7 +1578,7 @@ function formatLocalCollaborationMessages(frontendSessionId, messages) {
|
|
|
1577
1578
|
* agent-keyed selector can never reach it. Delivery still needs an agent (the
|
|
1578
1579
|
* confirm gate and prompt formatting assume a TUI); driving a terminal —
|
|
1579
1580
|
* run/capture — is a shell operation and works on any pane. */
|
|
1580
|
-
async function inspectCollaborationTmux(binding, requestedPane, allowPlainPane = false) {
|
|
1581
|
+
async function inspectCollaborationTmux(binding, requestedPane, allowPlainPane = false, nativeProcessIdentity = false) {
|
|
1581
1582
|
if (!binding.tmuxSessionName)
|
|
1582
1583
|
return { state: 'offline', reason: 'TMUX_BINDING_MISSING' };
|
|
1583
1584
|
const name = `=${binding.tmuxSessionName}`;
|
|
@@ -1596,12 +1597,19 @@ async function inspectCollaborationTmux(binding, requestedPane, allowPlainPane =
|
|
|
1596
1597
|
const serverPid = Number((await runTmux(['display-message', '-p', '#{pid}'])).trim());
|
|
1597
1598
|
if (!Number.isInteger(serverPid) || serverPid <= 0)
|
|
1598
1599
|
throw new Error('TMUX_SERVER_IDENTITY_UNAVAILABLE');
|
|
1600
|
+
if (nativeProcessIdentity && process.platform === 'linux')
|
|
1601
|
+
await getProcessSnapshot(true);
|
|
1599
1602
|
const panes = await Promise.all(layout.windows.flatMap((window) => window.panes).map(async (pane) => {
|
|
1600
1603
|
// Native identity needs the foreground argv even for a named binary;
|
|
1601
1604
|
// pane_current_command alone cannot prove an exact resumed conversation.
|
|
1602
1605
|
const program = await resolveTmuxPaneProgram(pane, true);
|
|
1603
1606
|
const agent = detectAgentFromCommand(program?.rawArgs ?? program?.command ?? '', agentCustomCommands());
|
|
1604
|
-
const
|
|
1607
|
+
const identity = nativeProcessIdentity ? await observeProgramNativeIdentity(agent, program)
|
|
1608
|
+
: { nativeId: agent && program?.rawArgs ? inferResumeSessionId(agent, splitCommandToArgv(program.rawArgs)) : null, failure: null };
|
|
1609
|
+
if (identity.failure)
|
|
1610
|
+
integrationError(identity.failure === 'NATIVE_SESSION_IDENTITY_CONFLICT'
|
|
1611
|
+
? 'NATIVE_SESSION_ID_MISMATCH' : 'NATIVE_SESSION_OWNER_UNCONFIRMED', 'Current process native identity evidence conflicts or cannot be uniquely verified', 409);
|
|
1612
|
+
const nativeSessionId = identity.nativeId;
|
|
1605
1613
|
return { serverPid, sessionId: layout.sessionId, paneId: pane.id, panePid: pane.pid,
|
|
1606
1614
|
agentSlug: agent?.slug ?? '', nativeSessionId, cwd: pane.currentPath,
|
|
1607
1615
|
isShell: shellNamesBackend.has(normalizeProgramName(program?.command ?? '')?.toLowerCase() ?? '') };
|
|
@@ -1872,7 +1880,7 @@ export const integrationSessionAdapter = {
|
|
|
1872
1880
|
const binding = { sessionId: record.session_id, backendSessionId: terminal.backendSessionId,
|
|
1873
1881
|
mode: 'tmux', tmuxSessionName: terminal.tmuxSessionName, agentSlug: record.agent_slug,
|
|
1874
1882
|
nativeSessionId: record.agent_native_session_id, pane: record.terminal_binding ?? null };
|
|
1875
|
-
const inspected = await inspectCollaborationTmux(binding);
|
|
1883
|
+
const inspected = await inspectCollaborationTmux(binding, undefined, false, true);
|
|
1876
1884
|
if (inspected.state === 'offline')
|
|
1877
1885
|
return { exists: false, running: false, shell: false, agentSlug: null, nativeId: null };
|
|
1878
1886
|
if (inspected.state !== 'ready' || !inspected.pane)
|
|
@@ -1938,11 +1946,13 @@ async function assertIntegrationNativeResumeAvailable(record) {
|
|
|
1938
1946
|
return Promise.all(layout.windows.flatMap(window => window.panes).map(async (pane) => {
|
|
1939
1947
|
const program = await resolveTmuxPaneProgram(pane, true, true);
|
|
1940
1948
|
const agent = detectAgentFromCommand(program?.rawArgs ?? program?.command ?? '', agentCustomCommands());
|
|
1949
|
+
const identity = await observeProgramNativeIdentity(agent, program);
|
|
1941
1950
|
const shell = await inspectIntegrationShell({ panePid: pane.pid,
|
|
1942
1951
|
isShell: !agent && shellNamesBackend.has(normalizeProgramName(program?.command ?? '')?.toLowerCase() ?? '') });
|
|
1943
|
-
return { confirmed: shell.confirmed || program?.source === 'tmux-tty' && Boolean(program.rawArgs),
|
|
1952
|
+
return { confirmed: !identity.failure && (shell.confirmed || program?.source === 'tmux-tty' && Boolean(program.rawArgs)),
|
|
1944
1953
|
agentSlug: agent?.slug ?? null,
|
|
1945
|
-
nativeId:
|
|
1954
|
+
nativeId: identity.nativeId, nativeIdentityFailure: identity.failure, nativeIdentitySource: identity.source,
|
|
1955
|
+
processPid: program?.processPid,
|
|
1946
1956
|
paneId: pane.id, panePid: pane.pid, tmuxSessionId: layout.sessionId, program: program?.command ?? null,
|
|
1947
1957
|
processSource: program?.source, argumentsObserved: !!program?.rawArgs,
|
|
1948
1958
|
shellFailure: shell.failure, pgid: shell.pgid, tpgid: shell.tpgid };
|
|
@@ -3948,13 +3958,15 @@ async function resolveTmuxPaneProgram(pane, requireArgs = false, refreshTty = fa
|
|
|
3948
3958
|
}
|
|
3949
3959
|
const selected = selectTmuxForegroundProgram({
|
|
3950
3960
|
panePid: pane.pid,
|
|
3961
|
+
includePaneProcess: requireArgs,
|
|
3951
3962
|
rows,
|
|
3952
3963
|
shellNames: shellNamesBackend,
|
|
3953
3964
|
genericProgramNames,
|
|
3954
3965
|
extractProgramLabel,
|
|
3955
3966
|
});
|
|
3956
3967
|
if (selected) {
|
|
3957
|
-
return { command: selected.command, source: 'tmux-tty', rawArgs: selected.rawArgs
|
|
3968
|
+
return { command: selected.command, source: 'tmux-tty', rawArgs: selected.rawArgs,
|
|
3969
|
+
...(selected.processPid ? { processPid: selected.processPid, processComm: selected.processComm } : {}) };
|
|
3958
3970
|
}
|
|
3959
3971
|
}
|
|
3960
3972
|
catch {
|
|
@@ -3962,6 +3974,23 @@ async function resolveTmuxPaneProgram(pane, requireArgs = false, refreshTty = fa
|
|
|
3962
3974
|
}
|
|
3963
3975
|
return { command, source: 'tmux-pane', rawArgs: null };
|
|
3964
3976
|
}
|
|
3977
|
+
async function observeProgramNativeIdentity(agent, program) {
|
|
3978
|
+
const fromArgs = agent && program?.rawArgs ? inferResumeSessionId(agent, splitCommandToArgv(program.rawArgs)) : null;
|
|
3979
|
+
const config = agent ? getPluginNativeIdentityConfig(agent.slug) : undefined;
|
|
3980
|
+
if (config && program?.processPid) {
|
|
3981
|
+
const proof = await readNativeProcessIdentity(program.processPid, config, program.processComm, program.rawArgs ?? undefined);
|
|
3982
|
+
if (proof.outcome === 'ambiguous' || proof.outcome === 'process_changed' || proof.outcome === 'unconfirmed')
|
|
3983
|
+
return { nativeId: null, source: null,
|
|
3984
|
+
failure: proof.outcome === 'ambiguous' ? 'NATIVE_SESSION_IDENTITY_AMBIGUOUS'
|
|
3985
|
+
: proof.outcome === 'unconfirmed' ? 'NATIVE_SESSION_OWNERSHIP_UNCONFIRMED' : 'NATIVE_SESSION_PROCESS_CHANGED' };
|
|
3986
|
+
if (proof.nativeId) {
|
|
3987
|
+
if (fromArgs && fromArgs !== proof.nativeId)
|
|
3988
|
+
return { nativeId: null, source: null, failure: 'NATIVE_SESSION_IDENTITY_CONFLICT' };
|
|
3989
|
+
return { nativeId: proof.nativeId, source: 'linux-flock-owner', failure: null };
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
return { nativeId: fromArgs, source: fromArgs ? 'argv' : null, failure: null };
|
|
3993
|
+
}
|
|
3965
3994
|
/**
|
|
3966
3995
|
* Extract a human-readable program label from a full command line.
|
|
3967
3996
|
*
|