agent-tower 0.7.2 → 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 (339) hide show
  1. package/dist/executors/__tests__/providers.test.js +53 -0
  2. package/dist/executors/__tests__/providers.test.js.map +1 -1
  3. package/dist/executors/providers.d.ts +2 -0
  4. package/dist/executors/providers.d.ts.map +1 -1
  5. package/dist/executors/providers.js +3 -2
  6. package/dist/executors/providers.js.map +1 -1
  7. package/dist/git/base-branch.d.ts +3 -0
  8. package/dist/git/base-branch.d.ts.map +1 -0
  9. package/dist/git/base-branch.js +17 -0
  10. package/dist/git/base-branch.js.map +1 -0
  11. package/dist/git/worktree.manager.d.ts +2 -0
  12. package/dist/git/worktree.manager.d.ts.map +1 -1
  13. package/dist/git/worktree.manager.js +29 -8
  14. package/dist/git/worktree.manager.js.map +1 -1
  15. package/dist/git/worktree.manager.test.js +38 -0
  16. package/dist/git/worktree.manager.test.js.map +1 -1
  17. package/dist/mcp/http-client.d.ts +4 -6
  18. package/dist/mcp/http-client.d.ts.map +1 -1
  19. package/dist/mcp/http-client.js +6 -0
  20. package/dist/mcp/http-client.js.map +1 -1
  21. package/dist/mcp/tools/__tests__/providers.test.d.ts +2 -0
  22. package/dist/mcp/tools/__tests__/providers.test.d.ts.map +1 -0
  23. package/dist/mcp/tools/__tests__/providers.test.js +22 -0
  24. package/dist/mcp/tools/__tests__/providers.test.js.map +1 -0
  25. package/dist/mcp/tools/__tests__/tasks.test.d.ts +2 -0
  26. package/dist/mcp/tools/__tests__/tasks.test.d.ts.map +1 -0
  27. package/dist/mcp/tools/__tests__/tasks.test.js +49 -0
  28. package/dist/mcp/tools/__tests__/tasks.test.js.map +1 -0
  29. package/dist/mcp/tools/providers.d.ts.map +1 -1
  30. package/dist/mcp/tools/providers.js +3 -2
  31. package/dist/mcp/tools/providers.js.map +1 -1
  32. package/dist/mcp/tools/tasks.d.ts.map +1 -1
  33. package/dist/mcp/tools/tasks.js +31 -2
  34. package/dist/mcp/tools/tasks.js.map +1 -1
  35. package/dist/mcp/types.d.ts +27 -0
  36. package/dist/mcp/types.d.ts.map +1 -1
  37. package/dist/mcp/types.js +13 -0
  38. package/dist/mcp/types.js.map +1 -1
  39. package/dist/output/__tests__/text-block-lifecycle.test.js +37 -0
  40. package/dist/output/__tests__/text-block-lifecycle.test.js.map +1 -1
  41. package/dist/output/text-block-tracker.d.ts.map +1 -1
  42. package/dist/output/text-block-tracker.js +24 -1
  43. package/dist/output/text-block-tracker.js.map +1 -1
  44. package/dist/prompts/task-handoff.d.ts +6 -0
  45. package/dist/prompts/task-handoff.d.ts.map +1 -0
  46. package/dist/prompts/task-handoff.js +17 -0
  47. package/dist/prompts/task-handoff.js.map +1 -0
  48. package/dist/prompts/task-handoff.test.d.ts +2 -0
  49. package/dist/prompts/task-handoff.test.d.ts.map +1 -0
  50. package/dist/prompts/task-handoff.test.js +27 -0
  51. package/dist/prompts/task-handoff.test.js.map +1 -0
  52. package/dist/prompts/workspace-background-service-policy.d.ts +6 -0
  53. package/dist/prompts/workspace-background-service-policy.d.ts.map +1 -1
  54. package/dist/prompts/workspace-background-service-policy.js +2 -1
  55. package/dist/prompts/workspace-background-service-policy.js.map +1 -1
  56. package/dist/routes/__tests__/providers.test.js +67 -0
  57. package/dist/routes/__tests__/providers.test.js.map +1 -1
  58. package/dist/routes/__tests__/task-handoff.test.d.ts +2 -0
  59. package/dist/routes/__tests__/task-handoff.test.d.ts.map +1 -0
  60. package/dist/routes/__tests__/task-handoff.test.js +185 -0
  61. package/dist/routes/__tests__/task-handoff.test.js.map +1 -0
  62. package/dist/routes/__tests__/tasks.test.js +46 -0
  63. package/dist/routes/__tests__/tasks.test.js.map +1 -1
  64. package/dist/routes/providers.d.ts.map +1 -1
  65. package/dist/routes/providers.js +8 -0
  66. package/dist/routes/providers.js.map +1 -1
  67. package/dist/routes/tasks.d.ts.map +1 -1
  68. package/dist/routes/tasks.js +60 -2
  69. package/dist/routes/tasks.js.map +1 -1
  70. package/dist/runtime/acp/history-reconciler.js +1 -1
  71. package/dist/runtime/acp/history-reconciler.js.map +1 -1
  72. package/dist/services/__tests__/session-manager.lifecycle.test.js +171 -1
  73. package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
  74. package/dist/services/__tests__/task.service.test.js +142 -76
  75. package/dist/services/__tests__/task.service.test.js.map +1 -1
  76. package/dist/services/__tests__/translation-protection.test.js +48 -18
  77. package/dist/services/__tests__/translation-protection.test.js.map +1 -1
  78. package/dist/services/__tests__/translation-session-blocks.test.js +66 -2
  79. package/dist/services/__tests__/translation-session-blocks.test.js.map +1 -1
  80. package/dist/services/__tests__/workspace-background-service.service.test.js +74 -26
  81. package/dist/services/__tests__/workspace-background-service.service.test.js.map +1 -1
  82. package/dist/services/__tests__/workspace.service.test.js +70 -0
  83. package/dist/services/__tests__/workspace.service.test.js.map +1 -1
  84. package/dist/services/message-delivery.service.d.ts +4 -1
  85. package/dist/services/message-delivery.service.d.ts.map +1 -1
  86. package/dist/services/project.service.d.ts +3 -0
  87. package/dist/services/project.service.d.ts.map +1 -1
  88. package/dist/services/provider-config.service.d.ts.map +1 -1
  89. package/dist/services/provider-config.service.js +1 -0
  90. package/dist/services/provider-config.service.js.map +1 -1
  91. package/dist/services/session-manager.d.ts +15 -5
  92. package/dist/services/session-manager.d.ts.map +1 -1
  93. package/dist/services/session-manager.js +19 -3
  94. package/dist/services/session-manager.js.map +1 -1
  95. package/dist/services/task-cleanup.service.d.ts +2 -0
  96. package/dist/services/task-cleanup.service.d.ts.map +1 -1
  97. package/dist/services/task-cleanup.service.js +2 -2
  98. package/dist/services/task-cleanup.service.js.map +1 -1
  99. package/dist/services/task-handoff.service.d.ts +18 -0
  100. package/dist/services/task-handoff.service.d.ts.map +1 -0
  101. package/dist/services/task-handoff.service.js +87 -0
  102. package/dist/services/task-handoff.service.js.map +1 -0
  103. package/dist/services/task.service.d.ts +45 -18
  104. package/dist/services/task.service.d.ts.map +1 -1
  105. package/dist/services/task.service.js +215 -160
  106. package/dist/services/task.service.js.map +1 -1
  107. package/dist/services/team-run.service.d.ts +3 -0
  108. package/dist/services/team-run.service.d.ts.map +1 -1
  109. package/dist/services/team-task-input.service.d.ts +3 -0
  110. package/dist/services/team-task-input.service.d.ts.map +1 -1
  111. package/dist/services/translation-protection.d.ts +3 -1
  112. package/dist/services/translation-protection.d.ts.map +1 -1
  113. package/dist/services/translation-protection.js +21 -67
  114. package/dist/services/translation-protection.js.map +1 -1
  115. package/dist/services/translation-settings.service.d.ts +4 -4
  116. package/dist/services/translation.service.d.ts.map +1 -1
  117. package/dist/services/translation.service.js +44 -20
  118. package/dist/services/translation.service.js.map +1 -1
  119. package/dist/services/workspace-background-service.service.d.ts +1 -3
  120. package/dist/services/workspace-background-service.service.d.ts.map +1 -1
  121. package/dist/services/workspace-background-service.service.js +17 -65
  122. package/dist/services/workspace-background-service.service.js.map +1 -1
  123. package/dist/services/workspace.service.d.ts +21 -6
  124. package/dist/services/workspace.service.d.ts.map +1 -1
  125. package/dist/services/workspace.service.js +56 -11
  126. package/dist/services/workspace.service.js.map +1 -1
  127. package/dist/web/assets/AgentDemoPage-JXetlNBf.js +1 -0
  128. package/dist/web/assets/{AgentEnvironmentSettingsPage-GHjKIWpb.js → AgentEnvironmentSettingsPage-CdYyUCwJ.js} +1 -1
  129. package/dist/web/assets/AttachmentPreview-DMS-PM-1.js +2 -0
  130. package/dist/web/assets/BranchSelect-BAq4L2ZS.js +1 -0
  131. package/dist/web/assets/BrandLogo--kWXXJtZ.js +1 -0
  132. package/dist/web/assets/ConversationPage-CvC4RyB5.js +1 -0
  133. package/dist/web/assets/CreateTaskInput-DjVxCAfc.js +1 -0
  134. package/dist/web/assets/{DemoPage-DK-ikg1_.js → DemoPage-cDVTGOp8.js} +3 -3
  135. package/dist/web/assets/DesignSystemPage-BI54IkKi.js +12 -0
  136. package/dist/web/assets/{ExecutionPermissionSelector-B8befbxJ.js → ExecutionPermissionSelector-f1UYIm1z.js} +1 -1
  137. package/dist/web/assets/GeneralSettingsPage-C0yto6C_.js +1 -0
  138. package/dist/web/assets/{LoadingPreviewPage-BDlf89rJ.js → LoadingPreviewPage-BiJSaHUi.js} +1 -1
  139. package/dist/web/assets/McpSettingsPage-BbHwZtyj.js +1 -0
  140. package/dist/web/assets/{MemberAvatar-YVr_erAr.js → MemberAvatar-BLezO3vS.js} +1 -1
  141. package/dist/web/assets/MobileTaskDetail-M_Kke9rL.js +3 -0
  142. package/dist/web/assets/NotificationSettingsPage-CiKoL2Kv.js +1 -0
  143. package/dist/web/assets/{ProfileSettingsPage-CHe4LlRp.js → ProfileSettingsPage-DCB48cPS.js} +2 -2
  144. package/dist/web/assets/ProjectKanbanPage-soDqevjQ.js +12 -0
  145. package/dist/web/assets/ProjectSettingsPage-COvIVq1h.js +2 -0
  146. package/dist/web/assets/ProviderSettingsPage-CPuJcZDg.js +67 -0
  147. package/dist/web/assets/{SettingsMasterDetail-DjWR9Sqk.js → SettingsMasterDetail-Cv7YaRJB.js} +1 -1
  148. package/dist/web/assets/TaskDetail-CqhPxvg3.js +1 -0
  149. package/dist/web/assets/TaskHandoffPanel-CxH3Mgyd.js +70 -0
  150. package/dist/web/assets/TeamSettingsPage-D-XDefNd.js +1 -0
  151. package/dist/web/assets/{TranslationSettingsPage-Cod9c4Ug.js → TranslationSettingsPage-BPHI-SZW.js} +1 -1
  152. package/dist/web/assets/{arc-DEy33vTc.js → arc-CC81aLuf.js} +1 -1
  153. package/dist/web/assets/{architectureDiagram-3BPJPVTR-Ky1D1LBk.js → architectureDiagram-3BPJPVTR-DL08XGaN.js} +1 -1
  154. package/dist/web/assets/{at-sign-C48_y345.js → at-sign-DYPIlD6D.js} +1 -1
  155. package/dist/web/assets/{blockDiagram-GPEHLZMM-BNgB7kNT.js → blockDiagram-GPEHLZMM-B4IW5WxI.js} +1 -1
  156. package/dist/web/assets/{c4Diagram-AAUBKEIU-4GMrPMgQ.js → c4Diagram-AAUBKEIU-Cj4d_FqK.js} +1 -1
  157. package/dist/web/assets/channel-CUa8oyzC.js +1 -0
  158. package/dist/web/assets/check-DaZqOL8l.js +1 -0
  159. package/dist/web/assets/{chevron-down-3y5Aiolp.js → chevron-down-DNhYsNAt.js} +1 -1
  160. package/dist/web/assets/{chevron-up-DqaYcjpq.js → chevron-up-CjrLieBR.js} +1 -1
  161. package/dist/web/assets/chip-Cf92J-pQ.js +1 -0
  162. package/dist/web/assets/{chunk-2J33WTMH-C439hRe_.js → chunk-2J33WTMH-DeCj8QSD.js} +1 -1
  163. package/dist/web/assets/{chunk-4BX2VUAB-D8GK3M3t.js → chunk-4BX2VUAB-k-6rzQmQ.js} +1 -1
  164. package/dist/web/assets/{chunk-55IACEB6-Dl4UhJ8X.js → chunk-55IACEB6-qMTCQ5bX.js} +1 -1
  165. package/dist/web/assets/{chunk-727SXJPM-BSEeayBq.js → chunk-727SXJPM-CMnu18d1.js} +1 -1
  166. package/dist/web/assets/{chunk-AQP2D5EJ-B6chbZfT.js → chunk-AQP2D5EJ-C0mFbUMn.js} +1 -1
  167. package/dist/web/assets/{chunk-FMBD7UC4-DaLkiLdQ.js → chunk-FMBD7UC4-u6-_M137.js} +1 -1
  168. package/dist/web/assets/{chunk-ND2GUHAM-DeQwZAAT.js → chunk-ND2GUHAM-DHrIE_8H.js} +1 -1
  169. package/dist/web/assets/{chunk-QZHKN3VN-CLRr4YRM.js → chunk-QZHKN3VN-aTyC0wZq.js} +1 -1
  170. package/dist/web/assets/classDiagram-4FO5ZUOK-BAx111NO.js +1 -0
  171. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BAx111NO.js +1 -0
  172. package/dist/web/assets/{code-block-OCS4YCEC-CnoxUpOx.js → code-block-OCS4YCEC-CBIOQOMH.js} +1 -1
  173. package/dist/web/assets/confirm-dialog-DWeBZLJW.js +1 -0
  174. package/dist/web/assets/content-JM9E1MeD.js +2 -0
  175. package/dist/web/assets/{copy-HRMhTkIG.js → copy-D0gl9kF8.js} +1 -1
  176. package/dist/web/assets/{cose-bilkent-S5V4N54A-C1UiYzfk.js → cose-bilkent-S5V4N54A-xLyliSgU.js} +1 -1
  177. package/dist/web/assets/cssMode-DO5UnQ64.js +1 -0
  178. package/dist/web/assets/{dagre-BM42HDAG-CTJpBgye.js → dagre-BM42HDAG-CbNpoO2d.js} +1 -1
  179. package/dist/web/assets/{diagram-2AECGRRQ-Dj1_TEYp.js → diagram-2AECGRRQ-CG3hKxAX.js} +1 -1
  180. package/dist/web/assets/{diagram-5GNKFQAL-V-KUpwqh.js → diagram-5GNKFQAL-mQu9M28s.js} +1 -1
  181. package/dist/web/assets/{diagram-KO2AKTUF-CXG1ZIeX.js → diagram-KO2AKTUF-BSeEp_Uw.js} +1 -1
  182. package/dist/web/assets/{diagram-LMA3HP47-BZSJUwk9.js → diagram-LMA3HP47-D6yRdao-.js} +1 -1
  183. package/dist/web/assets/{diagram-OG6HWLK6-CCr4gq4u.js → diagram-OG6HWLK6-BUBUW9wD.js} +1 -1
  184. package/dist/web/assets/dialog-D1lkhqVL.js +5 -0
  185. package/dist/web/assets/{erDiagram-TEJ5UH35-Djtyzh6z.js → erDiagram-TEJ5UH35-BQSZgRqq.js} +1 -1
  186. package/dist/web/assets/file-BijjwxRR.js +1 -0
  187. package/dist/web/assets/{flowDiagram-I6XJVG4X-BmSVSiXJ.js → flowDiagram-I6XJVG4X-D-eqyr5Y.js} +1 -1
  188. package/dist/web/assets/folder-picker-Czxm0_S9.js +1 -0
  189. package/dist/web/assets/{freemarker2-DdptX5f1.js → freemarker2-B4RvEa_U.js} +2 -2
  190. package/dist/web/assets/{ganttDiagram-6RSMTGT7-r5vQX-b5.js → ganttDiagram-6RSMTGT7-BWGkCt_x.js} +1 -1
  191. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-Zj9aloh9.js → gitGraphDiagram-PVQCEYII-DGVc49pR.js} +1 -1
  192. package/dist/web/assets/{globe-B9I-jHBB.js → globe-Ce01-45x.js} +1 -1
  193. package/dist/web/assets/handlebars-oyT7drqS.js +1 -0
  194. package/dist/web/assets/html-CxxWf2Qk.js +1 -0
  195. package/dist/web/assets/htmlMode-RfAHh5Jj.js +1 -0
  196. package/dist/web/assets/index-BMZFF1UJ.js +1 -0
  197. package/dist/web/assets/index-BN53vGxp.css +1 -0
  198. package/dist/web/assets/index-Cc3qXMhU.js +122 -0
  199. package/dist/web/assets/{index-Bhvt-E6d.js → index-DWrSDMX2.js} +2 -2
  200. package/dist/web/assets/{index-DkRy6eNX.js → index-Dh-tgeaL.js} +4 -4
  201. package/dist/web/assets/{index-B1oVmCie.js → index-Disv1kQS.js} +1 -1
  202. package/dist/web/assets/{index-DcOU2m2F.js → index-DyJfPc4O.js} +1 -1
  203. package/dist/web/assets/{index-Dk3bRQAK.js → index-Pt4TsHjm.js} +1 -1
  204. package/dist/web/assets/{index-CThFsQNP.js → index-T38iVLEb.js} +1 -1
  205. package/dist/web/assets/{index-BtUQYU5l.js → index-hpHE1jXY.js} +1 -1
  206. package/dist/web/assets/{infoDiagram-5YYISTIA-BHtVaxZx.js → infoDiagram-5YYISTIA-DLPs3dV_.js} +1 -1
  207. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-BYmwjkbC.js → ishikawaDiagram-YF4QCWOH-d_OrnwDY.js} +1 -1
  208. package/dist/web/assets/javascript-BNXtViSL.js +1 -0
  209. package/dist/web/assets/{journeyDiagram-JHISSGLW-c8BBqqRS.js → journeyDiagram-JHISSGLW-EtU1st_0.js} +1 -1
  210. package/dist/web/assets/jsonMode-Bqh5vIxx.js +7 -0
  211. package/dist/web/assets/{kanban-definition-UN3LZRKU-D4Vr-oFT.js → kanban-definition-UN3LZRKU-ZWzUnsFK.js} +1 -1
  212. package/dist/web/assets/{kbd-C88ut3t9.js → kbd-IjDGsZ2B.js} +1 -1
  213. package/dist/web/assets/{key-round-Cjbj2qNa.js → key-round-CbnAuUxp.js} +1 -1
  214. package/dist/web/assets/{layers-eaHAVcc3.js → layers-Pdy9vZ6K.js} +1 -1
  215. package/dist/web/assets/{linear-opi90s0L.js → linear-D7n23wcA.js} +1 -1
  216. package/dist/web/assets/{link-BSGb85gq.js → link-Bl1ZbjFh.js} +1 -1
  217. package/dist/web/assets/liquid-Ds47iVLN.js +1 -0
  218. package/dist/web/assets/{lspLanguageFeatures-DWu2Ubf0.js → lspLanguageFeatures-Dcch3bsp.js} +1 -1
  219. package/dist/web/assets/mdx-CM7ky-2B.js +1 -0
  220. package/dist/web/assets/{menu--sjyisSg.js → menu-DubntFHq.js} +1 -1
  221. package/dist/web/assets/{mermaid-NOHMQCX5-BSNbBj78.js → mermaid-NOHMQCX5-nBsnjgeU.js} +54 -54
  222. package/dist/web/assets/{message-square-BLEJN6p7.js → message-square-BE-pW0m_.js} +1 -1
  223. package/dist/web/assets/{mindmap-definition-RKZ34NQL-BN3ZjBh-.js → mindmap-definition-RKZ34NQL-ClHORNTt.js} +1 -1
  224. package/dist/web/assets/modal-CHLtZWvx.js +1 -0
  225. package/dist/web/assets/{monaco-BkuNyOkP.js → monaco-BbAmalE2.js} +3 -3
  226. package/dist/web/assets/nav-row-DH7rNq1V.js +1 -0
  227. package/dist/web/assets/{paperclip-CTNoXpUW.js → paperclip-YP3Bk1lI.js} +1 -1
  228. package/dist/web/assets/{pencil-Cp70ZwtG.js → pencil-BFDuhbtW.js} +1 -1
  229. package/dist/web/assets/{people-DT78SebR.js → people-DEeNaaLt.js} +1 -1
  230. package/dist/web/assets/{pieDiagram-4H26LBE5-CfmFHpPI.js → pieDiagram-4H26LBE5-BUOaKIyP.js} +1 -1
  231. package/dist/web/assets/popover-CUmBEXxL.js +1 -0
  232. package/dist/web/assets/provider-selection-DSu7SCI7.js +1 -0
  233. package/dist/web/assets/python-BODTtU6m.js +1 -0
  234. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-CeaumJAa.js → quadrantDiagram-W4KKPZXB-Caezg-U2.js} +1 -1
  235. package/dist/web/assets/razor-CHm8DY1p.js +1 -0
  236. package/dist/web/assets/{requirementDiagram-4Y6WPE33-BWWCDXCE.js → requirementDiagram-4Y6WPE33-BtdP3Ka6.js} +1 -1
  237. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-DAJut743.js → sankeyDiagram-5OEKKPKP-Bv74oA-n.js} +1 -1
  238. package/dist/web/assets/{search-DniD_AGC.js → search-ClmOQd4H.js} +1 -1
  239. package/dist/web/assets/{select-BPdJrr5M.js → select-BB7QG08o.js} +1 -1
  240. package/dist/web/assets/{select-B5RYunTs.js → select-CYtYPr-c.js} +1 -1
  241. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-CynjRGW4.js → sequenceDiagram-3UESZ5HK-DuNPl-dg.js} +1 -1
  242. package/dist/web/assets/{server-xswxWpKD.js → server-bEB_DY55.js} +1 -1
  243. package/dist/web/assets/{stateDiagram-AJRCARHV-amAtpx9w.js → stateDiagram-AJRCARHV-B72HAgBJ.js} +1 -1
  244. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Bu3l_CgB.js +1 -0
  245. package/dist/web/assets/{textarea-BVyPl9rM.js → textarea-BqYPvoO-.js} +1 -1
  246. package/dist/web/assets/{timeline-definition-PNZ67QCA-DZ9W7WlP.js → timeline-definition-PNZ67QCA-DTmHUgmQ.js} +1 -1
  247. package/dist/web/assets/{toggles-QcIz43DP.js → toggles-a5fkwT_D.js} +1 -1
  248. package/dist/web/assets/tooltip-DuyJMci9.js +1 -0
  249. package/dist/web/assets/{track-Ckye0-tt.js → track-DCjyrv3n.js} +1 -1
  250. package/dist/web/assets/{trash-2-CvUJsbtT.js → trash-2-CIyBqaVy.js} +1 -1
  251. package/dist/web/assets/tsMode-C1UXAkMh.js +11 -0
  252. package/dist/web/assets/typescript-Biht2wHb.js +1 -0
  253. package/dist/web/assets/{upload-CsXAmAun.js → upload-D05XS5-G.js} +1 -1
  254. package/dist/web/assets/use-profiles-CrWTyz1G.js +1 -0
  255. package/dist/web/assets/{use-providers-Td1-gyrN.js → use-providers-Dv1Nk6s7.js} +1 -1
  256. package/dist/web/assets/{use-translation-settings-NYM0lmBx.js → use-translation-settings-CcUnipVO.js} +1 -1
  257. package/dist/web/assets/useNormalizedLogs-D9W1cT4R.js +1 -0
  258. package/dist/web/assets/useTokenUsage-CZ5mDshD.js +1 -0
  259. package/dist/web/assets/{vennDiagram-CIIHVFJN-CB2Ubvy9.js → vennDiagram-CIIHVFJN-ByoNydXe.js} +1 -1
  260. package/dist/web/assets/{wardley-L42UT6IY-BXOkk77I.js → wardley-L42UT6IY-BqdMkOLl.js} +1 -1
  261. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-VA6ywdQQ.js → wardleyDiagram-YWT4CUSO-AO22HrEF.js} +1 -1
  262. package/dist/web/assets/xml-NLGqNFDv.js +1 -0
  263. package/dist/web/assets/{xychartDiagram-2RQKCTM6-FNcvDj5G.js → xychartDiagram-2RQKCTM6-vzPgjgqW.js} +1 -1
  264. package/dist/web/assets/yaml-AWlGS_Ig.js +1 -0
  265. package/dist/web/index.html +2 -2
  266. package/node_modules/@agent-tower/shared/dist/index.d.ts +1 -0
  267. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  268. package/node_modules/@agent-tower/shared/dist/index.js +1 -0
  269. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  270. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts +12 -0
  271. package/node_modules/@agent-tower/shared/dist/task-handoff.d.ts.map +1 -0
  272. package/node_modules/@agent-tower/shared/dist/task-handoff.js +2 -0
  273. package/node_modules/@agent-tower/shared/dist/task-handoff.js.map +1 -0
  274. package/node_modules/@agent-tower/shared/dist/translation.d.ts +12 -0
  275. package/node_modules/@agent-tower/shared/dist/translation.d.ts.map +1 -1
  276. package/node_modules/@agent-tower/shared/dist/translation.js +19 -0
  277. package/node_modules/@agent-tower/shared/dist/translation.js.map +1 -1
  278. package/node_modules/@agent-tower/shared/dist/types.d.ts +28 -1
  279. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  280. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  281. package/package.json +1 -1
  282. package/prisma/migrations/20261023000000_add_task_base_branch/migration.sql +2 -0
  283. package/prisma/migrations/20261023010000_add_subtasks/migration.sql +2 -0
  284. package/prisma/migrations/20261026000000_add_task_handoff/migration.sql +1 -0
  285. package/prisma/schema.prisma +6 -0
  286. package/dist/web/assets/AgentDemoPage-BfXkNCdN.js +0 -1
  287. package/dist/web/assets/AttachmentPreview-CLN1lnLe.js +0 -2
  288. package/dist/web/assets/BranchSelect-Cofp8vYf.js +0 -1
  289. package/dist/web/assets/BrandLogo-BUimEX-9.js +0 -1
  290. package/dist/web/assets/ConversationPage-R7vNaOQv.js +0 -1
  291. package/dist/web/assets/CreateTaskInput-CasITF8y.js +0 -1
  292. package/dist/web/assets/DesignSystemPage-BhjNs2vP.js +0 -12
  293. package/dist/web/assets/EditableTaskTitle-BJ9Rxggf.js +0 -70
  294. package/dist/web/assets/GeneralSettingsPage-RMN2al8I.js +0 -1
  295. package/dist/web/assets/McpSettingsPage-CPNxFF67.js +0 -1
  296. package/dist/web/assets/MobileTaskDetail-QNUHMq7u.js +0 -3
  297. package/dist/web/assets/NotificationSettingsPage-BEyuyu3Z.js +0 -1
  298. package/dist/web/assets/ProjectKanbanPage-Bt_01giW.js +0 -12
  299. package/dist/web/assets/ProjectSettingsPage-cus6Ns97.js +0 -2
  300. package/dist/web/assets/ProviderSettingsPage-DqA_B1Ey.js +0 -67
  301. package/dist/web/assets/TaskDetail-rEXNe2h3.js +0 -1
  302. package/dist/web/assets/TeamSettingsPage-DwWROtW6.js +0 -1
  303. package/dist/web/assets/channel-DTP-syjA.js +0 -1
  304. package/dist/web/assets/check-BmiJ7qMY.js +0 -1
  305. package/dist/web/assets/classDiagram-4FO5ZUOK-CUKgurLa.js +0 -1
  306. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-CUKgurLa.js +0 -1
  307. package/dist/web/assets/confirm-dialog-ltNBFYCs.js +0 -1
  308. package/dist/web/assets/content-DPQMfMae.js +0 -2
  309. package/dist/web/assets/cssMode-DkuSsGeg.js +0 -1
  310. package/dist/web/assets/dialog-CuFEdqLg.js +0 -5
  311. package/dist/web/assets/handlebars-B34f-LAf.js +0 -1
  312. package/dist/web/assets/hooks-BLxb16IA.js +0 -1
  313. package/dist/web/assets/html-CnvOQ63o.js +0 -1
  314. package/dist/web/assets/htmlMode-RdxZ8Svp.js +0 -1
  315. package/dist/web/assets/icon-button-CYiKa-7C.js +0 -1
  316. package/dist/web/assets/index-0XPZayUU.css +0 -1
  317. package/dist/web/assets/index-BJObbxud.js +0 -122
  318. package/dist/web/assets/index-DZLnTISJ.js +0 -1
  319. package/dist/web/assets/javascript-BhombDEO.js +0 -1
  320. package/dist/web/assets/jsonMode-BJGOUeaV.js +0 -7
  321. package/dist/web/assets/liquid-Czy1WpPz.js +0 -1
  322. package/dist/web/assets/mdx-DhXfoSnM.js +0 -1
  323. package/dist/web/assets/modal-CSGOqphK.js +0 -1
  324. package/dist/web/assets/popover-BiVxGGgp.js +0 -1
  325. package/dist/web/assets/python-DIcQDjcf.js +0 -1
  326. package/dist/web/assets/razor-DYK6Rxer.js +0 -1
  327. package/dist/web/assets/search-field-CQLDXLeA.js +0 -1
  328. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-V-lSceg1.js +0 -1
  329. package/dist/web/assets/tooltip-CPqGK_WZ.js +0 -1
  330. package/dist/web/assets/tsMode-c5x0d47n.js +0 -11
  331. package/dist/web/assets/typescript-i7eYblLt.js +0 -1
  332. package/dist/web/assets/use-profiles-DuXaBRju.js +0 -1
  333. package/dist/web/assets/use-projects-Ddz0knyr.js +0 -1
  334. package/dist/web/assets/use-workspaces-CdJZLmGh.js +0 -1
  335. package/dist/web/assets/useNormalizedLogs--RbVywYa.js +0 -1
  336. package/dist/web/assets/useTokenUsage-DDXf7E4p.js +0 -1
  337. package/dist/web/assets/xml-D2EGActB.js +0 -1
  338. package/dist/web/assets/yaml-jeiIZSL4.js +0 -1
  339. /package/dist/web/assets/{EditableTaskTitle-DcpNTP1B.css → TaskHandoffPanel-DcpNTP1B.css} +0 -0
