@rudderhq/server 0.5.2-canary.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  2. package/dist/bootstrap/register-api-routes.js +15 -0
  3. package/dist/bootstrap/register-api-routes.js.map +1 -1
  4. package/dist/bootstrap/types.d.ts +11 -0
  5. package/dist/bootstrap/types.d.ts.map +1 -1
  6. package/dist/bundled-plugins/plugin-linear/dist/worker.js +741 -7
  7. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
  8. package/dist/config.d.ts +2 -0
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +3 -0
  11. package/dist/config.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +28 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/local-runtime.d.ts +7 -0
  16. package/dist/local-runtime.d.ts.map +1 -1
  17. package/dist/local-runtime.js +15 -1
  18. package/dist/local-runtime.js.map +1 -1
  19. package/dist/middleware/auth.js +2 -2
  20. package/dist/middleware/auth.js.map +1 -1
  21. package/dist/middleware/error-handler.d.ts.map +1 -1
  22. package/dist/middleware/error-handler.js +3 -2
  23. package/dist/middleware/error-handler.js.map +1 -1
  24. package/dist/middleware/logger.d.ts +4 -0
  25. package/dist/middleware/logger.d.ts.map +1 -1
  26. package/dist/middleware/logger.js +82 -7
  27. package/dist/middleware/logger.js.map +1 -1
  28. package/dist/middleware/logger.test.js +94 -1
  29. package/dist/middleware/logger.test.js.map +1 -1
  30. package/dist/routes/chats.annotation-routes.d.ts +47 -0
  31. package/dist/routes/chats.annotation-routes.d.ts.map +1 -0
  32. package/dist/routes/chats.annotation-routes.js +164 -0
  33. package/dist/routes/chats.annotation-routes.js.map +1 -0
  34. package/dist/routes/chats.d.ts.map +1 -1
  35. package/dist/routes/chats.js +467 -165
  36. package/dist/routes/chats.js.map +1 -1
  37. package/dist/routes/chats.stream-routes.d.ts +1 -10
  38. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  39. package/dist/routes/chats.stream-routes.js +168 -110
  40. package/dist/routes/chats.stream-routes.js.map +1 -1
  41. package/dist/routes/chats.stream-support.d.ts +79 -0
  42. package/dist/routes/chats.stream-support.d.ts.map +1 -0
  43. package/dist/routes/chats.stream-support.js +121 -0
  44. package/dist/routes/chats.stream-support.js.map +1 -0
  45. package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
  46. package/dist/routes/issues.comments-attachments.js +16 -2
  47. package/dist/routes/issues.comments-attachments.js.map +1 -1
  48. package/dist/routes/issues.d.ts.map +1 -1
  49. package/dist/routes/issues.js +21 -4
  50. package/dist/routes/issues.js.map +1 -1
  51. package/dist/routes/issues.mutations.d.ts.map +1 -1
  52. package/dist/routes/issues.mutations.js +20 -33
  53. package/dist/routes/issues.mutations.js.map +1 -1
  54. package/dist/routes/managed-mcp-agent-bindings.d.ts +15 -0
  55. package/dist/routes/managed-mcp-agent-bindings.d.ts.map +1 -0
  56. package/dist/routes/managed-mcp-agent-bindings.js +66 -0
  57. package/dist/routes/managed-mcp-agent-bindings.js.map +1 -0
  58. package/dist/routes/managed-mcp-connections.d.ts +8 -0
  59. package/dist/routes/managed-mcp-connections.d.ts.map +1 -0
  60. package/dist/routes/managed-mcp-connections.js +189 -0
  61. package/dist/routes/managed-mcp-connections.js.map +1 -0
  62. package/dist/routes/managed-mcp-runtime.d.ts +5 -0
  63. package/dist/routes/managed-mcp-runtime.d.ts.map +1 -0
  64. package/dist/routes/managed-mcp-runtime.js +148 -0
  65. package/dist/routes/managed-mcp-runtime.js.map +1 -0
  66. package/dist/routes/messenger.d.ts.map +1 -1
  67. package/dist/routes/messenger.js +1 -1
  68. package/dist/routes/messenger.js.map +1 -1
  69. package/dist/services/access.d.ts.map +1 -1
  70. package/dist/services/access.js +91 -12
  71. package/dist/services/access.js.map +1 -1
  72. package/dist/services/agent-run-context.d.ts +1 -0
  73. package/dist/services/agent-run-context.d.ts.map +1 -1
  74. package/dist/services/agent-run-context.js +5 -1
  75. package/dist/services/agent-run-context.js.map +1 -1
  76. package/dist/services/board-auth.d.ts +2 -2
  77. package/dist/services/chat-annotation-copy-lineage.d.ts +32 -0
  78. package/dist/services/chat-annotation-copy-lineage.d.ts.map +1 -0
  79. package/dist/services/chat-annotation-copy-lineage.js +110 -0
  80. package/dist/services/chat-annotation-copy-lineage.js.map +1 -0
  81. package/dist/services/chat-assistant.annotations.d.ts +16 -0
  82. package/dist/services/chat-assistant.annotations.d.ts.map +1 -0
  83. package/dist/services/chat-assistant.annotations.js +108 -0
  84. package/dist/services/chat-assistant.annotations.js.map +1 -0
  85. package/dist/services/chat-assistant.annotations.test.d.ts +2 -0
  86. package/dist/services/chat-assistant.annotations.test.d.ts.map +1 -0
  87. package/dist/services/chat-assistant.annotations.test.js +257 -0
  88. package/dist/services/chat-assistant.annotations.test.js.map +1 -0
  89. package/dist/services/chat-assistant.d.ts.map +1 -1
  90. package/dist/services/chat-assistant.helpers.d.ts +2 -3
  91. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  92. package/dist/services/chat-assistant.helpers.js +37 -53
  93. package/dist/services/chat-assistant.helpers.js.map +1 -1
  94. package/dist/services/chat-assistant.js +61 -5
  95. package/dist/services/chat-assistant.js.map +1 -1
  96. package/dist/services/chat-generation-protocol.d.ts +8 -15
  97. package/dist/services/chat-generation-protocol.d.ts.map +1 -1
  98. package/dist/services/chat-generation-protocol.js +19 -59
  99. package/dist/services/chat-generation-protocol.js.map +1 -1
  100. package/dist/services/chat-generation-protocol.test.js +168 -6
  101. package/dist/services/chat-generation-protocol.test.js.map +1 -1
  102. package/dist/services/chat-generation-provenance.d.ts +14 -0
  103. package/dist/services/chat-generation-provenance.d.ts.map +1 -0
  104. package/dist/services/chat-generation-provenance.js +71 -0
  105. package/dist/services/chat-generation-provenance.js.map +1 -0
  106. package/dist/services/chat-inline-annotation-rendering.d.ts +2 -0
  107. package/dist/services/chat-inline-annotation-rendering.d.ts.map +1 -0
  108. package/dist/services/chat-inline-annotation-rendering.js +265 -0
  109. package/dist/services/chat-inline-annotation-rendering.js.map +1 -0
  110. package/dist/services/chat-inline-annotation-resolved-labels.d.ts +11 -0
  111. package/dist/services/chat-inline-annotation-resolved-labels.d.ts.map +1 -0
  112. package/dist/services/chat-inline-annotation-resolved-labels.js +490 -0
  113. package/dist/services/chat-inline-annotation-resolved-labels.js.map +1 -0
  114. package/dist/services/chat-inline-annotation-validation.d.ts +15 -0
  115. package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -0
  116. package/dist/services/chat-inline-annotation-validation.js +430 -0
  117. package/dist/services/chat-inline-annotation-validation.js.map +1 -0
  118. package/dist/services/chat-inline-annotations.d.ts +18 -0
  119. package/dist/services/chat-inline-annotations.d.ts.map +1 -0
  120. package/dist/services/chat-inline-annotations.js +52 -0
  121. package/dist/services/chat-inline-annotations.js.map +1 -0
  122. package/dist/services/chat-inline-annotations.test.d.ts +2 -0
  123. package/dist/services/chat-inline-annotations.test.d.ts.map +1 -0
  124. package/dist/services/chat-inline-annotations.test.js +1306 -0
  125. package/dist/services/chat-inline-annotations.test.js.map +1 -0
  126. package/dist/services/chat-queued-message-materialization.d.ts +126 -0
  127. package/dist/services/chat-queued-message-materialization.d.ts.map +1 -0
  128. package/dist/services/chat-queued-message-materialization.js +327 -0
  129. package/dist/services/chat-queued-message-materialization.js.map +1 -0
  130. package/dist/services/chat-steer-messages.d.ts +3 -3
  131. package/dist/services/chat-steer-messages.d.ts.map +1 -1
  132. package/dist/services/chat-steer-messages.js +50 -80
  133. package/dist/services/chat-steer-messages.js.map +1 -1
  134. package/dist/services/chat-work-manifest.d.ts.map +1 -1
  135. package/dist/services/chat-work-manifest.js +5 -1
  136. package/dist/services/chat-work-manifest.js.map +1 -1
  137. package/dist/services/chats.annotation-persistence.d.ts +20 -0
  138. package/dist/services/chats.annotation-persistence.d.ts.map +1 -0
  139. package/dist/services/chats.annotation-persistence.js +206 -0
  140. package/dist/services/chats.annotation-persistence.js.map +1 -0
  141. package/dist/services/chats.d.ts +129 -96
  142. package/dist/services/chats.d.ts.map +1 -1
  143. package/dist/services/chats.helpers.d.ts +2 -2
  144. package/dist/services/chats.inline-visual-persistence.d.ts +3 -3
  145. package/dist/services/chats.inline-visual-persistence.d.ts.map +1 -1
  146. package/dist/services/chats.inline-visual-persistence.js +114 -54
  147. package/dist/services/chats.inline-visual-persistence.js.map +1 -1
  148. package/dist/services/chats.js +743 -252
  149. package/dist/services/chats.js.map +1 -1
  150. package/dist/services/finance.d.ts +1 -1
  151. package/dist/services/index.d.ts +8 -0
  152. package/dist/services/index.d.ts.map +1 -1
  153. package/dist/services/index.js +8 -0
  154. package/dist/services/index.js.map +1 -1
  155. package/dist/services/integrations/agent-integrations.d.ts +2 -2
  156. package/dist/services/integrations/custom-integrations.d.ts +3 -1
  157. package/dist/services/integrations/custom-integrations.d.ts.map +1 -1
  158. package/dist/services/integrations/custom-integrations.js +13 -2
  159. package/dist/services/integrations/custom-integrations.js.map +1 -1
  160. package/dist/services/issues.comments-attachments.d.ts +7 -1
  161. package/dist/services/issues.comments-attachments.d.ts.map +1 -1
  162. package/dist/services/issues.comments-attachments.js +13 -1
  163. package/dist/services/issues.comments-attachments.js.map +1 -1
  164. package/dist/services/issues.d.ts +6 -0
  165. package/dist/services/issues.d.ts.map +1 -1
  166. package/dist/services/issues.js +22 -3
  167. package/dist/services/issues.js.map +1 -1
  168. package/dist/services/knowledge-portability/organization-portability.core.d.ts +4 -1
  169. package/dist/services/knowledge-portability/organization-portability.core.d.ts.map +1 -1
  170. package/dist/services/knowledge-portability/organization-portability.core.js +17 -3
  171. package/dist/services/knowledge-portability/organization-portability.core.js.map +1 -1
  172. package/dist/services/knowledge-portability/organization-portability.export.d.ts.map +1 -1
  173. package/dist/services/knowledge-portability/organization-portability.export.js +2 -2
  174. package/dist/services/knowledge-portability/organization-portability.export.js.map +1 -1
  175. package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
  176. package/dist/services/knowledge-portability/organization-portability.import.js +13 -3
  177. package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
  178. package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +3 -0
  179. package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
  180. package/dist/services/knowledge-portability/organization-skills.catalog.js +54 -23
  181. package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
  182. package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
  183. package/dist/services/knowledge-portability/organization-skills.js +627 -124
  184. package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
  185. package/dist/services/knowledge-portability/organization-skills.sources.d.ts +1 -0
  186. package/dist/services/knowledge-portability/organization-skills.sources.d.ts.map +1 -1
  187. package/dist/services/knowledge-portability/organization-skills.sources.js +53 -10
  188. package/dist/services/knowledge-portability/organization-skills.sources.js.map +1 -1
  189. package/dist/services/mcp/authorizer-lock.d.ts +9 -0
  190. package/dist/services/mcp/authorizer-lock.d.ts.map +1 -0
  191. package/dist/services/mcp/authorizer-lock.js +15 -0
  192. package/dist/services/mcp/authorizer-lock.js.map +1 -0
  193. package/dist/services/mcp/managed-bindings.d.ts +17 -0
  194. package/dist/services/mcp/managed-bindings.d.ts.map +1 -0
  195. package/dist/services/mcp/managed-bindings.js +332 -0
  196. package/dist/services/mcp/managed-bindings.js.map +1 -0
  197. package/dist/services/mcp/managed-client.d.ts +64 -0
  198. package/dist/services/mcp/managed-client.d.ts.map +1 -0
  199. package/dist/services/mcp/managed-client.js +238 -0
  200. package/dist/services/mcp/managed-client.js.map +1 -0
  201. package/dist/services/mcp/managed-client.test.d.ts +2 -0
  202. package/dist/services/mcp/managed-client.test.d.ts.map +1 -0
  203. package/dist/services/mcp/managed-client.test.js +398 -0
  204. package/dist/services/mcp/managed-client.test.js.map +1 -0
  205. package/dist/services/mcp/managed-connections.d.ts +81 -0
  206. package/dist/services/mcp/managed-connections.d.ts.map +1 -0
  207. package/dist/services/mcp/managed-connections.js +920 -0
  208. package/dist/services/mcp/managed-connections.js.map +1 -0
  209. package/dist/services/mcp/managed-runtime.d.ts +134 -0
  210. package/dist/services/mcp/managed-runtime.d.ts.map +1 -0
  211. package/dist/services/mcp/managed-runtime.js +282 -0
  212. package/dist/services/mcp/managed-runtime.js.map +1 -0
  213. package/dist/services/mcp/oauth-provider.d.ts +48 -0
  214. package/dist/services/mcp/oauth-provider.d.ts.map +1 -0
  215. package/dist/services/mcp/oauth-provider.js +120 -0
  216. package/dist/services/mcp/oauth-provider.js.map +1 -0
  217. package/dist/services/mcp/oauth-scope.test.d.ts +2 -0
  218. package/dist/services/mcp/oauth-scope.test.d.ts.map +1 -0
  219. package/dist/services/mcp/oauth-scope.test.js +69 -0
  220. package/dist/services/mcp/oauth-scope.test.js.map +1 -0
  221. package/dist/services/mcp/oauth-session-gc.d.ts +14 -0
  222. package/dist/services/mcp/oauth-session-gc.d.ts.map +1 -0
  223. package/dist/services/mcp/oauth-session-gc.js +34 -0
  224. package/dist/services/mcp/oauth-session-gc.js.map +1 -0
  225. package/dist/services/mcp/oauth-session-gc.test.d.ts +2 -0
  226. package/dist/services/mcp/oauth-session-gc.test.d.ts.map +1 -0
  227. package/dist/services/mcp/oauth-session-gc.test.js +66 -0
  228. package/dist/services/mcp/oauth-session-gc.test.js.map +1 -0
  229. package/dist/services/mcp/oauth.d.ts +61 -0
  230. package/dist/services/mcp/oauth.d.ts.map +1 -0
  231. package/dist/services/mcp/oauth.js +1690 -0
  232. package/dist/services/mcp/oauth.js.map +1 -0
  233. package/dist/services/mcp/pinned-fetch.d.ts +15 -0
  234. package/dist/services/mcp/pinned-fetch.d.ts.map +1 -0
  235. package/dist/services/mcp/pinned-fetch.js +400 -0
  236. package/dist/services/mcp/pinned-fetch.js.map +1 -0
  237. package/dist/services/mcp/pinned-fetch.test.d.ts +2 -0
  238. package/dist/services/mcp/pinned-fetch.test.d.ts.map +1 -0
  239. package/dist/services/mcp/pinned-fetch.test.js +190 -0
  240. package/dist/services/mcp/pinned-fetch.test.js.map +1 -0
  241. package/dist/services/mcp/provider-registry.d.ts +77 -0
  242. package/dist/services/mcp/provider-registry.d.ts.map +1 -0
  243. package/dist/services/mcp/provider-registry.js +80 -0
  244. package/dist/services/mcp/provider-registry.js.map +1 -0
  245. package/dist/services/mcp/provider-registry.test.d.ts +2 -0
  246. package/dist/services/mcp/provider-registry.test.d.ts.map +1 -0
  247. package/dist/services/mcp/provider-registry.test.js +80 -0
  248. package/dist/services/mcp/provider-registry.test.js.map +1 -0
  249. package/dist/services/mcp/security-policy.d.ts +48 -0
  250. package/dist/services/mcp/security-policy.d.ts.map +1 -0
  251. package/dist/services/mcp/security-policy.js +344 -0
  252. package/dist/services/mcp/security-policy.js.map +1 -0
  253. package/dist/services/mcp/security-policy.test.d.ts +2 -0
  254. package/dist/services/mcp/security-policy.test.d.ts.map +1 -0
  255. package/dist/services/mcp/security-policy.test.js +226 -0
  256. package/dist/services/mcp/security-policy.test.js.map +1 -0
  257. package/dist/services/mcp/tool-discovery.d.ts +49 -0
  258. package/dist/services/mcp/tool-discovery.d.ts.map +1 -0
  259. package/dist/services/mcp/tool-discovery.js +198 -0
  260. package/dist/services/mcp/tool-discovery.js.map +1 -0
  261. package/dist/services/mcp/tool-discovery.test.d.ts +2 -0
  262. package/dist/services/mcp/tool-discovery.test.d.ts.map +1 -0
  263. package/dist/services/mcp/tool-discovery.test.js +168 -0
  264. package/dist/services/mcp/tool-discovery.test.js.map +1 -0
  265. package/dist/services/messenger.d.ts +1 -1
  266. package/dist/services/messenger.d.ts.map +1 -1
  267. package/dist/services/messenger.js +21 -3
  268. package/dist/services/messenger.js.map +1 -1
  269. package/dist/services/plugin-registry.d.ts +3 -3
  270. package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -1
  271. package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
  272. package/dist/services/runtime-kernel/heartbeat.core.js +30 -4
  273. package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
  274. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  275. package/dist/services/runtime-kernel/heartbeat.wakeup.js +38 -23
  276. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  277. package/dist/services/secrets.d.ts +64 -6
  278. package/dist/services/secrets.d.ts.map +1 -1
  279. package/dist/services/secrets.js +38 -15
  280. package/dist/services/secrets.js.map +1 -1
  281. package/dist/services/side-chats.d.ts +1 -1
  282. package/dist/services/side-chats.d.ts.map +1 -1
  283. package/dist/services/side-chats.js +56 -14
  284. package/dist/services/side-chats.js.map +1 -1
  285. package/dist/services/workspace-runtime.services.d.ts +2 -2
  286. package/dist/services/workspace-web-preview.js +1 -1
  287. package/package.json +16 -13
  288. package/resources/bundled-skills/rudder-docs/references/operating-practices.md +12 -5
  289. package/skills/rudder-docs/references/operating-practices.md +12 -5
  290. package/ui-dist/assets/LibraryLiveSurface-CKOthw54.js +1 -0
  291. package/ui-dist/assets/{abnfDiagram-VRR7QNED-KGtP7zoC.js → abnfDiagram-VRR7QNED-C6a_wMho.js} +1 -1
  292. package/ui-dist/assets/{arc-JjnKyiOb.js → arc-CP0En6KA.js} +1 -1
  293. package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-AHfOdcaZ.js +36 -0
  294. package/ui-dist/assets/{blockDiagram-677ZJIJ3-D7YzMYrz.js → blockDiagram-677ZJIJ3-Bkl9lp63.js} +1 -1
  295. package/ui-dist/assets/{c4Diagram-LMCZKHZV-Dpf7UQxl.js → c4Diagram-LMCZKHZV-BpZareVt.js} +1 -1
  296. package/ui-dist/assets/channel-DqxL_Zcy.js +1 -0
  297. package/ui-dist/assets/{chunk-2Q5K7J3B-UJ3gsGlQ.js → chunk-2Q5K7J3B-BQAwVuXc.js} +1 -1
  298. package/ui-dist/assets/{chunk-32BRIVSS-BeHl2j2l.js → chunk-32BRIVSS-YkmfA08F.js} +1 -1
  299. package/ui-dist/assets/{chunk-5VM5RSS4-STE9fJd0.js → chunk-5VM5RSS4-DvvjwTnG.js} +1 -1
  300. package/ui-dist/assets/{chunk-EX3LRPZG-Bj8g8j4B.js → chunk-EX3LRPZG-TXD_Yhg5.js} +1 -1
  301. package/ui-dist/assets/{chunk-JWPE2WC7-0V4Pwta-.js → chunk-JWPE2WC7-CgMlj6XI.js} +1 -1
  302. package/ui-dist/assets/{chunk-MOJQB5TN-DNPn50gm.js → chunk-MOJQB5TN-BnJjU8Rc.js} +1 -1
  303. package/ui-dist/assets/{chunk-RYQCIY6F-Befmnv8r.js → chunk-RYQCIY6F-D9B64JKM.js} +1 -1
  304. package/ui-dist/assets/{chunk-V7JOEXUC-Boh8TjHj.js → chunk-V7JOEXUC-PS09QVpl.js} +1 -1
  305. package/ui-dist/assets/{chunk-VR4S4FIN-B_yEQSo3.js → chunk-VR4S4FIN-DQXb7pRS.js} +1 -1
  306. package/ui-dist/assets/{chunk-XXDRQBXY-Cf1GnUFx.js → chunk-XXDRQBXY-B1UdLiF2.js} +1 -1
  307. package/ui-dist/assets/classDiagram-OUVF2IWQ-DMwjzuJE.js +1 -0
  308. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-DMwjzuJE.js +1 -0
  309. package/ui-dist/assets/cose-bilkent-JH36ORCC-lqqmMB3m.js +1 -0
  310. package/ui-dist/assets/{cynefin-VYW2F7L2-Dv1yELYC.js → cynefin-VYW2F7L2-C9hu0XqZ.js} +1 -1
  311. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-CeFwjFoZ.js → cynefinDiagram-TSTJHNR4-Uuhz4c-1.js} +1 -1
  312. package/ui-dist/assets/{dagre-VKFMJZFB-8idd_4sX.js → dagre-VKFMJZFB-ChDrQYse.js} +1 -1
  313. package/ui-dist/assets/{diagram-FQU43EPY-BBVua6hM.js → diagram-FQU43EPY-DLtjkVSN.js} +1 -1
  314. package/ui-dist/assets/{diagram-G47NLZAW-D1zLQZlp.js → diagram-G47NLZAW-DCvEm4D0.js} +1 -1
  315. package/ui-dist/assets/{diagram-NH7WQ7WH-DE_FTSNv.js → diagram-NH7WQ7WH-Dz5jlsPG.js} +1 -1
  316. package/ui-dist/assets/{diagram-OA4YK3LP-61VSKBEn.js → diagram-OA4YK3LP-BXmQLO-Q.js} +1 -1
  317. package/ui-dist/assets/{diagram-WEI45ONY-C-tED6BJ.js → diagram-WEI45ONY-JnMU3xrx.js} +1 -1
  318. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-BEB8WGt0.js → ebnfDiagram-CCIWWBDH-Kryuqj4r.js} +1 -1
  319. package/ui-dist/assets/{erDiagram-Q63AITRT-AN_jm1TZ.js → erDiagram-Q63AITRT-Bn7g2mSW.js} +1 -1
  320. package/ui-dist/assets/{flowDiagram-23GEKE2U-BwLC6tI7.js → flowDiagram-23GEKE2U-DcjqTK91.js} +4 -4
  321. package/ui-dist/assets/ganttDiagram-NO4QXBWP-BRkw9-GE.js +292 -0
  322. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D9HCL9rY.js → gitGraphDiagram-IHSO6WYX-Cexcp6kR.js} +1 -1
  323. package/ui-dist/assets/graph-Cu7OV4Oi.js +1 -0
  324. package/ui-dist/assets/{index-BQXnpwke.js → index-ASZMPBYm.js} +5 -5
  325. package/ui-dist/assets/{index-5OM32_rf.js → index-B17BqCTj.js} +1 -1
  326. package/ui-dist/assets/{index-Cf0gLy6a.js → index-B9MccU1g.js} +1 -1
  327. package/ui-dist/assets/{index-BoSJ4Rx1.js → index-BPqbtW_X.js} +1 -1
  328. package/ui-dist/assets/{index-CpIdZ4-7.js → index-C2HjDa98.js} +1 -1
  329. package/ui-dist/assets/index-CMPtpj0A.js +1 -0
  330. package/ui-dist/assets/{index-p0ZGU1_7.js → index-CZXgOYtk.js} +1 -1
  331. package/ui-dist/assets/{index-74cx3ESJ.js → index-CknAauOP.js} +1 -1
  332. package/ui-dist/assets/index-Cu1GuH0a.js +1 -0
  333. package/ui-dist/assets/index-D8PdEImm.css +1 -0
  334. package/ui-dist/assets/{index-DrVDiJhe.js → index-DB1YX9-p.js} +1 -1
  335. package/ui-dist/assets/{index-vHMnAQSb.js → index-DBxLZXK7.js} +624 -610
  336. package/ui-dist/assets/{index-CpEfk5zb.js → index-DOCVOD97.js} +1 -1
  337. package/ui-dist/assets/{index-DMuWG0YN.js → index-DUbakRAC.js} +1 -1
  338. package/ui-dist/assets/{index-AT8juaIi.js → index-Dbg1ZwO2.js} +1 -1
  339. package/ui-dist/assets/index-DwbLsoLt.js +1 -0
  340. package/ui-dist/assets/index-hCLSotmS.js +1 -0
  341. package/ui-dist/assets/index-jrhEFc-j.js +1 -0
  342. package/ui-dist/assets/index-kz8ueXgo.js +1 -0
  343. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CyO3CAA1.js → infoDiagram-FWYZ7A6U-DugVGI3K.js} +1 -1
  344. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BLLZuF1_.js → ishikawaDiagram-FXEZZL3T-CHy06Oev.js} +1 -1
  345. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CWzQocvE.js → journeyDiagram-5HDEW3XC-BI0w53rb.js} +1 -1
  346. package/ui-dist/assets/{kanban-definition-HUTT4EX6-Dz4SFU-I.js → kanban-definition-HUTT4EX6-v_bLwx7B.js} +1 -1
  347. package/ui-dist/assets/layout-Dwp1s_bo.js +1 -0
  348. package/ui-dist/assets/{linear-BlzDlAaL.js → linear-DAV4STLZ.js} +1 -1
  349. package/ui-dist/assets/map-Bs6QpaaQ.js +1 -0
  350. package/ui-dist/assets/{mermaid.core-BtCydd-w.js → mermaid.core-fVmaL47V.js} +4 -4
  351. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BeIkKRBx.js → mindmap-definition-LN4V7U3C-5dWD4dxm.js} +1 -1
  352. package/ui-dist/assets/{pegDiagram-2B236MQR-BZgpT4TN.js → pegDiagram-2B236MQR-DB2XsDPN.js} +1 -1
  353. package/ui-dist/assets/{pieDiagram-ENE6RG2P-BRFggcg2.js → pieDiagram-ENE6RG2P-Bc5teLCS.js} +1 -1
  354. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CibUo3gT.js → quadrantDiagram-ABIIQ3AL-BlfNdETt.js} +1 -1
  355. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-DINJqhgn.js → railroadDiagram-RFXS5EU6-BbXywkq3.js} +1 -1
  356. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-NegluQIe.js → requirementDiagram-TGXJPOKE-DU-CapdF.js} +1 -1
  357. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-Kq41yHAd.js → sankeyDiagram-HTMAVEWB-Ftvh3MqA.js} +1 -1
  358. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CngfEI4f.js → sequenceDiagram-DBY2YBRQ-CCtHt9V4.js} +1 -1
  359. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-DUWRNlme.js → sizeCapture-X5ZJPWSS-x0jnIKHx.js} +1 -1
  360. package/ui-dist/assets/{stateDiagram-2N3HPSRC-BwHC_Fz4.js → stateDiagram-2N3HPSRC-2qCrydE7.js} +1 -1
  361. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-CvGRUgRf.js +1 -0
  362. package/ui-dist/assets/{swimlanes-5IMT3BWC-Cqp2rT1K.js → swimlanes-5IMT3BWC-6kqJq5Xf.js} +2 -2
  363. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-EBa7-qSc.js +8 -0
  364. package/ui-dist/assets/{timeline-definition-FHXFAJF6-BG3GYPCu.js → timeline-definition-FHXFAJF6-BuRz8KHZ.js} +1 -1
  365. package/ui-dist/assets/{vennDiagram-L72KCM5P-D1IFU27N.js → vennDiagram-L72KCM5P-BKzMZ_Rv.js} +1 -1
  366. package/ui-dist/assets/{wardleyDiagram-EHGQE667-DboO2kYu.js → wardleyDiagram-EHGQE667-8uXlFtBV.js} +1 -1
  367. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-vI2BGrXP.js → xychartDiagram-FW5EYKEG-CWKGlpF9.js} +1 -1
  368. package/ui-dist/index.html +2 -2
  369. package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-DHyfFgK0.js +0 -36
  370. package/ui-dist/assets/channel-TMGI9Qt9.js +0 -1
  371. package/ui-dist/assets/classDiagram-OUVF2IWQ-r5zIpY6G.js +0 -1
  372. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-r5zIpY6G.js +0 -1
  373. package/ui-dist/assets/cose-bilkent-JH36ORCC-fSNs9447.js +0 -1
  374. package/ui-dist/assets/ganttDiagram-NO4QXBWP-CJYhBF1C.js +0 -292
  375. package/ui-dist/assets/graph-DdBT9W3J.js +0 -1
  376. package/ui-dist/assets/index-6UIO-gg7.js +0 -1
  377. package/ui-dist/assets/index-BVkQVMFn.js +0 -1
  378. package/ui-dist/assets/index-BaAPxkqh.css +0 -1
  379. package/ui-dist/assets/index-CmCERTmS.js +0 -1
  380. package/ui-dist/assets/index-Cr4lUIaJ.js +0 -1
  381. package/ui-dist/assets/index-D0fAqlES.js +0 -1
  382. package/ui-dist/assets/index-DfNcAyZq.js +0 -1
  383. package/ui-dist/assets/layout-D3tY9q4Q.js +0 -1
  384. package/ui-dist/assets/map-Ctg1-a9N.js +0 -1
  385. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-C6P3NY96.js +0 -1
  386. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BNdDt62J.js +0 -8
