termdock 1.4.294 → 1.4.296

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/dist/client/assets/App-BiLjlrk-.js +171 -0
  2. package/dist/client/assets/App-BiLjlrk-.js.br +0 -0
  3. package/dist/client/assets/App-BiLjlrk-.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-D2hm-GX-.js → ChangeWalkthroughPanel-DXprT-7m.js} +1 -1
  5. package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-DLmKJylJ.js → DagPlayground-DZcUunT5.js} +1 -1
  8. package/dist/client/assets/DagPlayground-DZcUunT5.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-DZcUunT5.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-Bje07ujV.js → DiffLab-Brv4DCTb.js} +1 -1
  11. package/dist/client/assets/DiffLab-Brv4DCTb.js.br +0 -0
  12. package/dist/client/assets/DiffLab-Brv4DCTb.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-CcLHnQu-.js → DiffReview-BuLu1qCi.js} +1 -1
  14. package/dist/client/assets/DiffReview-BuLu1qCi.js.br +0 -0
  15. package/dist/client/assets/DiffReview-BuLu1qCi.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-CIieaScN.js → DiffReviewLab-DSGamuAD.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-DSGamuAD.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-DSGamuAD.js.gz +0 -0
  19. package/dist/client/assets/{DiffViewer-0nWShFhC.js → DiffViewer-BUCYxg27.js} +1 -1
  20. package/dist/client/assets/DiffViewer-BUCYxg27.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-BUCYxg27.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-B2Hrizox.js → ModelPreview-Bnlo-oVE.js} +1 -1
  23. package/dist/client/assets/ModelPreview-Bnlo-oVE.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-Bnlo-oVE.js.gz +0 -0
  25. package/dist/client/assets/{RightSidebar-CiYxOpjs.js → RightSidebar-r_3Lk5Rp.js} +48 -52
  26. package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-BvhG-Int.js → abnfDiagram-VCTEODGH-K6Ppkrbt.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.gz +0 -0
  31. package/dist/client/assets/app-CATnTUZF.js +35 -0
  32. package/dist/client/assets/app-CATnTUZF.js.br +0 -0
  33. package/dist/client/assets/app-CATnTUZF.js.gz +0 -0
  34. package/dist/client/assets/{arc-Cfd1r0w4.js → arc-Co2OQqtH.js} +1 -1
  35. package/dist/client/assets/arc-Co2OQqtH.js.br +0 -0
  36. package/dist/client/assets/arc-Co2OQqtH.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CYcaAY_y.js → architectureDiagram-5GKGNRK7-BQnl7tVF.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-uGZowYBC.js → blockDiagram-I7D4REHJ-Bm5D5-qq.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-BuAjHXw2.js → c4Diagram-7LVT6UL2-B9oPpey8.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.gz +0 -0
  46. package/dist/client/assets/channel-Czv2W4qA.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-DpblcTaf.js → chunk-2Q5K7J3B-TMaWHH0U.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-9DQ9Xhrj.js → chunk-5VM5RSS4-CH7XpknV.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-C99DgtYa.js → chunk-F27PBJKO-CJ1rotBe.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-CJs2LWtE.js → chunk-IMKFNOWR-UYKZ5uOG.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-Bi6NDDYZ.js → chunk-JWPE2WC7-BYL8aXjQ.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-EDihZ7ei.js → chunk-POPQ4Y6H-C3E8yfU2.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-Deb_Pzwv.js → chunk-SVP7TREG-DCTDYa1E.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-C6wEoKIF.js → chunk-TICWLB2K-wHiaOsBh.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-DkhA8D3i.js → chunk-XXDRQBXY-D_d479hK.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-BdvOOL9k.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BdvOOL9k.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-CAImJ8rh.js → cose-bilkent-JH36ORCC-DnhlPBLr.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-D3Egll3y.js → cynefin-OW5HDTMX-BsjzNqJZ.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.br +0 -0
  71. package/dist/client/assets/{cynefin-OW5HDTMX-D3Egll3y.js.gz → cynefin-OW5HDTMX-BsjzNqJZ.js.gz} +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-MjvCZkx7.js → cynefinDiagram-5FMLGOSQ-sVUb0zY5.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-CrJn07Be.js → dagre-GXQ25YYZ-YErk8S-c.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-x2YTsmxU.js → diagram-S7CK7UJ4-BaHbV5ho.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-D98MzrbS.js → diagram-UQ7AKVKN-BocCYx3V.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-DB_ACbg-.js → diagram-VSXAHHWV-ZMTYwWOQ.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-Drt4iWsQ.js → diagram-VX7I27RA-xmk_EY1h.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-D8mGxerZ.js → diagram-Z3DM3KII-DKDY_cG6.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-DTYnrl_Q.js → ebnfDiagram-PWID7BFC-C_0z-Jx9.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-ChsNovQf.js → erDiagram-RLTQ6QDP-DHUaG7mt.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-DsFiio6_.js → flowDiagram-HODETNUW-BMNsdMBD.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BbHyLMqr.js → ganttDiagram-EL5Y4UJY-BH1AiKuF.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-3rZyzlwR.js → gestureArbiter-DvupkPra.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-DvupkPra.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-DvupkPra.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CTPZoYHy.js → gitGraphDiagram-WWUBYQGX-DAgV0MV-.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.gz +0 -0
  111. package/dist/client/assets/{index-DxhkHH1u.css → index-Bi9A6QUP.css} +1 -1
  112. package/dist/client/assets/index-Bi9A6QUP.css.br +0 -0
  113. package/dist/client/assets/index-Bi9A6QUP.css.gz +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-DqG9-3Dv.js → infoDiagram-27XIBGKW-C7iSg0cB.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-C7BUr6Q1.js → ishikawaDiagram-5VMMS53U-BpZwsQvw.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-DeKgSie7.js → journeyDiagram-3NMN7TZE-DXEeFLvw.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-BXLMRLCs.js → kanban-definition-UXKFOSKX-CV9ivL5u.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.gz +0 -0
  124. package/dist/client/assets/{layout-BIGVsIVB.js → layout-DsKKsJKX.js} +1 -1
  125. package/dist/client/assets/layout-DsKKsJKX.js.br +0 -0
  126. package/dist/client/assets/layout-DsKKsJKX.js.gz +0 -0
  127. package/dist/client/assets/{linear-BhqHS_Ls.js → linear-Dl9MDNV3.js} +1 -1
  128. package/dist/client/assets/linear-Dl9MDNV3.js.br +0 -0
  129. package/dist/client/assets/linear-Dl9MDNV3.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-DYTWj2Pu.js → mermaid.core-CxZFe3as.js} +5 -5
  131. package/dist/client/assets/mermaid.core-CxZFe3as.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-CxZFe3as.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-BKVI5pEJ.js → mindmap-definition-YA3MSWOX-C3ormYdL.js} +2 -2
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-DyrBaUP8.js → passwordBootstrap-a8z6jJNY.js} +2 -2
  137. package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-DdRPwDgf.js → pegDiagram-XKGWAZYB-CG0wNylH.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-KzblsFOl.js → pieDiagram-E7YTZNPT-D2-ydqyX.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-Co2Mu1vo.js → quadrantDiagram-AXDQQJYC-CK3fRC18.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DjvJ0Ysn.js → railroadDiagram-O6MQD6OU-V4EFlydu.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-VL4F6lii.js → referenceLongPress-BjmYd1vB.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-BjmYd1vB.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-BjmYd1vB.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-QJ6afxsR.js → requirementDiagram-BXWQKSXE-Di1_tyAX.js} +1 -1
  155. package/dist/client/assets/{requirementDiagram-BXWQKSXE-QJ6afxsR.js.br → requirementDiagram-BXWQKSXE-Di1_tyAX.js.br} +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-Di1_tyAX.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CYd9OO8p.js → sankeyDiagram-P5KCCOFB-D-qpWNza.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-Cau471RQ.js → sequenceDiagram-WJ2MYXX4-DmIxafrB.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-CWBvI9Ex.js → sizeCapture-INFHLROL-4MTh3tTt.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-BpYEa9ns.js → sparkles-Dk93lHg0.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-ih47Vg8d.js → stateDiagram-D77RDMKH-CgYgneIv.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CvPf0fxf.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-Cks23MF4.js → swimlanes-42K2YHIH-OWA9HFVf.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CZ_OauyW.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-C3Z6mGRy.js → timeline-definition-24CTP7MA-BAzx5DyO.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-onS3H-d9.js → triangle-alert-CUIBOwqW.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-9GLhPNes.js → vennDiagram-4TSXK5OY-CBZ-PhTQ.js} +1 -1
  180. package/dist/client/assets/{vennDiagram-4TSXK5OY-9GLhPNes.js.br → vennDiagram-4TSXK5OY-CBZ-PhTQ.js.br} +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-CBZ-PhTQ.js.gz +0 -0
  182. package/dist/client/assets/{videoCompression-KoXLuxZT.js → videoCompression-BCnqCkRK.js} +1 -1
  183. package/dist/client/assets/videoCompression-BCnqCkRK.js.br +0 -0
  184. package/dist/client/assets/videoCompression-BCnqCkRK.js.gz +0 -0
  185. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-BsZeAlBb.js → wardleyDiagram-VM6X3IG4-DbWzC6l5.js} +1 -1
  186. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.br +0 -0
  187. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.gz +0 -0
  188. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BRT0eEuC.js → xychartDiagram-S5SC5T6Z-BmApnTWE.js} +1 -1
  189. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.br +0 -0
  190. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.gz +0 -0
  191. package/dist/client/index.html +2 -2
  192. package/dist/client/index.html.br +0 -0
  193. package/dist/client/index.html.gz +0 -0
  194. package/dist/client/sw.js +1 -1
  195. package/dist/client/sw.js.br +0 -0
  196. package/dist/client/sw.js.gz +0 -0
  197. package/dist/server/agent/collaborationCli.js +45 -4
  198. package/dist/server/agent/collaborationDeliveryWorker.js +1 -1
  199. package/dist/server/agent/collaborationPeerTransport.js +8 -2
  200. package/dist/server/agent/collaborationRoutes.js +18 -2
  201. package/dist/server/agent/collaborationService.js +43 -1
  202. package/dist/server/agent/collaborationTaskBundles.js +121 -0
  203. package/dist/server/agent/collaborationTaskCli.js +54 -0
  204. package/dist/server/agent/collaborationTaskRoutes.js +58 -0
  205. package/dist/server/agent/collaborationTaskService.js +355 -0
  206. package/dist/server/agent/collaborationTaskStore.js +773 -0
  207. package/dist/server/agent/collaborationTaskTypes.js +1 -0
  208. package/dist/server/agent/collaborationTaskWorkspace.js +100 -0
  209. package/dist/server/entry.js +11 -1
  210. package/dist/server/federation/runtime.js +6 -1
  211. package/dist/server/routes/terminal.js +66 -2
  212. package/package.json +1 -1
  213. package/runtime-manifest.json +3 -3
  214. package/dist/client/assets/App-CDGrOo_r.js +0 -158
  215. package/dist/client/assets/App-CDGrOo_r.js.br +0 -0
  216. package/dist/client/assets/App-CDGrOo_r.js.gz +0 -0
  217. package/dist/client/assets/ChangeWalkthroughPanel-D2hm-GX-.js.br +0 -0
  218. package/dist/client/assets/ChangeWalkthroughPanel-D2hm-GX-.js.gz +0 -0
  219. package/dist/client/assets/DagPlayground-DLmKJylJ.js.br +0 -0
  220. package/dist/client/assets/DagPlayground-DLmKJylJ.js.gz +0 -0
  221. package/dist/client/assets/DiffLab-Bje07ujV.js.br +0 -0
  222. package/dist/client/assets/DiffLab-Bje07ujV.js.gz +0 -0
  223. package/dist/client/assets/DiffReview-CcLHnQu-.js.br +0 -0
  224. package/dist/client/assets/DiffReview-CcLHnQu-.js.gz +0 -0
  225. package/dist/client/assets/DiffReviewLab-CIieaScN.js.br +0 -0
  226. package/dist/client/assets/DiffReviewLab-CIieaScN.js.gz +0 -0
  227. package/dist/client/assets/DiffViewer-0nWShFhC.js.br +0 -0
  228. package/dist/client/assets/DiffViewer-0nWShFhC.js.gz +0 -0
  229. package/dist/client/assets/ModelPreview-B2Hrizox.js.br +0 -0
  230. package/dist/client/assets/ModelPreview-B2Hrizox.js.gz +0 -0
  231. package/dist/client/assets/RightSidebar-CiYxOpjs.js.br +0 -0
  232. package/dist/client/assets/RightSidebar-CiYxOpjs.js.gz +0 -0
  233. package/dist/client/assets/abnfDiagram-VCTEODGH-BvhG-Int.js.br +0 -0
  234. package/dist/client/assets/abnfDiagram-VCTEODGH-BvhG-Int.js.gz +0 -0
  235. package/dist/client/assets/app-CjvwGDVy.js +0 -35
  236. package/dist/client/assets/app-CjvwGDVy.js.br +0 -0
  237. package/dist/client/assets/app-CjvwGDVy.js.gz +0 -0
  238. package/dist/client/assets/arc-Cfd1r0w4.js.br +0 -0
  239. package/dist/client/assets/arc-Cfd1r0w4.js.gz +0 -0
  240. package/dist/client/assets/architectureDiagram-5GKGNRK7-CYcaAY_y.js.br +0 -0
  241. package/dist/client/assets/architectureDiagram-5GKGNRK7-CYcaAY_y.js.gz +0 -0
  242. package/dist/client/assets/blockDiagram-I7D4REHJ-uGZowYBC.js.br +0 -0
  243. package/dist/client/assets/blockDiagram-I7D4REHJ-uGZowYBC.js.gz +0 -0
  244. package/dist/client/assets/c4Diagram-7LVT6UL2-BuAjHXw2.js.br +0 -0
  245. package/dist/client/assets/c4Diagram-7LVT6UL2-BuAjHXw2.js.gz +0 -0
  246. package/dist/client/assets/channel-klM1FsDR.js +0 -1
  247. package/dist/client/assets/chunk-F27PBJKO-C99DgtYa.js.br +0 -0
  248. package/dist/client/assets/chunk-F27PBJKO-C99DgtYa.js.gz +0 -0
  249. package/dist/client/assets/chunk-IMKFNOWR-CJs2LWtE.js.br +0 -0
  250. package/dist/client/assets/chunk-IMKFNOWR-CJs2LWtE.js.gz +0 -0
  251. package/dist/client/assets/chunk-SVP7TREG-Deb_Pzwv.js.br +0 -0
  252. package/dist/client/assets/chunk-SVP7TREG-Deb_Pzwv.js.gz +0 -0
  253. package/dist/client/assets/chunk-TICWLB2K-C6wEoKIF.js.br +0 -0
  254. package/dist/client/assets/chunk-TICWLB2K-C6wEoKIF.js.gz +0 -0
  255. package/dist/client/assets/classDiagram-ZZMXUADV-Ccewxt_X.js +0 -1
  256. package/dist/client/assets/classDiagram-v2-VYDZK3BY-Ccewxt_X.js +0 -1
  257. package/dist/client/assets/cose-bilkent-JH36ORCC-CAImJ8rh.js.br +0 -0
  258. package/dist/client/assets/cose-bilkent-JH36ORCC-CAImJ8rh.js.gz +0 -0
  259. package/dist/client/assets/cynefin-OW5HDTMX-D3Egll3y.js.br +0 -0
  260. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MjvCZkx7.js.br +0 -0
  261. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MjvCZkx7.js.gz +0 -0
  262. package/dist/client/assets/dagre-GXQ25YYZ-CrJn07Be.js.br +0 -0
  263. package/dist/client/assets/dagre-GXQ25YYZ-CrJn07Be.js.gz +0 -0
  264. package/dist/client/assets/diagram-S7CK7UJ4-x2YTsmxU.js.br +0 -0
  265. package/dist/client/assets/diagram-S7CK7UJ4-x2YTsmxU.js.gz +0 -0
  266. package/dist/client/assets/diagram-UQ7AKVKN-D98MzrbS.js.br +0 -0
  267. package/dist/client/assets/diagram-UQ7AKVKN-D98MzrbS.js.gz +0 -0
  268. package/dist/client/assets/diagram-VSXAHHWV-DB_ACbg-.js.br +0 -0
  269. package/dist/client/assets/diagram-VSXAHHWV-DB_ACbg-.js.gz +0 -0
  270. package/dist/client/assets/diagram-VX7I27RA-Drt4iWsQ.js.br +0 -0
  271. package/dist/client/assets/diagram-VX7I27RA-Drt4iWsQ.js.gz +0 -0
  272. package/dist/client/assets/diagram-Z3DM3KII-D8mGxerZ.js.br +0 -0
  273. package/dist/client/assets/diagram-Z3DM3KII-D8mGxerZ.js.gz +0 -0
  274. package/dist/client/assets/ebnfDiagram-PWID7BFC-DTYnrl_Q.js.br +0 -0
  275. package/dist/client/assets/ebnfDiagram-PWID7BFC-DTYnrl_Q.js.gz +0 -0
  276. package/dist/client/assets/erDiagram-RLTQ6QDP-ChsNovQf.js.br +0 -0
  277. package/dist/client/assets/erDiagram-RLTQ6QDP-ChsNovQf.js.gz +0 -0
  278. package/dist/client/assets/flowDiagram-HODETNUW-DsFiio6_.js.br +0 -0
  279. package/dist/client/assets/flowDiagram-HODETNUW-DsFiio6_.js.gz +0 -0
  280. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BbHyLMqr.js.br +0 -0
  281. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BbHyLMqr.js.gz +0 -0
  282. package/dist/client/assets/gestureArbiter-3rZyzlwR.js.br +0 -0
  283. package/dist/client/assets/gestureArbiter-3rZyzlwR.js.gz +0 -0
  284. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CTPZoYHy.js.br +0 -0
  285. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CTPZoYHy.js.gz +0 -0
  286. package/dist/client/assets/index-DxhkHH1u.css.br +0 -0
  287. package/dist/client/assets/index-DxhkHH1u.css.gz +0 -0
  288. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C7BUr6Q1.js.br +0 -0
  289. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C7BUr6Q1.js.gz +0 -0
  290. package/dist/client/assets/journeyDiagram-3NMN7TZE-DeKgSie7.js.br +0 -0
  291. package/dist/client/assets/journeyDiagram-3NMN7TZE-DeKgSie7.js.gz +0 -0
  292. package/dist/client/assets/kanban-definition-UXKFOSKX-BXLMRLCs.js.br +0 -0
  293. package/dist/client/assets/kanban-definition-UXKFOSKX-BXLMRLCs.js.gz +0 -0
  294. package/dist/client/assets/layout-BIGVsIVB.js.br +0 -0
  295. package/dist/client/assets/layout-BIGVsIVB.js.gz +0 -0
  296. package/dist/client/assets/linear-BhqHS_Ls.js.br +0 -0
  297. package/dist/client/assets/linear-BhqHS_Ls.js.gz +0 -0
  298. package/dist/client/assets/mermaid.core-DYTWj2Pu.js.br +0 -0
  299. package/dist/client/assets/mermaid.core-DYTWj2Pu.js.gz +0 -0
  300. package/dist/client/assets/mindmap-definition-YA3MSWOX-BKVI5pEJ.js.br +0 -0
  301. package/dist/client/assets/mindmap-definition-YA3MSWOX-BKVI5pEJ.js.gz +0 -0
  302. package/dist/client/assets/passwordBootstrap-DyrBaUP8.js.br +0 -0
  303. package/dist/client/assets/passwordBootstrap-DyrBaUP8.js.gz +0 -0
  304. package/dist/client/assets/pegDiagram-XKGWAZYB-DdRPwDgf.js.br +0 -0
  305. package/dist/client/assets/pegDiagram-XKGWAZYB-DdRPwDgf.js.gz +0 -0
  306. package/dist/client/assets/pieDiagram-E7YTZNPT-KzblsFOl.js.br +0 -0
  307. package/dist/client/assets/pieDiagram-E7YTZNPT-KzblsFOl.js.gz +0 -0
  308. package/dist/client/assets/quadrantDiagram-AXDQQJYC-Co2Mu1vo.js.br +0 -0
  309. package/dist/client/assets/quadrantDiagram-AXDQQJYC-Co2Mu1vo.js.gz +0 -0
  310. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjvJ0Ysn.js.br +0 -0
  311. package/dist/client/assets/railroadDiagram-O6MQD6OU-DjvJ0Ysn.js.gz +0 -0
  312. package/dist/client/assets/referenceLongPress-VL4F6lii.js.br +0 -0
  313. package/dist/client/assets/referenceLongPress-VL4F6lii.js.gz +0 -0
  314. package/dist/client/assets/requirementDiagram-BXWQKSXE-QJ6afxsR.js.gz +0 -0
  315. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYd9OO8p.js.br +0 -0
  316. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYd9OO8p.js.gz +0 -0
  317. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cau471RQ.js.br +0 -0
  318. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cau471RQ.js.gz +0 -0
  319. package/dist/client/assets/sizeCapture-INFHLROL-CWBvI9Ex.js.br +0 -0
  320. package/dist/client/assets/sizeCapture-INFHLROL-CWBvI9Ex.js.gz +0 -0
  321. package/dist/client/assets/stateDiagram-D77RDMKH-ih47Vg8d.js.br +0 -0
  322. package/dist/client/assets/stateDiagram-D77RDMKH-ih47Vg8d.js.gz +0 -0
  323. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-D0ybQxyy.js +0 -1
  324. package/dist/client/assets/swimlanes-42K2YHIH-Cks23MF4.js.br +0 -0
  325. package/dist/client/assets/swimlanes-42K2YHIH-Cks23MF4.js.gz +0 -0
  326. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CTe40kKh.js +0 -8
  327. package/dist/client/assets/timeline-definition-24CTP7MA-C3Z6mGRy.js.br +0 -0
  328. package/dist/client/assets/timeline-definition-24CTP7MA-C3Z6mGRy.js.gz +0 -0
  329. package/dist/client/assets/vennDiagram-4TSXK5OY-9GLhPNes.js.gz +0 -0
  330. package/dist/client/assets/videoCompression-KoXLuxZT.js.br +0 -0
  331. package/dist/client/assets/videoCompression-KoXLuxZT.js.gz +0 -0
  332. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsZeAlBb.js.br +0 -0
  333. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsZeAlBb.js.gz +0 -0
  334. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BRT0eEuC.js.br +0 -0
  335. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BRT0eEuC.js.gz +0 -0