@@ -3,13 +3,20 @@ 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
- import { ensureProjectIsMutable, getStoredProjectGitCapability } from './project-guards.js';
8
+ import { ensureProjectIsMutable, getStoredProjectGitCapability, detectProjectGitCapability } from './project-guards.js';
9
+ import { requireLocalBaseBranch } from '../git/base-branch.js';
9
10
  import { defaultTeamLockService } from './team-lock.service.js';
10
11
  import { TaskPriority } from '@agent-tower/shared';
11
12
  import prismaPkg from '@prisma/client';
12
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
+ `;
13
20
  /**
14
21
  * 合法的状态流转规则
15
22
  * 看板拖拽场景下允许任意状态互转,状态变更无危险副作用
@@ -210,13 +217,17 @@ function normalizeUpdateTaskInput(current, input) {
210
217
  ]),
211
218
  };
212
219
  }
220
+ function withoutTaskHandoff(task) {
221
+ const { handoff: _handoff, ...metadata } = task;
222
+ return metadata;
223
+ }
213
224
  function withTaskPreviews(task, options = {}) {
214
225
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
215
226
  const contentPreview = buildTextPreview(task.description);
216
227
  const isTruncated = task.title !== titlePreview
217
228
  || Boolean(task.description && task.description !== contentPreview);
218
229
  const base = {
219
- ...task,
230
+ ...withoutTaskHandoff(task),
220
231
  title: titlePreview,
221
232
  titlePreview,
222
233
  contentPreview: contentPreview || undefined,
@@ -234,13 +245,15 @@ function withProjectGitMetadata(task) {
234
245
  return withProjectGitCapability(task, capability);
235
246
  }
236
247
  function withProjectGitCapability(task, capability) {
237
- return {
248
+ const result = {
238
249
  ...task,
239
250
  project: {
240
251
  ...task.project,
241
252
  ...capability,
242
253
  },
243
254
  };
255
+ delete result.handoff;
256
+ return result;
244
257
  }
245
258
  function buildProjectGitMetadata(project) {
246
259
  return getStoredProjectGitCapability(project);
@@ -274,30 +287,28 @@ export class TaskService {
274
287
  deletedAt: null,
275
288
  project: { archivedAt: null },
276
289
  ...(params.projectId ? { projectId: params.projectId } : {}),
290
+ ...(params.parentTaskId ? { parentTaskId: params.parentTaskId } : {}),
277
291
  ...(params.status ? { status: params.status } : {}),
278
292
  };
279
293
  const projectFilter = params.projectId
280
294
  ? Prisma.sql `AND task."projectId" = ${params.projectId}`
281
295
  : Prisma.empty;
296
+ const parentFilter = params.parentTaskId
297
+ ? Prisma.sql `AND task."parentTaskId" = ${params.parentTaskId}`
298
+ : Prisma.empty;
282
299
  const statusFilter = params.status
283
300
  ? Prisma.sql `AND task."status" = ${params.status}`
284
301
  : Prisma.empty;
285
302
  const [tasks, total] = await Promise.all([
286
303
  prisma.$queryRaw(Prisma.sql `