@@ -1,24 +1,30 @@
1
1
  import { agentIntegrationChatBindings, agentIntegrations, approvals, assets, chatAttachments, chatContextLinks, chatControlActions, chatConversations, chatConversationUserStates, chatGenerationEvents, chatGenerations, chatMessages, chatQueuedMessages } from "@rudderhq/db";
2
2
  import { sanitizeChatStructuredPayload } from "@rudderhq/shared";
3
- import { and, asc, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, or, sql } from "drizzle-orm";
3
+ import { withChatTranscriptGenerationProvenance } from "@rudderhq/shared/chat-transcript-provenance";
4
+ import { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, lte, or, sql } from "drizzle-orm";
4
5
  import { randomUUID } from "node:crypto";
5
- import { conflict, notFound, unprocessable } from "../errors.js";
6
+ import { conflict, HttpError, notFound, unprocessable } from "../errors.js";
6
7
  import { logActivity } from "./activity-log.js";
7
8
  import { agentService } from "./agents.js";
8
9
  import { approvalService } from "./approvals.js";
9
10
  import { ensureChatFamilyGroup } from "./chat-family-groups.js";
10
11
  import { chatGenerationProtocolService } from "./chat-generation-protocol.js";
12
+ import { validateCanonicalChatInlineAnnotations } from "./chat-inline-annotation-validation.js";
13
+ import { hydrateQueuedMessage, materializeQueuedUserMessage, normalizeQueuedMessagePayload, QUEUED_ANNOTATION_ASSETS_KEY, queuedAnnotationAssetState, queuedMessageMutationFingerprint, withQueuedAnnotationAssetState, } from "./chat-queued-message-materialization.js";
14
+ import { createChatAnnotationMessagePersistence } from "./chats.annotation-persistence.js";
11
15
  import { ACTIVE_CHAT_GENERATION_STATUSES, CHAT_GENERATION_CONTROL_LEASE_MS, NATIVE_STEER_GENERATION_STATUSES, SERVER_QUEUE_RUNNING_STATUSES, } from "./chats.constants.js";
