@rudderhq/server 0.4.6-canary.9 → 0.5.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 (350) hide show
  1. package/dist/agent-runtimes/http/execute.d.ts.map +1 -1
  2. package/dist/agent-runtimes/http/execute.js +8 -2
  3. package/dist/agent-runtimes/http/execute.js.map +1 -1
  4. package/dist/agent-runtimes/process/execute.d.ts.map +1 -1
  5. package/dist/agent-runtimes/process/execute.js +3 -1
  6. package/dist/agent-runtimes/process/execute.js.map +1 -1
  7. package/dist/agent-runtimes/utils.d.ts +6 -1
  8. package/dist/agent-runtimes/utils.d.ts.map +1 -1
  9. package/dist/agent-runtimes/utils.js +1 -1
  10. package/dist/agent-runtimes/utils.js.map +1 -1
  11. package/dist/app.d.ts +3 -3
  12. package/dist/app.d.ts.map +1 -1
  13. package/dist/app.js +17 -8
  14. package/dist/app.js.map +1 -1
  15. package/dist/bootstrap/create-http-app.d.ts +6 -1
  16. package/dist/bootstrap/create-http-app.d.ts.map +1 -1
  17. package/dist/bootstrap/create-http-app.js +122 -46
  18. package/dist/bootstrap/create-http-app.js.map +1 -1
  19. package/dist/bootstrap/plugin-host-runtime.d.ts.map +1 -1
  20. package/dist/bootstrap/plugin-host-runtime.js +89 -31
  21. package/dist/bootstrap/plugin-host-runtime.js.map +1 -1
  22. package/dist/bootstrap/register-api-routes.d.ts +3 -1
  23. package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
  24. package/dist/bootstrap/register-api-routes.js +3 -3
  25. package/dist/bootstrap/register-api-routes.js.map +1 -1
  26. package/dist/bootstrap/types.d.ts +1 -0
  27. package/dist/bootstrap/types.d.ts.map +1 -1
  28. package/dist/bundled-plugins/plugin-linear/dist/worker.js +348 -172
  29. package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
  30. package/dist/config.d.ts +2 -0
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +23 -0
  33. package/dist/config.js.map +1 -1
  34. package/dist/home-paths.d.ts.map +1 -1
  35. package/dist/home-paths.js +79 -7
  36. package/dist/home-paths.js.map +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +31 -54
  39. package/dist/index.js.map +1 -1
  40. package/dist/routes/access-onboarding.helpers.js +3 -3
  41. package/dist/routes/access-onboarding.helpers.js.map +1 -1
  42. package/dist/routes/access.helpers.d.ts.map +1 -1
  43. package/dist/routes/access.helpers.js +3 -2
  44. package/dist/routes/access.helpers.js.map +1 -1
  45. package/dist/routes/access.js +1 -1
  46. package/dist/routes/access.js.map +1 -1
  47. package/dist/routes/activity.d.ts.map +1 -1
  48. package/dist/routes/activity.js +5 -1
  49. package/dist/routes/activity.js.map +1 -1
  50. package/dist/routes/agents.management-routes.d.ts.map +1 -1
  51. package/dist/routes/agents.management-routes.js +13 -13
  52. package/dist/routes/agents.management-routes.js.map +1 -1
  53. package/dist/routes/assets.d.ts.map +1 -1
  54. package/dist/routes/assets.js +9 -2
  55. package/dist/routes/assets.js.map +1 -1
  56. package/dist/routes/chat-background-runtime.d.ts +19 -0
  57. package/dist/routes/chat-background-runtime.d.ts.map +1 -0
  58. package/dist/routes/chat-background-runtime.js +129 -0
  59. package/dist/routes/chat-background-runtime.js.map +1 -0
  60. package/dist/routes/chats.d.ts +2 -1
  61. package/dist/routes/chats.d.ts.map +1 -1
  62. package/dist/routes/chats.js +944 -24
  63. package/dist/routes/chats.js.map +1 -1
  64. package/dist/routes/chats.stream-routes.d.ts.map +1 -1
  65. package/dist/routes/chats.stream-routes.js +552 -76
  66. package/dist/routes/chats.stream-routes.js.map +1 -1
  67. package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
  68. package/dist/routes/issues.comments-attachments.js +14 -31
  69. package/dist/routes/issues.comments-attachments.js.map +1 -1
  70. package/dist/routes/onboarding.js +1 -1
  71. package/dist/routes/onboarding.js.map +1 -1
  72. package/dist/routes/orgs.d.ts +2 -1
  73. package/dist/routes/orgs.d.ts.map +1 -1
  74. package/dist/routes/orgs.js +31 -2
  75. package/dist/routes/orgs.js.map +1 -1
  76. package/dist/routes/run-intelligence.d.ts.map +1 -1
  77. package/dist/routes/run-intelligence.js +76 -16
  78. package/dist/routes/run-intelligence.js.map +1 -1
  79. package/dist/services/activity-log.d.ts +4 -1
  80. package/dist/services/activity-log.d.ts.map +1 -1
  81. package/dist/services/activity-log.js +12 -4
  82. package/dist/services/activity-log.js.map +1 -1
  83. package/dist/services/activity.d.ts +11 -394
  84. package/dist/services/activity.d.ts.map +1 -1
  85. package/dist/services/activity.js +24 -18
  86. package/dist/services/activity.js.map +1 -1
  87. package/dist/services/agents.d.ts +741 -118
  88. package/dist/services/agents.d.ts.map +1 -1
  89. package/dist/services/approvals.d.ts +3 -3
  90. package/dist/services/automation-chat-output.d.ts.map +1 -1
  91. package/dist/services/automation-chat-output.js +115 -94
  92. package/dist/services/automation-chat-output.js.map +1 -1
  93. package/dist/services/budgets.d.ts.map +1 -1
  94. package/dist/services/budgets.js +38 -36
  95. package/dist/services/budgets.js.map +1 -1
  96. package/dist/services/chat-agent-runs.d.ts +3 -1
  97. package/dist/services/chat-agent-runs.d.ts.map +1 -1
  98. package/dist/services/chat-agent-runs.js +95 -34
  99. package/dist/services/chat-agent-runs.js.map +1 -1
  100. package/dist/services/chat-assistant.d.ts.map +1 -1
  101. package/dist/services/chat-assistant.helpers.d.ts +27 -4
  102. package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
  103. package/dist/services/chat-assistant.helpers.js +66 -2
  104. package/dist/services/chat-assistant.helpers.js.map +1 -1
  105. package/dist/services/chat-assistant.inline-visuals.test.d.ts +2 -0
  106. package/dist/services/chat-assistant.inline-visuals.test.d.ts.map +1 -0
  107. package/dist/services/chat-assistant.inline-visuals.test.js +86 -0
  108. package/dist/services/chat-assistant.inline-visuals.test.js.map +1 -0
  109. package/dist/services/chat-assistant.js +115 -22
  110. package/dist/services/chat-assistant.js.map +1 -1
  111. package/dist/services/chat-generation-locks.d.ts +85 -0
  112. package/dist/services/chat-generation-locks.d.ts.map +1 -1
  113. package/dist/services/chat-generation-locks.js +371 -9
  114. package/dist/services/chat-generation-locks.js.map +1 -1
  115. package/dist/services/chat-generation-locks.test.d.ts +2 -0
  116. package/dist/services/chat-generation-locks.test.d.ts.map +1 -0
  117. package/dist/services/chat-generation-locks.test.js +324 -0
  118. package/dist/services/chat-generation-locks.test.js.map +1 -0
  119. package/dist/services/chat-generation-protocol.d.ts +674 -0
  120. package/dist/services/chat-generation-protocol.d.ts.map +1 -0
  121. package/dist/services/chat-generation-protocol.js +1571 -0
  122. package/dist/services/chat-generation-protocol.js.map +1 -0
  123. package/dist/services/chat-generation-protocol.test.d.ts +2 -0
  124. package/dist/services/chat-generation-protocol.test.d.ts.map +1 -0
  125. package/dist/services/chat-generation-protocol.test.js +1156 -0
  126. package/dist/services/chat-generation-protocol.test.js.map +1 -0
  127. package/dist/services/chat-title-generation.d.ts +0 -3
  128. package/dist/services/chat-title-generation.d.ts.map +1 -1
  129. package/dist/services/chat-title-generation.js +2 -32
  130. package/dist/services/chat-title-generation.js.map +1 -1
  131. package/dist/services/chat-work-manifest.d.ts.map +1 -1
  132. package/dist/services/chat-work-manifest.js +5 -1
  133. package/dist/services/chat-work-manifest.js.map +1 -1
  134. package/dist/services/chats.d.ts +1451 -272
  135. package/dist/services/chats.d.ts.map +1 -1
  136. package/dist/services/chats.js +1372 -35
  137. package/dist/services/chats.js.map +1 -1
  138. package/dist/services/costs.d.ts +34 -7
  139. package/dist/services/costs.d.ts.map +1 -1
  140. package/dist/services/costs.js +129 -61
  141. package/dist/services/costs.js.map +1 -1
  142. package/dist/services/finance.d.ts +3 -3
  143. package/dist/services/goals.d.ts +2 -2
  144. package/dist/services/heartbeat.d.ts +1 -1
  145. package/dist/services/heartbeat.d.ts.map +1 -1
  146. package/dist/services/heartbeat.js +1 -1
  147. package/dist/services/heartbeat.js.map +1 -1
  148. package/dist/services/index.d.ts +1 -0
  149. package/dist/services/index.d.ts.map +1 -1
  150. package/dist/services/index.js +1 -0
  151. package/dist/services/index.js.map +1 -1
  152. package/dist/services/integrations/agent-integrations.d.ts +1 -1
  153. package/dist/services/integrations/custom-integrations.d.ts +1 -1
  154. package/dist/services/integrations/feishu/runtime.d.ts.map +1 -1
  155. package/dist/services/integrations/feishu/runtime.js +16 -0
  156. package/dist/services/integrations/feishu/runtime.js.map +1 -1
  157. package/dist/services/issues.comments-attachments.d.ts +111 -0
  158. package/dist/services/issues.comments-attachments.d.ts.map +1 -1
  159. package/dist/services/issues.comments-attachments.js +138 -66
  160. package/dist/services/issues.comments-attachments.js.map +1 -1
  161. package/dist/services/issues.d.ts +56 -0
  162. package/dist/services/issues.d.ts.map +1 -1
  163. package/dist/services/issues.helpers.d.ts.map +1 -1
  164. package/dist/services/issues.helpers.js +2 -2
  165. package/dist/services/issues.helpers.js.map +1 -1
  166. package/dist/services/issues.js +5 -2
  167. package/dist/services/issues.js.map +1 -1
  168. package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
  169. package/dist/services/knowledge-portability/organization-skills.catalog.js +7 -1
  170. package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
  171. package/dist/services/messenger.d.ts.map +1 -1
  172. package/dist/services/messenger.js +11 -9
  173. package/dist/services/messenger.js.map +1 -1
  174. package/dist/services/organization-workspace-browser.d.ts.map +1 -1
  175. package/dist/services/organization-workspace-browser.js +19 -3
  176. package/dist/services/organization-workspace-browser.js.map +1 -1
  177. package/dist/services/orgs.d.ts.map +1 -1
  178. package/dist/services/orgs.js +36 -12
  179. package/dist/services/orgs.js.map +1 -1
  180. package/dist/services/plugin-dev-watcher.d.ts +19 -2
  181. package/dist/services/plugin-dev-watcher.d.ts.map +1 -1
  182. package/dist/services/plugin-dev-watcher.js +45 -11
  183. package/dist/services/plugin-dev-watcher.js.map +1 -1
  184. package/dist/services/plugin-registry.d.ts +28 -28
  185. package/dist/services/projects.d.ts +1 -1
  186. package/dist/services/run-events.d.ts +20 -0
  187. package/dist/services/run-events.d.ts.map +1 -0
  188. package/dist/services/run-events.js +34 -0
  189. package/dist/services/run-events.js.map +1 -0
  190. package/dist/services/run-intelligence.d.ts +16 -4
  191. package/dist/services/run-intelligence.d.ts.map +1 -1
  192. package/dist/services/run-intelligence.js +90 -15
  193. package/dist/services/run-intelligence.js.map +1 -1
  194. package/dist/services/runtime-kernel/heartbeat.core.d.ts +23 -0
  195. package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
  196. package/dist/services/runtime-kernel/heartbeat.core.js +1 -0
  197. package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
  198. package/dist/services/runtime-kernel/heartbeat.d.ts +95 -37
  199. package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
  200. package/dist/services/runtime-kernel/heartbeat.execute.d.ts +3 -1
  201. package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
  202. package/dist/services/runtime-kernel/heartbeat.execute.js +389 -219
  203. package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
  204. package/dist/services/runtime-kernel/heartbeat.js +594 -204
  205. package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
  206. package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
  207. package/dist/services/runtime-kernel/heartbeat.misc.js +34 -24
  208. package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
  209. package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
  210. package/dist/services/runtime-kernel/heartbeat.recovery.js +56 -6
  211. package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
  212. package/dist/services/runtime-kernel/heartbeat.release.d.ts +3 -1
  213. package/dist/services/runtime-kernel/heartbeat.release.d.ts.map +1 -1
  214. package/dist/services/runtime-kernel/heartbeat.release.js +276 -44
  215. package/dist/services/runtime-kernel/heartbeat.release.js.map +1 -1
  216. package/dist/services/runtime-kernel/heartbeat.sessions.d.ts +42 -1
  217. package/dist/services/runtime-kernel/heartbeat.sessions.d.ts.map +1 -1
  218. package/dist/services/runtime-kernel/heartbeat.sessions.js +110 -8
  219. package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
  220. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +575 -0
  221. package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -0
  222. package/dist/services/runtime-kernel/heartbeat.terminal.js +589 -0
  223. package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -0
  224. package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
  225. package/dist/services/runtime-kernel/heartbeat.wakeup.js +238 -92
  226. package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
  227. package/dist/services/runtime-kernel/model-fallback.d.ts.map +1 -1
  228. package/dist/services/runtime-kernel/model-fallback.js +11 -0
  229. package/dist/services/runtime-kernel/model-fallback.js.map +1 -1
  230. package/dist/services/side-chats.d.ts +212 -0
  231. package/dist/services/side-chats.d.ts.map +1 -0
  232. package/dist/services/side-chats.js +379 -0
  233. package/dist/services/side-chats.js.map +1 -0
  234. package/dist/services/workspace-runtime.services.d.ts +1 -1
  235. package/dist/services/workspace-web-preview.d.ts +36 -0
  236. package/dist/services/workspace-web-preview.d.ts.map +1 -0
  237. package/dist/services/workspace-web-preview.js +566 -0
  238. package/dist/services/workspace-web-preview.js.map +1 -0
  239. package/package.json +13 -13
  240. package/resources/bundled-skills/rudder-docs/SKILL.md +161 -0
  241. package/resources/bundled-skills/rudder-docs/evals/trigger-evals.json +82 -0
  242. package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/api-reference.md +17 -1
  243. package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/cli-reference.md +49 -94
  244. package/resources/bundled-skills/rudder-docs/references/control-plane-practices.md +231 -0
  245. package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/organization-skills.md +15 -1
  246. package/resources/bundled-skills/rudder-docs/references/source-map.md +138 -0
  247. package/resources/bundled-skills/visualize/SKILL.md +132 -0
  248. package/resources/bundled-skills/visualize/agents/openai.yaml +7 -0
  249. package/resources/bundled-skills/visualize/assets/example-chart.html +89 -0
  250. package/resources/bundled-skills/visualize/references/runtime-contract.md +114 -0
  251. package/skills/rudder-docs/SKILL.md +161 -0
  252. package/skills/rudder-docs/evals/trigger-evals.json +82 -0
  253. package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/api-reference.md +17 -1
  254. package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/cli-reference.md +49 -94
  255. package/skills/rudder-docs/references/control-plane-practices.md +231 -0
  256. package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/organization-skills.md +15 -1
  257. package/skills/rudder-docs/references/source-map.md +138 -0
  258. package/skills/visualize/SKILL.md +132 -0
  259. package/skills/visualize/agents/openai.yaml +7 -0
  260. package/skills/visualize/assets/example-chart.html +89 -0
  261. package/skills/visualize/references/runtime-contract.md +114 -0
  262. package/ui-dist/assets/{abnfDiagram-VRR7QNED-CKLAbEpH.js → abnfDiagram-VRR7QNED-Dpnx79fp.js} +1 -1
  263. package/ui-dist/assets/{arc-CS20WjB1.js → arc-Cpvh_rCf.js} +1 -1
  264. package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BY4lounK.js → architectureDiagram-ZJ3FMSHR-sDD_mBAI.js} +1 -1
  265. package/ui-dist/assets/{blockDiagram-677ZJIJ3-BHtHl-hm.js → blockDiagram-677ZJIJ3-4O4wWti1.js} +1 -1
  266. package/ui-dist/assets/{c4Diagram-LMCZKHZV-DgOJp7yn.js → c4Diagram-LMCZKHZV-BkGODE4P.js} +1 -1
  267. package/ui-dist/assets/channel-CM4Myg_6.js +1 -0
  268. package/ui-dist/assets/{chunk-2Q5K7J3B-Uin6HTPH.js → chunk-2Q5K7J3B-DsDQQpID.js} +1 -1
  269. package/ui-dist/assets/{chunk-32BRIVSS-BxAI7yMx.js → chunk-32BRIVSS-Bzx_VnIw.js} +1 -1
  270. package/ui-dist/assets/{chunk-5VM5RSS4-Ce6AR6Sg.js → chunk-5VM5RSS4-D4LoPCni.js} +1 -1
  271. package/ui-dist/assets/{chunk-EX3LRPZG-DgxPO59e.js → chunk-EX3LRPZG-BO0R-mS4.js} +3 -3
  272. package/ui-dist/assets/{chunk-JWPE2WC7-Bn3BYmxA.js → chunk-JWPE2WC7-B4Nr8KRL.js} +1 -1
  273. package/ui-dist/assets/{chunk-MOJQB5TN-BlTdGU58.js → chunk-MOJQB5TN--uaDDrYM.js} +1 -1
  274. package/ui-dist/assets/{chunk-RYQCIY6F-C0V6evNH.js → chunk-RYQCIY6F-CyBY2w_j.js} +1 -1
  275. package/ui-dist/assets/{chunk-V7JOEXUC-DjvZ40fp.js → chunk-V7JOEXUC-BMid_x8S.js} +3 -3
  276. package/ui-dist/assets/{chunk-VR4S4FIN-CejmQVxi.js → chunk-VR4S4FIN-DVqdPKuG.js} +1 -1
  277. package/ui-dist/assets/{chunk-XXDRQBXY-tj-vHLHN.js → chunk-XXDRQBXY-vy03lLSS.js} +1 -1
  278. package/ui-dist/assets/classDiagram-OUVF2IWQ-CtlIEzTt.js +1 -0
  279. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CtlIEzTt.js +1 -0
  280. package/ui-dist/assets/{cose-bilkent-JH36ORCC-Ci7DAkTj.js → cose-bilkent-JH36ORCC-jQDZmrG3.js} +1 -1
  281. package/ui-dist/assets/{cynefin-VYW2F7L2-CHU2plsQ.js → cynefin-VYW2F7L2-BxBj6aea.js} +1 -1
  282. package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-pqqNoczb.js → cynefinDiagram-TSTJHNR4-BuUubF5I.js} +1 -1
  283. package/ui-dist/assets/{dagre-VKFMJZFB-Bk8Y_kGf.js → dagre-VKFMJZFB-B8HipJ3a.js} +1 -1
  284. package/ui-dist/assets/{diagram-FQU43EPY-CiSxIS2M.js → diagram-FQU43EPY-r7gkbKSt.js} +2 -2
  285. package/ui-dist/assets/{diagram-G47NLZAW-qpVsOLnz.js → diagram-G47NLZAW-CxClRsxD.js} +1 -1
  286. package/ui-dist/assets/{diagram-NH7WQ7WH-Cda5JA3K.js → diagram-NH7WQ7WH-C8HYTLlq.js} +1 -1
  287. package/ui-dist/assets/{diagram-OA4YK3LP-Dh_DdE2d.js → diagram-OA4YK3LP-CjwqqLKz.js} +1 -1
  288. package/ui-dist/assets/{diagram-WEI45ONY-r5ouUhNZ.js → diagram-WEI45ONY-2IWfCqZb.js} +1 -1
  289. package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-C6lDAmmi.js → ebnfDiagram-CCIWWBDH-DVGZkgjy.js} +1 -1
  290. package/ui-dist/assets/{erDiagram-Q63AITRT-BznPfnRu.js → erDiagram-Q63AITRT-C_-u3Gmw.js} +1 -1
  291. package/ui-dist/assets/{flowDiagram-23GEKE2U-bCaogpDj.js → flowDiagram-23GEKE2U-BvGg0NCq.js} +4 -4
  292. package/ui-dist/assets/{ganttDiagram-NO4QXBWP-Bn-e5kP6.js → ganttDiagram-NO4QXBWP-U3CZUUUR.js} +1 -1
  293. package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D7E-RLhl.js → gitGraphDiagram-IHSO6WYX-CIWuEBIo.js} +1 -1
  294. package/ui-dist/assets/{graph-3haZvvEy.js → graph-D3P5q-7m.js} +1 -1
  295. package/ui-dist/assets/index-B1rLBtZ1.js +1875 -0
  296. package/ui-dist/assets/{index-BUneV0z1.js → index-BAMXpiI5.js} +1 -1
  297. package/ui-dist/assets/{index-CheuLaX8.js → index-BAgtKYR1.js} +1 -1
  298. package/ui-dist/assets/{index-BDji4WI2.js → index-CBOr9F6w.js} +1 -1
  299. package/ui-dist/assets/{index-DlcNrNku.js → index-CbHqnWUe.js} +1 -1
  300. package/ui-dist/assets/{index-C1NcizE3.js → index-CjztBLJi.js} +1 -1
  301. package/ui-dist/assets/{index-JqztCnnf.js → index-Cu1iMhjl.js} +1 -1
  302. package/ui-dist/assets/{index-BFnwTV9t.js → index-D7ycf1qU.js} +1 -1
  303. package/ui-dist/assets/{index-JLGi6sfB.js → index-DAYlwxsg.js} +1 -1
  304. package/ui-dist/assets/{index-Bje1DqHh.js → index-DCFf9dps.js} +4 -4
  305. package/ui-dist/assets/{index-zyCQr__a.js → index-DOPIV8vd.js} +1 -1
  306. package/ui-dist/assets/{index-JEy5ZJbE.js → index-DRxLJD40.js} +1 -1
  307. package/ui-dist/assets/{index-Doc88zxN.js → index-DYMtCDKT.js} +1 -1
  308. package/ui-dist/assets/index-DekHKOsL.css +1 -0
  309. package/ui-dist/assets/{index-BN3GMGqN.js → index-DidCCw78.js} +1 -1
  310. package/ui-dist/assets/{index-DKRH0hjR.js → index-Ds_qma4-.js} +1 -1
  311. package/ui-dist/assets/{index-B2bHoMTu.js → index-HkQRx1D6.js} +1 -1
  312. package/ui-dist/assets/{index-DLxzQvPB.js → index-ikpL--zj.js} +1 -1
  313. package/ui-dist/assets/{index-mE0kmjy0.js → index-mnl55Stn.js} +1 -1
  314. package/ui-dist/assets/{infoDiagram-FWYZ7A6U-NSQ6saR1.js → infoDiagram-FWYZ7A6U-Cx8Fa-I4.js} +1 -1
  315. package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-XhGk3DMM.js → ishikawaDiagram-FXEZZL3T-Cmuz1QUN.js} +1 -1
  316. package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CUyh1IKu.js → journeyDiagram-5HDEW3XC-CJgkiVXG.js} +1 -1
  317. package/ui-dist/assets/{kanban-definition-HUTT4EX6-P_0zllUc.js → kanban-definition-HUTT4EX6-RAWpefOB.js} +1 -1
  318. package/ui-dist/assets/layout-DyWQv0Rr.js +1 -0
  319. package/ui-dist/assets/{linear-DWu-hp7y.js → linear-D5pdgIf9.js} +1 -1
  320. package/ui-dist/assets/{map-B2IiHu-a.js → map-bVSzNLl2.js} +1 -1
  321. package/ui-dist/assets/mermaid.core-CeaXGD6B.js +312 -0
  322. package/ui-dist/assets/{mindmap-definition-LN4V7U3C-DpKo7DKc.js → mindmap-definition-LN4V7U3C-B8B8AX5C.js} +1 -1
  323. package/ui-dist/assets/{pegDiagram-2B236MQR-DzDDMRRX.js → pegDiagram-2B236MQR-C4Sv-ua6.js} +1 -1
  324. package/ui-dist/assets/{pieDiagram-ENE6RG2P-Dr7ZCi_a.js → pieDiagram-ENE6RG2P-CLARlOHx.js} +1 -1
  325. package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BB_gv9s9.js → quadrantDiagram-ABIIQ3AL-DPCXQmeT.js} +1 -1
  326. package/ui-dist/assets/{railroadDiagram-RFXS5EU6-C2ji_jMN.js → railroadDiagram-RFXS5EU6-BpqRuKUQ.js} +1 -1
  327. package/ui-dist/assets/{requirementDiagram-TGXJPOKE-BJg8vYwc.js → requirementDiagram-TGXJPOKE-Dd-h62HZ.js} +1 -1
  328. package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-msvrlvO_.js → sankeyDiagram-HTMAVEWB-DylT_pqq.js} +1 -1
  329. package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-B_B68qK5.js → sequenceDiagram-DBY2YBRQ-RiCF1REj.js} +1 -1
  330. package/ui-dist/assets/{sizeCapture-X5ZJPWSS-D0VdKiQi.js → sizeCapture-X5ZJPWSS-DXW4JQz2.js} +1 -1
  331. package/ui-dist/assets/{stateDiagram-2N3HPSRC-B4V-owEQ.js → stateDiagram-2N3HPSRC-6U3ESIGT.js} +1 -1
  332. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KQTrcZvp.js +1 -0
  333. package/ui-dist/assets/{swimlanes-5IMT3BWC-B1JTwGb7.js → swimlanes-5IMT3BWC-CeNHVNCi.js} +2 -2
  334. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CcxS1c6r.js +8 -0
  335. package/ui-dist/assets/{timeline-definition-FHXFAJF6-C-8YlQA9.js → timeline-definition-FHXFAJF6-Vlwoe_1b.js} +1 -1
  336. package/ui-dist/assets/{vennDiagram-L72KCM5P-DP13xuPX.js → vennDiagram-L72KCM5P-DBP7D3HE.js} +6 -6
  337. package/ui-dist/assets/{wardleyDiagram-EHGQE667-D6BNPJwb.js → wardleyDiagram-EHGQE667-DG-wzqpy.js} +1 -1
  338. package/ui-dist/assets/{xychartDiagram-FW5EYKEG-my7CwP2H.js → xychartDiagram-FW5EYKEG-BAq3mbha.js} +1 -1
  339. package/ui-dist/index.html +2 -2
  340. package/resources/bundled-skills/rudder/SKILL.md +0 -314
  341. package/skills/rudder/SKILL.md +0 -314
  342. package/ui-dist/assets/channel-D-nDuwao.js +0 -1
  343. package/ui-dist/assets/classDiagram-OUVF2IWQ-Ds2nJ2Kt.js +0 -1
  344. package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Ds2nJ2Kt.js +0 -1
  345. package/ui-dist/assets/index-BA7XbHmf.css +0 -1
  346. package/ui-dist/assets/index-hE0ZX3Ud.js +0 -1820
  347. package/ui-dist/assets/layout-C-tMX-j8.js +0 -1
  348. package/ui-dist/assets/mermaid.core-WA5rvr0F.js +0 -314
  349. package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DAB9Vdyb.js +0 -1
  350. package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DRt36f68.js +0 -8