287
- SELECT
288
- task."id" AS "id",
289
- task."projectId" AS "projectId",
290
- task."title" AS "title",
291
- task."status" AS "status",
292
- task."priority" AS "priority",
293
- task."position" AS "position",
294
- task."updatedAt" AS "updatedAt"
304
+ SELECT ${taskBoardColumns}
295
305
  FROM "Task" task
296
306
  INNER JOIN "Project" project ON project."id" = task."projectId"
297
307
  WHERE task."deletedAt" IS NULL
298
308
  AND project."archivedAt" IS NULL
299
309
  ${projectFilter}
300
310
  ${statusFilter}
311
+ ${parentFilter}
301
312
  ORDER BY
302
313
  CASE task."status"
303
314
  WHEN ${TaskStatus.IN_PROGRESS} THEN 0
@@ -314,7 +325,19 @@ export class TaskService {
314
325
  `),
315
326
  prisma.task.count({ where }),
316
327
  ]);
317
- 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);
318
341
  const workspaceRows = taskIds.length === 0
319
342
  ? []
320
343
  : await prisma.$queryRaw(Prisma.sql `
@@ -362,7 +385,7 @@ export class TaskService {
362
385
  workspacesByTaskId.set(workspace.taskId, group);
363
386
  }
364
387
  const preferredWorkspaceByTaskId = new Map();
365
- for (const task of tasks) {
388
+ for (const task of allTasks) {
366
389
  const candidates = workspacesByTaskId.get(task.id) ?? [];
367
390
  const preferred = candidates.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
368
391
  ?? candidates[0];
@@ -370,7 +393,7 @@ export class TaskService {
370
393
  preferredWorkspaceByTaskId.set(task.id, preferred);
371
394
  }
372
395
  }
373
- const data = tasks.map((task) => {
396
+ const data = allTasks.map((task) => {
374
397
  const titlePreview = buildTextPreview(task.title, TASK_TITLE_MAX_LENGTH);
375
398
  const preferredWorkspace = preferredWorkspaceByTaskId.get(task.id);
376
399
  const hasRunningSession = (workspacesByTaskId.get(task.id) ?? [])
@@ -381,6 +404,9 @@ export class TaskService {
381
404
  return {
382
405
  id: task.id,
383
406
  projectId: task.projectId,
407
+ parentTaskId: task.parentTaskId,
408
+ subtaskCount: Number(task.subtaskCount),
409
+ runningSubtaskCount: Number(task.runningSubtaskCount),
384
410
  title: titlePreview,
385
411
  status: task.status,
386
412
  priority: task.priority ?? TaskPriority.NORMAL,
@@ -400,7 +426,8 @@ export class TaskService {
400
426
  };
401
427
  });
402
428
  return {
403
- data,
429
+ data: data.slice(0, tasks.length),
430
+ ...(parents.length ? { parents: data.slice(tasks.length) } : {}),
404
431
  total,
405
432
  page,
406
433
  limit,
@@ -441,6 +468,8 @@ export class TaskService {
441
468
  id: true,
442
469
  projectId: true,
443
470
  title: true,
471
+ baseBranch: true,
472
+ parentTaskId: true,
444
473
  status: true,
445
474
  priority: true,
446
475
  position: true,
@@ -487,6 +516,9 @@ export class TaskService {
487
516
  projectId: true,
488
517
  project: true,
489
518
  title: true,
519
+ baseBranch: true,
520
+ parentTaskId: true,
521
+ _count: { select: { subtasks: { where: { deletedAt: null } } } },
490
522
  status: true,
491
523
  priority: true,
492
524
  position: true,
@@ -498,7 +530,14 @@ export class TaskService {
498
530
  if (!task) {
499
531
  throw new NotFoundError('Task', id);
500
532
  }
501
- 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 });
502
541
  }
503
542
  async findBodyById(id) {
504
543
  const task = await prisma.task.findFirst({
@@ -551,27 +590,73 @@ export class TaskService {
551
590
  throw new NotFoundError('Project', projectId);
552
591
  }
553
592
  ensureProjectIsMutable(project, 'create tasks');
554
- // 自动计算 position
555
- const maxPosition = await prisma.task.aggregate({
556
- where: { projectId, status: TaskStatus.TODO, deletedAt: null },
557
- _max: { position: true },
558
- });
559
- const created = await prisma.task.create({
560
- data: {
561
- title: normalizedInput.title,
562
- description: normalizedInput.description,
563
- priority: normalizedInput.priority ?? TaskPriority.NORMAL,
564
- position: (maxPosition._max.position ?? 0) + 1,
565
- projectId,
566
- },
567
- });
568
- return {
569
- ...created,
570
- project: {
571
- ...project,
572
- ...buildProjectGitMetadata(project),
573
- },
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
+ }
617
+ }
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
+ };
574
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();
575
660
  }
576
661
  /**
577
662
  * 更新任务基本信息
@@ -594,7 +679,7 @@ export class TaskService {
594
679
  this.emitTaskUpdated(id, task.projectId, updated.status, updated.priority);
595
680
  }
596
681
  return {
597
- ...updated,
682
+ ...withoutTaskHandoff(updated),
598
683
  project: {
599
684
  ...task.project,
600
685
  ...buildProjectGitMetadata(task.project),
@@ -639,7 +724,7 @@ export class TaskService {
639
724
  // 通知前端
640
725
  this.emitTaskUpdated(id, task.projectId, status);
641
726
  return {
642
- ...updated,
727
+ ...withoutTaskHandoff(updated),
643
728
  project: {
644
729
  ...task.project,
645
730
  ...buildProjectGitMetadata(task.project),
@@ -676,7 +761,7 @@ export class TaskService {
676
761
  this.emitTaskUpdated(id, task.projectId, status);
677
762
  }
678
763
  return {
679
- ...updated,
764
+ ...withoutTaskHandoff(updated),
680
765
  project: {
681
766
  ...task.project,
682
767
  ...buildProjectGitMetadata(task.project),
@@ -686,149 +771,119 @@ export class TaskService {
686
771
  /**
687
772
  * 快速删除任务
688
773
  *
689
- * 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
690
- * 2. 保存后台清理快照
774
+ * 1. 在同一事务中标记父子 Task 为 deletedAt,并保存清理快照
775
+ * 2. 普通读取立即隐藏已删除的任务
691
776
  * 3. 通过 EventBus 通知前端实时删除
692
777
  * 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
693
778
  */
694
- async delete(id) {
695
- const deletedAt = new Date();
696
- const taskForGuard = await prisma.task.findUnique({
697
- where: { id },
698
- include: { project: true },
699
- });
700
- 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)
701
782
  throw new NotFoundError('Task', id);
702
- }
703
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
+ }
704
789
  const workspaceIds = await prisma.workspace.findMany({
705
- where: { taskId: id },
790
+ where: { task: { OR: [{ id }, { parentTaskId: id }], deletedAt: null } },
706
791
  select: { id: true },
707
792
  });
708
- let marked;
709
- await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
710
- for (const workspace of workspaceIds) {
793
+ const result = await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
794
+ for (const workspace of workspaceIds)
711
795
  await this.backgroundService?.stopAllForWorkspace(workspace.id);
712
- }
713
- marked = await prisma.task.updateMany({
714
- where: {
715
- id,
716
- deletedAt: null,
717
- project: { archivedAt: null },
718
- },
719
- data: { deletedAt },
720
- });
721
- });
722
- if (marked.count === 0) {
723
- const current = await prisma.task.findUnique({
724
- where: { id },
725
- include: { project: true },
726
- });
727
- if (!current || current.deletedAt) {
728
- throw new NotFoundError('Task', id);
729
- }
730
- ensureProjectIsMutable(current.project, 'delete tasks');
731
- throw new NotFoundError('Task', id);
732
- }
733
- let deleteResult;
734
- let cleanupJobCreated = false;
735
- try {
736
- const taskForCleanup = await prisma.task.findUnique({
737
- where: { id },
738
- include: {
739
- project: true,
740
- teamRun: {
741
- include: {
742
- invocations: {
743
- where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
744
- select: { id: true },
745
- },
746
- },
747
- },
748
- workspaces: {
749
- 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
+ } } },
750
819
  },
751
- },
752
- });
753
- if (!taskForCleanup) {
754
- throw new NotFoundError('Task', id);
755
- }
756
- ensureProjectIsMutable(taskForCleanup.project, 'delete tasks');
757
- const snapshot = {
758
- taskId: taskForCleanup.id,
759
- projectId: taskForCleanup.projectId,
760
- project: {
761
- repoPath: taskForCleanup.project.repoPath,
762
- mainBranch: taskForCleanup.project.mainBranch,
763
- },
764
- workspaces: taskForCleanup.workspaces.map((workspace) => ({
765
- id: workspace.id,
766
- worktreePath: workspace.worktreePath,
767
- workingDir: getWorkspaceWorkingDir(workspace),
768
- workspaceKind: workspace.workspaceKind,
769
- branchName: workspace.branchName,
770
- baseBranch: workspace.baseBranch,
771
- // Logical completion does not close a reusable ACP adapter.
772
- sessions: workspace.sessions.map((session) => ({ id: session.id })),
773
- })),
774
- };
775
- const teamRunId = taskForCleanup.teamRun?.id;
776
- const cancelledInvocationIds = taskForCleanup.teamRun?.invocations.map((invocation) => invocation.id) ?? [];
777
- 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
+ };
778
836
  await tx.taskCleanupJob.create({
779
- data: {
780
- taskId: taskForCleanup.id,
781
- projectId: taskForCleanup.projectId,
782
- payload: JSON.stringify(snapshot),
783
- },
837
+ data: { taskId: id, projectId: parent.projectId, payload: JSON.stringify(snapshot) },
784
838
  });