12
16
  import { createChatConversation, createChatWithInitialMessage } from "./chats.create.js";
13
17
  import { conversationMutability, nextForkTitle } from "./chats.fork-helpers.js";
14
18
  import { buildSearchSnippet, CHAT_TRANSCRIPT_KEY, chatTranscriptFromPayload, chatTranscriptSummaryFromEntries, contentPath, escapeLikePattern, incomingMessagePreviewSql, issueProposalFromPayload, isVisibleIncomingChatMessage, listContextLinksForConversationIds, listPrimaryIssues, operationProposalDecisionStatusFromPayload, operationProposalFromPayload, resolveContextEntities, safeTrim, stripChatMetadataFromPayload, textContains, truncatePreview, visibleIncomingMessageSql, withOperationProposalDecisionState, withPersistedTranscript, } from "./chats.helpers.js";
15
- import { copyForkInlineVisualMessages, listRecentUserChatMessages, updateTrustedInlineVisualMappings, } from "./chats.inline-visual-persistence.js";
19
+ import { copyForkChatMessages, listRecentUserChatMessages, updateTrustedInlineVisualMappings, } from "./chats.inline-visual-persistence.js";
16
20
  import { issueApprovalService } from "./issue-approvals.js";
17
21
  import { issueService } from "./issues.js";
18
22
  import { normalizeLocalLibraryPathMarkdown } from "./library-path-markdown.js";
19
23
  import { organizationService } from "./orgs.js";
20
24
  import { isPostgresError } from "./postgres-errors.js";
21
25
  const CHAT_TITLE_MAX_LENGTH = 200;
