agent-tower 0.5.4 → 0.6.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (406) hide show
  1. package/README.md +3 -1
  2. package/dist/core/event-bus.d.ts +4 -1
  3. package/dist/core/event-bus.d.ts.map +1 -1
  4. package/dist/core/event-bus.js.map +1 -1
  5. package/dist/executors/__tests__/codex.executor.test.js +27 -21
  6. package/dist/executors/__tests__/codex.executor.test.js.map +1 -1
  7. package/dist/executors/__tests__/providers-availability.test.js +41 -1
  8. package/dist/executors/__tests__/providers-availability.test.js.map +1 -1
  9. package/dist/executors/__tests__/providers.test.js +34 -1
  10. package/dist/executors/__tests__/providers.test.js.map +1 -1
  11. package/dist/executors/codex.executor.d.ts.map +1 -1
  12. package/dist/executors/codex.executor.js +4 -0
  13. package/dist/executors/codex.executor.js.map +1 -1
  14. package/dist/executors/default-providers.json +106 -0
  15. package/dist/executors/index.d.ts +1 -1
  16. package/dist/executors/index.d.ts.map +1 -1
  17. package/dist/executors/index.js +33 -14
  18. package/dist/executors/index.js.map +1 -1
  19. package/dist/executors/providers.d.ts +12 -3
  20. package/dist/executors/providers.d.ts.map +1 -1
  21. package/dist/executors/providers.js +45 -18
  22. package/dist/executors/providers.js.map +1 -1
  23. package/dist/output/types.d.ts +32 -2
  24. package/dist/output/types.d.ts.map +1 -1
  25. package/dist/output/types.js +10 -0
  26. package/dist/output/types.js.map +1 -1
  27. package/dist/routes/__tests__/providers.test.js +197 -1
  28. package/dist/routes/__tests__/providers.test.js.map +1 -1
  29. package/dist/routes/__tests__/sessions-runtime.test.d.ts +2 -0
  30. package/dist/routes/__tests__/sessions-runtime.test.d.ts.map +1 -0
  31. package/dist/routes/__tests__/sessions-runtime.test.js +107 -0
  32. package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -0
  33. package/dist/routes/providers.d.ts.map +1 -1
  34. package/dist/routes/providers.js +14 -5
  35. package/dist/routes/providers.js.map +1 -1
  36. package/dist/routes/sessions.d.ts.map +1 -1
  37. package/dist/routes/sessions.js +50 -3
  38. package/dist/routes/sessions.js.map +1 -1
  39. package/dist/routes/system.d.ts.map +1 -1
  40. package/dist/routes/system.js +8 -6
  41. package/dist/routes/system.js.map +1 -1
  42. package/dist/runtime/__tests__/acp-agent-definitions.test.d.ts +2 -0
  43. package/dist/runtime/__tests__/acp-agent-definitions.test.d.ts.map +1 -0
  44. package/dist/runtime/__tests__/acp-agent-definitions.test.js +367 -0
  45. package/dist/runtime/__tests__/acp-agent-definitions.test.js.map +1 -0
  46. package/dist/runtime/__tests__/acp-driver-cleanup.test.d.ts +2 -0
  47. package/dist/runtime/__tests__/acp-driver-cleanup.test.d.ts.map +1 -0
  48. package/dist/runtime/__tests__/acp-driver-cleanup.test.js +41 -0
  49. package/dist/runtime/__tests__/acp-driver-cleanup.test.js.map +1 -0
  50. package/dist/runtime/__tests__/acp-driver-lifecycle.test.d.ts +2 -0
  51. package/dist/runtime/__tests__/acp-driver-lifecycle.test.d.ts.map +1 -0
  52. package/dist/runtime/__tests__/acp-driver-lifecycle.test.js +241 -0
  53. package/dist/runtime/__tests__/acp-driver-lifecycle.test.js.map +1 -0
  54. package/dist/runtime/__tests__/acp-driver.test.d.ts +2 -0
  55. package/dist/runtime/__tests__/acp-driver.test.d.ts.map +1 -0
  56. package/dist/runtime/__tests__/acp-driver.test.js +122 -0
  57. package/dist/runtime/__tests__/acp-driver.test.js.map +1 -0
  58. package/dist/runtime/__tests__/acp-history-reconciler.test.d.ts +2 -0
  59. package/dist/runtime/__tests__/acp-history-reconciler.test.d.ts.map +1 -0
  60. package/dist/runtime/__tests__/acp-history-reconciler.test.js +69 -0
  61. package/dist/runtime/__tests__/acp-history-reconciler.test.js.map +1 -0
  62. package/dist/runtime/__tests__/acp-process-manager.test.d.ts +2 -0
  63. package/dist/runtime/__tests__/acp-process-manager.test.d.ts.map +1 -0
  64. package/dist/runtime/__tests__/acp-process-manager.test.js +51 -0
  65. package/dist/runtime/__tests__/acp-process-manager.test.js.map +1 -0
  66. package/dist/runtime/__tests__/acp-projector.test.d.ts +2 -0
  67. package/dist/runtime/__tests__/acp-projector.test.d.ts.map +1 -0
  68. package/dist/runtime/__tests__/acp-projector.test.js +195 -0
  69. package/dist/runtime/__tests__/acp-projector.test.js.map +1 -0
  70. package/dist/runtime/__tests__/runtime-coordinator.test.d.ts +2 -0
  71. package/dist/runtime/__tests__/runtime-coordinator.test.d.ts.map +1 -0
  72. package/dist/runtime/__tests__/runtime-coordinator.test.js +175 -0
  73. package/dist/runtime/__tests__/runtime-coordinator.test.js.map +1 -0
  74. package/dist/runtime/acp/acp-driver.d.ts +7 -0
  75. package/dist/runtime/acp/acp-driver.d.ts.map +1 -0
  76. package/dist/runtime/acp/acp-driver.js +441 -0
  77. package/dist/runtime/acp/acp-driver.js.map +1 -0
  78. package/dist/runtime/acp/agents/claude-code.d.ts +3 -0
  79. package/dist/runtime/acp/agents/claude-code.d.ts.map +1 -0
  80. package/dist/runtime/acp/agents/claude-code.js +147 -0
  81. package/dist/runtime/acp/agents/claude-code.js.map +1 -0
  82. package/dist/runtime/acp/agents/codex.d.ts +3 -0
  83. package/dist/runtime/acp/agents/codex.d.ts.map +1 -0
  84. package/dist/runtime/acp/agents/codex.js +70 -0
  85. package/dist/runtime/acp/agents/codex.js.map +1 -0
  86. package/dist/runtime/acp/agents/cursor-agent.d.ts +2 -0
  87. package/dist/runtime/acp/agents/cursor-agent.d.ts.map +1 -0
  88. package/dist/runtime/acp/agents/cursor-agent.js +12 -0
  89. package/dist/runtime/acp/agents/cursor-agent.js.map +1 -0
  90. package/dist/runtime/acp/agents/executable-resolution.d.ts +6 -0
  91. package/dist/runtime/acp/agents/executable-resolution.d.ts.map +1 -0
  92. package/dist/runtime/acp/agents/executable-resolution.js +72 -0
  93. package/dist/runtime/acp/agents/executable-resolution.js.map +1 -0
  94. package/dist/runtime/acp/agents/gemini-cli.d.ts +2 -0
  95. package/dist/runtime/acp/agents/gemini-cli.d.ts.map +1 -0
  96. package/dist/runtime/acp/agents/gemini-cli.js +13 -0
  97. package/dist/runtime/acp/agents/gemini-cli.js.map +1 -0
  98. package/dist/runtime/acp/agents/grok-build.d.ts +2 -0
  99. package/dist/runtime/acp/agents/grok-build.d.ts.map +1 -0
  100. package/dist/runtime/acp/agents/grok-build.js +26 -0
  101. package/dist/runtime/acp/agents/grok-build.js.map +1 -0
  102. package/dist/runtime/acp/agents/kiro-cli.d.ts +2 -0
  103. package/dist/runtime/acp/agents/kiro-cli.d.ts.map +1 -0
  104. package/dist/runtime/acp/agents/kiro-cli.js +18 -0
  105. package/dist/runtime/acp/agents/kiro-cli.js.map +1 -0
  106. package/dist/runtime/acp/agents/managed-directory.d.ts +6 -0
  107. package/dist/runtime/acp/agents/managed-directory.d.ts.map +1 -0
  108. package/dist/runtime/acp/agents/managed-directory.js +31 -0
  109. package/dist/runtime/acp/agents/managed-directory.js.map +1 -0
  110. package/dist/runtime/acp/agents/minion-acp-sitecustomize.d.ts +2 -0
  111. package/dist/runtime/acp/agents/minion-acp-sitecustomize.d.ts.map +1 -0
  112. package/dist/runtime/acp/agents/minion-acp-sitecustomize.js +118 -0
  113. package/dist/runtime/acp/agents/minion-acp-sitecustomize.js.map +1 -0
  114. package/dist/runtime/acp/agents/minion-code.d.ts +2 -0
  115. package/dist/runtime/acp/agents/minion-code.d.ts.map +1 -0
  116. package/dist/runtime/acp/agents/minion-code.js +53 -0
  117. package/dist/runtime/acp/agents/minion-code.js.map +1 -0
  118. package/dist/runtime/acp/agents/native-agent.d.ts +23 -0
  119. package/dist/runtime/acp/agents/native-agent.d.ts.map +1 -0
  120. package/dist/runtime/acp/agents/native-agent.js +158 -0
  121. package/dist/runtime/acp/agents/native-agent.js.map +1 -0
  122. package/dist/runtime/acp/agents/opencode.d.ts +2 -0
  123. package/dist/runtime/acp/agents/opencode.d.ts.map +1 -0
  124. package/dist/runtime/acp/agents/opencode.js +35 -0
  125. package/dist/runtime/acp/agents/opencode.js.map +1 -0
  126. package/dist/runtime/acp/agents/pi-coding-agent.d.ts +3 -0
  127. package/dist/runtime/acp/agents/pi-coding-agent.d.ts.map +1 -0
  128. package/dist/runtime/acp/agents/pi-coding-agent.js +180 -0
  129. package/dist/runtime/acp/agents/pi-coding-agent.js.map +1 -0
  130. package/dist/runtime/acp/agents/qwen-code.d.ts +3 -0
  131. package/dist/runtime/acp/agents/qwen-code.d.ts.map +1 -0
  132. package/dist/runtime/acp/agents/qwen-code.js +67 -0
  133. package/dist/runtime/acp/agents/qwen-code.js.map +1 -0
  134. package/dist/runtime/acp/agents/registry.d.ts +5 -0
  135. package/dist/runtime/acp/agents/registry.d.ts.map +1 -0
  136. package/dist/runtime/acp/agents/registry.js +34 -0
  137. package/dist/runtime/acp/agents/registry.js.map +1 -0
  138. package/dist/runtime/acp/agents/types.d.ts +35 -0
  139. package/dist/runtime/acp/agents/types.d.ts.map +1 -0
  140. package/dist/runtime/acp/agents/types.js +2 -0
  141. package/dist/runtime/acp/agents/types.js.map +1 -0
  142. package/dist/runtime/acp/codex-provider-config.d.ts +9 -0
  143. package/dist/runtime/acp/codex-provider-config.d.ts.map +1 -0
  144. package/dist/runtime/acp/codex-provider-config.js +107 -0
  145. package/dist/runtime/acp/codex-provider-config.js.map +1 -0
  146. package/dist/runtime/acp/history-reconciler.d.ts +8 -0
  147. package/dist/runtime/acp/history-reconciler.d.ts.map +1 -0
  148. package/dist/runtime/acp/history-reconciler.js +82 -0
  149. package/dist/runtime/acp/history-reconciler.js.map +1 -0
  150. package/dist/runtime/acp/process-manager.d.ts +33 -0
  151. package/dist/runtime/acp/process-manager.d.ts.map +1 -0
  152. package/dist/runtime/acp/process-manager.js +204 -0
  153. package/dist/runtime/acp/process-manager.js.map +1 -0
  154. package/dist/runtime/acp/projector.d.ts +23 -0
  155. package/dist/runtime/acp/projector.d.ts.map +1 -0
  156. package/dist/runtime/acp/projector.js +354 -0
  157. package/dist/runtime/acp/projector.js.map +1 -0
  158. package/dist/runtime/cli-driver.d.ts +7 -0
  159. package/dist/runtime/cli-driver.d.ts.map +1 -0
  160. package/dist/runtime/cli-driver.js +282 -0
  161. package/dist/runtime/cli-driver.js.map +1 -0
  162. package/dist/runtime/cli-parser.d.ts +5 -0
  163. package/dist/runtime/cli-parser.d.ts.map +1 -0
  164. package/dist/runtime/cli-parser.js +15 -0
  165. package/dist/runtime/cli-parser.js.map +1 -0
  166. package/dist/runtime/contracts.d.ts +113 -0
  167. package/dist/runtime/contracts.d.ts.map +1 -0
  168. package/dist/runtime/contracts.js +2 -0
  169. package/dist/runtime/contracts.js.map +1 -0
  170. package/dist/runtime/errors.d.ts +9 -0
  171. package/dist/runtime/errors.d.ts.map +1 -0
  172. package/dist/runtime/errors.js +29 -0
  173. package/dist/runtime/errors.js.map +1 -0
  174. package/dist/runtime/index.d.ts +9 -0
  175. package/dist/runtime/index.d.ts.map +1 -0
  176. package/dist/runtime/index.js +9 -0
  177. package/dist/runtime/index.js.map +1 -0
  178. package/dist/runtime/runtime-coordinator.d.ts +33 -0
  179. package/dist/runtime/runtime-coordinator.d.ts.map +1 -0
  180. package/dist/runtime/runtime-coordinator.js +347 -0
  181. package/dist/runtime/runtime-coordinator.js.map +1 -0
  182. package/dist/runtime/runtime-registry.d.ts +8 -0
  183. package/dist/runtime/runtime-registry.d.ts.map +1 -0
  184. package/dist/runtime/runtime-registry.js +17 -0
  185. package/dist/runtime/runtime-registry.js.map +1 -0
  186. package/dist/runtime/runtime-state-view.d.ts +5 -0
  187. package/dist/runtime/runtime-state-view.d.ts.map +1 -0
  188. package/dist/runtime/runtime-state-view.js +15 -0
  189. package/dist/runtime/runtime-state-view.js.map +1 -0
  190. package/dist/services/__tests__/provider-config.service.test.js +31 -2
  191. package/dist/services/__tests__/provider-config.service.test.js.map +1 -1
  192. package/dist/services/__tests__/session-manager.lifecycle.test.js +107 -2
  193. package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
  194. package/dist/services/__tests__/session-manager.team-run.test.js +22 -14
  195. package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -1
  196. package/dist/services/conversation.service.d.ts.map +1 -1
  197. package/dist/services/conversation.service.js +3 -0
  198. package/dist/services/conversation.service.js.map +1 -1
  199. package/dist/services/mcp-config.integration.test.d.ts +2 -0
  200. package/dist/services/mcp-config.integration.test.d.ts.map +1 -0
  201. package/dist/services/mcp-config.integration.test.js +75 -0
  202. package/dist/services/mcp-config.integration.test.js.map +1 -0
  203. package/dist/services/mcp-config.service.d.ts +10 -3
  204. package/dist/services/mcp-config.service.d.ts.map +1 -1
  205. package/dist/services/mcp-config.service.js +70 -18
  206. package/dist/services/mcp-config.service.js.map +1 -1
  207. package/dist/services/mcp-config.service.test.js +44 -13
  208. package/dist/services/mcp-config.service.test.js.map +1 -1
  209. package/dist/services/provider-config.service.d.ts.map +1 -1
  210. package/dist/services/provider-config.service.js +34 -5
  211. package/dist/services/provider-config.service.js.map +1 -1
  212. package/dist/services/provider-effective-connection.service.d.ts +7 -1
  213. package/dist/services/provider-effective-connection.service.d.ts.map +1 -1
  214. package/dist/services/provider-effective-connection.service.js +56 -1
  215. package/dist/services/provider-effective-connection.service.js.map +1 -1
  216. package/dist/services/session-manager.d.ts +32 -24
  217. package/dist/services/session-manager.d.ts.map +1 -1
  218. package/dist/services/session-manager.js +280 -445
  219. package/dist/services/session-manager.js.map +1 -1
  220. package/dist/services/team-reconciler.service.d.ts +3 -0
  221. package/dist/services/team-reconciler.service.d.ts.map +1 -1
  222. package/dist/services/team-reconciler.service.js +14 -3
  223. package/dist/services/team-reconciler.service.js.map +1 -1
  224. package/dist/services/team-run.service.d.ts.map +1 -1
  225. package/dist/services/team-run.service.js +5 -1
  226. package/dist/services/team-run.service.js.map +1 -1
  227. package/dist/services/workspace.service.d.ts +2 -0
  228. package/dist/services/workspace.service.d.ts.map +1 -1
  229. package/dist/socket/__tests__/socket-gateway.test.js +42 -0
  230. package/dist/socket/__tests__/socket-gateway.test.js.map +1 -1
  231. package/dist/socket/events.d.ts +1 -1
  232. package/dist/socket/events.d.ts.map +1 -1
  233. package/dist/socket/events.js.map +1 -1
  234. package/dist/socket/index.js +1 -1
  235. package/dist/socket/index.js.map +1 -1
  236. package/dist/socket/socket-gateway.d.ts.map +1 -1
  237. package/dist/socket/socket-gateway.js +13 -1
  238. package/dist/socket/socket-gateway.js.map +1 -1
  239. package/dist/types/index.d.ts +7 -1
  240. package/dist/types/index.d.ts.map +1 -1
  241. package/dist/types/index.js +6 -0
  242. package/dist/types/index.js.map +1 -1
  243. package/dist/web/agent-icons/codex.svg +4 -1
  244. package/dist/web/agent-icons/grok-build.svg +5 -0
  245. package/dist/web/agent-icons/kiro-cli.svg +6 -0
  246. package/dist/web/agent-icons/opencode.svg +5 -0
  247. package/dist/web/agent-icons/pi-coding-agent.svg +5 -0
  248. package/dist/web/agent-icons/qwen-code.svg +13 -0
  249. package/dist/web/assets/{AgentDemoPage-C3SHOCDR.js → AgentDemoPage-XrttOpQW.js} +1 -1
  250. package/dist/web/assets/{AgentEnvironmentSettingsPage-D-BWLcC0.js → AgentEnvironmentSettingsPage-CAo0bCpZ.js} +1 -1
  251. package/dist/web/assets/AgentLogo-BCwbooTN.js +1 -0
  252. package/dist/web/assets/ConversationPage-kyHuICil.js +3 -0
  253. package/dist/web/assets/CreateTaskInput-B67vzMJD.js +1 -0
  254. package/dist/web/assets/{DemoPage-Cd1ggnZY.js → DemoPage-DHMh9-zU.js} +1 -1
  255. package/dist/web/assets/{GeneralSettingsPage-B9D9wA_9.js → GeneralSettingsPage-DvWaqR9O.js} +1 -1
  256. package/dist/web/assets/{Icons-CM6Yg2lw.js → Icons-DADUmnB1.js} +1 -1
  257. package/dist/web/assets/{LoadingPreviewPage-BJ_Se_T_.js → LoadingPreviewPage-Dk91Ua1W.js} +1 -1
  258. package/dist/web/assets/{McpSettingsPage-B5GtqNme.js → McpSettingsPage-CH8jFyxp.js} +1 -1
  259. package/dist/web/assets/{MemberAvatar-yC8O30hv.js → MemberAvatar-l-WCdlLA.js} +1 -1
  260. package/dist/web/assets/{NotificationSettingsPage-BaJpYHLD.js → NotificationSettingsPage-Byu8b9X3.js} +1 -1
  261. package/dist/web/assets/{ProfileSettingsPage-DYXew66J.js → ProfileSettingsPage-BdylqWUj.js} +1 -1
  262. package/dist/web/assets/{ProjectKanbanPage-DbgIwiUC.js → ProjectKanbanPage--cx6wNDR.js} +37 -36
  263. package/dist/web/assets/{ProjectSettingsPage-B2K8Kw_S.js → ProjectSettingsPage-6psG0Vfc.js} +1 -1
  264. package/dist/web/assets/ProviderSettingsPage-BPEEkhtA.js +93 -0
  265. package/dist/web/assets/{SettingsMasterDetail-ByKe_zcK.js → SettingsMasterDetail-DTGIIGdI.js} +1 -1
  266. package/dist/web/assets/{TeamSettingsPage-4sHo9Wpb.js → TeamSettingsPage-BXuG4Bou.js} +1 -1
  267. package/dist/web/assets/agent-tower-lockup-DPAcheCt.png +0 -0
  268. package/dist/web/assets/{arc-g6Y4qqcK.js → arc-DrcjdWTE.js} +1 -1
  269. package/dist/web/assets/{architectureDiagram-3BPJPVTR-ILRcqNPe.js → architectureDiagram-3BPJPVTR-CAoBhXSL.js} +1 -1
  270. package/dist/web/assets/{arrow-left-D0MkHwKU.js → arrow-left-CCWDjOmp.js} +1 -1
  271. package/dist/web/assets/{blockDiagram-GPEHLZMM-ZTMQ6CTT.js → blockDiagram-GPEHLZMM-CcTSBbUq.js} +1 -1
  272. package/dist/web/assets/{c4Diagram-AAUBKEIU-CAW8PcM8.js → c4Diagram-AAUBKEIU-BuUKdgUg.js} +1 -1
  273. package/dist/web/assets/channel-Bh_PCZNP.js +1 -0
  274. package/dist/web/assets/check-BN_PVKzE.js +1 -0
  275. package/dist/web/assets/{chevron-down-CmGXPki1.js → chevron-down-BQcW9sFf.js} +1 -1
  276. package/dist/web/assets/{chevron-right-jrkLz9qG.js → chevron-right-pH923eYp.js} +1 -1
  277. package/dist/web/assets/{chevron-up-UwBybVMK.js → chevron-up-DyQW115F.js} +1 -1
  278. package/dist/web/assets/{chunk-2J33WTMH-BvuLBX1d.js → chunk-2J33WTMH-BWNSmQa8.js} +1 -1
  279. package/dist/web/assets/{chunk-4BX2VUAB-DJO5Yx2A.js → chunk-4BX2VUAB-Blldhw0k.js} +1 -1
  280. package/dist/web/assets/{chunk-55IACEB6-Dc117Qwf.js → chunk-55IACEB6-hTffyPXL.js} +1 -1
  281. package/dist/web/assets/{chunk-727SXJPM-DsZmTw67.js → chunk-727SXJPM-Cd48r7c-.js} +1 -1
  282. package/dist/web/assets/{chunk-AQP2D5EJ-DgmF7gD6.js → chunk-AQP2D5EJ-BdDrOIL7.js} +1 -1
  283. package/dist/web/assets/{chunk-FMBD7UC4-vgh3Erl0.js → chunk-FMBD7UC4-6XIvQYVJ.js} +1 -1
  284. package/dist/web/assets/{chunk-ND2GUHAM-Ogtl4wwd.js → chunk-ND2GUHAM-BJlcb_pB.js} +1 -1
  285. package/dist/web/assets/{chunk-QZHKN3VN-Cif7bPpF.js → chunk-QZHKN3VN-3TZzFkxK.js} +1 -1
  286. package/dist/web/assets/{circle-alert-B6ZM3dSn.js → circle-alert-oa-BQ5e7.js} +1 -1
  287. package/dist/web/assets/classDiagram-4FO5ZUOK-B0lQFDoP.js +1 -0
  288. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-B0lQFDoP.js +1 -0
  289. package/dist/web/assets/{code-block-OCS4YCEC-BQnKZly0.js → code-block-OCS4YCEC-xJQWFyR7.js} +1 -1
  290. package/dist/web/assets/{confirm-dialog-BZeaAl5j.js → confirm-dialog-CRiC3xBd.js} +1 -1
  291. package/dist/web/assets/{copy-DqNqPI8d.js → copy-DaoAUKTO.js} +1 -1
  292. package/dist/web/assets/{cose-bilkent-S5V4N54A-rxJB-TPw.js → cose-bilkent-S5V4N54A-Cjcm-rW_.js} +1 -1
  293. package/dist/web/assets/cssMode-_D6u4YDd.js +1 -0
  294. package/dist/web/assets/{dagre-BM42HDAG-BkB6VpXn.js → dagre-BM42HDAG-YqubcAJi.js} +1 -1
  295. package/dist/web/assets/{diagram-2AECGRRQ-BH2e-eqy.js → diagram-2AECGRRQ-Dk4ayFqg.js} +1 -1
  296. package/dist/web/assets/{diagram-5GNKFQAL-B0I47Yhs.js → diagram-5GNKFQAL-CQ0joYfB.js} +1 -1
  297. package/dist/web/assets/{diagram-KO2AKTUF-BVXUYRvI.js → diagram-KO2AKTUF-DmuZzIdW.js} +1 -1
  298. package/dist/web/assets/{diagram-LMA3HP47-C1fEOLke.js → diagram-LMA3HP47-YTPEfEZy.js} +1 -1
  299. package/dist/web/assets/{diagram-OG6HWLK6-CVE8cOcO.js → diagram-OG6HWLK6-DmwRuz8-.js} +1 -1
  300. package/dist/web/assets/{erDiagram-TEJ5UH35-qCfnXmfp.js → erDiagram-TEJ5UH35-LRL_DMXa.js} +1 -1
  301. package/dist/web/assets/{flowDiagram-I6XJVG4X-KMhbF5QC.js → flowDiagram-I6XJVG4X-ELBDyOvk.js} +1 -1
  302. package/dist/web/assets/{folder-picker-C3BgqamQ.js → folder-picker-WUhXxIM5.js} +1 -1
  303. package/dist/web/assets/{freemarker2-tYgVOcFl.js → freemarker2-DxJFkRz5.js} +2 -2
  304. package/dist/web/assets/{ganttDiagram-6RSMTGT7-CaX3qYV1.js → ganttDiagram-6RSMTGT7-D0e9ILuF.js} +1 -1
  305. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-CBAlZX7C.js → gitGraphDiagram-PVQCEYII-Ck6OXXw0.js} +1 -1
  306. package/dist/web/assets/handlebars-djdO6ZKR.js +1 -0
  307. package/dist/web/assets/html-CHeBxmFi.js +1 -0
  308. package/dist/web/assets/htmlMode-SZh8FaPi.js +1 -0
  309. package/dist/web/assets/index-CjAJoGg5.css +1 -0
  310. package/dist/web/assets/{index-BuQDZjol.js → index-CjdqAcbu.js} +4 -4
  311. package/dist/web/assets/{index-DHKgc87I.js → index-D9kBRMR0.js} +18 -18
  312. package/dist/web/assets/{infoDiagram-5YYISTIA-Bp_GeFtA.js → infoDiagram-5YYISTIA-Cx8be-MV.js} +1 -1
  313. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-BfBNEV_D.js → ishikawaDiagram-YF4QCWOH-Dlc7akl7.js} +1 -1
  314. package/dist/web/assets/javascript-l6fV6hf7.js +1 -0
  315. package/dist/web/assets/{journeyDiagram-JHISSGLW-DJkUQG4Z.js → journeyDiagram-JHISSGLW-BVDSbS9-.js} +1 -1
  316. package/dist/web/assets/{jsonMode-D4_2geMa.js → jsonMode-B5CJhoWd.js} +2 -2
  317. package/dist/web/assets/{kanban-definition-UN3LZRKU-Bk7sA26p.js → kanban-definition-UN3LZRKU-nFR0zB7B.js} +1 -1
  318. package/dist/web/assets/{layers-BFkjcml2.js → layers-Bo4aml98.js} +1 -1
  319. package/dist/web/assets/{linear-CmC68WUf.js → linear-BCWZjcpP.js} +1 -1
  320. package/dist/web/assets/liquid-DkFOeJ6J.js +1 -0
  321. package/dist/web/assets/{lspLanguageFeatures-vY541IS0.js → lspLanguageFeatures-Cbk7wEkj.js} +1 -1
  322. package/dist/web/assets/mdx-BOPNdexu.js +1 -0
  323. package/dist/web/assets/{mermaid-NOHMQCX5-C6wHDXti.js → mermaid-NOHMQCX5-DWQaYVL0.js} +59 -61
  324. package/dist/web/assets/{message-square-Wwixl9YA.js → message-square-DxbkpGqF.js} +1 -1
  325. package/dist/web/assets/{mindmap-definition-RKZ34NQL-Ck1je2Hf.js → mindmap-definition-RKZ34NQL-LI7W1FIX.js} +1 -1
  326. package/dist/web/assets/{modal-kFSArLJi.js → modal-Cymk-6Df.js} +1 -1
  327. package/dist/web/assets/{monaco-D3FVQqPj.js → monaco-Bc-Z2u8N.js} +3 -3
  328. package/dist/web/assets/{pencil-5Kegi5_H.js → pencil-DOL2nlgg.js} +1 -1
  329. package/dist/web/assets/{pieDiagram-4H26LBE5-6Ao8YaoY.js → pieDiagram-4H26LBE5-mgUzAFkk.js} +1 -1
  330. package/dist/web/assets/python-D_wlYLCL.js +1 -0
  331. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-md1eBgjS.js → quadrantDiagram-W4KKPZXB-CaDF2kqA.js} +1 -1
  332. package/dist/web/assets/razor-DBFVg4JU.js +1 -0
  333. package/dist/web/assets/{requirementDiagram-4Y6WPE33-DJOb4wf-.js → requirementDiagram-4Y6WPE33-DhmHdr_0.js} +1 -1
  334. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-B7srXhHb.js → sankeyDiagram-5OEKKPKP-B7qnGfue.js} +1 -1
  335. package/dist/web/assets/{select-C2v0aFwN.js → select-BpKQyMvD.js} +1 -1
  336. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-L0qwgf37.js → sequenceDiagram-3UESZ5HK-D2LhPgpM.js} +1 -1
  337. package/dist/web/assets/{stateDiagram-AJRCARHV-DGbH9dJ5.js → stateDiagram-AJRCARHV-ClkO4Gu5.js} +1 -1
  338. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-YnSeSD_N.js +1 -0
  339. package/dist/web/assets/{switch-DjnF5A3R.js → switch-BJ4Ar_OA.js} +1 -1
  340. package/dist/web/assets/{textarea-CbZnd5PO.js → textarea-DjwjAAG8.js} +1 -1
  341. package/dist/web/assets/{timeline-definition-PNZ67QCA-Dku_Yjmk.js → timeline-definition-PNZ67QCA-DhGIG-GC.js} +1 -1
  342. package/dist/web/assets/{trash-2-DA--Uikt.js → trash-2-R46i6Uus.js} +1 -1
  343. package/dist/web/assets/{tsMode-CRNPFR24.js → tsMode-CKaqpIrz.js} +2 -2
  344. package/dist/web/assets/typescript-DsixnGVt.js +1 -0
  345. package/dist/web/assets/{upload-hWnFHuhE.js → upload-CgWqsmdI.js} +1 -1
  346. package/dist/web/assets/{use-profiles-roHXR8He.js → use-profiles-BlGgvdYm.js} +1 -1
  347. package/dist/web/assets/{use-projects-DiJMGduU.js → use-projects-Cuv0A0RC.js} +1 -1
  348. package/dist/web/assets/{use-providers-efX4HCcr.js → use-providers-DLcAY6E1.js} +1 -1
  349. package/dist/web/assets/{useNormalizedLogs-xEobyuGF.js → useNormalizedLogs-tfxFAJOF.js} +1 -1
  350. package/dist/web/assets/{vennDiagram-CIIHVFJN-_ufI1hxk.js → vennDiagram-CIIHVFJN-BngQhsYJ.js} +1 -1
  351. package/dist/web/assets/{wardley-L42UT6IY-Dve662aX.js → wardley-L42UT6IY-BEY6sftT.js} +1 -1
  352. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-Dn2x3M_Q.js → wardleyDiagram-YWT4CUSO-IH_Yh-cw.js} +1 -1
  353. package/dist/web/assets/xml-dEcQ1gzo.js +1 -0
  354. package/dist/web/assets/{xychartDiagram-2RQKCTM6-BMe6JPre.js → xychartDiagram-2RQKCTM6-BdrGFDsr.js} +1 -1
  355. package/dist/web/assets/yaml-BfGVqGj4.js +1 -0
  356. package/dist/web/index.html +2 -2
  357. package/node_modules/@agent-tower/shared/dist/agent-runtime-support.d.ts +11 -0
  358. package/node_modules/@agent-tower/shared/dist/agent-runtime-support.d.ts.map +1 -0
  359. package/node_modules/@agent-tower/shared/dist/agent-runtime-support.js +26 -0
  360. package/node_modules/@agent-tower/shared/dist/agent-runtime-support.js.map +1 -0
  361. package/node_modules/@agent-tower/shared/dist/index.d.ts +1 -0
  362. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  363. package/node_modules/@agent-tower/shared/dist/index.js +1 -0
  364. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  365. package/node_modules/@agent-tower/shared/dist/log-adapter.d.ts +38 -3
  366. package/node_modules/@agent-tower/shared/dist/log-adapter.d.ts.map +1 -1
  367. package/node_modules/@agent-tower/shared/dist/log-adapter.js +21 -2
  368. package/node_modules/@agent-tower/shared/dist/log-adapter.js.map +1 -1
  369. package/node_modules/@agent-tower/shared/dist/provider-capabilities.d.ts.map +1 -1
  370. package/node_modules/@agent-tower/shared/dist/provider-capabilities.js +58 -0
  371. package/node_modules/@agent-tower/shared/dist/provider-capabilities.js.map +1 -1
  372. package/node_modules/@agent-tower/shared/dist/socket/events.d.ts +17 -0
  373. package/node_modules/@agent-tower/shared/dist/socket/events.d.ts.map +1 -1
  374. package/node_modules/@agent-tower/shared/dist/socket/events.js +3 -0
  375. package/node_modules/@agent-tower/shared/dist/socket/events.js.map +1 -1
  376. package/node_modules/@agent-tower/shared/dist/types.d.ts +63 -2
  377. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  378. package/node_modules/@agent-tower/shared/dist/types.js +12 -0
  379. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  380. package/package.json +9 -3
  381. package/prisma/migrations/20260723000000_add_agent_runtime/migration.sql +2 -0
  382. package/prisma/schema.prisma +2 -0
  383. package/scripts/patch-claude-agent-acp.mjs +77 -0
  384. package/scripts/postinstall.js +5 -0
  385. package/dist/web/assets/AgentLogo-DOc8QQHe.js +0 -1
  386. package/dist/web/assets/ConversationPage-sknzMK-I.js +0 -3
  387. package/dist/web/assets/CreateTaskInput-Cn0k5D8K.js +0 -1
  388. package/dist/web/assets/ProviderSettingsPage-n2AFrtkZ.js +0 -93
  389. package/dist/web/assets/channel-DpisCjBV.js +0 -1
  390. package/dist/web/assets/check-VMcYyBr4.js +0 -1
  391. package/dist/web/assets/classDiagram-4FO5ZUOK-ByrAXg7x.js +0 -1
  392. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-ByrAXg7x.js +0 -1
  393. package/dist/web/assets/cssMode-BvIKUSIl.js +0 -1
  394. package/dist/web/assets/handlebars-O23dzm6n.js +0 -1
  395. package/dist/web/assets/html-QaPgl834.js +0 -1
  396. package/dist/web/assets/htmlMode-CWyii3pY.js +0 -1
  397. package/dist/web/assets/index-BeEbDvc9.css +0 -1
  398. package/dist/web/assets/javascript-C_IpQDkn.js +0 -1
  399. package/dist/web/assets/liquid-C6FS7ekL.js +0 -1
  400. package/dist/web/assets/mdx-LQJToB8m.js +0 -1
  401. package/dist/web/assets/python-HMxhKAEt.js +0 -1
  402. package/dist/web/assets/razor-CfDc2DcT.js +0 -1
  403. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-Bq1LxgY_.js +0 -1
  404. package/dist/web/assets/typescript-CNopspEc.js +0 -1
  405. package/dist/web/assets/xml-dqhfYLW3.js +0 -1
  406. package/dist/web/assets/yaml-BOxNX83z.js +0 -1
