@memberjunction/ng-conversations 5.40.2 → 5.41.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 (378) hide show
  1. package/README.md +57 -0
  2. package/dist/__tests__/channel-optional-surface.test.d.ts +2 -0
  3. package/dist/__tests__/channel-optional-surface.test.d.ts.map +1 -0
  4. package/dist/__tests__/channel-optional-surface.test.js +53 -0
  5. package/dist/__tests__/channel-optional-surface.test.js.map +1 -0
  6. package/dist/__tests__/chat-events.test.d.ts +14 -0
  7. package/dist/__tests__/chat-events.test.d.ts.map +1 -0
  8. package/dist/__tests__/chat-events.test.js +109 -0
  9. package/dist/__tests__/chat-events.test.js.map +1 -0
  10. package/dist/__tests__/conversation-naming.test.d.ts +2 -0
  11. package/dist/__tests__/conversation-naming.test.d.ts.map +1 -0
  12. package/dist/__tests__/conversation-naming.test.js +110 -0
  13. package/dist/__tests__/conversation-naming.test.js.map +1 -0
  14. package/dist/__tests__/delegation-result-parser.test.d.ts +2 -0
  15. package/dist/__tests__/delegation-result-parser.test.d.ts.map +1 -0
  16. package/dist/__tests__/delegation-result-parser.test.js +107 -0
  17. package/dist/__tests__/delegation-result-parser.test.js.map +1 -0
  18. package/dist/__tests__/event-wiring.test.d.ts +15 -0
  19. package/dist/__tests__/event-wiring.test.d.ts.map +1 -0
  20. package/dist/__tests__/event-wiring.test.js +100 -0
  21. package/dist/__tests__/event-wiring.test.js.map +1 -0
  22. package/dist/__tests__/narration-template.test.d.ts +2 -0
  23. package/dist/__tests__/narration-template.test.d.ts.map +1 -0
  24. package/dist/__tests__/narration-template.test.js +76 -0
  25. package/dist/__tests__/narration-template.test.js.map +1 -0
  26. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +2 -0
  27. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +1 -0
  28. package/dist/__tests__/realtime-agent-picker-models.test.js +49 -0
  29. package/dist/__tests__/realtime-agent-picker-models.test.js.map +1 -0
  30. package/dist/__tests__/realtime-audio-visuals.test.d.ts +2 -0
  31. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +1 -0
  32. package/dist/__tests__/realtime-audio-visuals.test.js +123 -0
  33. package/dist/__tests__/realtime-audio-visuals.test.js.map +1 -0
  34. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +2 -0
  35. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +1 -0
  36. package/dist/__tests__/realtime-delegation-card-cancel.test.js +48 -0
  37. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +1 -0
  38. package/dist/__tests__/realtime-disclosure.test.d.ts +2 -0
  39. package/dist/__tests__/realtime-disclosure.test.d.ts.map +1 -0
  40. package/dist/__tests__/realtime-disclosure.test.js +164 -0
  41. package/dist/__tests__/realtime-disclosure.test.js.map +1 -0
  42. package/dist/__tests__/realtime-pairing.test.d.ts +2 -0
  43. package/dist/__tests__/realtime-pairing.test.d.ts.map +1 -0
  44. package/dist/__tests__/realtime-pairing.test.js +207 -0
  45. package/dist/__tests__/realtime-pairing.test.js.map +1 -0
  46. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +2 -0
  47. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +1 -0
  48. package/dist/__tests__/realtime-review-lifecycle.test.js +154 -0
  49. package/dist/__tests__/realtime-review-lifecycle.test.js.map +1 -0
  50. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +2 -0
  51. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +1 -0
  52. package/dist/__tests__/realtime-session-cancel-usage.test.js +230 -0
  53. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +1 -0
  54. package/dist/__tests__/realtime-session-channels.test.d.ts +2 -0
  55. package/dist/__tests__/realtime-session-channels.test.d.ts.map +1 -0
  56. package/dist/__tests__/realtime-session-channels.test.js +252 -0
  57. package/dist/__tests__/realtime-session-channels.test.js.map +1 -0
  58. package/dist/__tests__/realtime-session-client-tools.test.d.ts +2 -0
  59. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +1 -0
  60. package/dist/__tests__/realtime-session-client-tools.test.js +103 -0
  61. package/dist/__tests__/realtime-session-client-tools.test.js.map +1 -0
  62. package/dist/__tests__/realtime-session-minimized.test.d.ts +2 -0
  63. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +1 -0
  64. package/dist/__tests__/realtime-session-minimized.test.js +32 -0
  65. package/dist/__tests__/realtime-session-minimized.test.js.map +1 -0
  66. package/dist/__tests__/realtime-session-mint.test.d.ts +2 -0
  67. package/dist/__tests__/realtime-session-mint.test.d.ts.map +1 -0
  68. package/dist/__tests__/realtime-session-mint.test.js +69 -0
  69. package/dist/__tests__/realtime-session-mint.test.js.map +1 -0
  70. package/dist/__tests__/realtime-session-policy.test.d.ts +2 -0
  71. package/dist/__tests__/realtime-session-policy.test.d.ts.map +1 -0
  72. package/dist/__tests__/realtime-session-policy.test.js +303 -0
  73. package/dist/__tests__/realtime-session-policy.test.js.map +1 -0
  74. package/dist/__tests__/realtime-session-review.service.test.d.ts +2 -0
  75. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +1 -0
  76. package/dist/__tests__/realtime-session-review.service.test.js +743 -0
  77. package/dist/__tests__/realtime-session-review.service.test.js.map +1 -0
  78. package/dist/__tests__/realtime-session-state.test.d.ts +2 -0
  79. package/dist/__tests__/realtime-session-state.test.d.ts.map +1 -0
  80. package/dist/__tests__/realtime-session-state.test.js +83 -0
  81. package/dist/__tests__/realtime-session-state.test.js.map +1 -0
  82. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +2 -0
  83. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +1 -0
  84. package/dist/__tests__/realtime-session-timeline-card.test.js +106 -0
  85. package/dist/__tests__/realtime-session-timeline-card.test.js.map +1 -0
  86. package/dist/__tests__/realtime-session-timeline.test.d.ts +2 -0
  87. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +1 -0
  88. package/dist/__tests__/realtime-session-timeline.test.js +142 -0
  89. package/dist/__tests__/realtime-session-timeline.test.js.map +1 -0
  90. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +19 -0
  91. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +1 -0
  92. package/dist/__tests__/realtime-sessions-adapter.test.js +188 -0
  93. package/dist/__tests__/realtime-sessions-adapter.test.js.map +1 -0
  94. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +2 -0
  95. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +1 -0
  96. package/dist/__tests__/realtime-surface-panel-prefs.test.js +100 -0
  97. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +1 -0
  98. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +2 -0
  99. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +1 -0
  100. package/dist/__tests__/realtime-surface-tabs-model.test.js +193 -0
  101. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +1 -0
  102. package/dist/__tests__/remote-browser-audio-player.test.d.ts +2 -0
  103. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +1 -0
  104. package/dist/__tests__/remote-browser-audio-player.test.js +137 -0
  105. package/dist/__tests__/remote-browser-audio-player.test.js.map +1 -0
  106. package/dist/__tests__/remote-browser-channel.test.d.ts +2 -0
  107. package/dist/__tests__/remote-browser-channel.test.d.ts.map +1 -0
  108. package/dist/__tests__/remote-browser-channel.test.js +423 -0
  109. package/dist/__tests__/remote-browser-channel.test.js.map +1 -0
  110. package/dist/__tests__/slot-defaults.test.d.ts +24 -0
  111. package/dist/__tests__/slot-defaults.test.d.ts.map +1 -0
  112. package/dist/__tests__/slot-defaults.test.js +63 -0
  113. package/dist/__tests__/slot-defaults.test.js.map +1 -0
  114. package/dist/__tests__/user-authorization.test.d.ts +2 -0
  115. package/dist/__tests__/user-authorization.test.d.ts.map +1 -0
  116. package/dist/__tests__/user-authorization.test.js +97 -0
  117. package/dist/__tests__/user-authorization.test.js.map +1 -0
  118. package/dist/__tests__/voice-session-narration.test.d.ts +2 -0
  119. package/dist/__tests__/voice-session-narration.test.d.ts.map +1 -0
  120. package/dist/__tests__/voice-session-narration.test.js +609 -0
  121. package/dist/__tests__/voice-session-narration.test.js.map +1 -0
  122. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +2 -0
  123. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +1 -0
  124. package/dist/__tests__/whiteboard-artifact-viewer.test.js +101 -0
  125. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +1 -0
  126. package/dist/__tests__/whiteboard-channel.test.d.ts +2 -0
  127. package/dist/__tests__/whiteboard-channel.test.d.ts.map +1 -0
  128. package/dist/__tests__/whiteboard-channel.test.js +260 -0
  129. package/dist/__tests__/whiteboard-channel.test.js.map +1 -0
  130. package/dist/__tests__/whiteboard-restore-state.test.d.ts +2 -0
  131. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +1 -0
  132. package/dist/__tests__/whiteboard-restore-state.test.js +108 -0
  133. package/dist/__tests__/whiteboard-restore-state.test.js.map +1 -0
  134. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +205 -3
  135. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  136. package/dist/lib/components/conversation/conversation-chat-area.component.js +911 -342
  137. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  138. package/dist/lib/components/mention/mention-dropdown.component.js +35 -17
  139. package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
  140. package/dist/lib/components/mention/mention-editor.component.d.ts +4 -0
  141. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  142. package/dist/lib/components/mention/mention-editor.component.js +43 -19
  143. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  144. package/dist/lib/components/message/message-input-box.component.d.ts +17 -1
  145. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  146. package/dist/lib/components/message/message-input-box.component.js +73 -15
  147. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  148. package/dist/lib/components/message/message-input.component.d.ts +142 -6
  149. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  150. package/dist/lib/components/message/message-input.component.js +328 -82
  151. package/dist/lib/components/message/message-input.component.js.map +1 -1
  152. package/dist/lib/components/message/message-item.component.d.ts +28 -3
  153. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  154. package/dist/lib/components/message/message-item.component.js +180 -108
  155. package/dist/lib/components/message/message-item.component.js.map +1 -1
  156. package/dist/lib/components/message/message-list.component.d.ts +81 -2
  157. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  158. package/dist/lib/components/message/message-list.component.js +252 -87
  159. package/dist/lib/components/message/message-list.component.js.map +1 -1
  160. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +282 -0
  161. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -0
  162. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +158 -0
  163. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -0
  164. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts +25 -0
  165. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts.map +1 -0
  166. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js +140 -0
  167. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js.map +1 -0
  168. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +35 -0
  169. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -0
  170. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +58 -0
  171. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -0
  172. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +63 -0
  173. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -0
  174. package/dist/lib/components/realtime/realtime-activity-rail.component.js +260 -0
  175. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -0
  176. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +117 -0
  177. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -0
  178. package/dist/lib/components/realtime/realtime-agent-banner.component.js +504 -0
  179. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -0
  180. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +168 -0
  181. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -0
  182. package/dist/lib/components/realtime/realtime-agent-picker.component.js +556 -0
  183. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -0
  184. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +97 -0
  185. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -0
  186. package/dist/lib/components/realtime/realtime-audio-visuals.js +139 -0
  187. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -0
  188. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts +29 -0
  189. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts.map +1 -0
  190. package/dist/lib/components/realtime/realtime-channel-strip.component.js +69 -0
  191. package/dist/lib/components/realtime/realtime-channel-strip.component.js.map +1 -0
  192. package/dist/lib/components/realtime/realtime-composer.component.d.ts +65 -0
  193. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -0
  194. package/dist/lib/components/realtime/realtime-composer.component.js +256 -0
  195. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -0
  196. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +71 -0
  197. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -0
  198. package/dist/lib/components/realtime/realtime-delegation-card.component.js +324 -0
  199. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -0
  200. package/dist/lib/components/realtime/realtime-disclosure.d.ts +135 -0
  201. package/dist/lib/components/realtime/realtime-disclosure.d.ts.map +1 -0
  202. package/dist/lib/components/realtime/realtime-disclosure.js +188 -0
  203. package/dist/lib/components/realtime/realtime-disclosure.js.map +1 -0
  204. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +491 -0
  205. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -0
  206. package/dist/lib/components/realtime/realtime-session-overlay.component.js +1274 -0
  207. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -0
  208. package/dist/lib/components/realtime/realtime-session-state.d.ts +191 -0
  209. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -0
  210. package/dist/lib/components/realtime/realtime-session-state.js +244 -0
  211. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -0
  212. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts +56 -0
  213. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts.map +1 -0
  214. package/dist/lib/components/realtime/realtime-session-thread.component.js +246 -0
  215. package/dist/lib/components/realtime/realtime-session-thread.component.js.map +1 -0
  216. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +51 -0
  217. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -0
  218. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +193 -0
  219. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -0
  220. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts +77 -0
  221. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts.map +1 -0
  222. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js +114 -0
  223. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js.map +1 -0
  224. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +173 -0
  225. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -0
  226. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +496 -0
  227. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -0
  228. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +181 -0
  229. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -0
  230. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +223 -0
  231. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -0
  232. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts +163 -0
  233. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts.map +1 -0
  234. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js +309 -0
  235. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js.map +1 -0
  236. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +168 -0
  237. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -0
  238. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +524 -0
  239. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -0
  240. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +346 -0
  241. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -0
  242. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +851 -0
  243. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -0
  244. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +86 -0
  245. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -0
  246. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +210 -0
  247. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -0
  248. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts +48 -0
  249. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts.map +1 -0
  250. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js +180 -0
  251. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js.map +1 -0
  252. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +119 -0
  253. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -0
  254. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +274 -0
  255. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -0
  256. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts +11 -0
  257. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts.map +1 -0
  258. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js +98 -0
  259. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js.map +1 -0
  260. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts +9 -0
  261. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts.map +1 -0
  262. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js +35 -0
  263. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js.map +1 -0
  264. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts +28 -0
  265. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts.map +1 -0
  266. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js +104 -0
  267. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js.map +1 -0
  268. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts +11 -0
  269. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts.map +1 -0
  270. package/dist/lib/components/slots/mj-chat-header-default.component.js +103 -0
  271. package/dist/lib/components/slots/mj-chat-header-default.component.js.map +1 -0
  272. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts +15 -0
  273. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts.map +1 -0
  274. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js +73 -0
  275. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js.map +1 -0
  276. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts +9 -0
  277. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts.map +1 -0
  278. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js +34 -0
  279. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js.map +1 -0
  280. package/dist/lib/components/slots/slot-interfaces.d.ts +95 -0
  281. package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -0
  282. package/dist/lib/components/slots/slot-interfaces.js +18 -0
  283. package/dist/lib/components/slots/slot-interfaces.js.map +1 -0
  284. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -0
  285. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  286. package/dist/lib/components/workspace/conversation-workspace.component.js +28 -4
  287. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  288. package/dist/lib/conversations.module.d.ts +12 -1
  289. package/dist/lib/conversations.module.d.ts.map +1 -1
  290. package/dist/lib/conversations.module.js +93 -5
  291. package/dist/lib/conversations.module.js.map +1 -1
  292. package/dist/lib/directives/chat-slot.directive.d.ts +44 -0
  293. package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -0
  294. package/dist/lib/directives/chat-slot.directive.js +54 -0
  295. package/dist/lib/directives/chat-slot.directive.js.map +1 -0
  296. package/dist/lib/events/chat-events.d.ts +137 -0
  297. package/dist/lib/events/chat-events.d.ts.map +1 -0
  298. package/dist/lib/events/chat-events.js +189 -0
  299. package/dist/lib/events/chat-events.js.map +1 -0
  300. package/dist/lib/models/conversation-state.model.d.ts +2 -1
  301. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  302. package/dist/lib/models/conversation-state.model.js.map +1 -1
  303. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  304. package/dist/lib/services/artifact-state.service.js +23 -6
  305. package/dist/lib/services/artifact-state.service.js.map +1 -1
  306. package/dist/lib/services/conversation-agent.service.d.ts +60 -74
  307. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  308. package/dist/lib/services/conversation-agent.service.js +100 -313
  309. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  310. package/dist/lib/services/conversation-bridge.service.d.ts +11 -70
  311. package/dist/lib/services/conversation-bridge.service.d.ts.map +1 -1
  312. package/dist/lib/services/conversation-bridge.service.js +51 -85
  313. package/dist/lib/services/conversation-bridge.service.js.map +1 -1
  314. package/dist/lib/services/conversation-naming.d.ts +63 -0
  315. package/dist/lib/services/conversation-naming.d.ts.map +1 -0
  316. package/dist/lib/services/conversation-naming.js +58 -0
  317. package/dist/lib/services/conversation-naming.js.map +1 -0
  318. package/dist/lib/services/conversation-streaming.service.d.ts +24 -154
  319. package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
  320. package/dist/lib/services/conversation-streaming.service.js +39 -361
  321. package/dist/lib/services/conversation-streaming.service.js.map +1 -1
  322. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts +10 -0
  323. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts.map +1 -0
  324. package/dist/lib/services/conversations-runtime-bootstrap.service.js +104 -0
  325. package/dist/lib/services/conversations-runtime-bootstrap.service.js.map +1 -0
  326. package/dist/lib/services/delegation-result-parser.d.ts +45 -0
  327. package/dist/lib/services/delegation-result-parser.d.ts.map +1 -0
  328. package/dist/lib/services/delegation-result-parser.js +48 -0
  329. package/dist/lib/services/delegation-result-parser.js.map +1 -0
  330. package/dist/lib/services/mention-autocomplete.service.d.ts +19 -4
  331. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  332. package/dist/lib/services/mention-autocomplete.service.js +65 -4
  333. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  334. package/dist/lib/services/mention-parser.service.d.ts +8 -53
  335. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  336. package/dist/lib/services/mention-parser.service.js +32 -243
  337. package/dist/lib/services/mention-parser.service.js.map +1 -1
  338. package/dist/lib/services/narration-template.d.ts +42 -0
  339. package/dist/lib/services/narration-template.d.ts.map +1 -0
  340. package/dist/lib/services/narration-template.js +73 -0
  341. package/dist/lib/services/narration-template.js.map +1 -0
  342. package/dist/lib/services/realtime-pairing.d.ts +120 -0
  343. package/dist/lib/services/realtime-pairing.d.ts.map +1 -0
  344. package/dist/lib/services/realtime-pairing.js +150 -0
  345. package/dist/lib/services/realtime-pairing.js.map +1 -0
  346. package/dist/lib/services/realtime-session-review.service.d.ts +233 -0
  347. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -0
  348. package/dist/lib/services/realtime-session-review.service.js +417 -0
  349. package/dist/lib/services/realtime-session-review.service.js.map +1 -0
  350. package/dist/lib/services/realtime-session.service.d.ts +739 -0
  351. package/dist/lib/services/realtime-session.service.d.ts.map +1 -0
  352. package/dist/lib/services/realtime-session.service.js +1647 -0
  353. package/dist/lib/services/realtime-session.service.js.map +1 -0
  354. package/dist/lib/services/realtime-sessions-adapter.d.ts +54 -0
  355. package/dist/lib/services/realtime-sessions-adapter.d.ts.map +1 -0
  356. package/dist/lib/services/realtime-sessions-adapter.js +154 -0
  357. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -0
  358. package/dist/lib/services/user-authorization.d.ts +67 -0
  359. package/dist/lib/services/user-authorization.d.ts.map +1 -0
  360. package/dist/lib/services/user-authorization.js +66 -0
  361. package/dist/lib/services/user-authorization.js.map +1 -0
  362. package/dist/lib/utils/realtime-session-timeline.d.ts +84 -0
  363. package/dist/lib/utils/realtime-session-timeline.d.ts.map +1 -0
  364. package/dist/lib/utils/realtime-session-timeline.js +94 -0
  365. package/dist/lib/utils/realtime-session-timeline.js.map +1 -0
  366. package/dist/public-api.d.ts +41 -0
  367. package/dist/public-api.d.ts.map +1 -1
  368. package/dist/public-api.js +50 -0
  369. package/dist/public-api.js.map +1 -1
  370. package/package.json +27 -24
  371. package/dist/__tests__/conversation-bridge.service.test.d.ts +0 -2
  372. package/dist/__tests__/conversation-bridge.service.test.d.ts.map +0 -1
  373. package/dist/__tests__/conversation-bridge.service.test.js +0 -98
  374. package/dist/__tests__/conversation-bridge.service.test.js.map +0 -1
  375. package/dist/__tests__/mention-parser.test.d.ts +0 -2
  376. package/dist/__tests__/mention-parser.test.d.ts.map +0 -1
  377. package/dist/__tests__/mention-parser.test.js +0 -154
  378. package/dist/__tests__/mention-parser.test.js.map +0 -1
