agent-tower 0.7.4-beta.0 → 0.7.4-beta.2

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 (233) hide show
  1. package/dist/executors/__tests__/codex-mcp-windows.test.d.ts +2 -0
  2. package/dist/executors/__tests__/codex-mcp-windows.test.d.ts.map +1 -0
  3. package/dist/executors/__tests__/codex-mcp-windows.test.js +128 -0
  4. package/dist/executors/__tests__/codex-mcp-windows.test.js.map +1 -0
  5. package/dist/executors/__tests__/codex.executor.test.js +394 -1
  6. package/dist/executors/__tests__/codex.executor.test.js.map +1 -1
  7. package/dist/executors/codex.executor.d.ts +6 -6
  8. package/dist/executors/codex.executor.d.ts.map +1 -1
  9. package/dist/executors/codex.executor.js +118 -32
  10. package/dist/executors/codex.executor.js.map +1 -1
  11. package/dist/mcp/tools/tasks.js +3 -3
  12. package/dist/mcp/tools/tasks.js.map +1 -1
  13. package/dist/mcp/types.js +2 -2
  14. package/dist/mcp/types.js.map +1 -1
  15. package/dist/prompts/task-handoff.d.ts.map +1 -1
  16. package/dist/prompts/task-handoff.js +2 -1
  17. package/dist/prompts/task-handoff.js.map +1 -1
  18. package/dist/routes/__tests__/sessions-runtime.test.js +2 -0
  19. package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -1
  20. package/dist/routes/__tests__/tasks.test.js +41 -0
  21. package/dist/routes/__tests__/tasks.test.js.map +1 -1
  22. package/dist/routes/__tests__/workspace-history.test.d.ts +2 -0
  23. package/dist/routes/__tests__/workspace-history.test.d.ts.map +1 -0
  24. package/dist/routes/__tests__/workspace-history.test.js +174 -0
  25. package/dist/routes/__tests__/workspace-history.test.js.map +1 -0
  26. package/dist/routes/sessions.d.ts.map +1 -1
  27. package/dist/routes/sessions.js +10 -0
  28. package/dist/routes/sessions.js.map +1 -1
  29. package/dist/routes/tasks.d.ts.map +1 -1
  30. package/dist/routes/tasks.js +8 -0
  31. package/dist/routes/tasks.js.map +1 -1
  32. package/dist/runtime/__tests__/acp-process-manager.test.js +65 -0
  33. package/dist/runtime/__tests__/acp-process-manager.test.js.map +1 -1
  34. package/dist/runtime/__tests__/cli-driver-lifecycle.test.js +84 -0
  35. package/dist/runtime/__tests__/cli-driver-lifecycle.test.js.map +1 -1
  36. package/dist/runtime/acp/agents/pi-coding-agent.d.ts.map +1 -1
  37. package/dist/runtime/acp/agents/pi-coding-agent.js +3 -0
  38. package/dist/runtime/acp/agents/pi-coding-agent.js.map +1 -1
  39. package/dist/runtime/cli-driver.js +11 -8
  40. package/dist/runtime/cli-driver.js.map +1 -1
  41. package/dist/services/__tests__/task.service.test.js +128 -13
  42. package/dist/services/__tests__/task.service.test.js.map +1 -1
  43. package/dist/services/__tests__/workspace.service.test.js +95 -5
  44. package/dist/services/__tests__/workspace.service.test.js.map +1 -1
  45. package/dist/services/message-delivery.service.d.ts +1 -0
  46. package/dist/services/message-delivery.service.d.ts.map +1 -1
  47. package/dist/services/session-manager.d.ts +4 -0
  48. package/dist/services/session-manager.d.ts.map +1 -1
  49. package/dist/services/session-manager.js +3 -1
  50. package/dist/services/session-manager.js.map +1 -1
  51. package/dist/services/session-resource-cleanup.d.ts +5 -0
  52. package/dist/services/session-resource-cleanup.d.ts.map +1 -1
  53. package/dist/services/session-resource-cleanup.js +7 -0
  54. package/dist/services/session-resource-cleanup.js.map +1 -1
  55. package/dist/services/task-cleanup.service.d.ts +2 -0
  56. package/dist/services/task-cleanup.service.d.ts.map +1 -1
  57. package/dist/services/task-cleanup.service.js +4 -0
  58. package/dist/services/task-cleanup.service.js.map +1 -1
  59. package/dist/services/task-display-state.d.ts +10 -0
  60. package/dist/services/task-display-state.d.ts.map +1 -0
  61. package/dist/services/task-display-state.js +45 -0
  62. package/dist/services/task-display-state.js.map +1 -0
  63. package/dist/services/task.service.d.ts +20 -1
  64. package/dist/services/task.service.d.ts.map +1 -1
  65. package/dist/services/task.service.js +134 -101
  66. package/dist/services/task.service.js.map +1 -1
  67. package/dist/services/workspace.service.d.ts +10 -7
  68. package/dist/services/workspace.service.d.ts.map +1 -1
  69. package/dist/services/workspace.service.js +162 -89
  70. package/dist/services/workspace.service.js.map +1 -1
  71. package/dist/web/assets/{AgentDemoPage-JXetlNBf.js → AgentDemoPage-CrkoVCOZ.js} +1 -1
  72. package/dist/web/assets/{AgentEnvironmentSettingsPage-CdYyUCwJ.js → AgentEnvironmentSettingsPage-BRihy3_Q.js} +1 -1
  73. package/dist/web/assets/{AttachmentPreview-DMS-PM-1.js → AttachmentPreview-B9z1Mxb-.js} +2 -2
  74. package/dist/web/assets/{BranchSelect-BAq4L2ZS.js → BranchSelect-Bo5uhKCt.js} +1 -1
  75. package/dist/web/assets/{BrandLogo--kWXXJtZ.js → BrandLogo-DEL42B23.js} +1 -1
  76. package/dist/web/assets/{ConversationPage-CvC4RyB5.js → ConversationPage-C46RuN9U.js} +1 -1
  77. package/dist/web/assets/{CreateTaskInput-DjVxCAfc.js → CreateTaskInput-4aHJpsfL.js} +1 -1
  78. package/dist/web/assets/{DemoPage-cDVTGOp8.js → DemoPage-CAZqNag-.js} +1 -1
  79. package/dist/web/assets/{DesignSystemPage-BI54IkKi.js → DesignSystemPage-CjCYC24B.js} +1 -1
  80. package/dist/web/assets/{ExecutionPermissionSelector-f1UYIm1z.js → ExecutionPermissionSelector-CJgc_EVo.js} +1 -1
  81. package/dist/web/assets/{GeneralSettingsPage-C0yto6C_.js → GeneralSettingsPage-DFT7Cubi.js} +1 -1
  82. package/dist/web/assets/{LoadingPreviewPage-BiJSaHUi.js → LoadingPreviewPage-CB4VDPAZ.js} +1 -1
  83. package/dist/web/assets/{McpSettingsPage-BbHwZtyj.js → McpSettingsPage-CgiyGvkH.js} +1 -1
  84. package/dist/web/assets/{MemberAvatar-BLezO3vS.js → MemberAvatar-Cfz8LU6k.js} +1 -1
  85. package/dist/web/assets/MobileTaskDetail-yZNyZMQt.js +3 -0
  86. package/dist/web/assets/{NotificationSettingsPage-CiKoL2Kv.js → NotificationSettingsPage-BSMJ45yx.js} +1 -1
  87. package/dist/web/assets/{ProfileSettingsPage-DCB48cPS.js → ProfileSettingsPage-BwNThOnH.js} +1 -1
  88. package/dist/web/assets/ProjectKanbanPage-DtZW0bOh.js +14 -0
  89. package/dist/web/assets/{ProjectSettingsPage-COvIVq1h.js → ProjectSettingsPage-CMBMkbgI.js} +1 -1
  90. package/dist/web/assets/{ProviderSettingsPage-CPuJcZDg.js → ProviderSettingsPage-D759BSRa.js} +2 -2
  91. package/dist/web/assets/{SettingsMasterDetail-Cv7YaRJB.js → SettingsMasterDetail-DpYUTM-Q.js} +1 -1
  92. package/dist/web/assets/TaskDetail-rRHGOvSw.js +1 -0
  93. package/dist/web/assets/{TaskHandoffPanel-CxH3Mgyd.js → TaskHandoffPanel-DBcC1xQW.js} +24 -24
  94. package/dist/web/assets/{TeamSettingsPage-D-XDefNd.js → TeamSettingsPage-BszQfKp0.js} +1 -1
  95. package/dist/web/assets/{TranslationSettingsPage-BPHI-SZW.js → TranslationSettingsPage-lLaxWCy3.js} +1 -1
  96. package/dist/web/assets/{arc-CC81aLuf.js → arc-DtgXFw4D.js} +1 -1
  97. package/dist/web/assets/{architectureDiagram-3BPJPVTR-DL08XGaN.js → architectureDiagram-3BPJPVTR-B3p3EMX1.js} +1 -1
  98. package/dist/web/assets/{at-sign-DYPIlD6D.js → at-sign-B8rXgKEE.js} +1 -1
  99. package/dist/web/assets/{blockDiagram-GPEHLZMM-B4IW5WxI.js → blockDiagram-GPEHLZMM-BmekCfr0.js} +1 -1
  100. package/dist/web/assets/{c4Diagram-AAUBKEIU-Cj4d_FqK.js → c4Diagram-AAUBKEIU-CF6hJhuL.js} +1 -1
  101. package/dist/web/assets/channel-BdK_fAel.js +1 -0
  102. package/dist/web/assets/check-DwK55QS2.js +1 -0
  103. package/dist/web/assets/{chevron-down-DNhYsNAt.js → chevron-down-Dftc5hr4.js} +1 -1
  104. package/dist/web/assets/{chevron-up-CjrLieBR.js → chevron-up-CKRxN6zX.js} +1 -1
  105. package/dist/web/assets/{chip-Cf92J-pQ.js → chip-ZTBHcXU2.js} +1 -1
  106. package/dist/web/assets/{chunk-2J33WTMH-DeCj8QSD.js → chunk-2J33WTMH-Bjelop4M.js} +1 -1
  107. package/dist/web/assets/{chunk-4BX2VUAB-k-6rzQmQ.js → chunk-4BX2VUAB-B1rJPbL6.js} +1 -1
  108. package/dist/web/assets/{chunk-55IACEB6-qMTCQ5bX.js → chunk-55IACEB6-B5qfH7Gj.js} +1 -1
  109. package/dist/web/assets/{chunk-727SXJPM-CMnu18d1.js → chunk-727SXJPM-ClFMbgrH.js} +1 -1
  110. package/dist/web/assets/{chunk-AQP2D5EJ-C0mFbUMn.js → chunk-AQP2D5EJ-BMFjTO7B.js} +1 -1
  111. package/dist/web/assets/{chunk-FMBD7UC4-u6-_M137.js → chunk-FMBD7UC4-Pr1oOFfR.js} +1 -1
  112. package/dist/web/assets/{chunk-ND2GUHAM-DHrIE_8H.js → chunk-ND2GUHAM-BPm4TRpO.js} +1 -1
  113. package/dist/web/assets/{chunk-QZHKN3VN-aTyC0wZq.js → chunk-QZHKN3VN-DCPA_U0M.js} +1 -1
  114. package/dist/web/assets/classDiagram-4FO5ZUOK-CFzCq0ml.js +1 -0
  115. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-CFzCq0ml.js +1 -0
  116. package/dist/web/assets/{code-block-OCS4YCEC-CBIOQOMH.js → code-block-OCS4YCEC-Cx53F4d7.js} +1 -1
  117. package/dist/web/assets/{confirm-dialog-DWeBZLJW.js → confirm-dialog-C_j-w4TS.js} +1 -1
  118. package/dist/web/assets/{content-JM9E1MeD.js → content-DeIfej_n.js} +2 -2
  119. package/dist/web/assets/{copy-D0gl9kF8.js → copy-B-BaLIlh.js} +1 -1
  120. package/dist/web/assets/{cose-bilkent-S5V4N54A-xLyliSgU.js → cose-bilkent-S5V4N54A-gIys-0yu.js} +1 -1
  121. package/dist/web/assets/{cssMode-DO5UnQ64.js → cssMode-xF0bjbbU.js} +1 -1
  122. package/dist/web/assets/{dagre-BM42HDAG-CbNpoO2d.js → dagre-BM42HDAG-Bwc3Ntc-.js} +1 -1
  123. package/dist/web/assets/{diagram-2AECGRRQ-CG3hKxAX.js → diagram-2AECGRRQ-zkydBsMT.js} +1 -1
  124. package/dist/web/assets/{diagram-5GNKFQAL-mQu9M28s.js → diagram-5GNKFQAL-p9As3QtI.js} +1 -1
  125. package/dist/web/assets/{diagram-KO2AKTUF-BSeEp_Uw.js → diagram-KO2AKTUF-N_pUU-iU.js} +1 -1
  126. package/dist/web/assets/{diagram-LMA3HP47-D6yRdao-.js → diagram-LMA3HP47-CA2rl4Gq.js} +1 -1
  127. package/dist/web/assets/{diagram-OG6HWLK6-BUBUW9wD.js → diagram-OG6HWLK6-BoXxBlUC.js} +1 -1
  128. package/dist/web/assets/{dialog-D1lkhqVL.js → dialog-CnjWIdWp.js} +1 -1
  129. package/dist/web/assets/{erDiagram-TEJ5UH35-BQSZgRqq.js → erDiagram-TEJ5UH35-CVBHQTCN.js} +1 -1
  130. package/dist/web/assets/{file-BijjwxRR.js → file-jMLGlXos.js} +1 -1
  131. package/dist/web/assets/{flowDiagram-I6XJVG4X-D-eqyr5Y.js → flowDiagram-I6XJVG4X-kK7iKiAv.js} +1 -1
  132. package/dist/web/assets/{folder-picker-Czxm0_S9.js → folder-picker-CR8LiSQf.js} +1 -1
  133. package/dist/web/assets/{freemarker2-B4RvEa_U.js → freemarker2-CTMcTiBd.js} +1 -1
  134. package/dist/web/assets/{ganttDiagram-6RSMTGT7-BWGkCt_x.js → ganttDiagram-6RSMTGT7-CJVvLTkW.js} +1 -1
  135. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-DGVc49pR.js → gitGraphDiagram-PVQCEYII-D8uj_SCw.js} +1 -1
  136. package/dist/web/assets/{globe-Ce01-45x.js → globe-FFVKce9-.js} +1 -1
  137. package/dist/web/assets/{handlebars-oyT7drqS.js → handlebars-NHxbmOs3.js} +1 -1
  138. package/dist/web/assets/{html-CxxWf2Qk.js → html-oFqzz32q.js} +1 -1
  139. package/dist/web/assets/{htmlMode-RfAHh5Jj.js → htmlMode-sgeetciK.js} +1 -1
  140. package/dist/web/assets/index-408ws6FY.css +1 -0
  141. package/dist/web/assets/index-9KNEuwGB.js +122 -0
  142. package/dist/web/assets/{index-hpHE1jXY.js → index-BzqyZD7L.js} +1 -1
  143. package/dist/web/assets/{index-DWrSDMX2.js → index-C5lNOKJb.js} +1 -1
  144. package/dist/web/assets/{index-Disv1kQS.js → index-C9vIrQ3Q.js} +1 -1
  145. package/dist/web/assets/{index-Pt4TsHjm.js → index-Cm_7q6ju.js} +1 -1
  146. package/dist/web/assets/{index-Dh-tgeaL.js → index-CwfWGzxi.js} +4 -4
  147. package/dist/web/assets/{index-DyJfPc4O.js → index-D8TNxwWk.js} +1 -1
  148. package/dist/web/assets/{index-T38iVLEb.js → index-biTPUXAe.js} +1 -1
  149. package/dist/web/assets/{index-BMZFF1UJ.js → index-gqrRO6ZJ.js} +1 -1
  150. package/dist/web/assets/{infoDiagram-5YYISTIA-DLPs3dV_.js → infoDiagram-5YYISTIA-CExRZE3Y.js} +1 -1
  151. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-d_OrnwDY.js → ishikawaDiagram-YF4QCWOH-CsqM2X33.js} +1 -1
  152. package/dist/web/assets/javascript-COt-61wn.js +1 -0
  153. package/dist/web/assets/{journeyDiagram-JHISSGLW-EtU1st_0.js → journeyDiagram-JHISSGLW-DX9OMhPD.js} +1 -1
  154. package/dist/web/assets/{jsonMode-Bqh5vIxx.js → jsonMode-VQg5cpLV.js} +1 -1
  155. package/dist/web/assets/{kanban-definition-UN3LZRKU-ZWzUnsFK.js → kanban-definition-UN3LZRKU-CWHMx6vc.js} +1 -1
  156. package/dist/web/assets/{kbd-IjDGsZ2B.js → kbd-GGofKfKu.js} +1 -1
  157. package/dist/web/assets/{key-round-CbnAuUxp.js → key-round-p-Fpykfj.js} +1 -1
  158. package/dist/web/assets/{layers-Pdy9vZ6K.js → layers-pbPa_eZq.js} +1 -1
  159. package/dist/web/assets/{linear-D7n23wcA.js → linear-9VwGhaEA.js} +1 -1
  160. package/dist/web/assets/{link-Bl1ZbjFh.js → link-CG4UwIS1.js} +1 -1
  161. package/dist/web/assets/{liquid-Ds47iVLN.js → liquid-SJInUaiB.js} +1 -1
  162. package/dist/web/assets/{lspLanguageFeatures-Dcch3bsp.js → lspLanguageFeatures-DJOdVyBc.js} +1 -1
  163. package/dist/web/assets/{mdx-CM7ky-2B.js → mdx-CToYr-3b.js} +1 -1
  164. package/dist/web/assets/{menu-DubntFHq.js → menu-BFQ1mKU7.js} +1 -1
  165. package/dist/web/assets/{mermaid-NOHMQCX5-nBsnjgeU.js → mermaid-NOHMQCX5-BOViobLk.js} +58 -58
  166. package/dist/web/assets/{message-square-BE-pW0m_.js → message-square-BYZsUsLz.js} +1 -1
  167. package/dist/web/assets/{mindmap-definition-RKZ34NQL-ClHORNTt.js → mindmap-definition-RKZ34NQL-BTcDnn6X.js} +1 -1
  168. package/dist/web/assets/{modal-CHLtZWvx.js → modal-BT_7Lhtw.js} +1 -1
  169. package/dist/web/assets/{monaco-BbAmalE2.js → monaco-DUftz48_.js} +3 -3
  170. package/dist/web/assets/{nav-row-DH7rNq1V.js → nav-row-BjDckaPC.js} +1 -1
  171. package/dist/web/assets/{paperclip-YP3Bk1lI.js → paperclip-CFAH5neE.js} +1 -1
  172. package/dist/web/assets/{pencil-BFDuhbtW.js → pencil-Bg9TWm_-.js} +1 -1
  173. package/dist/web/assets/{people-DEeNaaLt.js → people-BIQwajM2.js} +1 -1
  174. package/dist/web/assets/{pieDiagram-4H26LBE5-BUOaKIyP.js → pieDiagram-4H26LBE5-CRjqdnP8.js} +1 -1
  175. package/dist/web/assets/{popover-CUmBEXxL.js → popover-DpYhrrCE.js} +1 -1
  176. package/dist/web/assets/{python-BODTtU6m.js → python-DbmUucgH.js} +1 -1
  177. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-Caezg-U2.js → quadrantDiagram-W4KKPZXB-uvA8TYC6.js} +1 -1
  178. package/dist/web/assets/{razor-CHm8DY1p.js → razor-B0AJhw7W.js} +1 -1
  179. package/dist/web/assets/{requirementDiagram-4Y6WPE33-BtdP3Ka6.js → requirementDiagram-4Y6WPE33-BWLugaKX.js} +1 -1
  180. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-Bv74oA-n.js → sankeyDiagram-5OEKKPKP-DHrbZZv8.js} +1 -1
  181. package/dist/web/assets/{search-ClmOQd4H.js → search-BH8nze-X.js} +1 -1
  182. package/dist/web/assets/{select-BB7QG08o.js → select-BegY9RkP.js} +1 -1
  183. package/dist/web/assets/{select-CYtYPr-c.js → select-EcEr5QSS.js} +1 -1
  184. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-DuNPl-dg.js → sequenceDiagram-3UESZ5HK-CKwEKcTn.js} +1 -1
  185. package/dist/web/assets/{server-bEB_DY55.js → server-BxEUjx0l.js} +1 -1
  186. package/dist/web/assets/{stateDiagram-AJRCARHV-B72HAgBJ.js → stateDiagram-AJRCARHV-vhgmSpa9.js} +1 -1
  187. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-C0z5OTY4.js +1 -0
  188. package/dist/web/assets/{textarea-BqYPvoO-.js → textarea-CvoGGROs.js} +1 -1
  189. package/dist/web/assets/{timeline-definition-PNZ67QCA-DTmHUgmQ.js → timeline-definition-PNZ67QCA-bSDKcwDZ.js} +1 -1
  190. package/dist/web/assets/{toggles-a5fkwT_D.js → toggles-CEDUD08u.js} +1 -1
  191. package/dist/web/assets/{tooltip-DuyJMci9.js → tooltip-CKaUbVZ7.js} +1 -1
  192. package/dist/web/assets/{track-DCjyrv3n.js → track-1qw2cvTy.js} +1 -1
  193. package/dist/web/assets/{trash-2-CIyBqaVy.js → trash-2-ChiXon87.js} +1 -1
  194. package/dist/web/assets/{tsMode-C1UXAkMh.js → tsMode-bnV8W2Ru.js} +1 -1
  195. package/dist/web/assets/{typescript-Biht2wHb.js → typescript-d1MHIeEx.js} +1 -1
  196. package/dist/web/assets/{upload-D05XS5-G.js → upload-CQ-cQaOz.js} +1 -1
  197. package/dist/web/assets/{use-profiles-CrWTyz1G.js → use-profiles-CJzoqarX.js} +1 -1
  198. package/dist/web/assets/{use-providers-Dv1Nk6s7.js → use-providers-S29TCag2.js} +1 -1
  199. package/dist/web/assets/{use-translation-settings-CcUnipVO.js → use-translation-settings-DifNJaCu.js} +1 -1
  200. package/dist/web/assets/{useNormalizedLogs-D9W1cT4R.js → useNormalizedLogs-BVdY8qDk.js} +1 -1
  201. package/dist/web/assets/{useTokenUsage-CZ5mDshD.js → useTokenUsage-nh_KDA3q.js} +1 -1
  202. package/dist/web/assets/{vennDiagram-CIIHVFJN-ByoNydXe.js → vennDiagram-CIIHVFJN-D7QbDYzC.js} +1 -1
  203. package/dist/web/assets/{wardley-L42UT6IY-BqdMkOLl.js → wardley-L42UT6IY-C9aHlNVa.js} +1 -1
  204. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-AO22HrEF.js → wardleyDiagram-YWT4CUSO-CZhubDRC.js} +1 -1
  205. package/dist/web/assets/{xml-NLGqNFDv.js → xml-DAKKxHAr.js} +1 -1
  206. package/dist/web/assets/{xychartDiagram-2RQKCTM6-vzPgjgqW.js → xychartDiagram-2RQKCTM6-BgDlpFBh.js} +1 -1
  207. package/dist/web/assets/{yaml-AWlGS_Ig.js → yaml-D_hSVVz0.js} +1 -1
  208. package/dist/web/index.html +2 -2
  209. package/node_modules/@agent-tower/shared/dist/index.d.ts +1 -0
  210. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  211. package/node_modules/@agent-tower/shared/dist/index.js +1 -0
  212. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  213. package/node_modules/@agent-tower/shared/dist/task-hierarchy.d.ts +17 -0
  214. package/node_modules/@agent-tower/shared/dist/task-hierarchy.d.ts.map +1 -0
  215. package/node_modules/@agent-tower/shared/dist/task-hierarchy.js +12 -0
  216. package/node_modules/@agent-tower/shared/dist/task-hierarchy.js.map +1 -0
  217. package/node_modules/@agent-tower/shared/dist/types.d.ts +20 -4
  218. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  219. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  220. package/package.json +1 -1
  221. package/prisma/migrations/20261027000000_preserve_workspace_history/migration.sql +2 -0
  222. package/prisma/schema.prisma +2 -0
  223. package/dist/web/assets/MobileTaskDetail-M_Kke9rL.js +0 -3
  224. package/dist/web/assets/ProjectKanbanPage-soDqevjQ.js +0 -12
  225. package/dist/web/assets/TaskDetail-CqhPxvg3.js +0 -1
  226. package/dist/web/assets/channel-CUa8oyzC.js +0 -1
  227. package/dist/web/assets/check-DaZqOL8l.js +0 -1
  228. package/dist/web/assets/classDiagram-4FO5ZUOK-BAx111NO.js +0 -1
  229. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-BAx111NO.js +0 -1
  230. package/dist/web/assets/index-BN53vGxp.css +0 -1
  231. package/dist/web/assets/index-Cc3qXMhU.js +0 -122
  232. package/dist/web/assets/javascript-BNXtViSL.js +0 -1
  233. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Bu3l_CgB.js +0 -1
