@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
@@ -0,0 +1,1306 @@
1
+ import { agents, applyPendingMigrations, assets, chatAttachments, chatConversations, chatGenerationEvents, chatGenerations, chatMessages, createDb, ensurePostgresDatabase, issues, organizationSkills, organizations, } from "@rudderhq/db";
2
+ import { buildIssueMentionHref, createMarkdownSourceBoundaryMap, } from "@rudderhq/shared";
3
+ import { eq } from "drizzle-orm";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import net from "node:net";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
10
+ import { bindPreparedChatInlineAnnotationFiles, chatInlineAnnotationService, } from "./chat-inline-annotations.js";
11
+ import { createChatAnnotationMessagePersistence } from "./chats.annotation-persistence.js";
12
+ async function getAvailablePort() {
13
+ return new Promise((resolve, reject) => {
14
+ const server = net.createServer();
15
+ server.unref();
16
+ server.on("error", reject);
17
+ server.listen(0, "127.0.0.1", () => {
18
+ const address = server.address();
19
+ if (!address || typeof address === "string") {
20
+ server.close(() => reject(new Error("Failed to allocate test port")));
21
+ return;
22
+ }
23
+ server.close((error) => error ? reject(error) : resolve(address.port));
24
+ });
25
+ });
26
+ }
27
+ async function startTempDatabase() {
28
+ const externalUrl = process.env.RUDDER_CHAT_ANNOTATION_TEST_DATABASE_URL?.trim();
29
+ if (externalUrl) {
30
+ await applyPendingMigrations(externalUrl);
31
+ return { connectionString: externalUrl, dataDir: "", instance: null };
32
+ }
33
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "rudder-chat-inline-annotations-"));
34
+ const port = await getAvailablePort();
35
+ const mod = await import("embedded-postgres");
36
+ const EmbeddedPostgres = mod.default;
37
+ const instance = new EmbeddedPostgres({
38
+ databaseDir: dataDir,
39
+ user: "rudder",
40
+ password: "rudder",
41
+ port,
42
+ persistent: true,
43
+ initdbFlags: ["--encoding=UTF8", "--locale=C"],
44
+ onLog: () => { },
45
+ onError: () => { },
46
+ });
47
+ await instance.initialise();
48
+ await instance.start();
49
+ const adminUrl = `postgres://rudder:rudder@127.0.0.1:${port}/postgres`;
50
+ await ensurePostgresDatabase(adminUrl, "rudder");
51
+ const connectionString = `postgres://rudder:rudder@127.0.0.1:${port}/rudder`;
52
+ await applyPendingMigrations(connectionString);
53
+ return { connectionString, dataDir, instance };
54
+ }
55
+ function sha256(value) {
56
+ return createHash("sha256").update(value, "utf8").digest("hex");
57
+ }
58
+ describe("chatInlineAnnotationService", () => {
59
+ let db;
60
+ let service;
61
+ let instance = null;
62
+ let dataDir = "";
63
+ beforeAll(async () => {
64
+ const started = await startTempDatabase();
65
+ db = createDb(started.connectionString);
66
+ service = chatInlineAnnotationService(db);
67
+ instance = started.instance;
68
+ dataDir = started.dataDir;
69
+ }, 30_000);
70
+ afterEach(async () => {
71
+ await db.delete(chatGenerationEvents);
72
+ await db.delete(chatGenerations);
73
+ await db.delete(chatAttachments);
74
+ await db.delete(assets);
75
+ await db.delete(chatMessages);
76
+ await db.delete(chatConversations);
77
+ await db.delete(issues);
78
+ await db.delete(agents);
79
+ await db.delete(organizationSkills);
80
+ await db.delete(organizations);
81
+ });
82
+ afterAll(async () => {
83
+ await instance?.stop();
84
+ if (dataDir)
85
+ fs.rmSync(dataDir, { recursive: true, force: true });
86
+ });
87
+ async function seedSource(input = {}) {
88
+ const orgId = randomUUID();
89
+ const conversationId = randomUUID();
90
+ const sourceMessageId = randomUUID();
91
+ await db.insert(organizations).values({
92
+ id: orgId,
93
+ name: "Annotation test",
94
+ urlKey: `annotation-${orgId}`,
95
+ issuePrefix: `A${orgId.replaceAll("-", "").slice(0, 5).toUpperCase()}`,
96
+ requireBoardApprovalForNewAgents: false,
97
+ });
98
+ await db.insert(chatConversations).values({
99
+ id: conversationId,
100
+ orgId,
101
+ title: "Annotation source",
102
+ });
103
+ await db.insert(chatMessages).values({
104
+ id: sourceMessageId,
105
+ orgId,
106
+ conversationId,
107
+ role: input.role ?? "assistant",
108
+ kind: "message",
109
+ status: input.status ?? "completed",
110
+ body: input.body ?? "Read [the docs](https://example.test) and `run tests` before shipping.",
111
+ supersededAt: input.supersededAt ?? null,
112
+ });
113
+ return { orgId, conversationId, sourceMessageId };
114
+ }
115
+ function assistantAnnotation(source, body, overrides = {}) {
116
+ const start = body.indexOf("the docs");
117
+ const end = body.indexOf("run tests") + "run tests".length;
118
+ return {
119
+ id: randomUUID(),
120
+ surface: "assistant_body",
121
+ selectedText: "the docs and run tests",
122
+ comment: "Explain why this sequence matters.",
123
+ sourceConversationId: source.conversationId,
124
+ sourceMessageId: source.sourceMessageId,
125
+ sourceHash: sha256(body),
126
+ start,
127
+ end,
128
+ prefix: body.slice(Math.max(0, start - 5), start),
129
+ suffix: body.slice(end, end + 16),
130
+ attachmentIds: [],
131
+ attachmentFileIndexes: [0],
132
+ ...overrides,
133
+ };
134
+ }
135
+ async function seedProcessEvidence(input) {
136
+ const generationId = randomUUID();
137
+ const text = input.text ?? "Inspect";
138
+ const kind = input.entryOverrides?.kind === "assistant" ? "assistant" : "thinking";
139
+ const ts = "2026-07-23T10:00:00.000Z";
140
+ await db.insert(chatGenerations).values({
141
+ id: generationId,
142
+ orgId: input.source.orgId,
143
+ conversationId: input.source.conversationId,
144
+ status: "completed",
145
+ completedAt: new Date(),
146
+ });
147
+ await db.insert(chatGenerationEvents).values({
148
+ orgId: input.source.orgId,
149
+ generationId,
150
+ generationSeq: 1,
151
+ attemptEpoch: 1,
152
+ eventKind: "transcript",
153
+ payload: {
154
+ entry: {
155
+ kind,
156
+ ts,
157
+ text,
158
+ delta: true,
159
+ ...input.entryOverrides,
160
+ },
161
+ },
162
+ assistantMessageId: input.source.sourceMessageId,
163
+ });
164
+ if (input.projectToVisibleMessage !== false) {
165
+ await db.update(chatMessages).set({
166
+ structuredPayload: {
167
+ __chatTranscript: [{
168
+ kind,
169
+ ts,
170
+ text,
171
+ delta: true,
172
+ generationId,
173
+ generationSeqStart: 1,
174
+ generationSeqEnd: 1,
175
+ }],
176
+ },
177
+ }).where(eq(chatMessages.id, input.source.sourceMessageId));
178
+ }
179
+ return { generationId, text };
180
+ }
181
+ it("validates Markdown source anchors without equating rendered selected text to the raw slice", async () => {
182
+ const body = "Read [the docs](https://example.test) and `run tests` before shipping.";
183
+ const source = await seedSource({ body });
184
+ const annotation = assistantAnnotation(source, body);
185
+ const prepared = await service.prepare({
186
+ orgId: source.orgId,
187
+ conversationId: source.conversationId,
188
+ annotations: [annotation],
189
+ uploadedFileCount: 1,
190
+ });
191
+ const canonical = bindPreparedChatInlineAnnotationFiles(prepared, prepared.annotations, [randomUUID()]);
192
+ expect(canonical).toEqual([expect.objectContaining({
193
+ id: annotation.id,
194
+ selectedText: "the docs and run tests",
195
+ sourceHash: sha256(body),
196
+ attachmentIds: [expect.any(String)],
197
+ })]);
198
+ expect(canonical[0]).not.toHaveProperty("attachmentFileIndexes");
199
+ });
200
+ it("signals committed annotation uploads before post-transaction message hydration", async () => {
201
+ const body = "Read [the docs](https://example.test) and `run tests` before shipping.";
202
+ const source = await seedSource({ body });
203
+ const prepared = await service.prepare({
204
+ orgId: source.orgId,
205
+ conversationId: source.conversationId,
206
+ annotations: [assistantAnnotation(source, body)],
207
+ uploadedFileCount: 1,
208
+ });
209
+ const onTransactionCommitted = vi.fn();
210
+ const persist = createChatAnnotationMessagePersistence(db, async () => null);
211
+ await expect(persist(source.conversationId, source.orgId, "", null, {
212
+ structuredPayload: { inlineAnnotations: prepared.annotations },
213
+ structuredPayloadProvided: true,
214
+ attachments: [{
215
+ provider: "local_disk",
216
+ objectKey: "chats/annotation/committed-before-hydration.txt",
217
+ contentType: "text/plain",
218
+ byteSize: 4,
219
+ sha256: "a".repeat(64),
220
+ originalFilename: "context.txt",
221
+ createdByAgentId: null,
222
+ createdByUserId: "user-1",
223
+ }],
224
+ attachmentFileIndexesByAnnotationId: prepared.attachmentFileIndexesByAnnotationId,
225
+ onTransactionCommitted,
226
+ })).rejects.toThrow("Failed to hydrate created chat message");
227
+ expect(onTransactionCommitted).toHaveBeenCalledTimes(1);
228
+ expect(await db.select().from(assets)).toEqual([
229
+ expect.objectContaining({
230
+ objectKey: "chats/annotation/committed-before-hydration.txt",
231
+ }),
232
+ ]);
233
+ expect(await db.select().from(chatAttachments)).toHaveLength(1);
234
+ expect((await db.select().from(chatMessages))
235
+ .filter((message) => message.role === "user")).toHaveLength(1);
236
+ const [committedUserMessage] = (await db.select().from(chatMessages))
237
+ .filter((message) => message.role === "user");
238
+ expect(onTransactionCommitted).toHaveBeenCalledWith(committedUserMessage?.id);
239
+ });
240
+ it("accepts rendered assistant selections across links, inline code, CJK, entities, whitespace, and blocks", async () => {
241
+ const body = [
242
+ "## 说明",
243
+ "",
244
+ "阅读 [中文文档](https://example.test/docs) 与 `npm test` & verify.",
245
+ "",
246
+ "Second block.",
247
+ ].join("\n");
248
+ const source = await seedSource({ body });
249
+ const start = body.indexOf("中文文档");
250
+ const end = body.indexOf("Second block.") + "Second block.".length;
251
+ await expect(service.prepare({
252
+ orgId: source.orgId,
253
+ conversationId: source.conversationId,
254
+ uploadedFileCount: 0,
255
+ annotations: [{
256
+ id: randomUUID(),
257
+ surface: "assistant_body",
258
+ selectedText: "中文文档 与 npm test & verify.\nSecond block.",
259
+ comment: null,
260
+ sourceConversationId: source.conversationId,
261
+ sourceMessageId: source.sourceMessageId,
262
+ sourceHash: sha256(body),
263
+ start,
264
+ end,
265
+ prefix: body.slice(Math.max(0, start - 12), start),
266
+ suffix: body.slice(end, end + 12),
267
+ attachmentIds: [],
268
+ }],
269
+ })).resolves.toMatchObject({
270
+ annotations: [expect.objectContaining({
271
+ selectedText: "中文文档 与 npm test & verify.\nSecond block.",
272
+ })],
273
+ });
274
+ });
275
+ it("accepts only current resolved issue and skill labels for anchored Markdown links", async () => {
276
+ const source = await seedSource({ body: "placeholder" });
277
+ const issueId = randomUUID();
278
+ const skillId = randomUUID();
279
+ const agentId = randomUUID();
280
+ await db.insert(agents).values({
281
+ id: agentId,
282
+ orgId: source.orgId,
283
+ name: "Annotation agent",
284
+ role: "engineer",
285
+ });
286
+ await db.insert(issues).values({
287
+ id: issueId,
288
+ orgId: source.orgId,
289
+ title: "Current issue title",
290
+ identifier: "RUD-42",
291
+ createdByAgentId: agentId,
292
+ });
293
+ await db.insert(organizationSkills).values({
294
+ id: skillId,
295
+ orgId: source.orgId,
296
+ key: "org:current-skill",
297
+ slug: "current-skill",
298
+ name: "Current skill",
299
+ markdown: "# Current skill",
300
+ });
301
+ const issueLink = `[stale issue](issue://${issueId})`;
302
+ const skillLink = `[stale-skill](skill://org/${skillId}?ref=stale-skill)`;
303
+ const body = `Review ${issueLink} with ${skillLink} before shipping.`;
304
+ await db.update(chatMessages)
305
+ .set({ body })
306
+ .where(eq(chatMessages.id, source.sourceMessageId));
307
+ const start = body.indexOf(issueLink);
308
+ const end = body.indexOf(skillLink) + skillLink.length;
309
+ const annotation = assistantAnnotation(source, body, {
310
+ selectedText: "RUD-42 Current issue title with current-skill",
311
+ start,
312
+ end,
313
+ prefix: body.slice(Math.max(0, start - 12), start),
314
+ suffix: body.slice(end, end + 16),
315
+ attachmentFileIndexes: [],
316
+ });
317
+ await expect(service.prepare({
318
+ orgId: source.orgId,
319
+ conversationId: source.conversationId,
320
+ uploadedFileCount: 0,
321
+ annotations: [annotation],
322
+ })).resolves.toMatchObject({
323
+ annotations: [expect.objectContaining({
324
+ selectedText: "RUD-42 Current issue title with current-skill",
325
+ })],
326
+ });
327
+ await expect(service.prepare({
328
+ orgId: source.orgId,
329
+ conversationId: source.conversationId,
330
+ uploadedFileCount: 0,
331
+ annotations: [{
332
+ ...annotation,
333
+ id: randomUUID(),
334
+ selectedText: "stale issue with stale-skill",
335
+ }],
336
+ })).rejects.toMatchObject({
337
+ status: 422,
338
+ message: expect.stringContaining("selected text"),
339
+ });
340
+ await expect(service.prepare({
341
+ orgId: source.orgId,
342
+ conversationId: source.conversationId,
343
+ uploadedFileCount: 0,
344
+ annotations: [{
345
+ ...annotation,
346
+ id: randomUUID(),
347
+ selectedText: "RUD-42 Current issue title with fabricated-skill",
348
+ }],
349
+ })).rejects.toMatchObject({
350
+ status: 422,
351
+ message: expect.stringContaining("selected text"),
352
+ });
353
+ const issueLabel = "RUD-42 Current issue title";
354
+ const skillLabel = "current-skill";
355
+ const partialStart = start
356
+ + createMarkdownSourceBoundaryMap(issueLink, issueLabel)
357
+ .renderedBoundaryToRaw["RUD-42 ".length];
358
+ const skillStart = body.indexOf(skillLink);
359
+ const partialEnd = skillStart
360
+ + createMarkdownSourceBoundaryMap(skillLink, skillLabel)
361
+ .renderedBoundaryToRaw["current".length];
362
+ await expect(service.prepare({
363
+ orgId: source.orgId,
364
+ conversationId: source.conversationId,
365
+ uploadedFileCount: 0,
366
+ annotations: [{
367
+ ...annotation,
368
+ id: randomUUID(),
369
+ selectedText: "Current issue title with current",
370
+ start: partialStart,
371
+ end: partialEnd,
372
+ prefix: body.slice(Math.max(0, partialStart - 160), partialStart),
373
+ suffix: body.slice(partialEnd, partialEnd + 160),
374
+ }],
375
+ })).resolves.toMatchObject({
376
+ annotations: [expect.objectContaining({
377
+ selectedText: "Current issue title with current",
378
+ })],
379
+ });
380
+ await expect(service.prepare({
381
+ orgId: source.orgId,
382
+ conversationId: source.conversationId,
383
+ uploadedFileCount: 0,
384
+ annotations: [assistantAnnotation(source, body, {
385
+ id: randomUUID(),
386
+ selectedText: "Review ",
387
+ start: 0,
388
+ end: start,
389
+ prefix: "",
390
+ suffix: body.slice(start, start + 160),
391
+ attachmentFileIndexes: [],
392
+ })],
393
+ })).resolves.toMatchObject({
394
+ annotations: [expect.objectContaining({
395
+ selectedText: "Review ",
396
+ })],
397
+ });
398
+ await expect(service.prepare({
399
+ orgId: source.orgId,
400
+ conversationId: source.conversationId,
401
+ uploadedFileCount: 0,
402
+ annotations: [assistantAnnotation(source, body, {
403
+ id: randomUUID(),
404
+ selectedText: " before shipping.",
405
+ start: end,
406
+ end: body.length,
407
+ prefix: body.slice(Math.max(0, end - 160), end),
408
+ suffix: "",
409
+ attachmentFileIndexes: [],
410
+ })],
411
+ })).resolves.toMatchObject({
412
+ annotations: [expect.objectContaining({
413
+ selectedText: " before shipping.",
414
+ })],
415
+ });
416
+ const otherOrgId = randomUUID();
417
+ const otherIssueId = randomUUID();
418
+ await db.insert(organizations).values({
419
+ id: otherOrgId,
420
+ name: "Other annotation organization",
421
+ urlKey: `other-annotation-${otherOrgId}`,
422
+ issuePrefix: `O${otherOrgId.replaceAll("-", "").slice(0, 5).toUpperCase()}`,
423
+ requireBoardApprovalForNewAgents: false,
424
+ });
425
+ await db.insert(issues).values({
426
+ id: otherIssueId,
427
+ orgId: otherOrgId,
428
+ title: "Private cross-org issue",
429
+ identifier: "PRIVATE-1",
430
+ });
431
+ const crossOrgBody = `[stale](${buildIssueMentionHref(otherIssueId, "PRIVATE-1")})`;
432
+ await db.update(chatMessages)
433
+ .set({ body: crossOrgBody })
434
+ .where(eq(chatMessages.id, source.sourceMessageId));
435
+ await expect(service.prepare({
436
+ orgId: source.orgId,
437
+ conversationId: source.conversationId,
438
+ uploadedFileCount: 0,
439
+ annotations: [{
440
+ ...annotation,
441
+ id: randomUUID(),
442
+ selectedText: "PRIVATE-1 Private cross-org issue",
443
+ sourceHash: sha256(crossOrgBody),
444
+ start: 0,
445
+ end: crossOrgBody.length,
446
+ prefix: "",
447
+ suffix: "",
448
+ }],
449
+ })).rejects.toMatchObject({
450
+ status: 422,
451
+ message: expect.stringContaining("selected text"),
452
+ });
453
+ });
454
+ it("rejects malformed encoded issue hrefs without surfacing a URIError", async () => {
455
+ const body = "Review [x](/issues/%E0%A4%A) before shipping.";
456
+ const source = await seedSource({ body });
457
+ const link = "[x](/issues/%E0%A4%A)";
458
+ const start = body.indexOf(link);
459
+ await expect(service.prepare({
460
+ orgId: source.orgId,
461
+ conversationId: source.conversationId,
462
+ uploadedFileCount: 0,
463
+ annotations: [{
464
+ id: randomUUID(),
465
+ surface: "assistant_body",
466
+ selectedText: "fabricated issue label",
467
+ comment: null,
468
+ sourceConversationId: source.conversationId,
469
+ sourceMessageId: source.sourceMessageId,
470
+ sourceHash: sha256(body),
471
+ start,
472
+ end: start + link.length,
473
+ prefix: body.slice(0, start),
474
+ suffix: body.slice(start + link.length),
475
+ attachmentIds: [],
476
+ }],
477
+ })).rejects.toMatchObject({
478
+ status: 422,
479
+ message: expect.stringContaining("selected text"),
480
+ });
481
+ });
482
+ it.each([
483
+ ["omitted rendered characters", "中文文档 与 npm test verify.\nSecond block."],
484
+ ["altered block whitespace", "中文文档 与 npm test & verify. Second block."],
485
+ ["inserted rendered characters", "中文文档 与 npm test & verify. EXTRA\nSecond block."],
486
+ ])("rejects %s from an otherwise valid Markdown range", async (_label, selectedText) => {
487
+ const body = [
488
+ "## 说明",
489
+ "",
490
+ "阅读 [中文文档](https://example.test/docs) 与 `npm test` & verify.",
491
+ "",
492
+ "Second block.",
493
+ ].join("\n");
494
+ const source = await seedSource({ body });
495
+ const start = body.indexOf("中文文档");
496
+ const end = body.indexOf("Second block.") + "Second block.".length;
497
+ await expect(service.prepare({
498
+ orgId: source.orgId,
499
+ conversationId: source.conversationId,
500
+ uploadedFileCount: 0,
501
+ annotations: [{
502
+ id: randomUUID(),
503
+ surface: "assistant_body",
504
+ selectedText,
505
+ comment: null,
506
+ sourceConversationId: source.conversationId,
507
+ sourceMessageId: source.sourceMessageId,
508
+ sourceHash: sha256(body),
509
+ start,
510
+ end,
511
+ prefix: body.slice(Math.max(0, start - 12), start),
512
+ suffix: body.slice(end, end + 12),
513
+ attachmentIds: [],
514
+ }],
515
+ })).rejects.toMatchObject({
516
+ status: 422,
517
+ message: expect.stringContaining("selected text"),
518
+ });
519
+ });
520
+ it("rejects a zero-width-only rendered selection", async () => {
521
+ const body = "\u200b";
522
+ const source = await seedSource({ body });
523
+ await expect(service.prepare({
524
+ orgId: source.orgId,
525
+ conversationId: source.conversationId,
526
+ uploadedFileCount: 0,
527
+ annotations: [{
528
+ id: randomUUID(),
529
+ surface: "assistant_body",
530
+ selectedText: body,
531
+ comment: null,
532
+ sourceConversationId: source.conversationId,
533
+ sourceMessageId: source.sourceMessageId,
534
+ sourceHash: sha256(body),
535
+ start: 0,
536
+ end: body.length,
537
+ prefix: "",
538
+ suffix: "",
539
+ attachmentIds: [],
540
+ }],
541
+ })).rejects.toMatchObject({
542
+ status: 422,
543
+ message: expect.stringContaining("visible text"),
544
+ });
545
+ });
546
+ it("rejects fabricated assistant selected text even when the raw range contains Markdown", async () => {
547
+ const body = "Read [the docs](https://example.test) and `run tests` before shipping.";
548
+ const source = await seedSource({ body });
549
+ await expect(service.prepare({
550
+ orgId: source.orgId,
551
+ conversationId: source.conversationId,
552
+ annotations: [assistantAnnotation(source, body, {
553
+ selectedText: "fabricated anchor",
554
+ attachmentFileIndexes: [],
555
+ })],
556
+ uploadedFileCount: 0,
557
+ })).rejects.toMatchObject({
558
+ status: 422,
559
+ message: expect.stringContaining("selected text"),
560
+ });
561
+ });
562
+ it.each([
563
+ ["wrong source hash", { sourceHash: "0".repeat(64) }, "source hash"],
564
+ ["out-of-bounds source range", { end: 10_000 }, "source range"],
565
+ ["stale prefix", { prefix: "wrong" }, "prefix"],
566
+ ["stale suffix", { suffix: "wrong" }, "suffix"],
567
+ ["wrong source conversation", { sourceConversationId: randomUUID() }, "conversation"],
568
+ ])("rejects %s before canonicalization", async (_label, overrides, expectedMessage) => {
569
+ const body = "Read [the docs](https://example.test) and `run tests` before shipping.";
570
+ const source = await seedSource({ body });
571
+ await expect(service.prepare({
572
+ orgId: source.orgId,
573
+ conversationId: source.conversationId,
574
+ annotations: [assistantAnnotation(source, body, overrides)],
575
+ uploadedFileCount: 1,
576
+ })).rejects.toMatchObject({
577
+ status: 422,
578
+ message: expect.stringContaining(expectedMessage),
579
+ });
580
+ });
581
+ it.each([
582
+ ["user source", { role: "user" }, "assistant"],
583
+ ["streaming source", { status: "streaming" }, "stable"],
584
+ ["interrupted source", { status: "interrupted" }, "stable"],
585
+ ["superseded source", { supersededAt: new Date() }, "visible"],
586
+ ])("rejects a %s message", async (_label, sourceOverrides, expectedMessage) => {
587
+ const body = "Stable answer";
588
+ const source = await seedSource({ body, ...sourceOverrides });
589
+ await expect(service.prepare({
590
+ orgId: source.orgId,
591
+ conversationId: source.conversationId,
592
+ annotations: [assistantAnnotation(source, body, {
593
+ selectedText: "Stable",
594
+ start: 0,
595
+ end: 6,
596
+ prefix: "",
597
+ suffix: " answer",
598
+ attachmentFileIndexes: [],
599
+ })],
600
+ uploadedFileCount: 0,
601
+ })).rejects.toMatchObject({
602
+ status: 422,
603
+ message: expect.stringContaining(expectedMessage),
604
+ });
605
+ });
606
+ it("rejects a source message owned by another organization", async () => {
607
+ const body = "Stable answer";
608
+ const source = await seedSource({ body });
609
+ const otherOrgId = randomUUID();
610
+ await db.insert(organizations).values({
611
+ id: otherOrgId,
612
+ name: "Other annotation org",
613
+ urlKey: `other-annotation-${otherOrgId}`,
614
+ issuePrefix: `O${otherOrgId.replaceAll("-", "").slice(0, 5).toUpperCase()}`,
615
+ requireBoardApprovalForNewAgents: false,
616
+ });
617
+ await expect(service.prepare({
618
+ orgId: otherOrgId,
619
+ conversationId: source.conversationId,
620
+ annotations: [assistantAnnotation(source, body, {
621
+ selectedText: "Stable",
622
+ start: 0,
623
+ end: 6,
624
+ prefix: "",
625
+ suffix: " answer",
626
+ attachmentFileIndexes: [],
627
+ })],
628
+ uploadedFileCount: 0,
629
+ })).rejects.toMatchObject({
630
+ status: 422,
631
+ message: expect.stringContaining("organization"),
632
+ });
633
+ });
634
+ it.each(["another message", "another organization"])("rejects an existing annotation attachment from %s", async (ownershipScenario) => {
635
+ const body = "Stable answer";
636
+ const source = await seedSource({ body });
637
+ const editMessageId = randomUUID();
638
+ await db.insert(chatMessages).values({
639
+ id: editMessageId,
640
+ orgId: source.orgId,
641
+ conversationId: source.conversationId,
642
+ role: "user",
643
+ kind: "message",
644
+ status: "completed",
645
+ body: "Edit target",
646
+ });
647
+ let attachmentOrgId = source.orgId;
648
+ let attachmentConversationId = source.conversationId;
649
+ if (ownershipScenario === "another organization") {
650
+ attachmentOrgId = randomUUID();
651
+ attachmentConversationId = randomUUID();
652
+ await db.insert(organizations).values({
653
+ id: attachmentOrgId,
654
+ name: "Attachment owner",
655
+ urlKey: `attachment-owner-${attachmentOrgId}`,
656
+ issuePrefix: `X${attachmentOrgId.replaceAll("-", "").slice(0, 5).toUpperCase()}`,
657
+ requireBoardApprovalForNewAgents: false,
658
+ });
659
+ await db.insert(chatConversations).values({
660
+ id: attachmentConversationId,
661
+ orgId: attachmentOrgId,
662
+ title: "Attachment owner",
663
+ });
664
+ }
665
+ const ownerMessageId = randomUUID();
666
+ await db.insert(chatMessages).values({
667
+ id: ownerMessageId,
668
+ orgId: attachmentOrgId,
669
+ conversationId: attachmentConversationId,
670
+ role: "user",
671
+ kind: "message",
672
+ status: "completed",
673
+ body: "Attachment owner",
674
+ });
675
+ const [asset] = await db.insert(assets).values({
676
+ orgId: attachmentOrgId,
677
+ provider: "local_disk",
678
+ objectKey: `annotation/${randomUUID()}`,
679
+ contentType: "text/plain",
680
+ byteSize: 4,
681
+ sha256: "asset",
682
+ }).returning();
683
+ const [borrowedAttachment] = await db.insert(chatAttachments).values({
684
+ orgId: attachmentOrgId,
685
+ conversationId: attachmentConversationId,
686
+ messageId: ownerMessageId,
687
+ assetId: asset.id,
688
+ }).returning();
689
+ const annotation = assistantAnnotation(source, body, {
690
+ selectedText: "Stable",
691
+ start: 0,
692
+ end: 6,
693
+ prefix: "",
694
+ suffix: " answer",
695
+ attachmentIds: [borrowedAttachment.id],
696
+ attachmentFileIndexes: [],
697
+ });
698
+ const { attachmentFileIndexes: _attachmentFileIndexes, ...persistedAnnotation } = annotation;
699
+ await db
700
+ .update(chatMessages)
701
+ .set({ structuredPayload: { inlineAnnotations: [persistedAnnotation] } })
702
+ .where(eq(chatMessages.id, editMessageId));
703
+ await expect(service.prepare({
704
+ orgId: source.orgId,
705
+ conversationId: source.conversationId,
706
+ editUserMessageId: editMessageId,
707
+ annotations: [annotation],
708
+ uploadedFileCount: 0,
709
+ })).rejects.toMatchObject({
710
+ status: 422,
711
+ message: expect.stringContaining("edited user message"),
712
+ });
713
+ });
714
+ it("rejects an invalid uploaded file index before any attachment can be staged", async () => {
715
+ const body = "Stable answer";
716
+ const source = await seedSource({ body });
717
+ await expect(service.prepare({
718
+ orgId: source.orgId,
719
+ conversationId: source.conversationId,
720
+ annotations: [assistantAnnotation(source, body, {
721
+ selectedText: "Stable",
722
+ start: 0,
723
+ end: 6,
724
+ prefix: "",
725
+ suffix: " answer",
726
+ attachmentFileIndexes: [1],
727
+ })],
728
+ uploadedFileCount: 1,
729
+ })).rejects.toMatchObject({
730
+ status: 422,
731
+ message: expect.stringContaining("file index"),
732
+ });
733
+ });
734
+ it("anchors Process selections to one terminal generation's visible prose event range", async () => {
735
+ const source = await seedSource({ body: "Final answer" });
736
+ const generationId = randomUUID();
737
+ await db.insert(chatGenerations).values({
738
+ id: generationId,
739
+ orgId: source.orgId,
740
+ conversationId: source.conversationId,
741
+ status: "completed",
742
+ completedAt: new Date(),
743
+ });
744
+ await db.insert(chatGenerationEvents).values([
745
+ {
746
+ orgId: source.orgId,
747
+ generationId,
748
+ generationSeq: 1,
749
+ attemptEpoch: 1,
750
+ eventKind: "transcript",
751
+ payload: {
752
+ entry: {
753
+ kind: "thinking",
754
+ ts: "2026-07-23T10:00:00.000Z",
755
+ text: "Inspect [the docs](https://example.test) ",
756
+ delta: true,
757
+ },
758
+ },
759
+ assistantMessageId: source.sourceMessageId,
760
+ },
761
+ {
762
+ orgId: source.orgId,
763
+ generationId,
764
+ generationSeq: 2,
765
+ attemptEpoch: 1,
766
+ eventKind: "transcript",
767
+ payload: {
768
+ entry: {
769
+ kind: "thinking",
770
+ ts: "2026-07-23T10:00:01.000Z",
771
+ text: "before editing.",
772
+ delta: true,
773
+ },
774
+ },
775
+ assistantMessageId: source.sourceMessageId,
776
+ },
777
+ ]);
778
+ const processSource = "Inspect [the docs](https://example.test) before editing.";
779
+ await db.update(chatMessages).set({
780
+ structuredPayload: {
781
+ __chatTranscript: [{
782
+ kind: "thinking",
783
+ ts: "2026-07-23T10:00:01.000Z",
784
+ text: processSource,
785
+ delta: true,
786
+ generationId,
787
+ generationSeqStart: 1,
788
+ generationSeqEnd: 2,
789
+ }],
790
+ },
791
+ }).where(eq(chatMessages.id, source.sourceMessageId));
792
+ const annotation = {
793
+ id: randomUUID(),
794
+ surface: "process_transcript",
795
+ transcriptKind: "thinking",
796
+ selectedText: "the docs before editing.",
797
+ comment: null,
798
+ sourceConversationId: source.conversationId,
799
+ sourceMessageId: source.sourceMessageId,
800
+ sourceHash: sha256(processSource),
801
+ generationId,
802
+ generationSeqStart: 1,
803
+ generationSeqEnd: 2,
804
+ start: processSource.indexOf("the docs"),
805
+ end: processSource.indexOf("editing.") + "editing.".length,
806
+ prefix: processSource.slice(0, processSource.indexOf("the docs")),
807
+ suffix: "",
808
+ attachmentIds: [],
809
+ };
810
+ await expect(service.prepare({
811
+ orgId: source.orgId,
812
+ conversationId: source.conversationId,
813
+ annotations: [annotation],
814
+ uploadedFileCount: 0,
815
+ })).resolves.toMatchObject({
816
+ annotations: [expect.objectContaining({
817
+ generationId,
818
+ generationSeqStart: 1,
819
+ generationSeqEnd: 2,
820
+ })],
821
+ });
822
+ });
823
+ it("accepts Process evidence from the exact parent anchor of a Side Chat", async () => {
824
+ const source = await seedSource({ body: "Final answer" });
825
+ const evidence = await seedProcessEvidence({
826
+ source,
827
+ text: "Inspect the parent process evidence.",
828
+ });
829
+ const sideConversationId = randomUUID();
830
+ await db.insert(chatConversations).values({
831
+ id: sideConversationId,
832
+ orgId: source.orgId,
833
+ title: "Process annotation Side Chat",
834
+ conversationKind: "side_chat",
835
+ sideChatState: "active",
836
+ forkedFromConversationId: source.conversationId,
837
+ forkedFromMessageId: source.sourceMessageId,
838
+ });
839
+ await expect(service.prepare({
840
+ orgId: source.orgId,
841
+ conversationId: sideConversationId,
842
+ uploadedFileCount: 0,
843
+ annotations: [{
844
+ id: randomUUID(),
845
+ surface: "process_transcript",
846
+ transcriptKind: "thinking",
847
+ selectedText: evidence.text,
848
+ comment: null,
849
+ sourceConversationId: source.conversationId,
850
+ sourceMessageId: source.sourceMessageId,
851
+ sourceHash: sha256(evidence.text),
852
+ generationId: evidence.generationId,
853
+ generationSeqStart: 1,
854
+ generationSeqEnd: 1,
855
+ start: 0,
856
+ end: evidence.text.length,
857
+ prefix: "",
858
+ suffix: "",
859
+ attachmentIds: [],
860
+ }],
861
+ })).resolves.toMatchObject({
862
+ annotations: [expect.objectContaining({
863
+ sourceConversationId: source.conversationId,
864
+ sourceMessageId: source.sourceMessageId,
865
+ generationId: evidence.generationId,
866
+ })],
867
+ });
868
+ });
869
+ it("rejects hidden thinking evidence even when its text appears in the visible projection", async () => {
870
+ const source = await seedSource({ body: "Final answer" });
871
+ const evidence = await seedProcessEvidence({
872
+ source,
873
+ entryOverrides: { visibility: "internal" },
874
+ });
875
+ const annotation = {
876
+ id: randomUUID(),
877
+ surface: "process_transcript",
878
+ transcriptKind: "thinking",
879
+ selectedText: evidence.text,
880
+ sourceConversationId: source.conversationId,
881
+ sourceMessageId: source.sourceMessageId,
882
+ sourceHash: sha256(evidence.text),
883
+ generationId: evidence.generationId,
884
+ generationSeqStart: 1,
885
+ generationSeqEnd: 1,
886
+ start: 0,
887
+ end: evidence.text.length,
888
+ prefix: "",
889
+ suffix: "",
890
+ attachmentIds: [],
891
+ };
892
+ await expect(service.prepare({
893
+ orgId: source.orgId,
894
+ conversationId: source.conversationId,
895
+ annotations: [annotation],
896
+ uploadedFileCount: 0,
897
+ })).rejects.toMatchObject({
898
+ status: 422,
899
+ message: expect.stringContaining("visible assistant or thinking prose"),
900
+ });
901
+ });
902
+ it("rejects Process evidence that is absent from the visible message projection", async () => {
903
+ const source = await seedSource({ body: "Final answer" });
904
+ const evidence = await seedProcessEvidence({
905
+ source,
906
+ projectToVisibleMessage: false,
907
+ });
908
+ const annotation = {
909
+ id: randomUUID(),
910
+ surface: "process_transcript",
911
+ transcriptKind: "thinking",
912
+ selectedText: evidence.text,
913
+ sourceConversationId: source.conversationId,
914
+ sourceMessageId: source.sourceMessageId,
915
+ sourceHash: sha256(evidence.text),
916
+ generationId: evidence.generationId,
917
+ generationSeqStart: 1,
918
+ generationSeqEnd: 1,
919
+ start: 0,
920
+ end: evidence.text.length,
921
+ prefix: "",
922
+ suffix: "",
923
+ attachmentIds: [],
924
+ };
925
+ await expect(service.prepare({
926
+ orgId: source.orgId,
927
+ conversationId: source.conversationId,
928
+ annotations: [annotation],
929
+ uploadedFileCount: 0,
930
+ })).rejects.toMatchObject({
931
+ status: 422,
932
+ message: expect.stringContaining("visible message projection"),
933
+ });
934
+ });
935
+ it("rejects the assistant final-answer suffix hidden by the Chat Process projection", async () => {
936
+ const finalAnswer = "Final answer";
937
+ const processSource = `Exploration\n${finalAnswer}`;
938
+ const source = await seedSource({ body: finalAnswer });
939
+ const evidence = await seedProcessEvidence({
940
+ source,
941
+ text: processSource,
942
+ entryOverrides: { kind: "assistant" },
943
+ });
944
+ const start = processSource.indexOf(finalAnswer);
945
+ const annotation = {
946
+ id: randomUUID(),
947
+ surface: "process_transcript",
948
+ transcriptKind: "assistant",
949
+ selectedText: finalAnswer,
950
+ sourceConversationId: source.conversationId,
951
+ sourceMessageId: source.sourceMessageId,
952
+ sourceHash: sha256(processSource),
953
+ generationId: evidence.generationId,
954
+ generationSeqStart: 1,
955
+ generationSeqEnd: 1,
956
+ start,
957
+ end: processSource.length,
958
+ prefix: processSource.slice(0, start),
959
+ suffix: "",
960
+ attachmentIds: [],
961
+ };
962
+ await expect(service.prepare({
963
+ orgId: source.orgId,
964
+ conversationId: source.conversationId,
965
+ annotations: [annotation],
966
+ uploadedFileCount: 0,
967
+ })).rejects.toMatchObject({
968
+ status: 422,
969
+ message: expect.stringContaining("visible message projection"),
970
+ });
971
+ });
972
+ it("accepts the visible assistant Process prefix after final-answer suffix redaction", async () => {
973
+ const visibleProcess = "Exploration";
974
+ const finalAnswer = "Final answer";
975
+ const processSource = `${visibleProcess}\n${finalAnswer}`;
976
+ const source = await seedSource({ body: finalAnswer });
977
+ const evidence = await seedProcessEvidence({
978
+ source,
979
+ text: processSource,
980
+ entryOverrides: { kind: "assistant" },
981
+ });
982
+ const annotation = {
983
+ id: randomUUID(),
984
+ surface: "process_transcript",
985
+ transcriptKind: "assistant",
986
+ selectedText: visibleProcess,
987
+ sourceConversationId: source.conversationId,
988
+ sourceMessageId: source.sourceMessageId,
989
+ sourceHash: sha256(visibleProcess),
990
+ generationId: evidence.generationId,
991
+ generationSeqStart: 1,
992
+ generationSeqEnd: 1,
993
+ start: 0,
994
+ end: visibleProcess.length,
995
+ prefix: "",
996
+ suffix: "",
997
+ attachmentIds: [],
998
+ };
999
+ await expect(service.prepare({
1000
+ orgId: source.orgId,
1001
+ conversationId: source.conversationId,
1002
+ annotations: [annotation],
1003
+ uploadedFileCount: 0,
1004
+ })).resolves.toMatchObject({
1005
+ annotations: [expect.objectContaining({
1006
+ selectedText: visibleProcess,
1007
+ sourceHash: sha256(visibleProcess),
1008
+ })],
1009
+ });
1010
+ });
1011
+ it("rejects assistant protocol text hidden by the Chat Process projection", async () => {
1012
+ const processSource = "Visible investigation\nRUDDER_RESULT_BEGIN\nPRIVATE_FINAL_PROTOCOL";
1013
+ const source = await seedSource({ body: "Final answer" });
1014
+ const evidence = await seedProcessEvidence({
1015
+ source,
1016
+ text: processSource,
1017
+ entryOverrides: { kind: "assistant" },
1018
+ });
1019
+ const selectedText = "PRIVATE_FINAL_PROTOCOL";
1020
+ const start = processSource.indexOf(selectedText);
1021
+ const annotation = {
1022
+ id: randomUUID(),
1023
+ surface: "process_transcript",
1024
+ transcriptKind: "assistant",
1025
+ selectedText,
1026
+ sourceConversationId: source.conversationId,
1027
+ sourceMessageId: source.sourceMessageId,
1028
+ sourceHash: sha256(processSource),
1029
+ generationId: evidence.generationId,
1030
+ generationSeqStart: 1,
1031
+ generationSeqEnd: 1,
1032
+ start,
1033
+ end: start + selectedText.length,
1034
+ prefix: processSource.slice(Math.max(0, start - 160), start),
1035
+ suffix: "",
1036
+ attachmentIds: [],
1037
+ };
1038
+ await expect(service.prepare({
1039
+ orgId: source.orgId,
1040
+ conversationId: source.conversationId,
1041
+ annotations: [annotation],
1042
+ uploadedFileCount: 0,
1043
+ })).rejects.toMatchObject({
1044
+ status: 422,
1045
+ message: expect.stringContaining("visible message projection"),
1046
+ });
1047
+ });
1048
+ it("rejects protocol text split across hidden lifecycle transcript entries", async () => {
1049
+ const source = await seedSource({ body: "Final answer" });
1050
+ const generationId = randomUUID();
1051
+ const ts = "2026-07-23T10:00:00.000Z";
1052
+ const privateChunk = "BEGIN\nPRIVATE_PROTOCOL";
1053
+ await db.insert(chatGenerations).values({
1054
+ id: generationId,
1055
+ orgId: source.orgId,
1056
+ conversationId: source.conversationId,
1057
+ status: "completed",
1058
+ completedAt: new Date(),
1059
+ });
1060
+ await db.insert(chatGenerationEvents).values([
1061
+ {
1062
+ orgId: source.orgId,
1063
+ generationId,
1064
+ generationSeq: 1,
1065
+ attemptEpoch: 1,
1066
+ eventKind: "transcript",
1067
+ payload: { entry: { kind: "assistant", ts, text: "RUDDER_RESULT_", delta: true } },
1068
+ assistantMessageId: source.sourceMessageId,
1069
+ },
1070
+ {
1071
+ orgId: source.orgId,
1072
+ generationId,
1073
+ generationSeq: 2,
1074
+ attemptEpoch: 1,
1075
+ eventKind: "transcript",
1076
+ payload: { entry: { kind: "system", ts, text: "reasoning completed" } },
1077
+ assistantMessageId: source.sourceMessageId,
1078
+ },
1079
+ {
1080
+ orgId: source.orgId,
1081
+ generationId,
1082
+ generationSeq: 3,
1083
+ attemptEpoch: 1,
1084
+ eventKind: "transcript",
1085
+ payload: { entry: { kind: "assistant", ts, text: privateChunk, delta: true } },
1086
+ assistantMessageId: source.sourceMessageId,
1087
+ },
1088
+ ]);
1089
+ await db.update(chatMessages).set({
1090
+ structuredPayload: {
1091
+ __chatTranscript: [
1092
+ {
1093
+ kind: "assistant",
1094
+ ts,
1095
+ text: "RUDDER_RESULT_",
1096
+ delta: true,
1097
+ generationId,
1098
+ generationSeqStart: 1,
1099
+ generationSeqEnd: 1,
1100
+ },
1101
+ {
1102
+ kind: "system",
1103
+ ts,
1104
+ text: "reasoning completed",
1105
+ generationId,
1106
+ generationSeqStart: 2,
1107
+ generationSeqEnd: 2,
1108
+ },
1109
+ {
1110
+ kind: "assistant",
1111
+ ts,
1112
+ text: privateChunk,
1113
+ delta: true,
1114
+ generationId,
1115
+ generationSeqStart: 3,
1116
+ generationSeqEnd: 3,
1117
+ },
1118
+ ],
1119
+ },
1120
+ }).where(eq(chatMessages.id, source.sourceMessageId));
1121
+ const selectedText = "PRIVATE_PROTOCOL";
1122
+ const start = privateChunk.indexOf(selectedText);
1123
+ await expect(service.prepare({
1124
+ orgId: source.orgId,
1125
+ conversationId: source.conversationId,
1126
+ uploadedFileCount: 0,
1127
+ annotations: [{
1128
+ id: randomUUID(),
1129
+ surface: "process_transcript",
1130
+ transcriptKind: "assistant",
1131
+ selectedText,
1132
+ comment: null,
1133
+ sourceConversationId: source.conversationId,
1134
+ sourceMessageId: source.sourceMessageId,
1135
+ sourceHash: sha256(privateChunk),
1136
+ generationId,
1137
+ generationSeqStart: 3,
1138
+ generationSeqEnd: 3,
1139
+ start,
1140
+ end: start + selectedText.length,
1141
+ prefix: privateChunk.slice(0, start),
1142
+ suffix: "",
1143
+ attachmentIds: [],
1144
+ }],
1145
+ })).rejects.toMatchObject({
1146
+ status: 422,
1147
+ message: expect.stringContaining("visible message projection"),
1148
+ });
1149
+ });
1150
+ it("rejects fabricated selected text for a plain Process source range", async () => {
1151
+ const source = await seedSource({ body: "Final answer" });
1152
+ const evidence = await seedProcessEvidence({ source });
1153
+ const annotation = {
1154
+ id: randomUUID(),
1155
+ surface: "process_transcript",
1156
+ transcriptKind: "thinking",
1157
+ selectedText: "Fabricated",
1158
+ sourceConversationId: source.conversationId,
1159
+ sourceMessageId: source.sourceMessageId,
1160
+ sourceHash: sha256(evidence.text),
1161
+ generationId: evidence.generationId,
1162
+ generationSeqStart: 1,
1163
+ generationSeqEnd: 1,
1164
+ start: 0,
1165
+ end: evidence.text.length,
1166
+ prefix: "",
1167
+ suffix: "",
1168
+ attachmentIds: [],
1169
+ };
1170
+ await expect(service.prepare({
1171
+ orgId: source.orgId,
1172
+ conversationId: source.conversationId,
1173
+ annotations: [annotation],
1174
+ uploadedFileCount: 0,
1175
+ })).rejects.toMatchObject({
1176
+ status: 422,
1177
+ message: expect.stringContaining("selected text"),
1178
+ });
1179
+ });
1180
+ it("accepts rendered Process selections across Markdown, CJK, entities, whitespace, and blocks", async () => {
1181
+ const source = await seedSource({ body: "Final answer" });
1182
+ const processSource = [
1183
+ "检查 [中文文档](https://example.test/docs) 与 `npm test` & verify.",
1184
+ "",
1185
+ "Second block.",
1186
+ ].join("\n");
1187
+ const evidence = await seedProcessEvidence({ source, text: processSource });
1188
+ await expect(service.prepare({
1189
+ orgId: source.orgId,
1190
+ conversationId: source.conversationId,
1191
+ uploadedFileCount: 0,
1192
+ annotations: [{
1193
+ id: randomUUID(),
1194
+ surface: "process_transcript",
1195
+ transcriptKind: "thinking",
1196
+ selectedText: "中文文档 与 npm test & verify.\nSecond block.",
1197
+ comment: null,
1198
+ sourceConversationId: source.conversationId,
1199
+ sourceMessageId: source.sourceMessageId,
1200
+ sourceHash: sha256(processSource),
1201
+ generationId: evidence.generationId,
1202
+ generationSeqStart: 1,
1203
+ generationSeqEnd: 1,
1204
+ start: processSource.indexOf("中文文档"),
1205
+ end: processSource.length,
1206
+ prefix: processSource.slice(0, processSource.indexOf("中文文档")),
1207
+ suffix: "",
1208
+ attachmentIds: [],
1209
+ }],
1210
+ })).resolves.toMatchObject({
1211
+ annotations: [expect.objectContaining({
1212
+ selectedText: "中文文档 与 npm test & verify.\nSecond block.",
1213
+ })],
1214
+ });
1215
+ });
1216
+ it("rejects fabricated Process selected text when the raw range contains Markdown", async () => {
1217
+ const source = await seedSource({ body: "Final answer" });
1218
+ const processSource = "Inspect [the docs](https://example.test) before editing.";
1219
+ const evidence = await seedProcessEvidence({ source, text: processSource });
1220
+ await expect(service.prepare({
1221
+ orgId: source.orgId,
1222
+ conversationId: source.conversationId,
1223
+ uploadedFileCount: 0,
1224
+ annotations: [{
1225
+ id: randomUUID(),
1226
+ surface: "process_transcript",
1227
+ transcriptKind: "thinking",
1228
+ selectedText: "fabricated process anchor",
1229
+ comment: null,
1230
+ sourceConversationId: source.conversationId,
1231
+ sourceMessageId: source.sourceMessageId,
1232
+ sourceHash: sha256(processSource),
1233
+ generationId: evidence.generationId,
1234
+ generationSeqStart: 1,
1235
+ generationSeqEnd: 1,
1236
+ start: 0,
1237
+ end: processSource.length,
1238
+ prefix: "",
1239
+ suffix: "",
1240
+ attachmentIds: [],
1241
+ }],
1242
+ })).rejects.toMatchObject({
1243
+ status: 422,
1244
+ message: expect.stringContaining("selected text"),
1245
+ });
1246
+ });
1247
+ it.each([
1248
+ ["nonterminal generation", "running", "terminal"],
1249
+ ["wrong generation identity", "wrong_generation", "generation"],
1250
+ ["non-prose evidence", "tool", "visible"],
1251
+ ["missing evidence", "missing", "evidence"],
1252
+ ])("rejects Process annotation with %s", async (_label, scenario, expectedMessage) => {
1253
+ const source = await seedSource({ body: "Final answer" });
1254
+ const generationId = randomUUID();
1255
+ await db.insert(chatGenerations).values({
1256
+ id: generationId,
1257
+ orgId: source.orgId,
1258
+ conversationId: source.conversationId,
1259
+ status: scenario === "running" ? "running" : "completed",
1260
+ completedAt: scenario === "running" ? null : new Date(),
1261
+ });
1262
+ if (scenario !== "missing") {
1263
+ await db.insert(chatGenerationEvents).values({
1264
+ orgId: source.orgId,
1265
+ generationId,
1266
+ generationSeq: 1,
1267
+ attemptEpoch: 1,
1268
+ eventKind: "transcript",
1269
+ payload: {
1270
+ entry: scenario === "tool"
1271
+ ? { kind: "tool_call", ts: "2026-07-23T10:00:00.000Z", name: "shell", input: {} }
1272
+ : { kind: "thinking", ts: "2026-07-23T10:00:00.000Z", text: "Inspect", delta: true },
1273
+ },
1274
+ assistantMessageId: source.sourceMessageId,
1275
+ });
1276
+ }
1277
+ const evidence = "Inspect";
1278
+ const annotation = {
1279
+ id: randomUUID(),
1280
+ surface: "process_transcript",
1281
+ transcriptKind: "thinking",
1282
+ selectedText: evidence,
1283
+ sourceConversationId: source.conversationId,
1284
+ sourceMessageId: source.sourceMessageId,
1285
+ sourceHash: sha256(evidence),
1286
+ generationId: scenario === "wrong_generation" ? randomUUID() : generationId,
1287
+ generationSeqStart: 1,
1288
+ generationSeqEnd: 1,
1289
+ start: 0,
1290
+ end: evidence.length,
1291
+ prefix: "",
1292
+ suffix: "",
1293
+ attachmentIds: [],
1294
+ };
1295
+ await expect(service.prepare({
1296
+ orgId: source.orgId,
1297
+ conversationId: source.conversationId,
1298
+ annotations: [annotation],
1299
+ uploadedFileCount: 0,
1300
+ })).rejects.toMatchObject({
1301
+ status: 422,
1302
+ message: expect.stringContaining(expectedMessage),
1303
+ });
1304
+ });
1305
+ });
1306
+ //# sourceMappingURL=chat-inline-annotations.test.js.map