agent-tower 0.7.3 → 0.7.4-beta.0

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 (272) hide show
  1. package/dist/git/worktree.manager.d.ts +2 -0
  2. package/dist/git/worktree.manager.d.ts.map +1 -1
  3. package/dist/git/worktree.manager.js +29 -8
  4. package/dist/git/worktree.manager.js.map +1 -1
  5. package/dist/git/worktree.manager.test.js +38 -0
  6. package/dist/git/worktree.manager.test.js.map +1 -1
  7. package/dist/mcp/http-client.d.ts +3 -1
  8. package/dist/mcp/http-client.d.ts.map +1 -1
  9. package/dist/mcp/http-client.js +6 -0
  10. package/dist/mcp/http-client.js.map +1 -1
  11. package/dist/mcp/tools/__tests__/tasks.test.js +33 -1
  12. package/dist/mcp/tools/__tests__/tasks.test.js.map +1 -1
  13. package/dist/mcp/tools/tasks.d.ts.map +1 -1
  14. package/dist/mcp/tools/tasks.js +30 -2
  15. package/dist/mcp/tools/tasks.js.map +1 -1
  16. package/dist/mcp/types.d.ts +24 -0
  17. package/dist/mcp/types.d.ts.map +1 -1
  18. package/dist/mcp/types.js +12 -1
  19. package/dist/mcp/types.js.map +1 -1
  20. package/dist/prompts/task-handoff.d.ts +6 -0
  21. package/dist/prompts/task-handoff.d.ts.map +1 -0
  22. package/dist/prompts/task-handoff.js +17 -0
  23. package/dist/prompts/task-handoff.js.map +1 -0
  24. package/dist/prompts/task-handoff.test.d.ts +2 -0
  25. package/dist/prompts/task-handoff.test.d.ts.map +1 -0
  26. package/dist/prompts/task-handoff.test.js +27 -0
  27. package/dist/prompts/task-handoff.test.js.map +1 -0
  28. package/dist/prompts/workspace-background-service-policy.d.ts +6 -0
  29. package/dist/prompts/workspace-background-service-policy.d.ts.map +1 -1
  30. package/dist/prompts/workspace-background-service-policy.js +2 -1
  31. package/dist/prompts/workspace-background-service-policy.js.map +1 -1
  32. package/dist/routes/__tests__/task-handoff.test.d.ts +2 -0
  33. package/dist/routes/__tests__/task-handoff.test.d.ts.map +1 -0
  34. package/dist/routes/__tests__/task-handoff.test.js +185 -0
  35. package/dist/routes/__tests__/task-handoff.test.js.map +1 -0
  36. package/dist/routes/__tests__/tasks.test.js +23 -0
  37. package/dist/routes/__tests__/tasks.test.js.map +1 -1
  38. package/dist/routes/tasks.d.ts.map +1 -1
  39. package/dist/routes/tasks.js +59 -2
  40. package/dist/routes/tasks.js.map +1 -1
  41. package/dist/services/__tests__/session-manager.lifecycle.test.js +171 -1
  42. package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
  43. package/dist/services/__tests__/task.service.test.js +99 -76
  44. package/dist/services/__tests__/task.service.test.js.map +1 -1
  45. package/dist/services/__tests__/workspace-background-service.service.test.js +74 -26
  46. package/dist/services/__tests__/workspace-background-service.service.test.js.map +1 -1
  47. package/dist/services/__tests__/workspace.service.test.js +36 -0
  48. package/dist/services/__tests__/workspace.service.test.js.map +1 -1
  49. package/dist/services/message-delivery.service.d.ts +2 -0
  50. package/dist/services/message-delivery.service.d.ts.map +1 -1
  51. package/dist/services/project.service.d.ts +2 -0
  52. package/dist/services/project.service.d.ts.map +1 -1
  53. package/dist/services/session-manager.d.ts +8 -1
  54. package/dist/services/session-manager.d.ts.map +1 -1
  55. package/dist/services/session-manager.js +18 -2
  56. package/dist/services/session-manager.js.map +1 -1
  57. package/dist/services/task-cleanup.service.d.ts +2 -0
  58. package/dist/services/task-cleanup.service.d.ts.map +1 -1
  59. package/dist/services/task-cleanup.service.js +2 -2
  60. package/dist/services/task-cleanup.service.js.map +1 -1
  61. package/dist/services/task-handoff.service.d.ts +18 -0
  62. package/dist/services/task-handoff.service.d.ts.map +1 -0
  63. package/dist/services/task-handoff.service.js +87 -0
  64. package/dist/services/task-handoff.service.js.map +1 -0
  65. package/dist/services/task.service.d.ts +35 -10
  66. package/dist/services/task.service.d.ts.map +1 -1
  67. package/dist/services/task.service.js +210 -168
  68. package/dist/services/task.service.js.map +1 -1
  69. package/dist/services/team-run.service.d.ts +2 -0
  70. package/dist/services/team-run.service.d.ts.map +1 -1
  71. package/dist/services/team-task-input.service.d.ts +2 -0
  72. package/dist/services/team-task-input.service.d.ts.map +1 -1
  73. package/dist/services/workspace-background-service.service.d.ts +1 -3
  74. package/dist/services/workspace-background-service.service.d.ts.map +1 -1
  75. package/dist/services/workspace-background-service.service.js +17 -65
  76. package/dist/services/workspace-background-service.service.js.map +1 -1
  77. package/dist/services/workspace.service.d.ts +10 -0
  78. package/dist/services/workspace.service.d.ts.map +1 -1
  79. package/dist/services/workspace.service.js +50 -7
  80. package/dist/services/workspace.service.js.map +1 -1
  81. package/dist/web/assets/AgentDemoPage-JXetlNBf.js +1 -0
  82. package/dist/web/assets/{AgentEnvironmentSettingsPage-CvvkMKT1.js → AgentEnvironmentSettingsPage-CdYyUCwJ.js} +1 -1
  83. package/dist/web/assets/AttachmentPreview-DMS-PM-1.js +2 -0
  84. package/dist/web/assets/{BranchSelect-Cv92vEjG.js → BranchSelect-BAq4L2ZS.js} +1 -1
  85. package/dist/web/assets/BrandLogo--kWXXJtZ.js +1 -0
  86. package/dist/web/assets/ConversationPage-CvC4RyB5.js +1 -0
  87. package/dist/web/assets/CreateTaskInput-DjVxCAfc.js +1 -0
  88. package/dist/web/assets/{DemoPage-CpYNBQhA.js → DemoPage-cDVTGOp8.js} +3 -3
  89. package/dist/web/assets/{DesignSystemPage-DLeH6DnV.js → DesignSystemPage-BI54IkKi.js} +3 -3
  90. package/dist/web/assets/{ExecutionPermissionSelector-IoWR9YLb.js → ExecutionPermissionSelector-f1UYIm1z.js} +1 -1
  91. package/dist/web/assets/{GeneralSettingsPage-BAYgxCqx.js → GeneralSettingsPage-C0yto6C_.js} +1 -1
  92. package/dist/web/assets/{LoadingPreviewPage-CumrzyxW.js → LoadingPreviewPage-BiJSaHUi.js} +1 -1
  93. package/dist/web/assets/{McpSettingsPage-CFNwxT9Y.js → McpSettingsPage-BbHwZtyj.js} +1 -1
  94. package/dist/web/assets/{MemberAvatar-BVXD0jTy.js → MemberAvatar-BLezO3vS.js} +1 -1
  95. package/dist/web/assets/MobileTaskDetail-M_Kke9rL.js +3 -0
  96. package/dist/web/assets/{NotificationSettingsPage-B0s5YbG3.js → NotificationSettingsPage-CiKoL2Kv.js} +1 -1
  97. package/dist/web/assets/{ProfileSettingsPage-BuhoF-mL.js → ProfileSettingsPage-DCB48cPS.js} +2 -2
  98. package/dist/web/assets/ProjectKanbanPage-soDqevjQ.js +12 -0
  99. package/dist/web/assets/{ProjectSettingsPage-DDMg6axy.js → ProjectSettingsPage-COvIVq1h.js} +2 -2
  100. package/dist/web/assets/{ProviderSettingsPage-DRt9m4mF.js → ProviderSettingsPage-CPuJcZDg.js} +2 -2
  101. package/dist/web/assets/{SettingsMasterDetail-D7vStQjR.js → SettingsMasterDetail-Cv7YaRJB.js} +1 -1
  102. package/dist/web/assets/TaskDetail-CqhPxvg3.js +1 -0
  103. package/dist/web/assets/TaskHandoffPanel-CxH3Mgyd.js +70 -0
  104. package/dist/web/assets/TeamSettingsPage-D-XDefNd.js +1 -0
  105. package/dist/web/assets/{TranslationSettingsPage-D2oks6OM.js → TranslationSettingsPage-BPHI-SZW.js} +1 -1
  106. package/dist/web/assets/{arc-vYWCZsE_.js → arc-CC81aLuf.js} +1 -1
  107. package/dist/web/assets/{architectureDiagram-3BPJPVTR-Cu1VIpcJ.js → architectureDiagram-3BPJPVTR-DL08XGaN.js} +1 -1
  108. package/dist/web/assets/{at-sign-BhSPafOS.js → at-sign-DYPIlD6D.js} +1 -1
  109. package/dist/web/assets/{blockDiagram-GPEHLZMM-C4x_nj5S.js → blockDiagram-GPEHLZMM-B4IW5WxI.js} +1 -1
  110. package/dist/web/assets/{c4Diagram-AAUBKEIU-jHOo5lal.js → c4Diagram-AAUBKEIU-Cj4d_FqK.js} +1 -1
  111. package/dist/web/assets/channel-CUa8oyzC.js +1 -0
  112. package/dist/web/assets/check-DaZqOL8l.js +1 -0
  113. package/dist/web/assets/{chevron-down-BoS08pMa.js → chevron-down-DNhYsNAt.js} +1 -1
  114. package/dist/web/assets/{chevron-up-BWtKfdKT.js → chevron-up-CjrLieBR.js} +1 -1
  115. package/dist/web/assets/{chip-Ck4dqxR3.js → chip-Cf92J-pQ.js} +1 -1
  116. package/dist/web/assets/{chunk-2J33WTMH-BZcpjmfm.js → chunk-2J33WTMH-DeCj8QSD.js} +1 -1
  117. package/dist/web/assets/{chunk-4BX2VUAB-DZGB3q-I.js → chunk-4BX2VUAB-k-6rzQmQ.js} +1 -1
  118. package/dist/web/assets/{chunk-55IACEB6-CcNrOmyy.js → chunk-55IACEB6-qMTCQ5bX.js} +1 -1
  119. package/dist/web/assets/{chunk-727SXJPM-dWrxio8O.js → chunk-727SXJPM-CMnu18d1.js} +1 -1
  120. package/dist/web/assets/{chunk-AQP2D5EJ-K4gkVzqE.js → chunk-AQP2D5EJ-C0mFbUMn.js} +1 -1
  121. package/dist/web/assets/{chunk-FMBD7UC4-CnHyY4qD.js → chunk-FMBD7UC4-u6-_M137.js} +1 -1
  122. package/dist/web/assets/{chunk-ND2GUHAM-CHUmcyDv.js → chunk-ND2GUHAM-DHrIE_8H.js} +1 -1
  123. package/dist/web/assets/{chunk-QZHKN3VN-Bzzjd-Qt.js → chunk-QZHKN3VN-aTyC0wZq.js} +1 -1
  124. package/dist/web/assets/classDiagram-4FO5ZUOK-BAx111NO.js +1 -0
  125. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BAx111NO.js +1 -0
  126. package/dist/web/assets/{code-block-OCS4YCEC-B2hHBO_G.js → code-block-OCS4YCEC-CBIOQOMH.js} +1 -1
  127. package/dist/web/assets/confirm-dialog-DWeBZLJW.js +1 -0
  128. package/dist/web/assets/{content-D0qmHfHK.js → content-JM9E1MeD.js} +1 -1
  129. package/dist/web/assets/{copy-C4WnOY6Y.js → copy-D0gl9kF8.js} +1 -1
  130. package/dist/web/assets/{cose-bilkent-S5V4N54A-Dcn8tOhb.js → cose-bilkent-S5V4N54A-xLyliSgU.js} +1 -1
  131. package/dist/web/assets/cssMode-DO5UnQ64.js +1 -0
  132. package/dist/web/assets/{dagre-BM42HDAG-DWBV4UfP.js → dagre-BM42HDAG-CbNpoO2d.js} +1 -1
  133. package/dist/web/assets/{diagram-2AECGRRQ-CnNTJQ3q.js → diagram-2AECGRRQ-CG3hKxAX.js} +1 -1
  134. package/dist/web/assets/{diagram-5GNKFQAL-DlXJFzDF.js → diagram-5GNKFQAL-mQu9M28s.js} +1 -1
  135. package/dist/web/assets/{diagram-KO2AKTUF-DZ22YV4n.js → diagram-KO2AKTUF-BSeEp_Uw.js} +1 -1
  136. package/dist/web/assets/{diagram-LMA3HP47-B4pQJjG9.js → diagram-LMA3HP47-D6yRdao-.js} +1 -1
  137. package/dist/web/assets/{diagram-OG6HWLK6-DJBLjtgL.js → diagram-OG6HWLK6-BUBUW9wD.js} +1 -1
  138. package/dist/web/assets/{dialog-1o8KQWoQ.js → dialog-D1lkhqVL.js} +2 -2
  139. package/dist/web/assets/{erDiagram-TEJ5UH35-CDsfoJUT.js → erDiagram-TEJ5UH35-BQSZgRqq.js} +1 -1
  140. package/dist/web/assets/{file-6qwbIga1.js → file-BijjwxRR.js} +1 -1
  141. package/dist/web/assets/{flowDiagram-I6XJVG4X-CuGOAYmY.js → flowDiagram-I6XJVG4X-D-eqyr5Y.js} +1 -1
  142. package/dist/web/assets/{folder-picker-Cj1VZOZU.js → folder-picker-Czxm0_S9.js} +1 -1
  143. package/dist/web/assets/{freemarker2-BG8VyI2V.js → freemarker2-B4RvEa_U.js} +2 -2
  144. package/dist/web/assets/{ganttDiagram-6RSMTGT7-BjrSOqAM.js → ganttDiagram-6RSMTGT7-BWGkCt_x.js} +1 -1
  145. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-4VVr4F8_.js → gitGraphDiagram-PVQCEYII-DGVc49pR.js} +1 -1
  146. package/dist/web/assets/{globe-CxeM27yy.js → globe-Ce01-45x.js} +1 -1
  147. package/dist/web/assets/handlebars-oyT7drqS.js +1 -0
  148. package/dist/web/assets/html-CxxWf2Qk.js +1 -0
  149. package/dist/web/assets/htmlMode-RfAHh5Jj.js +1 -0
  150. package/dist/web/assets/index-BMZFF1UJ.js +1 -0
  151. package/dist/web/assets/index-BN53vGxp.css +1 -0
  152. package/dist/web/assets/index-Cc3qXMhU.js +122 -0
  153. package/dist/web/assets/{index-DVdiOmqt.js → index-DWrSDMX2.js} +1 -1
  154. package/dist/web/assets/{index-CrKx8Yaz.js → index-Dh-tgeaL.js} +4 -4
  155. package/dist/web/assets/{index-hHrknzh7.js → index-Disv1kQS.js} +1 -1
  156. package/dist/web/assets/{index-Da2mWAtm.js → index-DyJfPc4O.js} +1 -1
  157. package/dist/web/assets/{index-cu8sedJH.js → index-Pt4TsHjm.js} +1 -1
  158. package/dist/web/assets/{index-C1lMEjmZ.js → index-T38iVLEb.js} +1 -1
  159. package/dist/web/assets/{index-Db28hRe8.js → index-hpHE1jXY.js} +1 -1
  160. package/dist/web/assets/{infoDiagram-5YYISTIA-Cy9ntb6t.js → infoDiagram-5YYISTIA-DLPs3dV_.js} +1 -1
  161. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-D6UiVzRz.js → ishikawaDiagram-YF4QCWOH-d_OrnwDY.js} +1 -1
  162. package/dist/web/assets/javascript-BNXtViSL.js +1 -0
  163. package/dist/web/assets/{journeyDiagram-JHISSGLW-owdKzZoh.js → journeyDiagram-JHISSGLW-EtU1st_0.js} +1 -1
  164. package/dist/web/assets/jsonMode-Bqh5vIxx.js +7 -0
  165. package/dist/web/assets/{kanban-definition-UN3LZRKU-v5TBnyp0.js → kanban-definition-UN3LZRKU-ZWzUnsFK.js} +1 -1
  166. package/dist/web/assets/{kbd-Cl4AzzJp.js → kbd-IjDGsZ2B.js} +1 -1
  167. package/dist/web/assets/{key-round-CnYjSrCz.js → key-round-CbnAuUxp.js} +1 -1
  168. package/dist/web/assets/{layers-XImbMX-P.js → layers-Pdy9vZ6K.js} +1 -1
  169. package/dist/web/assets/{linear-DNlKYt8R.js → linear-D7n23wcA.js} +1 -1
  170. package/dist/web/assets/{link-DQk-Vapq.js → link-Bl1ZbjFh.js} +1 -1
  171. package/dist/web/assets/liquid-Ds47iVLN.js +1 -0
  172. package/dist/web/assets/{lspLanguageFeatures-DD_y1bZa.js → lspLanguageFeatures-Dcch3bsp.js} +1 -1
  173. package/dist/web/assets/mdx-CM7ky-2B.js +1 -0
  174. package/dist/web/assets/{menu-DXF6iUsq.js → menu-DubntFHq.js} +1 -1
  175. package/dist/web/assets/{mermaid-NOHMQCX5-CG_LpwAI.js → mermaid-NOHMQCX5-nBsnjgeU.js} +49 -49
  176. package/dist/web/assets/{message-square-BviHdb67.js → message-square-BE-pW0m_.js} +1 -1
  177. package/dist/web/assets/{mindmap-definition-RKZ34NQL-Bn_rnnGT.js → mindmap-definition-RKZ34NQL-ClHORNTt.js} +1 -1
  178. package/dist/web/assets/modal-CHLtZWvx.js +1 -0
  179. package/dist/web/assets/{monaco-yuGji-2E.js → monaco-BbAmalE2.js} +3 -3
  180. package/dist/web/assets/nav-row-DH7rNq1V.js +1 -0
  181. package/dist/web/assets/{paperclip-D9ka2IAB.js → paperclip-YP3Bk1lI.js} +1 -1
  182. package/dist/web/assets/{pencil-isP1rXC-.js → pencil-BFDuhbtW.js} +1 -1
  183. package/dist/web/assets/{people-CbmH8m4i.js → people-DEeNaaLt.js} +1 -1
  184. package/dist/web/assets/{pieDiagram-4H26LBE5-DIE_f9W_.js → pieDiagram-4H26LBE5-BUOaKIyP.js} +1 -1
  185. package/dist/web/assets/{popover-D8VtO5qF.js → popover-CUmBEXxL.js} +1 -1
  186. package/dist/web/assets/python-BODTtU6m.js +1 -0
  187. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-CavFxJCk.js → quadrantDiagram-W4KKPZXB-Caezg-U2.js} +1 -1
  188. package/dist/web/assets/razor-CHm8DY1p.js +1 -0
  189. package/dist/web/assets/{requirementDiagram-4Y6WPE33-Dm1FwUUh.js → requirementDiagram-4Y6WPE33-BtdP3Ka6.js} +1 -1
  190. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-qeqcO27M.js → sankeyDiagram-5OEKKPKP-Bv74oA-n.js} +1 -1
  191. package/dist/web/assets/{search-DOMr5jPS.js → search-ClmOQd4H.js} +1 -1
  192. package/dist/web/assets/{select-CzZNbe1Z.js → select-BB7QG08o.js} +1 -1
  193. package/dist/web/assets/{select-Cw5BaOLQ.js → select-CYtYPr-c.js} +1 -1
  194. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-Dqtb3BYG.js → sequenceDiagram-3UESZ5HK-DuNPl-dg.js} +1 -1
  195. package/dist/web/assets/{server-u-PC-CcX.js → server-bEB_DY55.js} +1 -1
  196. package/dist/web/assets/{stateDiagram-AJRCARHV-C_MqF9Fy.js → stateDiagram-AJRCARHV-B72HAgBJ.js} +1 -1
  197. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Bu3l_CgB.js +1 -0
  198. package/dist/web/assets/{textarea-xt2hLb-d.js → textarea-BqYPvoO-.js} +1 -1
  199. package/dist/web/assets/{timeline-definition-PNZ67QCA-Bzov6t31.js → timeline-definition-PNZ67QCA-DTmHUgmQ.js} +1 -1
  200. package/dist/web/assets/{toggles-B6E1UBYN.js → toggles-a5fkwT_D.js} +1 -1
  201. package/dist/web/assets/tooltip-DuyJMci9.js +1 -0
  202. package/dist/web/assets/{track-BeqaPopG.js → track-DCjyrv3n.js} +1 -1
  203. package/dist/web/assets/{trash-2-5jI3_KHh.js → trash-2-CIyBqaVy.js} +1 -1
  204. package/dist/web/assets/tsMode-C1UXAkMh.js +11 -0
  205. package/dist/web/assets/typescript-Biht2wHb.js +1 -0
  206. package/dist/web/assets/{upload-DUteazR7.js → upload-D05XS5-G.js} +1 -1
  207. package/dist/web/assets/use-profiles-CrWTyz1G.js +1 -0
  208. package/dist/web/assets/{use-providers-CQwBj9hp.js → use-providers-Dv1Nk6s7.js} +1 -1
  209. package/dist/web/assets/{use-translation-settings-DBzFqP-d.js → use-translation-settings-CcUnipVO.js} +1 -1
  210. package/dist/web/assets/{useNormalizedLogs-DsdwmfS7.js → useNormalizedLogs-D9W1cT4R.js} +1 -1
  211. package/dist/web/assets/useTokenUsage-CZ5mDshD.js +1 -0
  212. package/dist/web/assets/{vennDiagram-CIIHVFJN-BqxICvT_.js → vennDiagram-CIIHVFJN-ByoNydXe.js} +1 -1
  213. package/dist/web/assets/{wardley-L42UT6IY-BTdLPFcA.js → wardley-L42UT6IY-BqdMkOLl.js} +1 -1
  214. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-icnFsFn4.js → wardleyDiagram-YWT4CUSO-AO22HrEF.js} +1 -1
  215. package/dist/web/assets/xml-NLGqNFDv.js +1 -0
  216. package/dist/web/assets/{xychartDiagram-2RQKCTM6-BN2_ducK.js → xychartDiagram-2RQKCTM6-vzPgjgqW.js} +1 -1
  217. package/dist/web/assets/yaml-AWlGS_Ig.js +1 -0
  218. package/dist/web/index.html +2 -2
  219. package/node_modules/@agent-tower/shared/dist/index.d.ts +1 -0
  220. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  221. package/node_modules/@agent-tower/shared/dist/index.js +1 -0
  222. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  223. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts +12 -0
  224. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts.map +1 -0
  225. package/node_modules/@agent-tower/shared/dist/task-handoff.js +2 -0
  226. package/node_modules/@agent-tower/shared/dist/task-handoff.js.map +1 -0
  227. package/node_modules/@agent-tower/shared/dist/types.d.ts +16 -1
  228. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  229. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  230. package/package.json +1 -1
  231. package/prisma/migrations/20261023010000_add_subtasks/migration.sql +2 -0
  232. package/prisma/migrations/20261026000000_add_task_handoff/migration.sql +1 -0
  233. package/prisma/schema.prisma +5 -0
  234. package/dist/web/assets/AgentDemoPage-Dwux6TyV.js +0 -1
  235. package/dist/web/assets/AttachmentPreview-CFJ6MZ1f.js +0 -2
  236. package/dist/web/assets/BrandLogo-H4nCbVzs.js +0 -1
  237. package/dist/web/assets/ConversationPage-CZzGME21.js +0 -1
  238. package/dist/web/assets/CreateTaskInput-BM2tx4hJ.js +0 -1
  239. package/dist/web/assets/EditableTaskTitle-BEsiULnD.js +0 -70
  240. package/dist/web/assets/MobileTaskDetail-IOQ1WnS8.js +0 -3
  241. package/dist/web/assets/ProjectKanbanPage-DMtv15rq.js +0 -12
  242. package/dist/web/assets/TaskDetail-DzKbs5Jt.js +0 -1
  243. package/dist/web/assets/TeamSettingsPage-fjlaqaLo.js +0 -1
  244. package/dist/web/assets/channel-D1cEj22-.js +0 -1
  245. package/dist/web/assets/check-D_VFGpIy.js +0 -1
  246. package/dist/web/assets/classDiagram-4FO5ZUOK-DIrDMefa.js +0 -1
  247. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-DIrDMefa.js +0 -1
  248. package/dist/web/assets/confirm-dialog-CL_1pF4K.js +0 -1
  249. package/dist/web/assets/cssMode-CXBMyfYY.js +0 -1
  250. package/dist/web/assets/handlebars-Ci38FPsA.js +0 -1
  251. package/dist/web/assets/html-5hdYEgVF.js +0 -1
  252. package/dist/web/assets/htmlMode-BzU9qsw0.js +0 -1
  253. package/dist/web/assets/icon-button-BmrJtYFC.js +0 -1
  254. package/dist/web/assets/index-BR3Wg2fS.js +0 -122
  255. package/dist/web/assets/index-DSy5JLC1.js +0 -1
  256. package/dist/web/assets/index-DZLg4-xM.css +0 -1
  257. package/dist/web/assets/javascript-Ib060GQ8.js +0 -1
  258. package/dist/web/assets/jsonMode-CLmu6gzc.js +0 -7
  259. package/dist/web/assets/liquid-Bc7OSAPR.js +0 -1
  260. package/dist/web/assets/mdx-CxJNHHTn.js +0 -1
  261. package/dist/web/assets/modal-D496f1fE.js +0 -1
  262. package/dist/web/assets/python-C0E43gUo.js +0 -1
  263. package/dist/web/assets/razor-xarhYR9V.js +0 -1
  264. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-BxmbGAhh.js +0 -1
  265. package/dist/web/assets/tooltip-C8--8CNX.js +0 -1
  266. package/dist/web/assets/tsMode-uT3sMVHt.js +0 -11
  267. package/dist/web/assets/typescript-Dkdj2ks0.js +0 -1
  268. package/dist/web/assets/use-profiles-T3saKiBg.js +0 -1
  269. package/dist/web/assets/useTokenUsage-Drh3v3kx.js +0 -1
  270. package/dist/web/assets/xml-BO8gYu0L.js +0 -1
  271. package/dist/web/assets/yaml-DlovbQzw.js +0 -1
  272. /package/dist/web/assets/{EditableTaskTitle-DcpNTP1B.css → TaskHandoffPanel-DcpNTP1B.css} +0 -0