26
+ class InvalidQueueDeliveryActionLinkError extends Error {
27
+ }
22
28
  export function chatService(db) {
23
29
  const generationProtocol = chatGenerationProtocolService(db);
24
30
  const QUEUED_MESSAGE_CLAIM_LEASE_MS = 2 * 60 * 1000;
@@ -27,6 +33,7 @@ export function chatService(db) {
27
33
  const issueApprovalsSvc = issueApprovalService(db);
28
34
  const organizationsSvc = organizationService(db);
29
35
  const agentsSvc = agentService(db);
36
+ const addUserChatMessage = createChatAnnotationMessagePersistence(db, getMessage);
30
37
  async function ensureConversationUserStates(rows, userId) {
31
38
  if (rows.length === 0)
32
39
  return;
@@ -415,30 +422,6 @@ export function chatService(db) {
415
422
  function isQueuePositionConflict(error) {
416
423
  return isPostgresError(error, "23505");
417
424
  }
418
- function normalizeQueuedPayload(payload) {
419
- return {
420
- body: String(payload.body ?? ""),
421
- attachmentIds: Array.isArray(payload.attachmentIds)
422
- ? payload.attachmentIds.filter((id) => typeof id === "string")
423
- : [],
424
- projectId: typeof payload.projectId === "string" ? payload.projectId : null,
425
- skillRefs: Array.isArray(payload.skillRefs)
426
- ? payload.skillRefs.filter((ref) => typeof ref === "string")
427
- : [],
428
- accessMode: typeof payload.accessMode === "string" ? payload.accessMode : null,
429
- model: typeof payload.model === "string" ? payload.model : null,
430
- effort: typeof payload.effort === "string" ? payload.effort : null,
431
- metadata: payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata)
432
- ? payload.metadata
433
- : null,
434
- };
435
- }
436
- function hydrateQueuedMessage(row) {
437
- return {
438
- ...row,
439
- payload: normalizeQueuedPayload(row.payload),
440
- };
441
- }
442
425
  async function createGeneration(orgId, conversationId) {
443
426
  const now = new Date();
444
427
  const [row] = await db
@@ -447,8 +430,8 @@ export function chatService(db) {
447
430
  orgId,
448
431
  conversationId,
449
432
  status: "active",
450
- controlOwnerToken: randomUUID(),
451
- controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
433
+ controlOwnerToken: null,
434
+ controlLeaseExpiresAt: null,
452
435
  startedAt: now,
453
436
  createdAt: now,
454
437
  updatedAt: now,
@@ -552,6 +535,7 @@ export function chatService(db) {
552
535
  }
553
536
  async function listQueuedMessages(conversationId) {
554
537
  await reclaimStaleQueuedMessageClaims(conversationId);
538
+ await reconcileDeliveredQueuedMessages({ conversationId });
555
539
  const rows = await db
556
540
  .select()
557
541
  .from(chatQueuedMessages)
@@ -793,6 +777,10 @@ export function chatService(db) {
793
777
  });
794
778
  }
795
779
  async function beginSteerControlAction(input) {
780
+ await reconcileDeliveredQueuedMessages({
781
+ orgId: input.orgId,
782
+ conversationId: input.conversationId,
783
+ });
796
784
  return db.transaction(async (tx) => {
797
785
  const existingAction = await tx
798
786
  .select()
@@ -837,11 +825,29 @@ export function chatService(db) {
837
825
  const item = await tx
838
826
  .select()
839
827
  .from(chatQueuedMessages)
840
- .where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending"])))
828
+ .where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending", "failed_actionable"])))
841
829
  .limit(1)
842
830
  .then((rows) => rows[0] ?? null);
843
831
  if (!item)
844
832
  throw conflict("Queued feedback is no longer steerable");
833
+ if (item.status === "failed_actionable") {
834
+ const failedAction = item.controlActionId
835
+ ? await tx
836
+ .select()
837
+ .from(chatControlActions)
838
+ .where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
839
+ .limit(1)
840
+ .then((rows) => rows[0] ?? null)
841
+ : null;
842
+ const confirmedPreDeliveryFailure = Boolean(failedAction
843
+ && ["not_sent", "rejected"].includes(failedAction.providerDisposition ?? "not_sent")
844
+ && !item.sourceMessageId
845
+ && !item.deliveredMessageId
846
+ && !item.continuationMessageId);
847
+ if (!confirmedPreDeliveryFailure) {
848
+ throw conflict("Queued feedback delivery is not safely retryable");
849
+ }
850
+ }
845
851
  if (item.status === "steer_pending") {
846
852
  const boundAction = item.controlActionId
847
853
  ? await tx
@@ -1261,39 +1267,85 @@ export function chatService(db) {
1261
1267
  return action;
1262
1268
  });
1263
1269
  }
1264
- async function createQueuedMessage(input) {
1265
- const payload = {
1266
- ...input.payload,
1267
- body: input.payload.body.trim(),
1268
- attachmentIds: input.payload.attachmentIds ?? [],
1269
- skillRefs: input.payload.skillRefs ?? [],
1270
- projectId: input.payload.projectId ?? null,
1271
- accessMode: input.payload.accessMode ?? null,
1272
- model: input.payload.model ?? null,
1273
- effort: input.payload.effort ?? null,
1274
- metadata: input.payload.metadata ?? null,
1275
- };
1270
+ async function createQueuedMessageWithStagedAttachments(input) {
1271
+ if ((input.payload.attachmentIds?.length ?? 0) > 0) {
1272
+ throw unprocessable("Queued messages cannot reference client-provided attachment ids");
1273
+ }
1274
+ const payload = normalizeQueuedMessagePayload(input.payload);
1275
+ if (!payload.body.trim() && (payload.inlineAnnotations?.length ?? 0) === 0) {
1276
+ throw unprocessable("Queued message body or at least one inline annotation is required");
1277
+ }
1278
+ const fingerprint = queuedMessageMutationFingerprint({
1279
+ payload,
1280
+ stagedAttachments: input.stagedAttachments,
1281
+ attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
1282
+ });
1276
1283
  for (let attempt = 0; attempt < 3; attempt += 1) {
1277
1284
  try {
1278
1285
  return await db.transaction(async (tx) => {
1279
1286
  const existing = await tx
1280
1287
  .select()
1281
1288
  .from(chatQueuedMessages)
1282
- .where(and(eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.clientMutationId, input.clientMutationId)))
1289
+ .where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.clientMutationId, input.clientMutationId)))
1283
1290
  .limit(1)
1284
1291
  .then((rows) => rows[0] ?? null);
1285
1292
  if (existing) {
1286
- if (JSON.stringify(existing.payload) !== JSON.stringify(payload)) {
1293
+ const existingPrivateState = queuedAnnotationAssetState(existing.payload);
1294
+ const existingFingerprint = existingPrivateState?.fingerprint
1295
+ ?? queuedMessageMutationFingerprint({
1296
+ payload: normalizeQueuedMessagePayload(existing.payload),
1297
+ stagedAttachments: [],
1298
+ attachmentFileIndexesByAnnotationId: new Map(),
1299
+ });
1300
+ if (existingFingerprint !== fingerprint) {
1287
1301
  throw conflict("Queued message idempotency key reused with a different payload");
1288
1302
  }
1289
- return hydrateQueuedMessage(existing);
1303
+ return {
1304
+ item: hydrateQueuedMessage(existing),
1305
+ accepted: false,
1306
+ cleanupAttachments: [...input.stagedAttachments],
1307
+ };
1308
+ }
1309
+ await validateCanonicalChatInlineAnnotations(tx, {
1310
+ orgId: input.orgId,
1311
+ conversationId: input.conversationId,
1312
+ annotations: payload.inlineAnnotations ?? [],
1313
+ uploadedFileCount: input.stagedAttachments.length,
1314
+ attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
1315
+ });
1316
+ const assetIds = [];
1317
+ for (const attachment of input.stagedAttachments) {
1318
+ const [asset] = await tx
1319
+ .insert(assets)
1320
+ .values({
1321
+ orgId: input.orgId,
1322
+ provider: attachment.provider,
1323
+ objectKey: attachment.objectKey,
1324
+ contentType: attachment.contentType,
1325
+ byteSize: attachment.byteSize,
1326
+ sha256: attachment.sha256,
1327
+ originalFilename: attachment.originalFilename,
1328
+ createdByAgentId: attachment.createdByAgentId,
1329
+ createdByUserId: attachment.createdByUserId,
1330
+ })
1331
+ .returning({ id: assets.id });
1332
+ if (!asset)
1333
+ throw new Error("Failed to stage queued annotation asset");
1334
+ assetIds.push(asset.id);
1290
1335
  }
1336
+ const persistedPayload = withQueuedAnnotationAssetState({
1337
+ payload,
1338
+ fingerprint,
1339
+ assetIds,
1340
+ stagedAttachments: input.stagedAttachments,
1341
+ attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
1342
+ });
1291
1343
  const [positionRow] = await tx
1292
1344
  .select({
1293
1345
  nextPosition: sql `coalesce(max(${chatQueuedMessages.position}), 0) + 1`,
1294
1346
  })
1295
1347
  .from(chatQueuedMessages)
1296
- .where(eq(chatQueuedMessages.conversationId, input.conversationId));
1348
+ .where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId)));
1297
1349
  const [row] = await tx
1298
1350
  .insert(chatQueuedMessages)
1299
1351
  .values({
@@ -1301,14 +1353,18 @@ export function chatService(db) {
1301
1353
  conversationId: input.conversationId,
1302
1354
  clientMutationId: input.clientMutationId,
1303
1355
  position: Number(positionRow?.nextPosition ?? 1),
1304
- payload,
1356
+ payload: persistedPayload,
1305
1357
  requestActor: input.requestActor ?? null,
1306
1358
  expectedGenerationId: input.expectedGenerationId ?? null,
1307
1359
  })
1308
1360
  .returning();
1309
1361
  if (!row)
1310
1362
  throw new Error("Failed to create queued chat message");
1311
- return hydrateQueuedMessage(row);
1363
+ return {
1364
+ item: hydrateQueuedMessage(row),
1365
+ accepted: true,
1366
+ cleanupAttachments: [],
1367
+ };
1312
1368
  });
1313
1369
  }
1314
1370
  catch (error) {
@@ -1319,54 +1375,235 @@ export function chatService(db) {
1319
1375
  }
1320
1376
  throw new Error("Failed to create queued chat message");
1321
1377
  }
1322
- async function updateQueuedMessage(input) {
1323
- const now = new Date();
1324
- const [row] = await db
1325
- .update(chatQueuedMessages)
1326
- .set({
1327
- payload: {
1378
+ async function createQueuedMessage(input) {
1379
+ const result = await createQueuedMessageWithStagedAttachments({
1380
+ ...input,
1381
+ stagedAttachments: [],
1382
+ attachmentFileIndexesByAnnotationId: new Map(),
1383
+ });
1384
+ return result.item;
1385
+ }
1386
+ async function queuedAssetCleanupRows(tx, orgId, payload) {
1387
+ const state = queuedAnnotationAssetState(payload);
1388
+ if (!state || state.attachments.length === 0)
1389
+ return [];
1390
+ const rows = await tx
1391
+ .select()
1392
+ .from(assets)
1393
+ .where(and(eq(assets.orgId, orgId), inArray(assets.id, state.attachments.map((attachment) => attachment.assetId))))
1394
+ .for("share");
1395
+ if (rows.length !== state.attachments.length) {
1396
+ throw unprocessable("Queued annotation assets are incomplete");
1397
+ }
1398
+ return rows.map((asset) => ({
1399
+ assetId: asset.id,
1400
+ provider: asset.provider,
1401
+ objectKey: asset.objectKey,
1402
+ contentType: asset.contentType,
1403
+ byteSize: asset.byteSize,
1404
+ sha256: asset.sha256,
1405
+ originalFilename: asset.originalFilename,
1406
+ createdByAgentId: asset.createdByAgentId,
1407
+ createdByUserId: asset.createdByUserId,
1408
+ }));
1409
+ }
1410
+ async function updateQueuedMessageWithStagedAttachments(input) {
1411
+ if ((input.payload.attachmentIds?.length ?? 0) > 0) {
1412
+ throw unprocessable("Queued messages cannot reference client-provided attachment ids");
1413
+ }
1414
+ if (input.stagedAttachments.length > 0 && input.payload.inlineAnnotations === undefined) {
1415
+ throw unprocessable("Queued annotation files require an explicit annotation replacement");
1416
+ }
1417
+ return db.transaction(async (tx) => {
1418
+ const current = await tx
1419
+ .select()
1420
+ .from(chatQueuedMessages)
1421
+ .where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.version, input.version), eq(chatQueuedMessages.status, "queued"), isNull(chatQueuedMessages.cancelledAt)))
1422
+ .limit(1)
1423
+ .for("update")
1424
+ .then((rows) => rows[0] ?? null);
1425
+ if (!current)
1426
+ throw conflict("Queued message was changed or is no longer editable");
1427
+ const currentPayload = normalizeQueuedMessagePayload(current.payload);
1428
+ const payload = normalizeQueuedMessagePayload({
1328
1429
  ...input.payload,
1329
- body: input.payload.body.trim(),
1330
- attachmentIds: input.payload.attachmentIds ?? [],
1331
- skillRefs: input.payload.skillRefs ?? [],
1332
- projectId: input.payload.projectId ?? null,
1333
- accessMode: input.payload.accessMode ?? null,
1334
- model: input.payload.model ?? null,
1335
- effort: input.payload.effort ?? null,
1336
- metadata: input.payload.metadata ?? null,
1337
- },
1338
- version: input.version + 1,
1339
- lastDeliveryReason: null,
1340
- updatedAt: now,
1341
- })
1342
- .where(and(eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.version, input.version), eq(chatQueuedMessages.status, "queued")))
1343
- .returning();
1344
- if (!row)
1345
- throw conflict("Queued message was changed or is no longer editable");
1346
- return hydrateQueuedMessage(row);
1430
+ inlineAnnotations: input.payload.inlineAnnotations === undefined
1431
+ ? currentPayload.inlineAnnotations
1432
+ : input.payload.inlineAnnotations,
1433
+ });
1434
+ if (!payload.body.trim() && (payload.inlineAnnotations?.length ?? 0) === 0) {
1435
+ throw unprocessable("Queued message body or at least one inline annotation is required");
1436
+ }
1437
+ const replacingAnnotations = input.payload.inlineAnnotations !== undefined;
1438
+ if (replacingAnnotations && current.sourceMessageId) {
1439
+ throw unprocessable("Sent annotation snapshots are immutable across queued message edits");
1440
+ }
1441
+ const currentPrivateState = queuedAnnotationAssetState(current.payload);
1442
+ const currentAttachmentFileIndexesByAnnotationId = new Map();
1443
+ for (const attachment of currentPrivateState?.attachments ?? []) {
1444
+ const indexes = currentAttachmentFileIndexesByAnnotationId.get(attachment.annotationId);
1445
+ if (indexes)
1446
+ indexes.push(attachment.fileIndex);
1447
+ else
1448
+ currentAttachmentFileIndexesByAnnotationId.set(attachment.annotationId, [attachment.fileIndex]);
1449
+ }
1450
+ await validateCanonicalChatInlineAnnotations(tx, {
1451
+ orgId: input.orgId,
1452
+ conversationId: input.conversationId,
1453
+ annotations: payload.inlineAnnotations ?? [],
1454
+ uploadedFileCount: replacingAnnotations
1455
+ ? input.stagedAttachments.length
1456
+ : currentPrivateState?.attachments.length ?? 0,
1457
+ attachmentFileIndexesByAnnotationId: replacingAnnotations
1458
+ ? input.attachmentFileIndexesByAnnotationId
1459
+ : currentAttachmentFileIndexesByAnnotationId,
1460
+ editUserMessageId: replacingAnnotations ? null : current.sourceMessageId,
1461
+ });
1462
+ const cleanupAttachments = replacingAnnotations
1463
+ ? await queuedAssetCleanupRows(tx, input.orgId, current.payload)
1464
+ : [];
1465
+ let persistedPayload = payload;
1466
+ if (replacingAnnotations) {
1467
+ const fingerprint = queuedMessageMutationFingerprint({
1468
+ payload,
1469
+ stagedAttachments: input.stagedAttachments,
1470
+ attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
1471
+ });
1472
+ const assetIds = [];
1473
+ for (const attachment of input.stagedAttachments) {
1474
+ const [asset] = await tx
1475
+ .insert(assets)
1476
+ .values({
1477
+ orgId: input.orgId,
1478
+ provider: attachment.provider,
1479
+ objectKey: attachment.objectKey,
1480
+ contentType: attachment.contentType,
1481
+ byteSize: attachment.byteSize,
1482
+ sha256: attachment.sha256,
1483
+ originalFilename: attachment.originalFilename,
1484
+ createdByAgentId: attachment.createdByAgentId,
1485
+ createdByUserId: attachment.createdByUserId,
1486
+ })
1487
+ .returning({ id: assets.id });
1488
+ if (!asset)
1489
+ throw new Error("Failed to stage replacement queued annotation asset");
1490
+ assetIds.push(asset.id);
1491
+ }
1492
+ persistedPayload = withQueuedAnnotationAssetState({
1493
+ payload,
1494
+ fingerprint,
1495
+ assetIds,
1496
+ stagedAttachments: input.stagedAttachments,
1497
+ attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
1498
+ });
1499
+ }
1500
+ else if (currentPrivateState) {
1501
+ persistedPayload = {
1502
+ ...payload,
1503
+ [QUEUED_ANNOTATION_ASSETS_KEY]: current.payload[QUEUED_ANNOTATION_ASSETS_KEY],
1504
+ };
1505
+ }
1506
+ const now = new Date();
1507
+ const [row] = await tx
1508
+ .update(chatQueuedMessages)
1509
+ .set({
1510
+ payload: persistedPayload,
1511
+ version: input.version + 1,
1512
+ lastDeliveryReason: current.sourceMessageId
1513
+ ? current.lastDeliveryReason
1514
+ : null,
1515
+ updatedAt: now,
1516
+ })
1517
+ .where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.version, input.version), eq(chatQueuedMessages.status, "queued"), isNull(chatQueuedMessages.cancelledAt)))
1518
+ .returning();
1519
+ if (!row)
1520
+ throw conflict("Queued message was changed or is no longer editable");
1521
+ if (current.sourceMessageId) {
1522
+ const [updatedMessage] = await tx
1523
+ .update(chatMessages)
1524
+ .set({ body: payload.body, updatedAt: now })
1525
+ .where(and(eq(chatMessages.id, current.sourceMessageId), eq(chatMessages.orgId, input.orgId), eq(chatMessages.conversationId, input.conversationId), eq(chatMessages.role, "user"), isNull(chatMessages.supersededAt)))
1526
+ .returning({ id: chatMessages.id });
1527
+ if (!updatedMessage) {
1528
+ throw conflict("Materialized queued message changed while its prose was edited");
1529
+ }
1530
+ }
1531
+ return { item: hydrateQueuedMessage(row), cleanupAttachments };
1532
+ });
1533
+ }
1534
+ async function updateQueuedMessage(input) {
1535
+ const result = await updateQueuedMessageWithStagedAttachments({
1536
+ ...input,
1537
+ stagedAttachments: [],
1538
+ attachmentFileIndexesByAnnotationId: new Map(),
1539
+ });
1540
+ return result.item;
1541
+ }
1542
+ async function cancelQueuedMessageWithStagedAttachments(input) {
1543
+ return db.transaction(async (tx) => {
1544
+ const conditions = [
1545
+ eq(chatQueuedMessages.orgId, input.orgId),
1546
+ eq(chatQueuedMessages.conversationId, input.conversationId),
1547
+ eq(chatQueuedMessages.id, input.itemId),
1548
+ eq(chatQueuedMessages.status, "queued"),
1549
+ isNull(chatQueuedMessages.cancelledAt),
1550
+ ];
1551
+ if (input.version)
1552
+ conditions.push(eq(chatQueuedMessages.version, input.version));
1553
+ const current = await tx
1554
+ .select()
1555
+ .from(chatQueuedMessages)
1556
+ .where(and(...conditions))
1557
+ .limit(1)
1558
+ .for("update")
1559
+ .then((rows) => rows[0] ?? null);
1560
+ if (!current)
1561
+ throw conflict("Queued message was changed or is no longer cancellable");
1562
+ const cleanupAttachments = await queuedAssetCleanupRows(tx, input.orgId, current.payload);
1563
+ const now = new Date();
1564
+ const [row] = await tx
1565
+ .update(chatQueuedMessages)
1566
+ .set({
1567
+ payload: normalizeQueuedMessagePayload(current.payload),
1568
+ status: "cancelled",
1569
+ version: sql `${chatQueuedMessages.version} + 1`,
1570
+ cancelledAt: now,
1571
+ updatedAt: now,
1572
+ })
1573
+ .where(and(...conditions, eq(chatQueuedMessages.version, current.version)))
1574
+ .returning();
1575
+ if (!row)
1576
+ throw conflict("Queued message was changed or is no longer cancellable");
1577
+ return { item: hydrateQueuedMessage(row), cleanupAttachments };
1578
+ });
1347
1579
  }
