agent-tower 0.7.2-beta.6 → 0.7.2-beta.8

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 (555) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +4 -1
  3. package/dist/app.js.map +1 -1
  4. package/dist/app.test.js +80 -1
  5. package/dist/app.test.js.map +1 -1
  6. package/dist/cli.js +4 -10
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/container.d.ts +8 -0
  9. package/dist/core/container.d.ts.map +1 -1
  10. package/dist/core/container.js +42 -0
  11. package/dist/core/container.js.map +1 -1
  12. package/dist/core/event-bus.d.ts +2 -0
  13. package/dist/core/event-bus.d.ts.map +1 -1
  14. package/dist/core/event-bus.js.map +1 -1
  15. package/dist/executors/__tests__/image-utils.test.d.ts +2 -0
  16. package/dist/executors/__tests__/image-utils.test.d.ts.map +1 -0
  17. package/dist/executors/__tests__/image-utils.test.js +25 -0
  18. package/dist/executors/__tests__/image-utils.test.js.map +1 -0
  19. package/dist/executors/base.executor.d.ts +2 -0
  20. package/dist/executors/base.executor.d.ts.map +1 -1
  21. package/dist/executors/base.executor.js +6 -0
  22. package/dist/executors/base.executor.js.map +1 -1
  23. package/dist/executors/image-utils.d.ts.map +1 -1
  24. package/dist/executors/image-utils.js +10 -8
  25. package/dist/executors/image-utils.js.map +1 -1
  26. package/dist/git/git-cli.d.ts +2 -0
  27. package/dist/git/git-cli.d.ts.map +1 -1
  28. package/dist/git/git-cli.js +2 -0
  29. package/dist/git/git-cli.js.map +1 -1
  30. package/dist/git/worktree.manager.d.ts +2 -1
  31. package/dist/git/worktree.manager.d.ts.map +1 -1
  32. package/dist/git/worktree.manager.js +18 -12
  33. package/dist/git/worktree.manager.js.map +1 -1
  34. package/dist/index.js +16 -31
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/http-client.d.ts +1 -0
  37. package/dist/mcp/http-client.d.ts.map +1 -1
  38. package/dist/mcp/http-client.js +3 -0
  39. package/dist/mcp/http-client.js.map +1 -1
  40. package/dist/mcp/server.d.ts.map +1 -1
  41. package/dist/mcp/server.js +15 -0
  42. package/dist/mcp/server.js.map +1 -1
  43. package/dist/mcp/tools/sessions.js +2 -2
  44. package/dist/mcp/tools/sessions.js.map +1 -1
  45. package/dist/mcp/tools/workspaces.d.ts.map +1 -1
  46. package/dist/mcp/tools/workspaces.js +2 -1
  47. package/dist/mcp/tools/workspaces.js.map +1 -1
  48. package/dist/middleware/access-auth.d.ts +1 -0
  49. package/dist/middleware/access-auth.d.ts.map +1 -1
  50. package/dist/middleware/access-auth.js +1 -1
  51. package/dist/middleware/access-auth.js.map +1 -1
  52. package/dist/output/__tests__/text-block-lifecycle.test.d.ts +2 -0
  53. package/dist/output/__tests__/text-block-lifecycle.test.d.ts.map +1 -0
  54. package/dist/output/__tests__/text-block-lifecycle.test.js +241 -0
  55. package/dist/output/__tests__/text-block-lifecycle.test.js.map +1 -0
  56. package/dist/output/claude-code-parser.d.ts +3 -0
  57. package/dist/output/claude-code-parser.d.ts.map +1 -1
  58. package/dist/output/claude-code-parser.js +58 -7
  59. package/dist/output/claude-code-parser.js.map +1 -1
  60. package/dist/output/codex-parser.d.ts.map +1 -1
  61. package/dist/output/codex-parser.js +4 -6
  62. package/dist/output/codex-parser.js.map +1 -1
  63. package/dist/output/cursor-agent-parser.d.ts.map +1 -1
  64. package/dist/output/cursor-agent-parser.js +4 -0
  65. package/dist/output/cursor-agent-parser.js.map +1 -1
  66. package/dist/output/gemini-parser.d.ts +18 -0
  67. package/dist/output/gemini-parser.d.ts.map +1 -0
  68. package/dist/output/gemini-parser.js +91 -0
  69. package/dist/output/gemini-parser.js.map +1 -0
  70. package/dist/output/msg-store.d.ts +6 -0
  71. package/dist/output/msg-store.d.ts.map +1 -1
  72. package/dist/output/msg-store.js +17 -0
  73. package/dist/output/msg-store.js.map +1 -1
  74. package/dist/output/text-block-tracker.d.ts +29 -0
  75. package/dist/output/text-block-tracker.d.ts.map +1 -0
  76. package/dist/output/text-block-tracker.js +149 -0
  77. package/dist/output/text-block-tracker.js.map +1 -0
  78. package/dist/output/types.d.ts +9 -0
  79. package/dist/output/types.d.ts.map +1 -1
  80. package/dist/output/types.js +0 -4
  81. package/dist/output/types.js.map +1 -1
  82. package/dist/routes/__tests__/sessions-runtime.test.js +3 -0
  83. package/dist/routes/__tests__/sessions-runtime.test.js.map +1 -1
  84. package/dist/routes/__tests__/team-run-translations.test.d.ts +2 -0
  85. package/dist/routes/__tests__/team-run-translations.test.d.ts.map +1 -0
  86. package/dist/routes/__tests__/team-run-translations.test.js +418 -0
  87. package/dist/routes/__tests__/team-run-translations.test.js.map +1 -0
  88. package/dist/routes/__tests__/translations.test.d.ts +2 -0
  89. package/dist/routes/__tests__/translations.test.d.ts.map +1 -0
  90. package/dist/routes/__tests__/translations.test.js +286 -0
  91. package/dist/routes/__tests__/translations.test.js.map +1 -0
  92. package/dist/routes/__tests__/workspaces.test.js +140 -0
  93. package/dist/routes/__tests__/workspaces.test.js.map +1 -1
  94. package/dist/routes/conversations.d.ts.map +1 -1
  95. package/dist/routes/conversations.js +19 -2
  96. package/dist/routes/conversations.js.map +1 -1
  97. package/dist/routes/index.d.ts.map +1 -1
  98. package/dist/routes/index.js +2 -0
  99. package/dist/routes/index.js.map +1 -1
  100. package/dist/routes/sessions.d.ts.map +1 -1
  101. package/dist/routes/sessions.js +32 -3
  102. package/dist/routes/sessions.js.map +1 -1
  103. package/dist/routes/team-runs.d.ts.map +1 -1
  104. package/dist/routes/team-runs.js +84 -15
  105. package/dist/routes/team-runs.js.map +1 -1
  106. package/dist/routes/translations.d.ts +4 -0
  107. package/dist/routes/translations.d.ts.map +1 -0
  108. package/dist/routes/translations.js +148 -0
  109. package/dist/routes/translations.js.map +1 -0
  110. package/dist/routes/user-input-origin.d.ts +4 -0
  111. package/dist/routes/user-input-origin.d.ts.map +1 -0
  112. package/dist/routes/user-input-origin.js +7 -0
  113. package/dist/routes/user-input-origin.js.map +1 -0
  114. package/dist/routes/workspaces.d.ts.map +1 -1
  115. package/dist/routes/workspaces.js +32 -2
  116. package/dist/routes/workspaces.js.map +1 -1
  117. package/dist/runtime/__tests__/acp-driver-lifecycle.test.js +2 -2
  118. package/dist/runtime/__tests__/acp-driver-lifecycle.test.js.map +1 -1
  119. package/dist/runtime/__tests__/acp-driver-submission.test.d.ts +2 -0
  120. package/dist/runtime/__tests__/acp-driver-submission.test.d.ts.map +1 -0
  121. package/dist/runtime/__tests__/acp-driver-submission.test.js +88 -0
  122. package/dist/runtime/__tests__/acp-driver-submission.test.js.map +1 -0
  123. package/dist/runtime/__tests__/acp-history-reconciler.test.js +244 -0
  124. package/dist/runtime/__tests__/acp-history-reconciler.test.js.map +1 -1
  125. package/dist/runtime/__tests__/acp-projector.test.js +36 -0
  126. package/dist/runtime/__tests__/acp-projector.test.js.map +1 -1
  127. package/dist/runtime/__tests__/claude-cli-control.test.js +20 -0
  128. package/dist/runtime/__tests__/claude-cli-control.test.js.map +1 -1
  129. package/dist/runtime/__tests__/cli-driver-submission.test.d.ts +2 -0
  130. package/dist/runtime/__tests__/cli-driver-submission.test.d.ts.map +1 -0
  131. package/dist/runtime/__tests__/cli-driver-submission.test.js +61 -0
  132. package/dist/runtime/__tests__/cli-driver-submission.test.js.map +1 -0
  133. package/dist/runtime/__tests__/codex-cli-control.test.js +25 -0
  134. package/dist/runtime/__tests__/codex-cli-control.test.js.map +1 -1
  135. package/dist/runtime/acp/acp-driver.js +38 -1
  136. package/dist/runtime/acp/acp-driver.js.map +1 -1
  137. package/dist/runtime/acp/history-reconciler.d.ts.map +1 -1
  138. package/dist/runtime/acp/history-reconciler.js +95 -36
  139. package/dist/runtime/acp/history-reconciler.js.map +1 -1
  140. package/dist/runtime/acp/projector.d.ts +5 -2
  141. package/dist/runtime/acp/projector.d.ts.map +1 -1
  142. package/dist/runtime/acp/projector.js +50 -23
  143. package/dist/runtime/acp/projector.js.map +1 -1
  144. package/dist/runtime/claude-cli-control.d.ts +4 -1
  145. package/dist/runtime/claude-cli-control.d.ts.map +1 -1
  146. package/dist/runtime/claude-cli-control.js +3 -1
  147. package/dist/runtime/claude-cli-control.js.map +1 -1
  148. package/dist/runtime/cli-driver.js +21 -7
  149. package/dist/runtime/cli-driver.js.map +1 -1
  150. package/dist/runtime/cli-parser.d.ts.map +1 -1
  151. package/dist/runtime/cli-parser.js +3 -0
  152. package/dist/runtime/cli-parser.js.map +1 -1
  153. package/dist/runtime/codex-cli-control.d.ts +4 -1
  154. package/dist/runtime/codex-cli-control.d.ts.map +1 -1
  155. package/dist/runtime/codex-cli-control.js +9 -6
  156. package/dist/runtime/codex-cli-control.js.map +1 -1
  157. package/dist/runtime/codex-cli-projector.d.ts +1 -1
  158. package/dist/runtime/codex-cli-projector.d.ts.map +1 -1
  159. package/dist/runtime/codex-cli-projector.js +4 -2
  160. package/dist/runtime/codex-cli-projector.js.map +1 -1
  161. package/dist/runtime/contracts.d.ts +15 -2
  162. package/dist/runtime/contracts.d.ts.map +1 -1
  163. package/dist/runtime/runtime-coordinator.d.ts.map +1 -1
  164. package/dist/runtime/runtime-coordinator.js +34 -1
  165. package/dist/runtime/runtime-coordinator.js.map +1 -1
  166. package/dist/services/__tests__/conflict-admission.test.d.ts +2 -0
  167. package/dist/services/__tests__/conflict-admission.test.d.ts.map +1 -0
  168. package/dist/services/__tests__/conflict-admission.test.js +37 -0
  169. package/dist/services/__tests__/conflict-admission.test.js.map +1 -0
  170. package/dist/services/__tests__/conflict-recovery-key.test.d.ts +2 -0
  171. package/dist/services/__tests__/conflict-recovery-key.test.d.ts.map +1 -0
  172. package/dist/services/__tests__/conflict-recovery-key.test.js +143 -0
  173. package/dist/services/__tests__/conflict-recovery-key.test.js.map +1 -0
  174. package/dist/services/__tests__/message-delivery.integration.test.d.ts +2 -0
  175. package/dist/services/__tests__/message-delivery.integration.test.d.ts.map +1 -0
  176. package/dist/services/__tests__/message-delivery.integration.test.js +1137 -0
  177. package/dist/services/__tests__/message-delivery.integration.test.js.map +1 -0
  178. package/dist/services/__tests__/review-team-user-counterexamples.test.d.ts +2 -0
  179. package/dist/services/__tests__/review-team-user-counterexamples.test.d.ts.map +1 -0
  180. package/dist/services/__tests__/review-team-user-counterexamples.test.js +200 -0
  181. package/dist/services/__tests__/review-team-user-counterexamples.test.js.map +1 -0
  182. package/dist/services/__tests__/session-manager.lifecycle.test.js +37 -0
  183. package/dist/services/__tests__/session-manager.lifecycle.test.js.map +1 -1
  184. package/dist/services/__tests__/session-manager.team-run.test.js +19 -2
  185. package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -1
  186. package/dist/services/__tests__/team-initial-delivery.integration.test.d.ts +2 -0
  187. package/dist/services/__tests__/team-initial-delivery.integration.test.d.ts.map +1 -0
  188. package/dist/services/__tests__/team-initial-delivery.integration.test.js +469 -0
  189. package/dist/services/__tests__/team-initial-delivery.integration.test.js.map +1 -0
  190. package/dist/services/__tests__/team-reconciler.service.test.js +102 -101
  191. package/dist/services/__tests__/team-reconciler.service.test.js.map +1 -1
  192. package/dist/services/__tests__/team-room-delivery.test.d.ts +2 -0
  193. package/dist/services/__tests__/team-room-delivery.test.d.ts.map +1 -0
  194. package/dist/services/__tests__/team-room-delivery.test.js +670 -0
  195. package/dist/services/__tests__/team-room-delivery.test.js.map +1 -0
  196. package/dist/services/__tests__/team-user-delivery.integration.test.d.ts +2 -0
  197. package/dist/services/__tests__/team-user-delivery.integration.test.d.ts.map +1 -0
  198. package/dist/services/__tests__/team-user-delivery.integration.test.js +622 -0
  199. package/dist/services/__tests__/team-user-delivery.integration.test.js.map +1 -0
  200. package/dist/services/__tests__/translation-client.test.d.ts +2 -0
  201. package/dist/services/__tests__/translation-client.test.d.ts.map +1 -0
  202. package/dist/services/__tests__/translation-client.test.js +240 -0
  203. package/dist/services/__tests__/translation-client.test.js.map +1 -0
  204. package/dist/services/__tests__/translation-protection.test.d.ts +2 -0
  205. package/dist/services/__tests__/translation-protection.test.d.ts.map +1 -0
  206. package/dist/services/__tests__/translation-protection.test.js +156 -0
  207. package/dist/services/__tests__/translation-protection.test.js.map +1 -0
  208. package/dist/services/__tests__/translation-queue.test.d.ts +2 -0
  209. package/dist/services/__tests__/translation-queue.test.d.ts.map +1 -0
  210. package/dist/services/__tests__/translation-queue.test.js +277 -0
  211. package/dist/services/__tests__/translation-queue.test.js.map +1 -0
  212. package/dist/services/__tests__/translation-room-admission.test.d.ts +2 -0
  213. package/dist/services/__tests__/translation-room-admission.test.d.ts.map +1 -0
  214. package/dist/services/__tests__/translation-room-admission.test.js +371 -0
  215. package/dist/services/__tests__/translation-room-admission.test.js.map +1 -0
  216. package/dist/services/__tests__/translation-session-blocks.test.d.ts +2 -0
  217. package/dist/services/__tests__/translation-session-blocks.test.d.ts.map +1 -0
  218. package/dist/services/__tests__/translation-session-blocks.test.js +367 -0
  219. package/dist/services/__tests__/translation-session-blocks.test.js.map +1 -0
  220. package/dist/services/__tests__/translation-settings.service.test.d.ts +2 -0
  221. package/dist/services/__tests__/translation-settings.service.test.d.ts.map +1 -0
  222. package/dist/services/__tests__/translation-settings.service.test.js +154 -0
  223. package/dist/services/__tests__/translation-settings.service.test.js.map +1 -0
  224. package/dist/services/__tests__/translation.service.test.d.ts +2 -0
  225. package/dist/services/__tests__/translation.service.test.d.ts.map +1 -0
  226. package/dist/services/__tests__/translation.service.test.js +480 -0
  227. package/dist/services/__tests__/translation.service.test.js.map +1 -0
  228. package/dist/services/access-auth.service.d.ts +1 -0
  229. package/dist/services/access-auth.service.d.ts.map +1 -1
  230. package/dist/services/access-auth.service.js +8 -0
  231. package/dist/services/access-auth.service.js.map +1 -1
  232. package/dist/services/attachment-context.d.ts +7 -1
  233. package/dist/services/attachment-context.d.ts.map +1 -1
  234. package/dist/services/attachment-context.js +23 -2
  235. package/dist/services/attachment-context.js.map +1 -1
  236. package/dist/services/conflict-admission.d.ts +37 -0
  237. package/dist/services/conflict-admission.d.ts.map +1 -0
  238. package/dist/services/conflict-admission.js +97 -0
  239. package/dist/services/conflict-admission.js.map +1 -0
  240. package/dist/services/conflict-resolution-context.d.ts +33 -0
  241. package/dist/services/conflict-resolution-context.d.ts.map +1 -0
  242. package/dist/services/conflict-resolution-context.js +434 -0
  243. package/dist/services/conflict-resolution-context.js.map +1 -0
  244. package/dist/services/message-delivery.service.d.ts +195 -0
  245. package/dist/services/message-delivery.service.d.ts.map +1 -0
  246. package/dist/services/message-delivery.service.js +956 -0
  247. package/dist/services/message-delivery.service.js.map +1 -0
  248. package/dist/services/project.service.d.ts +1 -1
  249. package/dist/services/session-manager.d.ts +94 -74
  250. package/dist/services/session-manager.d.ts.map +1 -1
  251. package/dist/services/session-manager.js +91 -17
  252. package/dist/services/session-manager.js.map +1 -1
  253. package/dist/services/task.service.d.ts +26 -20
  254. package/dist/services/task.service.d.ts.map +1 -1
  255. package/dist/services/task.service.js +9 -26
  256. package/dist/services/task.service.js.map +1 -1
  257. package/dist/services/team-room-delivery.d.ts +541 -0
  258. package/dist/services/team-room-delivery.d.ts.map +1 -0
  259. package/dist/services/team-room-delivery.js +123 -0
  260. package/dist/services/team-room-delivery.js.map +1 -0
  261. package/dist/services/team-run.service.d.ts +83 -0
  262. package/dist/services/team-run.service.d.ts.map +1 -1
  263. package/dist/services/team-run.service.js +207 -18
  264. package/dist/services/team-run.service.js.map +1 -1
  265. package/dist/services/team-scheduler.service.d.ts +3 -0
  266. package/dist/services/team-scheduler.service.d.ts.map +1 -1
  267. package/dist/services/team-scheduler.service.js +65 -11
  268. package/dist/services/team-scheduler.service.js.map +1 -1
  269. package/dist/services/team-task-input.d.ts +37 -0
  270. package/dist/services/team-task-input.d.ts.map +1 -0
  271. package/dist/services/team-task-input.js +98 -0
  272. package/dist/services/team-task-input.js.map +1 -0
  273. package/dist/services/team-task-input.service.d.ts +72 -0
  274. package/dist/services/team-task-input.service.d.ts.map +1 -0
  275. package/dist/services/team-task-input.service.js +205 -0
  276. package/dist/services/team-task-input.service.js.map +1 -0
  277. package/dist/services/translation-client.d.ts +31 -0
  278. package/dist/services/translation-client.d.ts.map +1 -0
  279. package/dist/services/translation-client.js +169 -0
  280. package/dist/services/translation-client.js.map +1 -0
  281. package/dist/services/translation-errors.d.ts +14 -0
  282. package/dist/services/translation-errors.d.ts.map +1 -0
  283. package/dist/services/translation-errors.js +16 -0
  284. package/dist/services/translation-errors.js.map +1 -0
  285. package/dist/services/translation-protection.d.ts +27 -0
  286. package/dist/services/translation-protection.d.ts.map +1 -0
  287. package/dist/services/translation-protection.js +547 -0
  288. package/dist/services/translation-protection.js.map +1 -0
  289. package/dist/services/translation-queue.d.ts +49 -0
  290. package/dist/services/translation-queue.d.ts.map +1 -0
  291. package/dist/services/translation-queue.js +211 -0
  292. package/dist/services/translation-queue.js.map +1 -0
  293. package/dist/services/translation-settings.service.d.ts +152 -0
  294. package/dist/services/translation-settings.service.d.ts.map +1 -0
  295. package/dist/services/translation-settings.service.js +174 -0
  296. package/dist/services/translation-settings.service.js.map +1 -0
  297. package/dist/services/translation.service.d.ts +168 -0
  298. package/dist/services/translation.service.d.ts.map +1 -0
  299. package/dist/services/translation.service.js +861 -0
  300. package/dist/services/translation.service.js.map +1 -0
  301. package/dist/services/workspace.service.d.ts +84 -93
  302. package/dist/services/workspace.service.d.ts.map +1 -1
  303. package/dist/services/workspace.service.js +69 -5
  304. package/dist/services/workspace.service.js.map +1 -1
  305. package/dist/socket/__tests__/socket-gateway.test.js +131 -1
  306. package/dist/socket/__tests__/socket-gateway.test.js.map +1 -1
  307. package/dist/socket/socket-gateway.d.ts +6 -0
  308. package/dist/socket/socket-gateway.d.ts.map +1 -1
  309. package/dist/socket/socket-gateway.js +312 -6
  310. package/dist/socket/socket-gateway.js.map +1 -1
  311. package/dist/utils/database-schema.d.ts +9 -0
  312. package/dist/utils/database-schema.d.ts.map +1 -0
  313. package/dist/utils/database-schema.js +434 -0
  314. package/dist/utils/database-schema.js.map +1 -0
  315. package/dist/utils/database-schema.test.d.ts +2 -0
  316. package/dist/utils/database-schema.test.d.ts.map +1 -0
  317. package/dist/utils/database-schema.test.js +359 -0
  318. package/dist/utils/database-schema.test.js.map +1 -0
  319. package/dist/utils/database-team-foundation.test.d.ts +2 -0
  320. package/dist/utils/database-team-foundation.test.d.ts.map +1 -0
  321. package/dist/utils/database-team-foundation.test.js +467 -0
  322. package/dist/utils/database-team-foundation.test.js.map +1 -0
  323. package/dist/utils/owned-child-process.test.js +6 -3
  324. package/dist/utils/owned-child-process.test.js.map +1 -1
  325. package/dist/utils/text-preview.d.ts +4 -0
  326. package/dist/utils/text-preview.d.ts.map +1 -0
  327. package/dist/utils/text-preview.js +21 -0
  328. package/dist/utils/text-preview.js.map +1 -0
  329. package/dist/web/assets/AgentDemoPage-CtCYXopw.js +1 -0
  330. package/dist/web/assets/{AgentEnvironmentSettingsPage-BnWVgvNe.js → AgentEnvironmentSettingsPage-DSWN546Y.js} +1 -1
  331. package/dist/web/assets/AttachmentPreview-DRG5_cfN.js +2 -0
  332. package/dist/web/assets/BranchSelect-Dxs7HnMM.js +1 -0
  333. package/dist/web/assets/BrandLogo-Ilm880Xs.js +1 -0
  334. package/dist/web/assets/ConversationPage-B55Bj_ft.js +1 -0
  335. package/dist/web/assets/CreateTaskInput-CxWPeToI.js +1 -0
  336. package/dist/web/assets/{DemoPage-BwX2SvcN.js → DemoPage-Dyn-U9JO.js} +3 -3
  337. package/dist/web/assets/DesignSystemPage-BxypsRUZ.js +12 -0
  338. package/dist/web/assets/EditableTaskTitle-Dh-SmvFW.js +70 -0
  339. package/dist/web/assets/{ExecutionPermissionSelector-DeeNd4Kh.js → ExecutionPermissionSelector-CQ_GNxR0.js} +10 -10
  340. package/dist/web/assets/GeneralSettingsPage-CzoejPZP.js +1 -0
  341. package/dist/web/assets/{LoadingPreviewPage-BxEFnEQh.js → LoadingPreviewPage-BlW2PJhI.js} +1 -1
  342. package/dist/web/assets/McpSettingsPage-DD7EqD4L.js +1 -0
  343. package/dist/web/assets/MemberAvatar-B2pAdWUi.js +1 -0
  344. package/dist/web/assets/MobileTaskDetail-CvmNW0BQ.js +3 -0
  345. package/dist/web/assets/NotificationSettingsPage-BIqldIRf.js +1 -0
  346. package/dist/web/assets/ProfileSettingsPage-DMxpHmF1.js +3 -0
  347. package/dist/web/assets/ProjectKanbanPage-CHW79td4.js +12 -0
  348. package/dist/web/assets/ProjectSettingsPage-DKiEUkoa.js +2 -0
  349. package/dist/web/assets/ProviderSettingsPage-DZT7wyE2.js +67 -0
  350. package/dist/web/assets/SettingsMasterDetail-DnF0lsV1.js +1 -0
  351. package/dist/web/assets/TaskDetail-D_970BDY.js +1 -0
  352. package/dist/web/assets/TeamSettingsPage-BJIEZ7xL.js +1 -0
  353. package/dist/web/assets/TranslationSettingsPage-D3mazqEW.js +1 -0
  354. package/dist/web/assets/arc-hUh7ykl8.js +1 -0
  355. package/dist/web/assets/{architectureDiagram-3BPJPVTR-B5EIiXuX.js → architectureDiagram-3BPJPVTR-CcV9CLkm.js} +1 -1
  356. package/dist/web/assets/{at-sign-BE17rU5b.js → at-sign-B-nhoGyH.js} +1 -1
  357. package/dist/web/assets/{blockDiagram-GPEHLZMM-GFwnvSLM.js → blockDiagram-GPEHLZMM-5q3Y9y0e.js} +1 -1
  358. package/dist/web/assets/{c4Diagram-AAUBKEIU-iJfLTxEa.js → c4Diagram-AAUBKEIU-CorBXi06.js} +1 -1
  359. package/dist/web/assets/channel-By24WpLg.js +1 -0
  360. package/dist/web/assets/check-DqPSoX0r.js +1 -0
  361. package/dist/web/assets/{chevron-down-BaQEBzp3.js → chevron-down-CPnvItQw.js} +1 -1
  362. package/dist/web/assets/{chevron-up-D0y6skqr.js → chevron-up-B8PE5N0G.js} +1 -1
  363. package/dist/web/assets/{chunk-2J33WTMH-CDxRPp-8.js → chunk-2J33WTMH-BUf5B4z4.js} +1 -1
  364. package/dist/web/assets/{chunk-4BX2VUAB-mzPbgyiz.js → chunk-4BX2VUAB-CdnOHkro.js} +1 -1
  365. package/dist/web/assets/{chunk-55IACEB6-KNAJbQyw.js → chunk-55IACEB6-U7edZDpP.js} +1 -1
  366. package/dist/web/assets/{chunk-727SXJPM-BqGfqkDK.js → chunk-727SXJPM-B64Tuhla.js} +1 -1
  367. package/dist/web/assets/{chunk-AQP2D5EJ-D27ue-H0.js → chunk-AQP2D5EJ-dXfe56jB.js} +1 -1
  368. package/dist/web/assets/{chunk-FMBD7UC4-DRza-3q8.js → chunk-FMBD7UC4-DDYbxc1Q.js} +1 -1
  369. package/dist/web/assets/{chunk-ND2GUHAM-CUgkviLu.js → chunk-ND2GUHAM-BQDGMv0R.js} +1 -1
  370. package/dist/web/assets/{chunk-QZHKN3VN-BAlOJwZb.js → chunk-QZHKN3VN-DB8q4xAj.js} +1 -1
  371. package/dist/web/assets/classDiagram-4FO5ZUOK-DxHoimtR.js +1 -0
  372. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-DxHoimtR.js +1 -0
  373. package/dist/web/assets/code-block-OCS4YCEC-BSdICyOa.js +2 -0
  374. package/dist/web/assets/confirm-dialog-DxEBZEbV.js +1 -0
  375. package/dist/web/assets/content-CMUL3HGx.js +2 -0
  376. package/dist/web/assets/{copy-DejkmIHZ.js → copy-DyxlXxiU.js} +1 -1
  377. package/dist/web/assets/{cose-bilkent-S5V4N54A-8TBY7BP_.js → cose-bilkent-S5V4N54A-Dx6eMnll.js} +1 -1
  378. package/dist/web/assets/cssMode-ZnNWfjZ6.js +1 -0
  379. package/dist/web/assets/{dagre-BM42HDAG-CkMzDiim.js → dagre-BM42HDAG-Cxe8pLgD.js} +1 -1
  380. package/dist/web/assets/{diagram-2AECGRRQ-CAOQJ2N1.js → diagram-2AECGRRQ-RMsswEBe.js} +1 -1
  381. package/dist/web/assets/{diagram-5GNKFQAL-DKhE0hiI.js → diagram-5GNKFQAL-Bi4E0hzx.js} +1 -1
  382. package/dist/web/assets/{diagram-KO2AKTUF-DBdkXSpa.js → diagram-KO2AKTUF-Mv6CDCyn.js} +1 -1
  383. package/dist/web/assets/{diagram-LMA3HP47-DRi2t32c.js → diagram-LMA3HP47-CJ3qKNzK.js} +1 -1
  384. package/dist/web/assets/{diagram-OG6HWLK6-DtP92tI1.js → diagram-OG6HWLK6-D8SBY4ft.js} +1 -1
  385. package/dist/web/assets/dialog-BYdeYC2Y.js +5 -0
  386. package/dist/web/assets/{erDiagram-TEJ5UH35-DWFKXtst.js → erDiagram-TEJ5UH35-DwVSCU6X.js} +1 -1
  387. package/dist/web/assets/{flowDiagram-I6XJVG4X-LDE0h4ri.js → flowDiagram-I6XJVG4X-CyipYAWd.js} +1 -1
  388. package/dist/web/assets/{freemarker2-CLksOOZX.js → freemarker2-DbH_yMCO.js} +2 -2
  389. package/dist/web/assets/{ganttDiagram-6RSMTGT7-DD2RfdyV.js → ganttDiagram-6RSMTGT7-CsC6Wjt4.js} +1 -1
  390. package/dist/web/assets/{gitGraphDiagram-PVQCEYII-DkKrR8ex.js → gitGraphDiagram-PVQCEYII-E5jNbqlq.js} +1 -1
  391. package/dist/web/assets/{globe-CD4g63c9.js → globe-B2rgTPH3.js} +1 -1
  392. package/dist/web/assets/handlebars-C5VY_S-z.js +1 -0
  393. package/dist/web/assets/hooks-Ct0srfV-.js +1 -0
  394. package/dist/web/assets/html-e5uLnAgN.js +1 -0
  395. package/dist/web/assets/htmlMode-BeNiGozX.js +1 -0
  396. package/dist/web/assets/icon-button-BEMJj_6T.js +1 -0
  397. package/dist/web/assets/index-0XPZayUU.css +1 -0
  398. package/dist/web/assets/index-BYPOXP6w.js +1 -0
  399. package/dist/web/assets/{index-D_QVcsLn.js → index-C-M0QDqc.js} +5 -5
  400. package/dist/web/assets/index-CAjqC8pT.js +41 -0
  401. package/dist/web/assets/index-CPGzLjEC.js +1 -0
  402. package/dist/web/assets/index-Cm6snkmR.js +122 -0
  403. package/dist/web/assets/index-DNdMmXBr.js +1 -0
  404. package/dist/web/assets/index-DppwrK2G.js +1 -0
  405. package/dist/web/assets/index-IMdA08Ok.js +1 -0
  406. package/dist/web/assets/index-kr6U6_pi.js +1 -0
  407. package/dist/web/assets/{infoDiagram-5YYISTIA-BdPA9Xfi.js → infoDiagram-5YYISTIA-5_0wrRXl.js} +1 -1
  408. package/dist/web/assets/{ishikawaDiagram-YF4QCWOH-220u4uiv.js → ishikawaDiagram-YF4QCWOH-aQujIFZw.js} +5 -5
  409. package/dist/web/assets/javascript-D7QVj9jR.js +1 -0
  410. package/dist/web/assets/{journeyDiagram-JHISSGLW-BvE0Y3tZ.js → journeyDiagram-JHISSGLW-ClMzHdqI.js} +1 -1
  411. package/dist/web/assets/jsonMode-nHmLVf-n.js +7 -0
  412. package/dist/web/assets/{kanban-definition-UN3LZRKU-Q0JH1P6D.js → kanban-definition-UN3LZRKU-BFVlt0tz.js} +1 -1
  413. package/dist/web/assets/kbd-Bfq5K6Ux.js +1 -0
  414. package/dist/web/assets/{key-round-DZm1FLLr.js → key-round-BqsaQdce.js} +1 -1
  415. package/dist/web/assets/{layers-Dda7MM7a.js → layers-DI4vFJ4F.js} +1 -1
  416. package/dist/web/assets/{linear-Dr6SfnSn.js → linear-DZ-6PAPS.js} +1 -1
  417. package/dist/web/assets/{link-BUeSP3T7.js → link-BU4PN723.js} +1 -1
  418. package/dist/web/assets/liquid-BPIyogoj.js +1 -0
  419. package/dist/web/assets/{lspLanguageFeatures-Bbp4PWlJ.js → lspLanguageFeatures-DH4Z8jiM.js} +1 -1
  420. package/dist/web/assets/mdx-CI8yO2pV.js +1 -0
  421. package/dist/web/assets/menu-C3uB3M7p.js +1 -0
  422. package/dist/web/assets/menu-styles-DWynpVVz.js +1 -0
  423. package/dist/web/assets/mermaid-NOHMQCX5-BqeX1osS.js +123 -0
  424. package/dist/web/assets/{message-square-CFriSt4D.js → message-square-E5K8E3A-.js} +1 -1
  425. package/dist/web/assets/{mindmap-definition-RKZ34NQL-bvDaNfKs.js → mindmap-definition-RKZ34NQL-C7dea0db.js} +1 -1
  426. package/dist/web/assets/modal-Cz1iIVeD.js +1 -0
  427. package/dist/web/assets/{monaco-BuDK9Fat.js → monaco-DM3DuGN5.js} +3 -3
  428. package/dist/web/assets/{paperclip-BOSklTt7.js → paperclip-COXNce_v.js} +1 -1
  429. package/dist/web/assets/{pencil-BRQ9W_a-.js → pencil-BFc4n44f.js} +1 -1
  430. package/dist/web/assets/people-BaCetIqh.js +1 -0
  431. package/dist/web/assets/{pieDiagram-4H26LBE5-BQCca9Cq.js → pieDiagram-4H26LBE5-2q5medug.js} +2 -2
  432. package/dist/web/assets/popover-CJCQLoGu.js +1 -0
  433. package/dist/web/assets/python-CSxVPG9p.js +1 -0
  434. package/dist/web/assets/{quadrantDiagram-W4KKPZXB-BEM2U7Cd.js → quadrantDiagram-W4KKPZXB-DHPEM56s.js} +1 -1
  435. package/dist/web/assets/razor-CVoIieeS.js +1 -0
  436. package/dist/web/assets/{requirementDiagram-4Y6WPE33-CcmFIALZ.js → requirementDiagram-4Y6WPE33-B_KyH7PP.js} +1 -1
  437. package/dist/web/assets/{sankeyDiagram-5OEKKPKP-D9Nrfbqk.js → sankeyDiagram-5OEKKPKP-CrqkZAYW.js} +1 -1
  438. package/dist/web/assets/search-CkiBPir_.js +1 -0
  439. package/dist/web/assets/search-field-CbM2P2KE.js +1 -0
  440. package/dist/web/assets/select-Br0TAs7A.js +1 -0
  441. package/dist/web/assets/select-DJsEG0jq.js +1 -0
  442. package/dist/web/assets/{sequenceDiagram-3UESZ5HK-5udQDi0x.js → sequenceDiagram-3UESZ5HK-BRTm1WQA.js} +1 -1
  443. package/dist/web/assets/{server-DJaSJ9m1.js → server-DYeSE1wn.js} +1 -1
  444. package/dist/web/assets/stateDiagram-AJRCARHV-qN49iyR2.js +1 -0
  445. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-B1clBPkJ.js +1 -0
  446. package/dist/web/assets/{textarea-Ft9KMoEI.js → textarea-B7vtoRE3.js} +1 -1
  447. package/dist/web/assets/{timeline-definition-PNZ67QCA-DXS236a-.js → timeline-definition-PNZ67QCA-BzarcDi3.js} +1 -1
  448. package/dist/web/assets/toggles-jo27o0AH.js +1 -0
  449. package/dist/web/assets/tooltip-DbVtCXcO.js +1 -0
  450. package/dist/web/assets/track-C0vxnITw.js +1 -0
  451. package/dist/web/assets/{trash-2-Bc-URu8c.js → trash-2-CUM-XX8B.js} +1 -1
  452. package/dist/web/assets/tsMode-7D3gqep6.js +11 -0
  453. package/dist/web/assets/typescript-CbOVEQNg.js +1 -0
  454. package/dist/web/assets/{upload-C9Z4jLtv.js → upload-CJ6jVmte.js} +1 -1
  455. package/dist/web/assets/use-profiles-B0xXAf7Z.js +1 -0
  456. package/dist/web/assets/{use-projects-DI27nNwl.js → use-projects-BlTwWxkY.js} +1 -1
  457. package/dist/web/assets/{use-providers-DB9ghBPq.js → use-providers-FMSfyTD1.js} +1 -1
  458. package/dist/web/assets/use-translation-settings-BOlszecX.js +1 -0
  459. package/dist/web/assets/use-workspaces-DYrEp0Lw.js +1 -0
  460. package/dist/web/assets/useNormalizedLogs-D0N8MJdH.js +1 -0
  461. package/dist/web/assets/useTokenUsage-WB3ZQ25v.js +1 -0
  462. package/dist/web/assets/{vennDiagram-CIIHVFJN-BzyNrP06.js → vennDiagram-CIIHVFJN-B6oZetvf.js} +1 -1
  463. package/dist/web/assets/{wardley-L42UT6IY-BQ4SyU9_.js → wardley-L42UT6IY-Bzuhm76f.js} +1 -1
  464. package/dist/web/assets/{wardleyDiagram-YWT4CUSO-CcCqmB58.js → wardleyDiagram-YWT4CUSO-DaC-FUf5.js} +1 -1
  465. package/dist/web/assets/xml-Cfa9tkdT.js +1 -0
  466. package/dist/web/assets/{xychartDiagram-2RQKCTM6-RWm1MtQk.js → xychartDiagram-2RQKCTM6-CCvRIJRL.js} +1 -1
  467. package/dist/web/assets/yaml-C86f7VLL.js +1 -0
  468. package/dist/web/index.html +2 -2
  469. package/node_modules/@agent-tower/shared/dist/index.d.ts +1 -0
  470. package/node_modules/@agent-tower/shared/dist/index.d.ts.map +1 -1
  471. package/node_modules/@agent-tower/shared/dist/index.js +1 -0
  472. package/node_modules/@agent-tower/shared/dist/index.js.map +1 -1
  473. package/node_modules/@agent-tower/shared/dist/log-adapter.d.ts +11 -0
  474. package/node_modules/@agent-tower/shared/dist/log-adapter.d.ts.map +1 -1
  475. package/node_modules/@agent-tower/shared/dist/log-adapter.js +2 -0
  476. package/node_modules/@agent-tower/shared/dist/log-adapter.js.map +1 -1
  477. package/node_modules/@agent-tower/shared/dist/socket/events.d.ts +5 -2
  478. package/node_modules/@agent-tower/shared/dist/socket/events.d.ts.map +1 -1
  479. package/node_modules/@agent-tower/shared/dist/socket/events.js +2 -0
  480. package/node_modules/@agent-tower/shared/dist/socket/events.js.map +1 -1
  481. package/node_modules/@agent-tower/shared/dist/translation.d.ts +306 -0
  482. package/node_modules/@agent-tower/shared/dist/translation.d.ts.map +1 -0
  483. package/node_modules/@agent-tower/shared/dist/translation.js +8 -0
  484. package/node_modules/@agent-tower/shared/dist/translation.js.map +1 -0
  485. package/node_modules/@agent-tower/shared/dist/types.d.ts +23 -0
  486. package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
  487. package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
  488. package/package.json +1 -1
  489. package/prisma/migrations/20261016000000_add_translation_foundation/migration.sql +104 -0
  490. package/prisma/migrations/20261016010000_add_message_deliveries/migration.sql +158 -0
  491. package/prisma/migrations/20261019000000_add_room_translation_eligibility/migration.sql +3 -0
  492. package/prisma/migrations/20261019100000_add_team_delivery_foundation/migration.sql +217 -0
  493. package/prisma/schema.prisma +224 -0
  494. package/dist/web/assets/AgentDemoPage-D0LQRcXm.js +0 -1
  495. package/dist/web/assets/AgentLogo-BDyW3ihb.js +0 -1
  496. package/dist/web/assets/BranchSelect-Dmp5hq0i.js +0 -1
  497. package/dist/web/assets/BrandLogo-YGT4Vlw5.js +0 -1
  498. package/dist/web/assets/ConversationPage-BhapUYGe.js +0 -3
  499. package/dist/web/assets/CreateTaskInput-GLF3Oyc1.js +0 -1
  500. package/dist/web/assets/DesignSystemPage-j-1-1JtQ.js +0 -16
  501. package/dist/web/assets/EditableTaskTitle-mHT39coo.js +0 -70
  502. package/dist/web/assets/GeneralSettingsPage-BBe3EXcj.js +0 -1
  503. package/dist/web/assets/McpSettingsPage-DDA8yUxu.js +0 -1
  504. package/dist/web/assets/MemberAvatar-CCRbS97s.js +0 -1
  505. package/dist/web/assets/MobileTaskDetail-BOIFPybn.js +0 -5
  506. package/dist/web/assets/NotificationSettingsPage-B2kUnE8w.js +0 -1
  507. package/dist/web/assets/ProfileSettingsPage-DPZpUoFd.js +0 -3
  508. package/dist/web/assets/ProjectKanbanPage-DQnwUBFg.js +0 -12
  509. package/dist/web/assets/ProjectSettingsPage-CcXAOYb7.js +0 -2
  510. package/dist/web/assets/ProviderSettingsPage-BAIRvcAS.js +0 -67
  511. package/dist/web/assets/SettingsMasterDetail-BuxwsR9R.js +0 -1
  512. package/dist/web/assets/TaskDetail-B3pOSx3f.js +0 -3
  513. package/dist/web/assets/TeamSettingsPage-DoWmzvKu.js +0 -1
  514. package/dist/web/assets/agent-meta-MzuGtqxl.js +0 -1
  515. package/dist/web/assets/arc-Bm_QIbJA.js +0 -1
  516. package/dist/web/assets/arrow-down-BVePf1ZM.js +0 -1
  517. package/dist/web/assets/channel-CYYV7_Oi.js +0 -1
  518. package/dist/web/assets/check-qs0S_9rB.js +0 -1
  519. package/dist/web/assets/circle-alert-Cbvy2cMo.js +0 -1
  520. package/dist/web/assets/classDiagram-4FO5ZUOK-Cl-3ikxt.js +0 -1
  521. package/dist/web/assets/classDiagram-v2-Q7XG4LA2-Cl-3ikxt.js +0 -1
  522. package/dist/web/assets/code-block-OCS4YCEC-CO23R1Os.js +0 -2
  523. package/dist/web/assets/confirm-dialog-BrgNTypL.js +0 -1
  524. package/dist/web/assets/cssMode-hzDRnxMx.js +0 -1
  525. package/dist/web/assets/gauge-CPlvCZUV.js +0 -1
  526. package/dist/web/assets/handlebars-C1M9t0Lo.js +0 -1
  527. package/dist/web/assets/html-DKVKQovh.js +0 -1
  528. package/dist/web/assets/htmlMode-BSWjg5ir.js +0 -1
  529. package/dist/web/assets/index-BnqdwOEH.css +0 -1
  530. package/dist/web/assets/index-CMJV-mgF.js +0 -122
  531. package/dist/web/assets/javascript-WwrMY2Bg.js +0 -1
  532. package/dist/web/assets/jsonMode-Dz0hnnvg.js +0 -7
  533. package/dist/web/assets/liquid-CzGEh0ls.js +0 -1
  534. package/dist/web/assets/mdx-y6S3OMMk.js +0 -1
  535. package/dist/web/assets/mermaid-NOHMQCX5-C72362la.js +0 -123
  536. package/dist/web/assets/modal-DeigO7aN.js +0 -1
  537. package/dist/web/assets/play-Mxkwq69n.js +0 -1
  538. package/dist/web/assets/python-CMnGKsU9.js +0 -1
  539. package/dist/web/assets/razor-mLurbGgY.js +0 -1
  540. package/dist/web/assets/select-DKS02St6.js +0 -1
  541. package/dist/web/assets/stateDiagram-AJRCARHV-D-cF1NGV.js +0 -1
  542. package/dist/web/assets/stateDiagram-v2-BHNVJYJU-a4ugQnZs.js +0 -1
  543. package/dist/web/assets/status-styles-sQm4KLdp.js +0 -1
  544. package/dist/web/assets/switch-7LWQ_sEx.js +0 -1
  545. package/dist/web/assets/task-CDYaC_No.js +0 -42
  546. package/dist/web/assets/task-priority-acVgTiil.js +0 -1
  547. package/dist/web/assets/tsMode-Vr3bndW1.js +0 -11
  548. package/dist/web/assets/typescript-BfjoaUSn.js +0 -1
  549. package/dist/web/assets/use-attachments-EypT-7PI.js +0 -2
  550. package/dist/web/assets/use-profiles-nMPGwy3v.js +0 -1
  551. package/dist/web/assets/use-workspaces-d4n9m9cX.js +0 -1
  552. package/dist/web/assets/useNormalizedLogs-DRoPbP0w.js +0 -1
  553. package/dist/web/assets/useTokenUsage-4qTDa8SY.js +0 -1
  554. package/dist/web/assets/xml-BGVoPR0b.js +0 -1
  555. package/dist/web/assets/yaml-eFhNavrs.js +0 -1