@@ -1,18 +1,20 @@
1
1
  import { prisma } from '../utils/index.js';
2
- import { AgentType, SessionStatus, SessionPurpose, TaskStatus, SessionContext } from '../types/index.js';
3
- import { getExecutor, getExecutorByProvider, getProviderById, ExecutionEnv, ExecutorConfigurationError, ExecutorNotFoundError, normalizeExecutorStartError, } from '../executors/index.js';
2
+ import { SessionStatus, SessionPurpose, TaskStatus, SessionContext } from '../types/index.js';
3
+ import { getProviderById, ExecutionEnv, normalizeExecutorStartError, } from '../executors/index.js';
4
4
  import { filterAgentSubprocessExternalEnv } from '../executors/execution-env.js';
5
- import { sessionMsgStoreManager, createClaudeCodeParser, createCursorAgentParser, createCodexParser, createUserMessage, addNormalizedEntry, } from '../output/index.js';
6
- import { AgentPipeline } from '../pipeline/agent-pipeline.js';
5
+ import { sessionMsgStoreManager, createUserMessage, addNormalizedEntry, } from '../output/index.js';
7
6
  import { execGit } from '../git/git-cli.js';
8
7
  import { getCommitMessageService } from '../core/container.js';
9
8
  import { TeamReconcilerService } from './team-reconciler.service.js';