@@ -3,7 +3,7 @@ export { buildTextPreview } from '../utils/text-preview.js';
3
3
  import { prisma } from '../utils/index.js';
4
4
  import { TaskStatus, SessionStatus, SessionPurpose, WorkspaceKind, WorkspaceStatus } from '../types/index.js';
5
5
  import { getWorkspaceWorkingDir } from './workspace-kind.js';
6
- import { NotFoundError, ValidationError, InvalidStateTransitionError, } from '../errors.js';
6
+ import { NotFoundError, ServiceError, ValidationError, InvalidStateTransitionError, } from '../errors.js';
7
7
  import { defaultWorkspaceLifecycleBarrier } from './workspace-lifecycle-barrier.js';
8
8
  import { ensureProjectIsMutable, getStoredProjectGitCapability, detectProjectGitCapability } from './project-guards.js';
9
9
  import { requireLocalBaseBranch } from '../git/base-branch.js';
@@ -11,6 +11,12 @@ import { defaultTeamLockService } from './team-lock.service.js';
11
11
  import { TaskPriority } from '@agent-tower/shared';
12
12
  import prismaPkg from '@prisma/client';
13
13
  const { Prisma } = prismaPkg;
14
+ const taskBoardColumns = Prisma.sql `
15
+ task."id", task."projectId", task."title", task."status", task."priority",
16
+ task."position", task."updatedAt", task."parentTaskId",
17
+ (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL) AS "subtaskCount",
18
+ (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL AND child."status" = 'IN_PROGRESS') AS "runningSubtaskCount"
19
+ `;
14
20
  /**
15
21
  * 合法的状态流转规则
16
22
  * 看板拖拽场景下允许任意状态互转,状态变更无危险副作用
@@ -211,13 +217,17 @@ function normalizeUpdateTaskInput(current, input) {
211
217
  ]),
212
218
  };
213
219
  }
220
+ function withoutTaskHandoff(task) {
221
+ const { handoff: _handoff, ...metadata } = task;
222
+ return metadata;
223
+ }
214
224
  function withTaskPreviews(task, options = {}) {
215
225
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
216
226
  const contentPreview = buildTextPreview(task.description);
217
227
  const isTruncated = task.title !== titlePreview
218
228
  || Boolean(task.description && task.description !== contentPreview);
219
229
  const base = {
220
- ...task,
230
+ ...withoutTaskHandoff(task),
221
231
  title: titlePreview,
222
232
  titlePreview,
223
233
  contentPreview: contentPreview || undefined,
@@ -235,13 +245,15 @@ function withProjectGitMetadata(task) {
235
245
  return withProjectGitCapability(task, capability);
236
246
  }
237
247
  function withProjectGitCapability(task, capability) {
238
- return {
248
+ const result = {
239
249
  ...task,
240
250
  project: {
241
251
  ...task.project,
242
252
  ...capability,
243
253
  },
244
254
  };
255
+ delete result.handoff;
256
+ return result;
245
257
  }
246
258
  function buildProjectGitMetadata(project) {
247
259
  return getStoredProjectGitCapability(project);
@@ -275,30 +287,28 @@ export class TaskService {
275
287
  deletedAt: null,
276
288
  project: { archivedAt: null },
277
289
  ...(params.projectId ? { projectId: params.projectId } : {}),
290
+ ...(params.parentTaskId ? { parentTaskId: params.parentTaskId } : {}),
278
291
  ...(params.status ? { status: params.status } : {}),
279
292
  };
280
293
  const projectFilter = params.projectId
281
294
  ? Prisma.sql `AND task."projectId" = ${params.projectId}`
282
295
  : Prisma.empty;
296
+ const parentFilter = params.parentTaskId
297
+ ? Prisma.sql `AND task."parentTaskId" = ${params.parentTaskId}`
298
+ : Prisma.empty;
283
299
  const statusFilter = params.status
284
300
  ? Prisma.sql `AND task."status" = ${params.status}`
285
301
  : Prisma.empty;
286
302
  const [tasks, total] = await Promise.all([
287
303
  prisma.$queryRaw(Prisma.sql `
288
- SELECT
289
- task."id" AS "id",
290
- task."projectId" AS "projectId",
291
- task."title" AS "title",
292
- task."status" AS "status",
293
- task."priority" AS "priority",
294
- task."position" AS "position",
295
- task."updatedAt" AS "updatedAt"
304
+ SELECT ${taskBoardColumns}
296
305
  FROM "Task" task
297
306
  INNER JOIN "Project" project ON project."id" = task."projectId"
298
307
  WHERE task."deletedAt" IS NULL
299
308
  AND project."archivedAt" IS NULL
300
309
  ${projectFilter}
301
310
  ${statusFilter}
311
+ ${parentFilter}
302
312
  ORDER BY
303
313
  CASE task."status"
304
314
  WHEN ${TaskStatus.IN_PROGRESS} THEN 0
@@ -315,7 +325,19 @@ export class TaskService {
315
325
  `),
316
326
  prisma.task.count({ where }),
317
327
  ]);
318
- const taskIds = tasks.map((task) => task.id);
328
+ // A child can rank ahead of its completed parent or fall on another page.
329
+ // Return compact parent context without changing the page's count/limit.
330
+ const pageIds = new Set(tasks.map((task) => task.id));
331
+ const missingParentIds = [...new Set(tasks.flatMap((task) => task.parentTaskId && !pageIds.has(task.parentTaskId) ? [task.parentTaskId] : []))];
332
+ const parents = params.parentTaskId || missingParentIds.length === 0 ? []
333
+ : await prisma.$queryRaw(Prisma.sql `
334
+ SELECT ${taskBoardColumns} FROM "Task" task
335
+ INNER JOIN "Project" project ON project."id" = task."projectId"
336
+ WHERE task."id" IN (${Prisma.join(missingParentIds)})
337
+ AND task."deletedAt" IS NULL AND project."archivedAt" IS NULL
338
+ `);
339
+ const allTasks = [...tasks, ...parents];
340
+ const taskIds = allTasks.map((task) => task.id);
319
341
  const workspaceRows = taskIds.length === 0
320
342
  ? []
321
343
  : await prisma.$queryRaw(Prisma.sql `
@@ -363,7 +385,7 @@ export class TaskService {
363
385
  workspacesByTaskId.set(workspace.taskId, group);
364
386
  }
365
387
  const preferredWorkspaceByTaskId = new Map();
366
- for (const task of tasks) {
388
+ for (const task of allTasks) {
367
389
  const candidates = workspacesByTaskId.get(task.id) ?? [];
368
390
  const preferred = candidates.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
369
391
  ?? candidates[0];
@@ -371,7 +393,7 @@ export class TaskService {
371
393
  preferredWorkspaceByTaskId.set(task.id, preferred);
372
394
  }
373
395
  }
374
- const data = tasks.map((task) => {
396
+ const data = allTasks.map((task) => {
375
397
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
376
398
  const preferredWorkspace = preferredWorkspaceByTaskId.get(task.id);
377
399
  const hasRunningSession = (workspacesByTaskId.get(task.id) ?? [])
@@ -382,6 +404,9 @@ export class TaskService {
382
404
  return {
383
405
  id: task.id,
384
406
  projectId: task.projectId,
407
+ parentTaskId: task.parentTaskId,
408
+ subtaskCount: Number(task.subtaskCount),
409
+ runningSubtaskCount: Number(task.runningSubtaskCount),
385
410
  title: titlePreview,
386
411
  status: task.status,
387
412
  priority: task.priority ?? TaskPriority.NORMAL,
@@ -401,7 +426,8 @@ export class TaskService {
401
426
  };
402
427
  });
403
428
  return {
404
- data,
429
+ data: data.slice(0, tasks.length),
430
+ ...(parents.length ? { parents: data.slice(tasks.length) } : {}),
405
431
  total,
406
432
  page,
407
433
  limit,
@@ -443,6 +469,7 @@ export class TaskService {
443
469
  projectId: true,
444
470
  title: true,
445
471
  baseBranch: true,
472
+ parentTaskId: true,
446
473
  status: true,
447
474
  priority: true,
448
475
  position: true,
@@ -490,6 +517,8 @@ export class TaskService {
490
517
  project: true,
491
518
  title: true,
492
519
  baseBranch: true,
520
+ parentTaskId: true,
521
+ _count: { select: { subtasks: { where: { deletedAt: null } } } },
493
522
  status: true,
494
523
  priority: true,
495
524
  position: true,
@@ -501,7 +530,14 @@ export class TaskService {
501
530
  if (!task) {
502
531
  throw new NotFoundError('Task', id);
503
532
  }
504
- return withTaskPreviews(withProjectGitMetadata(task), { omitDescription: true });
533
+ const { _count, ...metadata } = task;
534
+ return withTaskPreviews(withProjectGitMetadata({ ...metadata, subtaskCount: _count.subtasks }), { omitDescription: true });
535
+ }
536
+ async findSubtasks(id, params = {}) {
537
+ const parent = await prisma.task.findFirst({ where: { id, deletedAt: null } });
538
+ if (!parent)
539
+ throw new NotFoundError('Task', id);
540
+ return this.findBoard({ ...params, projectId: parent.projectId, parentTaskId: id });
505
541
  }
506
542
  async findBodyById(id) {
507
543
  const task = await prisma.task.findFirst({
@@ -554,37 +590,73 @@ export class TaskService {
554
590
  throw new NotFoundError('Project', projectId);
555
591
  }
556
592
  ensureProjectIsMutable(project, 'create tasks');
557
- // Resolve at task creation so delayed starts and TeamRun main workspaces
558
- // cannot silently follow a later project default change. Local tasks opt out.
559
- let baseBranch = null;
560
- if (input.baseBranch !== null) {
561
- const requestedBranch = input.baseBranch ?? ((await detectProjectGitCapability(project.repoPath)).worktreeReady ? project.mainBranch : null);
562
- if (requestedBranch !== null) {
563
- baseBranch = await requireLocalBaseBranch(project.repoPath, requestedBranch);
593
+ const parent = input.parentTaskId ? await prisma.task.findFirst({
594
+ where: { id: input.parentTaskId, projectId, deletedAt: null },
595
+ include: { workspaces: {
596
+ where: { parentWorkspaceId: null, ownerMemberId: null, status: { not: WorkspaceStatus.ABANDONED } },
597
+ orderBy: { createdAt: 'desc' },
598
+ } },
599
+ }) : null;
600
+ if (input.parentTaskId && !parent)
601
+ throw new NotFoundError('Parent task', input.parentTaskId);
602
+ if (parent?.parentTaskId)
603
+ throw new ValidationError('Subtasks cannot have their own subtasks');
604
+ const parentWorkspace = parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
605
+ ?? parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.HIBERNATED)
606
+ ?? parent?.workspaces[0];
607
+ const parentBranch = parentWorkspace?.branchName || parent?.baseBranch;
608
+ const createWithParentBranch = async () => {
609
+ // Resolve at task creation so delayed starts and TeamRun main workspaces
610
+ // cannot silently follow a later project default change. Local tasks opt out.
611
+ let baseBranch = null;
612
+ if (input.baseBranch !== null) {
613
+ const requestedBranch = input.baseBranch ?? parentBranch ?? ((await detectProjectGitCapability(project.repoPath)).worktreeReady ? project.mainBranch : null);
614
+ if (requestedBranch !== null) {
615
+ baseBranch = await requireLocalBaseBranch(project.repoPath, requestedBranch);
616
+ }
564
617
  }
565
- }
566
- // 自动计算 position
567
- const maxPosition = await prisma.task.aggregate({
568
- where: { projectId, status: TaskStatus.TODO, deletedAt: null },
569
- _max: { position: true },
570
- });
571
- const created = await prisma.task.create({
572
- data: {
573
- title: normalizedInput.title,
574
- description: normalizedInput.description,
575
- priority: normalizedInput.priority ?? TaskPriority.NORMAL,
576
- baseBranch,
577
- position: (maxPosition._max.position ?? 0) + 1,
578
- projectId,
579
- },
580
- });
581
- return {
582
- ...created,
583
- project: {
584
- ...project,
585
- ...buildProjectGitMetadata(project),
586
- },
618
+ // 自动计算 position
619
+ const maxPosition = await prisma.task.aggregate({
620
+ where: { projectId, status: TaskStatus.TODO, deletedAt: null },
621
+ _max: { position: true },
622
+ });
623
+ const created = await prisma.$transaction(async (tx) => {
624
+ // Recheck after Git validation, serialized with the deletion marker.
625
+ if (parent) {
626
+ const liveParent = await tx.task.findFirst({
627
+ where: { id: parent.id, projectId, deletedAt: null, project: { archivedAt: null } },
628
+ });
629
+ if (!liveParent)
630
+ throw new NotFoundError('Parent task', parent.id);
631
+ }
632
+ return tx.task.create({
633
+ data: {
634
+ title: normalizedInput.title,
635
+ description: normalizedInput.description,
636
+ priority: normalizedInput.priority ?? TaskPriority.NORMAL,
637
+ baseBranch,
638
+ parentTaskId: parent?.id,
639
+ position: (maxPosition._max.position ?? 0) + 1,
640
+ projectId,
641
+ },
642
+ });
643
+ });
644
+ this.eventBus.emit('task:updated', { taskId: created.id, projectId, status: created.status });
645
+ if (parent)
646
+ this.eventBus.emit('task:updated', { taskId: parent.id, projectId, status: parent.status });
647
+ return {
648
+ ...created,
649
+ project: {
650
+ ...project,
651
+ ...buildProjectGitMetadata(project),
652
+ },
653
+ };
587
654
  };
655
+ // Serialize branch validation and child persistence with parent workspace
656
+ // cleanup, so its reference becomes visible before that branch is reclaimed.
657
+ return parentWorkspace
658
+ ? defaultWorkspaceLifecycleBarrier.withWorkspace(parentWorkspace.id, createWithParentBranch)
659
+ : createWithParentBranch();
588
660
  }
589
661
  /**
590
662
  * 更新任务基本信息
@@ -607,7 +679,7 @@ export class TaskService {
607
679
  this.emitTaskUpdated(id, task.projectId, updated.status, updated.priority);
608
680
  }
609
681
  return {
610
- ...updated,
682
+ ...withoutTaskHandoff(updated),
611
683
  project: {
612
684
  ...task.project,
613
685
  ...buildProjectGitMetadata(task.project),
@@ -652,7 +724,7 @@ export class TaskService {
652
724
  // 通知前端
653
725
  this.emitTaskUpdated(id, task.projectId, status);
654
726
  return {
655
- ...updated,
727
+ ...withoutTaskHandoff(updated),
656
728
  project: {
657
729
  ...task.project,
658
730
  ...buildProjectGitMetadata(task.project),
@@ -689,7 +761,7 @@ export class TaskService {
689
761
  this.emitTaskUpdated(id, task.projectId, status);
690
762
  }
691
763
  return {
692
- ...updated,
764
+ ...withoutTaskHandoff(updated),
693
765
  project: {
694
766
  ...task.project,
695
767
  ...buildProjectGitMetadata(task.project),
@@ -699,149 +771,119 @@ export class TaskService {
699
771
  /**
700
772
  * 快速删除任务
701
773
  *
702
- * 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
703
- * 2. 保存后台清理快照
774
+ * 1. 在同一事务中标记父子 Task 为 deletedAt,并保存清理快照
775
+ * 2. 普通读取立即隐藏已删除的任务
704
776
  * 3. 通过 EventBus 通知前端实时删除
705
777
  * 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
706
778
  */