@@ -1,4 +1,4 @@
1
- import { addChatMessageSchema, cancelChatQueuedMessageSchema, chatAutomationCreateFromStructuredPayload, createChatConversationSchema, createChatQueuedMessageSchema, forkChatConversationSchema, steerChatQueuedMessageSchema, updateChatConversationSchema, updateChatQueuedMessageSchema } from "@rudderhq/shared";
1
+ import { addChatMessageSchema, cancelChatQueuedMessageSchema, chatAutomationCreateFromStructuredPayload, createChatConversationSchema, createChatQueuedMessageSchema, createSideChatSchema, forkChatConversationSchema, parseCodexInlineVisualDirectives, steerChatQueuedMessageSchema, updateChatConversationSchema, updateChatQueuedMessageSchema, } from "@rudderhq/shared";
2
2
  import { Router } from "express";
3
3
  import multer from "multer";
4
4
  import { randomUUID } from "node:crypto";
@@ -9,16 +9,23 @@ import { validate } from "../middleware/validate.js";
9
9
  import { assertTimeZone } from "../services/automations.scheduler.js";
10
10
  import { chatAgentRunService } from "../services/chat-agent-runs.js";
11
11
  import { CHAT_ASSISTANT_USER_ERROR_MESSAGE, chatAssistantService, ChatAssistantStreamError, userVisiblePartialBodyFromError, } from "../services/chat-assistant.js";