10
- import { NotFoundError } from '../errors.js';
9
+ import { NotFoundError, ValidationError } from '../errors.js';
11
10
  import { ensureTaskNotDeleted } from './deleted-task-guard.js';
12
11
  import { getWorkspaceWorkingDir, isMainDirectoryWorkspace, } from './workspace-kind.js';
13
12
  import { writeErrorLog } from '../utils/error-log.js';
14
13
  import { INTERNAL_API_TOKEN_ENV, readInternalApiTokenFromEnv } from '../utils/internal-api-token.js';
15
14
  import { createHash } from 'node:crypto';
15
+ import { RuntimeType, supportsAgentRuntime } from '@agent-tower/shared';
16
+ import { getProviderRuntimeType } from '../executors/providers.js';
17
+ import { CliRuntimeDriver, AcpRuntimeDriver, RuntimeCoordinator, StaticRuntimeRegistry, setRuntimeStateSnapshot, } from '../runtime/index.js';
16
18
  const DEBUG_SNAPSHOT = process.env.DEBUG_SNAPSHOT === 'true';
17
19
  function hashForLog(value) {
18
20
  return createHash('sha256').update(value).digest('hex').slice(0, 12);
@@ -46,12 +48,9 @@ function isAgentProgressPatch(patch) {
46
48
  }
47
49
  export class SessionManager {
48
50
  eventBus;
49
- pipelines = new Map();
50
- pendingSpawns = new Map();
51
- cancelTokens = new Map();
52
51
  snapshotFlushTimers = new Map();
53
52
  snapshotFlushChains = new Map();
54
- snapshotWriterChain = Promise.resolve();
53
+ sessionPersistenceWriterChain = Promise.resolve();
55
54
  dirtySnapshots = new Set();
56
55
  persistedSnapshotHashes = new Map();
57
56
  pendingSnapshotStatus = new Map();
@@ -70,13 +69,15 @@ export class SessionManager {
70
69
  followUpReservationReleases = new Set();
71
70
  /** Incremented for every start/send cycle so late post-processing cannot affect a new turn. */
72
71
  sessionGenerations = new Map();
73
- logicalCompletionCleanupTimers = new Map();
74
72
  // 每个 session 上次写入 TeamRun 心跳时间戳的时刻,用于节流 lastHeartbeatAt 落库。
75
73
  heartbeatThrottle = new Map();
76
74
  teamReconciler;
75
+ runtimeCoordinator;
76
+ runtimeProcessIds = new Map();
77
+ runtimePermissionStates = new Map();
78
+ externalSessionPersistence = new Map();
77
79
  static SNAPSHOT_CHECKPOINT_MS = 15_000;
78
80
  static HEARTBEAT_THROTTLE_MS = 30_000;
79
- static CODEX_TURN_COMPLETION_GRACE_MS = 250;
80
81
  constructor(eventBus, teamReconciler) {
81
82
  this.eventBus = eventBus;
82
83
  this.teamReconciler = teamReconciler ?? new TeamReconcilerService({
@@ -86,6 +87,14 @@ export class SessionManager {
86
87
  // session 退出时的首次 reconcile(COMPLETED 判定 / 首次补催)仍即时执行,不依赖该定时器。
87
88
  scheduleReminders: false,
88
89
  });
90
+ this.runtimeCoordinator = new RuntimeCoordinator(new StaticRuntimeRegistry([
91
+ new CliRuntimeDriver(),
92
+ new AcpRuntimeDriver(),
93
+ ]), {
94
+ onTurnEvent: (event) => this.handleRuntimeTurnEvent(event),
95
+ onRuntimeState: (state) => this.handleRuntimeState(state),
96
+ onProcessEvent: (event) => this.handleRuntimeProcessEvent(event),
97
+ });
89
98
  // Patches only mark the snapshot dirty. A low-frequency checkpoint keeps the
90
99
  // hot stream away from SQLite while terminal paths still force a final flush.
91
100
  this.eventBus.on('session:patch', ({ sessionId, patch }) => {
@@ -105,7 +114,6 @@ export class SessionManager {
105
114
  // The parser has already written raw stdout, the final assistant entry,
106
115
  // usage and all other state from the turn.completed chunk at this point.
107
116
  this.terminalSessions.set(sessionId, SessionStatus.COMPLETED);
108
- this.scheduleLogicalCompletionCleanup(sessionId);
109
117
  this.startSessionFinalization(sessionId, 0, { logicalCompletion: true });
110
118
  });
111
119
  this.eventBus.on('session:turn-failed', ({ sessionId }) => {
@@ -115,30 +123,8 @@ export class SessionManager {
115
123
  // without an exit code. Use a synthetic non-zero code for the shared
116
124
  // finalization path so success-only post-processing cannot run.
117
125
  this.terminalSessions.set(sessionId, SessionStatus.FAILED);
118
- this.scheduleLogicalCompletionCleanup(sessionId);
119
126
  this.startSessionFinalization(sessionId, 1, { logicalCompletion: true });
120
127
  });
121
- this.eventBus.on('session:exit', ({ sessionId, exitCode }) => {
122
- const terminalStatus = this.terminalSessions.get(sessionId);
123
- this.clearLogicalCompletionCleanup(sessionId);
124
- const pipeline = this.pipelines.get(sessionId);
125
- if (DEBUG_SNAPSHOT) {
126
- console.log(`[SessionManager:snapshot] session:exit sessionId=${sessionId} exitCode=${exitCode} hasPipeline=${Boolean(pipeline)}`);
127
- }
128
- if (pipeline) {
129
- // Must call destroy() to remove MsgStore onPatch/onSessionId listeners.
130
- // Without this, stale listeners accumulate across send/exit cycles and
131
- // each subsequent pushPatch() forwards the same patch N times.
132
- pipeline.destroy();
133
- this.pipelines.delete(sessionId);
134
- }
135
- this.cancelTokens.delete(sessionId);
136
- this.heartbeatThrottle.delete(sessionId);
137
- if (terminalStatus)
138
- return;
139
- this.terminalSessions.set(sessionId, typeof exitCode === 'number' && exitCode !== 0 ? SessionStatus.FAILED : SessionStatus.COMPLETED);
140
- this.startSessionFinalization(sessionId, exitCode);
141
- });
142
128
  // NOTE: checkTaskAutoRevert is called directly (awaited) inside start()
143
129
  // and sendMessage() to guarantee the task status is updated before the
144
130
  // HTTP response is sent. A fire-and-forget EventBus listener here caused
@@ -151,6 +137,12 @@ export class SessionManager {
151
137
  include: { processes: true, workspace: true, conversation: true },
152
138
  });
153
139
  }
140
+ getRuntimeState(sessionId, runtimeType = RuntimeType.CLI) {
141
+ return this.runtimeCoordinator.getState(sessionId, runtimeType);
142
+ }
143
+ async resolveRuntimePermission(sessionId, requestId, optionId) {
144
+ await this.runtimeCoordinator.resolvePermission(sessionId, requestId, optionId);
145
+ }
154
146
  async create(workspaceId, agentType, prompt, variant = 'DEFAULT', providerId) {
155
147
  const workspace = await prisma.workspace.findUnique({
156
148
  where: { id: workspaceId },
@@ -160,11 +152,23 @@ export class SessionManager {
160
152
  throw new NotFoundError('Workspace', workspaceId);
161
153
  }
162
154
  ensureTaskNotDeleted(workspace.task);
155
+ const provider = providerId ? getProviderById(providerId) : null;
156
+ if (providerId && !provider) {
157
+ throw new ValidationError(`Provider not found: ${providerId}`);
158
+ }
159
+ if (provider && provider.agentType !== agentType) {
160
+ throw new ValidationError(`Provider '${provider.name}' belongs to agent '${provider.agentType}', not '${agentType}'`);
161
+ }
162
+ const runtimeType = provider ? getProviderRuntimeType(provider) : RuntimeType.CLI;
163
+ if (!supportsAgentRuntime(agentType, runtimeType)) {
164
+ throw new ValidationError(`Agent '${agentType}' does not support the '${runtimeType}' runtime`);
165
+ }
163
166
  return prisma.session.create({
164
167
  data: {
165
168
  workspaceId,
166
169
  context: SessionContext.WORKSPACE,
167
170
  agentType,
171
+ runtimeType,
168
172
  variant,
169
173
  providerId: providerId ?? null,
170
174
  prompt,
@@ -190,39 +194,7 @@ export class SessionManager {
190
194
  prompt: summarizeTextForLog(session.prompt),
191
195
  workingDir,
192
196
  });
193
- const agentType = session.agentType;
194
- const executor = this.resolveExecutor(agentType, session.variant, session.providerId);
195
- console.log('[SessionManager] ✅ Executor found, spawning process...');
196
- const env = ExecutionEnv.default(workingDir);
197
- // 如果有 provider,注入 provider 的环境变量
198
- if (session.providerId) {
199
- const provider = getProviderById(session.providerId);
200
- if (provider && Object.keys(provider.env).length > 0) {
201
- env.merge(filterAgentSubprocessExternalEnv(provider.env));
202
- }
203
- }
204
- this.injectAgentTowerMcpServiceEnv(env);
205
- if (!this.isConversationSession(session)) {
206
- await this.injectTeamRunInvocationEnv(id, env);
207
- }
208
- let spawnResult;
209
- try {
210
- spawnResult = await executor.spawn({
211
- workingDir,
212
- prompt: session.prompt,
213
- env,
214
- });
215
- }
216
- catch (error) {
217
- this.logSessionError('session.spawn', error, {
218
- sessionId: id,
219
- agentType: session.agentType,
220
- providerId: session.providerId,
221
- workingDir,
222
- });
223
- throw normalizeExecutorStartError(error);
224
- }
225
- await this.activateSpawnedSession(id, agentType, workingDir, spawnResult);
197
+ await this.startRuntimeTurn(session, session.prompt, session.externalSessionId);
226
198
  return session;
227
199
  }
228
200
  async startFollowUp(id, resumeFromSessionId) {
@@ -238,10 +210,11 @@ export class SessionManager {
238
210
  this.ensureExecutionRecordIsLive(session);
239
211
  const resumeFromSession = await prisma.session.findUnique({
240
212
  where: { id: resumeFromSessionId },
241
- select: { logSnapshot: true },
213
+ select: { logSnapshot: true, externalSessionId: true },
242
214
  });
243
215
  const agentSessionId = resumeFromSession
244
- ? this.resolveAgentSessionId(resumeFromSessionId, resumeFromSession.logSnapshot)
216
+ ? resumeFromSession.externalSessionId
217
+ ?? this.resolveAgentSessionId(resumeFromSessionId, resumeFromSession.logSnapshot)
245
218
  : null;
246
219
  console.log('[SessionManager] Follow-up session details:', {
247
220
  id: session.id,
@@ -252,76 +225,7 @@ export class SessionManager {
252
225
  prompt: summarizeTextForLog(session.prompt),
253
226
  workingDir: this.getExecutionWorkingDir(session),
254
227
  });
255
- const agentType = session.agentType;
256
- const executor = this.resolveExecutor(agentType, session.variant, session.providerId);
257
- const workingDir = this.getExecutionWorkingDir(session);
258
- const env = ExecutionEnv.default(workingDir);
259
- if (session.providerId) {
260
- const provider = getProviderById(session.providerId);
261
- if (provider && Object.keys(provider.env).length > 0) {
262
- env.merge(filterAgentSubprocessExternalEnv(provider.env));
263
- }
264
- }
265
- this.injectAgentTowerMcpServiceEnv(env);
266
- if (!this.isConversationSession(session)) {
267
- await this.injectTeamRunInvocationEnv(id, env);
268
- }
269
- const spawnConfig = {
270
- workingDir,
271
- prompt: session.prompt,
272
- env,
273
- };
274
- let spawnResult;
275
- if (agentSessionId && executor.spawnFollowUp) {
276
- try {
277
- spawnResult = await executor.spawnFollowUp(spawnConfig, agentSessionId);
278
- }
279
- catch (error) {
280
- console.warn(`[SessionManager] Follow-up spawn failed for ${id}, falling back to a new agent session:`, error instanceof Error ? error.message : error);
281
- writeErrorLog({
282
- level: 'warn',
283
- source: 'session.spawnFollowUpResume',
284
- message: `Follow-up spawn failed for session ${id}; falling back to a new agent session`,
285
- error,
286
- metadata: {
287
- sessionId: id,
288
- resumeFromSessionId,
289
- agentType: session.agentType,
290
- providerId: session.providerId,
291
- workingDir,
292
- },
293
- });
294
- try {
295
- spawnResult = await executor.spawn(spawnConfig);
296
- }
297
- catch (fallbackError) {
298
- this.logSessionError('session.spawnFollowUpFallback', fallbackError, {
299
- sessionId: id,
300
- resumeFromSessionId,
301
- agentType: session.agentType,
302
- providerId: session.providerId,
303
- workingDir,
304
- });
305
- throw normalizeExecutorStartError(fallbackError);
306
- }
307
- }
308
- }
309
- else {
310
- try {
311
- spawnResult = await executor.spawn(spawnConfig);
312
- }
313
- catch (error) {
314
- this.logSessionError('session.spawnFollowUp', error, {
315
- sessionId: id,
316
- resumeFromSessionId,
317
- agentType: session.agentType,
318
- providerId: session.providerId,
319
- workingDir,
320
- });
321
- throw normalizeExecutorStartError(error);
322
- }
323
- }
324
- await this.activateSpawnedSession(id, agentType, workingDir, spawnResult);
228
+ await this.startRuntimeTurn(session, session.prompt, agentSessionId, session.providerId, 'resume');
325
229
  return session;
326
230
  }
327
231
  async sendMessage(id, message, providerId) {
@@ -353,6 +257,9 @@ export class SessionManager {
353
257
  if (String(effectiveProvider.agentType) !== session.agentType) {
354
258
  throw new Error(`Cannot switch provider: agentType mismatch. Session uses '${session.agentType}', but provider '${effectiveProvider.name}' is for '${effectiveProvider.agentType}'`);
355
259
  }
260
+ if (getProviderRuntimeType(effectiveProvider) !== this.normalizeRuntimeType(session.runtimeType)) {
261
+ throw new Error(`Cannot switch provider: runtimeType mismatch. Session uses '${session.runtimeType}', but provider '${effectiveProvider.name}' uses '${getProviderRuntimeType(effectiveProvider)}'`);
262
+ }
356
263
  }
357
264
  if (providerId && providerId !== session.providerId) {
358
265
  const switchedProvider = getProviderById(providerId);
@@ -368,18 +275,18 @@ export class SessionManager {
368
275
  // the old execution with the new one.
369
276
  this.invalidateSessionGeneration(id);
370
277
  this.beginSessionExecution(id);
371
- // Destroying the residual PTY before waiting for the completed generation's
372
- // auto-commit prevents a late exit event from being mistaken for the new
373
- // generation while the Git boundary is in flight.
374
- const existing = this.pipelines.get(id);
375
- if (existing) {
278
+ // Stop the previous turn before waiting for its auto-commit boundary. The
279
+ // coordinator suppresses that superseded turn's terminal event so it cannot
280
+ // finalize the newly reserved generation.
281
+ if (this.runtimeCoordinator.hasActiveTurn(id)) {
376
282
  if (DEBUG_SNAPSHOT) {
377
- console.log(`[SessionManager:snapshot] sendMessage checkpoint before pipeline replace sessionId=${id}`);
283
+ console.log(`[SessionManager:snapshot] sendMessage checkpoint before runtime turn replace sessionId=${id}`);
378
284
  }
379
285
  await this.flushSnapshotPersist(id);
380
- existing.destroy();
381
- this.pipelines.delete(id);
382
- this.cancelTokens.delete(id);
286
+ const canReuseDriverSession = await this.runtimeCoordinator.abandonTurn(id);
287
+ if (!canReuseDriverSession) {
288
+ await this.runtimeCoordinator.disposeSession(id);
289
+ }
383
290
  }
384
291
  await this.waitForPendingAutoCommit(id);
385
292
  const isNewStore = !sessionMsgStoreManager.has(id);
@@ -416,74 +323,12 @@ export class SessionManager {
416
323
  // MsgStore's patchListeners are empty at this point. Without this line
417
324
  // the user-message patch would never reach WebSocket subscribers.
418
325
  this.eventBus.emit('session:patch', { sessionId: id, patch: userPatch, seq: userPatchSeq });
419
- const agentSessionId = this.resolveAgentSessionId(id, session.logSnapshot);
420
- const agentType = session.agentType;
421
- const executor = this.resolveExecutor(agentType, session.variant, effectiveProviderId);
422
- const workingDir = this.getExecutionWorkingDir(session);
423
- const env = ExecutionEnv.default(workingDir);
424
- // 如果有 provider,注入 provider 的环境变量
425
- if (effectiveProviderId) {
426
- const provider = getProviderById(effectiveProviderId);
427
- if (provider && Object.keys(provider.env).length > 0) {
428
- env.merge(filterAgentSubprocessExternalEnv(provider.env));
429
- }
430
- }
431
- this.injectAgentTowerMcpServiceEnv(env);
432
- if (!this.isConversationSession(session)) {
433
- await this.injectTeamRunInvocationEnv(id, env);
434
- }
435
- const spawnConfig = {
436
- workingDir,
437
- prompt: message,
438
- env,
439
- };
440
- let spawnResult;
441
- if (agentSessionId && executor.spawnFollowUp) {
442
- try {
443
- spawnResult = await executor.spawnFollowUp(spawnConfig, agentSessionId);
444
- }
445
- catch (resumeError) {
446
- writeErrorLog({
447
- level: 'warn',
448
- source: 'session.messageSpawnResume',
449
- message: `Message follow-up spawn failed for session ${id}; falling back to a new agent session`,
450
- error: resumeError,
451
- metadata: {
452
- sessionId: id,
453
- agentType: session.agentType,
454
- providerId: effectiveProviderId,
455
- workingDir,
456
- },
457
- });
458
- try {
459
- spawnResult = await executor.spawn(spawnConfig);
460
- }
461
- catch (error) {
462
- this.logSessionError('session.messageSpawnFallback', error, {
463
- sessionId: id,
464
- agentType: session.agentType,
465
- providerId: effectiveProviderId,
466
- workingDir,
467
- });
468
- throw normalizeExecutorStartError(error);
469
- }
470
- }
471
- }
472
- else {
473
- try {
474
- spawnResult = await executor.spawn(spawnConfig);
475
- }
476
- catch (error) {
477
- this.logSessionError('session.messageSpawn', error, {
478
- sessionId: id,
479
- agentType: session.agentType,
480
- providerId: effectiveProviderId,
481
- workingDir,
482
- });
483
- throw normalizeExecutorStartError(error);
484
- }
326
+ const agentSessionId = session.externalSessionId
327
+ ?? this.resolveAgentSessionId(id, session.logSnapshot);
328
+ if (providerId && providerId !== session.providerId) {
329
+ await this.runtimeCoordinator.disposeSession(id);
485
330
  }
486
- await this.activateSpawnedSession(id, agentType, workingDir, spawnResult);
331
+ await this.startRuntimeTurn(session, message, agentSessionId, effectiveProviderId);
487
332
  return session;
488
333
  }
489
334
  finally {
@@ -495,19 +340,19 @@ export class SessionManager {
495
340
  if (!session)
496
341
  return null;
497
342
  const terminalStatus = this.terminalSessions.get(id);
343
+ const hasActiveTurn = this.runtimeCoordinator.hasActiveTurn(id);
498
344
  const persistedTerminal = [
499
345
  SessionStatus.COMPLETED,
500
346
  SessionStatus.FAILED,
501
347
  SessionStatus.CANCELLED,
502
348
  ].includes(session.status);
503
- if (terminalStatus || persistedTerminal) {
349
+ if (!hasActiveTurn && (terminalStatus || persistedTerminal)) {
504
350
  const pendingFinalization = this.sessionFinalizations.get(id);
505
351
  // A terminal transition already won the race. A late user stop may
506
352
  // clean up the PTY, but it must not regress the persisted status. The
507
353
  // backing TeamRun invocation may still be waiting for a room reply, so
508
354
  // it must still pass through the cancellation reconciler.
509
- this.clearLogicalCompletionCleanup(id);
510
- this.destroyActivePipeline(id);
355
+ await this.runtimeCoordinator.disposeSession(id);
511
356
  this.maybeClearTerminalState(id);
512
357
  await pendingFinalization;
513
358
  if (!options.skipTeamRunReconcile && !this.isConversationSession(session)) {
@@ -515,34 +360,26 @@ export class SessionManager {
515
360
  }
516
361
  return session;
517
362
  }
518
- this.clearLogicalCompletionCleanup(id);
519
363
  this.terminalSessions.set(id, SessionStatus.CANCELLED);
520
- const pendingSpawn = this.pendingSpawns.get(id);
521
- if (pendingSpawn) {
522
- this.pendingSpawns.delete(id);
523
- try {
524
- pendingSpawn.cancel?.cancel();
525
- }
526
- catch {
527
- // ignore cancellation failures for pending spawns
528
- }
529
- try {
530
- pendingSpawn.pty.kill();
531
- }
532
- catch {
533
- // ignore kill errors for pending spawns
364
+ const runtimeType = this.normalizeRuntimeType(session.runtimeType);
365
+ if (runtimeType === RuntimeType.ACP && hasActiveTurn) {
366
+ const canReuseDriverSession = await this.runtimeCoordinator.abandonTurn(id).catch((error) => {
367
+ this.logSessionError('session.runtimeCancel', error, { sessionId: id });
368
+ return false;
369
+ });
370
+ if (!canReuseDriverSession) {
371
+ await this.runtimeCoordinator.disposeSession(id).catch((error) => {
372
+ this.logSessionError('session.runtimeDispose', error, { sessionId: id });
373
+ });
534
374
  }
535
375
  }
536
- const pipeline = this.pipelines.get(id);
537
- if (pipeline) {
538
- // Try graceful shutdown via SIGINT first
539
- const cancel = this.cancelTokens.get(id);
540
- if (cancel) {
541
- cancel.cancel();
542
- }
543
- pipeline.destroy();
544
- this.pipelines.delete(id);
545
- this.cancelTokens.delete(id);
376
+ else {
377
+ await this.runtimeCoordinator.cancelTurn(id).catch((error) => {
378
+ this.logSessionError('session.runtimeCancel', error, { sessionId: id });
379
+ });
380
+ await this.runtimeCoordinator.disposeSession(id).catch((error) => {
381
+ this.logSessionError('session.runtimeDispose', error, { sessionId: id });
382
+ });
546
383
  }
547
384
  const msgStore = sessionMsgStoreManager.get(id);
548
385
  if (msgStore) {
@@ -568,19 +405,21 @@ export class SessionManager {
568
405
  await this.teamReconciler.handleSessionStopped(id);
569
406
  }
570
407
  this.eventBus.emit('session:stopped', { sessionId: id });
571
- // stop() 路径不会触发 session:exit(pipeline.destroy 先于 PTY 退出,
572
- // onExit 被 destroyed 标志短路),handleSessionExit 不会执行,
573
- // 因此在这里释放 MsgStore。快照已在上方持久化(CANCELLED)。
408
+ // Cancellation does not run normal terminal finalization, so release the
409
+ // store after the CANCELLED snapshot has been persisted above.
574
410
  sessionMsgStoreManager.delete(id);
575
411
  this.releaseSnapshotPersistenceState(id);
576
412
  return session;
577
413
  }
578
- /**
579
- * 是否仍持有该 session 的活跃 PTY pipeline。
580
- * 供 TeamRun 心跳 watchdog 判断 invocation 是真卡死(pipeline 存活)还是孤儿(进程已脱管)。
581
- */
414
+ /** @deprecated Use hasActiveTurn(). */
582
415
  hasActivePipeline(sessionId) {
583
- return this.pipelines.has(sessionId);
416
+ return this.runtimeCoordinator.hasActiveTurn(sessionId);
417
+ }
418
+ hasActiveTurn(sessionId) {
419
+ return this.runtimeCoordinator.hasActiveTurn(sessionId);
420
+ }
421
+ isAwaitingPermission(sessionId) {
422
+ return this.runtimeCoordinator.isAwaitingPermission(sessionId);
584
423
  }
585
424
  /**
586
425
  * 节流写入 TeamRun invocation 的心跳时间戳。非 TeamRun session 无对应 invocation,updateMany 命中 0 行无副作用。
@@ -601,37 +440,16 @@ export class SessionManager {
601
440
  });
602
441
  }
603
442
  writeInput(sessionId, data) {
604
- const pipeline = this.pipelines.get(sessionId);
605
- if (!pipeline)
606
- return;
607
- pipeline.write(data);
443
+ this.runtimeCoordinator.writeInput(sessionId, data);
608
444
  }
609
445
  resize(sessionId, cols, rows) {
610
- const pipeline = this.pipelines.get(sessionId);
611
- if (!pipeline)
612
- return;
613
- pipeline.resize(cols, rows);
446
+ this.runtimeCoordinator.resize(sessionId, cols, rows);
614
447
  }
615
- /**
616
- * Destroy all active pipelines. Called on graceful server shutdown.
617
- */
618
- destroyAll() {
619
- if (this.pipelines.size > 0) {
620
- console.log(`[SessionManager] Destroying all ${this.pipelines.size} active pipelines`);
621
- }
622
- for (const [sessionId, pipeline] of this.pipelines) {
623
- const cancel = this.cancelTokens.get(sessionId);
624
- if (cancel) {
625
- cancel.cancel();
626
- }
627
- pipeline.destroy();
628
- }
629
- this.pipelines.clear();
630
- this.cancelTokens.clear();
631
- for (const timer of this.logicalCompletionCleanupTimers.values()) {
632
- clearTimeout(timer);
633
- }
634
- this.logicalCompletionCleanupTimers.clear();
448
+ /** Close all runtime driver sessions during graceful server shutdown. */
449
+ async destroyAll() {
450
+ await this.runtimeCoordinator.destroyAll();
451
+ await Promise.allSettled(this.externalSessionPersistence.values());
452
+ this.externalSessionPersistence.clear();
635
453
  this.terminalSessions.clear();
636
454
  for (const resolve of this.pendingAutoCommitResolvers.values())
637
455
  resolve();
@@ -661,163 +479,208 @@ export class SessionManager {
661
479
  }
662
480
  return null;
663
481
  }
664
- resolveExecutor(agentType, variant, providerId) {
665
- let executor;
666
- try {
667
- executor = providerId
668
- ? getExecutorByProvider(providerId)
669
- : getExecutor(agentType, variant ?? 'DEFAULT');
482
+ async startRuntimeTurn(session, prompt, resumeExternalSessionId, providerId = session.providerId, resumeMode = 'load') {
483
+ const workingDir = this.getExecutionWorkingDir(session);
484
+ const env = ExecutionEnv.default(workingDir);
485
+ if (providerId) {
486
+ const provider = getProviderById(providerId);
487
+ if (provider && Object.keys(provider.env).length > 0) {
488
+ env.merge(filterAgentSubprocessExternalEnv(provider.env));
489
+ }
670
490
  }
671
- catch (error) {
672
- throw new ExecutorConfigurationError(agentType, error, providerId);
491
+ this.injectAgentTowerMcpServiceEnv(env);
492
+ if (!this.isConversationSession(session)) {
493
+ await this.injectTeamRunInvocationEnv(session.id, env);
673
494
  }
674
- if (!executor) {
675
- throw new ExecutorNotFoundError(agentType, providerId);
495
+ const isNewStore = !sessionMsgStoreManager.has(session.id);
496
+ const msgStore = sessionMsgStoreManager.getOrCreate(session.id);
497
+ if (isNewStore && session.logSnapshot) {
498
+ try {
499
+ msgStore.restoreFromSnapshot(JSON.parse(session.logSnapshot));
500
+ }
501
+ catch (error) {
502
+ this.logSessionError('session.snapshotRestore', error, { sessionId: session.id });
503
+ }
676
504
  }
677
- return executor;
678
- }
679
- async activateSpawnedSession(sessionId, agentType, workingDir, spawnResult) {
680
505
  try {
681
- await this.registerSpawnedSessionIfTaskLive(sessionId, spawnResult);
682
- this.attachPipeline(sessionId, agentType, workingDir, spawnResult);
683
- this.pendingSpawns.delete(sessionId);
684
- this.eventBus.emit('session:started', { sessionId });
685
- await this.checkTaskAutoRevert(sessionId);
506
+ // Session status follows the logical Runtime turn, not the lifetime of
507
+ // its backing OS process. ACP reuses one adapter process across turns,
508
+ // so a follow-up must become RUNNING even when no process starts.
509
+ await prisma.session.update({
510
+ where: { id: session.id },
511
+ data: { status: SessionStatus.RUNNING },
512
+ });
513
+ const handle = await this.runtimeCoordinator.startTurn({
514
+ towerSessionId: session.id,
515
+ agentType: session.agentType,
516
+ runtimeType: this.normalizeRuntimeType(session.runtimeType),
517
+ variant: session.variant ?? 'DEFAULT',
518
+ providerId,
519
+ workingDir,
520
+ env,
521
+ externalSessionId: session.externalSessionId,
522
+ msgStore,
523
+ prompt,
524
+ resumeExternalSessionId,
525
+ resumeMode,
526
+ });
527
+ // Terminal persistence is driven by Runtime turn events. Attach a catch
528
+ // so the public start/message methods do not leave a rejected handle
529
+ // unobserved after they have returned to the HTTP caller.
530
+ void handle.completion.catch(() => undefined);
531
+ this.eventBus.emit('session:started', { sessionId: session.id });
532
+ await this.checkTaskAutoRevert(session.id);
686
533
  }
687
534
  catch (error) {
688
- await this.cancelSpawnedSession(sessionId, spawnResult);
689
- throw error;
535
+ await this.runtimeCoordinator.disposeSession(session.id).catch(() => undefined);
536
+ await prisma.session.update({
537
+ where: { id: session.id },
538
+ data: { status: SessionStatus.CANCELLED },
539
+ }).catch(() => undefined);
540
+ sessionMsgStoreManager.delete(session.id);
541
+ this.releaseSnapshotPersistenceState(session.id);
542
+ this.logSessionError('session.runtimeStart', error, {
543
+ sessionId: session.id,
544
+ agentType: session.agentType,
545
+ runtimeType: session.runtimeType,
546
+ providerId,
547
+ workingDir,
548
+ });
549
+ throw normalizeExecutorStartError(error);
690
550
  }
691
551
  }
692
- attachPipeline(sessionId, agentType, workingDir, spawnResult) {
693
- const msgStore = sessionMsgStoreManager.getOrCreate(sessionId);
694
- const parser = this.createParser(agentType, workingDir, msgStore);
695
- // takeEarlyEvents 交回 spawn→attach 窗口内触发的 PTY data/exit 事件;
696
- // AgentPipeline 构造时同步重放,避免快速退出的进程丢失 exit(session 永久 RUNNING)。
697
- const pipeline = new AgentPipeline(sessionId, spawnResult.pty, parser, msgStore, this.eventBus, spawnResult.takeEarlyEvents?.());
698
- // 重放的 exit 会让 pipeline 在构造期间就自毁;已死的 pipeline 不能进 map,
699
- // 否则 hasActivePipeline 会误报存活。
700
- if (pipeline.isAlive) {
701
- this.pipelines.set(sessionId, pipeline);
702
- if (spawnResult.cancel) {
703
- this.cancelTokens.set(sessionId, spawnResult.cancel);
704
- }
552
+ handleRuntimeTurnEvent(envelope) {
553
+ const sessionId = envelope.towerSessionId;
554
+ const event = envelope.event;
555
+ if (event.type === 'stdout') {
556
+ this.eventBus.emit('session:stdout', { sessionId, data: event.data });
557
+ return;
705
558
  }
706
- }
707
- async registerSpawnedSessionIfTaskLive(sessionId, spawnResult) {
708
- this.pendingSpawns.set(sessionId, spawnResult);
709
- try {
710
- await prisma.$transaction(async (tx) => {
711
- const session = await tx.session.findUnique({
559
+ if (event.type === 'conversation_patch') {
560
+ this.eventBus.emit('session:patch', { sessionId, patch: event.patch, seq: event.seq });
561
+ return;
562
+ }
563
+ if (event.type === 'external_session_id') {
564
+ const previous = this.externalSessionPersistence.get(sessionId) ?? Promise.resolve();
565
+ const persistence = previous
566
+ .then(() => this.enqueueSessionPersistenceWrite(async () => {
567
+ await prisma.session.update({
712
568
  where: { id: sessionId },
713
- include: { workspace: { include: { task: true } }, conversation: true },
569
+ data: { externalSessionId: event.externalSessionId },
714
570
  });
715
- if (!session) {
716
- throw new NotFoundError('Session', sessionId);
717
- }
718
- if (this.isConversationSession(session)) {
719
- if (!session.conversation || session.conversation.deletedAt) {
720
- throw new NotFoundError('Conversation', session.conversationId ?? sessionId);
721
- }
722
- }
723
- else {
724
- if (!session.workspace) {
725
- throw new NotFoundError('Workspace', session.workspaceId ?? sessionId);
726
- }
727
- ensureTaskNotDeleted(session.workspace.task);
571
+ }))
572
+ .then(() => undefined)
573
+ .catch((error) => {
574
+ this.logSessionError('session.externalSessionId', error, { sessionId });
575
+ });
576
+ this.externalSessionPersistence.set(sessionId, persistence);
577
+ void persistence.finally(() => {
578
+ if (this.externalSessionPersistence.get(sessionId) === persistence) {
579
+ this.externalSessionPersistence.delete(sessionId);
728
580
  }
729
- await tx.session.update({
730
- where: { id: sessionId },
731
- data: { status: SessionStatus.RUNNING },
732
- });
733
- await tx.executionProcess.create({
734
- data: {
735
- sessionId,
736
- pid: spawnResult.pid,
737
- },
738
- });
739
581
  });
740
- const session = await prisma.session.findUnique({
741
- where: { id: sessionId },
742
- include: { workspace: { include: { task: true } }, conversation: true },
582
+ this.eventBus.emit('session:sessionId', {
583
+ sessionId,
584
+ agentSessionId: event.externalSessionId,
743
585
  });
744
- if (!session) {
745
- throw new NotFoundError('Session', sessionId);
746
- }
747
- if (this.isConversationSession(session)) {
748
- if (!session.conversation || session.conversation.deletedAt) {
749
- throw new NotFoundError('Conversation', session.conversationId ?? sessionId);
750
- }
751
- }
752
- else {
753
- if (!session.workspace) {
754
- throw new NotFoundError('Workspace', session.workspaceId ?? sessionId);
755
- }
756
- ensureTaskNotDeleted(session.workspace.task);
757
- }
758
- if (this.pendingSpawns.get(sessionId) !== spawnResult) {
759
- throw new NotFoundError(this.isConversationSession(session) ? 'Conversation' : 'Task', this.isConversationSession(session)
760
- ? (session.conversationId ?? sessionId)
761
- : (session.workspace?.task.id ?? sessionId));
762
- }
586
+ return;
763
587
  }
764
- catch (error) {
765
- await this.cancelSpawnedSession(sessionId, spawnResult);
766
- throw error;
588
+ if (event.type === 'permission_requested') {
589
+ this.eventBus.emit('session:permission_requested', {
590
+ sessionId,
591
+ permission: event.request,
592
+ });
593
+ return;
767
594
  }
768
- }
769
- async cancelSpawnedSession(sessionId, spawnResult) {
770
- if (this.pendingSpawns.get(sessionId) === spawnResult) {
771
- this.pendingSpawns.delete(sessionId);
595
+ if (event.type === 'permission_invalidated') {
596
+ this.eventBus.emit('session:permission_invalidated', {
597
+ sessionId,
598
+ turnId: envelope.turnId,
599
+ requestId: event.requestId,
600
+ });
601
+ return;
772
602
  }
773
- const pipeline = this.pipelines.get(sessionId);
774
- if (pipeline) {
775
- try {
776
- pipeline.destroy();
777
- }
778
- catch {
779
- // Continue with explicit PTY cancellation even if pipeline teardown fails.
780
- }
781
- this.pipelines.delete(sessionId);
782
- this.cancelTokens.delete(sessionId);
603
+ if (event.type === 'completed') {
604
+ this.eventBus.emit('session:turn-completed', { sessionId });
605
+ this.eventBus.emit('session:exit', { sessionId, exitCode: 0 });
606
+ return;
783
607
  }
784
- try {
785
- spawnResult.cancel?.cancel();
608
+ if (event.type === 'failed') {
609
+ this.eventBus.emit('session:turn-failed', { sessionId });
610
+ this.eventBus.emit('session:exit', { sessionId, exitCode: 1 });
786
611
  }
787
- catch {
788
- // ignore cancellation failures while compensating a deleted task race
612
+ }
613
+ handleRuntimeState(state) {
614
+ setRuntimeStateSnapshot(state);
615
+ this.eventBus.emit('session:runtime_state_changed', {
616
+ sessionId: state.sessionId,
617
+ state,
618
+ });
619
+ const awaitingPermission = state.turnState === 'AWAITING_PERMISSION';
620
+ const previous = this.runtimePermissionStates.get(state.sessionId) ?? false;
621
+ if (state.turnState === 'DISPOSED') {
622
+ this.runtimePermissionStates.delete(state.sessionId);
789
623
  }
790
- try {
791
- spawnResult.pty.kill();
624
+ else {
625
+ this.runtimePermissionStates.set(state.sessionId, awaitingPermission);
792
626
  }
793
- catch {
794
- // ignore kill failures while compensating a deleted task race
627
+ if (previous !== awaitingPermission) {
628
+ void this.invalidateTeamRunRuntimeState(state.sessionId);
795
629
  }
796
- await prisma.session.update({
797
- where: { id: sessionId },
798
- data: { status: SessionStatus.CANCELLED },
799
- }).catch(() => {
800
- // session may already have been removed by cleanup
630
+ }
631
+ async invalidateTeamRunRuntimeState(sessionId) {
632
+ const invocation = await prisma.agentInvocation.findFirst({
633
+ where: { sessionId },
634
+ select: {
635
+ teamRunId: true,
636
+ teamRun: { select: { taskId: true, task: { select: { projectId: true } } } },
637
+ },
801
638
  });
802
- await prisma.executionProcess.deleteMany({
803
- where: { sessionId, pid: spawnResult.pid },
804
- }).catch(() => {
805
- // process row may not have been created yet
639
+ if (!invocation)
640
+ return;
641
+ this.eventBus.emit('team-run:invalidated', {
642
+ teamRunId: invocation.teamRunId,
643
+ taskId: invocation.teamRun.taskId,
644
+ projectId: invocation.teamRun.task.projectId,
645
+ scopes: ['team-members', 'agent-invocations', 'team-run'],
646
+ reason: 'agent-invocation-updated',
806
647
  });
807
- // 补偿路径同样释放 MsgStore(sendMessage 在 spawn 前已 getOrCreate)
808
- sessionMsgStoreManager.delete(sessionId);
809
648
  }
810
- createParser(agentType, workingDir, msgStore) {
811
- if (agentType === AgentType.CLAUDE_CODE) {
812
- return createClaudeCodeParser(msgStore);
813
- }
814
- if (agentType === AgentType.CURSOR_AGENT) {
815
- return createCursorAgentParser(msgStore, workingDir);
816
- }
817
- if (agentType === AgentType.CODEX) {
818
- return createCodexParser(msgStore);
649
+ async handleRuntimeProcessEvent(event) {
650
+ if (event.type === 'started') {
651
+ const processRecord = await prisma.$transaction(async (tx) => {
652
+ const session = await tx.session.findUnique({
653
+ where: { id: event.towerSessionId },
654
+ include: { workspace: { include: { task: true } }, conversation: true },
655
+ });
656
+ if (!session)
657
+ throw new NotFoundError('Session', event.towerSessionId);
658
+ this.ensureExecutionRecordIsLive(session);
659
+ return tx.executionProcess.create({
660
+ data: { sessionId: event.towerSessionId, pid: event.pid },
661
+ select: { id: true },
662
+ });
663
+ });
664
+ this.runtimeProcessIds.set(event.runtimeInstanceId, processRecord.id);
665
+ return;
819
666
  }
820
- return null;
667
+ const processId = this.runtimeProcessIds.get(event.runtimeInstanceId);
668
+ if (!processId)
669
+ return;
670
+ this.runtimeProcessIds.delete(event.runtimeInstanceId);
671
+ await prisma.executionProcess.update({
672
+ where: { id: processId },
673
+ data: { exitCode: event.exitCode },
674
+ }).catch((error) => {
675
+ this.logSessionError('session.processExit', error, {
676
+ sessionId: event.towerSessionId,
677
+ processId,
678
+ exitCode: event.exitCode,
679
+ });
680
+ });
681
+ }
682
+ normalizeRuntimeType(value) {
683
+ return value === RuntimeType.ACP ? RuntimeType.ACP : RuntimeType.CLI;
821
684
  }
822
685
  injectAgentTowerMcpServiceEnv(env) {
823
686
  const serviceEnv = {};
@@ -954,7 +817,7 @@ export class SessionManager {
954
817
  .catch(() => {
955
818
  // Keep the chain alive even if previous flush failed.
956
819
  })
957
- .then(() => this.enqueueSnapshotWrite(() => this.persistSnapshot(sessionId)));
820
+ .then(() => this.enqueueSessionPersistenceWrite(() => this.persistSnapshot(sessionId)));
958
821
  this.snapshotFlushChains.set(sessionId, current);
959
822
  try {
960
823
  await current;
@@ -965,19 +828,19 @@ export class SessionManager {
965
828
  }
966
829
  }
967
830
  }
968
- async enqueueSnapshotWrite(write) {
969
- const queued = this.snapshotWriterChain
831
+ async enqueueSessionPersistenceWrite(write) {
832
+ const queued = this.sessionPersistenceWriterChain
970
833
  .catch(() => {
971
834
  // Keep the global writer alive after an isolated persistence failure.
972
835
  })
973
836
  .then(write);
974
- this.snapshotWriterChain = queued;
837
+ this.sessionPersistenceWriterChain = queued;
975
838
  try {
976
839
  await queued;
977
840
  }
978
841
  finally {
979
- if (this.snapshotWriterChain === queued) {
980
- this.snapshotWriterChain = Promise.resolve();
842
+ if (this.sessionPersistenceWriterChain === queued) {
843
+ this.sessionPersistenceWriterChain = Promise.resolve();
981
844
  }
982
845
  }
983
846
  }
@@ -1201,6 +1064,7 @@ export class SessionManager {
1201
1064
  */
1202
1065
  async handleSessionExit(sessionId, exitCode, options = {}) {
1203
1066
  const generation = options.generation ?? this.sessionGenerations.get(sessionId);
1067
+ await this.externalSessionPersistence.get(sessionId);
1204
1068
  const session = await prisma.session.findUnique({
1205
1069
  where: { id: sessionId },
1206
1070
  select: { purpose: true, context: true, conversationId: true },
@@ -1333,7 +1197,6 @@ export class SessionManager {
1333
1197
  }
1334
1198
  clearTerminalState(sessionId) {
1335
1199
  this.terminalSessions.delete(sessionId);
1336
- this.clearLogicalCompletionCleanup(sessionId);
1337
1200
  }
1338
1201
  startSessionFinalization(sessionId, exitCode, options = {}) {
1339
1202
  if (options.logicalCompletion) {
@@ -1362,10 +1225,6 @@ export class SessionManager {
1362
1225
  this.sessionFinalizations.set(sessionId, finalization);
1363
1226
  }
1364
1227
  maybeClearTerminalState(sessionId) {
1365
- if (this.pipelines.has(sessionId))
1366
- return;
1367
- if (this.logicalCompletionCleanupTimers.has(sessionId))
1368
- return;
1369
1228
  if (this.sessionFinalizations.has(sessionId))
1370
1229
  return;
1371
1230
  this.terminalSessions.delete(sessionId);
@@ -1426,30 +1285,6 @@ export class SessionManager {
1426
1285
  return;
1427
1286
  }
1428
1287
  }
1429
- destroyActivePipeline(sessionId) {
1430
- const pipeline = this.pipelines.get(sessionId);
1431
- if (pipeline) {
1432
- pipeline.destroy();
1433
- this.pipelines.delete(sessionId);
1434
- }
1435
- this.cancelTokens.delete(sessionId);
1436
- this.heartbeatThrottle.delete(sessionId);
1437
- }
1438
- clearLogicalCompletionCleanup(sessionId) {
1439
- const timer = this.logicalCompletionCleanupTimers.get(sessionId);
1440
- if (timer)
1441
- clearTimeout(timer);
1442
- this.logicalCompletionCleanupTimers.delete(sessionId);
1443
- }
1444
- scheduleLogicalCompletionCleanup(sessionId) {
1445
- this.clearLogicalCompletionCleanup(sessionId);
1446
- const timer = setTimeout(() => {
1447
- this.logicalCompletionCleanupTimers.delete(sessionId);
1448
- this.destroyActivePipeline(sessionId);
1449
- this.maybeClearTerminalState(sessionId);
1450
- }, SessionManager.CODEX_TURN_COMPLETION_GRACE_MS);
1451
- this.logicalCompletionCleanupTimers.set(sessionId, timer);
1452
- }
1453
1288
  logSessionError(source, error, metadata) {
1454
1289
  writeErrorLog({
1455
1290
  level: 'error',