@@ -0,0 +1,773 @@
1
+ import { randomBytes, createHash } from 'node:crypto';
2
+ import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ import { CollaborationError } from './collaborationProtocol.js';
5
+ import { taskMemberKey } from './collaborationTaskTypes.js';
6
+ const id = () => randomBytes(16).toString('hex');
7
+ const sameMember = (a, b) => a !== null && b !== null && taskMemberKey(a) === taskMemberKey(b);
8
+ function text(value, name, limit = 32_000, required = false) {
9
+ if (value !== undefined && typeof value !== 'string')
10
+ throw new CollaborationError('INVALID_TASK', `${name}必须是文本`);
11
+ const result = typeof value === 'string' ? value.trim() : '';
12
+ if (Buffer.byteLength(result) > limit || required && !result)
13
+ throw new CollaborationError('INVALID_TASK', `${name}${required ? '不能为空,且' : ''}最多 ${limit} 字节`);
14
+ return result;
15
+ }
16
+ function member(value) {
17
+ const m = value;
18
+ if (!m || typeof m.serviceId !== 'string' || !/^12D3KooW[a-zA-Z0-9]{30,60}$/.test(m.serviceId)
19
+ || typeof m.sessionId !== 'string' || !m.sessionId || m.sessionId.length > 256 || m.sessionId.startsWith('remote:')) {
20
+ throw new CollaborationError('INVALID_MEMBER', '成员身份无效,请重新选择');
21
+ }
22
+ return { serviceId: m.serviceId, sessionId: m.sessionId };
23
+ }
24
+ const stable = (value) => JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
25
+ ? Object.fromEntries(Object.keys(v).sort().map(key => [key, v[key]])) : v);
26
+ function validateSnapshot(task) {
27
+ const validId = (value) => typeof value === 'string' && /^[a-f0-9]{32}$/.test(value);
28
+ const time = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
29
+ const reportStatus = (value) => ['ack', 'working', 'blocked', 'complete', 'failed'].includes(String(value));
30
+ const invalid = () => { throw new CollaborationError('INVALID_TASK_SNAPSHOT', '远端任务记录无效'); };
31
+ if (!task || !validId(task.id) || typeof task.groupId !== 'string' || !task.groupId || !time(task.createdAt) || !time(task.updatedAt)
32
+ || !Number.isSafeInteger(task.revision) || task.revision < 1 || !['open', 'accepted', 'closed'].includes(task.status)
33
+ || task.activeAttemptId !== null && !validId(task.activeAttemptId) || task.parentTaskId !== null && !validId(task.parentTaskId)
34
+ || !Array.isArray(task.dependsOn) || task.dependsOn.length > 32 || task.dependsOn.some(v => !validId(v))
35
+ || !Array.isArray(task.attempts) || task.attempts.length > 100 || !Array.isArray(task.events) || task.events.length > 1000
36
+ || !Array.isArray(task.decisions) || task.decisions.length > 1000 || !Array.isArray(task.artifacts) || task.artifacts.length > 200
37
+ || !Array.isArray(task.deliveries) || task.deliveries.length > 2000 || Buffer.byteLength(JSON.stringify(task)) > 512_000)
38
+ invalid();
39
+ text(task.title, '标题', 640, true);
40
+ text(task.spec, '任务内容', 32_000, true);
41
+ text(task.constraints, '约束');
42
+ text(task.acceptance, '完成标准');
43
+ member({ serviceId: task.ownerServiceId, sessionId: 'validation' });
44
+ if (task.coordinator !== null)
45
+ member(task.coordinator);
46
+ if (task.workflow) {
47
+ if (!['goal', 'step'].includes(task.workflow.kind) || typeof task.workflow.paused !== 'boolean' || typeof task.workflow.isolated !== 'boolean'
48
+ || !Array.isArray(task.workflow.reviewers) || task.workflow.reviewers.length > 32 || !Number.isInteger(task.workflow.maxRevisions)
49
+ || task.workflow.maxRevisions < 1 || task.workflow.maxRevisions > 10 || task.workflow.rootTaskId && !validId(task.workflow.rootTaskId))
50
+ invalid();
51
+ if (task.workflow.maxParallel !== undefined && (!Number.isInteger(task.workflow.maxParallel) || task.workflow.maxParallel < 1 || task.workflow.maxParallel > 4))
52
+ invalid();
53
+ task.workflow.reviewers.forEach(member);
54
+ }
55
+ if (task.scheduledAssignee)
56
+ member(task.scheduledAssignee);
57
+ if (task.workspace && [task.workspace.cwd, task.workspace.repository, task.workspace.branch, task.workspace.base].some(v => typeof v !== 'string' || !v || v.length > 4096))
58
+ invalid();
59
+ for (const attempt of task.attempts) {
60
+ if (!attempt || !validId(attempt.id) || !validId(attempt.threadId) || !time(attempt.createdAt))
61
+ invalid();
62
+ member(attempt.assignee);
63
+ if (attempt.report && (!reportStatus(attempt.report.status) || !time(attempt.report.createdAt) || typeof attempt.report.content !== 'string'))
64
+ invalid();
65
+ }
66
+ if (task.activeAttemptId && !task.attempts.some(a => a.id === task.activeAttemptId))
67
+ invalid();
68
+ for (const event of task.events) {
69
+ if (!event || !validId(event.id) || !Number.isSafeInteger(event.sequence) || event.sequence < 1 || typeof event.kind !== 'string'
70
+ || typeof event.content !== 'string' || !time(event.createdAt) || event.attemptId !== null && !task.attempts.some(a => a.id === event.attemptId))
71
+ invalid();
72
+ if (event.actor !== null)
73
+ member(event.actor);
74
+ if (event.target)
75
+ member(event.target);
76
+ }
77
+ for (const decision of task.decisions) {
78
+ if (!decision || !validId(decision.id) || typeof decision.question !== 'string' || !time(decision.createdAt)
79
+ || !task.attempts.some(a => a.id === decision.attemptId) || !['pending', 'answered', 'superseded'].includes(decision.status)
80
+ || !Array.isArray(decision.options) || decision.options.length > 8 || decision.options.some(o => typeof o !== 'string')
81
+ || decision.status === 'answered' && (typeof decision.answer !== 'string' || !time(decision.answeredAt)))
82
+ invalid();
83
+ }
84
+ for (const artifact of task.artifacts) {
85
+ if (!artifact || !validId(artifact.id) || !task.attempts.some(a => a.id === artifact.attemptId) || !['plan', 'result', 'review'].includes(artifact.kind)
86
+ || typeof artifact.content !== 'string' || !time(artifact.createdAt))
87
+ invalid();
88
+ member(artifact.actor);
89
+ if (artifact.verdict && !['pass', 'changes', 'blocked'].includes(artifact.verdict))
90
+ invalid();
91
+ }
92
+ for (const delivery of task.deliveries)
93
+ if (!delivery || !validId(delivery.id) || typeof delivery.messageId !== 'string'
94
+ || typeof delivery.status !== 'string' || delivery.error !== null && typeof delivery.error !== 'string'
95
+ || delivery.attemptId !== null && !task.attempts.some(a => a.id === delivery.attemptId))
96
+ invalid();
97
+ }
98
+ /** Atomic document writes keep decisions and their outgoing messages in one transaction. */
99
+ export class CollaborationTaskStore {
100
+ file;
101
+ document = { version: 1, tasks: [], outbox: [], requests: {} };
102
+ constructor(file) {
103
+ this.file = file;
104
+ try {
105
+ const doc = JSON.parse(readFileSync(file, 'utf8'));
106
+ if (doc.version !== 1 || !Array.isArray(doc.tasks) || !Array.isArray(doc.outbox) || !doc.requests)
107
+ throw new Error('任务记录格式无效');
108
+ this.document = doc;
109
+ }
110
+ catch (error) {
111
+ if (error.code !== 'ENOENT')
112
+ throw error;
113
+ }
114
+ }
115
+ transaction(mutate) {
116
+ const next = structuredClone(this.document);
117
+ const result = mutate(next);
118
+ if (next.tasks.some(task => Buffer.byteLength(JSON.stringify(task)) > 512_000))
119
+ throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务记录达到 500 KiB,请建立后续任务并保留本任务为历史', 409);
120
+ const serialized = JSON.stringify(next);
121
+ if (Buffer.byteLength(serialized) > 64 * 1024 * 1024)
122
+ throw new CollaborationError('TASK_STORAGE_FULL', '任务记录已达到保存上限,请导出历史后整理', 409);
123
+ mkdirSync(dirname(this.file), { recursive: true, mode: 0o700 });
124
+ const temporary = `${this.file}.${process.pid}.tmp`;
125
+ writeFileSync(temporary, serialized, { mode: 0o600 });
126
+ renameSync(temporary, this.file);
127
+ this.document = next;
128
+ return structuredClone(result);
129
+ }
130
+ get(taskId) { return structuredClone(this.document.tasks.find(t => t.id === taskId) ?? null); }
131
+ owned(owner) { return structuredClone(this.document.tasks.filter(t => t.ownerServiceId === owner)); }
132
+ children(taskId) {
133
+ return structuredClone(this.document.tasks.filter(t => t.parentTaskId === taskId || t.workflow?.rootTaskId === taskId)
134
+ .map(({ id, title, status, revision, completionMode, workspace }) => ({ id, title, status, revision, completionMode, workspace })));
135
+ }
136
+ list(groupIds, summary = false) {
137
+ const tasks = this.document.tasks.filter(t => groupIds.includes(t.groupId)).sort((a, b) => b.updatedAt - a.updatedAt);
138
+ return structuredClone(tasks.map(task => {
139
+ if (!summary)
140
+ return task;
141
+ const current = task.attempts.find(a => a.id === task.activeAttemptId);
142
+ const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
143
+ const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
144
+ return { ...task, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
145
+ deliveries: task.deliveries.filter(d => d.attemptId === task.activeAttemptId && d.error).slice(-1),
146
+ attempts: current ? [{ ...current, report: current.report ? { ...current.report, content: '', evidence: undefined } : undefined }] : [],
147
+ artifacts: [plan, result, ...task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id)].filter((a) => !!a).map(a => ({ ...a, content: '', evidence: undefined })),
148
+ decisions: task.decisions.filter(d => d.status === 'pending').map(d => ({ ...d, question: '', options: [] })),
149
+ events: task.events.filter(e => ['revise', 'request-review'].includes(e.kind) && e.attemptId === task.activeAttemptId).slice(-4).map(e => ({ ...e, content: '' })) };
150
+ }));
151
+ }
152
+ heads(groupIds, owner) { return this.document.tasks.filter(t => groupIds.includes(t.groupId) && t.ownerServiceId === owner).map(t => ({ id: t.id, revision: t.revision })); }
153
+ pending(taskId, dueOnly = false) {
154
+ return structuredClone(this.document.outbox
155
+ .filter(o => (!taskId || o.taskId === taskId) && (!dueOnly || (o.nextRetryAt ?? 0) <= Date.now())));
156
+ }
157
+ event(task, kind, actor, content, attemptId = task.activeAttemptId) {
158
+ const event = { id: id(), sequence: (task.events.at(-1)?.sequence ?? 0) + 1, kind, actor, content, attemptId, createdAt: Math.max(Date.now(), (task.events.at(-1)?.createdAt ?? 0) + 1) };
159
+ task.events.push(event);
160
+ return event;
161
+ }
162
+ request(doc, actor, key, payload) {
163
+ if (typeof key !== 'string' || !key.trim() || key.length > 256)
164
+ throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', '需要有效的请求幂等键');
165
+ const scoped = `${actor ? taskMemberKey(actor) : 'user'}:${key}`;
166
+ const hash = createHash('sha256').update(stable(payload)).digest('hex');
167
+ const previous = doc.requests[scoped];
168
+ if (previous && previous.hash !== hash)
169
+ throw new CollaborationError('IDEMPOTENCY_CONFLICT', '该请求已用于不同内容;请保留原请求或重新开始', 409);
170
+ return { key: scoped, hash, previous: previous ? doc.tasks.find(t => t.id === previous.taskId) : undefined };
171
+ }
172
+ assign(doc, task, assignee, actor, content) {
173
+ doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
174
+ for (const decision of task.decisions)
175
+ if (decision.status === 'pending')
176
+ decision.status = 'superseded';
177
+ const attemptId = id(), threadId = id();
178
+ task.activeAttemptId = attemptId;
179
+ task.attempts.push({ id: attemptId, assignee: member(assignee), createdAt: Date.now(), threadId });
180
+ this.event(task, 'assigned', actor, content || '任务已分派');
181
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId, target: assignee, kind: 'task', threadId,
182
+ content: `任务:${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。如果同一尝试已经交付或任务已归档,不要重复实施。${task.workspace ? `\n代码只能在独立目录 ${task.workspace.cwd} 修改。分支 ${task.workspace.branch}。交付前提交代码,结果说明提交、变更、验证与限制。不能修改原仓库、合并到用户分支或发布。` : ''}${task.workflow?.kind === 'step' ? '\n完成报告会自动请求独立评审;评审要求修改时继续完成,重新提交 complete。只按用户授权运行测试。' : ''}` });
183
+ }
184
+ goalInstructions(task) {
185
+ return `你是此目标的协调者,负责把目标推进到可验收交付,不亲自实施子任务。\n目标:${task.spec}\n约束:${task.constraints || '未补充'}\n完成标准:${task.acceptance || '满足用户目标,说明交付与限制'}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --json 读取 roster、子任务、完整记录和 revision。\n先报告 ack;拆分为可交付子任务,用 td collab task create --group ${task.groupId} --parent ${task.id} --title '标题' --content '完整要求' --assignee <roster里的成员sessionId> --acceptance '明确标准'。子任务继承自动评审、隔离目录与协调者。依赖任务用 --depends-on id,id,服务在依赖评审通过后自动分派。保持分工合理,不根据终端输出推断谁有空。\n每次收到明确更新后读取最新记录,处理失败与交接。服务自动安排独立评审、返工和依赖接续;不要重复请求评审或催促正在等待回复的成员。必要的人类决策用 task ask --attempt <本轮id>;方案需要用户确认时提交 task plan,不把普通回复当授权。\n代码任务需创建 --integration 的最终集成子任务,--depends-on 依赖全部实施任务;独立目录会包含依赖的提交。不得在原仓库自动合并或发布。\n结束本次协调用 td collab task coordinate ${task.id} --revision <最新版本> --content '已安排的工作和下一步';有新事件服务会再次唤起你。所有子任务评审通过后报告 complete,汇总交付目录、分支、提交、评审结论和限制,等待用户最终验收。任何异常都要留下明确说明,不伪造完成或进度。`;
186
+ }
187
+ notifyCoordinator(doc, task, update) {
188
+ if (!task.coordinator)
189
+ return;
190
+ if (task.workflow)
191
+ return; // Managed goals are woken once per persisted event set by advance().
192
+ const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
193
+ if (update ? sameMember(task.coordinator, update.actor) : attempt && sameMember(task.coordinator, attempt.assignee))
194
+ return;
195
+ if (update && ['answer', 'revise'].includes(update.kind) && attempt && sameMember(task.coordinator, attempt.assignee))
196
+ return;
197
+ if (update) {
198
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
199
+ content: `协调任务「${task.title}」有明确更新:${update.kind}${update.reportStatus ? ` / ${update.reportStatus}` : ''}\n原始记录时间:${new Date(update.createdAt).toISOString()}\n${update.attemptId && update.attemptId !== task.activeAttemptId ? '这是历史分派的报告,请先核对当前任务。\n' : ''}${update.content.slice(0, 4000)}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --text 查看完整原文与当前版本;问题回答与结果验收由用户决定。` });
200
+ return;
201
+ }
202
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
203
+ 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,不推断成员忙闲,不停止其他成员终端。` });
204
+ }
205
+ create(owner, input, actor) {
206
+ const title = text(input.title, '标题', 640, true), spec = text(input.spec, '任务内容', 32_000, true);
207
+ const constraints = text(input.constraints, '约束'), acceptance = text(input.acceptance, '完成标准');
208
+ if (input.dependsOn !== undefined && (!Array.isArray(input.dependsOn) || input.dependsOn.length > 32 || input.dependsOn.some(v => typeof v !== 'string')))
209
+ throw new CollaborationError('INVALID_DEPENDENCIES', '依赖列表无效');
210
+ if (input.coordinator)
211
+ member(input.coordinator);
212
+ if (input.assignee)
213
+ member(input.assignee);
214
+ if (input.managed !== undefined && typeof input.managed !== 'boolean' || input.isolated !== undefined && typeof input.isolated !== 'boolean'
215
+ || input.integration !== undefined && typeof input.integration !== 'boolean')
216
+ throw new CollaborationError('INVALID_WORKFLOW', '协作模式无效');
217
+ if (input.reviewers && (!Array.isArray(input.reviewers) || input.reviewers.length > 32))
218
+ throw new CollaborationError('INVALID_REVIEWERS', '评审成员列表无效');
219
+ input.reviewers?.forEach(member);
220
+ return this.transaction(doc => {
221
+ const request = this.request(doc, actor, input.idempotencyKey, input);
222
+ if (request.previous)
223
+ return request.previous;
224
+ if (doc.tasks.length >= 2000)
225
+ throw new CollaborationError('TASK_LIMIT', '任务记录达到上限,请导出并整理历史', 409);
226
+ const dependsOn = [...new Set(input.dependsOn ?? [])];
227
+ for (const dependency of [...dependsOn, ...(input.parentTaskId ? [input.parentTaskId] : [])]) {
228
+ if (!doc.tasks.some(t => t.id === dependency && t.groupId === input.groupId))
229
+ throw new CollaborationError('TASK_DEPENDENCY_NOT_FOUND', '父任务或依赖任务不在当前协作组', 404);
230
+ }
231
+ const now = Date.now();
232
+ const parent = doc.tasks.find(t => t.id === input.parentTaskId);
233
+ if (parent?.workflow && actor && !sameMember(actor, parent.coordinator))
234
+ throw new CollaborationError('TASK_PERMISSION_DENIED', '只有协调者可以创建此目标的子任务', 403);
235
+ if (parent?.status !== undefined && parent.status !== 'open')
236
+ throw new CollaborationError('TASK_ARCHIVED', '目标已归档', 409);
237
+ if (parent?.workflow && doc.tasks.filter(t => t.workflow?.rootTaskId === (parent.workflow?.rootTaskId ?? parent.id)).length >= 32)
238
+ throw new CollaborationError('GOAL_TASK_LIMIT', '单个目标最多 32 个子任务,请收敛分工或建立后续目标', 409);
239
+ const workflow = parent?.workflow ? { ...parent.workflow, kind: 'step', rootTaskId: parent.workflow.rootTaskId ?? parent.id, paused: false, integration: input.integration === true }
240
+ : input.managed ? { kind: 'goal', reviewers: input.reviewers ?? [], isolated: input.isolated !== false, paused: false, maxRevisions: 3, maxParallel: Math.min(4, input.reviewers?.length ?? 1) } : undefined;
241
+ if (workflow?.kind === 'goal' && (!input.coordinator || workflow.reviewers.length < 1))
242
+ throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '请选择协调者和至少一位执行成员', 400);
243
+ if (workflow?.kind === 'goal' && !workflow.reviewers.some(m => !sameMember(m, input.coordinator ?? null)))
244
+ throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '执行成员需要与协调者独立');
245
+ if (workflow && parent && !input.assignee)
246
+ throw new CollaborationError('ASSIGNEE_REQUIRED', '自动协作子任务需要执行成员');
247
+ if (workflow && parent && sameMember(input.assignee ?? null, parent.coordinator))
248
+ throw new CollaborationError('COORDINATOR_IS_NOT_WORKER', '请把实施分派给执行成员,协调者负责组织与评审');
249
+ if (workflow && parent && !workflow.reviewers.some(m => sameMember(m, input.assignee ?? null)))
250
+ throw new CollaborationError('WORKER_TEMPLATE_REQUIRED', '请从此目标的执行成员中选择模板');
251
+ if (workflow?.rootTaskId && doc.tasks.find(t => t.id === workflow.rootTaskId)?.workflow?.paused)
252
+ throw new CollaborationError('WORKFLOW_PAUSED', '目标已暂停,请先继续协调');
253
+ if (workflow?.integration) {
254
+ const required = doc.tasks.filter(t => t.workflow?.rootTaskId === workflow.rootTaskId && !t.workflow?.integration && t.status !== 'closed');
255
+ if (!required.length || required.some(t => !dependsOn.includes(t.id)))
256
+ throw new CollaborationError('INTEGRATION_DEPENDENCIES_REQUIRED', '集成任务必须依赖目标下全部实施子任务');
257
+ }
258
+ const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance,
259
+ coordinator: parent?.workflow ? parent.coordinator : input.coordinator !== undefined ? input.coordinator : input.parentTaskId ? doc.tasks.find(t => t.id === input.parentTaskId)?.coordinator ?? actor : null,
260
+ parentTaskId: input.parentTaskId ?? null, dependsOn, createdAt: now, updatedAt: now, revision: 1,
261
+ status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
262
+ this.event(task, 'created', actor, spec);
263
+ if (parent?.constraints)
264
+ task.constraints = text([parent.constraints, constraints].filter(Boolean).join('\n'), '继承约束');
265
+ if (workflow?.kind === 'goal') {
266
+ this.assign(doc, task, member(input.coordinator), actor, this.goalInstructions(task));
267
+ task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: now };
268
+ }
269
+ else if (workflow && input.assignee) {
270
+ task.scheduledAssignee = input.assignee;
271
+ this.event(task, 'scheduled', actor, dependsOn.length ? '依赖评审通过后自动分派' : '等待准备执行目录与分派');
272
+ }
273
+ else if (input.assignee) {
274
+ if (dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
275
+ throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收;可以先保存任务,稍后分派', 409);
276
+ this.assign(doc, task, input.assignee, actor, '');
277
+ }
278
+ this.notifyCoordinator(doc, task);
279
+ doc.tasks.push(task);
280
+ this.touchParent(doc, task, '新子任务已创建');
281
+ doc.requests[request.key] = { hash: request.hash, taskId: task.id };
282
+ return task;
283
+ });
284
+ }
285
+ apply(taskId, input, actor) {
286
+ const content = text(input.content, '内容');
287
+ if (input.verdict !== undefined && !['pass', 'changes', 'blocked'].includes(input.verdict))
288
+ throw new CollaborationError('INVALID_VERDICT', '评审结论无效');
289
+ if (input.evidence !== undefined && Buffer.byteLength(JSON.stringify(input.evidence)) > 16_384)
290
+ throw new CollaborationError('EVIDENCE_TOO_LARGE', '证据最多 16 KiB');
291
+ return this.transaction(doc => {
292
+ const task = doc.tasks.find(t => t.id === taskId);
293
+ if (!task)
294
+ throw new CollaborationError('TASK_NOT_FOUND', '任务不存在或无权查看', 404);
295
+ const request = this.request(doc, actor, input.idempotencyKey, [taskId, input]);
296
+ if (request.previous)
297
+ return request.previous;
298
+ const coordination = actor === null || sameMember(actor, task.coordinator);
299
+ if (['assign', 'coordinator', 'close', 'reopen', 'revise', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(input.kind) && !coordination)
300
+ throw new CollaborationError('TASK_PERMISSION_DENIED', '此操作需要用户或当前协调者', 403);
301
+ if (['answer', 'accept', 'approve-plan'].includes(input.kind) && actor !== null)
302
+ throw new CollaborationError('USER_DECISION_REQUIRED', '回答、方案确认与验收需要用户操作', 403);
303
+ if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
304
+ throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
305
+ if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind))
306
+ throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
307
+ if (task.workflow && task.status !== 'open' && input.kind !== 'reopen')
308
+ throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
309
+ if (task.workflow?.rootTaskId && doc.tasks.find(t => t.id === task.workflow.rootTaskId)?.status !== 'open')
310
+ throw new CollaborationError('GOAL_ARCHIVED', '请先重新打开所属目标,再继续子任务', 409);
311
+ const attempt = task.attempts.find(a => a.id === input.attemptId);
312
+ if (['report', 'ask', 'submit-plan'].includes(input.kind) && (!attempt || !sameMember(actor, attempt.assignee)))
313
+ throw new CollaborationError('ATTEMPT_PERMISSION_DENIED', '只能对自己负责的执行尝试提交报告', 403);
314
+ if (['ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
315
+ throw new CollaborationError('ATTEMPT_SUPERSEDED', '这轮分派已被替换,不能再请求用户决定', 409);
316
+ if (task.workflow && ['report', 'ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
317
+ throw new CollaborationError('ATTEMPT_SUPERSEDED', '自动协作只能推进当前分派', 409);
318
+ if (input.kind === 'assign') {
319
+ if (task.workflow?.kind === 'goal' && !sameMember(input.assignee ?? null, task.coordinator))
320
+ throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '目标由协调者负责,请在子任务中分派实施');
321
+ if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
322
+ throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
323
+ if (task.workflow?.kind === 'step' && task.workflow.isolated) {
324
+ task.scheduledAssignee = member(input.assignee);
325
+ task.activeAttemptId = null;
326
+ delete task.workspace;
327
+ delete task.automationIssue;
328
+ this.event(task, 'scheduled', actor, content || '准备新一轮独立执行目录');
329
+ }
330
+ else
331
+ this.assign(doc, task, member(input.assignee), actor, content);
332
+ this.notifyCoordinator(doc, task);
333
+ }
334
+ else if (input.kind === 'coordinator') {
335
+ if (task.workflow && !input.coordinator)
336
+ throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '自动协作需要协调者');
337
+ const previousCoordinator = task.coordinator;
338
+ task.coordinator = input.coordinator ? member(input.coordinator) : null;
339
+ this.event(task, 'coordinator', actor, content || '协调者已更新');
340
+ if (task.workflow?.kind === 'goal') {
341
+ this.assign(doc, task, task.coordinator, actor, this.goalInstructions(task));
342
+ task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: Date.now() };
343
+ task.workflow.reviewers = [...task.workflow.reviewers.filter(m => !sameMember(m, task.coordinator)), ...(previousCoordinator && !sameMember(previousCoordinator, task.coordinator) ? [previousCoordinator] : [])];
344
+ for (const child of doc.tasks.filter(t => t.workflow?.rootTaskId === task.id)) {
345
+ child.coordinator = task.coordinator;
346
+ child.workflow.reviewers = task.workflow.reviewers;
347
+ child.revision++;
348
+ child.updatedAt = Date.now();
349
+ }
350
+ }
351
+ this.notifyCoordinator(doc, task);
352
+ }
353
+ else if (input.kind === 'report') {
354
+ if (!['ack', 'working', 'blocked', 'complete', 'failed'].includes(input.status ?? '') || !content)
355
+ throw new CollaborationError('INVALID_REPORT', '报告需要明确状态和正文');
356
+ if (input.status === 'complete' && task.workflow?.kind === 'goal' && (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id)
357
+ || doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status))))
358
+ throw new CollaborationError('CHILD_TASKS_PENDING', '请先完成全部子任务的独立评审,再汇总交付', 409);
359
+ if (input.status === 'complete' && task.workflow?.kind === 'goal' && task.workflow.isolated) {
360
+ const steps = doc.tasks.filter(t => t.workflow?.rootTaskId === task.id && !t.workflow.integration && t.status !== 'closed');
361
+ if (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
362
+ throw new CollaborationError('INTEGRATION_PENDING', '代码目标需要依赖全部实施任务的集成交付通过评审', 409);
363
+ }
364
+ if (input.status === 'complete') {
365
+ const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
366
+ if (plan && task.approvedPlanArtifactId !== plan.id)
367
+ throw new CollaborationError('PLAN_PENDING', '方案尚未由用户确认,请先等待决定', 409);
368
+ }
369
+ attempt.report = { status: input.status, content, evidence: input.evidence, createdAt: Date.now() };
370
+ const event = this.event(task, 'report', actor, content, attempt.id);
371
+ event.reportStatus = input.status;
372
+ event.evidence = input.evidence;
373
+ if (input.status === 'complete')
374
+ task.artifacts.push({ id: id(), attemptId: attempt.id, kind: 'result', content, evidence: input.evidence, actor: actor, createdAt: event.createdAt });
375
+ }
376
+ else if (input.kind === 'ask') {
377
+ if (!content || input.options !== undefined && (!Array.isArray(input.options) || input.options.length > 8 || input.options.some(v => typeof v !== 'string' || !v.trim() || v.length > 500)))
378
+ throw new CollaborationError('INVALID_QUESTION', '问题正文与选项无效');
379
+ task.decisions.push({ id: id(), attemptId: attempt.id, question: content, options: input.options ?? [], status: 'pending', createdAt: Date.now() });
380
+ this.event(task, 'question', actor, content, attempt.id);
381
+ }
382
+ else if (input.kind === 'answer') {
383
+ const decision = task.decisions.find(d => d.id === input.decisionId);
384
+ if (!decision || decision.status !== 'pending' || decision.attemptId !== task.activeAttemptId)
385
+ throw new CollaborationError('QUESTION_SETTLED', '该问题已回答或已不再适用,请查看最新记录', 409);
386
+ if (!content)
387
+ throw new CollaborationError('INVALID_ANSWER', '答案不能为空');
388
+ decision.status = 'answered';
389
+ decision.answer = content;
390
+ decision.answeredAt = Date.now();
391
+ this.event(task, 'answer', null, content, decision.attemptId);
392
+ const recipient = task.attempts.find(a => a.id === decision.attemptId);
393
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee, kind: 'message', threadId: recipient.threadId,
394
+ content: `任务「${task.title}」问题已回答\n问题:${decision.question}\n用户回答:${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
395
+ }
396
+ else if (input.kind === 'submit-plan' || input.kind === 'review') {
397
+ if (!content || !actor)
398
+ throw new CollaborationError('INVALID_ARTIFACT', '交付物需要成员身份和正文');
399
+ const reviewed = input.kind === 'review' ? task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review') : undefined;
400
+ if (input.kind === 'review' && (!reviewed || sameMember(actor, reviewed.actor)))
401
+ throw new CollaborationError('INVALID_REVIEW', '独立评审需要选择另一成员提交的明确版本');
402
+ if (input.kind === 'review' && task.workflow) {
403
+ const request = task.events.filter(e => e.kind === 'request-review' && e.artifactId === reviewed?.id).at(-1);
404
+ if (!request?.target || !sameMember(actor, request.target))
405
+ throw new CollaborationError('REVIEW_PERMISSION_DENIED', '只有此版本指定的独立评审者可以推进自动流程', 403);
406
+ if (!['pass', 'changes', 'blocked'].includes(input.verdict ?? ''))
407
+ throw new CollaborationError('REVIEW_VERDICT_REQUIRED', '自动评审需要 --verdict pass|changes|blocked');
408
+ if (reviewed?.attemptId !== task.activeAttemptId || task.artifacts.filter(a => a.kind === reviewed.kind && a.attemptId === task.activeAttemptId).at(-1)?.id !== reviewed.id)
409
+ throw new CollaborationError('RESULT_CHANGED', '请评审当前分派的最新版本', 409);
410
+ }
411
+ const artifactAttempt = reviewed?.attemptId ?? attempt.id;
412
+ task.artifacts.push({ id: id(), attemptId: artifactAttempt, kind: input.kind === 'review' ? 'review' : 'plan', content, evidence: input.evidence,
413
+ actor, createdAt: Date.now(), ...(reviewed ? { reviewsArtifactId: reviewed.id, ...(input.verdict ? { verdict: input.verdict } : {}) } : {}) });
414
+ this.event(task, input.kind, actor, content, artifactAttempt);
415
+ }
416
+ else if (input.kind === 'request-review' || input.kind === 'approve-plan') {
417
+ const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review' && a.attemptId === task.activeAttemptId);
418
+ if (!artifact)
419
+ throw new CollaborationError('ARTIFACT_CHANGED', '交付物已不属于当前分派', 409);
420
+ const recipient = input.kind === 'request-review' ? member(input.assignee) : artifact.actor;
421
+ if (input.kind === 'request-review' && sameMember(recipient, artifact.actor))
422
+ throw new CollaborationError('INVALID_REVIEW', '请选择独立评审成员');
423
+ if (input.kind === 'approve-plan') {
424
+ if (artifact.kind !== 'plan' || task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1)?.id !== artifact.id)
425
+ throw new CollaborationError('PLAN_CHANGED', '请选择本轮最新方案确认', 409);
426
+ task.approvedPlanArtifactId = artifact.id;
427
+ }
428
+ const event = this.event(task, input.kind, actor, content || (input.kind === 'request-review' ? '已请求独立评审' : '用户确认了此版本方案'));
429
+ event.artifactId = artifact.id;
430
+ event.target = recipient;
431
+ const threadId = task.attempts.find(a => a.id === task.activeAttemptId).threadId;
432
+ doc.outbox.push({ id: id(), taskId, attemptId: task.activeAttemptId, target: recipient, kind: 'message', threadId,
433
+ content: input.kind === 'request-review' ? `请独立评审任务「${task.title}」的交付物 ${artifact.id}。\n${content}\n用 td collab task get ${task.id} --text 查看上下文;用 td collab task review ${task.id} --artifact ${artifact.id} --content '评审结论' 提交。`
434
+ : `任务「${task.title}」方案 ${artifact.id} 已由用户确认。\n${content}\n任务 ID:${task.id}` });
435
+ }
436
+ else if (input.kind === 'accept') {
437
+ if (task.workflow?.kind === 'goal' && doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status)))
438
+ throw new CollaborationError('CHILD_TASKS_PENDING', '子任务尚未全部完成', 409);
439
+ if (task.workflow?.kind === 'goal' && task.workflow.isolated) {
440
+ const steps = doc.tasks.filter(t => t.workflow?.rootTaskId === task.id && !t.workflow.integration && t.status !== 'closed');
441
+ if (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
442
+ throw new CollaborationError('INTEGRATION_PENDING', '需要全部实施任务的集成交付通过评审');
443
+ }
444
+ if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
445
+ throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
446
+ if (task.decisions.some(d => d.status === 'pending'))
447
+ throw new CollaborationError('QUESTION_PENDING', '请先回答本轮待处理问题,再验收结果', 409);
448
+ const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind === 'result' && a.attemptId === task.activeAttemptId);
449
+ const latest = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
450
+ if (!artifact || latest?.id !== artifact.id)
451
+ throw new CollaborationError('RESULT_CHANGED', '请选择本轮最新结果验收', 409);
452
+ if (task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= artifact.createdAt))
453
+ throw new CollaborationError('REVISION_PENDING', '修改要求之后需要提交新结果才能验收', 409);
454
+ if (task.workflow && task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === artifact.id).at(-1)?.verdict !== 'pass')
455
+ throw new CollaborationError('REVIEW_PENDING', '当前结果尚未通过独立评审', 409);
456
+ task.status = 'accepted';
457
+ task.acceptedArtifactId = artifact.id;
458
+ this.event(task, 'accepted', null, content || '用户接受了此版本结果');
459
+ for (const decision of task.decisions)
460
+ if (decision.status === 'pending')
461
+ decision.status = 'superseded';
462
+ }
463
+ else if (input.kind === 'revise' || input.kind === 'comment') {
464
+ if (!content)
465
+ throw new CollaborationError('INVALID_COMMENT', '反馈不能为空');
466
+ this.event(task, input.kind, actor, content);
467
+ const recipient = task.attempts.find(a => a.id === task.activeAttemptId);
468
+ if (recipient && !sameMember(actor, recipient.assignee))
469
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee,
470
+ kind: 'message', threadId: recipient.threadId, content: `任务「${task.title}」${input.kind === 'revise' ? '要求修改' : '补充反馈'}:\n${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
471
+ }
472
+ else if (input.kind === 'close' || input.kind === 'reopen') {
473
+ task.status = input.kind === 'close' ? 'closed' : 'open';
474
+ this.event(task, input.kind, actor, content || (input.kind === 'close' ? '任务已关闭,终端工作未被中止' : '任务已重新打开'));
475
+ if (input.kind === 'close')
476
+ doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
477
+ else
478
+ delete task.acceptedArtifactId;
479
+ if (input.kind === 'close')
480
+ for (const decision of task.decisions)
481
+ if (decision.status === 'pending')
482
+ decision.status = 'superseded';
483
+ if (task.workflow?.kind === 'goal')
484
+ task.workflow.paused = input.kind === 'close';
485
+ }
486
+ else if (input.kind === 'coordinate') {
487
+ if (task.workflow?.kind !== 'goal' || !sameMember(actor, task.coordinator) || !content)
488
+ throw new CollaborationError('COORDINATOR_REQUIRED', '请由目标协调者提交本次协调说明', 403);
489
+ this.event(task, 'coordinate', actor, content);
490
+ task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: task.events.at(-1).sequence, notifiedAt: Date.now() };
491
+ delete task.automationIssue;
492
+ }
493
+ else if (input.kind === 'pause' || input.kind === 'resume' || input.kind === 'retry') {
494
+ if (!task.workflow)
495
+ throw new CollaborationError('WORKFLOW_REQUIRED', '此任务未启用自动协作');
496
+ if (input.kind !== 'retry')
497
+ task.workflow.paused = input.kind === 'pause';
498
+ delete task.automationIssue;
499
+ this.event(task, input.kind, actor, content || (input.kind === 'pause' ? '暂停后续自动安排,已写入终端的工作继续保留' : '继续推进自动协作'));
500
+ if (input.kind === 'retry' && task.coordination)
501
+ task.coordination.notifiedSequence = 0;
502
+ if (input.kind === 'retry')
503
+ for (const outbox of doc.outbox.filter(o => o.taskId === task.id)) {
504
+ const receipt = task.deliveries.find(d => d.id === outbox.id);
505
+ if (receipt && ['failed', 'expired'].includes(receipt.status)) {
506
+ outbox.id = id();
507
+ delete outbox.messageId;
508
+ }
509
+ delete outbox.lastError;
510
+ delete outbox.nextRetryAt;
511
+ }
512
+ }
513
+ else
514
+ throw new CollaborationError('INVALID_TASK_OPERATION', '不支持的任务操作');
515
+ if (['report', 'ask', 'answer', 'submit-plan', 'review', 'accept', 'revise', 'close', 'reopen'].includes(input.kind))
516
+ this.notifyCoordinator(doc, task, task.events.at(-1));
517
+ if (task.events.length > 1000 || task.artifacts.length > 200 || task.attempts.length > 100)
518
+ throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务历史达到上限,请建立后续任务并保留本任务为历史', 409);
519
+ task.revision++;
520
+ task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
521
+ if (task.workflow && input.kind !== 'coordinate')
522
+ this.touchParent(doc, task, `${input.kind}${input.status ? ` / ${input.status}` : ''}:${content.slice(0, 1500)}`);
523
+ if (task.workflow && ['blocked', 'failed'].includes(input.status ?? ''))
524
+ task.automationIssue = content.slice(0, 1000);
525
+ if (task.workflow && input.kind === 'report' && ['ack', 'working', 'complete'].includes(input.status ?? ''))
526
+ delete task.automationIssue;
527
+ doc.requests[request.key] = { hash: request.hash, taskId };
528
+ return task;
529
+ });
530
+ }
531
+ touchParent(doc, task, content) {
532
+ const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
533
+ if (!root || root.status !== 'open')
534
+ return;
535
+ this.event(root, 'child-update', null, `子任务「${task.title}」(${task.id}):${content}`);
536
+ const result = root.artifacts.filter(a => a.kind === 'result' && a.attemptId === root.activeAttemptId).at(-1);
537
+ if (result && !root.events.some(e => e.kind === 'revise' && e.attemptId === root.activeAttemptId && e.createdAt >= result.createdAt)) {
538
+ this.event(root, 'revise', null, '子任务在汇总之后发生变化,请重新核对并提交最终结果');
539
+ }
540
+ root.revision++;
541
+ root.updatedAt = Date.now();
542
+ }
543
+ scheduled(owner) {
544
+ return this.owned(owner).filter(t => t.workflow && t.status === 'open' && t.scheduledAssignee && !t.activeAttemptId && !t.automationIssue
545
+ && !t.workflow.paused && !this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.workflow?.paused
546
+ && this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.status === 'open'
547
+ && !this.planPending(this.document.tasks.find(r => r.id === t.workflow?.rootTaskId))
548
+ && t.dependsOn.every(id => this.document.tasks.find(d => d.id === id)?.status === 'accepted'));
549
+ }
550
+ planPending(task) {
551
+ const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
552
+ return !!plan && task.approvedPlanArtifactId !== plan.id;
553
+ }
554
+ activateScheduled(taskId, expectedRevision, assignee, workspace) {
555
+ this.transaction(doc => {
556
+ const task = doc.tasks.find(t => t.id === taskId);
557
+ const root = task?.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
558
+ if (!task || task.revision !== expectedRevision || task.activeAttemptId || !task.scheduledAssignee || task.status !== 'open'
559
+ || task.workflow?.paused || !root || root.status !== 'open' || root.workflow?.paused || this.planPending(root)
560
+ || task.dependsOn.some(id => doc.tasks.find(t => t.id === id)?.status !== 'accepted'))
561
+ return;
562
+ if (workspace)
563
+ task.workspace = workspace;
564
+ this.assign(doc, task, assignee, task.coordinator, '服务已接续依赖并准备执行');
565
+ delete task.scheduledAssignee;
566
+ task.revision++;
567
+ task.updatedAt = Date.now();
568
+ this.touchParent(doc, task, '已自动分派');
569
+ });
570
+ }
571
+ automationFailed(taskId, error) {
572
+ if (this.get(taskId)?.automationIssue === error.slice(0, 1000))
573
+ return;
574
+ this.transaction(doc => {
575
+ const task = doc.tasks.find(t => t.id === taskId);
576
+ if (!task)
577
+ return;
578
+ task.automationIssue = error.slice(0, 1000);
579
+ this.event(task, 'automation-blocked', null, task.automationIssue).source = 'system';
580
+ task.revision++;
581
+ task.updatedAt = Date.now();
582
+ this.touchParent(doc, task, task.automationIssue);
583
+ });
584
+ }
585
+ nextAction(task, doc) {
586
+ if (!task.workflow || task.status !== 'open' || task.workflow.paused)
587
+ return null;
588
+ const root = task.workflow.rootTaskId && doc.tasks.find(r => r.id === task.workflow.rootTaskId);
589
+ if (root && (root.status !== 'open' || root.workflow?.paused || this.planPending(root)))
590
+ return null;
591
+ const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
592
+ if (result && !task.automationIssue && !task.decisions.some(d => d.status === 'pending')
593
+ && !task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= result.createdAt)) {
594
+ const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result.id).at(-1);
595
+ if (!review && !task.events.some(e => e.kind === 'request-review' && e.artifactId === result.id))
596
+ return 'request-review';
597
+ if (review?.verdict === 'pass' && task.workflow.kind === 'step' && task.dependsOn.every(id => doc.tasks.find(t => t.id === id)?.status === 'accepted'))
598
+ return 'review-passed';
599
+ if (review && ['changes', 'blocked'].includes(review.verdict ?? '') && !task.events.some(e => e.kind === 'review-blocked' && e.artifactId === result.id))
600
+ return 'review-feedback';
601
+ }
602
+ if (task.workflow.kind === 'goal' && task.coordination) {
603
+ if (Date.now() - task.updatedAt >= 1000 && task.events.some(e => e.sequence > task.coordination.notifiedSequence
604
+ && !['coordinate', 'request-review', 'assigned'].includes(e.kind) && !sameMember(e.actor, task.coordinator)))
605
+ return 'wake';
606
+ if (!task.automationIssue && task.coordination.acknowledgedSequence < task.coordination.notifiedSequence
607
+ && !result && Date.now() - task.coordination.notifiedAt > 15 * 60_000)
608
+ return 'coordination-timeout';
609
+ }
610
+ return null;
611
+ }
612
+ /** Explicit, version-bound transitions; the browser does not drive this loop. */
613
+ advance(owner) {
614
+ if (!this.document.tasks.some(t => t.ownerServiceId === owner && this.nextAction(t, this.document)))
615
+ return;
616
+ this.transaction(doc => {
617
+ for (const task of doc.tasks.filter(t => t.ownerServiceId === owner)) {
618
+ const action = this.nextAction(task, doc);
619
+ if (!action)
620
+ continue;
621
+ const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
622
+ const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id).at(-1);
623
+ const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
624
+ if (action === 'request-review') {
625
+ const reviewers = [task.coordinator, ...task.workflow.reviewers].filter((m) => !!m && !sameMember(m, result.actor));
626
+ const reviewer = reviewers.find(m => m.serviceId === result.actor.serviceId) ?? reviewers[0];
627
+ if (!reviewer)
628
+ task.automationIssue = '没有可用的独立评审成员,请调整协调者或成员后继续';
629
+ else {
630
+ const event = this.event(task, 'request-review', null, '服务自动安排此版本独立评审');
631
+ event.artifactId = result.id;
632
+ event.target = reviewer;
633
+ event.source = 'system';
634
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: reviewer, kind: 'message', threadId: attempt.threadId,
635
+ content: `请独立评审「${task.title}」版本 ${result.id}。用 td collab task get ${task.id} --json 查看目标、结果和代码目录。${task.workspace ? `代码目录 ${task.workspace.cwd},分支 ${task.workspace.branch};只读检查此目录,不修改原仓库。` : ''}\n提交 td collab task review ${task.id} --artifact ${result.id} --verdict pass|changes|blocked --content '结论、具体问题与证据'。pass 表示符合完成标准;changes 给出可执行修改要求;blocked 留下必须处理的原因。只按用户授权运行测试,未执行不能称通过。` });
636
+ }
637
+ this.touchParent(doc, task, '已自动请求独立评审');
638
+ }
639
+ else if (action === 'review-passed') {
640
+ task.status = 'accepted';
641
+ task.acceptedArtifactId = result.id;
642
+ task.completionMode = 'reviewed';
643
+ this.event(task, 'review-passed', review.actor, review.content).source = 'system';
644
+ this.touchParent(doc, task, '独立评审通过,后续依赖可以接续');
645
+ }
646
+ else if (action === 'review-feedback') {
647
+ const rounds = task.events.filter(e => e.kind === 'revise' && e.actor === null && e.artifactId && e.attemptId === task.activeAttemptId).length;
648
+ const blocked = review.verdict === 'blocked' || rounds >= task.workflow.maxRevisions;
649
+ const event = this.event(task, blocked ? 'review-blocked' : 'revise', null, review.content);
650
+ event.artifactId = result.id;
651
+ event.source = 'system';
652
+ if (blocked)
653
+ task.automationIssue = `${review.verdict === 'blocked' ? '独立评审受阻' : '连续返工需要调整方案'}:${review.content.slice(0, 800)}`;
654
+ else
655
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: result.actor, kind: 'message', threadId: attempt.threadId,
656
+ content: `任务「${task.title}」独立评审要求修改:\n${review.content}\n请继续完成当前分派并重新 report --status complete;新版本会重新独立评审。任务 ${task.id},尝试 ${task.activeAttemptId}。` });
657
+ this.touchParent(doc, task, blocked ? task.automationIssue : '已自动交回修改');
658
+ }
659
+ else if (action === 'wake') {
660
+ const updates = task.events.filter(e => e.sequence > task.coordination.notifiedSequence && e.kind !== 'coordinate');
661
+ doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: task.coordinator, kind: 'message', threadId: task.id,
662
+ content: `目标「${task.title}」有新的明确事件,请继续协调:\n${updates.slice(-8).map(e => `${new Date(e.createdAt).toISOString()} ${e.content.slice(0, 1200)}`).join('\n')}\n用 td collab task get ${task.id} --json 读取完整上下文与子任务;安排必要下一步后 task coordinate ${task.id} --revision <最新版本> --content '协调说明'。自动评审和依赖已由服务接续,不要重复分派。最终汇总已经通过评审时等待用户验收,不要重复提交相同结果。` });
663
+ task.coordination.notifiedSequence = task.events.at(-1).sequence;
664
+ task.coordination.notifiedAt = Date.now();
665
+ }
666
+ else
667
+ task.automationIssue = '协调通知发出后 15 分钟未收到本次协调说明,请查看终端或点击继续协调。';
668
+ task.revision++;
669
+ task.updatedAt = Date.now();
670
+ }
671
+ });
672
+ }
673
+ merge(task, issuer) {
674
+ validateSnapshot(task);
675
+ if (task.ownerServiceId !== issuer)
676
+ throw new CollaborationError('INVALID_TASK_OWNER', '任务来源身份不符');
677
+ const current = this.get(task.id);
678
+ if (current && current.ownerServiceId !== issuer)
679
+ throw new CollaborationError('TASK_ID_CONFLICT', '任务身份冲突');
680
+ if (current && current.revision >= task.revision)
681
+ return;
682
+ this.transaction(doc => {
683
+ const index = doc.tasks.findIndex(t => t.id === task.id);
684
+ if (index < 0) {
685
+ if (doc.tasks.length >= 2000)
686
+ throw new CollaborationError('TASK_LIMIT', '任务记录达到上限', 409);
687
+ doc.tasks.push(task);
688
+ }
689
+ else
690
+ doc.tasks[index] = task;
691
+ });
692
+ }
693
+ delivered(outboxId, receipt) {
694
+ if (!receipt || typeof receipt.messageId !== 'string' || !receipt.messageId || receipt.messageId.length > 128
695
+ || !['pending', 'delivered', 'failed', 'expired'].includes(receipt.status)
696
+ || receipt.deliveredAt !== null && (!Number.isSafeInteger(receipt.deliveredAt) || receipt.deliveredAt <= 0)
697
+ || receipt.error !== null && typeof receipt.error !== 'string')
698
+ throw new CollaborationError('INVALID_TASK_RECEIPT', '远端投递凭证无效');
699
+ const current = this.document.outbox.find(o => o.id === outboxId);
700
+ if (!current)
701
+ return;
702
+ const previous = this.document.tasks.find(t => t.id === current.taskId)?.deliveries.find(d => d.id === outboxId);
703
+ if (JSON.stringify(previous) === JSON.stringify({ id: outboxId, attemptId: current.attemptId, kind: current.kind, ...receipt })) {
704
+ current.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
705
+ return;
706
+ }
707
+ this.transaction(doc => {
708
+ const outbox = doc.outbox.find(o => o.id === outboxId);
709
+ outbox.messageId = receipt.messageId;
710
+ outbox.lastError = receipt.error ?? undefined;
711
+ const task = doc.tasks.find(t => t.id === outbox.taskId);
712
+ const attempt = task.attempts.find(a => a.id === outbox.attemptId);
713
+ const previous = task.deliveries.find(d => d.id === outboxId);
714
+ const delivery = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, ...receipt };
715
+ const changed = JSON.stringify(previous) !== JSON.stringify(delivery);
716
+ if (changed) {
717
+ if (previous)
718
+ Object.assign(previous, delivery);
719
+ else
720
+ task.deliveries.push(delivery);
721
+ task.revision++;
722
+ task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
723
+ }
724
+ // Later feedback has its own receipt; it must not overwrite the initial dispatch.
725
+ if (outbox.kind === 'task') {
726
+ attempt.messageId = receipt.messageId;
727
+ attempt.deliveryStatus = receipt.status;
728
+ attempt.deliveredAt = receipt.deliveredAt;
729
+ attempt.deliveryError = receipt.error;
730
+ }
731
+ if (receipt.status === 'delivered' || !task.workflow && ['failed', 'expired'].includes(receipt.status))
732
+ doc.outbox = doc.outbox.filter(o => o.id !== outboxId);
733
+ else
734
+ outbox.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
735
+ if (task.workflow && ['failed', 'expired'].includes(receipt.status)) {
736
+ task.automationIssue = `消息投递${receipt.status === 'expired' ? '过期' : '失败'}:${receipt.error ?? '请检查成员终端后重试'}`;
737
+ this.touchParent(doc, task, task.automationIssue);
738
+ }
739
+ else if (receipt.status === 'delivered' && task.automationIssue?.startsWith('消息投递正在重试'))
740
+ delete task.automationIssue;
741
+ });
742
+ }
743
+ failed(outboxId, error) {
744
+ const current = this.document.outbox.find(o => o.id === outboxId);
745
+ if (!current)
746
+ return;
747
+ if (current.lastError === error.slice(0, 500)) {
748
+ current.nextRetryAt = Date.now() + 10_000;
749
+ return;
750
+ }
751
+ this.transaction(doc => {
752
+ const outbox = doc.outbox.find(o => o.id === outboxId);
753
+ const changed = outbox.lastError !== error.slice(0, 500);
754
+ outbox.lastError = error.slice(0, 500);
755
+ outbox.nextRetryAt = Date.now() + 10_000;
756
+ const task = doc.tasks.find(t => t.id === outbox.taskId);
757
+ if (changed) {
758
+ const previous = task.deliveries.find(d => d.id === outboxId);
759
+ const record = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, messageId: outbox.messageId ?? '', status: 'retrying', deliveredAt: null, error: outbox.lastError };
760
+ if (previous)
761
+ Object.assign(previous, record);
762
+ else
763
+ task.deliveries.push(record);
764
+ task.revision++;
765
+ task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
766
+ if (task.workflow && !task.automationIssue) {
767
+ task.automationIssue = `消息投递正在重试:${outbox.lastError}`;
768
+ this.touchParent(doc, task, task.automationIssue);
769
+ }
770
+ }
771
+ });
772
+ }
773
+ }