707
- async delete(id) {
708
- const deletedAt = new Date();
709
- const taskForGuard = await prisma.task.findUnique({
710
- where: { id },
711
- include: { project: true },
712
- });
713
- if (!taskForGuard || taskForGuard.deletedAt) {
779
+ async delete(id, expectedSubtaskCount) {
780
+ const taskForGuard = await prisma.task.findUnique({ where: { id }, include: { project: true } });
781
+ if (!taskForGuard || taskForGuard.deletedAt)
714
782
  throw new NotFoundError('Task', id);
715
- }
716
783
  ensureProjectIsMutable(taskForGuard.project, 'delete tasks');
784
+ if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== await prisma.task.count({
785
+ where: { parentTaskId: id, deletedAt: null },
786
+ })) {
787
+ throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
788
+ }
717
789
  const workspaceIds = await prisma.workspace.findMany({
718
- where: { taskId: id },
790
+ where: { task: { OR: [{ id }, { parentTaskId: id }], deletedAt: null } },
719
791
  select: { id: true },
720
792
  });
721
- let marked;
722
- await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
723
- for (const workspace of workspaceIds) {
793
+ const result = await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
794
+ for (const workspace of workspaceIds)
724
795
  await this.backgroundService?.stopAllForWorkspace(workspace.id);
725
- }
726
- marked = await prisma.task.updateMany({
727
- where: {
728
- id,
729
- deletedAt: null,
730
- project: { archivedAt: null },
731
- },
732
- data: { deletedAt },
733
- });
734
- });
735
- if (marked.count === 0) {
736
- const current = await prisma.task.findUnique({
737
- where: { id },
738
- include: { project: true },
739
- });
740
- if (!current || current.deletedAt) {
741
- throw new NotFoundError('Task', id);
742
- }
743
- ensureProjectIsMutable(current.project, 'delete tasks');
744
- throw new NotFoundError('Task', id);
745
- }
746
- let deleteResult;
747
- let cleanupJobCreated = false;
748
- try {
749
- const taskForCleanup = await prisma.task.findUnique({
750
- where: { id },
751
- include: {
752
- project: true,
753
- teamRun: {
754
- include: {
755
- invocations: {
756
- where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
757
- select: { id: true },
758
- },
759
- },
760
- },
761
- workspaces: {
762
- include: { sessions: true },
796
+ // Mark the complete family and persist its recovery snapshot together.
797
+ // Child creation rechecks the parent inside its own transaction.
798
+ return prisma.$transaction(async (tx) => {
799
+ const parent = await tx.task.findFirst({ where: { id, deletedAt: null }, include: { project: true } });
800
+ if (!parent)
801
+ throw new NotFoundError('Task', id);
802
+ ensureProjectIsMutable(parent.project, 'delete tasks');
803
+ const family = await tx.task.findMany({
804
+ where: { OR: [{ id }, { parentTaskId: id }], deletedAt: null },
805
+ select: { id: true },
806
+ });
807
+ if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== family.length - 1) {
808
+ throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
809
+ }
810
+ const taskIds = [...family.filter((task) => task.id !== id).map((task) => task.id), id];
811
+ await tx.task.updateMany({ where: { id: { in: taskIds } }, data: { deletedAt: new Date() } });
812
+ const tasks = await tx.task.findMany({
813
+ where: { id: { in: taskIds } },
814
+ include: {
815
+ workspaces: { include: { sessions: true } },
816
+ teamRun: { include: { invocations: {
817
+ where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } }, select: { id: true },
818
+ } } },
763
819
  },
764
- },
765
- });
766
- if (!taskForCleanup) {
767
- throw new NotFoundError('Task', id);
768
- }
769
- ensureProjectIsMutable(taskForCleanup.project, 'delete tasks');
770
- const snapshot = {
771
- taskId: taskForCleanup.id,
772
- projectId: taskForCleanup.projectId,
773
- project: {
774
- repoPath: taskForCleanup.project.repoPath,
775
- mainBranch: taskForCleanup.project.mainBranch,
776
- },
777
- workspaces: taskForCleanup.workspaces.map((workspace) => ({
778
- id: workspace.id,
779
- worktreePath: workspace.worktreePath,
780
- workingDir: getWorkspaceWorkingDir(workspace),
781
- workspaceKind: workspace.workspaceKind,
782
- branchName: workspace.branchName,
783
- baseBranch: workspace.baseBranch,
784
- // Logical completion does not close a reusable ACP adapter.
785
- sessions: workspace.sessions.map((session) => ({ id: session.id })),
786
- })),
787
- };
788
- const teamRunId = taskForCleanup.teamRun?.id;
789
- const cancelledInvocationIds = taskForCleanup.teamRun?.invocations.map((invocation) => invocation.id) ?? [];
790
- await prisma.$transaction(async (tx) => {
820
+ });
821
+ const tasksById = new Map(tasks.map((task) => [task.id, task]));
822
+ const orderedTasks = taskIds.map((taskId) => tasksById.get(taskId));
823
+ const snapshot = {
824
+ taskId: id, taskIds, projectId: parent.projectId,
825
+ project: { repoPath: parent.project.repoPath, mainBranch: parent.project.mainBranch },
826
+ workspaces: orderedTasks.flatMap((task) => task.workspaces.map((workspace) => ({
827
+ id: workspace.id,
828
+ worktreePath: workspace.worktreePath,
829
+ workingDir: getWorkspaceWorkingDir(workspace),
830
+ workspaceKind: workspace.workspaceKind,
831
+ branchName: workspace.branchName,
832
+ baseBranch: workspace.baseBranch,
833
+ sessions: workspace.sessions.map((session) => ({ id: session.id })),
834
+ }))),
835
+ };
791
836
  await tx.taskCleanupJob.create({
792
- data: {
793
- taskId: taskForCleanup.id,
794
- projectId: taskForCleanup.projectId,
795
- payload: JSON.stringify(snapshot),
796
- },
837
+ data: { taskId: id, projectId: parent.projectId, payload: JSON.stringify(snapshot) },
797
838
  });
798
- if (teamRunId) {
839
+ const teamRunIds = tasks.flatMap((task) => task.teamRun ? [task.teamRun.id] : []);
840
+ if (teamRunIds.length) {
799
841
  await tx.workRequest.updateMany({
800
- where: {
801
- teamRunId,
802
- status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES },
803
- },
842
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES } },
804
843
  data: { status: 'CANCELLED' },
805
844
  });
806
845
  await tx.agentInvocation.updateMany({
807
- where: {
808
- teamRunId,
809
- status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES },
810
- },
811
- data: {
812
- status: 'CANCELLED',
813
- nextRoomReplyReminderAt: null,
814
- },
846
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
847
+ data: { status: 'CANCELLED', nextRoomReplyReminderAt: null },
815
848
  });
816
849
  }