@@ -0,0 +1,45 @@
1
+ import prismaPkg from '@prisma/client';
2
+ import { TaskStatus } from '@agent-tower/shared';
3
+ const { Prisma } = prismaPkg;
4
+ // Correlated to the outer `task` row. The supported hierarchy has three levels;
5
+ // use the parentTaskId index, including children outside the requested page.
6
+ // Keep project/status checks in the projection: putting them in WHERE makes
7
+ // SQLite prefer the project/status index and scan the project for every task.
8
+ function descendantCount(status) {
9
+ return Prisma.sql `(
10
+ SELECT COALESCE(SUM(CASE
11
+ WHEN descendant."projectId" = task."projectId" AND descendant."status" = ${status} THEN 1
12
+ ELSE 0
13
+ END), 0) FROM "Task" descendant
14
+ WHERE descendant."deletedAt" IS NULL
15
+ AND descendant."parentTaskId" IN (
16
+ SELECT task."id"
17
+ UNION ALL
18
+ SELECT CASE WHEN child."projectId" = task."projectId" THEN child."id" END FROM "Task" child
19
+ WHERE child."parentTaskId" = task."id"
20
+ AND child."deletedAt" IS NULL
21
+ )
22
+ )`;
23
+ }
24
+ const runningDescendantCount = descendantCount(TaskStatus.IN_PROGRESS);
25
+ const reviewDescendantCount = descendantCount(TaskStatus.IN_REVIEW);
26
+ // Keep the persisted status for writes and runtime lifecycle decisions.
27
+ // Reads, filters and statistics share this display-only expression.
28
+ export const taskDisplayStatusSql = Prisma.sql `CASE
29
+ WHEN task."status" = ${TaskStatus.IN_PROGRESS} OR ${runningDescendantCount} > 0 THEN ${TaskStatus.IN_PROGRESS}
30
+ WHEN task."status" = ${TaskStatus.IN_REVIEW} OR ${reviewDescendantCount} > 0 THEN ${TaskStatus.IN_REVIEW}
31
+ ELSE task."status"
32
+ END`;
33
+ export const taskDisplayColumnsSql = Prisma.sql `
34
+ ${taskDisplayStatusSql} AS "displayStatus",
35
+ ${runningDescendantCount} AS "runningDescendantCount",
36
+ ${reviewDescendantCount} AS "reviewDescendantCount"
37
+ `;
38
+ export function mapTaskDisplayState(row) {
39
+ return {
40
+ displayStatus: row.displayStatus,
41
+ runningDescendantCount: Number(row.runningDescendantCount),
42
+ reviewDescendantCount: Number(row.reviewDescendantCount),
43
+ };
44
+ }
45
+ //# sourceMappingURL=task-display-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-display-state.js","sourceRoot":"","sources":["../../src/services/task-display-state.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAyB,MAAM,qBAAqB,CAAC;AAExE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;AAE7B,gFAAgF;AAChF,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,SAAS,eAAe,CAAC,MAAkB;IACzC,OAAO,MAAM,CAAC,GAAG,CAAA;;iFAE8D,MAAM;;;;;;;;;;;IAWnF,CAAC;AACL,CAAC;AAED,MAAM,sBAAsB,GAAG,eAAe,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACvE,MAAM,qBAAqB,GAAG,eAAe,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;AAEpE,wEAAwE;AACxE,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAA;yBACrB,UAAU,CAAC,WAAW,OAAO,sBAAsB,aAAa,UAAU,CAAC,WAAW;yBACtF,UAAU,CAAC,SAAS,OAAO,qBAAqB,aAAa,UAAU,CAAC,SAAS;;IAEtG,CAAC;AAEL,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAA;IAC3C,oBAAoB;IACpB,sBAAsB;IACtB,qBAAqB;CACxB,CAAC;AAQF,MAAM,UAAU,mBAAmB,CAAC,GAAmB;IACrD,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,sBAAsB,EAAE,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC;QAC1D,qBAAqB,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;KACzD,CAAC;AACJ,CAAC"}
@@ -5,6 +5,7 @@ import type { SessionManager } from './session-manager.js';
5
5
  import type { TaskCleanupService } from './task-cleanup.service.js';
