@memberjunction/ng-conversations 5.43.0 → 5.45.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 (405) hide show
  1. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +0 -1
  2. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
  3. package/dist/lib/components/active-tasks/active-tasks-panel.component.js +56 -52
  4. package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
  5. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts +3 -1
  6. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts.map +1 -1
  7. package/dist/lib/components/artifact/artifact-share-modal.component.js +52 -52
  8. package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
  9. package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
  10. package/dist/lib/components/collection/collection-form-modal.component.js +10 -14
  11. package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
  12. package/dist/lib/components/collection/collection-share-modal.component.d.ts +3 -1
  13. package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -1
  14. package/dist/lib/components/collection/collection-share-modal.component.js +56 -56
  15. package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
  16. package/dist/lib/components/collection/collection-tree.component.d.ts +3 -1
  17. package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
  18. package/dist/lib/components/collection/collection-tree.component.js +10 -7
  19. package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
  20. package/dist/lib/components/collection/collection-view.component.d.ts +3 -1
  21. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  22. package/dist/lib/components/collection/collection-view.component.js +29 -41
  23. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  24. package/dist/lib/components/collection/collections-full-view.component.d.ts +4 -0
  25. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  26. package/dist/lib/components/collection/collections-full-view.component.js +134 -166
  27. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  28. package/dist/lib/components/composer/ai-composer.component.d.ts +103 -0
  29. package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -0
  30. package/dist/lib/components/composer/ai-composer.component.js +263 -0
  31. package/dist/lib/components/composer/ai-composer.component.js.map +1 -0
  32. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +76 -12
  33. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  34. package/dist/lib/components/conversation/conversation-chat-area.component.js +522 -158
  35. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  36. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +1 -1
  37. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
  38. package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
  39. package/dist/lib/components/conversation/pinned-messages-panel.component.js +6 -9
  40. package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
  41. package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
  42. package/dist/lib/components/library/library-full-view.component.js +20 -24
  43. package/dist/lib/components/library/library-full-view.component.js.map +1 -1
  44. package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
  45. package/dist/lib/components/members/members-modal.component.js +10 -11
  46. package/dist/lib/components/members/members-modal.component.js.map +1 -1
  47. package/dist/lib/components/message/message-input.component.d.ts +87 -23
  48. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  49. package/dist/lib/components/message/message-input.component.js +245 -158
  50. package/dist/lib/components/message/message-input.component.js.map +1 -1
  51. package/dist/lib/components/message/message-item.component.d.ts +11 -3
  52. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  53. package/dist/lib/components/message/message-item.component.js +73 -21
  54. package/dist/lib/components/message/message-item.component.js.map +1 -1
  55. package/dist/lib/components/message/message-list.component.js +6 -9
  56. package/dist/lib/components/message/message-list.component.js.map +1 -1
  57. package/dist/lib/components/overlay/chat-overlay.component.d.ts +4 -1
  58. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  59. package/dist/lib/components/overlay/chat-overlay.component.js +9 -4
  60. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  61. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +45 -0
  62. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
  63. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +10 -0
  64. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
  65. package/dist/lib/components/realtime/channels/client-context-channel.d.ts +46 -0
  66. package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -0
  67. package/dist/lib/components/realtime/channels/client-context-channel.js +174 -0
  68. package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -0
  69. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts +123 -0
  70. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts.map +1 -0
  71. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +397 -0
  72. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -0
  73. package/dist/lib/components/realtime/media/media-channel-state.d.ts +103 -0
  74. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -0
  75. package/dist/lib/components/realtime/media/media-channel-state.js +143 -0
  76. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -0
  77. package/dist/lib/components/realtime/media/media-channel-tools.d.ts +24 -0
  78. package/dist/lib/components/realtime/media/media-channel-tools.d.ts.map +1 -0
  79. package/dist/lib/components/realtime/media/media-channel-tools.js +107 -0
  80. package/dist/lib/components/realtime/media/media-channel-tools.js.map +1 -0
  81. package/dist/lib/components/realtime/media/media-channel.d.ts +89 -0
  82. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -0
  83. package/dist/lib/components/realtime/media/media-channel.js +253 -0
  84. package/dist/lib/components/realtime/media/media-channel.js.map +1 -0
  85. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +141 -0
  86. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -0
  87. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +574 -0
  88. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -0
  89. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +59 -14
  90. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
  91. package/dist/lib/components/realtime/realtime-activity-rail.component.js +277 -74
  92. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
  93. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +28 -27
  94. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  95. package/dist/lib/components/realtime/realtime-agent-banner.component.js +144 -143
  96. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  97. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -0
  98. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  99. package/dist/lib/components/realtime/realtime-agent-picker.component.js +105 -43
  100. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  101. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +2 -2
  102. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
  103. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
  104. package/dist/lib/components/realtime/realtime-composer.component.d.ts +41 -7
  105. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  106. package/dist/lib/components/realtime/realtime-composer.component.js +216 -102
  107. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  108. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +0 -2
  109. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
  110. package/dist/lib/components/realtime/realtime-delegation-card.component.js +67 -71
  111. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
  112. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +364 -44
  113. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  114. package/dist/lib/components/realtime/realtime-session-overlay.component.js +839 -153
  115. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  116. package/dist/lib/components/realtime/realtime-session-state.d.ts +5 -5
  117. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
  118. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
  119. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts +70 -0
  120. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts.map +1 -0
  121. package/dist/lib/components/realtime/realtime-surface-tab-style.js +115 -0
  122. package/dist/lib/components/realtime/realtime-surface-tab-style.js.map +1 -0
  123. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +55 -57
  124. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
  125. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +165 -143
  126. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
  127. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +80 -71
  128. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
  129. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +83 -113
  130. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
  131. package/dist/lib/components/realtime/realtime-ui-config.d.ts +270 -0
  132. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -0
  133. package/dist/lib/components/realtime/realtime-ui-config.js +212 -0
  134. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -0
  135. package/dist/lib/components/search/search-panel.component.d.ts +2 -0
  136. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  137. package/dist/lib/components/search/search-panel.component.js +92 -101
  138. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  139. package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
  140. package/dist/lib/components/share/share-modal.component.js +10 -11
  141. package/dist/lib/components/share/share-modal.component.js.map +1 -1
  142. package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
  143. package/dist/lib/components/shared/user-picker.component.js +12 -15
  144. package/dist/lib/components/shared/user-picker.component.js.map +1 -1
  145. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts +11 -3
  146. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts.map +1 -1
  147. package/dist/lib/components/sidebar/conversation-sidebar.component.js +34 -10
  148. package/dist/lib/components/sidebar/conversation-sidebar.component.js.map +1 -1
  149. package/dist/lib/components/sidebar/routines-section.component.d.ts +73 -0
  150. package/dist/lib/components/sidebar/routines-section.component.d.ts.map +1 -0
  151. package/dist/lib/components/sidebar/routines-section.component.js +211 -0
  152. package/dist/lib/components/sidebar/routines-section.component.js.map +1 -0
  153. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +12 -0
  154. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
  155. package/dist/lib/components/tasks/tasks-dropdown.component.js +37 -1
  156. package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
  157. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +16 -5
  158. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  159. package/dist/lib/components/workspace/conversation-workspace.component.js +51 -31
  160. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  161. package/dist/lib/composer-plugins/agent-mention.provider.d.ts +16 -0
  162. package/dist/lib/composer-plugins/agent-mention.provider.d.ts.map +1 -0
  163. package/dist/lib/composer-plugins/agent-mention.provider.js +28 -0
  164. package/dist/lib/composer-plugins/agent-mention.provider.js.map +1 -0
  165. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts +20 -0
  166. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts.map +1 -0
  167. package/dist/lib/composer-plugins/base-conversation-mention.provider.js +32 -0
  168. package/dist/lib/composer-plugins/base-conversation-mention.provider.js.map +1 -0
  169. package/dist/lib/composer-plugins/load-composer-plugins.d.ts +9 -0
  170. package/dist/lib/composer-plugins/load-composer-plugins.d.ts.map +1 -0
  171. package/dist/lib/composer-plugins/load-composer-plugins.js +17 -0
  172. package/dist/lib/composer-plugins/load-composer-plugins.js.map +1 -0
  173. package/dist/lib/composer-plugins/record-mention.provider.d.ts +14 -0
  174. package/dist/lib/composer-plugins/record-mention.provider.d.ts.map +1 -0
  175. package/dist/lib/composer-plugins/record-mention.provider.js +26 -0
  176. package/dist/lib/composer-plugins/record-mention.provider.js.map +1 -0
  177. package/dist/lib/composer-plugins/skill-command.provider.d.ts +15 -0
  178. package/dist/lib/composer-plugins/skill-command.provider.d.ts.map +1 -0
  179. package/dist/lib/composer-plugins/skill-command.provider.js +27 -0
  180. package/dist/lib/composer-plugins/skill-command.provider.js.map +1 -0
  181. package/dist/lib/conversations.module.d.ts +68 -64
  182. package/dist/lib/conversations.module.d.ts.map +1 -1
  183. package/dist/lib/conversations.module.js +69 -18
  184. package/dist/lib/conversations.module.js.map +1 -1
  185. package/dist/lib/models/conversation-state.model.d.ts +6 -3
  186. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  187. package/dist/lib/models/conversation-state.model.js.map +1 -1
  188. package/dist/lib/services/conversation-agent.service.d.ts +9 -2
  189. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  190. package/dist/lib/services/conversation-agent.service.js +26 -2
  191. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  192. package/dist/lib/services/conversation-attachment.service.d.ts +1 -1
  193. package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
  194. package/dist/lib/services/conversation-attachment.service.js.map +1 -1
  195. package/dist/lib/services/mention-autocomplete.service.d.ts +42 -19
  196. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  197. package/dist/lib/services/mention-autocomplete.service.js +113 -18
  198. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  199. package/dist/lib/services/realtime-audio-recorder.d.ts +143 -0
  200. package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -0
  201. package/dist/lib/services/realtime-audio-recorder.js +379 -0
  202. package/dist/lib/services/realtime-audio-recorder.js.map +1 -0
  203. package/dist/lib/services/realtime-pcm-wav.d.ts +75 -0
  204. package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -0
  205. package/dist/lib/services/realtime-pcm-wav.js +205 -0
  206. package/dist/lib/services/realtime-pcm-wav.js.map +1 -0
  207. package/dist/lib/services/realtime-session-review.service.d.ts +28 -1
  208. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  209. package/dist/lib/services/realtime-session-review.service.js +12 -3
  210. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  211. package/dist/lib/services/realtime-session.service.d.ts +210 -20
  212. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  213. package/dist/lib/services/realtime-session.service.js +452 -21
  214. package/dist/lib/services/realtime-session.service.js.map +1 -1
  215. package/dist/lib/services/realtime-sessions-adapter.js +1 -1
  216. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -1
  217. package/dist/lib/services/ui-command-handler.service.d.ts +7 -2
  218. package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
  219. package/dist/lib/services/ui-command-handler.service.js +6 -2
  220. package/dist/lib/services/ui-command-handler.service.js.map +1 -1
  221. package/dist/lib/utils/plan-mode-preference.d.ts +62 -0
  222. package/dist/lib/utils/plan-mode-preference.d.ts.map +1 -0
  223. package/dist/lib/utils/plan-mode-preference.js +130 -0
  224. package/dist/lib/utils/plan-mode-preference.js.map +1 -0
  225. package/dist/public-api.d.ts +29 -3
  226. package/dist/public-api.d.ts.map +1 -1
  227. package/dist/public-api.js +46 -3
  228. package/dist/public-api.js.map +1 -1
  229. package/package.json +30 -26
  230. package/dist/__tests__/attachment-badge.test.d.ts +0 -2
  231. package/dist/__tests__/attachment-badge.test.d.ts.map +0 -1
  232. package/dist/__tests__/attachment-badge.test.js +0 -66
  233. package/dist/__tests__/attachment-badge.test.js.map +0 -1
  234. package/dist/__tests__/channel-optional-surface.test.d.ts +0 -2
  235. package/dist/__tests__/channel-optional-surface.test.d.ts.map +0 -1
  236. package/dist/__tests__/channel-optional-surface.test.js +0 -53
  237. package/dist/__tests__/channel-optional-surface.test.js.map +0 -1
  238. package/dist/__tests__/chat-events.test.d.ts +0 -14
  239. package/dist/__tests__/chat-events.test.d.ts.map +0 -1
  240. package/dist/__tests__/chat-events.test.js +0 -109
  241. package/dist/__tests__/chat-events.test.js.map +0 -1
  242. package/dist/__tests__/collections-full-view.test.d.ts +0 -2
  243. package/dist/__tests__/collections-full-view.test.d.ts.map +0 -1
  244. package/dist/__tests__/collections-full-view.test.js +0 -225
  245. package/dist/__tests__/collections-full-view.test.js.map +0 -1
  246. package/dist/__tests__/conversation-cache.test.d.ts +0 -2
  247. package/dist/__tests__/conversation-cache.test.d.ts.map +0 -1
  248. package/dist/__tests__/conversation-cache.test.js +0 -233
  249. package/dist/__tests__/conversation-cache.test.js.map +0 -1
  250. package/dist/__tests__/conversation-naming.test.d.ts +0 -2
  251. package/dist/__tests__/conversation-naming.test.d.ts.map +0 -1
  252. package/dist/__tests__/conversation-naming.test.js +0 -110
  253. package/dist/__tests__/conversation-naming.test.js.map +0 -1
  254. package/dist/__tests__/delegation-result-parser.test.d.ts +0 -2
  255. package/dist/__tests__/delegation-result-parser.test.d.ts.map +0 -1
  256. package/dist/__tests__/delegation-result-parser.test.js +0 -107
  257. package/dist/__tests__/delegation-result-parser.test.js.map +0 -1
  258. package/dist/__tests__/distinct-artifacts.test.d.ts +0 -2
  259. package/dist/__tests__/distinct-artifacts.test.d.ts.map +0 -1
  260. package/dist/__tests__/distinct-artifacts.test.js +0 -56
  261. package/dist/__tests__/distinct-artifacts.test.js.map +0 -1
  262. package/dist/__tests__/event-wiring.test.d.ts +0 -15
  263. package/dist/__tests__/event-wiring.test.d.ts.map +0 -1
  264. package/dist/__tests__/event-wiring.test.js +0 -100
  265. package/dist/__tests__/event-wiring.test.js.map +0 -1
  266. package/dist/__tests__/index.test.d.ts +0 -2
  267. package/dist/__tests__/index.test.d.ts.map +0 -1
  268. package/dist/__tests__/index.test.js +0 -7
  269. package/dist/__tests__/index.test.js.map +0 -1
  270. package/dist/__tests__/narration-template.test.d.ts +0 -2
  271. package/dist/__tests__/narration-template.test.d.ts.map +0 -1
  272. package/dist/__tests__/narration-template.test.js +0 -76
  273. package/dist/__tests__/narration-template.test.js.map +0 -1
  274. package/dist/__tests__/notification-model.test.d.ts +0 -2
  275. package/dist/__tests__/notification-model.test.d.ts.map +0 -1
  276. package/dist/__tests__/notification-model.test.js +0 -68
  277. package/dist/__tests__/notification-model.test.js.map +0 -1
  278. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +0 -2
  279. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +0 -1
  280. package/dist/__tests__/realtime-agent-picker-models.test.js +0 -49
  281. package/dist/__tests__/realtime-agent-picker-models.test.js.map +0 -1
  282. package/dist/__tests__/realtime-audio-visuals.test.d.ts +0 -2
  283. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +0 -1
  284. package/dist/__tests__/realtime-audio-visuals.test.js +0 -123
  285. package/dist/__tests__/realtime-audio-visuals.test.js.map +0 -1
  286. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +0 -2
  287. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +0 -1
  288. package/dist/__tests__/realtime-delegation-card-cancel.test.js +0 -48
  289. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +0 -1
  290. package/dist/__tests__/realtime-disclosure.test.d.ts +0 -2
  291. package/dist/__tests__/realtime-disclosure.test.d.ts.map +0 -1
  292. package/dist/__tests__/realtime-disclosure.test.js +0 -164
  293. package/dist/__tests__/realtime-disclosure.test.js.map +0 -1
  294. package/dist/__tests__/realtime-pairing.test.d.ts +0 -2
  295. package/dist/__tests__/realtime-pairing.test.d.ts.map +0 -1
  296. package/dist/__tests__/realtime-pairing.test.js +0 -207
  297. package/dist/__tests__/realtime-pairing.test.js.map +0 -1
  298. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +0 -2
  299. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +0 -1
  300. package/dist/__tests__/realtime-review-lifecycle.test.js +0 -154
  301. package/dist/__tests__/realtime-review-lifecycle.test.js.map +0 -1
  302. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +0 -2
  303. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +0 -1
  304. package/dist/__tests__/realtime-session-cancel-usage.test.js +0 -230
  305. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +0 -1
  306. package/dist/__tests__/realtime-session-channels.test.d.ts +0 -2
  307. package/dist/__tests__/realtime-session-channels.test.d.ts.map +0 -1
  308. package/dist/__tests__/realtime-session-channels.test.js +0 -252
  309. package/dist/__tests__/realtime-session-channels.test.js.map +0 -1
  310. package/dist/__tests__/realtime-session-client-tools.test.d.ts +0 -2
  311. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +0 -1
  312. package/dist/__tests__/realtime-session-client-tools.test.js +0 -103
  313. package/dist/__tests__/realtime-session-client-tools.test.js.map +0 -1
  314. package/dist/__tests__/realtime-session-minimized.test.d.ts +0 -2
  315. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +0 -1
  316. package/dist/__tests__/realtime-session-minimized.test.js +0 -32
  317. package/dist/__tests__/realtime-session-minimized.test.js.map +0 -1
  318. package/dist/__tests__/realtime-session-mint.test.d.ts +0 -2
  319. package/dist/__tests__/realtime-session-mint.test.d.ts.map +0 -1
  320. package/dist/__tests__/realtime-session-mint.test.js +0 -69
  321. package/dist/__tests__/realtime-session-mint.test.js.map +0 -1
  322. package/dist/__tests__/realtime-session-policy.test.d.ts +0 -2
  323. package/dist/__tests__/realtime-session-policy.test.d.ts.map +0 -1
  324. package/dist/__tests__/realtime-session-policy.test.js +0 -303
  325. package/dist/__tests__/realtime-session-policy.test.js.map +0 -1
  326. package/dist/__tests__/realtime-session-review.service.test.d.ts +0 -2
  327. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +0 -1
  328. package/dist/__tests__/realtime-session-review.service.test.js +0 -743
  329. package/dist/__tests__/realtime-session-review.service.test.js.map +0 -1
  330. package/dist/__tests__/realtime-session-state.test.d.ts +0 -2
  331. package/dist/__tests__/realtime-session-state.test.d.ts.map +0 -1
  332. package/dist/__tests__/realtime-session-state.test.js +0 -83
  333. package/dist/__tests__/realtime-session-state.test.js.map +0 -1
  334. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +0 -2
  335. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +0 -1
  336. package/dist/__tests__/realtime-session-timeline-card.test.js +0 -106
  337. package/dist/__tests__/realtime-session-timeline-card.test.js.map +0 -1
  338. package/dist/__tests__/realtime-session-timeline.test.d.ts +0 -2
  339. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +0 -1
  340. package/dist/__tests__/realtime-session-timeline.test.js +0 -142
  341. package/dist/__tests__/realtime-session-timeline.test.js.map +0 -1
  342. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +0 -19
  343. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +0 -1
  344. package/dist/__tests__/realtime-sessions-adapter.test.js +0 -188
  345. package/dist/__tests__/realtime-sessions-adapter.test.js.map +0 -1
  346. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +0 -2
  347. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +0 -1
  348. package/dist/__tests__/realtime-surface-panel-prefs.test.js +0 -100
  349. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +0 -1
  350. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +0 -2
  351. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +0 -1
  352. package/dist/__tests__/realtime-surface-tabs-model.test.js +0 -193
  353. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +0 -1
  354. package/dist/__tests__/remote-browser-audio-player.test.d.ts +0 -2
  355. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +0 -1
  356. package/dist/__tests__/remote-browser-audio-player.test.js +0 -137
  357. package/dist/__tests__/remote-browser-audio-player.test.js.map +0 -1
  358. package/dist/__tests__/remote-browser-channel.test.d.ts +0 -2
  359. package/dist/__tests__/remote-browser-channel.test.d.ts.map +0 -1
  360. package/dist/__tests__/remote-browser-channel.test.js +0 -585
  361. package/dist/__tests__/remote-browser-channel.test.js.map +0 -1
  362. package/dist/__tests__/search-service.test.d.ts +0 -2
  363. package/dist/__tests__/search-service.test.d.ts.map +0 -1
  364. package/dist/__tests__/search-service.test.js +0 -114
  365. package/dist/__tests__/search-service.test.js.map +0 -1
  366. package/dist/__tests__/slot-defaults.test.d.ts +0 -24
  367. package/dist/__tests__/slot-defaults.test.d.ts.map +0 -1
  368. package/dist/__tests__/slot-defaults.test.js +0 -63
  369. package/dist/__tests__/slot-defaults.test.js.map +0 -1
  370. package/dist/__tests__/toast-service.test.d.ts +0 -2
  371. package/dist/__tests__/toast-service.test.d.ts.map +0 -1
  372. package/dist/__tests__/toast-service.test.js +0 -143
  373. package/dist/__tests__/toast-service.test.js.map +0 -1
  374. package/dist/__tests__/user-authorization.test.d.ts +0 -2
  375. package/dist/__tests__/user-authorization.test.d.ts.map +0 -1
  376. package/dist/__tests__/user-authorization.test.js +0 -97
  377. package/dist/__tests__/user-authorization.test.js.map +0 -1
  378. package/dist/__tests__/voice-session-narration.test.d.ts +0 -2
  379. package/dist/__tests__/voice-session-narration.test.d.ts.map +0 -1
  380. package/dist/__tests__/voice-session-narration.test.js +0 -609
  381. package/dist/__tests__/voice-session-narration.test.js.map +0 -1
  382. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +0 -2
  383. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +0 -1
  384. package/dist/__tests__/whiteboard-artifact-viewer.test.js +0 -101
  385. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +0 -1
  386. package/dist/__tests__/whiteboard-channel.test.d.ts +0 -2
  387. package/dist/__tests__/whiteboard-channel.test.d.ts.map +0 -1
  388. package/dist/__tests__/whiteboard-channel.test.js +0 -260
  389. package/dist/__tests__/whiteboard-channel.test.js.map +0 -1
  390. package/dist/__tests__/whiteboard-restore-state.test.d.ts +0 -2
  391. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +0 -1
  392. package/dist/__tests__/whiteboard-restore-state.test.js +0 -108
  393. package/dist/__tests__/whiteboard-restore-state.test.js.map +0 -1
  394. package/dist/lib/components/mention/mention-dropdown.component.d.ts +0 -53
  395. package/dist/lib/components/mention/mention-dropdown.component.d.ts.map +0 -1
  396. package/dist/lib/components/mention/mention-dropdown.component.js +0 -276
  397. package/dist/lib/components/mention/mention-dropdown.component.js.map +0 -1
  398. package/dist/lib/components/mention/mention-editor.component.d.ts +0 -256
  399. package/dist/lib/components/mention/mention-editor.component.d.ts.map +0 -1
  400. package/dist/lib/components/mention/mention-editor.component.js +0 -1233
  401. package/dist/lib/components/mention/mention-editor.component.js.map +0 -1
  402. package/dist/lib/components/message/message-input-box.component.d.ts +0 -137
  403. package/dist/lib/components/message/message-input-box.component.d.ts.map +0 -1
  404. package/dist/lib/components/message/message-input-box.component.js +0 -321
  405. package/dist/lib/components/message/message-input-box.component.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"narration-template.test.js","sourceRoot":"","sources":["../../src/__tests__/narration-template.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unit tests for the pure narration-template helpers used by RealtimeSessionService — the DB-driven\n * `{{ progressMessage }}` / `{{ priorNarrations }}` / `{{ updateNumber }}` substitution plus the\n * built-in fallback for deployments that haven't synced the\n * `Realtime Co-Agent - Progress Narration` prompt.\n */\nimport { describe, it, expect } from 'vitest';\nimport {\n BuildNarrationInstructions,\n DefaultNarrationInstructions\n} from '../lib/services/narration-template';\n\ndescribe('DefaultNarrationInstructions', () => {\n it('embeds the progress digest and keeps the first-person + variation rules', () => {\n const out = DefaultNarrationInstructions('Analyzing the request');\n\n expect(out).toContain('Analyzing the request');\n expect(out).toContain('FIRST PERSON');\n expect(out).toContain('VARY the');\n expect(out).toContain('must not be the subject');\n expect(out).toContain('Never repeat');\n });\n\n it('numbers the update and reports no prior narrations on the first update', () => {\n const out = DefaultNarrationInstructions('Fetching', { UpdateNumber: 1 });\n\n expect(out).toContain('update #1');\n expect(out).toContain('first spoken update');\n });\n\n it('chains prior spoken narrations so the model can build on them', () => {\n const out = DefaultNarrationInstructions('Fetching', {\n UpdateNumber: 3,\n PriorNarrations: [\"I'm pulling that up now\", 'Got the first batch']\n });\n\n expect(out).toContain('update #3');\n expect(out).toContain(`- \"I'm pulling that up now\"`);\n expect(out).toContain('- \"Got the first batch\"');\n });\n});\n\ndescribe('BuildNarrationInstructions', () => {\n it('substitutes {{ progressMessage }} (spaced token) everywhere it appears', () => {\n const template = 'Progress: \"{{ progressMessage }}\". Repeat: {{ progressMessage }}.';\n const out = BuildNarrationInstructions(template, 'Fetching records');\n\n expect(out).toBe('Progress: \"Fetching records\". Repeat: Fetching records.');\n });\n\n it('tolerates the no-spaces {{progressMessage}} variant', () => {\n const out = BuildNarrationInstructions('Now: {{progressMessage}}!', 'Running the query');\n\n expect(out).toBe('Now: Running the query!');\n });\n\n it('substitutes a mix of both token variants', () => {\n const out = BuildNarrationInstructions('A {{ progressMessage }} B {{progressMessage}}', 'x');\n\n expect(out).toBe('A x B x');\n });\n\n it('substitutes {{ updateNumber }} and {{ priorNarrations }}', () => {\n const template = 'Update #{{ updateNumber }}. Said before:\\n{{ priorNarrations }}';\n const out = BuildNarrationInstructions(template, 'x', {\n UpdateNumber: 2,\n PriorNarrations: ['First thing I said']\n });\n\n expect(out).toBe('Update #2. Said before:\\n- \"First thing I said\"');\n });\n\n it('renders a friendly \"nothing yet\" for {{ priorNarrations }} on the first update', () => {\n const out = BuildNarrationInstructions('Prior: {{ priorNarrations }}', 'x');\n\n expect(out).toContain('first spoken update');\n });\n\n it('defaults {{ updateNumber }} to 1 when no options are given', () => {\n const out = BuildNarrationInstructions('N={{updateNumber}}', 'x');\n\n expect(out).toBe('N=1');\n });\n\n it('falls back to the built-in wording when the template is null', () => {\n const out = BuildNarrationInstructions(null, 'Analyzing the request', { UpdateNumber: 2 });\n\n expect(out).toBe(DefaultNarrationInstructions('Analyzing the request', { UpdateNumber: 2 }));\n });\n\n it('falls back to the built-in wording when the template is blank', () => {\n const out = BuildNarrationInstructions(' ', 'Analyzing the request');\n\n expect(out).toBe(DefaultNarrationInstructions('Analyzing the request'));\n });\n\n it('returns a template without tokens unchanged (no accidental injection)', () => {\n const out = BuildNarrationInstructions('Say something brief.', 'ignored');\n\n expect(out).toBe('Say something brief.');\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=notification-model.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"notification-model.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/notification-model.test.ts"],"names":[],"mappings":""}
@@ -1,68 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { DEFAULT_NOTIFICATION_PREFERENCES } from '../lib/models/notification.model';
3
- describe('DEFAULT_NOTIFICATION_PREFERENCES', () => {
4
- it('should have sound disabled by default', () => {
5
- expect(DEFAULT_NOTIFICATION_PREFERENCES.enableSound).toBe(false);
6
- });
7
- it('should have desktop notifications disabled by default', () => {
8
- expect(DEFAULT_NOTIFICATION_PREFERENCES.enableDesktopNotifications).toBe(false);
9
- });
10
- it('should have in-app notifications enabled by default', () => {
11
- expect(DEFAULT_NOTIFICATION_PREFERENCES.enableInAppNotifications).toBe(true);
12
- });
13
- it('should have badges enabled by default', () => {
14
- expect(DEFAULT_NOTIFICATION_PREFERENCES.showBadges).toBe(true);
15
- });
16
- it('should have empty muted conversations list', () => {
17
- expect(DEFAULT_NOTIFICATION_PREFERENCES.mutedConversations).toEqual([]);
18
- });
19
- });
20
- describe('Notification types', () => {
21
- it('should support all notification types', () => {
22
- const types = ['message', 'artifact', 'agent_process', 'task'];
23
- expect(types).toHaveLength(4);
24
- });
25
- it('should support all priority levels', () => {
26
- const priorities = ['low', 'normal', 'high', 'urgent'];
27
- expect(priorities).toHaveLength(4);
28
- });
29
- });
30
- describe('ConversationNotification shape', () => {
31
- it('should be constructable with required fields', () => {
32
- const notification = {
33
- conversationId: 'conv-1',
34
- unreadMessageCount: 5,
35
- lastReadMessageTimestamp: null,
36
- lastMessageTimestamp: new Date(),
37
- hasNewArtifacts: true,
38
- hasActiveAgentProcesses: false,
39
- newArtifactCount: 2,
40
- activeAgentProcessCount: 0,
41
- lastNotificationTimestamp: new Date(),
42
- highestPriority: 'high'
43
- };
44
- expect(notification.conversationId).toBe('conv-1');
45
- expect(notification.unreadMessageCount).toBe(5);
46
- expect(notification.hasNewArtifacts).toBe(true);
47
- });
48
- });
49
- describe('BadgeConfig shape', () => {
50
- it('should represent hidden badge', () => {
51
- const badge = { show: false };
52
- expect(badge.show).toBe(false);
53
- expect(badge.count).toBeUndefined();
54
- });
55
- it('should represent visible badge with count', () => {
56
- const badge = {
57
- show: true,
58
- count: 3,
59
- type: 'count',
60
- priority: 'normal',
61
- animate: false
62
- };
63
- expect(badge.show).toBe(true);
64
- expect(badge.count).toBe(3);
65
- expect(badge.type).toBe('count');
66
- });
67
- });
68
- //# sourceMappingURL=notification-model.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"notification-model.test.js","sourceRoot":"","sources":["../../src/__tests__/notification-model.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n DEFAULT_NOTIFICATION_PREFERENCES\n} from '../lib/models/notification.model';\nimport type {\n NotificationType,\n NotificationPriority,\n ConversationNotification,\n BadgeConfig,\n NotificationPreferences\n} from '../lib/models/notification.model';\n\ndescribe('DEFAULT_NOTIFICATION_PREFERENCES', () => {\n it('should have sound disabled by default', () => {\n expect(DEFAULT_NOTIFICATION_PREFERENCES.enableSound).toBe(false);\n });\n\n it('should have desktop notifications disabled by default', () => {\n expect(DEFAULT_NOTIFICATION_PREFERENCES.enableDesktopNotifications).toBe(false);\n });\n\n it('should have in-app notifications enabled by default', () => {\n expect(DEFAULT_NOTIFICATION_PREFERENCES.enableInAppNotifications).toBe(true);\n });\n\n it('should have badges enabled by default', () => {\n expect(DEFAULT_NOTIFICATION_PREFERENCES.showBadges).toBe(true);\n });\n\n it('should have empty muted conversations list', () => {\n expect(DEFAULT_NOTIFICATION_PREFERENCES.mutedConversations).toEqual([]);\n });\n});\n\ndescribe('Notification types', () => {\n it('should support all notification types', () => {\n const types: NotificationType[] = ['message', 'artifact', 'agent_process', 'task'];\n expect(types).toHaveLength(4);\n });\n\n it('should support all priority levels', () => {\n const priorities: NotificationPriority[] = ['low', 'normal', 'high', 'urgent'];\n expect(priorities).toHaveLength(4);\n });\n});\n\ndescribe('ConversationNotification shape', () => {\n it('should be constructable with required fields', () => {\n const notification: ConversationNotification = {\n conversationId: 'conv-1',\n unreadMessageCount: 5,\n lastReadMessageTimestamp: null,\n lastMessageTimestamp: new Date(),\n hasNewArtifacts: true,\n hasActiveAgentProcesses: false,\n newArtifactCount: 2,\n activeAgentProcessCount: 0,\n lastNotificationTimestamp: new Date(),\n highestPriority: 'high'\n };\n expect(notification.conversationId).toBe('conv-1');\n expect(notification.unreadMessageCount).toBe(5);\n expect(notification.hasNewArtifacts).toBe(true);\n });\n});\n\ndescribe('BadgeConfig shape', () => {\n it('should represent hidden badge', () => {\n const badge: BadgeConfig = { show: false };\n expect(badge.show).toBe(false);\n expect(badge.count).toBeUndefined();\n });\n\n it('should represent visible badge with count', () => {\n const badge: BadgeConfig = {\n show: true,\n count: 3,\n type: 'count',\n priority: 'normal',\n animate: false\n };\n expect(badge.show).toBe(true);\n expect(badge.count).toBe(3);\n expect(badge.type).toBe('count');\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=realtime-agent-picker-models.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-agent-picker-models.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-agent-picker-models.test.ts"],"names":[],"mappings":""}
@@ -1,49 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { BuildRealtimeModelOptions, } from '../lib/services/realtime-pairing';
3
- /**
4
- * The "Voice model" option builder the picker applies to {@link AIEngineBase}'s cached
5
- * `Models` — the pure replacement for the previous narrow RunView
6
- * (`IsActive = 1 AND AIModelType = 'Realtime'`, `OrderBy: 'Name'`). Filter parity with the
7
- * SQL it replaced is the contract under test: active-only, Realtime-type-only
8
- * (trim + case-insensitive, matching SQL collation), Name-sorted, ID+Name projection.
9
- */
10
- function model(overrides) {
11
- return {
12
- AIModelType: 'Realtime',
13
- IsActive: true,
14
- ...overrides,
15
- };
16
- }
17
- describe('BuildRealtimeModelOptions', () => {
18
- it('keeps only ACTIVE models of type Realtime, projected to {ID, Name}', () => {
19
- const options = BuildRealtimeModelOptions([
20
- model({ ID: 'm1', Name: 'Realtime One' }),
21
- model({ ID: 'm2', Name: 'Inactive Realtime', IsActive: false }),
22
- model({ ID: 'm3', Name: 'Some LLM', AIModelType: 'LLM' }),
23
- model({ ID: 'm4', Name: 'Untyped', AIModelType: null }),
24
- ]);
25
- expect(options).toEqual([{ ID: 'm1', Name: 'Realtime One' }]);
26
- });
27
- it('matches the Realtime type trim + case-insensitively (SQL-collation parity)', () => {
28
- const options = BuildRealtimeModelOptions([
29
- model({ ID: 'm1', Name: 'A', AIModelType: ' realtime ' }),
30
- model({ ID: 'm2', Name: 'B', AIModelType: 'REALTIME' }),
31
- ]);
32
- expect(options.map(o => o.ID)).toEqual(['m1', 'm2']);
33
- });
34
- it('sorts by Name ascending (parity with the previous OrderBy)', () => {
35
- const options = BuildRealtimeModelOptions([
36
- model({ ID: 'm2', Name: 'Zeta Voice' }),
37
- model({ ID: 'm1', Name: 'Alpha Voice' }),
38
- model({ ID: 'm3', Name: 'Mid Voice' }),
39
- ]);
40
- expect(options.map(o => o.Name)).toEqual(['Alpha Voice', 'Mid Voice', 'Zeta Voice']);
41
- });
42
- it('returns an empty list for an empty cache and never mutates the input', () => {
43
- expect(BuildRealtimeModelOptions([])).toEqual([]);
44
- const input = [model({ ID: 'm2', Name: 'B' }), model({ ID: 'm1', Name: 'A' })];
45
- BuildRealtimeModelOptions(input);
46
- expect(input.map(m => m.ID)).toEqual(['m2', 'm1']); // input order untouched
47
- });
48
- });
49
- //# sourceMappingURL=realtime-agent-picker-models.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-agent-picker-models.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-agent-picker-models.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n BuildRealtimeModelOptions,\n VoiceModelCandidate,\n} from '../lib/services/realtime-pairing';\n\n/**\n * The \"Voice model\" option builder the picker applies to {@link AIEngineBase}'s cached\n * `Models` — the pure replacement for the previous narrow RunView\n * (`IsActive = 1 AND AIModelType = 'Realtime'`, `OrderBy: 'Name'`). Filter parity with the\n * SQL it replaced is the contract under test: active-only, Realtime-type-only\n * (trim + case-insensitive, matching SQL collation), Name-sorted, ID+Name projection.\n */\n\nfunction model(overrides: Partial<VoiceModelCandidate> & { ID: string; Name: string }): VoiceModelCandidate {\n return {\n AIModelType: 'Realtime',\n IsActive: true,\n ...overrides,\n };\n}\n\ndescribe('BuildRealtimeModelOptions', () => {\n it('keeps only ACTIVE models of type Realtime, projected to {ID, Name}', () => {\n const options = BuildRealtimeModelOptions([\n model({ ID: 'm1', Name: 'Realtime One' }),\n model({ ID: 'm2', Name: 'Inactive Realtime', IsActive: false }),\n model({ ID: 'm3', Name: 'Some LLM', AIModelType: 'LLM' }),\n model({ ID: 'm4', Name: 'Untyped', AIModelType: null }),\n ]);\n expect(options).toEqual([{ ID: 'm1', Name: 'Realtime One' }]);\n });\n\n it('matches the Realtime type trim + case-insensitively (SQL-collation parity)', () => {\n const options = BuildRealtimeModelOptions([\n model({ ID: 'm1', Name: 'A', AIModelType: ' realtime ' }),\n model({ ID: 'm2', Name: 'B', AIModelType: 'REALTIME' }),\n ]);\n expect(options.map(o => o.ID)).toEqual(['m1', 'm2']);\n });\n\n it('sorts by Name ascending (parity with the previous OrderBy)', () => {\n const options = BuildRealtimeModelOptions([\n model({ ID: 'm2', Name: 'Zeta Voice' }),\n model({ ID: 'm1', Name: 'Alpha Voice' }),\n model({ ID: 'm3', Name: 'Mid Voice' }),\n ]);\n expect(options.map(o => o.Name)).toEqual(['Alpha Voice', 'Mid Voice', 'Zeta Voice']);\n });\n\n it('returns an empty list for an empty cache and never mutates the input', () => {\n expect(BuildRealtimeModelOptions([])).toEqual([]);\n const input = [model({ ID: 'm2', Name: 'B' }), model({ ID: 'm1', Name: 'A' })];\n BuildRealtimeModelOptions(input);\n expect(input.map(m => m.ID)).toEqual(['m2', 'm1']); // input order untouched\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=realtime-audio-visuals.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-audio-visuals.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-audio-visuals.test.ts"],"names":[],"mappings":""}
@@ -1,123 +0,0 @@
1
- /**
2
- * Unit tests for the audio-reactive visual smoothing — the framework-free envelope +
3
- * direction-hysteresis math between raw RealtimeAudioActivity samples and the CSS
4
- * variables that drive the hero orb / EQ.
5
- */
6
- import { describe, it, expect } from 'vitest';
7
- import { GateLevel, RealtimeAudioVisualSmoother, SmoothLevel, AUDIO_INPUT_NOISE_GATE, AUDIO_OUTPUT_NOISE_GATE, AUDIO_DIRECTION_HOLD_MS, AUDIO_PRESENCE_FLOOR, AUDIO_VISUAL_BIN_COUNT } from '../lib/components/realtime/realtime-audio-visuals';
8
- function activity(partial) {
9
- return { InputLevel: null, OutputLevel: null, InputBins: null, OutputBins: null, ...partial };
10
- }
11
- describe('SmoothLevel (attack/decay envelope)', () => {
12
- it('attacks fast and decays slow (speaker-cone feel, never a strobe)', () => {
13
- const up = SmoothLevel(0, 1);
14
- const down = SmoothLevel(1, 0);
15
- expect(up).toBeGreaterThan(0.4); // fast attack
16
- expect(down).toBeGreaterThan(0.8); // slow decay — still ringing down
17
- });
18
- it('clamps into 0..1 and converges to the target', () => {
19
- let v = 0;
20
- for (let i = 0; i < 60; i++) {
21
- v = SmoothLevel(v, 0.7);
22
- }
23
- expect(v).toBeCloseTo(0.7, 1);
24
- expect(SmoothLevel(0.5, 5)).toBeLessThanOrEqual(1);
25
- });
26
- });
27
- describe('RealtimeAudioVisualSmoother', () => {
28
- it('null in → null out (un-metered drivers keep turn-state visuals)', () => {
29
- expect(new RealtimeAudioVisualSmoother().Next(null, 0)).toBeNull();
30
- });
31
- it('produces smoothed levels and a fixed-length bin array', () => {
32
- const s = new RealtimeAudioVisualSmoother();
33
- const frame = s.Next(activity({ OutputLevel: 0.8, OutputBins: new Array(9).fill(0.6) }), 0);
34
- expect(frame).not.toBeNull();
35
- expect(frame.OutputLevel).toBeGreaterThan(0);
36
- expect(frame.OutputLevel).toBeLessThan(0.8); // smoothed, not raw
37
- expect(frame.Bins).toHaveLength(AUDIO_VISUAL_BIN_COUNT);
38
- });
39
- it('adopts the louder direction from silence immediately', () => {
40
- const s = new RealtimeAudioVisualSmoother();
41
- const frame = s.Next(activity({ OutputLevel: 0.7, InputLevel: 0.1 }), 0);
42
- expect(frame.Direction).toBe('agent');
43
- });
44
- it('holds the direction across syllable gaps (no flicker)', () => {
45
- const s = new RealtimeAudioVisualSmoother();
46
- s.Next(activity({ OutputLevel: 0.7 }), 0);
47
- // Silence shorter than the hold window — direction must persist.
48
- let frame = s.Next(activity({ OutputLevel: 0 }), AUDIO_DIRECTION_HOLD_MS / 2);
49
- expect(frame.Direction).toBe('agent');
50
- // Keep feeding silence until the smoothed level decays under the floor AND the hold expires.
51
- for (let t = 1; t <= 40; t++) {
52
- frame = s.Next(activity({ OutputLevel: 0 }), AUDIO_DIRECTION_HOLD_MS / 2 + t * 100);
53
- }
54
- expect(frame.Direction).toBe('none');
55
- });
56
- it('the other side steals the direction only by clearly dominating', () => {
57
- const s = new RealtimeAudioVisualSmoother();
58
- for (let i = 0; i < 10; i++) {
59
- s.Next(activity({ OutputLevel: 0.6, InputLevel: 0 }), i);
60
- }
61
- // Slightly-louder mic does NOT steal…
62
- let frame = s.Next(activity({ OutputLevel: 0.5, InputLevel: 0.5 }), 100);
63
- expect(frame.Direction).toBe('agent');
64
- // …a clearly dominant mic does.
65
- for (let i = 0; i < 10; i++) {
66
- frame = s.Next(activity({ OutputLevel: 0.05, InputLevel: 0.9 }), 200 + i);
67
- }
68
- expect(frame.Direction).toBe('user');
69
- });
70
- it('bins follow the louder direction and decay when no spectrum is available', () => {
71
- const s = new RealtimeAudioVisualSmoother();
72
- let frame = s.Next(activity({
73
- OutputLevel: 0.8, InputLevel: 0.1,
74
- OutputBins: new Array(9).fill(1), InputBins: new Array(9).fill(0.1)
75
- }), 0);
76
- expect(frame.Bins[0]).toBeGreaterThan(0.3); // tracking output's loud spectrum
77
- const peak = frame.Bins[0];
78
- frame = s.Next(activity({ OutputLevel: 0.8 }), 16); // bins gone → decay
79
- expect(frame.Bins[0]).toBeLessThan(peak);
80
- });
81
- it('Reset returns to silence + no direction', () => {
82
- const s = new RealtimeAudioVisualSmoother();
83
- s.Next(activity({ OutputLevel: 0.9 }), 0);
84
- s.Reset();
85
- const frame = s.Next(activity({ OutputLevel: AUDIO_PRESENCE_FLOOR / 10 }), 1);
86
- expect(frame.Direction).toBe('none');
87
- expect(frame.OutputLevel).toBeLessThan(0.1);
88
- });
89
- });
90
- describe('noise gating (idle mic must not animate the visuals)', () => {
91
- it('GateLevel: at/below the gate is TRUE silence; above it rescales soft-knee to 0..1', () => {
92
- expect(GateLevel(0, AUDIO_INPUT_NOISE_GATE)).toBe(0);
93
- expect(GateLevel(AUDIO_INPUT_NOISE_GATE, AUDIO_INPUT_NOISE_GATE)).toBe(0);
94
- expect(GateLevel(NaN, AUDIO_INPUT_NOISE_GATE)).toBe(0);
95
- expect(GateLevel(1, AUDIO_INPUT_NOISE_GATE)).toBe(1);
96
- const mid = GateLevel(0.5, AUDIO_INPUT_NOISE_GATE);
97
- expect(mid).toBeGreaterThan(0.4);
98
- expect(mid).toBeLessThan(0.5);
99
- });
100
- it('idle mic noise (below the input gate) renders as silence: levels decay, bins decay, direction releases', () => {
101
- const s = new RealtimeAudioVisualSmoother();
102
- let frame = s.Next(activity({ InputLevel: 0.6, InputBins: new Array(9).fill(0.8) }), 0);
103
- expect(frame.Direction).toBe('user');
104
- // Mic falls back to its idle room-noise floor (non-zero, but under the 0.045 gate) —
105
- // the analyser keeps reporting it forever; the visuals must come to rest anyway.
106
- for (let t = 1; t <= 60; t++) {
107
- frame = s.Next(activity({ InputLevel: 0.03, InputBins: new Array(9).fill(0.3) }), t * 100);
108
- }
109
- expect(frame.InputLevel).toBeLessThan(0.01);
110
- expect(Math.max(...frame.Bins)).toBeLessThan(0.01);
111
- expect(frame.Direction).toBe('none');
112
- });
113
- it('the mic gate sits ABOVE the output gate (playback silence is true zero; mic silence is not)', () => {
114
- expect(AUDIO_INPUT_NOISE_GATE).toBeGreaterThan(AUDIO_OUTPUT_NOISE_GATE);
115
- });
116
- it('real speech still passes the gate untouched in feel (fast attack from rest)', () => {
117
- const s = new RealtimeAudioVisualSmoother();
118
- const frame = s.Next(activity({ InputLevel: 0.5, InputBins: new Array(9).fill(0.6) }), 0);
119
- expect(frame.InputLevel).toBeGreaterThan(0.15);
120
- expect(frame.Direction).toBe('user');
121
- });
122
- });
123
- //# sourceMappingURL=realtime-audio-visuals.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-audio-visuals.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-audio-visuals.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unit tests for the audio-reactive visual smoothing — the framework-free envelope +\n * direction-hysteresis math between raw RealtimeAudioActivity samples and the CSS\n * variables that drive the hero orb / EQ.\n */\nimport { describe, it, expect } from 'vitest';\nimport { RealtimeAudioActivity } from '@memberjunction/ai-realtime-client';\nimport {\n GateLevel,\n RealtimeAudioVisualSmoother,\n SmoothLevel,\n AUDIO_INPUT_NOISE_GATE,\n AUDIO_OUTPUT_NOISE_GATE,\n AUDIO_DIRECTION_HOLD_MS,\n AUDIO_PRESENCE_FLOOR,\n AUDIO_VISUAL_BIN_COUNT\n} from '../lib/components/realtime/realtime-audio-visuals';\n\nfunction activity(partial: Partial<RealtimeAudioActivity>): RealtimeAudioActivity {\n return { InputLevel: null, OutputLevel: null, InputBins: null, OutputBins: null, ...partial };\n}\n\ndescribe('SmoothLevel (attack/decay envelope)', () => {\n it('attacks fast and decays slow (speaker-cone feel, never a strobe)', () => {\n const up = SmoothLevel(0, 1);\n const down = SmoothLevel(1, 0);\n expect(up).toBeGreaterThan(0.4); // fast attack\n expect(down).toBeGreaterThan(0.8); // slow decay — still ringing down\n });\n\n it('clamps into 0..1 and converges to the target', () => {\n let v = 0;\n for (let i = 0; i < 60; i++) {\n v = SmoothLevel(v, 0.7);\n }\n expect(v).toBeCloseTo(0.7, 1);\n expect(SmoothLevel(0.5, 5)).toBeLessThanOrEqual(1);\n });\n});\n\ndescribe('RealtimeAudioVisualSmoother', () => {\n it('null in → null out (un-metered drivers keep turn-state visuals)', () => {\n expect(new RealtimeAudioVisualSmoother().Next(null, 0)).toBeNull();\n });\n\n it('produces smoothed levels and a fixed-length bin array', () => {\n const s = new RealtimeAudioVisualSmoother();\n const frame = s.Next(activity({ OutputLevel: 0.8, OutputBins: new Array(9).fill(0.6) }), 0);\n expect(frame).not.toBeNull();\n expect(frame!.OutputLevel).toBeGreaterThan(0);\n expect(frame!.OutputLevel).toBeLessThan(0.8); // smoothed, not raw\n expect(frame!.Bins).toHaveLength(AUDIO_VISUAL_BIN_COUNT);\n });\n\n it('adopts the louder direction from silence immediately', () => {\n const s = new RealtimeAudioVisualSmoother();\n const frame = s.Next(activity({ OutputLevel: 0.7, InputLevel: 0.1 }), 0);\n expect(frame!.Direction).toBe('agent');\n });\n\n it('holds the direction across syllable gaps (no flicker)', () => {\n const s = new RealtimeAudioVisualSmoother();\n s.Next(activity({ OutputLevel: 0.7 }), 0);\n // Silence shorter than the hold window — direction must persist.\n let frame = s.Next(activity({ OutputLevel: 0 }), AUDIO_DIRECTION_HOLD_MS / 2);\n expect(frame!.Direction).toBe('agent');\n // Keep feeding silence until the smoothed level decays under the floor AND the hold expires.\n for (let t = 1; t <= 40; t++) {\n frame = s.Next(activity({ OutputLevel: 0 }), AUDIO_DIRECTION_HOLD_MS / 2 + t * 100);\n }\n expect(frame!.Direction).toBe('none');\n });\n\n it('the other side steals the direction only by clearly dominating', () => {\n const s = new RealtimeAudioVisualSmoother();\n for (let i = 0; i < 10; i++) {\n s.Next(activity({ OutputLevel: 0.6, InputLevel: 0 }), i);\n }\n // Slightly-louder mic does NOT steal…\n let frame = s.Next(activity({ OutputLevel: 0.5, InputLevel: 0.5 }), 100);\n expect(frame!.Direction).toBe('agent');\n // …a clearly dominant mic does.\n for (let i = 0; i < 10; i++) {\n frame = s.Next(activity({ OutputLevel: 0.05, InputLevel: 0.9 }), 200 + i);\n }\n expect(frame!.Direction).toBe('user');\n });\n\n it('bins follow the louder direction and decay when no spectrum is available', () => {\n const s = new RealtimeAudioVisualSmoother();\n let frame = s.Next(activity({\n OutputLevel: 0.8, InputLevel: 0.1,\n OutputBins: new Array(9).fill(1), InputBins: new Array(9).fill(0.1)\n }), 0);\n expect(frame!.Bins[0]).toBeGreaterThan(0.3); // tracking output's loud spectrum\n const peak = frame!.Bins[0];\n frame = s.Next(activity({ OutputLevel: 0.8 }), 16); // bins gone → decay\n expect(frame!.Bins[0]).toBeLessThan(peak);\n });\n\n it('Reset returns to silence + no direction', () => {\n const s = new RealtimeAudioVisualSmoother();\n s.Next(activity({ OutputLevel: 0.9 }), 0);\n s.Reset();\n const frame = s.Next(activity({ OutputLevel: AUDIO_PRESENCE_FLOOR / 10 }), 1);\n expect(frame!.Direction).toBe('none');\n expect(frame!.OutputLevel).toBeLessThan(0.1);\n });\n});\n\ndescribe('noise gating (idle mic must not animate the visuals)', () => {\n it('GateLevel: at/below the gate is TRUE silence; above it rescales soft-knee to 0..1', () => {\n expect(GateLevel(0, AUDIO_INPUT_NOISE_GATE)).toBe(0);\n expect(GateLevel(AUDIO_INPUT_NOISE_GATE, AUDIO_INPUT_NOISE_GATE)).toBe(0);\n expect(GateLevel(NaN, AUDIO_INPUT_NOISE_GATE)).toBe(0);\n expect(GateLevel(1, AUDIO_INPUT_NOISE_GATE)).toBe(1);\n const mid = GateLevel(0.5, AUDIO_INPUT_NOISE_GATE);\n expect(mid).toBeGreaterThan(0.4);\n expect(mid).toBeLessThan(0.5);\n });\n\n it('idle mic noise (below the input gate) renders as silence: levels decay, bins decay, direction releases', () => {\n const s = new RealtimeAudioVisualSmoother();\n let frame = s.Next(activity({ InputLevel: 0.6, InputBins: new Array(9).fill(0.8) }), 0)!;\n expect(frame.Direction).toBe('user');\n\n // Mic falls back to its idle room-noise floor (non-zero, but under the 0.045 gate) —\n // the analyser keeps reporting it forever; the visuals must come to rest anyway.\n for (let t = 1; t <= 60; t++) {\n frame = s.Next(activity({ InputLevel: 0.03, InputBins: new Array(9).fill(0.3) }), t * 100)!;\n }\n expect(frame.InputLevel).toBeLessThan(0.01);\n expect(Math.max(...frame.Bins)).toBeLessThan(0.01);\n expect(frame.Direction).toBe('none');\n });\n\n it('the mic gate sits ABOVE the output gate (playback silence is true zero; mic silence is not)', () => {\n expect(AUDIO_INPUT_NOISE_GATE).toBeGreaterThan(AUDIO_OUTPUT_NOISE_GATE);\n });\n\n it('real speech still passes the gate untouched in feel (fast attack from rest)', () => {\n const s = new RealtimeAudioVisualSmoother();\n const frame = s.Next(activity({ InputLevel: 0.5, InputBins: new Array(9).fill(0.6) }), 0)!;\n expect(frame.InputLevel).toBeGreaterThan(0.15);\n expect(frame.Direction).toBe('user');\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- import '@angular/compiler';
2
- //# sourceMappingURL=realtime-delegation-card-cancel.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-delegation-card-cancel.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-delegation-card-cancel.test.ts"],"names":[],"mappings":"AAEA,OAAO,mBAAmB,CAAC"}
@@ -1,48 +0,0 @@
1
- // Angular components in this package are partial-compiled — load the JIT compiler first
2
- // (same convention as the whiteboard component suites in this node test environment).
3
- import '@angular/compiler';
4
- import { describe, it, expect, vi } from 'vitest';
5
- import { RealtimeDelegationCardComponent } from '../lib/components/realtime/realtime-delegation-card.component';
6
- /**
7
- * The WORKING card's ✕ cancel affordance — the first hop of the explicit-cancel event
8
- * chain (card ✕ → thread re-emit → overlay → `RealtimeSessionService.CancelDelegation`).
9
- * Class-level tests (no TestBed): the card's emission contract is what the chain depends
10
- * on — the thread/overlay hops are template re-emits ((CancelRequested)="….emit($event)").
11
- */
12
- function workingCard(overrides = {}) {
13
- return {
14
- CallID: 'call-1',
15
- AgentName: 'Sage',
16
- LatestMessage: 'Looking things up',
17
- LatestStep: 'prompt_execution',
18
- Done: false,
19
- Success: false,
20
- StartedAt: Date.now(),
21
- ...overrides,
22
- };
23
- }
24
- /** A minimal MouseEvent stand-in whose stopPropagation is observable. */
25
- function fakeClick() {
26
- return { stopPropagation: vi.fn() };
27
- }
28
- describe('RealtimeDelegationCardComponent — ✕ cancel affordance (explicit user intent)', () => {
29
- it('emits CancelRequested with the call id for a WORKING card and stops propagation', () => {
30
- const component = new RealtimeDelegationCardComponent();
31
- component.Card = workingCard({ CallID: 'call-9' });
32
- const emitted = [];
33
- component.CancelRequested.subscribe((id) => emitted.push(id));
34
- const event = fakeClick();
35
- component.CancelWork(event);
36
- expect(emitted).toEqual(['call-9']);
37
- expect(event.stopPropagation).toHaveBeenCalled();
38
- });
39
- it('does NOT emit for a DONE card (the affordance only exists while work runs)', () => {
40
- const component = new RealtimeDelegationCardComponent();
41
- component.Card = workingCard({ Done: true, Success: true, Result: 'all set' });
42
- const emitted = [];
43
- component.CancelRequested.subscribe((id) => emitted.push(id));
44
- component.CancelWork(fakeClick());
45
- expect(emitted).toEqual([]);
46
- });
47
- });
48
- //# sourceMappingURL=realtime-delegation-card-cancel.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-delegation-card-cancel.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-delegation-card-cancel.test.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,sFAAsF;AACtF,OAAO,mBAAmB,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,+BAA+B,EAAE,MAAM,+DAA+D,CAAC;AAGhH;;;;;GAKG;AAEH,SAAS,WAAW,CAAC,YAA+C,EAAE;IACpE,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,MAAM;QACjB,aAAa,EAAE,mBAAmB;QAClC,UAAU,EAAE,kBAAkB;QAC9B,IAAI,EAAE,KAAK;QACX,OAAO,EAAE,KAAK;QACd,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;QACrB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,SAAS,SAAS;IAChB,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE,EAA2E,CAAC;AAC/G,CAAC;AAED,QAAQ,CAAC,8EAA8E,EAAE,GAAG,EAAE;IAC5F,EAAE,CAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,MAAM,SAAS,GAAG,IAAI,+BAA+B,EAAE,CAAC;QACxD,SAAS,CAAC,IAAI,GAAG,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,SAAS,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAEtE,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;QAC1B,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAE5B,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,SAAS,GAAG,IAAI,+BAA+B,EAAE,CAAC;QACxD,SAAS,CAAC,IAAI,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/E,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,SAAS,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAEtE,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,CAAC;QAElC,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["// Angular components in this package are partial-compiled — load the JIT compiler first\n// (same convention as the whiteboard component suites in this node test environment).\nimport '@angular/compiler';\nimport { describe, it, expect, vi } from 'vitest';\nimport { RealtimeDelegationCardComponent } from '../lib/components/realtime/realtime-delegation-card.component';\nimport { RealtimeDelegationCardVM } from '../lib/components/realtime/realtime-session-state';\n\n/**\n * The WORKING card's ✕ cancel affordance — the first hop of the explicit-cancel event\n * chain (card ✕ → thread re-emit → overlay → `RealtimeSessionService.CancelDelegation`).\n * Class-level tests (no TestBed): the card's emission contract is what the chain depends\n * on — the thread/overlay hops are template re-emits ((CancelRequested)=\"….emit($event)\").\n */\n\nfunction workingCard(overrides: Partial<RealtimeDelegationCardVM> = {}): RealtimeDelegationCardVM {\n return {\n CallID: 'call-1',\n AgentName: 'Sage',\n LatestMessage: 'Looking things up',\n LatestStep: 'prompt_execution',\n Done: false,\n Success: false,\n StartedAt: Date.now(),\n ...overrides,\n };\n}\n\n/** A minimal MouseEvent stand-in whose stopPropagation is observable. */\nfunction fakeClick(): MouseEvent & { stopPropagation: ReturnType<typeof vi.fn> } {\n return { stopPropagation: vi.fn() } as unknown as MouseEvent & { stopPropagation: ReturnType<typeof vi.fn> };\n}\n\ndescribe('RealtimeDelegationCardComponent — ✕ cancel affordance (explicit user intent)', () => {\n it('emits CancelRequested with the call id for a WORKING card and stops propagation', () => {\n const component = new RealtimeDelegationCardComponent();\n component.Card = workingCard({ CallID: 'call-9' });\n const emitted: string[] = [];\n component.CancelRequested.subscribe((id: string) => emitted.push(id));\n\n const event = fakeClick();\n component.CancelWork(event);\n\n expect(emitted).toEqual(['call-9']);\n expect(event.stopPropagation).toHaveBeenCalled();\n });\n\n it('does NOT emit for a DONE card (the affordance only exists while work runs)', () => {\n const component = new RealtimeDelegationCardComponent();\n component.Card = workingCard({ Done: true, Success: true, Result: 'all set' });\n const emitted: string[] = [];\n component.CancelRequested.subscribe((id: string) => emitted.push(id));\n\n component.CancelWork(fakeClick());\n\n expect(emitted).toEqual([]);\n });\n});\n"]}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=realtime-disclosure.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-disclosure.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-disclosure.test.ts"],"names":[],"mappings":""}
@@ -1,164 +0,0 @@
1
- /**
2
- * Unit tests for the realtime overlay's PROGRESSIVE DISCLOSURE engine — the
3
- * framework-free levels/milestones model behind the pure-audio-first call UX
4
- * (persisted per-user via UserInfoEngine under `mj.realtimeVoice.uxMilestones.v1`).
5
- */
6
- import { describe, it, expect } from 'vitest';
7
- import { ClampUxLevel, DefaultUxMilestones, EffectiveBaseLevel, ParseUxMilestones, RatchetedMilestones, RealtimeDisclosureModel, SerializeUxMilestones, REALTIME_UX_MAX_LEVEL, REALTIME_UX_PREF_KEY, REALTIME_UX_PRO_CALLS } from '../lib/components/realtime/realtime-disclosure';
8
- describe('pref key', () => {
9
- it('uses the versioned mj.* naming convention', () => {
10
- expect(REALTIME_UX_PREF_KEY).toBe('mj.realtimeVoice.uxMilestones.v1');
11
- });
12
- });
13
- describe('ClampUxLevel', () => {
14
- it('passes valid levels through and clamps the band edges', () => {
15
- expect(ClampUxLevel(0)).toBe(0);
16
- expect(ClampUxLevel(3)).toBe(3);
17
- expect(ClampUxLevel(-2)).toBe(0);
18
- expect(ClampUxLevel(9)).toBe(REALTIME_UX_MAX_LEVEL);
19
- });
20
- it('rounds fractional and zeroes non-finite levels', () => {
21
- expect(ClampUxLevel(2.6)).toBe(3);
22
- expect(ClampUxLevel(NaN)).toBe(0);
23
- expect(ClampUxLevel(Infinity)).toBe(0);
24
- });
25
- });
26
- describe('ParseUxMilestones / SerializeUxMilestones', () => {
27
- it('round-trips milestones', () => {
28
- const m = { Level: 3, Calls: 7, Density: 'pro' };
29
- expect(ParseUxMilestones(SerializeUxMilestones(m))).toEqual(m);
30
- });
31
- it.each([
32
- ['missing', undefined],
33
- ['null', null],
34
- ['blank', ''],
35
- ['malformed JSON', '{level:'],
36
- ['non-object', '"3"'],
37
- ['JSON null', 'null']
38
- ])('falls back to defaults (never throws) for %s', (_label, raw) => {
39
- expect(ParseUxMilestones(raw)).toEqual(DefaultUxMilestones());
40
- });
41
- it('repairs field-wise: bad level/calls/density fall back individually', () => {
42
- expect(ParseUxMilestones('{"level":"high","calls":2,"density":"standard"}'))
43
- .toEqual({ Level: 0, Calls: 2, Density: 'standard' });
44
- expect(ParseUxMilestones('{"level":3,"calls":-4,"density":"ultra"}'))
45
- .toEqual({ Level: 3, Calls: 0, Density: 'auto' });
46
- expect(ParseUxMilestones('{"level":99,"calls":1.7,"density":"simple"}'))
47
- .toEqual({ Level: REALTIME_UX_MAX_LEVEL, Calls: 2, Density: 'simple' });
48
- });
49
- });
50
- describe('EffectiveBaseLevel', () => {
51
- it('day one: a brand-new user starts at PURE AUDIO (level 0)', () => {
52
- expect(EffectiveBaseLevel(DefaultUxMilestones())).toBe(0);
53
- });
54
- it('auto density follows the earned ratchet', () => {
55
- expect(EffectiveBaseLevel({ Level: 2, Calls: 1, Density: 'auto' })).toBe(2);
56
- });
57
- it(`auto density promotes to the full console after ${REALTIME_UX_PRO_CALLS} completed calls`, () => {
58
- expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS, Density: 'auto' })).toBe(REALTIME_UX_MAX_LEVEL);
59
- expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS - 1, Density: 'auto' })).toBe(1);
60
- });
61
- it('manual density overrides win outright (the escape hatch)', () => {
62
- const earned = { Level: 4, Calls: 10, Density: 'simple' };
63
- expect(EffectiveBaseLevel(earned)).toBe(0);
64
- expect(EffectiveBaseLevel({ ...earned, Density: 'standard' })).toBe(2);
65
- expect(EffectiveBaseLevel({ Level: 0, Calls: 0, Density: 'pro' })).toBe(REALTIME_UX_MAX_LEVEL);
66
- });
67
- });
68
- describe('RatchetedMilestones (call end)', () => {
69
- it('increments calls and ratchets the level to the session peak', () => {
70
- expect(RatchetedMilestones({ Level: 1, Calls: 1, Density: 'auto' }, 3))
71
- .toEqual({ Level: 3, Calls: 2, Density: 'auto' });
72
- });
73
- it('a completed first call always earns at least level 1 (text next time)', () => {
74
- expect(RatchetedMilestones(DefaultUxMilestones(), 0))
75
- .toEqual({ Level: 1, Calls: 1, Density: 'auto' });
76
- });
77
- it('NEVER lowers the earned level (ratchet)', () => {
78
- expect(RatchetedMilestones({ Level: 4, Calls: 5, Density: 'auto' }, 0).Level).toBe(4);
79
- });
80
- it('preserves the density override', () => {
81
- expect(RatchetedMilestones({ Level: 0, Calls: 0, Density: 'simple' }, 2).Density).toBe('simple');
82
- });
83
- });
84
- describe('RealtimeDisclosureModel', () => {
85
- it('Load resets the session level to the effective base', () => {
86
- const m = new RealtimeDisclosureModel();
87
- m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));
88
- expect(m.SessionLevel).toBe(2);
89
- expect(m.ShowThread).toBe(true);
90
- expect(m.ShowComposer).toBe(true);
91
- expect(m.ShowPanel).toBe(true);
92
- expect(m.PowerLevel).toBe(false);
93
- });
94
- it('day one exposes NOTHING beyond pure audio', () => {
95
- const m = new RealtimeDisclosureModel();
96
- m.Load(null);
97
- expect(m.SessionLevel).toBe(0);
98
- expect(m.ShowThread).toBe(false);
99
- expect(m.ShowComposer).toBe(false);
100
- expect(m.ShowPanel).toBe(false);
101
- expect(m.ShowGear).toBe(false);
102
- });
103
- it('Raise only raises — and reports whether anything changed', () => {
104
- const m = new RealtimeDisclosureModel();
105
- m.Load(null);
106
- expect(m.Raise('engaged')).toBe(true);
107
- expect(m.SessionLevel).toBe(2);
108
- expect(m.Raise('text')).toBe(false); // lower event after a higher one: no-op
109
- expect(m.SessionLevel).toBe(2);
110
- expect(m.Raise('channel')).toBe(true);
111
- expect(m.SessionLevel).toBe(3);
112
- });
113
- it('emits Changed$ on raises that change the level (and not on no-ops)', () => {
114
- const m = new RealtimeDisclosureModel();
115
- m.Load(null);
116
- let emissions = 0;
117
- m.Changed$.subscribe(() => emissions++);
118
- m.Raise('text');
119
- m.Raise('text');
120
- expect(emissions).toBe(1);
121
- });
122
- it('BeginSession resets the volatile level back to the base', () => {
123
- const m = new RealtimeDisclosureModel();
124
- m.Load(null);
125
- m.Raise('channel');
126
- m.BeginSession();
127
- expect(m.SessionLevel).toBe(0);
128
- });
129
- it('ReturnToPureAudio steps the volatile level back to 0 WITHOUT touching the ratchet', () => {
130
- const m = new RealtimeDisclosureModel();
131
- m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));
132
- m.Raise('power');
133
- let emissions = 0;
134
- m.Changed$.subscribe(() => emissions++);
135
- m.ReturnToPureAudio();
136
- expect(m.SessionLevel).toBe(0);
137
- expect(m.ShowThread).toBe(false);
138
- expect(m.Milestones.Level).toBe(2); // the cross-session ratchet is untouched
139
- m.ReturnToPureAudio(); // already there — no extra emission
140
- expect(emissions).toBe(1);
141
- // the user can step back up afterward — the door swings both ways
142
- expect(m.Raise('text')).toBe(true);
143
- expect(m.SessionLevel).toBe(1);
144
- });
145
- it('SetDensity applies immediately — simple retracts even a mid-call console', () => {
146
- const m = new RealtimeDisclosureModel();
147
- m.Load(SerializeUxMilestones({ Level: 4, Calls: 9, Density: 'auto' }));
148
- expect(m.SessionLevel).toBe(4);
149
- m.SetDensity('simple');
150
- expect(m.SessionLevel).toBe(0);
151
- expect(m.Milestones.Density).toBe('simple');
152
- m.SetDensity('pro');
153
- expect(m.SessionLevel).toBe(REALTIME_UX_MAX_LEVEL);
154
- });
155
- it('RatchetOnSessionEnd replaces milestones and returns the persistable payload', () => {
156
- const m = new RealtimeDisclosureModel();
157
- m.Load(null);
158
- m.Raise('channel');
159
- const serialized = m.RatchetOnSessionEnd();
160
- expect(m.Milestones).toEqual({ Level: 3, Calls: 1, Density: 'auto' });
161
- expect(ParseUxMilestones(serialized)).toEqual(m.Milestones);
162
- });
163
- });
164
- //# sourceMappingURL=realtime-disclosure.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"realtime-disclosure.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-disclosure.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unit tests for the realtime overlay's PROGRESSIVE DISCLOSURE engine — the\n * framework-free levels/milestones model behind the pure-audio-first call UX\n * (persisted per-user via UserInfoEngine under `mj.realtimeVoice.uxMilestones.v1`).\n */\nimport { describe, it, expect } from 'vitest';\nimport {\n ClampUxLevel,\n DefaultUxMilestones,\n EffectiveBaseLevel,\n ParseUxMilestones,\n RatchetedMilestones,\n RealtimeDisclosureModel,\n SerializeUxMilestones,\n REALTIME_UX_MAX_LEVEL,\n REALTIME_UX_PREF_KEY,\n REALTIME_UX_PRO_CALLS\n} from '../lib/components/realtime/realtime-disclosure';\n\ndescribe('pref key', () => {\n it('uses the versioned mj.* naming convention', () => {\n expect(REALTIME_UX_PREF_KEY).toBe('mj.realtimeVoice.uxMilestones.v1');\n });\n});\n\ndescribe('ClampUxLevel', () => {\n it('passes valid levels through and clamps the band edges', () => {\n expect(ClampUxLevel(0)).toBe(0);\n expect(ClampUxLevel(3)).toBe(3);\n expect(ClampUxLevel(-2)).toBe(0);\n expect(ClampUxLevel(9)).toBe(REALTIME_UX_MAX_LEVEL);\n });\n\n it('rounds fractional and zeroes non-finite levels', () => {\n expect(ClampUxLevel(2.6)).toBe(3);\n expect(ClampUxLevel(NaN)).toBe(0);\n expect(ClampUxLevel(Infinity)).toBe(0);\n });\n});\n\ndescribe('ParseUxMilestones / SerializeUxMilestones', () => {\n it('round-trips milestones', () => {\n const m = { Level: 3, Calls: 7, Density: 'pro' as const };\n expect(ParseUxMilestones(SerializeUxMilestones(m))).toEqual(m);\n });\n\n it.each([\n ['missing', undefined],\n ['null', null],\n ['blank', ''],\n ['malformed JSON', '{level:'],\n ['non-object', '\"3\"'],\n ['JSON null', 'null']\n ])('falls back to defaults (never throws) for %s', (_label, raw) => {\n expect(ParseUxMilestones(raw as string | null | undefined)).toEqual(DefaultUxMilestones());\n });\n\n it('repairs field-wise: bad level/calls/density fall back individually', () => {\n expect(ParseUxMilestones('{\"level\":\"high\",\"calls\":2,\"density\":\"standard\"}'))\n .toEqual({ Level: 0, Calls: 2, Density: 'standard' });\n expect(ParseUxMilestones('{\"level\":3,\"calls\":-4,\"density\":\"ultra\"}'))\n .toEqual({ Level: 3, Calls: 0, Density: 'auto' });\n expect(ParseUxMilestones('{\"level\":99,\"calls\":1.7,\"density\":\"simple\"}'))\n .toEqual({ Level: REALTIME_UX_MAX_LEVEL, Calls: 2, Density: 'simple' });\n });\n});\n\ndescribe('EffectiveBaseLevel', () => {\n it('day one: a brand-new user starts at PURE AUDIO (level 0)', () => {\n expect(EffectiveBaseLevel(DefaultUxMilestones())).toBe(0);\n });\n\n it('auto density follows the earned ratchet', () => {\n expect(EffectiveBaseLevel({ Level: 2, Calls: 1, Density: 'auto' })).toBe(2);\n });\n\n it(`auto density promotes to the full console after ${REALTIME_UX_PRO_CALLS} completed calls`, () => {\n expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS, Density: 'auto' })).toBe(REALTIME_UX_MAX_LEVEL);\n expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS - 1, Density: 'auto' })).toBe(1);\n });\n\n it('manual density overrides win outright (the escape hatch)', () => {\n const earned = { Level: 4, Calls: 10, Density: 'simple' as const };\n expect(EffectiveBaseLevel(earned)).toBe(0);\n expect(EffectiveBaseLevel({ ...earned, Density: 'standard' })).toBe(2);\n expect(EffectiveBaseLevel({ Level: 0, Calls: 0, Density: 'pro' })).toBe(REALTIME_UX_MAX_LEVEL);\n });\n});\n\ndescribe('RatchetedMilestones (call end)', () => {\n it('increments calls and ratchets the level to the session peak', () => {\n expect(RatchetedMilestones({ Level: 1, Calls: 1, Density: 'auto' }, 3))\n .toEqual({ Level: 3, Calls: 2, Density: 'auto' });\n });\n\n it('a completed first call always earns at least level 1 (text next time)', () => {\n expect(RatchetedMilestones(DefaultUxMilestones(), 0))\n .toEqual({ Level: 1, Calls: 1, Density: 'auto' });\n });\n\n it('NEVER lowers the earned level (ratchet)', () => {\n expect(RatchetedMilestones({ Level: 4, Calls: 5, Density: 'auto' }, 0).Level).toBe(4);\n });\n\n it('preserves the density override', () => {\n expect(RatchetedMilestones({ Level: 0, Calls: 0, Density: 'simple' }, 2).Density).toBe('simple');\n });\n});\n\ndescribe('RealtimeDisclosureModel', () => {\n it('Load resets the session level to the effective base', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));\n expect(m.SessionLevel).toBe(2);\n expect(m.ShowThread).toBe(true);\n expect(m.ShowComposer).toBe(true);\n expect(m.ShowPanel).toBe(true);\n expect(m.PowerLevel).toBe(false);\n });\n\n it('day one exposes NOTHING beyond pure audio', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n expect(m.SessionLevel).toBe(0);\n expect(m.ShowThread).toBe(false);\n expect(m.ShowComposer).toBe(false);\n expect(m.ShowPanel).toBe(false);\n expect(m.ShowGear).toBe(false);\n });\n\n it('Raise only raises — and reports whether anything changed', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n expect(m.Raise('engaged')).toBe(true);\n expect(m.SessionLevel).toBe(2);\n expect(m.Raise('text')).toBe(false); // lower event after a higher one: no-op\n expect(m.SessionLevel).toBe(2);\n expect(m.Raise('channel')).toBe(true);\n expect(m.SessionLevel).toBe(3);\n });\n\n it('emits Changed$ on raises that change the level (and not on no-ops)', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n let emissions = 0;\n m.Changed$.subscribe(() => emissions++);\n m.Raise('text');\n m.Raise('text');\n expect(emissions).toBe(1);\n });\n\n it('BeginSession resets the volatile level back to the base', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n m.Raise('channel');\n m.BeginSession();\n expect(m.SessionLevel).toBe(0);\n });\n\n it('ReturnToPureAudio steps the volatile level back to 0 WITHOUT touching the ratchet', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));\n m.Raise('power');\n let emissions = 0;\n m.Changed$.subscribe(() => emissions++);\n\n m.ReturnToPureAudio();\n expect(m.SessionLevel).toBe(0);\n expect(m.ShowThread).toBe(false);\n expect(m.Milestones.Level).toBe(2); // the cross-session ratchet is untouched\n m.ReturnToPureAudio(); // already there — no extra emission\n expect(emissions).toBe(1);\n\n // the user can step back up afterward — the door swings both ways\n expect(m.Raise('text')).toBe(true);\n expect(m.SessionLevel).toBe(1);\n });\n\n it('SetDensity applies immediately — simple retracts even a mid-call console', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 4, Calls: 9, Density: 'auto' }));\n expect(m.SessionLevel).toBe(4);\n m.SetDensity('simple');\n expect(m.SessionLevel).toBe(0);\n expect(m.Milestones.Density).toBe('simple');\n m.SetDensity('pro');\n expect(m.SessionLevel).toBe(REALTIME_UX_MAX_LEVEL);\n });\n\n it('RatchetOnSessionEnd replaces milestones and returns the persistable payload', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n m.Raise('channel');\n const serialized = m.RatchetOnSessionEnd();\n expect(m.Milestones).toEqual({ Level: 3, Calls: 1, Density: 'auto' });\n expect(ParseUxMilestones(serialized)).toEqual(m.Milestones);\n });\n});\n"]}