850
+ return {
851
+ taskIds, projectId: parent.projectId,
852
+ invocationIds: tasks.flatMap((task) => task.teamRun?.invocations.map((invocation) => invocation.id) ?? []),
853
+ };
817
854
  });
818
- cleanupJobCreated = true;
819
- deleteResult = {
820
- projectId: taskForCleanup.projectId,
821
- cancelledInvocationIds,
822
- };
823
- }
824
- catch (error) {
825
- if (!cleanupJobCreated) {
826
- await prisma.task.updateMany({
827
- where: { id, deletedAt },
828
- data: { deletedAt: null },
829
- }).catch(() => {
830
- // If rollback fails, leave the task hidden rather than masking the original error.
831
- });
832
- }
833
- throw error;
834
- }
835
- for (const invocationId of deleteResult.cancelledInvocationIds) {
836
- defaultTeamLockService.releaseByOwner(invocationId);
837
- }
838
- this.eventBus.emit('task:deleted', {
839
- taskId: id,
840
- projectId: deleteResult.projectId,
841
855
  });
856
+ for (const invocationId of result.invocationIds)
857
+ defaultTeamLockService.releaseByOwner(invocationId);
858
+ for (const taskId of result.taskIds)
859
+ this.eventBus.emit('task:deleted', { taskId, projectId: result.projectId });
842
860
  this.cleanupService?.trigger();