12
- import { cancelAndReleaseActiveChatGeneration, claimChatGeneration, getActiveChatGeneration, hasActiveChatGeneration } from "../services/chat-generation-locks.js";
12
+ import { cancelAndReleaseActiveChatGeneration, claimChatGeneration, createChatRuntimeControlCoordinator, getActiveChatGeneration, hasActiveChatGeneration, interruptActiveChatGeneration, steerActiveChatGeneration } from "../services/chat-generation-locks.js";
13
+ import { hashChatGenerationBody } from "../services/chat-generation-protocol.js";
13
14
  import { buildChatTitlePromptFromMessages, chatTitleGenerationService, } from "../services/chat-title-generation.js";
14
15
  import { chatWorkManifestService } from "../services/chat-work-manifest.js";
15
16
  import { validateCron } from "../services/cron.js";
16
- import { accessService, agentService, automationService, chatService, goalService, heartbeatService, issueService, logActivity, operatorProfileService, organizationService, productIntelligenceService, projectService, } from "../services/index.js";
17
+ import { accessService, agentService, automationService, chatService, goalService, heartbeatService, issueService, logActivity, operatorProfileService, organizationService, productIntelligenceService, projectService, sideChatService, } from "../services/index.js";
17
18
  import { runtimeResultText, sanitizeGeneratedTitle, } from "../services/title-generation.js";
18
19
  import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
20
+ import { createChatBackgroundRuntime, } from "./chat-background-runtime.js";
19
21
  import { wakeIssueAssigneeAfterChatConversion } from "./chat-issue-assignment-wakeup.js";
20
22
  import { registerChatStreamRoutes } from "./chats.stream-routes.js";