785
- if (teamRunId) {
839
+ const teamRunIds = tasks.flatMap((task) => task.teamRun ? [task.teamRun.id] : []);
840
+ if (teamRunIds.length) {
786
841
  await tx.workRequest.updateMany({
787
- where: {
788
- teamRunId,
789
- status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES },
790
- },
842
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES } },
791
843
  data: { status: 'CANCELLED' },
792
844
  });
793
845
  await tx.agentInvocation.updateMany({
794
- where: {
795
- teamRunId,
796
- status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES },
797
- },
798
- data: {
799
- status: 'CANCELLED',
800
- nextRoomReplyReminderAt: null,
801
- },
846
+ where: { teamRunId: { in: teamRunIds }, status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
847
+ data: { status: 'CANCELLED', nextRoomReplyReminderAt: null },
802
848
  });
803
849
  }
850
+ return {
851
+ taskIds, projectId: parent.projectId,
852
+ invocationIds: tasks.flatMap((task) => task.teamRun?.invocations.map((invocation) => invocation.id) ?? []),
853
+ };
804
854
  });
805
- cleanupJobCreated = true;
806
- deleteResult = {
807
- projectId: taskForCleanup.projectId,
808
- cancelledInvocationIds,
809
- };
810
- }
811
- catch (error) {
812
- if (!cleanupJobCreated) {
813
- await prisma.task.updateMany({
814
- where: { id, deletedAt },
815
- data: { deletedAt: null },
816
- }).catch(() => {
817
- // If rollback fails, leave the task hidden rather than masking the original error.
818
- });
819
- }
820
- throw error;
821
- }
822
- for (const invocationId of deleteResult.cancelledInvocationIds) {
823
- defaultTeamLockService.releaseByOwner(invocationId);
824
- }
825
- this.eventBus.emit('task:deleted', {
826
- taskId: id,
827
- projectId: deleteResult.projectId,
828
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 });
829
860
  this.cleanupService?.trigger();
830
861
  return true;
831
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
+ }
832
887
  /**
833
888
  * 获取项目的任务统计
834
889
  */