@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,13 +1,22 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import { BehaviorSubject, Subject } from 'rxjs';
3
3
  import { Metadata } from '@memberjunction/core';
4
+ import { UserInfoEngine } from '@memberjunction/core-entities';
4
5
  import { AIEngineBase } from '@memberjunction/ai-engine-base';
5
6
  import { MJGlobal } from '@memberjunction/global';
6
7
  import { BaseRealtimeClient, LoadAssemblyAIRealtimeClient, LoadElevenLabsRealtimeClient, LoadGeminiRealtimeClient, LoadOpenAIRealtimeClient, LoadxAIRealtimeClient } from '@memberjunction/ai-realtime-client';
7
8
  import { BuildNarrationInstructions } from './narration-template';
8
9
  import { ParseDelegationResultJson } from './delegation-result-parser';
9
10
  import { BaseRealtimeChannelClient } from '../components/realtime/channels/base-realtime-channel-client';
11
+ import { RealtimeAudioRecorder } from './realtime-audio-recorder';
10
12
  import * as i0 from "@angular/core";
13
+ /**
14
+ * `MJ: User Settings` key for the per-user "record this voice call" consent toggle. Stored as
15
+ * the literal string `'true'`/`'false'` (read with `=== 'true'`), cross-device via
16
+ * {@link UserInfoEngine}. The pre-call picker writes it; the session service reads it as the
17
+ * default when the caller doesn't pass an explicit consent value.
18
+ */
19
+ export const REALTIME_RECORDING_CONSENT_KEY = 'mj.realtimeVoice.recordingConsent.v1';
11
20
  // Tree-shaking prevention: the OpenAI client is resolved dynamically through the
12
21
  // ClassFactory (by the server-reported Provider key), so this static call is what keeps
13
22
  // its @RegisterClass side effect from being eliminated by the bundler.
@@ -37,7 +46,7 @@ LoadxAIRealtimeClient();
37
46
  * bakes the companion instructions + tool set into `SessionConfigJson`, which the client
38
47
  * driver applies verbatim.
39
48
  *
40
- * Lifecycle: {@link StartVoiceSession} → live duplex → {@link EndVoiceSession}.
49
+ * Lifecycle: {@link StartRealtimeSession} → live duplex → {@link EndRealtimeSession}.
41
50
  */