843
861
  return true;
844
862
  }
863
+ async getCleanupStatus(taskId) {
864
+ const job = await prisma.taskCleanupJob.findFirst({
865
+ where: { taskId }, orderBy: { createdAt: 'desc' },
866
+ select: { taskId: true, status: true, lastError: true, nextRetryAt: true },
867
+ });
868
+ if (!job)
869
+ throw new NotFoundError('Task cleanup', taskId);
870
+ return job;
871
+ }
872
+ async retryCleanup(taskId) {
873
+ const job = await prisma.taskCleanupJob.findFirst({ where: { taskId }, orderBy: { createdAt: 'desc' } });
874
+ if (!job)
875
+ throw new NotFoundError('Task cleanup', taskId);
876
+ const project = await prisma.project.findUnique({ where: { id: job.projectId } });
877
+ if (!project)
878
+ throw new NotFoundError('Project', job.projectId);
879
+ ensureProjectIsMutable(project, 'retry task cleanup');
880
+ await prisma.taskCleanupJob.updateMany({
881
+ where: { id: job.id, status: 'FAILED' },
882
+ data: { status: 'PENDING', attempts: 0, lastError: null, nextRetryAt: null },
883
+ });
884
+ this.cleanupService?.trigger();
885
+ return this.getCleanupStatus(taskId);
886
+ }
845
887
  /**
846
888
  * 获取项目的任务统计
847
889
  */