@@ -0,0 +1,743 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { Subject } from 'rxjs';
3
+ import { RunView } from '@memberjunction/core';
4
+ import { BuildReviewDelegationCard, BuildReviewThreadItems, MAX_REVIEW_LEGS, MergeChainChannelStates, RealtimeSessionReviewService } from '../lib/services/realtime-session-review.service';
5
+ import { RealtimeSessionState } from '../lib/components/realtime/realtime-session-state';
6
+ function sessionRow(overrides = {}) {
7
+ return {
8
+ ID: 'SESSION-1',
9
+ AgentID: 'AGENT-CO',
10
+ Agent: 'Realtime Co-Agent',
11
+ Status: 'Closed',
12
+ ConversationID: 'CONV-1',
13
+ Config: JSON.stringify({ targetAgentID: 'AGENT-TARGET', coAgentRunID: 'RUN-CO' }),
14
+ LastActiveAt: '2026-06-10T10:30:00Z',
15
+ ClosedAt: '2026-06-10T10:31:00Z',
16
+ CloseReason: 'Explicit',
17
+ __mj_CreatedAt: '2026-06-10T10:00:00Z',
18
+ ...overrides
19
+ };
20
+ }
21
+ function detailRow(role, message, at, hidden = false) {
22
+ return { ID: `D-${at}`, Role: role, Message: message, HiddenToUser: hidden, __mj_CreatedAt: at };
23
+ }
24
+ function runRow(id, overrides = {}) {
25
+ return {
26
+ ID: id,
27
+ AgentID: 'AGENT-X',
28
+ Agent: 'Sage',
29
+ Status: 'Completed',
30
+ Success: true,
31
+ Message: 'Did the thing',
32
+ ErrorMessage: null,
33
+ FinalStep: 'Success',
34
+ StartedAt: '2026-06-10T10:05:00Z',
35
+ CompletedAt: '2026-06-10T10:06:00Z',
36
+ ...overrides
37
+ };
38
+ }
39
+ function channelRow(channel, config) {
40
+ return { ID: `CH-${channel ?? 'x'}`, Channel: channel, Config: config };
41
+ }
42
+ // ── RunViews mock plumbing (suite convention: spy on RunView.FromMetadataProvider) ──
43
+ let capturedParams = [];
44
+ /** EVERY RunViews call's params, in call order (chain walks issue several calls). */
45
+ let capturedCalls = [];
46
+ function mockRunViews(results) {
47
+ const runViewsFn = vi.fn(async (params) => {
48
+ capturedParams = params;
49
+ capturedCalls.push(params);
50
+ return results;
51
+ });
52
+ vi.spyOn(RunView, 'FromMetadataProvider').mockReturnValue({ RunViews: runViewsFn });
53
+ }
54
+ /**
55
+ * Sequenced RunViews mock: call N returns `sequence[N]` (the last entry repeats when the
56
+ * sequence runs out). Lets chain-walk tests serve different rows per leg / per artifact query.
57
+ */
58
+ function mockRunViewsSequence(sequence) {
59
+ let call = 0;
60
+ const runViewsFn = vi.fn(async (params) => {
61
+ capturedParams = params;
62
+ capturedCalls.push(params);
63
+ const results = sequence[Math.min(call, sequence.length - 1)];
64
+ call++;
65
+ return results;
66
+ });
67
+ vi.spyOn(RunView, 'FromMetadataProvider').mockReturnValue({ RunViews: runViewsFn });
68
+ }
69
+ function ok(rows) {
70
+ return { Success: true, Results: rows };
71
+ }
72
+ function failed() {
73
+ return { Success: false, ErrorMessage: 'boom', Results: [] };
74
+ }
75
+ const provider = {};
76
+ describe('RealtimeSessionReviewService', () => {
77
+ let service;
78
+ beforeEach(() => {
79
+ service = new RealtimeSessionReviewService();
80
+ capturedParams = [];
81
+ capturedCalls = [];
82
+ });
83
+ afterEach(() => {
84
+ vi.restoreAllMocks();
85
+ });
86
+ describe('LoadSessionReview — null paths', () => {
87
+ it('returns null when the session row does not exist', async () => {
88
+ mockRunViews([ok([]), ok([]), ok([]), ok([])]);
89
+ const review = await service.LoadSessionReview('SESSION-MISSING', provider);
90
+ expect(review).toBeNull();
91
+ });
92
+ it('returns null when the session query itself fails', async () => {
93
+ mockRunViews([failed(), ok([]), ok([]), ok([])]);
94
+ expect(await service.LoadSessionReview('SESSION-1', provider)).toBeNull();
95
+ });
96
+ it('returns null for an empty / whitespace id without querying', async () => {
97
+ const spy = vi.spyOn(RunView, 'FromMetadataProvider');
98
+ expect(await service.LoadSessionReview(' ', provider)).toBeNull();
99
+ expect(spy).not.toHaveBeenCalled();
100
+ });
101
+ it('returns null (never throws) when the batch load rejects', async () => {
102
+ vi.spyOn(RunView, 'FromMetadataProvider').mockReturnValue({
103
+ RunViews: vi.fn(async () => {
104
+ throw new Error('network down');
105
+ })
106
+ });
107
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
108
+ expect(await service.LoadSessionReview('SESSION-1', provider)).toBeNull();
109
+ expect(errSpy).toHaveBeenCalled();
110
+ });
111
+ });
112
+ describe('LoadSessionReview — query shape', () => {
113
+ it('issues ONE batched RunViews call covering session, details, runs, and channels', async () => {
114
+ mockRunViews([ok([sessionRow()]), ok([]), ok([]), ok([])]);
115
+ await service.LoadSessionReview('SESSION-1', provider);
116
+ expect(capturedParams.map(p => p.EntityName)).toEqual([
117
+ 'MJ: AI Agent Sessions',
118
+ 'MJ: Conversation Details',
119
+ 'MJ: AI Agent Runs',
120
+ 'MJ: AI Agent Session Channels'
121
+ ]);
122
+ for (const p of capturedParams) {
123
+ expect(p.ResultType).toBe('simple');
124
+ expect(p.Fields?.length).toBeGreaterThan(0);
125
+ }
126
+ });
127
+ it('escapes single quotes in the session id used in filters', async () => {
128
+ mockRunViews([ok([]), ok([]), ok([]), ok([])]);
129
+ await service.LoadSessionReview("abc'def", provider);
130
+ expect(capturedParams[0].ExtraFilter).toBe("ID='abc''def'");
131
+ expect(capturedParams[1].ExtraFilter).toBe("AgentSessionID='abc''def'");
132
+ });
133
+ it('orders turns and runs chronologically at the database', async () => {
134
+ mockRunViews([ok([sessionRow()]), ok([]), ok([]), ok([])]);
135
+ await service.LoadSessionReview('SESSION-1', provider);
136
+ expect(capturedParams[1].OrderBy).toBe('__mj_CreatedAt ASC');
137
+ expect(capturedParams[2].OrderBy).toBe('StartedAt ASC');
138
+ });
139
+ });
140
+ describe('LoadSessionReview — session mapping', () => {
141
+ it('maps identity, lifecycle, and the Config targetAgentID', async () => {
142
+ mockRunViews([ok([sessionRow()]), ok([]), ok([]), ok([])]);
143
+ const review = await service.LoadSessionReview('SESSION-1', provider);
144
+ expect(review).not.toBeNull();
145
+ expect(review?.SessionID).toBe('SESSION-1');
146
+ expect(review?.AgentID).toBe('AGENT-CO');
147
+ expect(review?.AgentName).toBe('Realtime Co-Agent');
148
+ expect(review?.TargetAgentID).toBe('AGENT-TARGET');
149
+ expect(review?.ConversationID).toBe('CONV-1');
150
+ expect(review?.Status).toBe('Closed');
151
+ expect(review?.CloseReason).toBe('Explicit');
152
+ expect(review?.StartedAt?.toISOString()).toBe('2026-06-10T10:00:00.000Z');
153
+ expect(review?.ClosedAt?.toISOString()).toBe('2026-06-10T10:31:00.000Z');
154
+ });
155
+ it('falls back to the session AgentID when the Config carries no targetAgentID', async () => {
156
+ mockRunViews([ok([sessionRow({ Config: '{}' })]), ok([]), ok([]), ok([])]);
157
+ const review = await service.LoadSessionReview('SESSION-1', provider);
158
+ expect(review?.TargetAgentID).toBe('AGENT-CO');
159
+ });
160
+ it('tolerates malformed Config JSON (fallback target, no run exclusion)', async () => {
161
+ mockRunViews([ok([sessionRow({ Config: '{not json' })]), ok([]), ok([runRow('RUN-CO')]), ok([])]);
162
+ const review = await service.LoadSessionReview('SESSION-1', provider);
163
+ expect(review?.TargetAgentID).toBe('AGENT-CO');
164
+ expect(review?.DelegatedRuns.map(r => r.RunID)).toEqual(['RUN-CO']);
165
+ });
166
+ it('degrades failed sub-queries to empty collections (review still loads)', async () => {
167
+ mockRunViews([ok([sessionRow()]), failed(), failed(), failed()]);
168
+ const review = await service.LoadSessionReview('SESSION-1', provider);
169
+ expect(review).not.toBeNull();
170
+ expect(review?.Turns).toEqual([]);
171
+ expect(review?.DelegatedRuns).toEqual([]);
172
+ expect(review?.ChannelStates).toEqual([]);
173
+ });
174
+ });
175
+ describe('LoadSessionReview — turn mapping', () => {
176
+ it('maps visible User/AI rows in order, translating AI → Assistant', async () => {
177
+ mockRunViews([
178
+ ok([sessionRow()]),
179
+ ok([
180
+ detailRow('User', 'Hello there', '2026-06-10T10:01:00Z'),
181
+ detailRow('AI', 'Hi! How can I help?', '2026-06-10T10:01:30Z')
182
+ ]),
183
+ ok([]),
184
+ ok([])
185
+ ]);
186
+ const review = await service.LoadSessionReview('SESSION-1', provider);
187
+ expect(review?.Turns).toHaveLength(2);
188
+ expect(review?.Turns[0]).toMatchObject({ Role: 'User', Text: 'Hello there' });
189
+ expect(review?.Turns[1]).toMatchObject({ Role: 'Assistant', Text: 'Hi! How can I help?' });
190
+ expect(review?.Turns[0].At?.toISOString()).toBe('2026-06-10T10:01:00.000Z');
191
+ });
192
+ it('skips Error rows, hidden rows, and empty messages', async () => {
193
+ mockRunViews([
194
+ ok([sessionRow()]),
195
+ ok([
196
+ detailRow('Error', 'something broke', '2026-06-10T10:01:00Z'),
197
+ detailRow('User', 'visible', '2026-06-10T10:02:00Z'),
198
+ detailRow('AI', 'secret', '2026-06-10T10:03:00Z', true),
199
+ detailRow('AI', ' ', '2026-06-10T10:04:00Z'),
200
+ detailRow('AI', null, '2026-06-10T10:05:00Z')
201
+ ]),
202
+ ok([]),
203
+ ok([])
204
+ ]);
205
+ const review = await service.LoadSessionReview('SESSION-1', provider);
206
+ expect(review?.Turns.map(t => t.Text)).toEqual(['visible']);
207
+ });
208
+ });
209
+ describe('LoadSessionReview — delegated-run mapping', () => {
210
+ it("EXCLUDES the co-agent observability run named in the session Config's coAgentRunID", async () => {
211
+ mockRunViews([
212
+ ok([sessionRow()]),
213
+ ok([]),
214
+ ok([runRow('RUN-CO'), runRow('RUN-1'), runRow('RUN-2')]),
215
+ ok([])
216
+ ]);
217
+ const review = await service.LoadSessionReview('SESSION-1', provider);
218
+ expect(review?.DelegatedRuns.map(r => r.RunID)).toEqual(['RUN-1', 'RUN-2']);
219
+ });
220
+ it('matches the co-agent run id case-insensitively (SQL Server vs PostgreSQL casing)', async () => {
221
+ mockRunViews([
222
+ ok([sessionRow({ Config: JSON.stringify({ coAgentRunID: 'run-co' }) })]),
223
+ ok([]),
224
+ ok([runRow('RUN-CO'), runRow('RUN-1')]),
225
+ ok([])
226
+ ]);
227
+ const review = await service.LoadSessionReview('SESSION-1', provider);
228
+ expect(review?.DelegatedRuns.map(r => r.RunID)).toEqual(['RUN-1']);
229
+ });
230
+ it('maps the run preview fields (status, success, messages, timing)', async () => {
231
+ mockRunViews([
232
+ ok([sessionRow()]),
233
+ ok([]),
234
+ ok([runRow('RUN-1', { Status: 'Failed', Success: false, Message: null, ErrorMessage: 'it failed', FinalStep: 'Failed' })]),
235
+ ok([])
236
+ ]);
237
+ const review = await service.LoadSessionReview('SESSION-1', provider);
238
+ const run = review?.DelegatedRuns[0];
239
+ expect(run).toMatchObject({
240
+ RunID: 'RUN-1',
241
+ AgentName: 'Sage',
242
+ Status: 'Failed',
243
+ Success: false,
244
+ ErrorMessage: 'it failed',
245
+ FinalStep: 'Failed'
246
+ });
247
+ expect(run?.StartedAt?.toISOString()).toBe('2026-06-10T10:05:00.000Z');
248
+ expect(run?.CompletedAt?.toISOString()).toBe('2026-06-10T10:06:00.000Z');
249
+ });
250
+ });
251
+ describe('LoadSessionReview — channel-state extraction', () => {
252
+ it('maps named channel rows to {ChannelName, StateJson} and skips unnamed rows', async () => {
253
+ mockRunViews([
254
+ ok([sessionRow()]),
255
+ ok([]),
256
+ ok([]),
257
+ ok([
258
+ channelRow('Whiteboard', '{"items":[]}'),
259
+ channelRow('Voice', null),
260
+ channelRow(null, '{"orphan":true}')
261
+ ])
262
+ ]);
263
+ const review = await service.LoadSessionReview('SESSION-1', provider);
264
+ expect(review?.ChannelStates).toEqual([
265
+ { ChannelName: 'Whiteboard', StateJson: '{"items":[]}' },
266
+ { ChannelName: 'Voice', StateJson: null }
267
+ ]);
268
+ });
269
+ });
270
+ describe('LoadSessionReview — session-chain walk (lastSessionId legs)', () => {
271
+ it('surfaces a SINGLE leg (no extra queries) when the session has no LastSessionID', async () => {
272
+ mockRunViews([ok([sessionRow()]), ok([]), ok([]), ok([])]);
273
+ const review = await service.LoadSessionReview('SESSION-1', provider);
274
+ expect(review?.Legs).toHaveLength(1);
275
+ expect(review?.Legs[0].SessionID).toBe('SESSION-1');
276
+ expect(capturedCalls).toHaveLength(1);
277
+ });
278
+ it('walks the chain BACKWARDS and orders legs (and flattened turns) chronologically', async () => {
279
+ mockRunViewsSequence([
280
+ // Call 1: the reviewed (newest) leg — points back at SESSION-A.
281
+ [
282
+ ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'SESSION-A', __mj_CreatedAt: '2026-06-10T11:00:00Z' })]),
283
+ ok([detailRow('User', 'second leg turn', '2026-06-10T11:01:00Z')]),
284
+ ok([]),
285
+ ok([])
286
+ ],
287
+ // Call 2: the PRIOR leg.
288
+ [
289
+ ok([sessionRow({ ID: 'SESSION-A', CloseReason: 'Janitor', __mj_CreatedAt: '2026-06-10T10:00:00Z' })]),
290
+ ok([detailRow('User', 'first leg turn', '2026-06-10T10:01:00Z')]),
291
+ ok([])
292
+ ],
293
+ // Call 3: artifact junction query — none.
294
+ [ok([])]
295
+ ]);
296
+ const review = await service.LoadSessionReview('SESSION-B', provider);
297
+ expect(review?.Legs.map(l => l.SessionID)).toEqual(['SESSION-A', 'SESSION-B']);
298
+ expect(review?.Legs[0].CloseReason).toBe('Janitor');
299
+ expect(review?.Turns.map(t => t.Text)).toEqual(['first leg turn', 'second leg turn']);
300
+ // Prior-leg query carries NO channel query (channel state is latest-leg-only).
301
+ expect(capturedCalls[1].map(p => p.EntityName)).toEqual([
302
+ 'MJ: AI Agent Sessions',
303
+ 'MJ: Conversation Details',
304
+ 'MJ: AI Agent Runs'
305
+ ]);
306
+ });
307
+ it('excludes each leg\'s OWN co-agent observability run (per-leg Config)', async () => {
308
+ mockRunViewsSequence([
309
+ [
310
+ ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'SESSION-A', Config: JSON.stringify({ coAgentRunID: 'RUN-CO-B' }) })]),
311
+ ok([]),
312
+ ok([runRow('RUN-CO-B'), runRow('RUN-B1')]),
313
+ ok([])
314
+ ],
315
+ [
316
+ ok([sessionRow({ ID: 'SESSION-A', Config: JSON.stringify({ coAgentRunID: 'RUN-CO-A' }) })]),
317
+ ok([]),
318
+ ok([runRow('RUN-CO-A'), runRow('RUN-A1')])
319
+ ]
320
+ ]);
321
+ const review = await service.LoadSessionReview('SESSION-B', provider);
322
+ expect(review?.DelegatedRuns.map(r => r.RunID)).toEqual(['RUN-A1', 'RUN-B1']);
323
+ });
324
+ it(`caps the walk at ${MAX_REVIEW_LEGS} legs even when the chain is longer`, async () => {
325
+ // Every leg load returns a session that points at yet another prior session.
326
+ let n = 0;
327
+ const legResult = () => {
328
+ n++;
329
+ return [
330
+ ok([sessionRow({ ID: `SESSION-${n}`, LastSessionID: `SESSION-${n + 1}` })]),
331
+ ok([]),
332
+ ok([]),
333
+ ok([])
334
+ ];
335
+ };
336
+ mockRunViewsSequence(Array.from({ length: 10 }, legResult));
337
+ const review = await service.LoadSessionReview('SESSION-1', provider);
338
+ expect(review?.Legs).toHaveLength(MAX_REVIEW_LEGS);
339
+ });
340
+ it('NEVER loops on a cyclic chain (A→B→A) — the cycle guard stops the walk', async () => {
341
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
342
+ mockRunViewsSequence([
343
+ [ok([sessionRow({ ID: 'SESSION-A', LastSessionID: 'SESSION-B' })]), ok([]), ok([]), ok([])],
344
+ // SESSION-B points BACK at SESSION-A (already visited).
345
+ [ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'SESSION-A' })]), ok([]), ok([])]
346
+ ]);
347
+ const review = await service.LoadSessionReview('SESSION-A', provider);
348
+ expect(review?.Legs.map(l => l.SessionID)).toEqual(['SESSION-B', 'SESSION-A']);
349
+ expect(warnSpy).toHaveBeenCalled();
350
+ });
351
+ it('matches the cycle guard case-insensitively (SQL Server vs PostgreSQL casing)', async () => {
352
+ mockRunViewsSequence([
353
+ [ok([sessionRow({ ID: 'SESSION-A', LastSessionID: 'SESSION-B' })]), ok([]), ok([]), ok([])],
354
+ [ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'session-a' })]), ok([]), ok([])]
355
+ ]);
356
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
357
+ const review = await service.LoadSessionReview('SESSION-A', provider);
358
+ expect(review?.Legs).toHaveLength(2);
359
+ expect(warnSpy).toHaveBeenCalled();
360
+ });
361
+ it('ends the walk (review still loads) when a prior leg cannot be found', async () => {
362
+ mockRunViewsSequence([
363
+ [ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'SESSION-GONE' })]), ok([]), ok([]), ok([])],
364
+ [ok([]), ok([]), ok([])] // prior session row missing
365
+ ]);
366
+ const review = await service.LoadSessionReview('SESSION-B', provider);
367
+ expect(review?.Legs.map(l => l.SessionID)).toEqual(['SESSION-B']);
368
+ });
369
+ it('trims an older leg to the NEWEST rows when the 500-detail budget runs out, and stops walking', async () => {
370
+ const manyDetails = Array.from({ length: 498 }, (_, i) => detailRow('User', `turn ${i}`, `2026-06-10T11:00:${String(i % 60).padStart(2, '0')}Z`));
371
+ mockRunViewsSequence([
372
+ [ok([sessionRow({ ID: 'SESSION-C', LastSessionID: 'SESSION-B' })]), ok(manyDetails), ok([]), ok([])],
373
+ [
374
+ ok([sessionRow({ ID: 'SESSION-B', LastSessionID: 'SESSION-A' })]),
375
+ ok([
376
+ detailRow('User', 'oldest — trimmed', '2026-06-10T10:01:00Z'),
377
+ detailRow('User', 'kept 1', '2026-06-10T10:02:00Z'),
378
+ detailRow('User', 'kept 2', '2026-06-10T10:03:00Z')
379
+ ]),
380
+ ok([])
381
+ ],
382
+ // Artifact junction query (NOT another leg — the walk must have stopped).
383
+ [ok([])]
384
+ ]);
385
+ const review = await service.LoadSessionReview('SESSION-C', provider);
386
+ expect(review?.Legs.map(l => l.SessionID)).toEqual(['SESSION-B', 'SESSION-C']);
387
+ expect(review?.Legs[0].Turns.map(t => t.Text)).toEqual(['kept 1', 'kept 2']);
388
+ expect(review?.Turns).toHaveLength(500);
389
+ // No third LEG query was issued: call 3 is the artifact junction query.
390
+ expect(capturedCalls[2][0].EntityName).toBe('MJ: Conversation Detail Artifacts');
391
+ });
392
+ });
393
+ describe('LoadSessionReview — artifact collection', () => {
394
+ function junctionRow(versionId, detailId = 'D-1') {
395
+ return { ID: `J-${versionId ?? 'x'}`, ConversationDetailID: detailId, ArtifactVersionID: versionId };
396
+ }
397
+ function versionRow(id, artifactId, name, artifactName = 'Parent Artifact') {
398
+ return { ID: id, ArtifactID: artifactId, Name: name, Artifact: artifactName };
399
+ }
400
+ it('collects {ArtifactID, ArtifactVersionID, Name} through the junction → version queries', async () => {
401
+ mockRunViewsSequence([
402
+ [ok([sessionRow()]), ok([detailRow('User', 'hi', '2026-06-10T10:01:00Z')]), ok([]), ok([])],
403
+ [ok([junctionRow('AV-1'), junctionRow('AV-2')])],
404
+ [ok([versionRow('AV-1', 'A-1', 'Quarterly Report'), versionRow('AV-2', 'A-2', null, 'Member Chart')])]
405
+ ]);
406
+ const review = await service.LoadSessionReview('SESSION-1', provider);
407
+ expect(review?.Artifacts).toEqual([
408
+ { ArtifactID: 'A-1', ArtifactVersionID: 'AV-1', Name: 'Quarterly Report' },
409
+ { ArtifactID: 'A-2', ArtifactVersionID: 'AV-2', Name: 'Member Chart' } // version Name null → artifact name
410
+ ]);
411
+ // Junction query filters the chain's detail ids + Output direction.
412
+ const junctionParams = capturedCalls[1][0];
413
+ expect(junctionParams.EntityName).toBe('MJ: Conversation Detail Artifacts');
414
+ expect(junctionParams.ExtraFilter).toContain("ConversationDetailID IN ('D-2026-06-10T10:01:00Z')");
415
+ expect(junctionParams.ExtraFilter).toContain("Direction='Output'");
416
+ // Version query loads the junctioned version ids.
417
+ const versionParams = capturedCalls[2][0];
418
+ expect(versionParams.EntityName).toBe('MJ: Artifact Versions');
419
+ expect(versionParams.ExtraFilter).toBe("ID IN ('AV-1','AV-2')");
420
+ });
421
+ it('dedupes duplicate versions, skips null version ids and unresolvable versions', async () => {
422
+ mockRunViewsSequence([
423
+ [ok([sessionRow()]), ok([detailRow('User', 'hi', '2026-06-10T10:01:00Z')]), ok([]), ok([])],
424
+ [ok([junctionRow('AV-1'), junctionRow('AV-1'), junctionRow(null), junctionRow('AV-GONE')])],
425
+ [ok([versionRow('AV-1', 'A-1', 'Report')])]
426
+ ]);
427
+ const review = await service.LoadSessionReview('SESSION-1', provider);
428
+ expect(review?.Artifacts).toEqual([{ ArtifactID: 'A-1', ArtifactVersionID: 'AV-1', Name: 'Report' }]);
429
+ });
430
+ it('issues NO artifact queries when the chain has no caption details', async () => {
431
+ mockRunViews([ok([sessionRow()]), ok([]), ok([]), ok([])]);
432
+ const review = await service.LoadSessionReview('SESSION-1', provider);
433
+ expect(review?.Artifacts).toEqual([]);
434
+ expect(capturedCalls).toHaveLength(1);
435
+ });
436
+ it('degrades a failed junction query to an empty Artifacts list (review still loads)', async () => {
437
+ mockRunViewsSequence([
438
+ [ok([sessionRow()]), ok([detailRow('User', 'hi', '2026-06-10T10:01:00Z')]), ok([]), ok([])],
439
+ [failed()]
440
+ ]);
441
+ const review = await service.LoadSessionReview('SESSION-1', provider);
442
+ expect(review).not.toBeNull();
443
+ expect(review?.Artifacts).toEqual([]);
444
+ });
445
+ });
446
+ });
447
+ // ── Pure review → thread-item mapping ────────────────────────────────────────
448
+ function reviewFixture(overrides = {}) {
449
+ return {
450
+ SessionID: 'SESSION-1',
451
+ AgentID: 'AGENT-CO',
452
+ AgentName: 'Skye',
453
+ TargetAgentID: 'AGENT-TARGET',
454
+ ConversationID: 'CONV-1',
455
+ Status: 'Closed',
456
+ CloseReason: 'Explicit',
457
+ StartedAt: new Date('2026-06-10T10:00:00Z'),
458
+ LastActiveAt: new Date('2026-06-10T10:30:00Z'),
459
+ ClosedAt: new Date('2026-06-10T10:31:00Z'),
460
+ Turns: [],
461
+ DelegatedRuns: [],
462
+ ChannelStates: [],
463
+ Legs: [],
464
+ Artifacts: [],
465
+ ...overrides
466
+ };
467
+ }
468
+ function legFixture(overrides = {}) {
469
+ return {
470
+ SessionID: 'SESSION-1',
471
+ StartedAt: new Date('2026-06-10T10:00:00Z'),
472
+ ClosedAt: new Date('2026-06-10T10:31:00Z'),
473
+ CloseReason: 'Explicit',
474
+ Turns: [],
475
+ DelegatedRuns: [],
476
+ ...overrides
477
+ };
478
+ }
479
+ function reviewRun(id, startedAt, overrides = {}) {
480
+ return {
481
+ RunID: id,
482
+ AgentID: 'AGENT-X',
483
+ AgentName: 'Sage',
484
+ Status: 'Completed',
485
+ Success: true,
486
+ Message: 'done',
487
+ ErrorMessage: null,
488
+ FinalStep: 'Success',
489
+ StartedAt: startedAt ? new Date(startedAt) : null,
490
+ CompletedAt: startedAt ? new Date(new Date(startedAt).getTime() + 60_000) : null,
491
+ ...overrides
492
+ };
493
+ }
494
+ describe('BuildReviewThreadItems', () => {
495
+ it('interleaves turns and delegation cards chronologically (oldest first)', () => {
496
+ const review = reviewFixture({
497
+ Turns: [
498
+ { Role: 'User', Text: 'first', At: new Date('2026-06-10T10:01:00Z') },
499
+ { Role: 'Assistant', Text: 'third', At: new Date('2026-06-10T10:06:00Z') }
500
+ ],
501
+ DelegatedRuns: [reviewRun('RUN-1', '2026-06-10T10:03:00Z')]
502
+ });
503
+ const items = BuildReviewThreadItems(review);
504
+ expect(items.map(i => i.Kind)).toEqual(['caption', 'delegation', 'caption']);
505
+ expect(items[0]).toMatchObject({ Kind: 'caption', Role: 'User', Text: 'first' });
506
+ expect(items[2]).toMatchObject({ Kind: 'caption', Role: 'Assistant', Text: 'third' });
507
+ });
508
+ it('keeps build order for entries without timestamps (stable sort to the front)', () => {
509
+ const review = reviewFixture({
510
+ Turns: [
511
+ { Role: 'User', Text: 'a', At: null },
512
+ { Role: 'Assistant', Text: 'b', At: null }
513
+ ],
514
+ DelegatedRuns: [reviewRun('RUN-1', '2026-06-10T10:03:00Z')]
515
+ });
516
+ const items = BuildReviewThreadItems(review);
517
+ expect(items.map(i => (i.Kind === 'caption' ? i.Text : i.Kind === 'delegation' ? i.Card.CallID : i.Label))).toEqual(['a', 'b', 'RUN-1']);
518
+ });
519
+ it('names cards after the run agent, falling back to the review agent', () => {
520
+ const review = reviewFixture({
521
+ DelegatedRuns: [
522
+ reviewRun('RUN-1', '2026-06-10T10:03:00Z'),
523
+ reviewRun('RUN-2', '2026-06-10T10:04:00Z', { AgentName: null })
524
+ ]
525
+ });
526
+ const items = BuildReviewThreadItems(review);
527
+ const names = items.map(i => (i.Kind === 'delegation' ? i.Card.AgentName : ''));
528
+ expect(names).toEqual(['Sage', 'Skye']);
529
+ });
530
+ it('renders a SINGLE leg with no divider (identical to the flat thread)', () => {
531
+ const review = reviewFixture({
532
+ Legs: [
533
+ legFixture({
534
+ Turns: [{ Role: 'User', Text: 'hello', At: new Date('2026-06-10T10:01:00Z') }],
535
+ DelegatedRuns: [reviewRun('RUN-1', '2026-06-10T10:03:00Z')]
536
+ })
537
+ ]
538
+ });
539
+ const items = BuildReviewThreadItems(review);
540
+ expect(items.map(i => i.Kind)).toEqual(['caption', 'delegation']);
541
+ });
542
+ it('renders a DIVIDER between legs, stamped with the new leg start + the PREVIOUS leg\'s CloseReason', () => {
543
+ const review = reviewFixture({
544
+ Legs: [
545
+ legFixture({
546
+ SessionID: 'SESSION-A',
547
+ CloseReason: 'Janitor',
548
+ Turns: [{ Role: 'User', Text: 'leg one', At: new Date('2026-06-10T10:01:00Z') }]
549
+ }),
550
+ legFixture({
551
+ SessionID: 'SESSION-B',
552
+ StartedAt: new Date('2026-06-10T11:00:00Z'),
553
+ CloseReason: 'Explicit',
554
+ Turns: [{ Role: 'Assistant', Text: 'leg two', At: new Date('2026-06-10T11:01:00Z') }]
555
+ })
556
+ ]
557
+ });
558
+ const items = BuildReviewThreadItems(review);
559
+ expect(items.map(i => i.Kind)).toEqual(['caption', 'divider', 'caption']);
560
+ const divider = items[1];
561
+ if (divider.Kind !== 'divider') {
562
+ throw new Error('expected a divider');
563
+ }
564
+ expect(divider.Label).toBe('Session leg started');
565
+ expect(divider.At?.toISOString()).toBe('2026-06-10T11:00:00.000Z');
566
+ expect(divider.CloseReason).toBe('Janitor'); // why the PREVIOUS leg ended
567
+ expect(divider.Icon).toContain('fa-');
568
+ });
569
+ it('keeps leg boundaries even when item timestamps interleave across legs', () => {
570
+ // Leg B starts BEFORE leg A's last item timestamp — the divider must still sit between legs.
571
+ const review = reviewFixture({
572
+ Legs: [
573
+ legFixture({ Turns: [{ Role: 'User', Text: 'a-late', At: new Date('2026-06-10T12:00:00Z') }] }),
574
+ legFixture({
575
+ StartedAt: new Date('2026-06-10T11:00:00Z'),
576
+ Turns: [{ Role: 'User', Text: 'b-early', At: new Date('2026-06-10T11:30:00Z') }]
577
+ })
578
+ ]
579
+ });
580
+ const texts = BuildReviewThreadItems(review).map(i => (i.Kind === 'caption' ? i.Text : i.Kind));
581
+ expect(texts).toEqual(['a-late', 'divider', 'b-early']);
582
+ });
583
+ });
584
+ // ── Review→live continuation (StartLiveContinuation) ────────────────────────
585
+ describe('RealtimeSessionState.StartLiveContinuation', () => {
586
+ function loadedState() {
587
+ const state = new RealtimeSessionState();
588
+ state.LoadHistoricalItems(BuildReviewThreadItems(reviewFixture({
589
+ Turns: [{ Role: 'User', Text: 'historical', At: new Date('2026-06-10T10:01:00Z') }],
590
+ DelegatedRuns: [reviewRun('RUN-1', '2026-06-10T10:03:00Z')]
591
+ })));
592
+ return state;
593
+ }
594
+ it('KEEPS the historical thread and appends the "Resumed live session" divider', () => {
595
+ const state = loadedState();
596
+ let changes = 0;
597
+ state.Changed$.subscribe(() => changes++);
598
+ state.StartLiveContinuation(new Date('2026-06-10T12:00:00Z'));
599
+ expect(changes).toBe(1);
600
+ expect(state.Items.map(i => i.Kind)).toEqual(['caption', 'delegation', 'divider']);
601
+ const divider = state.Items[2];
602
+ if (divider.Kind !== 'divider') {
603
+ throw new Error('expected a divider');
604
+ }
605
+ expect(divider.Label).toBe('Resumed live session');
606
+ expect(divider.At?.toISOString()).toBe('2026-06-10T12:00:00.000Z');
607
+ expect(divider.CloseReason).toBeUndefined();
608
+ });
609
+ it('keeps the historical delegation cards in the rail (carryover, not a reset)', () => {
610
+ const state = loadedState();
611
+ state.StartLiveContinuation();
612
+ expect(state.Cards.map(c => c.CallID)).toEqual(['RUN-1']);
613
+ expect(state.ActiveCallId).toBeNull();
614
+ });
615
+ it('resets the caption bookkeeping so the NEW session\'s captions append AFTER the divider', () => {
616
+ const state = loadedState();
617
+ const captions$ = new Subject();
618
+ state.Attach({
619
+ Captions$: captions$.asObservable(),
620
+ DelegationProgress$: new Subject().asObservable(),
621
+ DelegationResult$: new Subject().asObservable(),
622
+ DelegationNarration$: new Subject().asObservable()
623
+ });
624
+ state.StartLiveContinuation();
625
+ // The fresh session's caption stream starts EMPTY — must be a no-op, not a reset.
626
+ captions$.next([]);
627
+ expect(state.Items).toHaveLength(3);
628
+ captions$.next([{ Role: 'User', Text: 'live turn' }]);
629
+ expect(state.Items.map(i => i.Kind)).toEqual(['caption', 'delegation', 'divider', 'caption']);
630
+ const live = state.Items[3];
631
+ expect(live.Kind === 'caption' && live.Text).toBe('live turn');
632
+ });
633
+ it('drops any lingering narration (nothing is running yet in the new leg)', () => {
634
+ const state = loadedState();
635
+ state.Narration = 'old note';
636
+ state.StartLiveContinuation();
637
+ expect(state.Narration).toBeNull();
638
+ });
639
+ });
640
+ describe('BuildReviewDelegationCard', () => {
641
+ it('builds a DONE card carrying the run id, ref, result, and timing', () => {
642
+ const card = BuildReviewDelegationCard(reviewRun('RUN-abc1', '2026-06-10T10:03:00Z'), 'Skye');
643
+ expect(card.Done).toBe(true);
644
+ expect(card.Success).toBe(true);
645
+ expect(card.CallID).toBe('RUN-abc1');
646
+ expect(card.RunID).toBe('RUN-abc1');
647
+ expect(card.RunRef).toBe('#abc1');
648
+ expect(card.Result).toBe('done');
649
+ expect(card.StartedAt).toBe(new Date('2026-06-10T10:03:00Z').getTime());
650
+ expect(card.FinishedAt).toBe(new Date('2026-06-10T10:04:00Z').getTime());
651
+ });
652
+ it('prefers the run Message, then ErrorMessage, then Status for the card text', () => {
653
+ const failedCard = BuildReviewDelegationCard(reviewRun('RUN-1', '2026-06-10T10:03:00Z', { Success: false, Message: null, ErrorMessage: 'exploded' }), 'Skye');
654
+ expect(failedCard.Success).toBe(false);
655
+ expect(failedCard.LatestMessage).toBe('exploded');
656
+ expect(failedCard.Result).toBe('exploded');
657
+ const bareCard = BuildReviewDelegationCard(reviewRun('RUN-2', null, { Message: null, ErrorMessage: null, FinalStep: null, Status: 'Cancelled' }), 'Skye');
658
+ expect(bareCard.LatestMessage).toBe('Cancelled');
659
+ expect(bareCard.Result).toBeNull();
660
+ expect(bareCard.StartedAt).toBe(0);
661
+ });
662
+ });
663
+ // ── RealtimeSessionState historical population ───────────────────────────────
664
+ describe('RealtimeSessionState.LoadHistoricalItems', () => {
665
+ let state;
666
+ beforeEach(() => {
667
+ state = new RealtimeSessionState();
668
+ });
669
+ function historicalItems() {
670
+ return BuildReviewThreadItems(reviewFixture({
671
+ Turns: [{ Role: 'User', Text: 'hello', At: new Date('2026-06-10T10:01:00Z') }],
672
+ DelegatedRuns: [
673
+ reviewRun('RUN-1', '2026-06-10T10:03:00Z'),
674
+ reviewRun('RUN-2', '2026-06-10T10:05:00Z')
675
+ ]
676
+ }));
677
+ }
678
+ it('replaces the thread with the historical items and emits Changed$', () => {
679
+ let changes = 0;
680
+ state.Changed$.subscribe(() => changes++);
681
+ state.LoadHistoricalItems(historicalItems());
682
+ expect(changes).toBe(1);
683
+ expect(state.Items.map(i => i.Kind)).toEqual(['caption', 'delegation', 'delegation']);
684
+ });
685
+ it('derives the rail Cards NEWEST first and no active call (all cards done)', () => {
686
+ state.LoadHistoricalItems(historicalItems());
687
+ expect(state.Cards.map(c => c.CallID)).toEqual(['RUN-2', 'RUN-1']);
688
+ expect(state.ActiveCallId).toBeNull();
689
+ expect(state.HasRunningDelegation).toBe(false);
690
+ });
691
+ it('re-populating replaces (does not append to) previous historical state', () => {
692
+ state.LoadHistoricalItems(historicalItems());
693
+ state.LoadHistoricalItems([{ Kind: 'caption', Role: 'User', Text: 'only this' }]);
694
+ expect(state.Items).toHaveLength(1);
695
+ expect(state.Cards).toHaveLength(0);
696
+ });
697
+ it('Clear() empties the thread, cards, and narration, and emits Changed$', () => {
698
+ state.LoadHistoricalItems(historicalItems());
699
+ let changes = 0;
700
+ state.Changed$.subscribe(() => changes++);
701
+ state.Clear();
702
+ expect(changes).toBe(1);
703
+ expect(state.Items).toEqual([]);
704
+ expect(state.Cards).toEqual([]);
705
+ expect(state.Narration).toBeNull();
706
+ expect(state.ActiveCallId).toBeNull();
707
+ });
708
+ });
709
+ describe('MergeChainChannelStates (multi-leg board restore)', () => {
710
+ const wb = (json) => ({ ChannelName: 'Whiteboard', StateJson: json });
711
+ it('an earlier leg\'s SAVED board survives a final leg that never saved one', () => {
712
+ const merged = MergeChainChannelStates([
713
+ [wb('{"version":2,"pages":[]}')], // leg 1 drew
714
+ [wb(null)] // leg 2 (newest) never touched the board
715
+ ]);
716
+ expect(merged).toEqual([wb('{"version":2,"pages":[]}')]);
717
+ });
718
+ it('the NEWEST leg with a saved state wins per channel', () => {
719
+ const merged = MergeChainChannelStates([
720
+ [wb('{"old":true}')],
721
+ [wb('{"new":true}')],
722
+ [wb('')] // newest, empty — registers nothing
723
+ ]);
724
+ expect(merged).toEqual([wb('{"new":true}')]);
725
+ });
726
+ it('channel names dedupe case-insensitively and other channels merge independently', () => {
727
+ const merged = MergeChainChannelStates([
728
+ [{ ChannelName: 'whiteboard', StateJson: '{"a":1}' }, { ChannelName: 'Notes', StateJson: null }],
729
+ [{ ChannelName: 'Whiteboard', StateJson: null }, { ChannelName: 'Notes', StateJson: '{"n":1}' }]
730
+ ]);
731
+ expect(merged).toHaveLength(2);
732
+ expect(merged.find(s => s.ChannelName.toLowerCase() === 'whiteboard')?.StateJson).toBe('{"a":1}');
733
+ expect(merged.find(s => s.ChannelName === 'Notes')?.StateJson).toBe('{"n":1}');
734
+ });
735
+ it('channels no leg ever saved keep a null-state entry (existence still reviewable)', () => {
736
+ expect(MergeChainChannelStates([[wb(null)], [wb(null)]])).toEqual([wb(null)]);
737
+ });
738
+ it('tolerates empty chains and blank channel names', () => {
739
+ expect(MergeChainChannelStates([])).toEqual([]);
740
+ expect(MergeChainChannelStates([[{ ChannelName: ' ', StateJson: '{"x":1}' }]])).toEqual([]);
741
+ });
742
+ });
743
+ //# sourceMappingURL=realtime-session-review.service.test.js.map