agent-tower 0.7.3 → 0.7.4-beta.1

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 (294) 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 +18 -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__/sessions-runtime.test.js +2 -0
  33. package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -1
  34. package/dist/routes/__tests__/task-handoff.test.d.ts +2 -0
  35. package/dist/routes/__tests__/task-handoff.test.d.ts.map +1 -0
  36. package/dist/routes/__tests__/task-handoff.test.js +185 -0
  37. package/dist/routes/__tests__/task-handoff.test.js.map +1 -0
  38. package/dist/routes/__tests__/tasks.test.js +64 -0
  39. package/dist/routes/__tests__/tasks.test.js.map +1 -1
  40. package/dist/routes/__tests__/workspace-history.test.d.ts +2 -0
  41. package/dist/routes/__tests__/workspace-history.test.d.ts.map +1 -0
  42. package/dist/routes/__tests__/workspace-history.test.js +174 -0
  43. package/dist/routes/__tests__/workspace-history.test.js.map +1 -0
  44. package/dist/routes/sessions.d.ts.map +1 -1
  45. package/dist/routes/sessions.js +10 -0
  46. package/dist/routes/sessions.js.map +1 -1
  47. package/dist/routes/tasks.d.ts.map +1 -1
  48. package/dist/routes/tasks.js +67 -2
  49. package/dist/routes/tasks.js.map +1 -1
  50. package/dist/runtime/__tests__/cli-driver-lifecycle.test.js +84 -0
  51. package/dist/runtime/__tests__/cli-driver-lifecycle.test.js.map +1 -1
  52. package/dist/runtime/cli-driver.js +11 -8
  53. package/dist/runtime/cli-driver.js.map +1 -1
  54. package/dist/services/__tests__/session-manager.lifecycle.test.js +171 -1
  55. package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
  56. package/dist/services/__tests__/task.service.test.js +152 -76
  57. package/dist/services/__tests__/task.service.test.js.map +1 -1
  58. package/dist/services/__tests__/workspace-background-service.service.test.js +74 -26
  59. package/dist/services/__tests__/workspace-background-service.service.test.js.map +1 -1
  60. package/dist/services/__tests__/workspace.service.test.js +100 -5
  61. package/dist/services/__tests__/workspace.service.test.js.map +1 -1
  62. package/dist/services/message-delivery.service.d.ts +3 -0
  63. package/dist/services/message-delivery.service.d.ts.map +1 -1
  64. package/dist/services/project.service.d.ts +2 -0
  65. package/dist/services/project.service.d.ts.map +1 -1
  66. package/dist/services/session-manager.d.ts +12 -1
  67. package/dist/services/session-manager.d.ts.map +1 -1
  68. package/dist/services/session-manager.js +21 -3
  69. package/dist/services/session-manager.js.map +1 -1
  70. package/dist/services/session-resource-cleanup.d.ts +5 -0
  71. package/dist/services/session-resource-cleanup.d.ts.map +1 -1
  72. package/dist/services/session-resource-cleanup.js +7 -0
  73. package/dist/services/session-resource-cleanup.js.map +1 -1
  74. package/dist/services/task-cleanup.service.d.ts +4 -0
  75. package/dist/services/task-cleanup.service.d.ts.map +1 -1
  76. package/dist/services/task-cleanup.service.js +6 -2
  77. package/dist/services/task-cleanup.service.js.map +1 -1
  78. package/dist/services/task-handoff.service.d.ts +18 -0
  79. package/dist/services/task-handoff.service.d.ts.map +1 -0
  80. package/dist/services/task-handoff.service.js +87 -0
  81. package/dist/services/task-handoff.service.js.map +1 -0
  82. package/dist/services/task.service.d.ts +46 -10
  83. package/dist/services/task.service.d.ts.map +1 -1
  84. package/dist/services/task.service.js +258 -170
  85. package/dist/services/task.service.js.map +1 -1
  86. package/dist/services/team-run.service.d.ts +2 -0
  87. package/dist/services/team-run.service.d.ts.map +1 -1
  88. package/dist/services/team-task-input.service.d.ts +2 -0
  89. package/dist/services/team-task-input.service.d.ts.map +1 -1
  90. package/dist/services/workspace-background-service.service.d.ts +1 -3
  91. package/dist/services/workspace-background-service.service.d.ts.map +1 -1
  92. package/dist/services/workspace-background-service.service.js +17 -65
  93. package/dist/services/workspace-background-service.service.js.map +1 -1
  94. package/dist/services/workspace.service.d.ts +20 -7
  95. package/dist/services/workspace.service.d.ts.map +1 -1
  96. package/dist/services/workspace.service.js +197 -82
  97. package/dist/services/workspace.service.js.map +1 -1
  98. package/dist/web/assets/AgentDemoPage-DiHEh1iv.js +1 -0
  99. package/dist/web/assets/{AgentEnvironmentSettingsPage-CvvkMKT1.js → AgentEnvironmentSettingsPage-B_Xk7Bef.js} +1 -1
  100. package/dist/web/assets/AttachmentPreview-BngGSahV.js +2 -0
  101. package/dist/web/assets/{BranchSelect-Cv92vEjG.js → BranchSelect-BgVKUnxj.js} +1 -1
  102. package/dist/web/assets/BrandLogo-CBahjTd7.js +1 -0
  103. package/dist/web/assets/ConversationPage-kgJIlyS5.js +1 -0
  104. package/dist/web/assets/CreateTaskInput-Cllw4abW.js +1 -0
  105. package/dist/web/assets/{DemoPage-CpYNBQhA.js → DemoPage-CkS__9CE.js} +3 -3
  106. package/dist/web/assets/{DesignSystemPage-DLeH6DnV.js → DesignSystemPage-BklN99GA.js} +3 -3
  107. package/dist/web/assets/{ExecutionPermissionSelector-IoWR9YLb.js → ExecutionPermissionSelector-Bsi1aa7q.js} +1 -1
  108. package/dist/web/assets/{GeneralSettingsPage-BAYgxCqx.js → GeneralSettingsPage-BC9TUWiu.js} +1 -1
  109. package/dist/web/assets/{LoadingPreviewPage-CumrzyxW.js → LoadingPreviewPage-Dx8Si6-g.js} +1 -1
  110. package/dist/web/assets/{McpSettingsPage-CFNwxT9Y.js → McpSettingsPage-fqXRGtFu.js} +1 -1
  111. package/dist/web/assets/{MemberAvatar-BVXD0jTy.js → MemberAvatar-CZ3F53Ep.js} +1 -1
  112. package/dist/web/assets/MobileTaskDetail-B68iYPOI.js +3 -0
  113. package/dist/web/assets/{NotificationSettingsPage-B0s5YbG3.js → NotificationSettingsPage-D9KGIFVA.js} +1 -1
  114. package/dist/web/assets/{ProfileSettingsPage-BuhoF-mL.js → ProfileSettingsPage-D_Hw5lpd.js} +2 -2
  115. package/dist/web/assets/ProjectKanbanPage-CUX0Kh-t.js +12 -0
  116. package/dist/web/assets/{ProjectSettingsPage-DDMg6axy.js → ProjectSettingsPage-CJANCPJX.js} +2 -2
  117. package/dist/web/assets/{ProviderSettingsPage-DRt9m4mF.js → ProviderSettingsPage-B6Ro5PQa.js} +2 -2
  118. package/dist/web/assets/{SettingsMasterDetail-D7vStQjR.js → SettingsMasterDetail-Q-049Dvh.js} +1 -1
  119. package/dist/web/assets/TaskDetail-DBtWf0YV.js +1 -0
  120. package/dist/web/assets/TaskHandoffPanel-BpGiZ5eT.js +70 -0
  121. package/dist/web/assets/TeamSettingsPage-DbKgqEO_.js +1 -0
  122. package/dist/web/assets/{TranslationSettingsPage-D2oks6OM.js → TranslationSettingsPage-Ba-iypOV.js} +1 -1
  123. package/dist/web/assets/{arc-vYWCZsE_.js → arc-SOqs3Svk.js} +1 -1
  124. package/dist/web/assets/{architectureDiagram-3BPJPVTR-Cu1VIpcJ.js → architectureDiagram-3BPJPVTR-BH25ukq_.js} +1 -1
  125. package/dist/web/assets/{at-sign-BhSPafOS.js → at-sign-CEsum1vJ.js} +1 -1
  126. package/dist/web/assets/{blockDiagram-GPEHLZMM-C4x_nj5S.js → blockDiagram-GPEHLZMM-CKA17iNP.js} +1 -1
  127. package/dist/web/assets/{c4Diagram-AAUBKEIU-jHOo5lal.js → c4Diagram-AAUBKEIU-DnZ_8oOf.js} +1 -1
  128. package/dist/web/assets/channel-xU45IQlP.js +1 -0
  129. package/dist/web/assets/check-DUA2DjGH.js +1 -0
  130. package/dist/web/assets/{chevron-down-BoS08pMa.js → chevron-down-Bmwy-nvS.js} +1 -1
  131. package/dist/web/assets/{chevron-up-BWtKfdKT.js → chevron-up-CClL6-SF.js} +1 -1
  132. package/dist/web/assets/{chip-Ck4dqxR3.js → chip-BSMT2uT1.js} +1 -1
  133. package/dist/web/assets/{chunk-2J33WTMH-BZcpjmfm.js → chunk-2J33WTMH-BrtUyBDF.js} +1 -1
  134. package/dist/web/assets/{chunk-4BX2VUAB-DZGB3q-I.js → chunk-4BX2VUAB-DZDNetDC.js} +1 -1
  135. package/dist/web/assets/{chunk-55IACEB6-CcNrOmyy.js → chunk-55IACEB6-xVa3bJqv.js} +1 -1
  136. package/dist/web/assets/{chunk-727SXJPM-dWrxio8O.js → chunk-727SXJPM-B7n9vxY4.js} +1 -1
  137. package/dist/web/assets/{chunk-AQP2D5EJ-K4gkVzqE.js → chunk-AQP2D5EJ-DQd4G76y.js} +1 -1
  138. package/dist/web/assets/{chunk-FMBD7UC4-CnHyY4qD.js → chunk-FMBD7UC4-CXyP08kl.js} +1 -1
  139. package/dist/web/assets/{chunk-ND2GUHAM-CHUmcyDv.js → chunk-ND2GUHAM-BtnIS-HK.js} +1 -1
  140. package/dist/web/assets/{chunk-QZHKN3VN-Bzzjd-Qt.js → chunk-QZHKN3VN-D4D-V_ex.js} +1 -1
  141. package/dist/web/assets/classDiagram-4FO5ZUOK-pvRulc6s.js +1 -0
  142. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-pvRulc6s.js +1 -0
  143. package/dist/web/assets/{code-block-OCS4YCEC-B2hHBO_G.js → code-block-OCS4YCEC-DfERo9L1.js} +1 -1
  144. package/dist/web/assets/confirm-dialog-8ItnHBi0.js +1 -0
  145. package/dist/web/assets/{content-D0qmHfHK.js → content-DrW8KnXu.js} +2 -2
  146. package/dist/web/assets/{copy-C4WnOY6Y.js → copy-prrQrInw.js} +1 -1
  147. package/dist/web/assets/{cose-bilkent-S5V4N54A-Dcn8tOhb.js → cose-bilkent-S5V4N54A-CXdH8Idq.js} +1 -1
  148. package/dist/web/assets/cssMode-CJ6_f10q.js +1 -0
  149. package/dist/web/assets/{dagre-BM42HDAG-DWBV4UfP.js → dagre-BM42HDAG-XZydDwXe.js} +1 -1
  150. package/dist/web/assets/{diagram-2AECGRRQ-CnNTJQ3q.js → diagram-2AECGRRQ-CofEe0Fr.js} +1 -1
  151. package/dist/web/assets/{diagram-5GNKFQAL-DlXJFzDF.js → diagram-5GNKFQAL-BbTtW5WH.js} +1 -1
  152. package/dist/web/assets/{diagram-KO2AKTUF-DZ22YV4n.js → diagram-KO2AKTUF-BJHZXpUM.js} +1 -1
  153. package/dist/web/assets/{diagram-LMA3HP47-B4pQJjG9.js → diagram-LMA3HP47-DN7A27YU.js} +1 -1
  154. package/dist/web/assets/{diagram-OG6HWLK6-DJBLjtgL.js → diagram-OG6HWLK6-4IdYadiw.js} +1 -1
  155. package/dist/web/assets/{dialog-1o8KQWoQ.js → dialog-BUsI4h2U.js} +2 -2
  156. package/dist/web/assets/{erDiagram-TEJ5UH35-CDsfoJUT.js → erDiagram-TEJ5UH35-BFijhCWv.js} +1 -1
  157. package/dist/web/assets/{file-6qwbIga1.js → file-C6QMUr5_.js} +1 -1
  158. package/dist/web/assets/{flowDiagram-I6XJVG4X-CuGOAYmY.js → flowDiagram-I6XJVG4X-D9MxC-El.js} +1 -1
  159. package/dist/web/assets/{folder-picker-Cj1VZOZU.js → folder-picker-CBdw-4v3.js} +1 -1
  160. package/dist/web/assets/{freemarker2-BG8VyI2V.js → freemarker2-CJVobC22.js} +2 -2
  161. package/dist/web/assets/{ganttDiagram-6RSMTGT7-BjrSOqAM.js → ganttDiagram-6RSMTGT7-BijCAyPQ.js} +1 -1
  162. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-4VVr4F8_.js → gitGraphDiagram-PVQCEYII-AvI5QqxQ.js} +1 -1
  163. package/dist/web/assets/{globe-CxeM27yy.js → globe-BOfcTsvL.js} +1 -1
  164. package/dist/web/assets/handlebars-DTqWmI25.js +1 -0
  165. package/dist/web/assets/html-Bx7L8DX_.js +1 -0
  166. package/dist/web/assets/htmlMode-D_S0NjZc.js +1 -0
  167. package/dist/web/assets/{index-hHrknzh7.js → index-BClyhWqz.js} +1 -1
  168. package/dist/web/assets/index-BDON669P.css +1 -0
  169. package/dist/web/assets/{index-DVdiOmqt.js → index-BFcDmhr0.js} +1 -1
  170. package/dist/web/assets/index-BRu1Yhrx.js +122 -0
  171. package/dist/web/assets/{index-Db28hRe8.js → index-Bg8TlDo_.js} +1 -1
  172. package/dist/web/assets/{index-Da2mWAtm.js → index-COigMgrJ.js} +1 -1
  173. package/dist/web/assets/{index-cu8sedJH.js → index-Cdmj8aPv.js} +1 -1
  174. package/dist/web/assets/index-CtOu4p9B.js +1 -0
  175. package/dist/web/assets/{index-CrKx8Yaz.js → index-DUmVscrR.js} +4 -4
  176. package/dist/web/assets/{index-C1lMEjmZ.js → index-JXcQeorP.js} +1 -1
  177. package/dist/web/assets/{infoDiagram-5YYISTIA-Cy9ntb6t.js → infoDiagram-5YYISTIA-76JLqjJ0.js} +1 -1
  178. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-D6UiVzRz.js → ishikawaDiagram-YF4QCWOH-CXHFTVly.js} +1 -1
  179. package/dist/web/assets/javascript-80FDzX3y.js +1 -0
  180. package/dist/web/assets/{journeyDiagram-JHISSGLW-owdKzZoh.js → journeyDiagram-JHISSGLW-6SDJs9C5.js} +1 -1
  181. package/dist/web/assets/jsonMode-BGg9MhFR.js +7 -0
  182. package/dist/web/assets/{kanban-definition-UN3LZRKU-v5TBnyp0.js → kanban-definition-UN3LZRKU-C0Y3zy1P.js} +1 -1
  183. package/dist/web/assets/{kbd-Cl4AzzJp.js → kbd-D4KIW7lm.js} +1 -1
  184. package/dist/web/assets/{key-round-CnYjSrCz.js → key-round-C4vhwqUu.js} +1 -1
  185. package/dist/web/assets/{layers-XImbMX-P.js → layers-BBHgMnhN.js} +1 -1
  186. package/dist/web/assets/{linear-DNlKYt8R.js → linear-CjeIgBwO.js} +1 -1
  187. package/dist/web/assets/{link-DQk-Vapq.js → link-C-Jq7dnc.js} +1 -1
  188. package/dist/web/assets/liquid-guocpCJB.js +1 -0
  189. package/dist/web/assets/{lspLanguageFeatures-DD_y1bZa.js → lspLanguageFeatures-CPXERxVW.js} +1 -1
  190. package/dist/web/assets/mdx-DA4ymd3l.js +1 -0
  191. package/dist/web/assets/{menu-DXF6iUsq.js → menu-BgtmXAzT.js} +1 -1
  192. package/dist/web/assets/{mermaid-NOHMQCX5-CG_LpwAI.js → mermaid-NOHMQCX5-XQbfQDGS.js} +49 -49
  193. package/dist/web/assets/{message-square-BviHdb67.js → message-square-DIFv9yrO.js} +1 -1
  194. package/dist/web/assets/{mindmap-definition-RKZ34NQL-Bn_rnnGT.js → mindmap-definition-RKZ34NQL-6ucD61UV.js} +1 -1
  195. package/dist/web/assets/modal-Dx4N46s5.js +1 -0
  196. package/dist/web/assets/{monaco-yuGji-2E.js → monaco-Mn1xgio_.js} +3 -3
  197. package/dist/web/assets/nav-row-DwEkbw3N.js +1 -0
  198. package/dist/web/assets/{paperclip-D9ka2IAB.js → paperclip-CdQvl5ee.js} +1 -1
  199. package/dist/web/assets/{pencil-isP1rXC-.js → pencil-ChX7G9j9.js} +1 -1
  200. package/dist/web/assets/{people-CbmH8m4i.js → people-D_VwidfV.js} +1 -1
  201. package/dist/web/assets/{pieDiagram-4H26LBE5-DIE_f9W_.js → pieDiagram-4H26LBE5-KVvAyrIn.js} +1 -1
  202. package/dist/web/assets/{popover-D8VtO5qF.js → popover-CGEFZKuu.js} +1 -1
  203. package/dist/web/assets/python-BzNNRn8W.js +1 -0
  204. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-CavFxJCk.js → quadrantDiagram-W4KKPZXB-ClVzvpE7.js} +1 -1
  205. package/dist/web/assets/razor-DF2ZxqCE.js +1 -0
  206. package/dist/web/assets/{requirementDiagram-4Y6WPE33-Dm1FwUUh.js → requirementDiagram-4Y6WPE33-uXN6Uoln.js} +1 -1
  207. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-qeqcO27M.js → sankeyDiagram-5OEKKPKP-Bi3Tc0wR.js} +1 -1
  208. package/dist/web/assets/{search-DOMr5jPS.js → search--FRFbCre.js} +1 -1
  209. package/dist/web/assets/{select-Cw5BaOLQ.js → select-D0sHgfcU.js} +1 -1
  210. package/dist/web/assets/{select-CzZNbe1Z.js → select-c5IIpQj0.js} +1 -1
  211. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-Dqtb3BYG.js → sequenceDiagram-3UESZ5HK-CLdrO2PY.js} +1 -1
  212. package/dist/web/assets/{server-u-PC-CcX.js → server-Dr_6KJFM.js} +1 -1
  213. package/dist/web/assets/{stateDiagram-AJRCARHV-C_MqF9Fy.js → stateDiagram-AJRCARHV-BGInldGc.js} +1 -1
  214. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-D44x8SXb.js +1 -0
  215. package/dist/web/assets/{textarea-xt2hLb-d.js → textarea-W-xQPto3.js} +1 -1
  216. package/dist/web/assets/{timeline-definition-PNZ67QCA-Bzov6t31.js → timeline-definition-PNZ67QCA-BUp6Qykc.js} +1 -1
  217. package/dist/web/assets/{toggles-B6E1UBYN.js → toggles-Bgp7vdwg.js} +1 -1
  218. package/dist/web/assets/tooltip-DZcaSN1F.js +1 -0
  219. package/dist/web/assets/{track-BeqaPopG.js → track-Cj1Ogapu.js} +1 -1
  220. package/dist/web/assets/{trash-2-5jI3_KHh.js → trash-2-DmFXzIhh.js} +1 -1
  221. package/dist/web/assets/tsMode-CSHMQttX.js +11 -0
  222. package/dist/web/assets/typescript-CyQKmnN-.js +1 -0
  223. package/dist/web/assets/{upload-DUteazR7.js → upload-C-4QDrwN.js} +1 -1
  224. package/dist/web/assets/use-profiles-D5mIgeQU.js +1 -0
  225. package/dist/web/assets/{use-providers-CQwBj9hp.js → use-providers-BViTes-6.js} +1 -1
  226. package/dist/web/assets/{use-translation-settings-DBzFqP-d.js → use-translation-settings-Dgw0oKP1.js} +1 -1
  227. package/dist/web/assets/{useNormalizedLogs-DsdwmfS7.js → useNormalizedLogs-BX7pMg0-.js} +1 -1
  228. package/dist/web/assets/useTokenUsage-T03faPfn.js +1 -0
  229. package/dist/web/assets/{vennDiagram-CIIHVFJN-BqxICvT_.js → vennDiagram-CIIHVFJN-iQYnP9Ux.js} +1 -1
  230. package/dist/web/assets/{wardley-L42UT6IY-BTdLPFcA.js → wardley-L42UT6IY-J7vXwA5J.js} +1 -1
  231. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-icnFsFn4.js → wardleyDiagram-YWT4CUSO-CdtM5GJv.js} +1 -1
  232. package/dist/web/assets/xml-C79KdvcD.js +1 -0
  233. package/dist/web/assets/{xychartDiagram-2RQKCTM6-BN2_ducK.js → xychartDiagram-2RQKCTM6-B3gcBChQ.js} +1 -1
  234. package/dist/web/assets/yaml-C1WLpuVe.js +1 -0
  235. package/dist/web/index.html +2 -2
  236. package/node_modules/@agent-tower/shared/dist/index.d.ts +2 -0
  237. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  238. package/node_modules/@agent-tower/shared/dist/index.js +2 -0
  239. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  240. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts +12 -0
  241. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts.map +1 -0
  242. package/node_modules/@agent-tower/shared/dist/task-handoff.js +2 -0
  243. package/node_modules/@agent-tower/shared/dist/task-handoff.js.map +1 -0
  244. package/node_modules/@agent-tower/shared/dist/task-hierarchy.d.ts +17 -0
  245. package/node_modules/@agent-tower/shared/dist/task-hierarchy.d.ts.map +1 -0
  246. package/node_modules/@agent-tower/shared/dist/task-hierarchy.js +12 -0
  247. package/node_modules/@agent-tower/shared/dist/task-hierarchy.js.map +1 -0
  248. package/node_modules/@agent-tower/shared/dist/types.d.ts +22 -1
  249. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  250. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  251. package/package.json +1 -1
  252. package/prisma/migrations/20261023010000_add_subtasks/migration.sql +2 -0
  253. package/prisma/migrations/20261026000000_add_task_handoff/migration.sql +1 -0
  254. package/prisma/migrations/20261027000000_preserve_workspace_history/migration.sql +2 -0
  255. package/prisma/schema.prisma +7 -0
  256. package/dist/web/assets/AgentDemoPage-Dwux6TyV.js +0 -1
  257. package/dist/web/assets/AttachmentPreview-CFJ6MZ1f.js +0 -2
  258. package/dist/web/assets/BrandLogo-H4nCbVzs.js +0 -1
  259. package/dist/web/assets/ConversationPage-CZzGME21.js +0 -1
  260. package/dist/web/assets/CreateTaskInput-BM2tx4hJ.js +0 -1
  261. package/dist/web/assets/EditableTaskTitle-BEsiULnD.js +0 -70
  262. package/dist/web/assets/MobileTaskDetail-IOQ1WnS8.js +0 -3
  263. package/dist/web/assets/ProjectKanbanPage-DMtv15rq.js +0 -12
  264. package/dist/web/assets/TaskDetail-DzKbs5Jt.js +0 -1
  265. package/dist/web/assets/TeamSettingsPage-fjlaqaLo.js +0 -1
  266. package/dist/web/assets/channel-D1cEj22-.js +0 -1
  267. package/dist/web/assets/check-D_VFGpIy.js +0 -1
  268. package/dist/web/assets/classDiagram-4FO5ZUOK-DIrDMefa.js +0 -1
  269. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-DIrDMefa.js +0 -1
  270. package/dist/web/assets/confirm-dialog-CL_1pF4K.js +0 -1
  271. package/dist/web/assets/cssMode-CXBMyfYY.js +0 -1
  272. package/dist/web/assets/handlebars-Ci38FPsA.js +0 -1
  273. package/dist/web/assets/html-5hdYEgVF.js +0 -1
  274. package/dist/web/assets/htmlMode-BzU9qsw0.js +0 -1
  275. package/dist/web/assets/icon-button-BmrJtYFC.js +0 -1
  276. package/dist/web/assets/index-BR3Wg2fS.js +0 -122
  277. package/dist/web/assets/index-DSy5JLC1.js +0 -1
  278. package/dist/web/assets/index-DZLg4-xM.css +0 -1
  279. package/dist/web/assets/javascript-Ib060GQ8.js +0 -1
  280. package/dist/web/assets/jsonMode-CLmu6gzc.js +0 -7
  281. package/dist/web/assets/liquid-Bc7OSAPR.js +0 -1
  282. package/dist/web/assets/mdx-CxJNHHTn.js +0 -1
  283. package/dist/web/assets/modal-D496f1fE.js +0 -1
  284. package/dist/web/assets/python-C0E43gUo.js +0 -1
  285. package/dist/web/assets/razor-xarhYR9V.js +0 -1
  286. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-BxmbGAhh.js +0 -1
  287. package/dist/web/assets/tooltip-C8--8CNX.js +0 -1
  288. package/dist/web/assets/tsMode-uT3sMVHt.js +0 -11
  289. package/dist/web/assets/typescript-Dkdj2ks0.js +0 -1
  290. package/dist/web/assets/use-profiles-T3saKiBg.js +0 -1
  291. package/dist/web/assets/useTokenUsage-Drh3v3kx.js +0 -1
  292. package/dist/web/assets/xml-BO8gYu0L.js +0 -1
  293. package/dist/web/assets/yaml-DlovbQzw.js +0 -1
  294. /package/dist/web/assets/{EditableTaskTitle-DcpNTP1B.css → TaskHandoffPanel-DcpNTP1B.css} +0 -0