21
- export function chatRoutes(db, storage) {
23
+ function chatVisibleOutputAdmissionClosed(error) {
24
+ return error instanceof HttpError
25
+ && error.status === 409
26
+ && error.message === "Chat-visible output admission is closed for this generation";
27
+ }
28
+ export function chatRoutes(db, storage, backgroundRuntime = createChatBackgroundRuntime()) {
22
29
  const router = Router();
23
30
  const svc = chatService(db);
24
31
  const organizationsSvc = organizationService(db);
@@ -35,6 +42,7 @@ export function chatRoutes(db, storage) {
35
42
  const heartbeat = heartbeatService(db);
36
43
  const productIntelligence = productIntelligenceService(db);
37
44
  const chatTitles = chatTitleGenerationService({ chats: svc, productIntelligence });
45
+ const sideChats = sideChatService(db);
38
46
  const CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE = "The assistant reply could not be completed. Rudder saved this attempt for diagnostics; retry when ready.";
39
47
  const upload = multer({
40
48
  storage: multer.memoryStorage(),
@@ -91,6 +99,7 @@ export function chatRoutes(db, storage) {
91
99
  if (!conversation)
92
100
  return null;
93
101
  assertCompanyAccess(req, conversation.orgId);
102
+ await sideChats.assertAccessible(conversation, req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null);
94
103
  return conversation;
95
104
  }
96
105
  function boardUserId(req) {
@@ -112,6 +121,12 @@ export function chatRoutes(db, storage) {
112
121
  throw conflict("Fork this Feishu chat to continue in Rudder");
113
122
  }
114
123
  }
124
+ async function assertSideChatMutationAllowed(req, conversation) {
125
+ await sideChats.assertMutable(conversation, req.actor.type === "board" ? boardUserId(req) : null);
126
+ }
127
+ async function touchSideChat(req, conversation) {
128
+ await sideChats.touch(conversation, req.actor.type === "board" ? boardUserId(req) : null);
129
+ }
115
130
  function isTitleOnlyChatUpdate(body) {
116
131
  const keys = Object.keys(body);
117
132
  return keys.length === 1 && keys[0] === "title";
@@ -219,6 +234,71 @@ export function chatRoutes(db, storage) {
219
234
  }
220
235
  }
221
236
  }
237
+ function queueRequestActor(req) {
238
+ if (req.actor.type === "agent") {
239
+ return {
240
+ type: "agent",
241
+ source: req.actor.source === "agent_jwt" ? "agent_jwt" : "agent_key",
242
+ orgId: req.actor.orgId,
243
+ agentId: req.actor.agentId,
244
+ runId: req.actor.runId,
245
+ adapterType: req.actor.adapterType,
246
+ };
247
+ }
248
+ if (req.actor.type !== "board")
249
+ throw unauthorized();
250
+ return {
251
+ type: "board",
252
+ source: req.actor.source === "local_implicit"
253
+ ? "local_implicit"
254
+ : req.actor.source === "board_key"
255
+ ? "board_key"
256
+ : "session",
257
+ userId: req.actor.userId,
258
+ orgIds: req.actor.orgIds,
259
+ isInstanceAdmin: req.actor.isInstanceAdmin,
260
+ runId: req.actor.runId,
261
+ };
262
+ }
263
+ function requestForQueuedActor(requestActor, orgId) {
264
+ if (!requestActor) {
265
+ throw new Error("Queued chat continuation is missing its authenticated request actor");
266
+ }
267
+ if (requestActor.type === "agent") {
268
+ if (!requestActor.agentId || requestActor.orgId !== orgId) {
269
+ throw new Error("Queued chat continuation has an invalid agent actor scope");
270
+ }
271
+ return {
272
+ actor: {
273
+ type: "agent",
274
+ source: requestActor.source === "agent_jwt" ? "agent_jwt" : "agent_key",
275
+ orgId,
276
+ agentId: requestActor.agentId,
277
+ runId: requestActor.runId,
278
+ adapterType: requestActor.adapterType,
279
+ },
280
+ };
281
+ }
282
+ const source = requestActor.source === "local_implicit"
283
+ ? "local_implicit"
284
+ : requestActor.source === "board_key"
285
+ ? "board_key"
286
+ : "session";
287
+ const orgIds = requestActor.orgIds ?? [];
288
+ if (source !== "local_implicit" && !requestActor.isInstanceAdmin && !orgIds.includes(orgId)) {
289
+ throw new Error("Queued chat continuation has an invalid board actor scope");
290
+ }
291
+ return {
292
+ actor: {
293
+ type: "board",
294
+ source,
295
+ userId: requestActor.userId,
296
+ orgIds,
297
+ isInstanceAdmin: requestActor.isInstanceAdmin,
298
+ runId: requestActor.runId,
299
+ },
300
+ };
301
+ }
222
302
  function turnContextFromUserMessage(userMessage) {
223
303
  if (!userMessage.chatTurnId) {
224
304
  throw new Error("User message missing chat turn id");
@@ -398,12 +478,19 @@ export function chatRoutes(db, storage) {
398
478
  const createdMessages = [];
399
479
  const { chatTurnId, turnVariant } = turnContext;
400
480
  const attachGeneratedFiles = async (message, generatedAttachments) => {
401
- if (!generatedAttachments || generatedAttachments.length === 0)
481
+ const finalDirectives = parseCodexInlineVisualDirectives(assistantReply.body).directives;
482
+ const inlineVisuals = (assistantReply.inlineVisuals ?? []).filter((visual) => finalDirectives.some((directive) => directive.index === visual.directiveIndex && directive.file === visual.file));
483
+ const generatedFiles = (generatedAttachments ?? []).filter((generated) => generated.source !== "codex_inline_visual"
484
+ || inlineVisuals.some((visual) => visual.status === "captured"
485
+ && visual.directiveIndex === generated.directiveIndex
486
+ && visual.file === generated.directiveFile));
487
+ if (generatedFiles.length === 0 && inlineVisuals.length === 0)
402
488
  return message;
403
489
  const attachments = [];
404
- for (const generated of generatedAttachments) {
490
+ const attachmentByVisualIndex = new Map();
491
+ for (const generated of generatedFiles) {
405
492
  if (generated.body.length > MAX_ATTACHMENT_BYTES) {
406
- throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body, generatedAttachments, { partialBodyUserVisible: true });
493
+ throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body, generatedFiles, { partialBodyUserVisible: true });
407
494
  }
408
495
  const stored = await storage.putFile({
409
496
  orgId: conversation.orgId,
@@ -425,10 +512,45 @@ export function chatRoutes(db, storage) {
425
512
  createdByAgentId: replyingAgentId,
426
513
  createdByUserId: null,
427
514
  });
428
- attachments.push(attachment);
515
+ const typedAttachment = attachment;
516
+ const publicAttachment = generated.source === "codex_inline_visual"
517
+ ? (({ provider: _provider, objectKey: _objectKey, ...safe }) => safe)(typedAttachment)
518
+ : typedAttachment;
519
+ attachments.push(publicAttachment);
520
+ if (generated.source === "codex_inline_visual") {
521
+ attachmentByVisualIndex.set(generated.directiveIndex, publicAttachment);
522
+ }
523
+ }
524
+ let structuredPayload = message.structuredPayload ?? null;
525
+ if (inlineVisuals.length > 0) {
526
+ const persistedMappings = inlineVisuals.map((visual) => {
527
+ if (visual.status === "captured") {
528
+ const attachment = attachmentByVisualIndex.get(visual.directiveIndex);
529
+ if (attachment) {
530
+ return {
531
+ directiveIndex: visual.directiveIndex,
532
+ file: visual.file,
533
+ status: "ready",
534
+ attachmentId: attachment.id,
535
+ };
536
+ }
537
+ }
538
+ return {
539
+ directiveIndex: visual.directiveIndex,
540
+ file: visual.file,
541
+ status: "unavailable",
542
+ reason: visual.status === "unavailable" ? visual.reason : "capture_failed",
543
+ };
544
+ });
545
+ structuredPayload = {
546
+ ...(structuredPayload ?? {}),
547
+ inlineVisuals: persistedMappings,
548
+ };
549
+ await svc.updateMessage(conversation.id, message.id, { structuredPayload });
429
550
  }
430
551
  return {
431
552
  ...message,
553
+ structuredPayload,
432
554
  attachments: [...(message.attachments ?? []), ...attachments],
433
555
  };
434
556
  };
@@ -825,6 +947,425 @@ export function chatRoutes(db, storage) {
825
947
  await chatRunsSvc.linkAssistantMessage(runId, conversation.id, message.id);
826
948
  }
827
949
  }
950
+ const queueWorkerId = `chat-queue:${process.pid}:${randomUUID()}`;
951
+ const queueLeaseMs = 30_000;
952
+ const queueWorkerConcurrency = 4;
953
+ const queueWorkerEnabled = process.env.NODE_ENV !== "test"
954
+ || process.env.RUDDER_CHAT_QUEUE_WORKER_TEST === "true";
955
+ const runningServerQueueTasks = new Set();
956
+ const terminalProjectorId = `chat-terminal:${process.pid}:${randomUUID()}`;
957
+ let terminalProjectionRetryTimer = null;
958
+ let terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
959
+ function scheduleTerminalProjectorAt(wakeAt) {
960
+ if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
961
+ return;
962
+ const wakeAtMs = Math.max(Date.now(), wakeAt.getTime());
963
+ if (terminalProjectionRetryTimer && terminalProjectionRetryAt <= wakeAtMs)
964
+ return;
965
+ if (terminalProjectionRetryTimer)
966
+ backgroundRuntime.clearTimer(terminalProjectionRetryTimer);
967
+ terminalProjectionRetryAt = wakeAtMs;
968
+ terminalProjectionRetryTimer = backgroundRuntime.setTimeout(() => {
969
+ terminalProjectionRetryTimer = null;
970
+ terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
971
+ wakeTerminalProjector();
972
+ }, Math.max(0, wakeAtMs - Date.now()));
973
+ }
974
+ async function drainTerminalProjections() {
975
+ while (backgroundRuntime.acceptingWork) {
976
+ const claim = await svc.generationProtocol.claimTerminalProjection({
977
+ workerId: terminalProjectorId,
978
+ leaseMs: 30_000,
979
+ });
980
+ if (!claim) {
981
+ const nextWakeAt = await svc.generationProtocol.getNextTerminalProjectionWakeAt();
982
+ if (nextWakeAt)
983
+ scheduleTerminalProjectorAt(nextWakeAt);
984
+ return;
985
+ }
986
+ // A claim requested before close may resolve after admission shuts. The
987
+ // terminal protocol has no release operation, so this tracked drain must
988
+ // finish that claim while close waits instead of leaking its lease.
989
+ try {
990
+ const finalStatus = typeof claim.payload.finalStatus === "string"
991
+ ? claim.payload.finalStatus
992
+ : null;
993
+ const controlActionKind = typeof claim.payload.controlActionKind === "string"
994
+ ? claim.payload.controlActionKind
995
+ : null;
996
+ const controlDisposition = finalStatus === "stopped"
997
+ && controlActionKind !== "steer"
998
+ ? "stopped"
999
+ : finalStatus === "interrupted_unverified"
1000
+ && controlActionKind !== "steer"
1001
+ ? "interrupted_unverified"
1002
+ : finalStatus === "control_lost"
1003
+ ? "control_lost"
1004
+ : undefined;
1005
+ const completed = await svc.generationProtocol.completeTerminalProjection({
1006
+ outboxId: claim.id,
1007
+ claimToken: claim.claimToken,
1008
+ claimEpoch: claim.claimEpoch,
1009
+ controlDisposition,
1010
+ });
1011
+ if (completed)
1012
+ wakeServerQueue();
1013
+ }
1014
+ catch (error) {
1015
+ const retryAt = new Date(Date.now() + 1_000);
1016
+ const retry = await svc.generationProtocol.retryTerminalProjection({
1017
+ outboxId: claim.id,
1018
+ claimToken: claim.claimToken,
1019
+ claimEpoch: claim.claimEpoch,
1020
+ error: error instanceof Error ? error.message : String(error),
1021
+ retryAt,
1022
+ maxAttempts: 5,
1023
+ }).catch(() => null);
1024
+ if (retry?.status === "retry_wait") {
1025
+ scheduleTerminalProjectorAt(retry.availableAt ?? retryAt);
1026
+ }
1027
+ }
1028
+ }
1029
+ }
1030
+ const terminalProjector = backgroundRuntime.createCoalescingTask(drainTerminalProjections, (error) => logger.warn({ err: error }, "chat terminal projection drain failed"));
1031
+ function wakeTerminalProjector() {
1032
+ if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
1033
+ return;
1034
+ if (terminalProjectionRetryTimer) {
1035
+ backgroundRuntime.clearTimer(terminalProjectionRetryTimer);
1036
+ terminalProjectionRetryTimer = null;
1037
+ terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
1038
+ }
1039
+ terminalProjector.wake();
1040
+ }
1041
+ async function runServerQueuedMessage(claim) {
1042
+ const conversation = await svc.getById(claim.item.conversationId);
1043
+ if (!conversation) {
1044
+ await svc.releaseServerQueuedMessageClaim({
1045
+ itemId: claim.item.id,
1046
+ generationId: claim.generationId,
1047
+ leaseToken: claim.leaseToken,
1048
+ leaseEpoch: claim.leaseEpoch,
1049
+ reason: "conversation_missing",
1050
+ });
1051
+ return;
1052
+ }
1053
+ let request;
1054
+ try {
1055
+ request = requestForQueuedActor(claim.item.requestActor, conversation.orgId);
1056
+ }
1057
+ catch (error) {
1058
+ await svc.completeServerQueuedMessageDelivery({
1059
+ itemId: claim.item.id,
1060
+ generationId: claim.generationId,
1061
+ leaseToken: claim.leaseToken,
1062
+ leaseEpoch: claim.leaseEpoch,
1063
+ status: "failed",
1064
+ reason: error instanceof Error ? error.message : "queued_request_actor_invalid",
1065
+ });
1066
+ return;
1067
+ }
1068
+ const actor = getActorInfo(request);
1069
+ const managedAbort = backgroundRuntime.manageAbortController();
1070
+ const abortController = managedAbort.controller;
1071
+ const releaseGeneration = claimChatGeneration(conversation.id, abortController, claim.generationId);
1072
+ if (!releaseGeneration) {
1073
+ managedAbort.release();
1074
+ await svc.releaseServerQueuedMessageClaim({
1075
+ itemId: claim.item.id,
1076
+ generationId: claim.generationId,
1077
+ leaseToken: claim.leaseToken,
1078
+ leaseEpoch: claim.leaseEpoch,
1079
+ reason: "local_generation_owner_busy",
1080
+ });
1081
+ return;
1082
+ }
1083
+ let leaseRenewing = false;
1084
+ let leaseLost = false;
1085
+ const renewLease = async () => {
1086
+ if (leaseRenewing || leaseLost)
1087
+ return;
1088
+ leaseRenewing = true;
1089
+ try {
1090
+ const renewed = await svc.renewServerQueuedMessageClaim({
1091
+ itemId: claim.item.id,
1092
+ generationId: claim.generationId,
1093
+ leaseToken: claim.leaseToken,
1094
+ leaseEpoch: claim.leaseEpoch,
1095
+ leaseMs: queueLeaseMs,
1096
+ });
1097
+ if (!renewed) {
1098
+ leaseLost = true;
1099
+ abortController.abort(new Error("Queued chat continuation lost its delivery lease"));
1100
+ }
1101
+ }
1102
+ finally {
1103
+ leaseRenewing = false;
1104
+ }
1105
+ };
1106
+ const leaseTimer = backgroundRuntime.setInterval(() => {
1107
+ return renewLease().catch((error) => {
1108
+ leaseLost = true;
1109
+ abortController.abort(error);
1110
+ });
1111
+ }, Math.floor(queueLeaseMs / 3));
1112
+ let terminalStatus = "failed";
1113
+ let terminalReason = null;
1114
+ let completionRecorded = false;
1115
+ let assistantConversation = conversation;
1116
+ let activeChatRunId = null;
1117
+ const transcript = [];
1118
+ let partialBody = "";
1119
+ try {
1120
+ const userMessage = await svc.getMessage(conversation.id, claim.userMessageId);
1121
+ if (!userMessage)
1122
+ throw new Error("Queued chat continuation user message is missing");
1123
+ const turnContext = turnContextFromUserMessage(userMessage);
1124
+ const assistantInput = await loadAssistantInput(conversation, actor);
1125
+ assistantConversation = assistantInput.conversation;
1126
+ const streamed = await assistantSvc.streamChatAssistantReply({
1127
+ ...assistantInput,
1128
+ userMessageId: userMessage.id,
1129
+ chatTurnId: turnContext.chatTurnId,
1130
+ turnVariant: turnContext.turnVariant,
1131
+ stream: false,
1132
+ abortSignal: abortController.signal,
1133
+ controlCoordinator: createChatRuntimeControlCoordinator(conversation.id, claim.generationId, {
1134
+ onAttemptStarted: async ({ generationId, attemptEpoch, ownerToken, attempt }) => {
1135
+ await svc.beginGenerationControlAttempt({
1136
+ orgId: conversation.orgId,
1137
+ conversationId: conversation.id,
1138
+ generationId,
1139
+ attemptEpoch,
1140
+ ownerToken,
1141
+ runtimeType: attempt.runtimeType,
1142
+ });
1143
+ },
1144
+ onHandleRegistered: async ({ generationId, attemptEpoch, ownerToken, handle }) => {
1145
+ await svc.markGenerationControlReady({
1146
+ generationId,
1147
+ attemptEpoch,
1148
+ ownerToken,
1149
+ runtimeType: handle.runtimeType,
1150
+ providerThreadId: handle.providerThreadId ?? null,
1151
+ providerTurnId: handle.providerTurnId ?? null,
1152
+ });
1153
+ },
1154
+ onAttemptLeaseRenewed: async ({ generationId, attemptEpoch, ownerToken }) => {
1155
+ await svc.renewGenerationControlLease({ generationId, attemptEpoch, ownerToken });
1156
+ },
1157
+ onAttemptCompleted: async ({ generationId, attemptEpoch, ownerToken }) => {
1158
+ await svc.markGenerationControlAttemptCompleted({
1159
+ generationId,
1160
+ attemptEpoch,
1161
+ ownerToken,
1162
+ });
1163
+ },
1164
+ }),
1165
+ onRunCreated: (runId) => {
1166
+ activeChatRunId = runId;
1167
+ },
1168
+ onAssistantDelta: async (delta) => {
1169
+ if (!abortController.signal.aborted)
1170
+ partialBody = `${partialBody}${delta}`;
1171
+ },
1172
+ onTranscriptEntry: async (entry) => {
1173
+ if (!abortController.signal.aborted)
1174
+ transcript.push(entry);
1175
+ },
1176
+ });
1177
+ partialBody = streamed.partialBody || partialBody;
1178
+ if (abortController.signal.aborted || streamed.outcome === "stopped") {
1179
+ terminalStatus = "stopped";
1180
+ terminalReason = "operator_stop";
1181
+ const stoppedMessage = await persistPartialAssistantMessage(assistantConversation, "", "stopped", turnContext, transcript, streamed.replyingAgentId, null, activeChatRunId);
1182
+ const stoppedMessages = stoppedMessage ? [stoppedMessage] : [];
1183
+ await linkChatRunMessages(assistantConversation, activeChatRunId, stoppedMessages);
1184
+ if (stoppedMessages.length > 0) {
1185
+ await logChatMessagesAdded(assistantConversation, stoppedMessages, {
1186
+ actorType: "system",
1187
+ actorId: "chat-assistant",
1188
+ agentId: streamed.replyingAgentId,
1189
+ });
1190
+ }
1191
+ }
1192
+ else {
1193
+ let completionMessageId = null;
1194
+ try {
1195
+ const attemptEpoch = Math.max(1, getActiveChatGeneration(conversation.id)?.attemptEpoch ?? 1);
1196
+ const completion = await svc.generationProtocol.appendVisibleEventAndProject({
1197
+ orgId: conversation.orgId,
1198
+ conversationId: conversation.id,
1199
+ generationId: claim.generationId,
1200
+ expectedAttemptEpoch: attemptEpoch,
1201
+ eventKind: "runtime_output",
1202
+ payload: {
1203
+ resultKind: streamed.reply.kind,
1204
+ body: streamed.reply.body,
1205
+ },
1206
+ bodyOffset: 0,
1207
+ bodyLength: streamed.reply.body.length,
1208
+ runId: activeChatRunId,
1209
+ bodyHash: hashChatGenerationBody(streamed.reply.body),
1210
+ body: streamed.reply.body,
1211
+ transcript,
1212
+ replyingAgentId: streamed.replyingAgentId,
1213
+ chatTurnId: turnContext.chatTurnId,
1214
+ turnVariant: turnContext.turnVariant,
1215
+ });
1216
+ completionMessageId = completion.message.id;
1217
+ }
1218
+ catch (error) {
1219
+ if (!chatVisibleOutputAdmissionClosed(error))
1220
+ throw error;
1221
+ terminalStatus = "stopped";
1222
+ terminalReason = "operator_stop";
1223
+ const stoppedMessage = await persistPartialAssistantMessage(assistantConversation, "", "stopped", turnContext, transcript, streamed.replyingAgentId, null, activeChatRunId);
1224
+ const stoppedMessages = stoppedMessage ? [stoppedMessage] : [];
1225
+ await linkChatRunMessages(assistantConversation, activeChatRunId, stoppedMessages);
1226
+ if (stoppedMessages.length > 0) {
1227
+ await logChatMessagesAdded(assistantConversation, stoppedMessages, {
1228
+ actorType: "system",
1229
+ actorId: "chat-assistant",
1230
+ agentId: streamed.replyingAgentId,
1231
+ });
1232
+ }
1233
+ return;
1234
+ }
1235
+ const createdMessages = await persistAssistantReply(request, assistantConversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId, completionMessageId, activeChatRunId);
1236
+ await linkChatRunMessages(assistantConversation, activeChatRunId, createdMessages);
1237
+ await logChatMessagesAdded(assistantConversation, createdMessages, {
1238
+ actorType: "system",
1239
+ actorId: "chat-assistant",
1240
+ agentId: streamed.replyingAgentId,
1241
+ });
1242
+ terminalStatus = "completed";
1243
+ }
1244
+ }
1245
+ catch (error) {
1246
+ terminalStatus = abortController.signal.aborted ? "aborted" : "failed";
1247
+ terminalReason = leaseLost
1248
+ ? "delivery_lease_lost"
1249
+ : error instanceof Error
1250
+ ? error.message
1251
+ : "queued_continuation_failed";
1252
+ logger.warn({ err: error, conversationId: conversation.id, queuedMessageId: claim.item.id }, "server-owned queued chat continuation failed");
1253
+ if (!leaseLost) {
1254
+ const failurePayload = recoverableFailurePayload(error, activeChatRunId);
1255
+ const failureBody = userVisiblePartialBodyFromError(error)
1256
+ || recoverableFailureBody(failurePayload)
1257
+ || CHAT_ASSISTANT_USER_ERROR_MESSAGE;
1258
+ const failedMessage = await persistPartialAssistantMessage(assistantConversation, failureBody, "failed", null, transcript, chatReplyingAgentId(assistantConversation), null, activeChatRunId, failurePayload).catch(() => null);
1259
+ const failedMessages = failedMessage ? [failedMessage] : [];
1260
+ await linkChatRunMessages(assistantConversation, activeChatRunId, failedMessages).catch(() => undefined);
1261
+ if (failedMessages.length > 0) {
1262
+ await logChatMessagesAdded(assistantConversation, failedMessages, {
1263
+ actorType: "system",
1264
+ actorId: "chat-assistant",
1265
+ agentId: chatReplyingAgentId(assistantConversation),
1266
+ }).catch(() => undefined);
1267
+ }
1268
+ }
1269
+ }
1270
+ finally {
1271
+ backgroundRuntime.clearTimer(leaseTimer);
1272
+ if (!leaseLost) {
1273
+ const latestGeneration = await svc.getLatestGeneration(conversation.id).catch(() => null);
1274
+ const terminalEvidence = latestGeneration?.id === claim.generationId
1275
+ ? await svc.generationProtocol.recordRuntimeTerminal({
1276
+ orgId: conversation.orgId,
1277
+ conversationId: conversation.id,
1278
+ generationId: claim.generationId,
1279
+ expectedAttemptEpoch: latestGeneration.attemptEpoch,
1280
+ expectedOwnerToken: latestGeneration.controlOwnerToken,
1281
+ finalStatus: terminalStatus,
1282
+ terminalReason: terminalReason ?? terminalStatus,
1283
+ }).catch((error) => {
1284
+ logger.warn({ err: error, generationId: claim.generationId }, "failed to record queued chat terminal evidence");
1285
+ return null;
1286
+ })
1287
+ : null;
1288
+ if (terminalEvidence) {
1289
+ wakeTerminalProjector();
1290
+ const completed = await svc.completeServerQueuedMessageDelivery({
1291
+ itemId: claim.item.id,
1292
+ generationId: claim.generationId,
1293
+ leaseToken: claim.leaseToken,
1294
+ leaseEpoch: claim.leaseEpoch,
1295
+ status: terminalStatus,
1296
+ reason: terminalReason,
1297
+ }).catch((error) => {
1298
+ logger.warn({ err: error, queuedMessageId: claim.item.id }, "failed to complete queued chat continuation");
1299
+ return null;
1300
+ });
1301
+ completionRecorded = Boolean(completed);
1302
+ }
1303
+ }
1304
+ if (!completionRecorded && !leaseLost) {
1305
+ await svc.releaseServerQueuedMessageClaim({
1306
+ itemId: claim.item.id,
1307
+ generationId: claim.generationId,
1308
+ leaseToken: claim.leaseToken,
1309
+ leaseEpoch: claim.leaseEpoch,
1310
+ reason: "queued_continuation_completion_unconfirmed",
1311
+ }).catch(() => null);
1312
+ }
1313
+ releaseGeneration();
1314
+ managedAbort.release();
1315
+ }
1316
+ }
1317
+ async function drainServerQueue() {
1318
+ await svc.recoverExpiredServerQueueClaims();
1319
+ while (backgroundRuntime.acceptingWork
1320
+ && runningServerQueueTasks.size < queueWorkerConcurrency) {
1321
+ const claim = await svc.claimNextServerQueuedMessage({
1322
+ workerId: queueWorkerId,
1323
+ leaseMs: queueLeaseMs,
1324
+ });
1325
+ if (!claim)
1326
+ return;
1327
+ if (!backgroundRuntime.acceptingWork) {
1328
+ await svc.releaseServerQueuedMessageClaim({
1329
+ itemId: claim.item.id,
1330
+ generationId: claim.generationId,
1331
+ leaseToken: claim.leaseToken,
1332
+ leaseEpoch: claim.leaseEpoch,
1333
+ reason: "chat_background_runtime_closing",
1334
+ });
1335
+ return;
1336
+ }
1337
+ let task;
1338
+ task = backgroundRuntime.track(runServerQueuedMessage(claim)
1339
+ .catch((error) => {
1340
+ logger.warn({ err: error, queuedMessageId: claim.item.id }, "server-owned chat continuation crashed");
1341
+ })
1342
+ .finally(() => {
1343
+ runningServerQueueTasks.delete(task);
1344
+ wakeServerQueue();
1345
+ }));
1346
+ runningServerQueueTasks.add(task);
1347
+ }
1348
+ }
1349
+ const serverQueueDrain = backgroundRuntime.createCoalescingTask(drainServerQueue, (error) => logger.warn({ err: error }, "server-owned chat queue drain failed"));
1350
+ function wakeServerQueue() {
1351
+ if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
1352
+ return;
1353
+ serverQueueDrain.wake();
1354
+ }
1355
+ if (queueWorkerEnabled) {
1356
+ const recoverChatControlOwners = () => {
1357
+ if (!backgroundRuntime.acceptingWork)
1358
+ return;
1359
+ return svc.generationProtocol.recoverStaleControlOwners({})
1360
+ .then(() => {
1361
+ wakeTerminalProjector();
1362
+ wakeServerQueue();
1363
+ })
1364
+ .catch((error) => logger.warn({ err: error }, "chat control recovery failed"));
1365
+ };
1366
+ backgroundRuntime.setTimeout(recoverChatControlOwners, 0);
1367
+ backgroundRuntime.setInterval(recoverChatControlOwners, 10_000);
1368
+ }
828
1369
  router.get("/orgs/:orgId/chats", async (req, res) => {
829
1370
  const orgId = req.params.orgId;
830
1371
  assertCompanyAccess(req, orgId);
@@ -833,12 +1374,21 @@ export function chatRoutes(db, storage) {
833
1374
  ? statusParam
834
1375
  : "active";
835
1376
  const q = typeof req.query.q === "string" ? req.query.q : undefined;
1377
+ const projectId = typeof req.query.projectId === "string"
1378
+ ? req.query.projectId.trim() || undefined
1379
+ : undefined;
836
1380
  const limit = typeof req.query.limit === "string"
837
1381
  ? positiveIntegerQuery(req.query.limit, 50, 500)
838
1382
  : undefined;
839
1383
  const userId = req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null;
840
- const conversations = await svc.list(orgId, { status, q, limit }, userId);
841
- res.json(await assistantSvc.enrichConversations(conversations));
1384
+ const conversations = await svc.list(orgId, {
1385
+ status,
1386
+ q,
1387
+ limit,
1388
+ ...(projectId ? { projectId } : {}),
1389
+ }, userId);
1390
+ const visibleConversations = conversations.filter((conversation) => conversation.messengerVisible !== false);
1391
+ res.json(await assistantSvc.enrichConversations(visibleConversations));
842
1392
  });
843
1393
  router.post("/orgs/:orgId/chats", validate(createChatConversationSchema), async (req, res) => {
844
1394
  const orgId = req.params.orgId;
@@ -915,6 +1465,7 @@ export function chatRoutes(db, storage) {
915
1465
  if (existing.mutability === "external_bound_chat" && !isTitleOnlyChatUpdate(req.body)) {
916
1466
  assertChatLocalMutationAllowed(existing);
917
1467
  }
1468
+ await assertSideChatMutationAllowed(req, existing);
918
1469
  if (req.body.primaryIssueId) {
919
1470
  const issue = await issuesSvc.getById(req.body.primaryIssueId);
920
1471
  if (!issue || issue.orgId !== existing.orgId) {
@@ -961,6 +1512,7 @@ export function chatRoutes(db, storage) {
961
1512
  res.status(404).json({ error: "Chat conversation not found" });
962
1513
  return;
963
1514
  }
1515
+ await assertSideChatMutationAllowed(req, existing);
964
1516
  const messages = await svc.listMessages(existing.id, { includeTranscript: false });
965
1517
  const prompt = buildChatTitlePromptFromMessages(messages);
966
1518
  if (!prompt) {
@@ -1026,6 +1578,96 @@ export function chatRoutes(db, storage) {
1026
1578
  });
1027
1579
  res.status(201).json(await assistantSvc.enrichConversation(forked));
1028
1580
  });
1581
+ router.post("/chats/:id/side-chats", validate(createSideChatSchema), async (req, res) => {
1582
+ assertBoard(req);
1583
+ const existing = await assertConversationAccess(req, req.params.id);
1584
+ if (!existing) {
1585
+ res.status(404).json({ error: "Chat conversation not found" });
1586
+ return;
1587
+ }
1588
+ const userId = boardUserId(req);
1589
+ const sideChat = await sideChats.create({
1590
+ sourceConversationId: existing.id,
1591
+ sourceMessageId: req.body.sourceMessageId,
1592
+ clientMutationId: req.body.clientMutationId,
1593
+ orgId: existing.orgId,
1594
+ userId,
1595
+ });
1596
+ const actor = getActorInfo(req);
1597
+ await logActivity(db, {
1598
+ orgId: existing.orgId,
1599
+ actorType: actor.actorType,
1600
+ actorId: actor.actorId,
1601
+ agentId: actor.agentId,
1602
+ runId: actor.runId,
1603
+ action: "chat.side_chat_created",
1604
+ entityType: "chat",
1605
+ entityId: sideChat.id,
1606
+ details: {
1607
+ sourceConversationId: existing.id,
1608
+ sourceMessageId: req.body.sourceMessageId,
1609
+ },
1610
+ });
1611
+ res.status(201).json(await assistantSvc.enrichConversation(sideChat));
1612
+ });
1613
+ router.post("/chats/:id/side-chat/complete", async (req, res) => {
1614
+ assertBoard(req);
1615
+ const existing = await assertConversationAccess(req, req.params.id);
1616
+ if (!existing || existing.conversationKind !== "side_chat") {
1617
+ res.status(404).json({ error: "Side Chat not found" });
1618
+ return;
1619
+ }
1620
+ const userId = boardUserId(req);
1621
+ const sideChat = await sideChats.complete({
1622
+ conversationId: req.params.id,
1623
+ userId,
1624
+ });
1625
+ const actor = getActorInfo(req);
1626
+ await logActivity(db, {
1627
+ orgId: sideChat.orgId,
1628
+ actorType: actor.actorType,
1629
+ actorId: actor.actorId,
1630
+ agentId: actor.agentId,
1631
+ runId: actor.runId,
1632
+ action: "chat.side_chat_completed",
1633
+ entityType: "chat",
1634
+ entityId: sideChat.id,
1635
+ details: {
1636
+ sourceConversationId: sideChat.forkedFromConversationId,
1637
+ sourceMessageId: sideChat.forkedFromMessageId,
1638
+ },
1639
+ });
1640
+ res.json(await assistantSvc.enrichConversation(sideChat));
1641
+ });
1642
+ router.post("/chats/:id/side-chat/keep", async (req, res) => {
1643
+ assertBoard(req);
1644
+ const existing = await assertConversationAccess(req, req.params.id);
1645
+ if (!existing || existing.conversationKind !== "side_chat") {
1646
+ res.status(404).json({ error: "Side Chat not found" });
1647
+ return;
1648
+ }
1649
+ const userId = boardUserId(req);
1650
+ const sideChat = await sideChats.keepInMessenger({
1651
+ conversationId: req.params.id,
1652
+ userId,
1653
+ });
1654
+ const actor = getActorInfo(req);
1655
+ await logActivity(db, {
1656
+ orgId: sideChat.orgId,
1657
+ actorType: actor.actorType,
1658
+ actorId: actor.actorId,
1659
+ agentId: actor.agentId,
1660
+ runId: actor.runId,
1661
+ action: "chat.side_chat_kept",
1662
+ entityType: "chat",
1663
+ entityId: sideChat.id,
1664
+ details: {
1665
+ sourceConversationId: sideChat.forkedFromConversationId,
1666
+ sourceMessageId: sideChat.forkedFromMessageId,
1667
+ },
1668
+ });
1669
+ res.json(await assistantSvc.enrichConversation(sideChat));
1670
+ });
1029
1671
  router.delete("/chats/:id", async (req, res) => {
1030
1672
  assertBoard(req);
1031
1673
  const existing = await assertConversationAccess(req, req.params.id);
@@ -1034,6 +1676,9 @@ export function chatRoutes(db, storage) {
1034
1676
  return;
1035
1677
  }
1036
1678
  assertChatLocalMutationAllowed(existing);
1679
+ if (existing.conversationKind === "side_chat") {
1680
+ throw conflict("Side Chat audit records cannot be deleted");
1681
+ }
1037
1682
  if (hasActiveChatGeneration(existing.id)) {
1038
1683
  if (req.query.cancelActive === "true") {
1039
1684
  cancelAndReleaseActiveChatGeneration(existing.id);
@@ -1050,7 +1695,9 @@ export function chatRoutes(db, storage) {
1050
1695
  }
1051
1696
  for (const attachment of attachments) {
1052
1697
  try {
1053
- await storage.deleteObject(attachment.orgId, attachment.objectKey);
1698
+ if (!await svc.assetHasAttachments(attachment.assetId)) {
1699
+ await storage.deleteObject(attachment.orgId, attachment.objectKey);
1700
+ }
1054
1701
  }
1055
1702
  catch (err) {
1056
1703
  logger.warn({ err, conversationId: existing.id, attachmentId: attachment.id }, "failed to delete chat attachment object during chat delete");
@@ -1088,12 +1735,15 @@ export function chatRoutes(db, storage) {
1088
1735
  return;
1089
1736
  }
1090
1737
  assertChatLocalMutationAllowed(conversation);
1738
+ await assertSideChatMutationAllowed(req, conversation);
1739
+ const requestActor = queueRequestActor(req);
1091
1740
  const item = await svc.createQueuedMessage({
1092
1741
  orgId: conversation.orgId,
1093
1742
  conversationId: conversation.id,
1094
1743
  clientMutationId: req.body.clientMutationId,
1095
1744
  expectedGenerationId: req.body.expectedGenerationId ?? getActiveChatGeneration(conversation.id)?.generationId ?? null,
1096
1745
  payload: req.body.payload,
1746
+ requestActor,
1097
1747
  });
1098
1748
  const actor = getActorInfo(req);
1099
1749
  await logActivity(db, {
@@ -1110,6 +1760,7 @@ export function chatRoutes(db, storage) {
1110
1760
  position: item.position,
1111
1761
  },
1112
1762
  });
1763
+ wakeServerQueue();
1113
1764
  res.status(201).json(item);
1114
1765
  });
1115
1766
  router.post("/chats/:id/queue/next/claim", async (req, res) => {
@@ -1119,6 +1770,7 @@ export function chatRoutes(db, storage) {
1119
1770
  return;
1120
1771
  }
1121
1772
  assertChatLocalMutationAllowed(conversation);
1773
+ await assertSideChatMutationAllowed(req, conversation);
1122
1774
  if (hasActiveChatGeneration(conversation.id)) {
1123
1775
  throw conflict("Cannot dequeue the next message while a reply is in progress");
1124
1776
  }
@@ -1153,6 +1805,7 @@ export function chatRoutes(db, storage) {
1153
1805
  return;
1154
1806
  }
1155
1807
  assertChatLocalMutationAllowed(conversation);
1808
+ await assertSideChatMutationAllowed(req, conversation);
1156
1809
  const item = await svc.releaseQueuedMessageClaim({
1157
1810
  conversationId: conversation.id,
1158
1811
  itemId: req.params.itemId,
@@ -1167,6 +1820,7 @@ export function chatRoutes(db, storage) {
1167
1820
  return;
1168
1821
  }
1169
1822
  assertChatLocalMutationAllowed(conversation);
1823
+ await assertSideChatMutationAllowed(req, conversation);
1170
1824
  const item = await svc.updateQueuedMessage({
1171
1825
  conversationId: conversation.id,
1172
1826
  itemId: req.params.itemId,
@@ -1182,6 +1836,7 @@ export function chatRoutes(db, storage) {
1182
1836
  return;
1183
1837
  }
1184
1838
  assertChatLocalMutationAllowed(conversation);
1839
+ await assertSideChatMutationAllowed(req, conversation);
1185
1840
  const parsed = cancelChatQueuedMessageSchema.safeParse(req.body ?? {});
1186
1841
  if (!parsed.success) {
1187
1842
  res.status(400).json({ error: "Invalid queued message cancel request", details: parsed.error.issues });
@@ -1201,27 +1856,283 @@ export function chatRoutes(db, storage) {
1201
1856
  return;
1202
1857
  }
1203
1858
  assertChatLocalMutationAllowed(conversation);
1859
+ await assertSideChatMutationAllowed(req, conversation);
1204
1860
  const active = getActiveChatGeneration(conversation.id);
1205
- if (!active?.generationId) {
1206
- throw conflict("Cannot steer queued follow-ups because no reply is in progress");
1207
- }
1208
- const expected = req.body.expectedActiveGenerationId ?? null;
1209
- const result = expected && expected !== active.generationId
1210
- ? "stale_generation"
1211
- : "unsupported";
1212
- const item = await svc.markQueuedMessageSteerFallback({
1861
+ const requestActor = queueRequestActor(req);
1862
+ const controlActionId = req.body.controlActionId ?? randomUUID();
1863
+ const expectedGenerationId = req.body.expectedActiveGenerationId
1864
+ ?? active?.generationId
1865
+ ?? null;
1866
+ if (!expectedGenerationId) {
1867
+ const scheduled = await svc.scheduleSteerContinuation({
1868
+ orgId: conversation.orgId,
1869
+ conversationId: conversation.id,
1870
+ itemId: req.params.itemId,
1871
+ controlActionId,
1872
+ requestActor,
1873
+ });
1874
+ wakeServerQueue();
1875
+ res.json({
1876
+ item: scheduled.item,
1877
+ result: "scheduled_next",
1878
+ disposition: "continuation_pending",
1879
+ controlActionId,
1880
+ activeGenerationId: scheduled.action.expectedGenerationId ?? null,
1881
+ queueVersion: scheduled.item.version,
1882
+ transcriptEventId: null,
1883
+ });
1884
+ return;
1885
+ }
1886
+ const queueSnapshot = await svc.getQueueSnapshot(conversation.id, expectedGenerationId);
1887
+ const expectedAttemptEpoch = req.body.expectedAttemptEpoch
1888
+ ?? queueSnapshot.activeAttemptEpoch
1889
+ ?? active?.attemptEpoch
1890
+ ?? 0;
1891
+ const expectedControlVersion = req.body.expectedControlVersion
1892
+ ?? queueSnapshot.activeControlVersion
1893
+ ?? 0;
1894
+ const started = await svc.beginSteerControlAction({
1895
+ orgId: conversation.orgId,
1213
1896
  conversationId: conversation.id,
1214
1897
  itemId: req.params.itemId,
1215
- reason: result,
1216
- activeGenerationId: active?.generationId ?? null,
1898
+ controlActionId,
1899
+ expectedGenerationId,
1900
+ expectedAttemptEpoch,
1901
+ expectedControlVersion,
1902
+ requestActor,
1217
1903
  });
1218
- res.json({
1904
+ const durableControlActionId = started.action.id;
1905
+ const durableGenerationId = started.action.expectedGenerationId ?? expectedGenerationId;
1906
+ const durableAttemptEpoch = started.action.expectedAttemptEpoch ?? expectedAttemptEpoch;
1907
+ const responseForDurableDisposition = (item, disposition) => ({
1219
1908
  item,
1220
- result: result === "unsupported" ? "queued_fallback" : result,
1221
- activeGenerationId: active?.generationId ?? null,
1909
+ result: disposition === "accepted_current"
1910
+ ? "delivered_current"
1911
+ : disposition === "acceptance_unknown"
1912
+ ? "acceptance_unknown"
1913
+ : disposition === "continuation_pending"
1914
+ ? "scheduled_next"
1915
+ : disposition === "failed_actionable"
1916
+ ? "failed_actionable"
1917
+ : "pending",
1918
+ disposition,
1919
+ controlActionId: durableControlActionId,
1920
+ activeGenerationId: durableGenerationId,
1222
1921
  queueVersion: item.version,
1223
1922
  transcriptEventId: null,
1224
1923
  });
1924
+ if (started.idempotent && started.action.localDisposition !== "pending") {
1925
+ if (started.action.localDisposition === "continuation_pending")
1926
+ wakeServerQueue();
1927
+ res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
1928
+ return;
1929
+ }
1930
+ if (started.action.localDisposition === "continuation_pending") {
1931
+ wakeServerQueue();
1932
+ res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
1933
+ return;
1934
+ }
1935
+ const providerSendState = { denied: null };
1936
+ const runtimeResult = await steerActiveChatGeneration({
1937
+ conversationId: conversation.id,
1938
+ expectedGenerationId: durableGenerationId,
1939
+ expectedAttemptEpoch: durableAttemptEpoch,
1940
+ feedback: {
1941
+ text: started.item.payload.body,
1942
+ clientMessageId: started.action.providerClientMessageId ?? durableControlActionId,
1943
+ },
1944
+ claimProviderSend: async () => {
1945
+ const sendClaim = await svc.claimSteerProviderSend({
1946
+ orgId: conversation.orgId,
1947
+ controlActionId: durableControlActionId,
1948
+ });
1949
+ if (!sendClaim)
1950
+ return null;
1951
+ if ("sendDenied" in sendClaim) {
1952
+ providerSendState.denied = sendClaim;
1953
+ return {
1954
+ sendDenied: true,
1955
+ reason: "generation_fence_changed",
1956
+ };
1957
+ }
1958
+ try {
1959
+ await svc.appendGenerationEvent({
1960
+ orgId: conversation.orgId,
1961
+ generationId: durableGenerationId,
1962
+ attemptEpoch: durableAttemptEpoch,
1963
+ eventKind: "steer_requested",
1964
+ payload: { controlActionId: durableControlActionId, queueItemId: started.item.id },
1965
+ controlActionId: durableControlActionId,
1966
+ queueItemId: started.item.id,
1967
+ });
1968
+ }
1969
+ catch (error) {
1970
+ await svc.releaseSteerProviderSendClaim({
1971
+ orgId: conversation.orgId,
1972
+ controlActionId: durableControlActionId,
1973
+ reason: "steer_requested_event_failed",
1974
+ }).catch(() => null);
1975
+ throw error;
1976
+ }
1977
+ return {
1978
+ clientMessageId: sendClaim.providerClientMessageId ?? durableControlActionId,
1979
+ release: async () => {
1980
+ const released = await svc.releaseSteerProviderSendClaim({
1981
+ orgId: conversation.orgId,
1982
+ controlActionId: durableControlActionId,
1983
+ reason: "runtime_owner_changed_before_provider_send",
1984
+ });
1985
+ if (!released) {
1986
+ throw new Error("Steer provider send claim could not be safely released before send");
1987
+ }
1988
+ },
1989
+ };
1990
+ },
1991
+ });
1992
+ if (runtimeResult.status === "provider_send_in_flight") {
1993
+ res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
1994
+ return;
1995
+ }
1996
+ const deniedProviderSend = providerSendState.denied;
1997
+ if (deniedProviderSend) {
1998
+ if (deniedProviderSend.reason === "stop_cutoff_won_before_provider_send") {
1999
+ await interruptActiveChatGeneration(conversation.id, "steer_fallback");
2000
+ }
2001
+ wakeServerQueue();
2002
+ res.json(responseForDurableDisposition(deniedProviderSend.item, deniedProviderSend.action.localDisposition));
2003
+ return;
2004
+ }
2005
+ let resolution;
2006
+ if (runtimeResult.status === "delivered_current") {
2007
+ resolution = await svc.resolveSteerControlAction({
2008
+ orgId: conversation.orgId,
2009
+ conversationId: conversation.id,
2010
+ itemId: started.item.id,
2011
+ controlActionId: durableControlActionId,
2012
+ status: "accepted_current",
2013
+ disposition: "accepted_current",
2014
+ providerDisposition: "acknowledged",
2015
+ providerThreadId: runtimeResult.providerThreadId,
2016
+ providerTurnId: runtimeResult.providerTurnId,
2017
+ providerEvidence: {
2018
+ receipt: "same_turn",
2019
+ attemptEpoch: runtimeResult.attemptEpoch,
2020
+ ownerChangedAfterSend: runtimeResult.ownerChangedAfterSend === true,
2021
+ },
2022
+ });
2023
+ await svc.appendGenerationEvent({
2024
+ orgId: conversation.orgId,
2025
+ generationId: durableGenerationId,
2026
+ attemptEpoch: runtimeResult.attemptEpoch,
2027
+ eventKind: "steer_acknowledged",
2028
+ payload: {
2029
+ controlActionId: durableControlActionId,
2030
+ providerThreadId: runtimeResult.providerThreadId,
2031
+ providerTurnId: runtimeResult.providerTurnId,
2032
+ },
2033
+ controlActionId: durableControlActionId,
2034
+ queueItemId: started.item.id,
2035
+ });
2036
+ }
2037
+ else if (runtimeResult.status === "acceptance_unknown") {
2038
+ resolution = await svc.resolveSteerControlAction({
2039
+ orgId: conversation.orgId,
2040
+ conversationId: conversation.id,
2041
+ itemId: started.item.id,
2042
+ controlActionId: durableControlActionId,
2043
+ status: "acceptance_unknown",
2044
+ disposition: "acceptance_unknown",
2045
+ providerDisposition: "connection_lost",
2046
+ reason: runtimeResult.reason,
2047
+ providerEvidence: {
2048
+ attemptEpoch: runtimeResult.attemptEpoch,
2049
+ ownerChangedAfterSend: runtimeResult.ownerChangedAfterSend === true,
2050
+ },
2051
+ });
2052
+ }
2053
+ else if (runtimeResult.status === "continuation_required") {
2054
+ const cutoff = await svc.generationProtocol.beginSteerFallbackCutoff({
2055
+ orgId: conversation.orgId,
2056
+ conversationId: conversation.id,
2057
+ generationId: durableGenerationId,
2058
+ expectedAttemptEpoch: durableAttemptEpoch,
2059
+ controlActionId: durableControlActionId,
2060
+ queueItemId: started.item.id,
2061
+ requestedRenderSeq: req.body.lastCommittedRenderSeq,
2062
+ requestedBodyHash: req.body.renderedBodyHash,
2063
+ });
2064
+ const completionCommitted = cutoff.outcome === "completion_committed";
2065
+ const interrupt = completionCommitted
2066
+ ? null
2067
+ : interruptActiveChatGeneration(conversation.id, "steer_fallback");
2068
+ const interruptDisposition = interrupt ? await interrupt : "unverified";
2069
+ resolution = await svc.resolveSteerControlAction({
2070
+ orgId: conversation.orgId,
2071
+ conversationId: conversation.id,
2072
+ itemId: started.item.id,
2073
+ controlActionId: durableControlActionId,
2074
+ status: "continuation_pending",
2075
+ disposition: "continuation_pending",
2076
+ providerDisposition: "not_sent",
2077
+ providerEvidence: {
2078
+ ...(cutoff.action.providerEvidence ?? {}),
2079
+ ...(completionCommitted
2080
+ ? { completionDisposition: "committed" }
2081
+ : {
2082
+ interruptDisposition,
2083
+ acceptedThroughSeq: cutoff.action.acceptedThroughSeq,
2084
+ frozenBodyHash: cutoff.action.frozenBodyHash,
2085
+ }),
2086
+ },
2087
+ reason: completionCommitted
2088
+ ? "target_generation_completion_committed"
2089
+ : interrupt
2090
+ ? "runtime_requires_continuation"
2091
+ : "runtime_owner_missing_during_steer_fallback",
2092
+ });
2093
+ if (!completionCommitted && !interrupt) {
2094
+ await svc.generationProtocol.recordRuntimeTerminal({
2095
+ orgId: conversation.orgId,
2096
+ conversationId: conversation.id,
2097
+ generationId: durableGenerationId,
2098
+ expectedAttemptEpoch: durableAttemptEpoch,
2099
+ finalStatus: "interrupted_unverified",
2100
+ terminalReason: "steer_fallback_runtime_owner_missing",
2101
+ controlActionId: durableControlActionId,
2102
+ payload: { interruptDisposition },
2103
+ });
2104
+ wakeTerminalProjector();
2105
+ }
2106
+ wakeServerQueue();
2107
+ }
2108
+ else {
2109
+ resolution = await svc.resolveSteerControlAction({
2110
+ orgId: conversation.orgId,
2111
+ conversationId: conversation.id,
2112
+ itemId: started.item.id,
2113
+ controlActionId: durableControlActionId,
2114
+ status: "continuation_pending",
2115
+ disposition: "continuation_pending",
2116
+ providerDisposition: "not_sent",
2117
+ reason: "stale_generation",
2118
+ });
2119
+ await svc.appendGenerationEvent({
2120
+ orgId: conversation.orgId,
2121
+ generationId: durableGenerationId,
2122
+ attemptEpoch: durableAttemptEpoch,
2123
+ eventKind: "continuation_scheduled",
2124
+ payload: { controlActionId: durableControlActionId, reason: "stale_generation" },
2125
+ controlActionId: durableControlActionId,
2126
+ queueItemId: started.item.id,
2127
+ });
2128
+ wakeServerQueue();
2129
+ }
2130
+ res.json({
2131
+ ...responseForDurableDisposition(resolution.item, resolution.action.localDisposition),
2132
+ activeGenerationId: durableGenerationId,
2133
+ queueVersion: resolution.item.version,
2134
+ transcriptEventId: null,
2135
+ });
1225
2136
  });
1226
2137
  router.get("/chats/:id/messages", async (req, res) => {
1227
2138
  const conversation = await assertConversationAccess(req, req.params.id);
@@ -1261,6 +2172,7 @@ export function chatRoutes(db, storage) {
1261
2172
  }
1262
2173
  const actor = getActorInfo(req);
1263
2174
  assertChatLocalMutationAllowed(conversation);
2175
+ await assertSideChatMutationAllowed(req, conversation);
1264
2176
  if (actor.actorType === "agent") {
1265
2177
  if (req.body.editUserMessageId) {
1266
2178
  res.status(422).json({ error: "Agent-authored chat messages cannot edit operator messages" });
@@ -1282,6 +2194,7 @@ export function chatRoutes(db, storage) {
1282
2194
  conversationId: conversation.id,
1283
2195
  clientMutationId: `message:${randomUUID()}`,
1284
2196
  expectedGenerationId: getActiveChatGeneration(conversation.id)?.generationId ?? null,
2197
+ requestActor: queueRequestActor(req),
1285
2198
  payload: {
1286
2199
  body: req.body.body,
1287
2200
  attachmentIds: [],
@@ -1295,11 +2208,13 @@ export function chatRoutes(db, storage) {
1295
2208
  },
1296
2209
  },
1297
2210
  });
2211
+ wakeServerQueue();
1298
2212
  res.status(202).json({ queued: item });
1299
2213
  return;
1300
2214
  }
1301
2215
  try {
1302
2216
  const userMessage = await addUserMessage(conversation, req.body.body, actor, req.body.editUserMessageId ?? null);
2217
+ await touchSideChat(req, conversation);
1303
2218
  if (!req.body.editUserMessageId) {
1304
2219
  startChatTitleGeneration(conversation, userMessage);
1305
2220
  }
@@ -1388,6 +2303,8 @@ export function chatRoutes(db, storage) {
1388
2303
  heartbeat,
1389
2304
  assertConversationAccess,
1390
2305
  assertChatLocalMutationAllowed,
2306
+ assertSideChatMutationAllowed,
2307
+ touchSideChat,
1391
2308
  boardUserId,
1392
2309
  assertCanAssignTasks,
1393
2310
  runSingleFileUpload,
@@ -1411,6 +2328,9 @@ export function chatRoutes(db, storage) {
1411
2328
  recoverableFailurePayload,
1412
2329
  recoverableFailureBody,
1413
2330
  writeStreamEvent,
2331
+ queueRequestActor,
2332
+ wakeServerQueue,
2333
+ wakeTerminalProjector,
1414
2334
  });
1415
2335
  return router;
1416
2336
  }