6
6
  import type { WorkspaceBackgroundService } from './workspace-background-service.service.js';
7
7
  import type { CreateTaskInput, TaskBoardResponse } from '@agent-tower/shared';
8
+ import { type TaskDeletionScope } from '@agent-tower/shared';
8
9
  interface UpdateTaskInput {
9
10
  title?: string;
10
11
  description?: string;
@@ -27,6 +28,8 @@ export declare class TaskService {
27
28
  private readonly cleanupService;
28
29
  private readonly backgroundService?;
29
30
  constructor(eventBus: EventBus, sessionManager: SessionManager, cleanupService?: Pick<TaskCleanupService, 'trigger'> | undefined, backgroundService?: Pick<WorkspaceBackgroundService, "stopAllForWorkspace"> | undefined);
31
+ /** Page and count using the same display status, before loading associations. */
32
+ private findPage;
30
33
  /**
31
34
  * Compact board read model. Query count remains fixed as project/task counts grow.
32
35
  */
@@ -36,9 +39,13 @@ export declare class TaskService {
36
39
  */
37
40
  findByProjectId(projectId: string, params?: FindTasksParams): Promise<{
38
41
  data: (Omit<Omit<{
42
+ subtaskCount: number;
43
+ displayStatus: import("@agent-tower/shared").TaskStatus;
44
+ runningDescendantCount: number;
45
+ reviewDescendantCount: number;
46
+ status: TaskStatus;
39
47
  id: string;
40
48
  createdAt: Date;
41
- status: string;
42
49
  priority: number;
43
50
  updatedAt: Date;
44
51
  project: {
@@ -96,7 +103,9 @@ export declare class TaskService {
96
103
  commitMessage: string | null;
97
104
  previewTarget: string | null;
98
105
  hibernatedAt: Date | null;
106
+ cleanedAt: Date | null;
99
107
  })[];
108
+ ancestorIds: string[];
100
109
  }, "project" | "handoff"> & {
101
110
  project: {
102
111
  name: string;
@@ -193,7 +202,9 @@ export declare class TaskService {
193
202
  commitMessage: string | null;
194
203
  previewTarget: string | null;
195
204
  hibernatedAt: Date | null;
205
+ cleanedAt: Date | null;
196
206
  })[];
207
+ ancestorIds: string[];
197
208
  }, "project" | "handoff"> & {
198
209
  project: {
199
210
  name: string;
@@ -219,6 +230,11 @@ export declare class TaskService {
219
230
  titlePreview: string;
220
231
  contentPreview: string | undefined;
221
232
  isTruncated: boolean;
233
+ } & {
234
+ displayStatus: import("@agent-tower/shared").TaskStatus;
235
+ runningDescendantCount: number;
236
+ reviewDescendantCount: number;
237
+ status: TaskStatus;
222
238
  }>;
223
239
  findSubtasks(id: string, params?: FindTasksParams): Promise<TaskBoardResponse>;
224
240
  findBodyById(id: string): Promise<{
@@ -236,6 +252,7 @@ export declare class TaskService {
236
252
  * - 自动计算 position(同状态下最大 position + 1)
237
253
  */
238
254
  create(projectId: string, input: CreateTaskInput): Promise<{
255
+ ancestorIds: string[];
239
256
  project: {
240
257
  isGitRepo: boolean;
241
258
  worktreeReady: boolean;
@@ -413,6 +430,7 @@ export declare class TaskService {
413
430
  * 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
414
431
  */
415
432
  delete(id: string, expectedSubtaskCount?: number): Promise<boolean>;
433
+ getDeletionScope(id: string): Promise<TaskDeletionScope>;
416
434
  getCleanupStatus(taskId: string): Promise<{
417
435
  status: string;
418
436
  lastError: string | null;
@@ -482,6 +500,7 @@ export declare class TaskService {
482
500
  projectId: string;
483
501
  parentTaskId: string | null;
484
502
  }>;
503
+ private withDisplayState;
485
504
  /**
486
505
  * 发射 task:updated 事件,通知 SocketGateway 转发到前端
487
506
  */
@@ -1 +1 @@
1
- {"version":3,"file":"task.service.d.ts","sourceRoot":"","sources":["../../src/services/task.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task.service.d.ts","sourceRoot":"","sources":["../../src/services/task.service.ts"],"names":[],"mappings":"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"}
@@ -8,12 +8,15 @@ import { defaultWorkspaceLifecycleBarrier } from './workspace-lifecycle-barrier.
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
+ import { taskDisplayColumnsSql, taskDisplayStatusSql, mapTaskDisplayState } from './task-display-state.js';
13
14
  const { Prisma } = prismaPkg;
14
15
  const taskBoardColumns = Prisma.sql `
15
16
  task."id", task."projectId", task."title", task."status", task."priority",
16
17
  task."position", task."updatedAt", task."parentTaskId",
18
+ ${taskDisplayColumnsSql},
19
+ (SELECT parent."parentTaskId" FROM "Task" parent WHERE parent."id" = task."parentTaskId") AS "grandparentTaskId",
17
20
  (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL) AS "subtaskCount",
18
21
  (SELECT COUNT(*) FROM "Task" child WHERE child."parentTaskId" = task."id" AND child."deletedAt" IS NULL AND child."status" = 'IN_PROGRESS') AS "runningSubtaskCount"
19
22
  `;
@@ -49,6 +52,19 @@ const visibleSessionSummary = {
49
52
  updatedAt: true,
50
53
  },
51
54
  };
55
+ // The schema stores adjacency; these bounded reads cover the supported three levels.
56
+ function subtreeWhere(id, projectId) {
57
+ return { deletedAt: null, ...(projectId ? { projectId } : {}), OR: [
58
+ { id }, { parentTaskId: id }, { parentTask: { parentTaskId: id, deletedAt: null } },
59
+ ] };
60
+ }
61
+ function ancestorIds(parentTaskId, grandparentTaskId) {
62
+ return [grandparentTaskId, parentTaskId].filter((id) => Boolean(id));
63
+ }
64
+ function withHierarchy(task) {
65
+ const { parentTask, ...metadata } = task;
66
+ return { ...metadata, ancestorIds: ancestorIds(task.parentTaskId, parentTask?.parentTaskId) };
67
+ }
52
68
  function sliceAtCompleteCodePointBoundary(value, maxLength) {
53
69
  let end = 0;
54
70
  for (const character of value) {
@@ -276,20 +292,11 @@ export class TaskService {
276
292
  this.cleanupService = cleanupService;
277
293
  this.backgroundService = backgroundService;
278
294
  }
279
- /**
280
- * Compact board read model. Query count remains fixed as project/task counts grow.
281
- */
282
- async findBoard(params = {}) {
295
+ /** Page and count using the same display status, before loading associations. */
296
+ async findPage(params, includeArchived = false) {
283
297
  const page = Math.max(1, params.page || 1);
284
298
  const limit = Math.min(1000, Math.max(1, params.limit || 1000));
285
299
  const skip = (page - 1) * limit;
286
- const where = {
287
- deletedAt: null,
288
- project: { archivedAt: null },
289
- ...(params.projectId ? { projectId: params.projectId } : {}),
290
- ...(params.parentTaskId ? { parentTaskId: params.parentTaskId } : {}),
291
- ...(params.status ? { status: params.status } : {}),
292
- };
293
300
  const projectFilter = params.projectId
294
301
  ? Prisma.sql `AND task."projectId" = ${params.projectId}`
295
302
  : Prisma.empty;
@@ -297,20 +304,21 @@ export class TaskService {
297
304
  ? Prisma.sql `AND task."parentTaskId" = ${params.parentTaskId}`
298
305
  : Prisma.empty;
299
306
  const statusFilter = params.status
300
- ? Prisma.sql `AND task."status" = ${params.status}`
307
+ ? Prisma.sql `AND (${taskDisplayStatusSql}) = ${params.status}`
301
308
  : Prisma.empty;
302
- const [tasks, total] = await Promise.all([
309
+ const fromWhere = Prisma.sql `
310
+ FROM "Task" task
311
+ INNER JOIN "Project" project ON project."id" = task."projectId"
312
+ WHERE task."deletedAt" IS NULL
313
+ ${includeArchived ? Prisma.empty : Prisma.sql `AND project."archivedAt" IS NULL`}
314
+ ${projectFilter} ${statusFilter} ${parentFilter}
315
+ `;
316
+ const [tasks, counts] = await Promise.all([
303
317
  prisma.$queryRaw(Prisma.sql `
304
318
  SELECT ${taskBoardColumns}
305
- FROM "Task" task
306
- INNER JOIN "Project" project ON project."id" = task."projectId"
307
- WHERE task."deletedAt" IS NULL
308
- AND project."archivedAt" IS NULL
309
- ${projectFilter}
310
- ${statusFilter}
311
- ${parentFilter}
319
+ ${fromWhere}
312
320
  ORDER BY
313
- CASE task."status"
321
+ CASE "displayStatus"
314
322
  WHEN ${TaskStatus.IN_PROGRESS} THEN 0
315
323
  WHEN ${TaskStatus.IN_REVIEW} THEN 1
316
324
  WHEN ${TaskStatus.TODO} THEN 2
@@ -318,22 +326,29 @@ export class TaskService {
318
326
  WHEN ${TaskStatus.CANCELLED} THEN 4
319
327
  ELSE 99
320
328
  END ASC,
321
- CASE WHEN task."status" = ${TaskStatus.IN_REVIEW} THEN task."priority" ELSE 0 END DESC,
329
+ CASE WHEN "displayStatus" = ${TaskStatus.IN_REVIEW} THEN task."priority" ELSE 0 END DESC,
322
330
  task."position" ASC,
323
331
  task."id" ASC
324
332
  LIMIT ${limit} OFFSET ${skip}
325
333
  `),
326
- prisma.task.count({ where }),
334
+ prisma.$queryRaw(Prisma.sql `SELECT COUNT(*) AS total ${fromWhere}`),
327
335
  ]);
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.
336
+ return { tasks, total: Number(counts[0].total), page, limit };
337
+ }
338
+ /**
339
+ * Compact board read model. Query count remains fixed as project/task counts grow.
340
+ */
341
+ async findBoard(params = {}) {
342
+ const { tasks, total, page, limit } = await this.findPage(params);
343
+ // Descendants can rank ahead of their ancestors or fall on another page.
344
+ // Return the complete compact path without changing the page count/limit.
330
345
  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] : []))];
346
+ const missingParentIds = [...new Set(tasks.flatMap((task) => ancestorIds(task.parentTaskId, task.grandparentTaskId).filter(id => !pageIds.has(id))))];
332
347
  const parents = params.parentTaskId || missingParentIds.length === 0 ? []
333
348
  : await prisma.$queryRaw(Prisma.sql `
334
349
  SELECT ${taskBoardColumns} FROM "Task" task
335
350
  INNER JOIN "Project" project ON project."id" = task."projectId"
336
- WHERE task."id" IN (${Prisma.join(missingParentIds)})
351
+ WHERE task."id" IN (SELECT value FROM json_each(${JSON.stringify(missingParentIds)}))
337
352
  AND task."deletedAt" IS NULL AND project."archivedAt" IS NULL
338
353
  `);
339
354
  const allTasks = [...tasks, ...parents];
@@ -405,10 +420,12 @@ export class TaskService {
405
420
  id: task.id,
406
421
  projectId: task.projectId,
407
422
  parentTaskId: task.parentTaskId,
423
+ ancestorIds: ancestorIds(task.parentTaskId, task.grandparentTaskId),
408
424
  subtaskCount: Number(task.subtaskCount),
409
425
  runningSubtaskCount: Number(task.runningSubtaskCount),
410
426
  title: titlePreview,
411
427
  status: task.status,
428
+ ...mapTaskDisplayState(task),
412
429
  priority: task.priority ?? TaskPriority.NORMAL,
413
430
  ...(preferredWorkspace ? {
414
431
  preferredWorkspace: {
@@ -445,60 +462,37 @@ export class TaskService {
445
462
  if (!project) {
446
463
  throw new NotFoundError('Project', projectId);
447
464
  }
448
- const page = Math.max(1, params.page || 1);
449
- const limit = Math.min(1000, Math.max(1, params.limit || 200));
450
- const skip = (page - 1) * limit;
451
- const where = { projectId, deletedAt: null };
452
- if (params.status) {
453
- where.status = params.status;
454
- }
455
- // Active tasks first (IN_PROGRESS → IN_REVIEW → TODO), then completed (DONE → CANCELLED).
456
- // Raw SQL CASE needed because Prisma orderBy doesn't support custom sort functions.
457
- const statusOrder = {
458
- [TaskStatus.IN_PROGRESS]: 0,
459
- [TaskStatus.IN_REVIEW]: 1,
460
- [TaskStatus.TODO]: 2,
461
- [TaskStatus.DONE]: 3,
462
- [TaskStatus.CANCELLED]: 4,
463
- };
464
- const [data, total] = await Promise.all([
465
- prisma.task.findMany({
466
- where,
467
- select: {
468
- id: true,
469
- projectId: true,
470
- title: true,
471
- baseBranch: true,
472
- parentTaskId: true,
473
- status: true,
474
- priority: true,
475
- position: true,
476
- createdAt: true,
477
- updatedAt: true,
478
- workspaces: { include: { sessions: visibleSessionSummary } },
479
- project: true,
480
- },
481
- orderBy: [{ updatedAt: 'desc' }],
482
- skip,
483
- take: limit,
484
- }),
485
- prisma.task.count({ where }),
486
- ]);
487
- data.sort((a, b) => {
488
- const sa = statusOrder[a.status] ?? 99;
489
- const sb = statusOrder[b.status] ?? 99;
490
- if (sa !== sb)
491
- return sa - sb;
492
- if (a.status === TaskStatus.IN_REVIEW) {
493
- const priorityDelta = (b.priority ?? TaskPriority.NORMAL) - (a.priority ?? TaskPriority.NORMAL);
494
- if (priorityDelta !== 0)
495
- return priorityDelta;
496
- }
497
- return (a.position ?? 0) - (b.position ?? 0) || a.id.localeCompare(b.id);
465
+ const { tasks, total, page, limit } = await this.findPage({
466
+ ...params, projectId, limit: params.limit || 200,
467
+ }, true);
468
+ const data = await prisma.task.findMany({
469
+ where: { id: { in: tasks.map(task => task.id) }, deletedAt: null },
470
+ select: {
471
+ id: true,
472
+ projectId: true,
473
+ title: true,
474
+ baseBranch: true,
475
+ parentTaskId: true,
476
+ parentTask: { select: { parentTaskId: true } },
477
+ status: true,
478
+ priority: true,
479
+ position: true,
480
+ createdAt: true,
481
+ updatedAt: true,
482
+ workspaces: { include: { sessions: visibleSessionSummary } },
483
+ project: true,
484
+ },
498
485
  });
486
+ const byId = new Map(data.map(task => [task.id, task]));
499
487
  const projectGitCapability = getStoredProjectGitCapability(project);
500
488
  return {
501
- data: data.map((task) => (withTaskPreviews(withProjectGitCapability(task, projectGitCapability), { omitDescription: true }))),
489
+ data: tasks.flatMap(row => {
490
+ const task = byId.get(row.id);
491
+ return task ? [withTaskPreviews(withProjectGitCapability({
492
+ ...withHierarchy(task), status: row.status, ...mapTaskDisplayState(row),
493
+ subtaskCount: Number(row.subtaskCount),
494
+ }, projectGitCapability), { omitDescription: true })] : [];
495
+ }),
502
496
  total,
503
497
  page,
504
498
  limit,
@@ -518,6 +512,7 @@ export class TaskService {
518
512
  title: true,
519
513
  baseBranch: true,
520
514
  parentTaskId: true,
515
+ parentTask: { select: { parentTaskId: true } },
521
516
  _count: { select: { subtasks: { where: { deletedAt: null } } } },
522
517
  status: true,
523
518
  priority: true,
@@ -531,7 +526,7 @@ export class TaskService {
531
526
  throw new NotFoundError('Task', id);
532
527
  }
533
528
  const { _count, ...metadata } = task;
534
- return withTaskPreviews(withProjectGitMetadata({ ...metadata, subtaskCount: _count.subtasks }), { omitDescription: true });
529
+ return this.withDisplayState(withTaskPreviews(withProjectGitMetadata({ ...withHierarchy(metadata), subtaskCount: _count.subtasks }), { omitDescription: true }));
535
530
  }
536
531
  async findSubtasks(id, params = {}) {
537
532
  const parent = await prisma.task.findFirst({ where: { id, deletedAt: null } });
@@ -592,19 +587,26 @@ export class TaskService {
592
587
  ensureProjectIsMutable(project, 'create tasks');
593
588
  const parent = input.parentTaskId ? await prisma.task.findFirst({
594
589
  where: { id: input.parentTaskId, projectId, deletedAt: null },
595
- include: { workspaces: {
596
- where: { parentWorkspaceId: null, ownerMemberId: null, status: { not: WorkspaceStatus.ABANDONED } },
590
+ include: { parentTask: { select: { parentTaskId: true } }, workspaces: {
591
+ where: { parentWorkspaceId: null, ownerMemberId: null, status: { not: WorkspaceStatus.ABANDONED }, cleanedAt: null },
597
592
  orderBy: { createdAt: 'desc' },
598
593
  } },
599
594
  }) : null;
600
595
  if (input.parentTaskId && !parent)
601
596
  throw new NotFoundError('Parent task', input.parentTaskId);
602
- if (parent?.parentTaskId)
603
- throw new ValidationError('Subtasks cannot have their own subtasks');
597
+ if (parent?.parentTask?.parentTaskId)
598
+ throw new ValidationError('Tasks support at most three levels');
604
599
  const parentWorkspace = parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.ACTIVE)
605
600
  ?? parent?.workspaces.find((workspace) => workspace.status === WorkspaceStatus.HIBERNATED)
606
601
  ?? parent?.workspaces[0];
607
602
  const parentBranch = parentWorkspace?.branchName || parent?.baseBranch;
603
+ // A grandchild may inherit an ancestor branch before its parent starts.
604
+ // Serialize validation/persistence with that branch owner's cleanup as well.
605
+ const requestedParentBranch = input.baseBranch ?? parentBranch;
606
+ const branchOwners = input.baseBranch === null || !requestedParentBranch ? [] : await prisma.workspace.findMany({
607
+ where: { task: { projectId, deletedAt: null }, branchName: requestedParentBranch },
608
+ select: { id: true },
609
+ });
608
610
  const createWithParentBranch = async () => {
609
611
  // Resolve at task creation so delayed starts and TeamRun main workspaces
610
612
  // cannot silently follow a later project default change. Local tasks opt out.
@@ -625,9 +627,12 @@ export class TaskService {
625
627
  if (parent) {
626
628
  const liveParent = await tx.task.findFirst({
627
629
  where: { id: parent.id, projectId, deletedAt: null, project: { archivedAt: null } },
630
+ include: { parentTask: { select: { parentTaskId: true } } },
628
631
  });
629
632
  if (!liveParent)
630
633
  throw new NotFoundError('Parent task', parent.id);
634
+ if (liveParent.parentTask?.parentTaskId)
635
+ throw new ValidationError('Tasks support at most three levels');
631
636
  }
632
637
  return tx.task.create({
633
638
  data: {
@@ -646,6 +651,7 @@ export class TaskService {
646
651
  this.eventBus.emit('task:updated', { taskId: parent.id, projectId, status: parent.status });
647
652
  return {
648
653
  ...created,
654
+ ancestorIds: parent ? ancestorIds(parent.id, parent.parentTaskId) : [],
649
655
  project: {
650
656
  ...project,
651
657
  ...buildProjectGitMetadata(project),
@@ -654,9 +660,7 @@ export class TaskService {
654
660
  };
655
661
  // Serialize branch validation and child persistence with parent workspace
656
662
  // cleanup, so its reference becomes visible before that branch is reclaimed.
657
- return parentWorkspace
658
- ? defaultWorkspaceLifecycleBarrier.withWorkspace(parentWorkspace.id, createWithParentBranch)
659
- : createWithParentBranch();
663
+ return defaultWorkspaceLifecycleBarrier.withWorkspaces([...branchOwners.map(workspace => workspace.id), ...(parentWorkspace ? [parentWorkspace.id] : [])], createWithParentBranch);
660
664
  }
661
665
  /**
662
666
  * 更新任务基本信息
@@ -781,13 +785,14 @@ export class TaskService {
781
785
  if (!taskForGuard || taskForGuard.deletedAt)
782
786
  throw new NotFoundError('Task', id);
783
787
  ensureProjectIsMutable(taskForGuard.project, 'delete tasks');
784
- if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== await prisma.task.count({
785
- where: { parentTaskId: id, deletedAt: null },
786
- })) {
788
+ const familyWhere = subtreeWhere(id, taskForGuard.projectId);
789
+ if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== (await prisma.task.count({
790
+ where: familyWhere,
791
+ })) - 1) {
787
792
  throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
788
793
  }
789
794
  const workspaceIds = await prisma.workspace.findMany({
790
- where: { task: { OR: [{ id }, { parentTaskId: id }], deletedAt: null } },
795
+ where: { task: familyWhere },
791
796
  select: { id: true },
792
797
  });
793
798
  const result = await defaultWorkspaceLifecycleBarrier.withWorkspaces(workspaceIds.map((workspace) => workspace.id), async () => {
@@ -801,13 +806,23 @@ export class TaskService {
801
806
  throw new NotFoundError('Task', id);
802
807
  ensureProjectIsMutable(parent.project, 'delete tasks');
803
808
  const family = await tx.task.findMany({
804
- where: { OR: [{ id }, { parentTaskId: id }], deletedAt: null },
805
- select: { id: true },
809
+ where: familyWhere,
810
+ select: { id: true, parentTaskId: true },
806
811
  });
807
812
  if (expectedSubtaskCount !== undefined && expectedSubtaskCount !== family.length - 1) {
808
813
  throw new ServiceError('Subtasks changed. Review the deletion confirmation again.', 'TASK_DELETE_SCOPE_CHANGED', 409);
809
814
  }
810
- const taskIds = [...family.filter((task) => task.id !== id).map((task) => task.id), id];
815
+ const byId = new Map(family.map(task => [task.id, task]));
816
+ const depth = (task) => {
817
+ let current = task;
818
+ let value = 0;
819
+ while (current.parentTaskId && byId.has(current.parentTaskId) && value < MAX_TASK_DEPTH) {
820
+ current = byId.get(current.parentTaskId);
821
+ value++;
822
+ }
823
+ return value;
824
+ };
825
+ const taskIds = family.sort((a, b) => depth(b) - depth(a) || a.id.localeCompare(b.id)).map(task => task.id);
811
826
  await tx.task.updateMany({ where: { id: { in: taskIds } }, data: { deletedAt: new Date() } });
812
827
  const tasks = await tx.task.findMany({
813
828
  where: { id: { in: taskIds } },
@@ -826,6 +841,7 @@ export class TaskService {
826
841
  workspaces: orderedTasks.flatMap((task) => task.workspaces.map((workspace) => ({
827
842
  id: workspace.id,
828
843
  worktreePath: workspace.worktreePath,
844
+ resourcesCleaned: workspace.cleanedAt != null,
829
845
  workingDir: getWorkspaceWorkingDir(workspace),
830
846
  workspaceKind: workspace.workspaceKind,
831
847
  branchName: workspace.branchName,
@@ -860,6 +876,14 @@ export class TaskService {
860
876
  this.cleanupService?.trigger();
861
877
  return true;
862
878
  }
879
+ async getDeletionScope(id) {
880
+ return prisma.$transaction(async (tx) => {
881
+ const task = await tx.task.findFirst({ where: { id, deletedAt: null }, select: { projectId: true } });
882
+ if (!task)
883
+ throw new NotFoundError('Task', id);
884
+ return { descendantCount: (await tx.task.count({ where: subtreeWhere(id, task.projectId) })) - 1 };
885
+ });
886
+ }
863
887
  async getCleanupStatus(taskId) {
864
888
  const job = await prisma.taskCleanupJob.findFirst({
865
889
  where: { taskId }, orderBy: { createdAt: 'desc' },
@@ -894,11 +918,11 @@ export class TaskService {
894
918
  if (!project) {
895
919
  throw new NotFoundError('Project', projectId);
896
920
  }
897
- const counts = await prisma.task.groupBy({
898
- by: ['status'],
899
- where: { projectId, deletedAt: null },
900
- _count: { id: true },
901
- });
921
+ const counts = await prisma.$queryRaw(Prisma.sql `
922
+ SELECT ${taskDisplayStatusSql} AS "displayStatus", COUNT(*) AS count
923
+ FROM "Task" task WHERE task."projectId" = ${projectId} AND task."deletedAt" IS NULL
924
+ GROUP BY "displayStatus"
925
+ `);
902
926
  const stats = {
903
927
  total: 0,
904
928
  todo: 0,
@@ -908,9 +932,9 @@ export class TaskService {
908
932
  cancelled: 0,
909
933
  };
910
934
  for (const row of counts) {
911
- const count = row._count.id;
935
+ const count = Number(row.count);
912
936
  stats.total += count;
913
- switch (row.status) {
937
+ switch (row.displayStatus) {
914
938
  case TaskStatus.TODO:
915
939
  stats.todo = count;
916
940
  break;
@@ -985,6 +1009,15 @@ export class TaskService {
985
1009
  };
986
1010
  }
987
1011
  // ── 内部方法 ────────────────────────────────────────────────────────────────
1012
+ async withDisplayState(task) {
1013
+ const [state] = await prisma.$queryRaw(Prisma.sql `
1014
+ SELECT task."status", ${taskDisplayColumnsSql} FROM "Task" task
1015
+ WHERE task."id" = ${task.id} AND task."deletedAt" IS NULL
1016
+ `);
1017
+ if (!state)
1018
+ throw new NotFoundError('Task', task.id);
1019
+ return { ...task, status: state.status, ...mapTaskDisplayState(state) };
1020
+ }
988
1021
  /**
989
1022
  * 发射 task:updated 事件,通知 SocketGateway 转发到前端
990
1023
  */