@@ -3,14 +3,21 @@ 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';
10
10
  import { defaultTeamLockService } from './team-lock.service.js';
11
- import { TaskPriority } from '@agent-tower/shared';
11
+ import { TaskPriority, MAX_TASK_DEPTH } 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 parent."parentTaskId" FROM "Task" parent WHERE parent."id" = task."parentTaskId") AS "grandparentTaskId",
18
+ (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL) AS "subtaskCount",
19
+ (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL AND child."status" = 'IN_PROGRESS') AS "runningSubtaskCount"
20
+ `;
14
21
  /**
15
22
  * 合法的状态流转规则
16
23
  * 看板拖拽场景下允许任意状态互转,状态变更无危险副作用
@@ -43,6 +50,19 @@ const visibleSessionSummary = {
43
50
  updatedAt: true,
44
51
  },
45
52
  };
53
+ // The schema stores adjacency; these bounded reads cover the supported three levels.
54
+ function subtreeWhere(id, projectId) {
55
+ return { deletedAt: null, ...(projectId ? { projectId } : {}), OR: [
56
+ { id }, { parentTaskId: id }, { parentTask: { parentTaskId: id, deletedAt: null } },
57
+ ] };
58
+ }
59
+ function ancestorIds(parentTaskId, grandparentTaskId) {
60
+ return [grandparentTaskId, parentTaskId].filter((id) => Boolean(id));
61
+ }
62
+ function withHierarchy(task) {
63
+ const { parentTask, ...metadata } = task;
64
+ return { ...metadata, ancestorIds: ancestorIds(task.parentTaskId, parentTask?.parentTaskId) };
65
+ }
46
66
  function sliceAtCompleteCodePointBoundary(value, maxLength) {
47
67
  let end = 0;
48
68
  for (const character of value) {
@@ -211,13 +231,17 @@ function normalizeUpdateTaskInput(current, input) {
211
231
  ]),
212
232
  };
213
233
  }
234
+ function withoutTaskHandoff(task) {
235
+ const { handoff: _handoff, ...metadata } = task;
236
+ return metadata;
237
+ }
214
238
  function withTaskPreviews(task, options = {}) {
215
239
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
216
240
  const contentPreview = buildTextPreview(task.description);
217
241
  const isTruncated = task.title !== titlePreview
218
242
  || Boolean(task.description && task.description !== contentPreview);
219
243
  const base = {
220
- ...task,
244
+ ...withoutTaskHandoff(task),
221
245
  title: titlePreview,
222
246
  titlePreview,
223
247
  contentPreview: contentPreview || undefined,
@@ -235,13 +259,15 @@ function withProjectGitMetadata(task) {
235
259
  return withProjectGitCapability(task, capability);
236
260
  }
237
261
  function withProjectGitCapability(task, capability) {
238
- return {
262
+ const result = {
239
263
  ...task,
240
264
  project: {
241
265
  ...task.project,
242
266
  ...capability,
243
267
  },
244
268
  };
269
+ delete result.handoff;
270
+ return result;
245
271
  }
246
272
  function buildProjectGitMetadata(project) {
247
273
  return getStoredProjectGitCapability(project);
@@ -275,30 +301,28 @@ export class TaskService {
275
301
  deletedAt: null,
276
302
  project: { archivedAt: null },
277
303
  ...(params.projectId ? { projectId: params.projectId } : {}),
304
+ ...(params.parentTaskId ? { parentTaskId: params.parentTaskId } : {}),
278
305
  ...(params.status ? { status: params.status } : {}),
279
306
  };
280
307
  const projectFilter = params.projectId
281
308
  ? Prisma.sql `AND task."projectId" = ${params.projectId}`
282
309
  : Prisma.empty;
310
+ const parentFilter = params.parentTaskId
311
+ ? Prisma.sql `AND task."parentTaskId" = ${params.parentTaskId}`
312
+ : Prisma.empty;
283
313
  const statusFilter = params.status
284
314
  ? Prisma.sql `AND task."status" = ${params.status}`
285
315
  : Prisma.empty;
286
316
  const [tasks, total] = await Promise.all([
287
317
  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"
318
+ SELECT ${taskBoardColumns}
296
319
  FROM "Task" task
297
320
  INNER JOIN "Project" project ON project."id" = task."projectId"
298
321
  WHERE task."deletedAt" IS NULL
299
322
  AND project."archivedAt" IS NULL
300
323
  ${projectFilter}
301
324
  ${statusFilter}
325
+ ${parentFilter}
302
326
  ORDER BY
303
327
  CASE task."status"
304
328
  WHEN ${TaskStatus.IN_PROGRESS} THEN 0
@@ -315,7 +339,19 @@ export class TaskService {
315
339
  `),