42
51
  export class RealtimeSessionService {
43
52
  // ── Reactive UI state ──────────────────────────────────────────────────────
@@ -75,7 +84,7 @@ export class RealtimeSessionService {
75
84
  /** Terminal result of a delegation, so the overlay can complete the working card with real content. */
76
85
  DelegationResult$ = this._delegationResult$.asObservable();
77
86
  /**
78
- * EPHEMERAL spoken progress narrations (see {@link VoiceDelegationNarration}). These are
87
+ * EPHEMERAL spoken progress narrations (see {@link RealtimeDelegationNarration}). These are
79
88
  * deliberately kept OUT of {@link Captions$} and never relayed/persisted — the overlay
80
89
  * renders them as a transient "live note" near the active working card.
81
90
  */
@@ -124,7 +133,7 @@ export class RealtimeSessionService {
124
133
  * Fired EXACTLY ONCE per session as teardown begins, with the prior
125
134
  * `agentSessionId` (so subscribers can correlate against `SessionStarted$`'s
126
135
  * sessionId) and the client-distinguishable reason — `'explicit'` when the
127
- * user called `EndVoiceSession`, `'error'` when teardown ran from a catch
136
+ * user called `EndRealtimeSession`, `'error'` when teardown ran from a catch
128
137
  * block. Server-side close paths (janitor, shutdown) do NOT propagate here —
129
138
  * they happen out-of-process and have no client push channel today.
130
139
  */
@@ -141,6 +150,18 @@ export class RealtimeSessionService {
141
150
  get ActiveChannels() {
142
151
  return this._activeChannels$.value;
143
152
  }
153
+ /**
154
+ * The `ChannelName`s the agent has used (acted on) at least once this session. The overlay
155
+ * uses this to register a channel's surface tab only after it has come into play. A fresh
156
+ * Set snapshot so callers can't mutate the service's tracking.
157
+ */
158
+ get UsedChannelNames() {
159
+ return new Set(this.usedChannelNames);
160
+ }
161
+ /** Whether the agent has used (acted on) the named channel at least once this session. */
162
+ HasChannelBeenUsed(channelName) {
163
+ return this.usedChannelNames.has(channelName);
164
+ }
144
165
  /** Synchronous access to the display name of the agent the active session fronts. */
145
166
  get CurrentAgentName() {
146
167
  return this._agentName$.value;
@@ -190,12 +211,69 @@ export class RealtimeSessionService {
190
211
  /** The mic capture stream — acquired here (permission UX) and handed to the client. */
191
212
  localStream = null;
192
213
  agentSessionId = null;
214
+ /**
215
+ * The application the active session runs in (sources the server-side app config cascade +
216
+ * RelevantAgents → allowed-agent union, and the default-agent chain). `null` when no app context
217
+ * was supplied. Set at {@link StartRealtimeSession}; sent to the mint mutation.
218
+ */
219
+ applicationId = null;
220
+ /**
221
+ * The live app-context snapshot (where the user is, what they see, the capability manifest),
222
+ * pushed by the host (Explorer) at session start and on subsequent changes via
223
+ * {@link UpdateAppContext}. The headless {@link import('../components/realtime/channels/client-context-channel').ClientContextChannel}
224
+ * subscribes to {@link AppContext$} and streams deltas to the model via `SendContextNote`.
225
+ */
226
+ _appContext$ = new BehaviorSubject(null);
227
+ /** Observable of the live app-context snapshot (see {@link _appContext$}). */
228
+ AppContext$ = this._appContext$.asObservable();
229
+ /**
230
+ * Push an updated app-context snapshot mid-session (the continuous-streaming half of client-context
231
+ * delivery). The host (Explorer) calls this when the user navigates / the active surface's state or
232
+ * capability manifest changes; the ClientContextChannel turns the delta into a `SendContextNote`.
233
+ * No-op semantics when no session is live — the channel simply re-reads on next start.
234
+ *
235
+ * @param snapshot The latest app-context snapshot (or null to clear).
236
+ */
237
+ UpdateAppContext(snapshot) {
238
+ this._appContext$.next(snapshot);
239
+ }
193
240
  /**
194
241
  * The DB-driven narration instruction template (server-resolved at session start, containing a
195
242
  * `{{ progressMessage }}` placeholder). `null` when the deployment hasn't synced the narration
196
243
  * prompt — {@link buildNarrationInstructions} then falls back to the built-in wording.
197
244
  */
198
245
  narrationTemplate = null;
246
+ // ── Browser-side call recording ────────────────────────────────────────────
247
+ /**
248
+ * The active session's audio recorder (mic + agent mix), or `null` when the user didn't
249
+ * consent or the browser can't record. Created after the client connects; stopped + uploaded
250
+ * at teardown.
251
+ */
252
+ recorder = null;
253
+ /** ISO timestamp of when recording started — sent to the server on session start. */
254
+ recordingStartedAtIso = null;
255
+ /** Interval that flushes ~15s crash-recovery shards to the server during a recording. */
256
+ segmentTimer = null;
257
+ /** 0-based index of the next recording shard to upload. */
258
+ segmentIndex = 0;
259
+ /** How often crash-recovery shards are flushed during a recording. */
260
+ static SegmentFlushMs = 15000;
261
+ /**
262
+ * Recording-relative ms offset at which the IN-FLIGHT (not-yet-finalized) turn's audio
263
+ * actually BEGAN — captured the moment that turn's audio/text starts flowing (its first
264
+ * interim transcript), NOT inherited from the previous turn's end. `null` before the first
265
+ * turn / between turns (until the next turn's audio starts). Sent as `utteranceStartMs` on
266
+ * the turn's final transcript so per-turn timing lines up with the recording even when a
267
+ * tool-call / silence gap sits between turns (the inherit-previous-end model mis-stamped
268
+ * the post-gap turn at the pre-gap offset). See {@link markTurnAudioStart}.
269
+ */
270
+ currentTurnStartMs = null;
271
+ /**
272
+ * Per-turn guard for {@link markTurnAudioStart}: `true` once the in-flight turn's audio-start
273
+ * offset has been captured, so mid-turn interim deltas don't overwrite it. Reset to `false`
274
+ * at each finalization so the NEXT turn re-stamps from where ITS audio begins.
275
+ */
276
+ turnAudioStartCaptured = false;
199
277
  // ── Delegated-run progress streaming ───────────────────────────────────────
200
278
  /** First spoken update fires no earlier than this long after delegated work starts. */
201
279
  static FirstNarrationDelayMs = 5000;
@@ -268,6 +346,14 @@ export class RealtimeSessionService {
268
346
  * the final save still lands on the just-closed session.
269
347
  */
270
348
  pendingChannelSaves = new Map();
349
+ /**
350
+ * `ChannelName`s the agent has ACTED ON at least once this session (the channel's first
351
+ * tool call routed to its local executor). The overlay reads this to decide which channel
352
+ * surface tabs to register: a channel earns its tab only once it's been used (the
353
+ * whiteboard is the sole exception — it tabs immediately, since a user may draw first).
354
+ * Reset at session start via {@link resetState}.
355
+ */
356
+ usedChannelNames = new Set();
271
357
  _provider = null;
272
358
  /**
273
359
  * Metadata provider used for the GraphQL relay mutations. Falls back to the
@@ -312,22 +398,47 @@ export class RealtimeSessionService {
312
398
  * authorization on any overrides — hosts only populate this from authorization-gated
313
399
  * pickers, and never synthesize overrides beyond what the user explicitly chose.
314
400
  * Omit/`null` for the server's defaults (today's behavior).
401
+ * @param recordingConsent Optional EXPLICIT "record this call" consent for THIS session. When
402
+ * `true`, the browser records a mic + agent-audio mix and uploads it at session end. When
403
+ * omitted/`null`, the per-user persisted preference (`mj.realtimeVoice.recordingConsent.v1`
404
+ * via {@link UserInfoEngine}) is read as the default. `false` never records.
405
+ * @param mediaCollectionId Optional per-session media-kit override (`MJ: Collections.ID`). When set,
406
+ * the server-side Media channel resolves THIS collection as the agent's media kit for the session,
407
+ * taking precedence over the agent's `DefaultMediaCollectionID`. The server UUID-validates it
408
+ * (malformed ⇒ ignored, the agent default applies). Omit/`null` to use the agent default kit.
315
409
  */
316
- async StartVoiceSession(targetAgentId, conversationId, lastSessionId, agentName, preferredModelId, clientTools, coAgentId, configOverridesJson) {
410
+ async StartRealtimeSession(targetAgentId, conversationId, lastSessionId, agentName, preferredModelId, clientTools, coAgentId, configOverridesJson, recordingConsent, mediaCollectionId, applicationId, appContext) {
317
411
  if (this.IsActive) {
318
412
  return; // a session is already running — ignore duplicate starts
319
413
  }
414
+ // App awareness (Move 1/3/4): the application the session runs in (sources the app config
415
+ // cascade + RelevantAgents → allowed-agent union) and the live app-context snapshot injected
416
+ // into the companion prompt at mint. Stored so the ClientContextChannel can stream subsequent
417
+ // deltas. Absent ⇒ no app layer / no mint-time context (the pre-app behavior).
418
+ this.applicationId = applicationId ?? null;
419
+ // Prefer the explicit param, but fall back to the snapshot the host has ALREADY pushed via
420
+ // UpdateAppContext (explorer-app streams the live snapshot continuously). The overlay's
421
+ // [appContext] binding can still read null at the instant the mic is clicked — without this
422
+ // fallback, StartRealtimeSession(null) would clobber a perfectly good snapshot and mint the
423
+ // companion prompt with no app context (no NavigableApps / no tool schemas → the co-agent guesses
424
+ // parameter names and navigation fails). Never overwrite a good value with null.
425
+ const effectiveAppContext = appContext ?? this._appContext$.value;
426
+ this._appContext$.next(effectiveAppContext);
320
427
  if (agentName) {
321
428
  this._agentName$.next(agentName);
322
429
  }
323
430
  this.resetState();
324
431
  this._active$.next(true);
325
432
  this._connectionState$.next('connecting');
433
+ // Resolve recording consent for this session: explicit value wins, else the per-user
434
+ // persisted preference. Computed before mint so it can be reported to the server.
435
+ const consent = recordingConsent ?? this.readPersistedRecordingConsent();
436
+ this.recordingStartedAtIso = consent ? new Date().toISOString() : null;
326
437
  try {
327
438
  // Resolve + initialize the interactive-channel plugins FIRST: their client-executed
328
439
  // tool sets must be declared to the realtime model at session mint.
329
440
  const allClientTools = [...(clientTools ?? []), ...(await this.startChannels())];
330
- const session = await this.mintSession(targetAgentId, conversationId, lastSessionId, preferredModelId, allClientTools, coAgentId, configOverridesJson);
441
+ const session = await this.mintSession(targetAgentId, conversationId, lastSessionId, preferredModelId, allClientTools, coAgentId, configOverridesJson, consent, this.recordingStartedAtIso, mediaCollectionId, this.applicationId, effectiveAppContext);
331
442
  this.agentSessionId = session.AgentSessionId;
332
443
  // A null input conversationId means the SERVER created a fresh conversation for
333
444
  // this session — track it so the host can fold it into the cached list, select
@@ -345,6 +456,14 @@ export class RealtimeSessionService {
345
456
  this.wireClientHandlers(client);
346
457
  this.localStream = await navigator.mediaDevices.getUserMedia({ audio: true });
347
458
  await client.Connect(this.buildClientConfig(session), this.localStream);
459
+ // Start browser-side recording (mic + agent mix) when consented. Best-effort: an
460
+ // unsupported browser / missing remote stream degrades gracefully (mic-only or off)
461
+ // and never blocks the call. The remote stream may still be null here (the WebRTC
462
+ // ontrack can land slightly after Connect resolves) — the recorder mixes the mic now
463
+ // and the agent audio rides through whenever its track is already attached.
464
+ if (consent) {
465
+ this.startRecording(client);
466
+ }
348
467
  this.subscribeDelegationProgress();
349
468
  // State advances to 'listening' once the provider control channel opens
350
469
  // (driven by the client's OnStateChange events).
@@ -367,7 +486,7 @@ export class RealtimeSessionService {
367
486
  * End the active session: stop the mic, tear down the provider connection, and close
368
487
  * the server-side agent session. Safe to call when no session is active.
369
488
  */
370
- async EndVoiceSession() {
489
+ async EndRealtimeSession() {
371
490
  if (!this.IsActive && !this.agentSessionId) {
372
491
  return;
373
492
  }
@@ -445,6 +564,177 @@ export class RealtimeSessionService {
445
564
  GetAudioActivity() {
446
565
  return this.client?.GetAudioActivity() ?? null;
447
566
  }
567
+ // ── Browser-side call recording ────────────────────────────────────────────
568
+ /**
569
+ * Reads the per-user recording-consent preference from `MJ: User Settings` (via
570
+ * {@link UserInfoEngine}'s synchronous cache). Defensive: any failure resolves to `false`
571
+ * (don't record) so a settings hiccup can never opt a user into recording.
572
+ */
573
+ readPersistedRecordingConsent() {
574
+ try {
575
+ return UserInfoEngine.Instance.GetSetting(REALTIME_RECORDING_CONSENT_KEY) === 'true';
576
+ }
577
+ catch {
578
+ return false;
579
+ }
580
+ }
581
+ /**
582
+ * Starts the browser-side recorder (mic + agent-audio mix). Best-effort — any failure is
583
+ * contained so it never disturbs the live call; an unsupported browser simply records
584
+ * nothing (the recorder disables itself).
585
+ */
586
+ startRecording(client) {
587
+ try {
588
+ if (!this.localStream) {
589
+ return;
590
+ }
591
+ const remoteStream = client.GetRemoteMediaStream?.() ?? null;
592
+ const recorder = new RealtimeAudioRecorder();
593
+ recorder.Start(this.localStream, remoteStream);
594
+ this.recorder = recorder.IsRecording ? recorder : null;
595
+ // First turn's audio starts at ~0 (recording begins right as the call goes live). Seed it
596
+ // here so the very first turn has a sane start even if its first interim is missed; later
597
+ // turns re-stamp from where THEIR audio begins via markTurnAudioStart (handles tool gaps).
598
+ this.currentTurnStartMs = recorder.IsRecording ? 0 : null;
599
+ this.turnAudioStartCaptured = false;
600
+ if (this.recorder) {
601
+ // The agent's WebRTC audio track usually lands AFTER Connect() resolves, so `remoteStream`
602
+ // above is typically null and we'd capture mic-only. Attach the agent stream whenever it
603
+ // arrives (fires immediately if already present) so the recording includes the agent voice.
604
+ client.OnRemoteMediaStream?.((stream) => this.recorder?.AttachRemoteStream(stream));
605
+ this.startSegmentFlushing();
606
+ }
607
+ }
608
+ catch (error) {
609
+ console.warn('[RealtimeSession] Failed to start call recording:', error);
610
+ this.recorder = null;
611
+ }
612
+ }
613
+ /** Begins flushing ~15s crash-recovery shards to the server for the duration of the recording. */
614
+ startSegmentFlushing() {
615
+ this.segmentIndex = 0;
616
+ this.segmentTimer = setInterval(() => { void this.flushRecordingSegment(); }, RealtimeSessionService.SegmentFlushMs);
617
+ }
618
+ /** Stops the periodic crash-recovery shard flush. */
619
+ stopSegmentFlushing() {
620
+ if (this.segmentTimer) {
621
+ clearInterval(this.segmentTimer);
622
+ this.segmentTimer = null;
623
+ }
624
+ }
625
+ /**
626
+ * Uploads the chunks captured since the last flush as one crash-recovery shard (durability only;
627
+ * the canonical file is still the full upload at teardown). Best-effort — never disturbs the call.
628
+ */
629
+ async flushRecordingSegment() {
630
+ const recorder = this.recorder;
631
+ const agentSessionId = this.agentSessionId;
632
+ if (!recorder || !agentSessionId) {
633
+ return;
634
+ }
635
+ const segment = recorder.SnapshotNewSegment();
636
+ if (!segment || segment.size === 0) {
637
+ return;
638
+ }
639
+ try {
640
+ const audioBase64 = await this.blobToBase64(segment);
641
+ if (!audioBase64) {
642
+ return;
643
+ }
644
+ const index = this.segmentIndex++;
645
+ const mutation = `
646
+ mutation UploadRealtimeRecordingSegment($agentSessionId: String!, $segmentIndex: Int!, $audioBase64: String!, $mimeType: String!) {
647
+ UploadRealtimeRecordingSegment(agentSessionId: $agentSessionId, segmentIndex: $segmentIndex, audioBase64: $audioBase64, mimeType: $mimeType)
648
+ }
649
+ `;
650
+ // Shards are HEADER-LESS raw little-endian PCM16 (mime audio/L16 with the capture sample rate),
651
+ // NOT individually-playable WAV — recovery is concatenate-in-order then WAV-wrap. The canonical
652
+ // seekable WAV is the consolidated end-of-call upload below.
653
+ const shardMime = `audio/L16;rate=${recorder.SampleRate}`;
654
+ await this.gql().ExecuteGQL(mutation, { agentSessionId, segmentIndex: index, audioBase64, mimeType: shardMime });
655
+ }
656
+ catch (error) {
657
+ console.warn('[RealtimeSession] Failed to flush recording shard:', error);
658
+ }
659
+ }
660
+ /**
661
+ * Stops the active recorder and uploads the captured audio via `UploadRealtimeRecording`.
662
+ * Fully best-effort and wrapped in try/catch — recording upload must NEVER block teardown.
663
+ * No-op when nothing was recorded or there's no session id to attach the file to.
664
+ */
665
+ async stopAndUploadRecording(agentSessionId) {
666
+ this.stopSegmentFlushing();
667
+ const recorder = this.recorder;
668
+ this.recorder = null;
669
+ this.currentTurnStartMs = null;
670
+ this.turnAudioStartCaptured = false;
671
+ if (!recorder) {
672
+ return;
673
+ }
674
+ try {
675
+ // Capture the recorder MIME (now 'audio/wav') BEFORE Stop() — the getter reads '' once stopped.
676
+ const mimeType = recorder.MimeType;
677
+ const blob = await recorder.Stop();
678
+ // Read the real waveform peaks computed during capture (survives Stop via the recorder's snapshot).
679
+ const peaks = recorder.GetPeaks();
680
+ if (!blob || blob.size === 0 || !agentSessionId) {
681
+ console.warn('[RealtimeSession] ⚠️ recording NOT uploaded — empty blob or no session id.');
682
+ return;
683
+ }
684
+ const audioBase64 = await this.blobToBase64(blob);
685
+ if (!audioBase64) {
686
+ console.warn('[RealtimeSession] ⚠️ recording NOT uploaded — base64 encoding failed.');
687
+ return;
688
+ }
689
+ await this.uploadRecording(agentSessionId, audioBase64, mimeType, peaks);
690
+ }
691
+ catch (error) {
692
+ console.warn('[RealtimeSession] Failed to stop/upload call recording:', error);
693
+ }
694
+ }
695
+ /**
696
+ * Runs the `UploadRealtimeRecording` mutation; failures are logged, never thrown. Sends the
697
+ * capture-time waveform `peaks` (max-abs per bucket, normalized 0..1) so the server can persist a
698
+ * `peaks.json` sidecar for fast waveform rendering without re-decoding the audio.
699
+ */
700
+ async uploadRecording(agentSessionId, audioBase64, mimeType, peaks) {
701
+ const mutation = `
702
+ mutation UploadRealtimeRecording($agentSessionId: String!, $audioBase64: String!, $mimeType: String!, $consent: Boolean, $peaks: [Float!]) {
703
+ UploadRealtimeRecording(agentSessionId: $agentSessionId, audioBase64: $audioBase64, mimeType: $mimeType, consent: $consent, peaks: $peaks) {
704
+ Success
705
+ FileID
706
+ ErrorMessage
707
+ }
708
+ }
709
+ `;
710
+ const result = await this.gql().ExecuteGQL(mutation, {
711
+ agentSessionId,
712
+ audioBase64,
713
+ mimeType,
714
+ consent: true,
715
+ peaks
716
+ });
717
+ const payload = result?.UploadRealtimeRecording;
718
+ if (!payload?.Success) {
719
+ console.warn(`[RealtimeSession] ❌ recording upload reported failure: ${payload?.ErrorMessage ?? 'unknown error'} (full result: ${JSON.stringify(result)})`);
720
+ }
721
+ }
722
+ /**
723
+ * Encodes a {@link Blob} as a base64 string (the data: prefix stripped) via {@link FileReader}.
724
+ * Resolves `''` on any read failure so the upload path can bail cleanly.
725
+ */
726
+ blobToBase64(blob) {
727
+ return new Promise((resolve) => {
728
+ const reader = new FileReader();
729
+ reader.onloadend = () => {
730
+ const result = typeof reader.result === 'string' ? reader.result : '';
731
+ const commaIndex = result.indexOf(',');
732
+ resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : '');
733
+ };
734
+ reader.onerror = () => resolve('');
735
+ reader.readAsDataURL(blob);
736
+ });
737
+ }
448
738
  // ── Interactive channels (registry-driven plugins) ─────────────────────────
449
739
  /**
450
740
  * Resolves, instantiates and initializes the session's interactive-channel plugins from
@@ -532,7 +822,10 @@ export class RealtimeSessionService {
532
822
  plugin.Initialize(this.buildChannelContext(plugin));
533
823
  this.RegisterClientToolHandler(plugin.ToolNamePrefix, (toolName, argsJson) => {
534
824
  // The agent is ACTING on this channel — surface-discovery signal for the overlay
535
- // (first activity auto-reveals + focuses the channel tab) before the tool applies.
825
+ // (first activity registers + auto-reveals + focuses the channel tab) before the
826
+ // tool applies. Record the channel as USED so the overlay tabs it (channels other
827
+ // than the whiteboard are tab-less until they're first used).
828
+ this.usedChannelNames.add(plugin.ChannelName);
536
829
  this._channelActivity$.next(plugin);
537
830
  return plugin.ApplyAgentTool(toolName, argsJson);
538
831
  });
@@ -544,6 +837,11 @@ export class RealtimeSessionService {
544
837
  const service = this;
545
838
  return {
546
839
  AgentName: this.CurrentAgentName,
840
+ // The live session's provider — threaded by channels into MJ-backed surfaces (e.g. the Media
841
+ // channel's mj-storage-media-player / CreateMediaAccessToken). `get` so it stays current.
842
+ get Provider() {
843
+ return service.Provider;
844
+ },
547
845
  SendContextNote: (text) => this.SendContextNote(text),
548
846
  RequestSpokenResponse: (instructions) => this.requestChannelSpokenResponse(instructions),
549
847
  RequestSave: (stateJson) => this.scheduleChannelSave(plugin.ChannelName, stateJson),
@@ -555,9 +853,62 @@ export class RealtimeSessionService {
555
853
  get AgentSessionID() {
556
854
  return service.agentSessionId;
557
855
  },
558
- ExecuteServerAction: (query, variables) => this.executeChannelServerAction(query, variables)
856
+ ExecuteServerAction: (query, variables) => this.executeChannelServerAction(query, variables),
857
+ // App-context stream + client-tool execution for the headless ClientContextChannel. The host
858
+ // (Explorer) feeds both; absent on hosts that supply no app context / register no client tools.
859
+ AppContext$: this.AppContext$,
860
+ ExecuteClientTool: (name, params) => this.executeAppClientTool(name, params)
559
861
  };
560
862
  }
863
+ /**
864
+ * Host registry of surface CLIENT TOOLS (Name → handler), fed by the host (Explorer) from the
865
+ * active surface's `SetAgentClientTools`. The headless ClientContextChannel's `ContextTool` proxy
866
+ * executes against this via {@link executeAppClientTool}. Keys are lower-cased for case-insensitive
867
+ * model-supplied action names.
868
+ */
869
+ appClientToolHandlers = new Map();
870
+ /**
871
+ * Replaces the set of host-registered surface client tools the realtime ContextTool can execute.
872
+ * The host calls this at session start and whenever the active surface's tool set changes (the
873
+ * continuous-capability half of client-context delivery). Passing `[]` clears them.
874
+ *
875
+ * @param tools The current surface client tools (name + handler). Descriptions/schemas ride the
876
+ * app-context manifest separately; only the executable handler is needed here.
877
+ */
878
+ RegisterAppClientTools(tools) {
879
+ this.appClientToolHandlers.clear();
880
+ for (const tool of tools) {
881
+ if (tool?.Name && typeof tool.Handler === 'function') {
882
+ this.appClientToolHandlers.set(tool.Name.trim().toLowerCase(), tool.Handler);
883
+ }
884
+ }
885
+ }
886
+ /**
887
+ * Executes a host-registered surface client tool by name (the {@link RealtimeChannelContext.ExecuteClientTool}
888
+ * implementation). Tolerant: an unknown tool or a thrown handler resolves to a structured
889
+ * `Success: false` result the channel narrates — never throws.
890
+ *
891
+ * @param name The tool name (the model's `action`).
892
+ * @param params The tool parameters.
893
+ * @returns A structured result for the channel to serialize back to the model.
894
+ */
895
+ async executeAppClientTool(name, params) {
896
+ const handler = this.appClientToolHandlers.get((name ?? '').trim().toLowerCase());
897
+ if (!handler) {
898
+ const available = Array.from(this.appClientToolHandlers.keys()).join(', ');
899
+ return {
900
+ Success: false,
901
+ ErrorMessage: `No client tool named "${name}" is available on this surface. Available: ${available || '(none)'}.`
902
+ };
903
+ }
904
+ try {
905
+ const result = await handler(params ?? {});
906
+ return { Success: true, Result: result };
907
+ }
908
+ catch (error) {
909
+ return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };
910
+ }
911
+ }
561
912
  /**
562
913
  * Runs a channel-specific GraphQL operation through the live session's provider (the
563
914
  * {@link RealtimeChannelContext.ExecuteServerAction} implementation). Best-effort: any
@@ -711,6 +1062,7 @@ export class RealtimeSessionService {
711
1062
  if (this._activeChannels$.value.length > 0) {
712
1063
  this._activeChannels$.next([]);
713
1064
  }
1065
+ this.usedChannelNames.clear();
714
1066
  }
715
1067
  // ── Realtime client resolution + wiring ────────────────────────────────────
716
1068
  /**
@@ -794,7 +1146,7 @@ export class RealtimeSessionService {
794
1146
  }
795
1147
  }
796
1148
  /**
797
- * Translates {@link RealtimeClientState} into {@link VoiceConnectionState}. `'connected'`
1149
+ * Translates {@link RealtimeClientState} into {@link RealtimeConnectionState}. `'connected'`
798
1150
  * is suppressed (the UI stays 'connecting' until the control channel opens → 'listening'),
799
1151
  * and `'closed'` never overwrites a terminal 'error' the service itself recorded.
800
1152
  */
@@ -821,14 +1173,21 @@ export class RealtimeSessionService {
821
1173
  }
822
1174
  // ── Transcript policy ──────────────────────────────────────────────────────
823
1175
  /**
824
- * Applies transcript policy to client transcript events. Interim deltas are ignored
825
- * (the client already drives the speaking state). Final NORMAL assistant turns become
1176
+ * Applies transcript policy to client transcript events. Interim deltas don't become
1177
+ * captions/turns (the client already drives the speaking state) but DO mark this turn's
1178
+ * audio-start offset against the recording (the first interim fires as the audio/text
1179
+ * starts flowing — see {@link markTurnAudioStart}). Final NORMAL assistant turns become
826
1180
  * captions + persisted transcripts; final NARRATION turns are EPHEMERAL by product
827
1181
  * decision — emitted on {@link DelegationNarration$} only, never a caption, never
828
1182
  * relayed/persisted. User turns ride the caption + relay path.
829
1183
  */
830
1184
  async onClientTranscript(transcript) {
831
1185
  if (!transcript.IsFinal) {
1186
+ // First interim of a NEW turn = that turn's audio is starting NOW. Stamp the
1187
+ // recording-relative start here so a turn whose audio begins AFTER a tool-call /
1188
+ // silence gap is timed where its audio really is — not inherited from the prior
1189
+ // turn's end. Narration interims are ephemeral and excluded (Kind guard inside).
1190
+ this.markTurnAudioStart(transcript.Kind);
832
1191
  return;
833
1192
  }
834
1193
  if (transcript.Role === 'Assistant') {
@@ -864,6 +1223,34 @@ export class RealtimeSessionService {
864
1223
  await this.onUserTranscript(transcript.Text);
865
1224
  }
866
1225
  }
1226
+ /**
1227
+ * Stamps the recording-relative offset at which the IN-FLIGHT turn's audio actually began,
1228
+ * the moment that turn's audio/text first starts flowing (its FIRST interim transcript).
1229
+ *
1230
+ * This is the fix for transcript cues drifting out of sync with the audio when a tool-call /
1231
+ * silence gap sits between turns: the old model inherited the next turn's start from the
1232
+ * PREVIOUS turn's end (assumes contiguous turns), so a post-gap turn's cue pointed ~gap-length
1233
+ * too early. Capturing the start where the audio truly begins keeps the cue aligned.
1234
+ *
1235
+ * Guards:
1236
+ * - only when recording ({@link recorder} present),
1237
+ * - only ONCE per turn ({@link turnAudioStartCaptured}) so mid-turn interim deltas don't move it,
1238
+ * - NORMAL turns only — NARRATION interims are ephemeral and never persisted, so they must not
1239
+ * claim the next real turn's start slot.
1240
+ *
1241
+ * Works for any role whose driver surfaces interim deltas (all drivers for the assistant; the
1242
+ * relevant case here — the post-tool-gap assistant answer — and user-interim drivers like
1243
+ * Gemini/AssemblyAI). For final-only user turns (OpenAI/xAI/ElevenLabs) no interim arrives, so
1244
+ * {@link relayTranscript} falls back to the seeded/prior start — the gap case that drifts is the
1245
+ * assistant answer, which always has interims.
1246
+ */
1247
+ markTurnAudioStart(kind) {
1248
+ if (!this.recorder || this.turnAudioStartCaptured || kind === 'narration') {
1249
+ return;
1250
+ }
1251
+ this.currentTurnStartMs = this.recorder.NowOffsetMs();
1252
+ this.turnAudioStartCaptured = true;
1253
+ }
867
1254
  /**
868
1255
  * Replaces the LAST caption of `role` in place (correction semantics); falls back to a
869
1256
  * plain append when no such caption exists yet (e.g. the superseded turn predates this
@@ -977,8 +1364,8 @@ export class RealtimeSessionService {
977
1364
  * {@link ParseDelegationResultJson}; if it isn't JSON, surfaces the raw string. Only delegation
978
1365
  * cards (created from progress events) react — non-delegation tool results have no card and are
979
1366
  * harmlessly ignored downstream. The `runId` (the delegated `MJ: AI Agent Runs` record) rides
980
- * along as {@link VoiceDelegationResult.RunID} for the overlay's dev links, and any `artifacts`
981
- * ride along as {@link VoiceDelegationResult.Artifacts} for the surface panel's artifact tabs.
1367
+ * along as {@link RealtimeDelegationResult.RunID} for the overlay's dev links, and any `artifacts`
1368
+ * ride along as {@link RealtimeDelegationResult.Artifacts} for the surface panel's artifact tabs.
982
1369
  */
983
1370
  emitDelegationResult(callId, resultJson) {
984
1371
  // The result will be spoken next — a deferred interim update is now pointless
@@ -1091,10 +1478,10 @@ export class RealtimeSessionService {
1091
1478
  }
1092
1479
  // ── Session minting (GraphQL) ──────────────────────────────────────────────
1093
1480
  /** Calls the `StartRealtimeClientSession` mutation to obtain an ephemeral token + config. */
1094
- async mintSession(targetAgentId, conversationId, lastSessionId, preferredModelId, clientTools, coAgentId, configOverridesJson) {
1481
+ async mintSession(targetAgentId, conversationId, lastSessionId, preferredModelId, clientTools, coAgentId, configOverridesJson, recordingConsent, recordingStartedAt, mediaCollectionId, applicationId, appContext) {
1095
1482
  const mutation = `
1096
- mutation StartRealtimeClientSession($targetAgentId: String!, $conversationId: String, $lastSessionId: String, $preferredModelId: String, $clientToolsJson: String, $coAgentId: String, $configOverridesJson: String) {
1097
- StartRealtimeClientSession(targetAgentId: $targetAgentId, conversationId: $conversationId, lastSessionId: $lastSessionId, preferredModelId: $preferredModelId, clientToolsJson: $clientToolsJson, coAgentId: $coAgentId, configOverridesJson: $configOverridesJson) {
1483
+ mutation StartRealtimeClientSession($targetAgentId: String!, $conversationId: String, $lastSessionId: String, $preferredModelId: String, $clientToolsJson: String, $coAgentId: String, $configOverridesJson: String, $recordingConsent: Boolean, $recordingStartedAt: String, $mediaCollectionId: String, $applicationId: String, $appContextJson: String) {
1484
+ StartRealtimeClientSession(targetAgentId: $targetAgentId, conversationId: $conversationId, lastSessionId: $lastSessionId, preferredModelId: $preferredModelId, clientToolsJson: $clientToolsJson, coAgentId: $coAgentId, configOverridesJson: $configOverridesJson, recordingConsent: $recordingConsent, recordingStartedAt: $recordingStartedAt, mediaCollectionId: $mediaCollectionId, applicationId: $applicationId, appContextJson: $appContextJson) {
1098
1485
  AgentSessionId
1099
1486
  ConversationId
1100
1487
  Provider
@@ -1115,7 +1502,12 @@ export class RealtimeSessionService {
1115
1502
  preferredModelId: preferredModelId ?? null,
1116
1503
  clientToolsJson: clientTools && clientTools.length > 0 ? JSON.stringify(clientTools) : null,
1117
1504
  coAgentId: coAgentId ?? null,
1118
- configOverridesJson: configOverridesJson ?? null
1505
+ configOverridesJson: configOverridesJson ?? null,
1506
+ recordingConsent: recordingConsent ?? false,
1507
+ recordingStartedAt: recordingStartedAt ?? null,
1508
+ mediaCollectionId: mediaCollectionId ?? null,
1509
+ applicationId: applicationId ?? null,
1510
+ appContextJson: appContext ? JSON.stringify(appContext) : null
1119
1511
  };
1120
1512
  const result = await this.gql().ExecuteGQL(mutation, variables);
1121
1513
  const payload = result?.StartRealtimeClientSession;
@@ -1177,6 +1569,19 @@ export class RealtimeSessionService {
1177
1569
  // ── Transcript relay (GraphQL) ─────────────────────────────────────────────
1178
1570
  /**
1179
1571
  * Relays a final transcript turn to MJ via `RelayRealtimeTranscript`.
1572
+ *
1573
+ * When the session is being recorded, per-turn timing rides along: `utteranceEndMs` is the
1574
+ * recording-relative offset at finalization, and `utteranceStartMs` is the offset captured by
1575
+ * {@link markTurnAudioStart} when THIS turn's audio actually began (its first interim) — NOT
1576
+ * inherited from the previous turn's end. That distinction is the timing fix: when a tool-call
1577
+ * / silence gap sits between turns, the post-gap turn's audio starts much later, so inheriting
1578
+ * the prior turn's end stamped the cue ~gap-length too early. Both are omitted (left `null`)
1579
+ * when the session isn't being recorded.
1580
+ *
1581
+ * A correction (`replacesPrevious`) doesn't open a new turn, so it carries no start and doesn't
1582
+ * reset the per-turn start guard. After a normal finalization the guard is cleared so the NEXT
1583
+ * turn re-stamps its start from where ITS audio begins.
1584
+ *
1180
1585
  * @param replacesPrevious CORRECTION semantics: the server updates the session's most
1181
1586
  * recent persisted turn of this role IN PLACE instead of appending (e.g. ElevenLabs'
1182
1587
  * post-barge-in `agent_response_correction`).
@@ -1185,17 +1590,31 @@ export class RealtimeSessionService {
1185
1590
  if (!this.agentSessionId) {
1186
1591
  return;
1187
1592
  }
1593
+ // Per-turn timing against the recording (when recording). `utteranceStartMs` is where this
1594
+ // turn's audio actually began (captured by markTurnAudioStart on the first interim); the
1595
+ // `?? 0` fallback covers a turn whose interim was missed / a final-only first turn.
1596
+ const utteranceEndMs = this.recorder ? this.recorder.NowOffsetMs() : null;
1597
+ const utteranceStartMs = this.recorder && !replacesPrevious ? (this.currentTurnStartMs ?? 0) : null;
1598
+ if (this.recorder && !replacesPrevious) {
1599
+ // This turn is finalized — arm the NEXT turn to re-stamp its start from its own first
1600
+ // interim (handles a tool-call gap before the next turn). Stop inheriting this end as the
1601
+ // next start. `null` means "not yet captured"; relay falls back to `?? 0` if no interim fires.
1602
+ this.currentTurnStartMs = null;
1603
+ this.turnAudioStartCaptured = false;
1604
+ }
1188
1605
  try {
1189
1606
  const mutation = `
1190
- mutation RelayRealtimeTranscript($agentSessionId: String!, $role: String!, $text: String!, $replacesPrevious: Boolean) {
1191
- RelayRealtimeTranscript(agentSessionId: $agentSessionId, role: $role, text: $text, replacesPrevious: $replacesPrevious)
1607
+ mutation RelayRealtimeTranscript($agentSessionId: String!, $role: String!, $text: String!, $replacesPrevious: Boolean, $utteranceStartMs: Int, $utteranceEndMs: Int) {
1608
+ RelayRealtimeTranscript(agentSessionId: $agentSessionId, role: $role, text: $text, replacesPrevious: $replacesPrevious, utteranceStartMs: $utteranceStartMs, utteranceEndMs: $utteranceEndMs)
1192
1609
  }
1193
1610
  `;
1194
1611
  await this.gql().ExecuteGQL(mutation, {
1195
1612
  agentSessionId: this.agentSessionId,
1196
1613
  role,
1197
1614
  text,
1198
- replacesPrevious
1615
+ replacesPrevious,
1616
+ utteranceStartMs,
1617
+ utteranceEndMs
1199
1618
  });
1200
1619
  }
1201
1620
  catch (error) {
@@ -1575,6 +1994,11 @@ export class RealtimeSessionService {
1575
1994
  await this.client.Disconnect();
1576
1995
  this.client = null;
1577
1996
  }
1997
+ // Stop + upload the call recording WHILE the live session id is still set (the file is
1998
+ // attached to it). Best-effort and never blocks teardown — stopAndUploadRecording swallows
1999
+ // its own errors. No-op when nothing was recorded.
2000
+ await this.stopAndUploadRecording(this.agentSessionId);
2001
+ this.recordingStartedAtIso = null;
1578
2002
  // Final usage flush WHILE the live session id is still set (the relay mutation also
1579
2003
  // accepts a Closed session, so ordering vs. CloseAgentSession is belt-and-braces).
1580
2004
  if (this.usageFlushTimer) {
@@ -1600,7 +2024,7 @@ export class RealtimeSessionService {
1600
2024
  this._connectionState$.next('closed');
1601
2025
  }
1602
2026
  // Surface generic session-ended for the conversations runtime bridge.
1603
- // `closeServerSession=true` means the user explicitly called EndVoiceSession;
2027
+ // `closeServerSession=true` means the user explicitly called EndRealtimeSession;
1604
2028
  // `false` means teardown ran from a catch block (start path error path).
1605
2029
  if (closedSessionId) {
1606
2030
  this._sessionEnded$.next({
@@ -1632,6 +2056,13 @@ export class RealtimeSessionService {
1632
2056
  resetState() {
1633
2057
  this._captions$.next([]);
1634
2058
  this.SetMinimized(false);
2059
+ this.stopSegmentFlushing();
2060
+ this.segmentIndex = 0;
2061
+ this.recorder = null;
2062
+ this.recordingStartedAtIso = null;
2063
+ this.currentTurnStartMs = null;
2064
+ this.turnAudioStartCaptured = false;
2065
+ this.usedChannelNames.clear();
1635
2066
  }
1636
2067
  /** The GraphQL provider used for relay mutations. */
1637
2068
  gql() {