1348
1580
  async function cancelQueuedMessage(input) {
1349
- const now = new Date();
1350
- const conditions = [
1351
- eq(chatQueuedMessages.conversationId, input.conversationId),
1352
- eq(chatQueuedMessages.id, input.itemId),
1353
- eq(chatQueuedMessages.status, "queued"),
1354
- ];
1355
- if (input.version)
1356
- conditions.push(eq(chatQueuedMessages.version, input.version));
1357
- const [row] = await db
1358
- .update(chatQueuedMessages)
1359
- .set({
1360
- status: "cancelled",
1361
- version: sql `${chatQueuedMessages.version} + 1`,
1362
- cancelledAt: now,
1363
- updatedAt: now,
1364
- })
1365
- .where(and(...conditions))
1366
- .returning();
1367
- if (!row)
1368
- throw conflict("Queued message was changed or is no longer cancellable");
1369
- return hydrateQueuedMessage(row);
1581
+ const result = await cancelQueuedMessageWithStagedAttachments(input);
1582
+ return result.item;
1583
+ }
1584
+ async function finalizeQueuedAnnotationAssetCleanup(input) {
1585
+ if (input.assetIds.length === 0)
1586
+ return [];
1587
+ return db.transaction(async (tx) => {
1588
+ const linkedRows = await tx
1589
+ .select({ assetId: chatAttachments.assetId })
1590
+ .from(chatAttachments)
1591
+ .where(and(eq(chatAttachments.orgId, input.orgId), inArray(chatAttachments.assetId, [...input.assetIds])));
1592
+ const linkedIds = new Set(linkedRows.map((row) => row.assetId));
1593
+ const queueRows = await tx
1594
+ .select({ payload: chatQueuedMessages.payload })
1595
+ .from(chatQueuedMessages)
1596
+ .where(eq(chatQueuedMessages.orgId, input.orgId));
1597
+ const referencedIds = new Set(queueRows.flatMap((row) => queuedAnnotationAssetState(row.payload)?.attachments.map((attachment) => attachment.assetId) ?? []));
1598
+ const safeIds = [...new Set(input.assetIds)].filter((assetId) => !linkedIds.has(assetId) && !referencedIds.has(assetId));
1599
+ if (safeIds.length === 0)
1600
+ return [];
1601
+ const deleted = await tx
1602
+ .delete(assets)
1603
+ .where(and(eq(assets.orgId, input.orgId), inArray(assets.id, safeIds)))
1604
+ .returning({ id: assets.id });
1605
+ return deleted.map((row) => row.id);
1606
+ });
1370
1607
  }