316
340
  prisma.task.count({ where }),
317
341
  ]);
318
- const taskIds = tasks.map((task) => task.id);
342
+ // Descendants can rank ahead of their ancestors or fall on another page.
343
+ // Return the complete compact path without changing the page count/limit.
344
+ const pageIds = new Set(tasks.map((task) => task.id));
345
+ const missingParentIds = [...new Set(tasks.flatMap((task) => ancestorIds(task.parentTaskId, task.grandparentTaskId).filter(id => !pageIds.has(id))))];
346
+ const parents = params.parentTaskId || missingParentIds.length === 0 ? []
347
+ : await prisma.$queryRaw(Prisma.sql `
348
+ SELECT ${taskBoardColumns} FROM "Task" task
349
+ INNER JOIN "Project" project ON project."id" = task."projectId"
350
+ WHERE task."id" IN (SELECT value FROM json_each(${JSON.stringify(missingParentIds)}))
351
+ AND task."deletedAt" IS NULL AND project."archivedAt" IS NULL
352
+ `);
353
+ const allTasks = [...tasks, ...parents];
354
+ const taskIds = allTasks.map((task) => task.id);
319
355
  const workspaceRows = taskIds.length === 0
320
356
  ? []
321
357
  : await prisma.$queryRaw(Prisma.sql `
@@ -363,7 +399,7 @@ export class TaskService {
363
399
  workspacesByTaskId.set(workspace.taskId, group);
364
400
  }
365
401
  const preferredWorkspaceByTaskId = new Map();
366
- for (const task of tasks) {
402
+ for (const task of allTasks) {
367
403
  const candidates = workspacesByTaskId.get(task.id) ?? [];
368
404
  const preferred = candidates.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
369
405
  ?? candidates[0];
@@ -371,7 +407,7 @@ export class TaskService {
371
407
  preferredWorkspaceByTaskId.set(task.id, preferred);
372
408
  }
373
409
  }
374
- const data = tasks.map((task) => {
410
+ const data = allTasks.map((task) => {
375
411
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
376
412
  const preferredWorkspace = preferredWorkspaceByTaskId.get(task.id);
377
413
  const hasRunningSession = (workspacesByTaskId.get(task.id) ?? [])
@@ -382,6 +418,10 @@ export class TaskService {
382
418
  return {
383
419
  id: task.id,
384
420
  projectId: task.projectId,
421
+ parentTaskId: task.parentTaskId,
422
+ ancestorIds: ancestorIds(task.parentTaskId, task.grandparentTaskId),
423
+ subtaskCount: Number(task.subtaskCount),
424
+ runningSubtaskCount: Number(task.runningSubtaskCount),
385
425
  title: titlePreview,
386
426
  status: task.status,
387
427
  priority: task.priority ?? TaskPriority.NORMAL,
@@ -401,7 +441,8 @@ export class TaskService {
401
441
  };
402
442
  });
403
443
  return {
404
- data,
444
+ data: data.slice(0, tasks.length),
445
+ ...(parents.length ? { parents: data.slice(tasks.length) } : {}),
405
446
  total,
406
447
  page,
407
448
  limit,
@@ -443,6 +484,8 @@ export class TaskService {
443
484
  projectId: true,
444
485
  title: true,
445
486
  baseBranch: true,
487
+ parentTaskId: true,
488
+ parentTask: { select: { parentTaskId: true } },
446
489
  status: true,
447
490
  priority: true,
448
491
  position: true,
@@ -471,7 +514,7 @@ export class TaskService {
471
514
  });
472
515
  const projectGitCapability = getStoredProjectGitCapability(project);
473
516
  return {
474
- data: data.map((task) => (withTaskPreviews(withProjectGitCapability(task, projectGitCapability), { omitDescription: true }))),
517
+ data: data.map((task) => (withTaskPreviews(withProjectGitCapability(withHierarchy(task), projectGitCapability), { omitDescription: true }))),
475
518
  total,
476
519
  page,
477
520
  limit,
@@ -490,6 +533,9 @@ export class TaskService {
490
533
  project: true,
491
534
  title: true,
492
535
  baseBranch: true,
536
+ parentTaskId: true,
537
+ parentTask: { select: { parentTaskId: true } },
538
+ _count: { select: { subtasks: { where: { deletedAt: null } } } },
493
539
  status: true,
494
540
  priority: true,
495
541
  position: true,
@@ -501,7 +547,14 @@ export class TaskService {
501
547
  if (!task) {
502
548
  throw new NotFoundError('Task', id);
503
549
  }
504
- return withTaskPreviews(withProjectGitMetadata(task), { omitDescription: true });
550
+ const { _count, ...metadata } = task;
551
+ return withTaskPreviews(withProjectGitMetadata({ ...withHierarchy(metadata), subtaskCount: _count.subtasks }), { omitDescription: true });
552
+ }
553
+ async findSubtasks(id, params = {}) {
554
+ const parent = await prisma.task.findFirst({ where: { id, deletedAt: null } });
555
+ if (!parent)
556
+ throw new NotFoundError('Task', id);
557
+ return this.findBoard({ ...params, projectId: parent.projectId, parentTaskId: id });
505
558
  }
506
559
  async findBodyById(id) {
507
560
  const task = await prisma.task.findFirst({
@@ -554,37 +607,82 @@ export class TaskService {
554
607
  throw new NotFoundError('Project', projectId);
555
608
  }
556
609
  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);
564
- }
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
- },
610
+ const parent = input.parentTaskId ? await prisma.task.findFirst({
611
+ where: { id: input.parentTaskId, projectId, deletedAt: null },
612
+ include: { parentTask: { select: { parentTaskId: true } }, workspaces: {
613
+ where: { parentWorkspaceId: null, ownerMemberId: null, status: { not: WorkspaceStatus.ABANDONED }, cleanedAt: null },
614
+ orderBy: { createdAt: 'desc' },
615
+ } },
616
+ }) : null;
617
+ if (input.parentTaskId && !parent)
618
+ throw new NotFoundError('Parent task', input.parentTaskId);
619
+ if (parent?.parentTask?.parentTaskId)
620
+ throw new ValidationError('Tasks support at most three levels');
621
+ const parentWorkspace = parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
622
+ ?? parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.HIBERNATED)
623
+ ?? parent?.workspaces[0];
624
+ const parentBranch = parentWorkspace?.branchName || parent?.baseBranch;
625
+ // A grandchild may inherit an ancestor branch before its parent starts.
626
+ // Serialize validation/persistence with that branch owner's cleanup as well.
627
+ const requestedParentBranch = input.baseBranch ?? parentBranch;
628
+ const branchOwners = input.baseBranch === null || !requestedParentBranch ? [] : await prisma.workspace.findMany({
629
+ where: { task: { projectId, deletedAt: null }, branchName: requestedParentBranch },
630
+ select: { id: true },
580
631
  });
581
- return {
582
- ...created,
583
- project: {
584
- ...project,
585
- ...buildProjectGitMetadata(project),
586
- },
632
+ const createWithParentBranch = async () => {
633
+ // Resolve at task creation so delayed starts and TeamRun main workspaces
634
+ // cannot silently follow a later project default change. Local tasks opt out.
635
+ let baseBranch = null;
636
+ if (input.baseBranch !== null) {
637
+ const requestedBranch = input.baseBranch ?? parentBranch ?? ((await detectProjectGitCapability(project.repoPath)).worktreeReady ? project.mainBranch : null);
638
+ if (requestedBranch !== null) {
639
+ baseBranch = await requireLocalBaseBranch(project.repoPath, requestedBranch);
640
+ }
641
+ }
642
+ // 自动计算 position
643
+ const maxPosition = await prisma.task.aggregate({
644
+ where: { projectId, status: TaskStatus.TODO, deletedAt: null },
645
+ _max: { position: true },
646
+ });
647
+ const created = await prisma.$transaction(async (tx) => {
648
+ // Recheck after Git validation, serialized with the deletion marker.
649
+ if (parent) {
650
+ const liveParent = await tx.task.findFirst({
651
+ where: { id: parent.id, projectId, deletedAt: null, project: { archivedAt: null } },
652
+ include: { parentTask: { select: { parentTaskId: true } } },
653
+ });
654
+ if (!liveParent)
655
+ throw new NotFoundError('Parent task', parent.id);
656
+ if (liveParent.parentTask?.parentTaskId)
657
+ throw new ValidationError('Tasks support at most three levels');
658
+ }
659
+ return tx.task.create({
660
+ data: {
661
+ title: normalizedInput.title,
662
+ description: normalizedInput.description,
663
+ priority: normalizedInput.priority ?? TaskPriority.NORMAL,
664
+ baseBranch,
665
+ parentTaskId: parent?.id,
666
+ position: (maxPosition._max.position ?? 0) + 1,
667
+ projectId,
668
+ },
669
+ });
670
+ });
671
+ this.eventBus.emit('task:updated', { taskId: created.id, projectId, status: created.status });
672
+ if (parent)
673
+ this.eventBus.emit('task:updated', { taskId: parent.id, projectId, status: parent.status });
674
+ return {
675
+ ...created,
676
+ ancestorIds: parent ? ancestorIds(parent.id, parent.parentTaskId) : [],
677
+ project: {
678
+ ...project,
679
+ ...buildProjectGitMetadata(project),
680
+ },
681
+ };
587
682
  };
683
+ // Serialize branch validation and child persistence with parent workspace
684
+ // cleanup, so its reference becomes visible before that branch is reclaimed.
685
+ return defaultWorkspaceLifecycleBarrier.withWorkspaces([...branchOwners.map(workspace => workspace.id), ...(parentWorkspace ? [parentWorkspace.id] : [])], createWithParentBranch);
588
686
  }
589
687
  /**
590
688
  * 更新任务基本信息
@@ -607,7 +705,7 @@ export class TaskService {
607
705
  this.emitTaskUpdated(id, task.projectId, updated.status, updated.priority);
608
706
  }
609
707
  return {
610
- ...updated,
708
+ ...withoutTaskHandoff(updated),
611
709
  project: {
612
710
  ...task.project,
613
711
  ...buildProjectGitMetadata(task.project),
@@ -652,7 +750,7 @@ export class TaskService {
652
750
  // 通知前端
653
751
  this.emitTaskUpdated(id, task.projectId, status);
654
752
  return {
655
- ...updated,
753
+ ...withoutTaskHandoff(updated),
656
754
  project: {
657
755
  ...task.project,
658
756
  ...buildProjectGitMetadata(task.project),
@@ -689,7 +787,7 @@ export class TaskService {
689
787
  this.emitTaskUpdated(id, task.projectId, status);
690
788
  }
691
789
  return {
692
- ...updated,
790
+ ...withoutTaskHandoff(updated),
693
791
  project: {
694
792
  ...task.project,
695
793
  ...buildProjectGitMetadata(task.project),
@@ -699,149 +797,139 @@ export class TaskService {
699
797
  /**
700
798
  * 快速删除任务
701
799
  *
702
- * 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
703
- * 2. 保存后台清理快照
800
+ * 1. 在同一事务中标记父子 Task 为 deletedAt,并保存清理快照
801
+ * 2. 普通读取立即隐藏已删除的任务
704
802
  * 3. 通过 EventBus 通知前端实时删除
705
803
  * 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
706
804
  */
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) {
805
+ async delete(id, expectedSubtaskCount) {
806
+ const taskForGuard = await prisma.task.findUnique({ where: { id }, include: { project: true } });
807
+ if (!taskForGuard || taskForGuard.deletedAt)
714
808
  throw new NotFoundError('Task', id);
715
- }
716
809
  ensureProjectIsMutable(taskForGuard.project, 'delete tasks');
810
+ const familyWhere = subtreeWhere(id, taskForGuard.projectId);
811
+ if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== (await prisma.task.count({
812
+ where: familyWhere,
813
+ })) - 1) {
814
+ throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
815
+ }
717
816
  const workspaceIds = await prisma.workspace.findMany({
718
- where: { taskId: id },
817
+ where: { task: familyWhere },
719
818
  select: { id: true },
720
819
  });
721
- let marked;
722
- await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
723
- for (const workspace of workspaceIds) {
820
+ const result = await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
821
+ for (const workspace of workspaceIds)
724
822
  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 },
823
+ // Mark the complete family and persist its recovery snapshot together.
824
+ // Child creation rechecks the parent inside its own transaction.
825
+ return prisma.$transaction(async (tx) => {
826
+ const parent = await tx.task.findFirst({ where: { id, deletedAt: null }, include: { project: true } });
827
+ if (!parent)
828
+ throw new NotFoundError('Task', id);
829
+ ensureProjectIsMutable(parent.project, 'delete tasks');
830
+ const family = await tx.task.findMany({
831
+ where: familyWhere,
832
+ select: { id: true, parentTaskId: true },
833
+ });
834
+ if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== family.length - 1) {
835
+ throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
836
+ }
837
+ const byId = new Map(family.map(task => [task.id, task]));
838
+ const depth = (task) => {
839
+ let current = task;
840
+ let value = 0;
841
+ while (current.parentTaskId && byId.has(current.parentTaskId) && value < MAX_TASK_DEPTH) {
842
+ current = byId.get(current.parentTaskId);
843
+ value++;
844
+ }
845
+ return value;
846
+ };
847
+ const taskIds = family.sort((a, b) => depth(b) - depth(a) || a.id.localeCompare(b.id)).map(task => task.id);
848
+ await tx.task.updateMany({ where: { id: { in: taskIds } }, data: { deletedAt: new Date() } });
849
+ const tasks = await tx.task.findMany({
850
+ where: { id: { in: taskIds } },
851
+ include: {
852
+ workspaces: { include: { sessions: true } },
853
+ teamRun: { include: { invocations: {
854
+ where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } }, select: { id: true },
855
+ } } },
763
856
  },
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) => {
857
+ });
858
+ const tasksById = new Map(tasks.map((task) => [task.id, task]));
859
+ const orderedTasks = taskIds.map((taskId) => tasksById.get(taskId));
860
+ const snapshot = {
861
+ taskId: id, taskIds, projectId: parent.projectId,
862
+ project: { repoPath: parent.project.repoPath, mainBranch: parent.project.mainBranch },
863
+ workspaces: orderedTasks.flatMap((task) => task.workspaces.map((workspace) => ({
864
+ id: workspace.id,
865
+ worktreePath: workspace.worktreePath,
866
+ resourcesCleaned: workspace.cleanedAt != null,
867
+ workingDir: getWorkspaceWorkingDir(workspace),
868
+ workspaceKind: workspace.workspaceKind,
869
+ branchName: workspace.branchName,
870
+ baseBranch: workspace.baseBranch,
871
+ sessions: workspace.sessions.map((session) => ({ id: session.id })),
872
+ }))),
873
+ };
791
874
  await tx.taskCleanupJob.create({
792
- data: {
793
- taskId: taskForCleanup.id,
794
- projectId: taskForCleanup.projectId,
795
- payload: JSON.stringify(snapshot),
796
- },
875
+ data: { taskId: id, projectId: parent.projectId, payload: JSON.stringify(snapshot) },
797
876
  });
798
- if (teamRunId) {
877
+ const teamRunIds = tasks.flatMap((task) => task.teamRun ? [task.teamRun.id] : []);
878
+ if (teamRunIds.length) {
799
879
  await tx.workRequest.updateMany({
800
- where: {
801
- teamRunId,
802
- status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES },
803
- },
880
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES } },
804
881
  data: { status: 'CANCELLED' },
805
882
  });
806
883
  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
- },
884
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
885
+ data: { status: 'CANCELLED', nextRoomReplyReminderAt: null },
815
886
  });
816
887
  }
888
+ return {
889
+ taskIds, projectId: parent.projectId,
890
+ invocationIds: tasks.flatMap((task) => task.teamRun?.invocations.map((invocation) => invocation.id) ?? []),
891
+ };
817
892
  });
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
893
  });
894
+ for (const invocationId of result.invocationIds)
895
+ defaultTeamLockService.releaseByOwner(invocationId);
896
+ for (const taskId of result.taskIds)
897
+ this.eventBus.emit('task:deleted', { taskId, projectId: result.projectId });
842
898
  this.cleanupService?.trigger();
843
899
  return true;
844
900
  }
901
+ async getDeletionScope(id) {
902
+ return prisma.$transaction(async (tx) => {
903
+ const task = await tx.task.findFirst({ where: { id, deletedAt: null }, select: { projectId: true } });
904
+ if (!task)
905
+ throw new NotFoundError('Task', id);
906
+ return { descendantCount: (await tx.task.count({ where: subtreeWhere(id, task.projectId) })) - 1 };
907
+ });
908
+ }
909
+ async getCleanupStatus(taskId) {
910
+ const job = await prisma.taskCleanupJob.findFirst({
911
+ where: { taskId }, orderBy: { createdAt: 'desc' },
912
+ select: { taskId: true, status: true, lastError: true, nextRetryAt: true },
913
+ });
914
+ if (!job)
915
+ throw new NotFoundError('Task cleanup', taskId);
916
+ return job;
917
+ }
918
+ async retryCleanup(taskId) {
919
+ const job = await prisma.taskCleanupJob.findFirst({ where: { taskId }, orderBy: { createdAt: 'desc' } });
920
+ if (!job)
921
+ throw new NotFoundError('Task cleanup', taskId);
922
+ const project = await prisma.project.findUnique({ where: { id: job.projectId } });
923
+ if (!project)
924
+ throw new NotFoundError('Project', job.projectId);
925
+ ensureProjectIsMutable(project, 'retry task cleanup');
926
+ await prisma.taskCleanupJob.updateMany({
927
+ where: { id: job.id, status: 'FAILED' },
928
+ data: { status: 'PENDING', attempts: 0, lastError: null, nextRetryAt: null },
929
+ });
930
+ this.cleanupService?.trigger();
931
+ return this.getCleanupStatus(taskId);
932
+ }
845
933
  /**
846
934
  * 获取项目的任务统计
847
935
  */