@@ -0,0 +1,622 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { createServer } from 'node:http';
3
+ import { randomUUID } from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import Fastify from 'fastify';
9
+ import { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect, vi } from 'vitest';
10
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
11
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
12
+ import { EventBus } from '../../core/event-bus.js';
13
+ let TranslationSettingsService;
14
+ let TranslationService;
15
+ let TeamTaskInputService;
16
+ let MessageDeliveryService;
17
+ let TeamLockService;
18
+ import { taskInputHash } from '../team-task-input.js';
19
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'tower-team-user-full-'));
20
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
21
+ process.env.AGENT_TOWER_DATABASE_URL = `file:${path.join(dir, 'test.db')}`;
22
+ process.env.AGENT_TOWER_DATA_DIR = dir;
23
+ const container = vi.hoisted(() => ({ settings: null, translation: null, deliveries: null, inputs: null, bus: null, runtime: null }));
24
+ vi.mock('../../core/container.js', () => ({ getTranslationSettingsService: () => container.settings, getTranslationService: () => container.translation, getMessageDeliveryService: () => container.deliveries, getTeamTaskInputService: () => container.inputs, getEventBus: () => container.bus, getSessionManager: () => container.runtime }));
25
+ let db;
26
+ let teamRuns;
27
+ let Scheduler;
28
+ let settings, translation, inputs, deliveries;
29
+ let auth;
30
+ let credentials;
31
+ let app;
32
+ let translator, baseUrl;
33
+ const requests = [];
34
+ const failed = new Set();
35
+ const held = new Map();
36
+ let fault;
37
+ let notify;
38
+ const wait = (ms = 5) => new Promise(resolve => setTimeout(resolve, ms));
39
+ async function until(check) {
40
+ const deadline = Date.now() + 8000;
41
+ while (!await check()) {
42
+ await translation.pump();
43
+ await deliveries.pump();
44
+ if (Date.now() > deadline)
45
+ throw new Error('Controlled full path timeout');
46
+ await wait();
47
+ }
48
+ }
49
+ async function enable(enabled = true) { const current = await settings.getPublic(); const draft = { enabled, apiBaseUrl: baseUrl, model: 'controlled', apiKey: { action: 'replace', value: '' }, sendTargetLanguage: 'English', displayTargetLanguage: 'Chinese', promptTemplate: '{{target_language}}\n{{text}}', maxConcurrency: 4 }; await settings.save({ ...draft, expectedRevision: current.revision }); }
50
+ async function room(mode = 'CONFIRM') {
51
+ const project = await db.project.create({ data: { name: 'Isolated project', repoPath: dir } });
52
+ const task = await db.task.create({ data: { projectId: project.id, title: '任务 Task T', description: '精确 task description ' } });
53
+ const team = await db.teamRun.create({ data: { taskId: task.id, mode } });
54
+ const members = [];
55
+ for (const name of ['A', 'B'])
56
+ members.push(await db.teamMember.create({ data: { teamRunId: team.id, name, aliases: '[]', providerId: 'controlled-executor', rolePrompt: 'Fixed role', capabilities: '{}', workspacePolicy: 'none', triggerPolicy: 'USER_MESSAGES' } }));
57
+ const workspace = await db.workspace.create({ data: { taskId: task.id, branchName: 'controlled', worktreePath: dir, workingDir: dir } });
58
+ await db.teamRun.update({ where: { id: team.id }, data: { mainWorkspaceId: workspace.id } });
59
+ return { team, task, members, workspace };
60
+ }
61
+ async function submit(r, content = '用户 hello', extra = {}) {
62
+ const clientSubmissionId = randomUUID();
63
+ const body = { clientSubmissionId, content, ...extra };
64
+ const response = await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, payload: body });
65
+ expect(response.statusCode, response.body).toBe(202);
66
+ return { body, operation: response.json() };
67
+ }
68
+ async function sent(id) { await until(async () => (await db.messageDelivery.findUniqueOrThrow({ where: { id } })).state === 'SENT'); return db.messageDelivery.findUniqueOrThrow({ where: { id } }); }
69
+ function scheduler(r, createHook) {
70
+ const runtime = { create: vi.fn(async (_workspaceId, agentType, prompt) => { const s = await db.session.create({ data: { workspaceId: r.workspace.id, agentType, prompt, status: 'PENDING' } }); await createHook?.(); return s; }), start: vi.fn(async (id) => { await db.session.update({ where: { id }, data: { status: 'RUNNING' } }); }), isRuntimeCleanupConfirmed: vi.fn(async () => true) };
71
+ const locks = new TeamLockService();
72
+ const service = new Scheduler(locks, { taskInputs: inputs, workspaceService: { create: async () => r.workspace, getOrCreateMainWorkspace: async () => r.workspace }, sessionManager: runtime, getProviderById: () => ({ agentType: 'CODEX' }), teamRunReviewAdvancer: { maybeAdvanceTeamRunToReview: async () => false } });
73
+ return { service, runtime, locks };
74
+ }
75
+ async function queued(r, memberIndex = 0) {
76
+ const message = await db.roomMessage.create({ data: { teamRunId: r.team.id, senderType: 'agent', kind: 'chat', content: 'Agent original', mentions: '[]' } });
77
+ return db.workRequest.create({ data: { teamRunId: r.team.id, triggerMessageId: message.id, targetMemberId: r.members[memberIndex].id, requesterType: 'agent', instruction: 'Agent original', status: 'QUEUED' } });
78
+ }
79
+ describe('TEAM_USER complete production path: real isolated HTTP, local translator and controlled executor', () => {
80
+ beforeAll(async () => {
81
+ execFileSync('pnpm', ['exec', 'prisma', 'db', 'push', '--skip-generate', '--schema=prisma/schema.prisma'], { cwd: root, stdio: 'pipe', env: process.env });
82
+ db = (await import('../../utils/index.js')).prisma;
83
+ TranslationSettingsService = (await import('../translation-settings.service.js')).TranslationSettingsService;
84
+ TranslationService = (await import('../translation.service.js')).TranslationService;
85
+ TeamTaskInputService = (await import('../team-task-input.service.js')).TeamTaskInputService;
86
+ MessageDeliveryService = (await import('../message-delivery.service.js')).MessageDeliveryService;
87
+ TeamLockService = (await import('../team-lock.service.js')).TeamLockService;
88
+ auth = (await import('../access-auth.service.js')).AccessAuthService;
89
+ credentials = await import('../../utils/agent-api-credential.js');
90
+ Scheduler = (await import('../team-scheduler.service.js')).TeamSchedulerService;
91
+ db.$use(async (params, next) => { const result = await next(params); const current = fault; if (current && current.model === params.model && current.action === params.action && (!current.predicate || current.predicate(params.args))) {
92
+ fault = undefined;
93
+ throw new Error('Controlled atomic write failure');
94
+ } return result; });
95
+ translator = createServer(async (req, res) => {
96
+ let body = '';
97
+ for await (const chunk of req)
98
+ body += chunk;
99
+ const prompt = JSON.parse(body).messages.at(-1).content;
100
+ const text = prompt.slice(prompt.indexOf('\n') + 1);
101
+ requests.push(text);
102
+ const key = [...held.keys()].find(key => text.includes(key));
103
+ if (key)
104
+ await new Promise(resolve => held.get(key).push(resolve));
105
+ res.setHeader('content-type', 'application/json');
106
+ res.end(JSON.stringify({ choices: [{ finish_reason: 'stop', message: { content: [...failed].some(key => text.includes(key)) ? ' ' : text.replaceAll('用户', 'User').replaceAll('任务', 'Task') } }] }));
107
+ });
108
+ await new Promise(resolve => translator.listen(0, '127.0.0.1', resolve));
109
+ baseUrl = `http://127.0.0.1:${translator.address().port}/v1`;
110
+ }, 30000);
111
+ beforeEach(async () => {
112
+ await db.messageDelivery.deleteMany();
113
+ await db.translationSource.deleteMany();
114
+ await db.project.deleteMany();
115
+ await db.translationSettings.deleteMany();
116
+ await db.accessAuthSettings.deleteMany();
117
+ auth.__test.resetSettingsCache();
118
+ credentials.clearAgentApiCredentials();
119
+ requests.length = 0;
120
+ failed.clear();
121
+ held.clear();
122
+ fault = undefined;
123
+ settings = new TranslationSettingsService(db);
124
+ await settings.getPublic();
125
+ translation = new TranslationService(db, settings, { limits: { tickMs: 10 } });
126
+ inputs = new TeamTaskInputService(db, translation);
127
+ teamRuns = new (await import('../team-run.service.js')).TeamRunService();
128
+ notify = vi.fn(async () => undefined);
129
+ const runtime = { setMessageDeliveryService: vi.fn(), start: vi.fn(), sendMessage: vi.fn() };
130
+ deliveries = new MessageDeliveryService(db, settings, translation, runtime, { tickMs: 10, taskInputs: inputs, teamRuns, roomPublished: notify });
131
+ Object.assign(container, { settings, translation, inputs, deliveries, runtime, bus: new EventBus() });
132
+ app = Fastify();
133
+ app.addHook('onRequest', (await import('../../middleware/access-auth.js')).accessAuthHook);
134
+ await app.register((await import('../../routes/team-runs.js')).teamRunRoutes, { prefix: '/api', service: teamRuns, workspaceService: {}, scheduler: { startNextSessions: vi.fn(async () => []) } });
135
+ await app.register((await import('../../routes/translations.js')).translationRoutes, { prefix: '/api' });
136
+ await app.register((await import('../../routes/system.js')).systemRoutes, { prefix: '/api' });
137
+ await translation.start();
138
+ await deliveries.start();
139
+ });
140
+ afterEach(async () => { fault = undefined; for (const resolves of held.values())
141
+ resolves.forEach(fn => fn()); held.clear(); await deliveries.shutdown(); await translation.shutdown(); await app.close(); credentials.clearAgentApiCredentials(); });
142
+ afterAll(async () => { await new Promise(resolve => translator.close(() => resolve())); await db.$disconnect(); fs.rmSync(dir, { recursive: true, force: true }); });
143
+ it.each(['CONFIRM', 'AUTO'])('202 → translation → atomic %s publication, fixed no-mention recipients, owner/Agent models and unchanged Task', async (mode) => {
144
+ const r = await room(mode);
145
+ await enable();
146
+ held.set('用户 hello', []);
147
+ const accepted = await submit(r);
148
+ expect(accepted.operation.state).toBe('WAITING_TRANSLATION');
149
+ expect(await db.roomMessage.count()).toBe(0);
150
+ expect(await db.agentInvocation.count()).toBe(0);
151
+ await db.teamMember.create({ data: { ...r.members[0], id: randomUUID(), name: 'Late member' } });
152
+ held.get('用户 hello').forEach(fn => fn());
153
+ held.delete('用户 hello');
154
+ const row = await sent(accepted.operation.operationId);
155
+ const message = await db.roomMessage.findUniqueOrThrow({ where: { deliveryId: row.id } });
156
+ const wrs = await db.workRequest.findMany({ where: { triggerMessageId: message.id } });
157
+ expect(wrs.map(w => w.targetMemberId).sort()).toEqual(r.members.map(m => m.id).sort());
158
+ expect(wrs.every(w => w.status === (mode === 'AUTO' ? 'QUEUED' : 'PENDING_APPROVAL'))).toBe(true);
159
+ expect(message.content).toBe('User hello');
160
+ expect(row.actualSentText).toBe(message.content);
161
+ expect((await teamRuns.getRoomMessage(r.team.id, message.id)).content).toBe('用户 hello');
162
+ expect(await teamRuns.getRoomMessage(r.team.id, message.id, { viewerMemberId: r.members[0].id })).not.toHaveProperty('userDelivery');
163
+ expect((await db.task.findUniqueOrThrow({ where: { id: r.task.id } }))).toEqual(r.task);
164
+ expect(await db.translationSource.count({ where: { sourceKind: 'TASK_INPUT' } })).toBe(1);
165
+ expect(await db.deliveryReceipt.findUnique({ where: { deliveryId: row.id } })).toMatchObject({ phase: 'SUBMITTED', evidence: 'ROOM_PUBLISHED', generation: row.revision });
166
+ const duplicate = await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, payload: accepted.body });
167
+ expect(duplicate.statusCode).toBe(202);
168
+ expect(duplicate.json().operationId).toBe(row.id);
169
+ expect(await db.roomMessage.count()).toBe(1);
170
+ const conflict = await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, payload: { ...accepted.body, content: 'changed' } });
171
+ expect(conflict.statusCode).toBe(409);
172
+ });
173
+ it('keeps failed body and closed-era messages behind the task channel predecessor and restores same UUID after lost response/restart', async () => {
174
+ const r = await room();
175
+ await enable();
176
+ failed.add('用户 fail');
177
+ const a = await submit(r, '用户 fail');
178
+ const b = await submit(r, '用户 second');
179
+ await until(async () => (await deliveries.get(a.operation.operationId)).state === 'TRANSLATION_FAILED');
180
+ await until(async () => ['READY', 'WAITING_PREDECESSOR'].includes((await deliveries.get(b.operation.operationId)).state));
181
+ await enable(false);
182
+ const c = await submit(r, '用户 closed');
183
+ await wait(40);
184
+ expect(await db.roomMessage.count()).toBe(0);
185
+ const latest = await deliveries.get(a.operation.operationId);
186
+ await deliveries.action(latest.id, 'cancel', latest.revision, randomUUID());
187
+ const paused = await deliveries.get(b.operation.operationId);
188
+ if (paused.state !== 'PAUSED_DISABLED')
189
+ await until(async () => (await deliveries.get(paused.id)).state === 'PAUSED_DISABLED');
190
+ const revision = (await deliveries.get(paused.id)).revision;
191
+ await deliveries.action(paused.id, 'retry_delivery', revision, randomUUID());
192
+ await sent(paused.id);
193
+ await sent(c.operation.operationId);
194
+ await deliveries.shutdown();
195
+ deliveries = new MessageDeliveryService(db, settings, translation, container.runtime, { taskInputs: inputs, teamRuns, tickMs: 10 });
196
+ container.deliveries = deliveries;
197
+ await deliveries.start();
198
+ const retry = await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, payload: c.body });
199
+ expect(retry.statusCode).toBe(202);
200
+ expect(retry.json().operationId).toBe(c.operation.operationId);
201
+ expect(await db.roomMessage.count()).toBe(2);
202
+ });
203
+ it.each(['startNext', 'startNextSessions'])('%s blocks before admission/session/claim and shares one task input for concurrent consumers', async (method) => {
204
+ const r = await room('AUTO');
205
+ await enable();
206
+ held.set('任务 Task T', []);
207
+ const first = await queued(r);
208
+ const second = await queued(r, 1);
209
+ const s = scheduler(r);
210
+ const lock = vi.spyOn(s.locks, 'acquire');
211
+ const { acquireTeamMemberAdmission } = await import('../team-member-admission-barrier.js');
212
+ const releases = await Promise.all(r.members.map(m => acquireTeamMemberAdmission(r.team.id, m.id)));
213
+ let timedOut = false;
214
+ try {
215
+ const blocked = await Promise.race([s.service[method](r.team.id), wait(150).then(() => { timedOut = true; return null; })]);
216
+ expect(blocked).toEqual([]);
217
+ expect(timedOut).toBe(false);
218
+ expect(lock).not.toHaveBeenCalled();
219
+ }
220
+ finally {
221
+ releases.forEach(release => release());
222
+ }
223
+ expect(s.runtime.create).not.toHaveBeenCalled();
224
+ expect(await db.agentInvocation.count()).toBe(0);
225
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: first.id } })).startAttemptCount).toBe(0);
226
+ expect(await db.teamTaskInputSnapshot.count()).toBe(1);
227
+ expect(await db.translationSource.count({ where: { sourceKind: 'TASK_INPUT' } })).toBe(1);
228
+ held.get('任务 Task T').forEach(fn => fn());
229
+ held.delete('任务 Task T');
230
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'SUCCESS');
231
+ const started = await s.service[method](r.team.id);
232
+ expect(started).toHaveLength(2);
233
+ const invocations = await db.agentInvocation.findMany();
234
+ expect(new Set(invocations.map(i => i.taskInputSnapshotId)).size).toBe(1);
235
+ expect(invocations.every(i => !!i.taskInputSnapshotId)).toBe(true);
236
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: second.id } })).status).toBe('STARTED');
237
+ if (method === 'startNextSessions') {
238
+ const sessions = await db.session.findMany();
239
+ expect(sessions.every(s => s.prompt.includes('Task Task T') && !s.prompt.includes('任务 Task T') && s.prompt.includes('Fixed role') && s.prompt.includes('Agent original'))).toBe(true);
240
+ }
241
+ });
242
+ it('retains old failed task dependency across edit+disable, and cancellation respects remaining consumers', async () => {
243
+ const r = await room('AUTO');
244
+ await enable();
245
+ failed.add('任务 Task T');
246
+ const first = await queued(r);
247
+ const second = await queued(r, 1);
248
+ const s = scheduler(r);
249
+ await s.service.startNext(r.team.id);
250
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'FAILED');
251
+ const old = await db.teamTaskInputSnapshot.findFirstOrThrow();
252
+ await db.task.update({ where: { id: r.task.id }, data: { title: '任务 Task U' } });
253
+ await enable(false);
254
+ expect(await s.service.startNext(r.team.id)).toEqual([]);
255
+ expect(await db.teamTaskInputSnapshot.count()).toBe(1);
256
+ await db.workRequest.update({ where: { id: first.id }, data: { status: 'CANCELLED' } });
257
+ await inputs.cancelUnused();
258
+ expect((await db.translationSource.findFirstOrThrow()).jobState).toBe('FAILED');
259
+ expect(await s.service.startNext(r.team.id)).toEqual([]);
260
+ const result = await inputs.action(r.team.id, old.id, 'send_original', old.revision, randomUUID());
261
+ expect(result.state).toBe('ACCEPTED');
262
+ const started = await s.service.startNext(r.team.id);
263
+ expect(started).toHaveLength(1);
264
+ const invocation = await db.agentInvocation.findFirstOrThrow({ where: { workRequestId: second.id } });
265
+ const adopted = await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: invocation.taskInputSnapshotId } });
266
+ expect(adopted.taskSourceHash).toBe(taskInputHash('任务 Task U', r.task.description));
267
+ });
268
+ it.each([
269
+ ['FAILED', 'WORK_REQUEST'], ['FAILED', 'DELIVERY'],
270
+ ['CANCELLED', 'WORK_REQUEST'], ['CANCELLED', 'DELIVERY'],
271
+ ])('releases an abandoned %s input after its last delivery is cancelled, for a new %s', async (sourceState, kind) => {
272
+ const r = await room('AUTO');
273
+ await enable();
274
+ if (sourceState === 'FAILED')
275
+ failed.add('任务 Task T');
276
+ else
277
+ held.set('任务 Task T', []);
278
+ const first = await submit(r);
279
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === (sourceState === 'FAILED' ? 'FAILED' : 'RUNNING'));
280
+ const old = await db.teamTaskInputSnapshot.findFirstOrThrow();
281
+ const current = await deliveries.get(first.operation.operationId);
282
+ await deliveries.action(current.id, 'cancel', current.revision, randomUUID());
283
+ await inputs.cancelUnused();
284
+ expect((await db.translationSource.findUniqueOrThrow({ where: { id: old.translationSourceId } })).jobState).toBe(sourceState);
285
+ await enable(false);
286
+ const before = requests.length;
287
+ if (kind === 'WORK_REQUEST') {
288
+ const next = await queued(r);
289
+ expect(await inputs.ensureForDispatch(r.team.id, { kind, id: next.id })).toMatchObject({ state: 'READY', token: { snapshotId: old.id, sentMode: 'ORIGINAL' } });
290
+ }
291
+ else {
292
+ const next = await submit(r, '用户 after abandoning last delivery');
293
+ expect(await sent(next.operation.operationId)).toMatchObject({ taskInputSnapshotId: old.id, sentMode: 'ORIGINAL', actualSentText: next.body.content });
294
+ expect(await db.roomMessage.count({ where: { senderType: 'user' } })).toBe(1);
295
+ }
296
+ for (const resolve of held.get('任务 Task T') ?? [])
297
+ resolve();
298
+ held.delete('任务 Task T');
299
+ await wait(20);
300
+ await translation.pump();
301
+ await deliveries.pump();
302
+ expect(requests).toHaveLength(before);
303
+ expect(await db.teamTaskInputSnapshot.count()).toBe(1);
304
+ expect(await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: old.id } })).toMatchObject({ state: 'ACCEPTED', sentMode: 'ORIGINAL', revision: old.revision + 1, acceptedAttemptId: null, effectiveUserText: `${r.task.title}\n\n${r.task.description}` });
305
+ expect((await db.messageDelivery.findUniqueOrThrow({ where: { id: current.id } })).state).toBe('CANCELLED');
306
+ expect(await db.task.findUniqueOrThrow({ where: { id: r.task.id } })).toEqual(r.task);
307
+ });
308
+ it.each(['WORK_REQUEST', 'DELIVERY'])('retains a shared failed barrier while an old %s remains active, including after Task edits', async (survivor) => {
309
+ const r = await room('AUTO');
310
+ await enable();
311
+ failed.add('任务 Task T');
312
+ const first = await queued(r);
313
+ const second = await queued(r, 1);
314
+ const s = scheduler(r);
315
+ await s.service.startNext(r.team.id);
316
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'FAILED');
317
+ const delivery = await submit(r);
318
+ await until(async () => !!(await db.messageDelivery.findUniqueOrThrow({ where: { id: delivery.operation.operationId } })).taskInputSnapshotId);
319
+ const old = await db.teamTaskInputSnapshot.findFirstOrThrow();
320
+ await s.service.cancelWorkRequest(first.id, { teamRunId: r.team.id, requesterMemberId: r.members[0].id });
321
+ if (survivor === 'DELIVERY')
322
+ await s.service.cancelWorkRequest(second.id, { teamRunId: r.team.id, requesterMemberId: r.members[1].id });
323
+ else {
324
+ const row = await deliveries.get(delivery.operation.operationId);
325
+ await deliveries.action(row.id, 'cancel', row.revision, randomUUID());
326
+ }
327
+ await enable(false);
328
+ await inputs.cancelUnused();
329
+ const next = await queued(r);
330
+ expect(await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })).toEqual({ state: 'BLOCKED', snapshotId: old.id });
331
+ const message = await submit(r, '用户 closed behind remaining consumer');
332
+ await until(async () => !!(await db.messageDelivery.findUniqueOrThrow({ where: { id: message.operation.operationId } })).taskInputSnapshotId);
333
+ await db.task.update({ where: { id: r.task.id }, data: { title: 'Task edit must not bypass active input' } });
334
+ expect(await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })).toEqual({ state: 'BLOCKED', snapshotId: old.id });
335
+ expect(await inputs.ensureForDispatch(r.team.id, { kind: 'DELIVERY', id: message.operation.operationId })).toEqual({ state: 'BLOCKED', snapshotId: old.id });
336
+ expect((await deliveries.get(message.operation.operationId)).state).toBe('WAITING_TASK_INPUT');
337
+ expect(await db.roomMessage.count({ where: { senderType: 'user' } })).toBe(0);
338
+ expect(await db.teamTaskInputSnapshot.count()).toBe(1);
339
+ expect(await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: old.id } })).toEqual(old);
340
+ expect(await s.service.startNextSessions(r.team.id)).toEqual([]);
341
+ expect(s.runtime.create).not.toHaveBeenCalled();
342
+ });
343
+ it('counts a newly bound consumer on repeated preflight after the older consumer is cancelled', async () => {
344
+ const r = await room('AUTO');
345
+ await enable();
346
+ failed.add('任务 Task T');
347
+ const first = await queued(r);
348
+ const s = scheduler(r);
349
+ await s.service.startNext(r.team.id);
350
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'FAILED');
351
+ await enable(false);
352
+ const next = await queued(r, 1);
353
+ const otherInputs = new TeamTaskInputService(db, translation);
354
+ const blocked = await otherInputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id });
355
+ expect(blocked.state).toBe('BLOCKED');
356
+ await s.service.cancelWorkRequest(first.id, { teamRunId: r.team.id, requesterMemberId: r.members[0].id });
357
+ await inputs.cancelUnused();
358
+ expect(await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })).toEqual(blocked);
359
+ expect(await otherInputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })).toEqual(blocked);
360
+ await s.service.cancelWorkRequest(next.id, { teamRunId: r.team.id, requesterMemberId: r.members[1].id });
361
+ const fresh = await queued(r);
362
+ expect(await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: fresh.id })).toMatchObject({ state: 'READY', token: { sentMode: 'ORIGINAL' } });
363
+ });
364
+ it('atomically adopts one abandoned same-hash candidate across concurrent service instances and repeated preflights', async () => {
365
+ const r = await room('AUTO');
366
+ await enable();
367
+ failed.add('任务 Task T');
368
+ const first = await queued(r);
369
+ const s = scheduler(r);
370
+ await s.service.startNext(r.team.id);
371
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'FAILED');
372
+ const old = await db.teamTaskInputSnapshot.findFirstOrThrow();
373
+ await s.service.cancelWorkRequest(first.id, { teamRunId: r.team.id, requesterMemberId: r.members[0].id });
374
+ await enable(false);
375
+ const consumers = await Promise.all([queued(r), queued(r, 1), queued(r)]);
376
+ const otherInputs = new TeamTaskInputService(db, translation);
377
+ fault = { model: 'WorkRequest', action: 'update', predicate: args => args.where.id === consumers[0].id };
378
+ await expect(otherInputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: consumers[0].id })).rejects.toThrow('Controlled atomic write failure');
379
+ expect(await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: old.id } })).toEqual(old);
380
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: consumers[0].id } })).taskInputSnapshotId).toBeNull();
381
+ const [results, message] = await Promise.all([
382
+ Promise.all(consumers.map((w, i) => (i % 2 ? inputs : otherInputs).ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: w.id }))),
383
+ submit(r, '用户 concurrent candidate reuse'),
384
+ ]);
385
+ expect(results.every(result => result.state === 'READY')).toBe(true);
386
+ expect(results[0]).toEqual(results[1]);
387
+ expect(results[1]).toEqual(results[2]);
388
+ const accepted = await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: old.id } });
389
+ expect(accepted).toMatchObject({ state: 'ACCEPTED', sentMode: 'ORIGINAL', revision: old.revision + 1 });
390
+ await sent(message.operation.operationId);
391
+ await Promise.all(consumers.map(w => otherInputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: w.id })));
392
+ expect(await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: old.id } })).toEqual(accepted);
393
+ expect(await db.teamTaskInputSnapshot.count()).toBe(1);
394
+ expect(await db.translationSource.count({ where: { sourceKind: 'TASK_INPUT' } })).toBe(1);
395
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: first.id } })).status).toBe('CANCELLED');
396
+ });
397
+ it('preserves accepted translation evidence and the frozen Invocation when new consumers revisit its hash', async () => {
398
+ const r = await room('AUTO');
399
+ await enable();
400
+ const first = await queued(r);
401
+ const s = scheduler(r);
402
+ await s.service.startNextSessions(r.team.id);
403
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'SUCCESS');
404
+ expect(await s.service.startNextSessions(r.team.id)).toHaveLength(1);
405
+ const invocation = await db.agentInvocation.findFirstOrThrow();
406
+ const snapshot = await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: invocation.taskInputSnapshotId } });
407
+ const attempt = await db.translationAttempt.findUniqueOrThrow({ where: { id: snapshot.acceptedAttemptId } });
408
+ const session = await db.session.findUniqueOrThrow({ where: { id: invocation.sessionId } });
409
+ expect(snapshot.sentMode).toBe('TRANSLATED');
410
+ await enable(false);
411
+ await db.task.update({ where: { id: r.task.id }, data: { title: 'Another input version' } });
412
+ const next = await queued(r, 1);
413
+ expect((await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })).state).toBe('READY');
414
+ await db.task.update({ where: { id: r.task.id }, data: { title: r.task.title } });
415
+ const otherInputs = new TeamTaskInputService(db, translation);
416
+ const results = await Promise.all([inputs, otherInputs].map(service => service.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id })));
417
+ expect(results[0]).toEqual(results[1]);
418
+ expect(results[0]).toMatchObject({ state: 'READY', token: { snapshotId: snapshot.id, sentMode: 'TRANSLATED', acceptedAttemptId: attempt.id } });
419
+ expect(await db.teamTaskInputSnapshot.findUniqueOrThrow({ where: { id: snapshot.id } })).toEqual(snapshot);
420
+ expect(await db.translationAttempt.findUniqueOrThrow({ where: { id: attempt.id } })).toEqual(attempt);
421
+ expect(await db.agentInvocation.findUniqueOrThrow({ where: { id: invocation.id } })).toEqual(invocation);
422
+ expect(await db.session.findUniqueOrThrow({ where: { id: session.id } })).toEqual(session);
423
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: first.id } })).taskInputSnapshotId).toBe(snapshot.id);
424
+ });
425
+ it('claim race after Session creation deletes unspawned Session and leaves QUEUED without start failures', async () => {
426
+ const r = await room('AUTO');
427
+ await queued(r);
428
+ const s = scheduler(r, async () => { await db.task.update({ where: { id: r.task.id }, data: { title: 'Task changed at final claim' } }); });
429
+ expect(await s.service.startNextSessions(r.team.id)).toEqual([]);
430
+ expect(await db.session.count()).toBe(0);
431
+ expect(s.runtime.start).not.toHaveBeenCalled();
432
+ expect(await db.workRequest.findFirstOrThrow()).toMatchObject({ status: 'QUEUED', startAttemptCount: 0, lastStartError: null });
433
+ expect(await db.agentInvocation.count()).toBe(0);
434
+ });
435
+ it.each(['RoomMessage', 'WorkRequest', 'DeliveryReceipt', 'MessageDelivery'])('rolls back %s publication fault and retries delivery without retranslating', async (model) => {
436
+ const r = await room();
437
+ await enable();
438
+ fault = { model, action: model === 'MessageDelivery' ? 'updateMany' : 'create', predicate: model === 'MessageDelivery' ? args => args.data?.state === 'SENT' : undefined };
439
+ const accepted = await submit(r);
440
+ await until(async () => (await deliveries.get(accepted.operation.operationId)).state === 'DELIVERY_FAILED');
441
+ expect(await db.roomMessage.count()).toBe(0);
442
+ expect(await db.workRequest.count()).toBe(0);
443
+ expect(await db.deliveryReceipt.count()).toBe(0);
444
+ expect((await db.teamRun.findUniqueOrThrow({ where: { id: r.team.id } })).currentTaskInputSnapshotId).toBeNull();
445
+ const before = requests.length;
446
+ const row = await deliveries.get(accepted.operation.operationId);
447
+ await deliveries.action(row.id, 'retry_delivery', row.revision, randomUUID());
448
+ await sent(row.id);
449
+ expect(requests).toHaveLength(before);
450
+ expect(await db.roomMessage.count()).toBe(1);
451
+ expect(await db.workRequest.count()).toBe(2);
452
+ });
453
+ it('concurrent duplicate HTTP and two workers keep one publication after a post-commit failure', async () => {
454
+ const r = await room();
455
+ await enable();
456
+ const other = new MessageDeliveryService(db, settings, translation, container.runtime, { taskInputs: inputs, teamRuns, tickMs: 10 });
457
+ await other.start();
458
+ notify.mockRejectedValueOnce(new Error('Controlled response lost after commit'));
459
+ try {
460
+ const body = { clientSubmissionId: randomUUID(), content: '用户 concurrent' };
461
+ const replies = await Promise.all(Array.from({ length: 4 }, () => app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, payload: body })));
462
+ expect(replies.every(reply => reply.statusCode === 202)).toBe(true);
463
+ const ids = new Set(replies.map(reply => reply.json().operationId));
464
+ expect(ids.size).toBe(1);
465
+ const row = await sent([...ids][0]);
466
+ expect(await db.messageDelivery.count()).toBe(1);
467
+ expect(await db.roomMessage.count()).toBe(1);
468
+ expect(await db.deliveryReceipt.count()).toBe(1);
469
+ expect(await db.workRequest.count()).toBe(2);
470
+ expect((await other.get(row.id)).state).toBe('SENT');
471
+ }
472
+ finally {
473
+ await other.shutdown();
474
+ }
475
+ }, 15000);
476
+ it('freezes explicit mentions, target owner and attachments; an invalid recipient cannot partially publish', async () => {
477
+ const r = await room();
478
+ await enable();
479
+ failed.add('用户 route');
480
+ const attachment = await db.attachment.create({ data: { hash: randomUUID(), originalName: 'proof.txt', mimeType: 'text/plain', sizeBytes: 4, storagePath: path.join(dir, 'proof.txt') } });
481
+ const target = { kind: 'WORKSPACE_COMMIT', purpose: 'REVIEW', sourceWorkspaceId: r.workspace.id, headSha: 'a'.repeat(40), branchName: 'controlled' };
482
+ const accepted = await submit(r, '用户 route', { attachmentIds: [attachment.id], mentions: r.members.map((m, index) => ({ memberId: m.id, ...(index ? {} : { target }) })) });
483
+ await until(async () => (await deliveries.get(accepted.operation.operationId)).state === 'TRANSLATION_FAILED');
484
+ await db.teamMember.update({ where: { id: r.members[1].id }, data: { membershipStatus: 'REMOVED' } });
485
+ let row = await deliveries.get(accepted.operation.operationId);
486
+ await deliveries.action(row.id, 'send_original', row.revision, randomUUID());
487
+ await until(async () => (await deliveries.get(row.id)).state === 'DELIVERY_FAILED');
488
+ expect((await deliveries.get(row.id)).lastError).toBe('RECIPIENT_UNAVAILABLE');
489
+ expect(await db.roomMessage.count()).toBe(0);
490
+ expect(await db.workRequest.count()).toBe(0);
491
+ await db.teamMember.update({ where: { id: r.members[1].id }, data: { membershipStatus: 'ACTIVE' } });
492
+ row = await deliveries.get(row.id);
493
+ await deliveries.action(row.id, 'retry_delivery', row.revision, randomUUID());
494
+ const published = await sent(row.id);
495
+ const message = await db.roomMessage.findUniqueOrThrow({ where: { deliveryId: row.id } });
496
+ expect(message.attachmentIds).toBe(JSON.stringify([attachment.id]));
497
+ expect((await db.workRequest.findFirstOrThrow({ where: { triggerMessageId: message.id, targetMemberId: r.members[0].id } })).targetHeadSha).toBe(target.headSha);
498
+ expect(published.sentMode).toBe('ORIGINAL');
499
+ });
500
+ it('shared owner actions are scoped, revision checked and idempotent; final claim rechecks an older input consumer', async () => {
501
+ const r = await room('AUTO');
502
+ await enable();
503
+ failed.add('任务 Task T');
504
+ const first = await queued(r);
505
+ await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: first.id });
506
+ await until(async () => (await db.translationSource.findFirst({ where: { sourceKind: 'TASK_INPUT' } }))?.jobState === 'FAILED');
507
+ const old = await db.teamTaskInputSnapshot.findFirstOrThrow();
508
+ const other = await room();
509
+ const action = { action: 'send_original', expectedRevision: old.revision, actionId: randomUUID() };
510
+ const url = `/api/team-runs/${r.team.id}/task-inputs/${old.id}/actions`;
511
+ expect((await app.inject({ method: 'POST', url: url.replace(r.team.id, other.team.id), payload: action })).statusCode).toBe(404);
512
+ expect((await app.inject({ method: 'POST', url, payload: action })).statusCode).toBe(200);
513
+ expect((await app.inject({ method: 'POST', url, payload: action })).statusCode).toBe(200);
514
+ expect((await app.inject({ method: 'POST', url, payload: { ...action, actionId: randomUUID() } })).statusCode).toBe(409);
515
+ await db.task.update({ where: { id: r.task.id }, data: { title: 'Task U' } });
516
+ await enable(false);
517
+ const next = await queued(r, 1);
518
+ const prepared = await inputs.ensureForDispatch(r.team.id, { kind: 'WORK_REQUEST', id: next.id });
519
+ expect(prepared.state).toBe('READY');
520
+ if (prepared.state !== 'READY')
521
+ throw new Error('Expected current input');
522
+ // Simulate an older consumer acquiring a different unresolved input after preflight.
523
+ const blocked = await db.teamTaskInputSnapshot.create({ data: { teamRunId: r.team.id, taskId: r.task.id, taskSourceHash: taskInputHash('Older input', null), originalTitle: 'Older input' } });
524
+ await db.workRequest.update({ where: { id: first.id }, data: { taskInputSnapshotId: blocked.id } });
525
+ await expect(db.$transaction(tx => inputs.assertForClaim(tx, r.team.id, { kind: 'WORK_REQUEST', id: next.id }, prepared.token))).rejects.toMatchObject({ code: 'TASK_INPUT_CHANGED' });
526
+ expect((await db.workRequest.findUniqueOrThrow({ where: { id: next.id } })).startAttemptCount).toBe(0);
527
+ });
528
+ it('uses actual owner/authKind with password on/off, explicit internal user origin and same-origin enforcement', async () => {
529
+ const r = await room();
530
+ const payload = () => ({ content: '用户 auth', clientSubmissionId: randomUUID() });
531
+ const url = `/api/team-runs/${r.team.id}/messages`;
532
+ expect((await app.inject({ method: 'POST', url, headers: { host: 'local.test', origin: 'https://other.test' }, payload: payload() })).statusCode).toBe(403);
533
+ const internal = randomUUID();
534
+ const prior = process.env.AGENT_TOWER_INTERNAL_TOKEN;
535
+ process.env.AGENT_TOWER_INTERNAL_TOKEN = internal;
536
+ try {
537
+ const headers = { 'x-agent-tower-internal-token': internal };
538
+ expect((await app.inject({ method: 'POST', url, headers, payload: payload() })).statusCode).toBe(403);
539
+ expect((await app.inject({ method: 'POST', url, headers: { ...headers, 'x-agent-tower-input-origin': 'user' }, payload: payload() })).statusCode).toBe(202);
540
+ const login = await auth.updateSettings({ enabled: true, newPassword: 'controlled-only-password' });
541
+ expect((await app.inject({ method: 'POST', url, payload: payload() })).statusCode).toBe(401);
542
+ expect((await app.inject({ method: 'POST', url, headers: { cookie: `${auth.cookieName}=${login.sessionToken}` }, payload: payload() })).statusCode).toBe(202);
543
+ }
544
+ finally {
545
+ if (prior === undefined)
546
+ delete process.env.AGENT_TOWER_INTERNAL_TOKEN;
547
+ else
548
+ process.env.AGENT_TOWER_INTERNAL_TOKEN = prior;
549
+ }
550
+ });
551
+ it('strict real credentials: solo Agent cannot fall back to user; snapshot GET and MCP are fixed, legacy is 409; owner originals stay private', async () => {
552
+ const r = await room('AUTO');
553
+ await db.task.update({ where: { id: r.task.id }, data: { description: 'Long fixed input. '.repeat(30) } });
554
+ await enable();
555
+ const accepted = await submit(r);
556
+ const delivered = await sent(accepted.operation.operationId);
557
+ const s = scheduler(r);
558
+ await s.service.startNextSessions(r.team.id);
559
+ const invocation = await db.agentInvocation.findFirstOrThrow({ where: { memberId: r.members[0].id } });
560
+ const credential = credentials.createAgentApiCredential({ sessionId: invocation.sessionId, invocationId: invocation.id });
561
+ const headers = { 'x-agent-tower-agent-credential': credential };
562
+ const fullUrl = `/api/team-runs/${r.team.id}/task-input?view=full`;
563
+ const full = await app.inject({ url: fullUrl, headers });
564
+ expect(full.statusCode, full.body).toBe(200);
565
+ expect(full.json().effectiveUserText).toContain('Task Task T');
566
+ const preview = await app.inject({ url: fullUrl.replace('full', 'preview'), headers });
567
+ expect(preview.json()).toMatchObject({ snapshotId: full.json().snapshotId, taskSourceHash: full.json().taskSourceHash, isTruncated: true, fullContentAvailable: true });
568
+ expect(preview.json().effectiveUserText.length).toBeLessThanOrEqual(240);
569
+ await db.task.update({ where: { id: r.task.id }, data: { title: 'New task version' } });
570
+ expect((await app.inject({ url: fullUrl, headers })).json()).toEqual(full.json());
571
+ expect((await app.inject({ url: `/api/team-runs/${r.team.id}/messages/${delivered.acceptedDestinationId}`, headers })).json().content).toBe('User hello');
572
+ expect((await app.inject({ url: `/api/team-runs/${r.team.id}/messages/${delivered.acceptedDestinationId}`, headers })).json()).not.toHaveProperty('userDelivery');
573
+ const posted = await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, headers, payload: { content: 'Agent body stays original' } });
574
+ expect(posted.statusCode, posted.body).toBe(201);
575
+ expect(posted.json().content).toBe('Agent body stays original');
576
+ const otherTeam = await room();
577
+ expect((await app.inject({ url: fullUrl.replace(r.team.id, otherTeam.team.id), headers })).statusCode).toBe(404);
578
+ await db.teamMember.update({ where: { id: invocation.memberId }, data: { membershipStatus: 'REMOVED' } });
579
+ expect((await app.inject({ url: fullUrl, headers })).statusCode).toBe(403);
580
+ await db.teamMember.update({ where: { id: invocation.memberId }, data: { membershipStatus: 'ACTIVE' } });
581
+ await db.agentInvocation.update({ where: { id: invocation.id }, data: { dispatchRevokedAt: new Date() } });
582
+ expect((await app.inject({ url: fullUrl, headers })).statusCode).toBe(403);
583
+ await db.agentInvocation.update({ where: { id: invocation.id }, data: { dispatchRevokedAt: null } });
584
+ for (const url of [`/api/team-runs/${r.team.id}/task-inputs`, `/api/message-deliveries/${delivered.id}`])
585
+ expect((await app.inject({ url, headers })).statusCode).toBe(403);
586
+ const solo = await db.session.create({ data: { workspaceId: r.workspace.id, agentType: 'CODEX', prompt: 'Solo', status: 'RUNNING' } });
587
+ const soloCredential = credentials.createAgentApiCredential({ sessionId: solo.id, invocationId: null });
588
+ expect((await app.inject({ method: 'POST', url: `/api/team-runs/${r.team.id}/messages`, headers: { 'x-agent-tower-agent-credential': soloCredential }, payload: { content: 'forged', clientSubmissionId: randomUUID() } })).statusCode).toBe(403);
589
+ expect((await app.inject({ url: fullUrl, headers: { ...headers, 'x-agent-tower-invocation-id': randomUUID() } })).statusCode).toBe(401);
590
+ expect((await app.inject({ url: fullUrl, headers: { 'x-agent-tower-agent-credential': 'invalid' } })).statusCode).toBe(401);
591
+ expect((await app.inject({ url: fullUrl })).statusCode).toBe(403);
592
+ await app.listen({ port: 0, host: '127.0.0.1' });
593
+ const prior = { ...process.env };
594
+ Object.assign(process.env, { AGENT_TOWER_TEAM_RUN_ID: r.team.id, AGENT_TOWER_MEMBER_ID: invocation.memberId, AGENT_TOWER_INVOCATION_ID: invocation.id, AGENT_TOWER_SESSION_ID: invocation.sessionId });
595
+ const server = await (await import('../../mcp/server.js')).createMcpServer(`http://127.0.0.1:${app.server.address().port}`, { agentApiCredential: credential });
596
+ const client = new Client({ name: 'team-input-controlled', version: '1' });
597
+ const [a, b] = InMemoryTransport.createLinkedPair();
598
+ await server.connect(b);
599
+ await client.connect(a);
600
+ try {
601
+ const result = await client.callTool({ name: 'get_team_task_input', arguments: { view: 'full' } });
602
+ expect(result.isError).not.toBe(true);
603
+ expect(JSON.parse(result.content[0].text)).toEqual(full.json());
604
+ await db.agentInvocation.update({ where: { id: invocation.id }, data: { taskInputSnapshotId: null } });
605
+ expect((await app.inject({ url: fullUrl, headers })).statusCode).toBe(409);
606
+ const legacy = await client.callTool({ name: 'get_team_task_input', arguments: { view: 'preview' } });
607
+ expect(legacy.isError).toBe(true);
608
+ expect(JSON.stringify(legacy.content)).toContain('LEGACY_TASK_INPUT_NOT_SNAPSHOTTED');
609
+ }
610
+ finally {
611
+ await client.close();
612
+ await server.close();
613
+ for (const key of ['AGENT_TOWER_TEAM_RUN_ID', 'AGENT_TOWER_MEMBER_ID', 'AGENT_TOWER_INVOCATION_ID', 'AGENT_TOWER_SESSION_ID']) {
614
+ if (prior[key] === undefined)
615
+ delete process.env[key];
616
+ else
617
+ process.env[key] = prior[key];
618
+ }
619
+ }
620
+ });
621
+ });
622
+ //# sourceMappingURL=team-user-delivery.integration.test.js.map