1371
1608
  async function scheduleSteerContinuation(input) {
1372
1609
  return db.transaction(async (tx) => {
@@ -1481,31 +1718,46 @@ export function chatService(db) {
1481
1718
  return hydrateQueuedMessage(row);
1482
1719
  }
1483
1720
  async function claimNextServerQueuedMessage(input) {
1721
+ await reconcileDeliveredQueuedMessages({ limit: 25 });
1484
1722
  const now = input.now ?? new Date();
1485
1723
  const leaseExpiresAt = new Date(now.getTime() + input.leaseMs);
1486
1724
  return db.transaction(async (tx) => {
1487
1725
  const batchSize = 25;
1488
1726
  let scanOffset = 0;
1727
+ const claimableQueuePredicate = and(inArray(chatQueuedMessages.status, ["continuation_pending", "steer_pending", "queued"]), isNull(chatQueuedMessages.cancelledAt), sql `jsonb_typeof(${chatQueuedMessages.requestActor}) = 'object'`, or(isNull(chatQueuedMessages.deliveryLeaseExpiresAt), lt(chatQueuedMessages.deliveryLeaseExpiresAt, now)), sql `(${chatQueuedMessages.status} <> 'steer_pending' or coalesce((
1728
+ select steer_action.provider_disposition
1729
+ from ${chatControlActions} as steer_action
1730
+ where steer_action.id = ${chatQueuedMessages.controlActionId}
1731
+ limit 1
1732
+ ), 'not_sent') in ('not_sent', 'rejected'))`);
1733
+ const claimableQueuePriority = sql `case
1734
+ when ${chatQueuedMessages.status} = 'continuation_pending' then 0
1735
+ when ${chatQueuedMessages.status} = 'steer_pending' then 1
1736
+ else 2
1737
+ end`;
1489
1738
  while (true) {
1490
1739
  const candidates = await tx
1491
1740
  .select()
1492
1741
  .from(chatQueuedMessages)
1493
- .where(and(inArray(chatQueuedMessages.status, ["continuation_pending", "steer_pending", "queued"]), isNull(chatQueuedMessages.cancelledAt), sql `jsonb_typeof(${chatQueuedMessages.requestActor}) = 'object'`, or(isNull(chatQueuedMessages.deliveryLeaseExpiresAt), lt(chatQueuedMessages.deliveryLeaseExpiresAt, now)), sql `(${chatQueuedMessages.status} <> 'steer_pending' or coalesce((
1494
- select steer_action.provider_disposition
1495
- from ${chatControlActions} as steer_action
1496
- where steer_action.id = ${chatQueuedMessages.controlActionId}
1497
- limit 1
1498
- ), 'not_sent') in ('not_sent', 'rejected'))`))
1499
- .orderBy(sql `case
1500
- when ${chatQueuedMessages.status} = 'continuation_pending' then 0
1501
- when ${chatQueuedMessages.status} = 'steer_pending' then 1
1502
- else 2
1503
- end`, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
1742
+ .where(claimableQueuePredicate)
1743
+ .orderBy(claimableQueuePriority, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
1504
1744
  .limit(batchSize)
1505
1745
  .offset(scanOffset)
1506
1746
  .for("update", { skipLocked: true });
1747
+ let retainedCandidateCount = 0;
1507
1748
  for (const candidate of candidates) {
1508
1749
  if (!candidate.requestActor || typeof candidate.requestActor !== "object" || Array.isArray(candidate.requestActor)) {
1750
+ retainedCandidateCount += 1;
1751
+ continue;
1752
+ }
1753
+ const [conversationHead] = await tx
1754
+ .select({ id: chatQueuedMessages.id })
1755
+ .from(chatQueuedMessages)
1756
+ .where(and(claimableQueuePredicate, eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId)))
1757
+ .orderBy(claimableQueuePriority, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
1758
+ .limit(1);
1759
+ if (!conversationHead || conversationHead.id !== candidate.id) {
1760
+ retainedCandidateCount += 1;
1509
1761
  continue;
1510
1762
  }
1511
1763
  const latestGeneration = await tx
@@ -1520,11 +1772,18 @@ export function chatService(db) {
1520
1772
  || candidate.status === "continuation_pending";
1521
1773
  if (latestGeneration
1522
1774
  && ACTIVE_CHAT_GENERATION_STATUSES.some((status) => status === latestGeneration.status)) {
1775
+ retainedCandidateCount += 1;
1523
1776
  continue;
1524
1777
  }
1525
1778
  if (candidate.status === "queued"
1526
1779
  && latestGeneration
1527
- && latestGeneration.status !== "completed") {
1780
+ && latestGeneration.status !== "completed"
1781
+ && !((latestGeneration.status === "stopped"
1782
+ && latestGeneration.terminalReason === "operator_stop")
1783
+ || (candidate.continuationMessageId
1784
+ && latestGeneration.status === "aborted"
1785
+ && latestGeneration.terminalReason === candidate.lastDeliveryReason))) {
1786
+ retainedCandidateCount += 1;
1528
1787
  continue;
1529
1788
  }
1530
1789
  if (isSteer
@@ -1554,9 +1813,58 @@ export function chatService(db) {
1554
1813
  }
1555
1814
  continue;
1556
1815
  }
1816
+ let materialized;
1817
+ try {
1818
+ materialized = await materializeQueuedUserMessage(tx, {
1819
+ orgId: candidate.orgId,
1820
+ conversationId: candidate.conversationId,
1821
+ item: candidate,
1822
+ now,
1823
+ expectedStatuses: [candidate.status],
1824
+ structuredPayload: isSteer
1825
+ ? {
1826
+ source: "steer",
1827
+ targetGenerationId: candidate.activeGenerationId ?? candidate.expectedGenerationId,
1828
+ queueItemId: candidate.id,
1829
+ controlActionId: candidate.controlActionId,
1830
+ deliveryDisposition: candidate.deliveryDisposition,
1831
+ }
1832
+ : { source: "queue", queueItemId: candidate.id },
1833
+ });
1834
+ }
1835
+ catch (error) {
1836
+ if (!(error instanceof HttpError) || error.status !== 422)
1837
+ throw error;
1838
+ const validationFailureReason = "queued_message_validation_failed";
1839
+ await tx
1840
+ .update(chatQueuedMessages)
1841
+ .set({
1842
+ status: "failed_actionable",
1843
+ deliveryDisposition: "failed_actionable",
1844
+ reconciliationReason: validationFailureReason,
1845
+ lastDeliveryReason: validationFailureReason,
1846
+ updatedAt: now,
1847
+ version: sql `${chatQueuedMessages.version} + 1`,
1848
+ })
1849
+ .where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId), eq(chatQueuedMessages.status, candidate.status), eq(chatQueuedMessages.version, candidate.version), isNull(chatQueuedMessages.cancelledAt)));
1850
+ if (candidate.controlActionId) {
1851
+ await tx
1852
+ .update(chatControlActions)
1853
+ .set({
1854
+ localDisposition: "failed_actionable",
1855
+ lastError: validationFailureReason,
1856
+ resolvedAt: now,
1857
+ updatedAt: now,
1858
+ })
1859
+ .where(and(eq(chatControlActions.id, candidate.controlActionId), eq(chatControlActions.orgId, candidate.orgId)));
1860
+ }
1861
+ continue;
1862
+ }
1863
+ const materializedItem = materialized.item;
1864
+ const userMessageId = materialized.message.id;
1557
1865
  const generationId = randomUUID();
1558
1866
  const leaseToken = randomUUID();
1559
- const leaseEpoch = candidate.deliveryLeaseEpoch + 1;
1867
+ const leaseEpoch = materializedItem.deliveryLeaseEpoch + 1;
1560
1868
  await tx.insert(chatGenerations).values({
1561
1869
  id: generationId,
1562
1870
  orgId: candidate.orgId,
@@ -1565,44 +1873,12 @@ export function chatService(db) {
1565
1873
  attemptEpoch: 0,
1566
1874
  controlVersion: 0,
1567
1875
  controlState: "unregistered",
1568
- controlOwnerToken: leaseToken,
1569
- controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
1876
+ controlOwnerToken: null,
1877
+ controlLeaseExpiresAt: null,
1570
1878
  startedAt: now,
1571
1879
  createdAt: now,
1572
1880
  updatedAt: now,
1573
1881
  });
1574
- let userMessageId = candidate.continuationMessageId;
1575
- if (userMessageId) {
1576
- const existingMessage = await tx
1577
- .select({ id: chatMessages.id })
1578
- .from(chatMessages)
1579
- .where(and(eq(chatMessages.id, userMessageId), eq(chatMessages.orgId, candidate.orgId), eq(chatMessages.conversationId, candidate.conversationId), eq(chatMessages.role, "user")))
1580
- .limit(1)
1581
- .then((rows) => rows[0] ?? null);
1582
- if (!existingMessage)
1583
- userMessageId = null;
1584
- }
1585
- if (!userMessageId) {
1586
- userMessageId = randomUUID();
1587
- await tx.insert(chatMessages).values({
1588
- id: userMessageId,
1589
- orgId: candidate.orgId,
1590
- conversationId: candidate.conversationId,
1591
- role: "user",
1592
- kind: "message",
1593
- status: "completed",
1594
- body: normalizeQueuedPayload(candidate.payload).body,
1595
- structuredPayload: null,
1596
- chatTurnId: randomUUID(),
1597
- turnVariant: 0,
1598
- createdAt: now,
1599
- updatedAt: now,
1600
- });
1601
- await tx
1602
- .update(chatConversations)
1603
- .set({ lastMessageAt: now, updatedAt: now })
1604
- .where(and(eq(chatConversations.id, candidate.conversationId), eq(chatConversations.orgId, candidate.orgId)));
1605
- }
1606
1882
  const [claimed] = await tx
1607
1883
  .update(chatQueuedMessages)
1608
1884
  .set({
@@ -1624,7 +1900,7 @@ export function chatService(db) {
1624
1900
  version: sql `${chatQueuedMessages.version} + 1`,
1625
1901
  updatedAt: now,
1626
1902
  })
1627
- .where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.version, candidate.version), isNull(chatQueuedMessages.cancelledAt)))
1903
+ .where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId), eq(chatQueuedMessages.status, candidate.status), eq(chatQueuedMessages.version, materializedItem.version), isNull(chatQueuedMessages.cancelledAt)))
1628
1904
  .returning();
1629
1905
  if (!claimed)
1630
1906
  throw conflict("Queued continuation changed while being claimed");
@@ -1648,7 +1924,7 @@ export function chatService(db) {
1648
1924
  }
1649
1925
  if (candidates.length < batchSize)
1650
1926
  return null;
1651
- scanOffset += candidates.length;
1927
+ scanOffset += retainedCandidateCount;
1652
1928
  }
1653
1929
  });
1654
1930
  }
@@ -1664,6 +1940,63 @@ export function chatService(db) {
1664
1940
  .returning({ id: chatQueuedMessages.id });
1665
1941
  return Boolean(row);
1666
1942
  }
1943
+ async function acknowledgeServerQueuedMessageDelivery(input) {
1944
+ const now = new Date();
1945
+ return db.transaction(async (tx) => {
1946
+ const item = await tx
1947
+ .select()
1948
+ .from(chatQueuedMessages)
1949
+ .where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.continuationGenerationId, input.generationId), eq(chatQueuedMessages.deliveryLeaseToken, input.leaseToken), eq(chatQueuedMessages.deliveryLeaseEpoch, input.leaseEpoch), inArray(chatQueuedMessages.status, SERVER_QUEUE_RUNNING_STATUSES), isNull(chatQueuedMessages.cancelledAt)))
1950
+ .limit(1)
1951
+ .then((rows) => rows[0] ?? null);
1952
+ if (!item || !item.deliveredMessageId)
1953
+ return null;
1954
+ const deliveredMessage = await tx
1955
+ .select({ id: chatMessages.id })
1956
+ .from(chatMessages)
1957
+ .where(and(eq(chatMessages.id, item.deliveredMessageId), eq(chatMessages.orgId, item.orgId), eq(chatMessages.conversationId, item.conversationId), eq(chatMessages.role, "user")))
1958
+ .limit(1)
1959
+ .then((rows) => rows[0] ?? null);
1960
+ if (!deliveredMessage)
1961
+ return null;
1962
+ const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
1963
+ const nextStatus = isSteer ? "delivered" : "completed";
1964
+ const nextDisposition = isSteer ? "delivered" : null;
1965
+ const [updated] = await tx
1966
+ .update(chatQueuedMessages)
1967
+ .set({
1968
+ status: nextStatus,
1969
+ deliveryDisposition: nextDisposition,
1970
+ deliveryLeaseToken: null,
1971
+ deliveryLeaseOwner: null,
1972
+ deliveryLeaseExpiresAt: null,
1973
+ lastDeliveryReason: null,
1974
+ reconciliationReason: null,
1975
+ version: sql `${chatQueuedMessages.version} + 1`,
1976
+ updatedAt: now,
1977
+ })
1978
+ .where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
1979
+ .returning();
1980
+ if (!updated)
1981
+ return null;
1982
+ if (item.controlActionId) {
1983
+ const [updatedAction] = await tx
1984
+ .update(chatControlActions)
1985
+ .set({
1986
+ localDisposition: "delivered",
1987
+ lastError: null,
1988
+ resolvedAt: now,
1989
+ updatedAt: now,
1990
+ })
1991
+ .where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, item.orgId)))
1992
+ .returning({ id: chatControlActions.id });
1993
+ if (!updatedAction) {
1994
+ throw new InvalidQueueDeliveryActionLinkError();
1995
+ }
1996
+ }
1997
+ return hydrateQueuedMessage(updated);
1998
+ });
1999
+ }
1667
2000
  async function completeServerQueuedMessageDelivery(input) {
1668
2001
  const now = new Date();
1669
2002
  return db.transaction(async (tx) => {
@@ -1802,6 +2135,7 @@ export function chatService(db) {
1802
2135
  });
1803
2136
  }
1804
2137
  async function recoverExpiredServerQueueClaims(now = new Date()) {
2138
+ await reconcileDeliveredQueuedMessages({ limit: 25 });
1805
2139
  const expired = await db
1806
2140
  .select({
1807
2141
  id: chatQueuedMessages.id,
@@ -1920,7 +2254,7 @@ export function chatService(db) {
1920
2254
  if (!row || row.status !== "dequeue_claimed") {
1921
2255
  throw conflict("Queued message is not claimed for delivery");
1922
2256
  }
1923
- const payload = normalizeQueuedPayload(row.payload);
2257
+ const payload = normalizeQueuedMessagePayload(row.payload);
1924
2258
  if (payload.body.trim() !== input.body.trim()) {
1925
2259
  throw conflict("Queued message body no longer matches claimed payload");
1926
2260
  }
@@ -1931,9 +2265,11 @@ export function chatService(db) {
1931
2265
  const [row] = await db
1932
2266
  .update(chatQueuedMessages)
1933
2267
  .set({
1934
- status: "running",
2268
+ status: "completed",
1935
2269
  sourceMessageId: input.sourceMessageId,
1936
2270
  deliveredMessageId: input.sourceMessageId,
2271
+ lastDeliveryReason: null,
2272
+ reconciliationReason: null,
1937
2273
  version: sql `${chatQueuedMessages.version} + 1`,
1938
2274
  updatedAt: now,
1939
2275
  })
@@ -1943,6 +2279,123 @@ export function chatService(db) {
1943
2279
  throw conflict("Queued message is no longer deliverable");
1944
2280
  return hydrateQueuedMessage(row);
1945
2281
  }
2282
+ async function reconcileDeliveredQueuedMessages(input = {}) {
2283
+ const candidates = await db
2284
+ .select()
2285
+ .from(chatQueuedMessages)
2286
+ .where(and(inArray(chatQueuedMessages.status, [
2287
+ "dequeue_claimed",
2288
+ "running",
2289
+ "running_next",
2290
+ "failed",
2291
+ "failed_actionable",
2292
+ ]), isNull(chatQueuedMessages.cancelledAt), ...(input.orgId ? [eq(chatQueuedMessages.orgId, input.orgId)] : []), ...(input.conversationId ? [eq(chatQueuedMessages.conversationId, input.conversationId)] : [])))
2293
+ .orderBy(asc(chatQueuedMessages.updatedAt), asc(chatQueuedMessages.id))
2294
+ .limit(input.limit ?? 100);
2295
+ const generationIds = [...new Set(candidates
2296
+ .map((item) => item.continuationGenerationId)
2297
+ .filter((id) => Boolean(id)))];
2298
+ const generations = generationIds.length === 0
2299
+ ? []
2300
+ : await db
2301
+ .select({ id: chatGenerations.id, status: chatGenerations.status })
2302
+ .from(chatGenerations)
2303
+ .where(inArray(chatGenerations.id, generationIds));
2304
+ const terminalGenerationIds = new Set(generations
2305
+ .filter((generation) => !ACTIVE_CHAT_GENERATION_STATUSES.includes(generation.status))
2306
+ .map((generation) => generation.id));
2307
+ const repairableCandidates = candidates.filter((item) => {
2308
+ const hasNoServerClaimIdentity = !item.deliveryLeaseToken
2309
+ && !item.continuationGenerationId
2310
+ && item.deliveryLeaseEpoch === 0;
2311
+ const terminalServerClaimReleased = !item.deliveryLeaseToken
2312
+ && !item.deliveryLeaseOwner
2313
+ && !item.deliveryLeaseExpiresAt
2314
+ && Boolean(item.continuationGenerationId && terminalGenerationIds.has(item.continuationGenerationId));
2315
+ return hasNoServerClaimIdentity || Boolean(terminalServerClaimReleased);
2316
+ });
2317
+ const evidenceMessageIds = [...new Set(repairableCandidates.flatMap((item) => [
2318
+ item.deliveredMessageId,
2319
+ item.sourceMessageId,
2320
+ item.continuationMessageId,
2321
+ ]).filter((id) => Boolean(id)))];
2322
+ if (evidenceMessageIds.length === 0)
2323
+ return 0;
2324
+ const messages = await db
2325
+ .select({
2326
+ id: chatMessages.id,
2327
+ orgId: chatMessages.orgId,
2328
+ conversationId: chatMessages.conversationId,
2329
+ role: chatMessages.role,
2330
+ })
2331
+ .from(chatMessages)
2332
+ .where(inArray(chatMessages.id, evidenceMessageIds));
2333
+ const messageById = new Map(messages.map((message) => [message.id, message]));
2334
+ const now = new Date();
2335
+ let reconciled = 0;
2336
+ for (const item of repairableCandidates) {
2337
+ const evidence = [item.deliveredMessageId, item.sourceMessageId, item.continuationMessageId]
2338
+ .map((id) => id ? messageById.get(id) : null)
2339
+ .find((message) => Boolean(message
2340
+ && message.role === "user"
2341
+ && message.orgId === item.orgId
2342
+ && message.conversationId === item.conversationId));
2343
+ if (!evidence)
2344
+ continue;
2345
+ const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
2346
+ const nextStatus = isSteer ? "delivered" : "completed";
2347
+ const nextDisposition = isSteer ? "delivered" : null;
2348
+ let reconciledItem;
2349
+ try {
2350
+ reconciledItem = await db.transaction(async (tx) => {
2351
+ const [updated] = await tx
2352
+ .update(chatQueuedMessages)
2353
+ .set({
2354
+ status: nextStatus,
2355
+ deliveryDisposition: nextDisposition,
2356
+ sourceMessageId: evidence.id,
2357
+ deliveredMessageId: evidence.id,
2358
+ continuationMessageId: evidence.id,
2359
+ deliveryLeaseToken: null,
2360
+ deliveryLeaseOwner: null,
2361
+ deliveryLeaseExpiresAt: null,
2362
+ lastDeliveryReason: null,
2363
+ reconciliationReason: "durable_delivery_evidence",
2364
+ version: sql `${chatQueuedMessages.version} + 1`,
2365
+ updatedAt: now,
2366
+ })
2367
+ .where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version), isNull(chatQueuedMessages.cancelledAt)))
2368
+ .returning({ id: chatQueuedMessages.id });
2369
+ if (!updated)
2370
+ return false;
2371
+ if (item.controlActionId) {
2372
+ const [updatedAction] = await tx
2373
+ .update(chatControlActions)
2374
+ .set({
2375
+ localDisposition: "delivered",
2376
+ lastError: null,
2377
+ resolvedAt: now,
2378
+ updatedAt: now,
2379
+ })
2380
+ .where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, item.orgId)))
2381
+ .returning({ id: chatControlActions.id });
2382
+ if (!updatedAction) {
2383
+ throw new InvalidQueueDeliveryActionLinkError();
2384
+ }
2385
+ }
2386
+ return true;
2387
+ });
2388
+ }
2389
+ catch (error) {
2390
+ if (error instanceof InvalidQueueDeliveryActionLinkError)
2391
+ continue;
2392
+ throw error;
2393
+ }
2394
+ if (reconciledItem)
2395
+ reconciled += 1;
2396
+ }
2397
+ return reconciled;
2398
+ }
1946
2399
  async function markQueuedMessageDeliveryTerminal(input) {
1947
2400
  const now = new Date();
1948
2401
  const nextStatus = input.status === "completed" ? "completed" : "failed";
@@ -1974,10 +2427,13 @@ export function chatService(db) {
1974
2427
  .select({
1975
2428
  assistantMessageId: chatGenerationEvents.assistantMessageId,
1976
2429
  generationId: chatGenerationEvents.generationId,
2430
+ generationCreatedAt: chatGenerations.createdAt,
2431
+ eventRecordedAt: chatGenerationEvents.recordedAt,
1977
2432
  })
1978
2433
  .from(chatGenerationEvents)
1979
- .where(inArray(chatGenerationEvents.assistantMessageId, assistantMessageIds))
1980
- .orderBy(desc(chatGenerationEvents.generationSeq))
2434
+ .innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
2435
+ .where(and(inArray(chatGenerationEvents.assistantMessageId, assistantMessageIds), inArray(chatGenerationEvents.orgId, [...new Set(rows.map((row) => row.orgId))])))
2436
+ .orderBy(desc(chatGenerations.createdAt), desc(chatGenerationEvents.recordedAt), desc(chatGenerationEvents.generationSeq))
1981
2437
  : Promise.resolve([]),
1982
2438
  nativeSteerMessageIds.length > 0
1983
2439
  ? db
@@ -1992,13 +2448,32 @@ export function chatService(db) {
1992
2448
  .where(or(inArray(chatQueuedMessages.sourceMessageId, nativeSteerMessageIds), inArray(chatQueuedMessages.continuationMessageId, nativeSteerMessageIds)))
1993
2449
  : Promise.resolve([]),
1994
2450
  ]);
2451
+ const generationIds = [...new Set(generationMessageRows.map((row) => row.generationId))];
2452
+ const generationsById = generationIds.length === 0
2453
+ ? new Map()
2454
+ : new Map((await db
2455
+ .select({
2456
+ id: chatGenerations.id,
2457
+ orgId: chatGenerations.orgId,
2458
+ conversationId: chatGenerations.conversationId,
2459
+ terminalReason: chatGenerations.terminalReason,
2460
+ })
2461
+ .from(chatGenerations)
2462
+ .where(and(inArray(chatGenerations.id, generationIds), inArray(chatGenerations.orgId, [...new Set(rows.map((row) => row.orgId))]))))
2463
+ .map((generation) => [generation.id, generation]));
2464
+ const messageById = new Map(rows.map((row) => [row.id, row]));
1995
2465
  const generationIdByAssistantMessageId = new Map();
1996
2466
  for (const generationMessageRow of generationMessageRows) {
1997
- if (!generationMessageRow.assistantMessageId)
2467
+ const assistantMessageId = generationMessageRow.assistantMessageId;
2468
+ if (!assistantMessageId || generationIdByAssistantMessageId.has(assistantMessageId))
2469
+ continue;
2470
+ const message = messageById.get(assistantMessageId);
2471
+ const generation = generationsById.get(generationMessageRow.generationId);
2472
+ if (!message || !generation)
1998
2473
  continue;
1999
- if (generationIdByAssistantMessageId.has(generationMessageRow.assistantMessageId))
2474
+ if (generation.orgId !== message.orgId || generation.conversationId !== message.conversationId)
2000
2475
  continue;
2001
- generationIdByAssistantMessageId.set(generationMessageRow.assistantMessageId, generationMessageRow.generationId);
2476
+ generationIdByAssistantMessageId.set(assistantMessageId, generationMessageRow.generationId);
2002
2477
  }
2003
2478
  const steerDispositionByMessageId = new Map();
2004
2479
  for (const queueRow of linkedSteerQueueRows) {
@@ -2046,6 +2521,72 @@ export function chatService(db) {
2046
2521
  const generationId = generationIdByAssistantMessageId.get(messageId);
2047
2522
  return Boolean(generationId && nativeSteerTargetGenerationIds.has(generationId));
2048
2523
  });
2524
+ const assistantGenerationIds = [
2525
+ ...new Set(generationIdByAssistantMessageId.values()),
2526
+ ];
2527
+ const assistantGenerationPairs = [...generationIdByAssistantMessageId].map(([assistantMessageId, generationId]) => ({ assistantMessageId, generationId }));
2528
+ const selectedGenerationEventCondition = assistantGenerationPairs.length > 0
2529
+ ? or(...assistantGenerationPairs.map(({ assistantMessageId, generationId }) => and(eq(chatGenerationEvents.assistantMessageId, assistantMessageId), eq(chatGenerationEvents.generationId, generationId))))
2530
+ : undefined;
2531
+ const generationIdsNeedingFullTranscript = includeTranscript
2532
+ ? assistantGenerationIds
2533
+ : [...nativeSteerTargetGenerationIds].filter((generationId) => assistantGenerationIds.includes(generationId));
2534
+ const assistantMessageIdsNeedingFullTranscript = assistantGenerationPairs
2535
+ .filter(({ generationId }) => generationIdsNeedingFullTranscript.includes(generationId))
2536
+ .map(({ assistantMessageId }) => assistantMessageId);
2537
+ const transcriptByAssistantMessageId = new Map();
2538
+ if (generationIdsNeedingFullTranscript.length > 0) {
2539
+ const transcriptEventRows = await db
2540
+ .select({
2541
+ assistantMessageId: chatGenerationEvents.assistantMessageId,
2542
+ generationId: chatGenerationEvents.generationId,
2543
+ generationSeq: chatGenerationEvents.generationSeq,
2544
+ payload: chatGenerationEvents.payload,
2545
+ })
2546
+ .from(chatGenerationEvents)
2547
+ .innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
2548
+ .where(and(selectedGenerationEventCondition, inArray(chatGenerationEvents.assistantMessageId, assistantMessageIdsNeedingFullTranscript), eq(chatGenerationEvents.eventKind, "transcript"), or(isNull(chatGenerations.acceptedThroughSeq), lte(chatGenerationEvents.generationSeq, chatGenerations.acceptedThroughSeq))))
2549
+ .orderBy(asc(chatGenerationEvents.generationId), asc(chatGenerationEvents.generationSeq));
2550
+ for (const eventRow of transcriptEventRows) {
2551
+ if (!eventRow.assistantMessageId)
2552
+ continue;
2553
+ const entry = eventRow.payload.entry;
2554
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
2555
+ continue;
2556
+ const provenancedEntry = withChatTranscriptGenerationProvenance(entry, {
2557
+ generationId: eventRow.generationId,
2558
+ generationSeq: eventRow.generationSeq,
2559
+ });
2560
+ const transcript = transcriptByAssistantMessageId.get(eventRow.assistantMessageId);
2561
+ if (transcript)
2562
+ transcript.push(provenancedEntry);
2563
+ else
2564
+ transcriptByAssistantMessageId.set(eventRow.assistantMessageId, [provenancedEntry]);
2565
+ }
2566
+ }
2567
+ const transcriptSummaryByAssistantMessageId = new Map();
2568
+ if (!includeTranscript && assistantGenerationPairs.length > 0) {
2569
+ const summaryRows = await db
2570
+ .select({
2571
+ assistantMessageId: chatGenerationEvents.assistantMessageId,
2572
+ entryCount: sql `count(*)`,
2573
+ startedAt: sql `min(${chatGenerationEvents.payload}->'entry'->>'ts')`,
2574
+ endedAt: sql `max(${chatGenerationEvents.payload}->'entry'->>'ts')`,
2575
+ })
2576
+ .from(chatGenerationEvents)
2577
+ .innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
2578
+ .where(and(selectedGenerationEventCondition, eq(chatGenerationEvents.eventKind, "transcript"), or(isNull(chatGenerations.acceptedThroughSeq), lte(chatGenerationEvents.generationSeq, chatGenerations.acceptedThroughSeq))))
2579
+ .groupBy(chatGenerationEvents.assistantMessageId);
2580
+ for (const summaryRow of summaryRows) {
2581
+ if (!summaryRow.assistantMessageId)
2582
+ continue;
2583
+ transcriptSummaryByAssistantMessageId.set(summaryRow.assistantMessageId, {
2584
+ entryCount: Number(summaryRow.entryCount),
2585
+ startedAt: summaryRow.startedAt,
2586
+ endedAt: summaryRow.endedAt,
2587
+ });
2588
+ }
2589
+ }
2049
2590
  const nativeSteerTranscriptPayloadByMessageId = new Map();
2050
2591
  if (nativeSteerAssistantMessageIds.length > 0) {
2051
2592
  const transcriptPayloadRows = await db
@@ -2064,14 +2605,20 @@ export function chatService(db) {
2064
2605
  const includeRowTranscript = includeTranscript
2065
2606
  || Boolean(generationId && nativeSteerTargetGenerationIds.has(generationId));
2066
2607
  const transcriptPayload = nativeSteerTranscriptPayloadByMessageId.get(row.id) ?? row.structuredPayload;
2067
- const transcript = includeRowTranscript ? chatTranscriptFromPayload(transcriptPayload) : [];
2608
+ const ledgerTranscript = transcriptByAssistantMessageId.get(row.id);
2609
+ const transcript = includeRowTranscript
2610
+ ? ledgerTranscript?.length
2611
+ ? ledgerTranscript
2612
+ : chatTranscriptFromPayload(transcriptPayload)
2613
+ : [];
2068
2614
  const transcriptSummary = includeRowTranscript
2069
2615
  ? chatTranscriptSummaryFromEntries(transcript)
2070
- : row.transcriptSummary ?? null;
2616
+ : transcriptSummaryByAssistantMessageId.get(row.id) ?? row.transcriptSummary ?? null;
2071
2617
  const structuredPayload = effectiveStructuredPayload(row);
2072
2618
  return {
2073
2619
  ...row,
2074
2620
  generationId,
2621
+ generationTerminalReason: generationId ? (generationsById.get(generationId)?.terminalReason ?? null) : null,
2075
2622
  structuredPayload: stripChatMetadataFromPayload(structuredPayload),
2076
2623
  transcript: includeRowTranscript ? transcript : undefined,
2077
2624
  transcriptSummary,
@@ -2347,10 +2894,10 @@ export function chatService(db) {
2347
2894
  const forkMessages = input.sourceMessageId
2348
2895
  ? messagesToCopy.slice(0, messagesToCopy.findIndex((message) => message.id === input.sourceMessageId) + 1)
2349
2896
  : messagesToCopy;
2350
- await copyForkInlineVisualMessages({
2897
+ await copyForkChatMessages({
2351
2898
  tx,
2352
2899
  messages: forkMessages,
2353
- sourceConversationId: source.id,
2900
+ sourceConversation: source,
2354
2901
  targetConversationId: child.id,
2355
2902
  orgId: input.orgId,
2356
2903
  });
@@ -2640,9 +3187,44 @@ export function chatService(db) {
2640
3187
  .then((rows) => rows[0] ?? null);
2641
3188
  if (!row)
2642
3189
  return null;
3190
+ const generation = await db
3191
+ .select({
3192
+ generationId: chatGenerationEvents.generationId,
3193
+ acceptedThroughSeq: chatGenerations.acceptedThroughSeq,
3194
+ })
3195
+ .from(chatGenerationEvents)
3196
+ .innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
3197
+ .where(eq(chatGenerationEvents.assistantMessageId, messageId))
3198
+ .orderBy(desc(chatGenerations.createdAt), desc(chatGenerationEvents.recordedAt), desc(chatGenerationEvents.generationSeq))
3199
+ .limit(1)
3200
+ .then((rows) => rows[0] ?? null);
3201
+ const transcript = generation
3202
+ ? await db
3203
+ .select({
3204
+ generationId: chatGenerationEvents.generationId,
3205
+ generationSeq: chatGenerationEvents.generationSeq,
3206
+ payload: chatGenerationEvents.payload,
3207
+ })
3208
+ .from(chatGenerationEvents)
3209
+ .where(and(eq(chatGenerationEvents.generationId, generation.generationId), eq(chatGenerationEvents.assistantMessageId, messageId), eq(chatGenerationEvents.eventKind, "transcript"), generation.acceptedThroughSeq === null
3210
+ ? undefined
3211
+ : lte(chatGenerationEvents.generationSeq, generation.acceptedThroughSeq)))
3212
+ .orderBy(asc(chatGenerationEvents.generationSeq))
3213
+ .then((events) => events.flatMap((event) => {
3214
+ const entry = event.payload.entry;
3215
+ return entry && typeof entry === "object" && !Array.isArray(entry)
3216
+ ? [withChatTranscriptGenerationProvenance(entry, {
3217
+ generationId: event.generationId,
3218
+ generationSeq: event.generationSeq,
3219
+ })]
3220
+ : [];
3221
+ }))
3222
+ : [];
2643
3223
  return {
2644
3224
  messageId: row.id,
2645
- transcript: chatTranscriptFromPayload(row.structuredPayload),
3225
+ transcript: transcript.length > 0
3226
+ ? transcript
3227
+ : chatTranscriptFromPayload(row.structuredPayload),
2646
3228
  };
2647
3229
  }
2648
3230
  async function getMessage(conversationId, messageId) {
@@ -2654,104 +3236,7 @@ export function chatService(db) {
2654
3236
  if (!row)
2655
3237
  return null;
2656
3238
  const [hydrated] = await hydrateMessages([row]);
2657
- return hydrated ?? null;
2658
- }
2659
- async function assignLegacyTurnChainForUserMessage(target) {
2660
- const turnId = randomUUID();
2661
- const now = new Date();
2662
- await db
2663
- .update(chatMessages)
2664
- .set({ chatTurnId: turnId, turnVariant: 0, updatedAt: now })
2665
- .where(eq(chatMessages.id, target.id));
2666
- const following = await db
2667
- .select()
2668
- .from(chatMessages)
2669
- .where(and(eq(chatMessages.conversationId, target.conversationId), gt(chatMessages.createdAt, target.createdAt)))
2670
- .orderBy(chatMessages.createdAt);
2671
- for (const row of following) {
2672
- if (row.role === "user")
2673
- break;
2674
- await db
2675
- .update(chatMessages)
2676
- .set({ chatTurnId: turnId, turnVariant: 0, updatedAt: now })
2677
- .where(eq(chatMessages.id, row.id));
2678
- }
2679
- }
2680
- async function supersedeActiveMessagesFrom(conversationId, fromCreatedAt) {
2681
- const now = new Date();
2682
- await db
2683
- .update(chatMessages)
2684
- .set({ supersededAt: now, updatedAt: now })
2685
- .where(and(eq(chatMessages.conversationId, conversationId), isNull(chatMessages.supersededAt), gte(chatMessages.createdAt, fromCreatedAt)));
2686
- }
2687
- async function copyMessageAttachments(sourceMessageId, targetMessageId) {
2688
- const sourceAttachments = await db
2689
- .select()
2690
- .from(chatAttachments)
2691
- .where(eq(chatAttachments.messageId, sourceMessageId))
2692
- .orderBy(chatAttachments.createdAt);
2693
- if (sourceAttachments.length === 0)
2694
- return;
2695
- await db
2696
- .insert(chatAttachments)
2697
- .values(sourceAttachments.map((attachment) => ({
2698
- orgId: attachment.orgId,
2699
- conversationId: attachment.conversationId,
2700
- messageId: targetMessageId,
2701
- assetId: attachment.assetId,
2702
- })));
2703
- }
2704
- async function addUserChatMessage(conversationId, orgId, body, editUserMessageId, options = {}) {
2705
- if (editUserMessageId) {
2706
- let [target] = await db
2707
- .select()
2708
- .from(chatMessages)
2709
- .where(and(eq(chatMessages.id, editUserMessageId), eq(chatMessages.conversationId, conversationId)))
2710
- .limit(1);
2711
- if (!target) {
2712
- throw notFound("Chat message not found");
2713
- }
2714
- if (target.role !== "user" || target.kind !== "message") {
2715
- throw unprocessable("Only plain user messages can be edited");
2716
- }
2717
- if (target.supersededAt) {
2718
- throw unprocessable("Cannot edit a superseded message");
2719
- }
2720
- if (!target.chatTurnId) {
2721
- await assignLegacyTurnChainForUserMessage(target);
2722
- [target] = await db
2723
- .select()
2724
- .from(chatMessages)
2725
- .where(eq(chatMessages.id, editUserMessageId))
2726
- .limit(1);
2727
- if (!target?.chatTurnId) {
2728
- throw new Error("Failed to assign chat turn metadata");
2729
- }
2730
- }
2731
- await supersedeActiveMessagesFrom(conversationId, target.createdAt);
2732
- const turnId = target.chatTurnId;
2733
- const nextVariant = target.turnVariant + 1;
2734
- const editedMessage = await addMessage(conversationId, {
2735
- orgId,
2736
- role: "user",
2737
- kind: "message",
2738
- body,
2739
- chatTurnId: turnId,
2740
- turnVariant: nextVariant,
2741
- });
2742
- await copyMessageAttachments(target.id, editedMessage.id);
2743
- return (await getMessage(conversationId, editedMessage.id)) ?? editedMessage;
2744
- }
2745
- const turnId = randomUUID();
2746
- return addMessage(conversationId, {
2747
- orgId,
2748
- role: "user",
2749
- kind: "message",
2750
- body,
2751
- structuredPayload: options.structuredPayload ?? null,
2752
- chatTurnId: turnId,
2753
- turnVariant: 0,
2754
- });
3239
+ return hydrated ? hydrated : null;
2755
3240
  }
2756
3241
  async function addMessage(conversationId, input) {
2757
3242
  const durableBody = input.role === "user"
@@ -3444,8 +3929,12 @@ export function chatService(db) {
3444
3929
  getQueueSnapshot,
3445
3930
  listQueuedMessages,
3446
3931
  createQueuedMessage,
3932
+ createQueuedMessageWithStagedAttachments,
3447
3933
  updateQueuedMessage,
3934
+ updateQueuedMessageWithStagedAttachments,
3448
3935
  cancelQueuedMessage,
3936
+ cancelQueuedMessageWithStagedAttachments,
3937
+ finalizeQueuedAnnotationAssetCleanup,
3449
3938
  scheduleSteerContinuation,
3450
3939
  markQueuedMessageSteerFallback,
3451
3940
  beginSteerControlAction,
@@ -3454,6 +3943,7 @@ export function chatService(db) {
3454
3943
  resolveSteerControlAction,
3455
3944
  claimNextServerQueuedMessage,
3456
3945
  renewServerQueuedMessageClaim,
3946
+ acknowledgeServerQueuedMessageDelivery,
3457
3947
  completeServerQueuedMessageDelivery,
3458
3948
  releaseServerQueuedMessageClaim,
3459
3949
  recoverExpiredServerQueueClaims,
@@ -3466,6 +3956,7 @@ export function chatService(db) {
3466
3956
  getMessageTranscript,
3467
3957
  addMessage,
3468
3958
  updateMessage,
3959
+ updateMessageStructuredPayload,
3469
3960
  updateMessageInternalInlineVisuals,
3470
3961
  markInterruptedStreamingMessages,
3471
3962
  addUserChatMessage,