@memberjunction/ng-conversations 5.40.1 → 5.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/README.md +57 -0
  2. package/dist/__tests__/channel-optional-surface.test.d.ts +2 -0
  3. package/dist/__tests__/channel-optional-surface.test.d.ts.map +1 -0
  4. package/dist/__tests__/channel-optional-surface.test.js +53 -0
  5. package/dist/__tests__/channel-optional-surface.test.js.map +1 -0
  6. package/dist/__tests__/chat-events.test.d.ts +14 -0
  7. package/dist/__tests__/chat-events.test.d.ts.map +1 -0
  8. package/dist/__tests__/chat-events.test.js +109 -0
  9. package/dist/__tests__/chat-events.test.js.map +1 -0
  10. package/dist/__tests__/conversation-naming.test.d.ts +2 -0
  11. package/dist/__tests__/conversation-naming.test.d.ts.map +1 -0
  12. package/dist/__tests__/conversation-naming.test.js +110 -0
  13. package/dist/__tests__/conversation-naming.test.js.map +1 -0
  14. package/dist/__tests__/delegation-result-parser.test.d.ts +2 -0
  15. package/dist/__tests__/delegation-result-parser.test.d.ts.map +1 -0
  16. package/dist/__tests__/delegation-result-parser.test.js +107 -0
  17. package/dist/__tests__/delegation-result-parser.test.js.map +1 -0
  18. package/dist/__tests__/event-wiring.test.d.ts +15 -0
  19. package/dist/__tests__/event-wiring.test.d.ts.map +1 -0
  20. package/dist/__tests__/event-wiring.test.js +100 -0
  21. package/dist/__tests__/event-wiring.test.js.map +1 -0
  22. package/dist/__tests__/narration-template.test.d.ts +2 -0
  23. package/dist/__tests__/narration-template.test.d.ts.map +1 -0
  24. package/dist/__tests__/narration-template.test.js +76 -0
  25. package/dist/__tests__/narration-template.test.js.map +1 -0
  26. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +2 -0
  27. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +1 -0
  28. package/dist/__tests__/realtime-agent-picker-models.test.js +49 -0
  29. package/dist/__tests__/realtime-agent-picker-models.test.js.map +1 -0
  30. package/dist/__tests__/realtime-audio-visuals.test.d.ts +2 -0
  31. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +1 -0
  32. package/dist/__tests__/realtime-audio-visuals.test.js +123 -0
  33. package/dist/__tests__/realtime-audio-visuals.test.js.map +1 -0
  34. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +2 -0
  35. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +1 -0
  36. package/dist/__tests__/realtime-delegation-card-cancel.test.js +48 -0
  37. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +1 -0
  38. package/dist/__tests__/realtime-disclosure.test.d.ts +2 -0
  39. package/dist/__tests__/realtime-disclosure.test.d.ts.map +1 -0
  40. package/dist/__tests__/realtime-disclosure.test.js +164 -0
  41. package/dist/__tests__/realtime-disclosure.test.js.map +1 -0
  42. package/dist/__tests__/realtime-pairing.test.d.ts +2 -0
  43. package/dist/__tests__/realtime-pairing.test.d.ts.map +1 -0
  44. package/dist/__tests__/realtime-pairing.test.js +207 -0
  45. package/dist/__tests__/realtime-pairing.test.js.map +1 -0
  46. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +2 -0
  47. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +1 -0
  48. package/dist/__tests__/realtime-review-lifecycle.test.js +154 -0
  49. package/dist/__tests__/realtime-review-lifecycle.test.js.map +1 -0
  50. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +2 -0
  51. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +1 -0
  52. package/dist/__tests__/realtime-session-cancel-usage.test.js +230 -0
  53. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +1 -0
  54. package/dist/__tests__/realtime-session-channels.test.d.ts +2 -0
  55. package/dist/__tests__/realtime-session-channels.test.d.ts.map +1 -0
  56. package/dist/__tests__/realtime-session-channels.test.js +252 -0
  57. package/dist/__tests__/realtime-session-channels.test.js.map +1 -0
  58. package/dist/__tests__/realtime-session-client-tools.test.d.ts +2 -0
  59. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +1 -0
  60. package/dist/__tests__/realtime-session-client-tools.test.js +103 -0
  61. package/dist/__tests__/realtime-session-client-tools.test.js.map +1 -0
  62. package/dist/__tests__/realtime-session-minimized.test.d.ts +2 -0
  63. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +1 -0
  64. package/dist/__tests__/realtime-session-minimized.test.js +32 -0
  65. package/dist/__tests__/realtime-session-minimized.test.js.map +1 -0
  66. package/dist/__tests__/realtime-session-mint.test.d.ts +2 -0
  67. package/dist/__tests__/realtime-session-mint.test.d.ts.map +1 -0
  68. package/dist/__tests__/realtime-session-mint.test.js +69 -0
  69. package/dist/__tests__/realtime-session-mint.test.js.map +1 -0
  70. package/dist/__tests__/realtime-session-policy.test.d.ts +2 -0
  71. package/dist/__tests__/realtime-session-policy.test.d.ts.map +1 -0
  72. package/dist/__tests__/realtime-session-policy.test.js +303 -0
  73. package/dist/__tests__/realtime-session-policy.test.js.map +1 -0
  74. package/dist/__tests__/realtime-session-review.service.test.d.ts +2 -0
  75. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +1 -0
  76. package/dist/__tests__/realtime-session-review.service.test.js +743 -0
  77. package/dist/__tests__/realtime-session-review.service.test.js.map +1 -0
  78. package/dist/__tests__/realtime-session-state.test.d.ts +2 -0
  79. package/dist/__tests__/realtime-session-state.test.d.ts.map +1 -0
  80. package/dist/__tests__/realtime-session-state.test.js +83 -0
  81. package/dist/__tests__/realtime-session-state.test.js.map +1 -0
  82. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +2 -0
  83. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +1 -0
  84. package/dist/__tests__/realtime-session-timeline-card.test.js +106 -0
  85. package/dist/__tests__/realtime-session-timeline-card.test.js.map +1 -0
  86. package/dist/__tests__/realtime-session-timeline.test.d.ts +2 -0
  87. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +1 -0
  88. package/dist/__tests__/realtime-session-timeline.test.js +142 -0
  89. package/dist/__tests__/realtime-session-timeline.test.js.map +1 -0
  90. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +19 -0
  91. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +1 -0
  92. package/dist/__tests__/realtime-sessions-adapter.test.js +188 -0
  93. package/dist/__tests__/realtime-sessions-adapter.test.js.map +1 -0
  94. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +2 -0
  95. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +1 -0
  96. package/dist/__tests__/realtime-surface-panel-prefs.test.js +100 -0
  97. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +1 -0
  98. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +2 -0
  99. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +1 -0
  100. package/dist/__tests__/realtime-surface-tabs-model.test.js +193 -0
  101. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +1 -0
  102. package/dist/__tests__/remote-browser-audio-player.test.d.ts +2 -0
  103. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +1 -0
  104. package/dist/__tests__/remote-browser-audio-player.test.js +137 -0
  105. package/dist/__tests__/remote-browser-audio-player.test.js.map +1 -0
  106. package/dist/__tests__/remote-browser-channel.test.d.ts +2 -0
  107. package/dist/__tests__/remote-browser-channel.test.d.ts.map +1 -0
  108. package/dist/__tests__/remote-browser-channel.test.js +423 -0
  109. package/dist/__tests__/remote-browser-channel.test.js.map +1 -0
  110. package/dist/__tests__/slot-defaults.test.d.ts +24 -0
  111. package/dist/__tests__/slot-defaults.test.d.ts.map +1 -0
  112. package/dist/__tests__/slot-defaults.test.js +63 -0
  113. package/dist/__tests__/slot-defaults.test.js.map +1 -0
  114. package/dist/__tests__/user-authorization.test.d.ts +2 -0
  115. package/dist/__tests__/user-authorization.test.d.ts.map +1 -0
  116. package/dist/__tests__/user-authorization.test.js +97 -0
  117. package/dist/__tests__/user-authorization.test.js.map +1 -0
  118. package/dist/__tests__/voice-session-narration.test.d.ts +2 -0
  119. package/dist/__tests__/voice-session-narration.test.d.ts.map +1 -0
  120. package/dist/__tests__/voice-session-narration.test.js +609 -0
  121. package/dist/__tests__/voice-session-narration.test.js.map +1 -0
  122. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +2 -0
  123. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +1 -0
  124. package/dist/__tests__/whiteboard-artifact-viewer.test.js +101 -0
  125. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +1 -0
  126. package/dist/__tests__/whiteboard-channel.test.d.ts +2 -0
  127. package/dist/__tests__/whiteboard-channel.test.d.ts.map +1 -0
  128. package/dist/__tests__/whiteboard-channel.test.js +260 -0
  129. package/dist/__tests__/whiteboard-channel.test.js.map +1 -0
  130. package/dist/__tests__/whiteboard-restore-state.test.d.ts +2 -0
  131. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +1 -0
  132. package/dist/__tests__/whiteboard-restore-state.test.js +108 -0
  133. package/dist/__tests__/whiteboard-restore-state.test.js.map +1 -0
  134. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +205 -3
  135. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  136. package/dist/lib/components/conversation/conversation-chat-area.component.js +911 -342
  137. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  138. package/dist/lib/components/mention/mention-dropdown.component.js +35 -17
  139. package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
  140. package/dist/lib/components/mention/mention-editor.component.d.ts +4 -0
  141. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  142. package/dist/lib/components/mention/mention-editor.component.js +43 -19
  143. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  144. package/dist/lib/components/message/message-input-box.component.d.ts +17 -1
  145. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  146. package/dist/lib/components/message/message-input-box.component.js +73 -15
  147. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  148. package/dist/lib/components/message/message-input.component.d.ts +142 -6
  149. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  150. package/dist/lib/components/message/message-input.component.js +328 -82
  151. package/dist/lib/components/message/message-input.component.js.map +1 -1
  152. package/dist/lib/components/message/message-item.component.d.ts +28 -3
  153. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  154. package/dist/lib/components/message/message-item.component.js +180 -108
  155. package/dist/lib/components/message/message-item.component.js.map +1 -1
  156. package/dist/lib/components/message/message-list.component.d.ts +81 -2
  157. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  158. package/dist/lib/components/message/message-list.component.js +252 -87
  159. package/dist/lib/components/message/message-list.component.js.map +1 -1
  160. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +282 -0
  161. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -0
  162. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +158 -0
  163. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -0
  164. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts +25 -0
  165. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts.map +1 -0
  166. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js +140 -0
  167. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js.map +1 -0
  168. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +35 -0
  169. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -0
  170. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +58 -0
  171. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -0
  172. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +63 -0
  173. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -0
  174. package/dist/lib/components/realtime/realtime-activity-rail.component.js +260 -0
  175. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -0
  176. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +117 -0
  177. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -0
  178. package/dist/lib/components/realtime/realtime-agent-banner.component.js +504 -0
  179. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -0
  180. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +168 -0
  181. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -0
  182. package/dist/lib/components/realtime/realtime-agent-picker.component.js +556 -0
  183. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -0
  184. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +97 -0
  185. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -0
  186. package/dist/lib/components/realtime/realtime-audio-visuals.js +139 -0
  187. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -0
  188. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts +29 -0
  189. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts.map +1 -0
  190. package/dist/lib/components/realtime/realtime-channel-strip.component.js +69 -0
  191. package/dist/lib/components/realtime/realtime-channel-strip.component.js.map +1 -0
  192. package/dist/lib/components/realtime/realtime-composer.component.d.ts +65 -0
  193. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -0
  194. package/dist/lib/components/realtime/realtime-composer.component.js +256 -0
  195. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -0
  196. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +71 -0
  197. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -0
  198. package/dist/lib/components/realtime/realtime-delegation-card.component.js +324 -0
  199. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -0
  200. package/dist/lib/components/realtime/realtime-disclosure.d.ts +135 -0
  201. package/dist/lib/components/realtime/realtime-disclosure.d.ts.map +1 -0
  202. package/dist/lib/components/realtime/realtime-disclosure.js +188 -0
  203. package/dist/lib/components/realtime/realtime-disclosure.js.map +1 -0
  204. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +491 -0
  205. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -0
  206. package/dist/lib/components/realtime/realtime-session-overlay.component.js +1274 -0
  207. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -0
  208. package/dist/lib/components/realtime/realtime-session-state.d.ts +191 -0
  209. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -0
  210. package/dist/lib/components/realtime/realtime-session-state.js +244 -0
  211. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -0
  212. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts +56 -0
  213. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts.map +1 -0
  214. package/dist/lib/components/realtime/realtime-session-thread.component.js +246 -0
  215. package/dist/lib/components/realtime/realtime-session-thread.component.js.map +1 -0
  216. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +51 -0
  217. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -0
  218. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +193 -0
  219. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -0
  220. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts +77 -0
  221. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts.map +1 -0
  222. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js +114 -0
  223. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js.map +1 -0
  224. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +173 -0
  225. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -0
  226. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +496 -0
  227. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -0
  228. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +181 -0
  229. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -0
  230. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +223 -0
  231. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -0
  232. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts +163 -0
  233. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts.map +1 -0
  234. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js +309 -0
  235. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js.map +1 -0
  236. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +168 -0
  237. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -0
  238. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +524 -0
  239. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -0
  240. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +346 -0
  241. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -0
  242. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +851 -0
  243. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -0
  244. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +86 -0
  245. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -0
  246. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +210 -0
  247. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -0
  248. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts +48 -0
  249. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts.map +1 -0
  250. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js +180 -0
  251. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js.map +1 -0
  252. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +119 -0
  253. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -0
  254. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +274 -0
  255. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -0
  256. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts +11 -0
  257. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts.map +1 -0
  258. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js +98 -0
  259. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js.map +1 -0
  260. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts +9 -0
  261. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts.map +1 -0
  262. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js +35 -0
  263. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js.map +1 -0
  264. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts +28 -0
  265. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts.map +1 -0
  266. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js +104 -0
  267. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js.map +1 -0
  268. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts +11 -0
  269. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts.map +1 -0
  270. package/dist/lib/components/slots/mj-chat-header-default.component.js +103 -0
  271. package/dist/lib/components/slots/mj-chat-header-default.component.js.map +1 -0
  272. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts +15 -0
  273. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts.map +1 -0
  274. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js +73 -0
  275. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js.map +1 -0
  276. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts +9 -0
  277. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts.map +1 -0
  278. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js +34 -0
  279. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js.map +1 -0
  280. package/dist/lib/components/slots/slot-interfaces.d.ts +95 -0
  281. package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -0
  282. package/dist/lib/components/slots/slot-interfaces.js +18 -0
  283. package/dist/lib/components/slots/slot-interfaces.js.map +1 -0
  284. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -0
  285. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  286. package/dist/lib/components/workspace/conversation-workspace.component.js +28 -4
  287. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  288. package/dist/lib/conversations.module.d.ts +12 -1
  289. package/dist/lib/conversations.module.d.ts.map +1 -1
  290. package/dist/lib/conversations.module.js +93 -5
  291. package/dist/lib/conversations.module.js.map +1 -1
  292. package/dist/lib/directives/chat-slot.directive.d.ts +44 -0
  293. package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -0
  294. package/dist/lib/directives/chat-slot.directive.js +54 -0
  295. package/dist/lib/directives/chat-slot.directive.js.map +1 -0
  296. package/dist/lib/events/chat-events.d.ts +137 -0
  297. package/dist/lib/events/chat-events.d.ts.map +1 -0
  298. package/dist/lib/events/chat-events.js +189 -0
  299. package/dist/lib/events/chat-events.js.map +1 -0
  300. package/dist/lib/models/conversation-state.model.d.ts +2 -1
  301. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  302. package/dist/lib/models/conversation-state.model.js.map +1 -1
  303. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  304. package/dist/lib/services/artifact-state.service.js +23 -6
  305. package/dist/lib/services/artifact-state.service.js.map +1 -1
  306. package/dist/lib/services/conversation-agent.service.d.ts +60 -74
  307. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  308. package/dist/lib/services/conversation-agent.service.js +100 -313
  309. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  310. package/dist/lib/services/conversation-bridge.service.d.ts +11 -70
  311. package/dist/lib/services/conversation-bridge.service.d.ts.map +1 -1
  312. package/dist/lib/services/conversation-bridge.service.js +51 -85
  313. package/dist/lib/services/conversation-bridge.service.js.map +1 -1
  314. package/dist/lib/services/conversation-naming.d.ts +63 -0
  315. package/dist/lib/services/conversation-naming.d.ts.map +1 -0
  316. package/dist/lib/services/conversation-naming.js +58 -0
  317. package/dist/lib/services/conversation-naming.js.map +1 -0
  318. package/dist/lib/services/conversation-streaming.service.d.ts +24 -154
  319. package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
  320. package/dist/lib/services/conversation-streaming.service.js +39 -361
  321. package/dist/lib/services/conversation-streaming.service.js.map +1 -1
  322. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts +10 -0
  323. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts.map +1 -0
  324. package/dist/lib/services/conversations-runtime-bootstrap.service.js +104 -0
  325. package/dist/lib/services/conversations-runtime-bootstrap.service.js.map +1 -0
  326. package/dist/lib/services/delegation-result-parser.d.ts +45 -0
  327. package/dist/lib/services/delegation-result-parser.d.ts.map +1 -0
  328. package/dist/lib/services/delegation-result-parser.js +48 -0
  329. package/dist/lib/services/delegation-result-parser.js.map +1 -0
  330. package/dist/lib/services/mention-autocomplete.service.d.ts +19 -4
  331. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  332. package/dist/lib/services/mention-autocomplete.service.js +65 -4
  333. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  334. package/dist/lib/services/mention-parser.service.d.ts +8 -53
  335. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  336. package/dist/lib/services/mention-parser.service.js +32 -243
  337. package/dist/lib/services/mention-parser.service.js.map +1 -1
  338. package/dist/lib/services/narration-template.d.ts +42 -0
  339. package/dist/lib/services/narration-template.d.ts.map +1 -0
  340. package/dist/lib/services/narration-template.js +73 -0
  341. package/dist/lib/services/narration-template.js.map +1 -0
  342. package/dist/lib/services/realtime-pairing.d.ts +120 -0
  343. package/dist/lib/services/realtime-pairing.d.ts.map +1 -0
  344. package/dist/lib/services/realtime-pairing.js +150 -0
  345. package/dist/lib/services/realtime-pairing.js.map +1 -0
  346. package/dist/lib/services/realtime-session-review.service.d.ts +233 -0
  347. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -0
  348. package/dist/lib/services/realtime-session-review.service.js +417 -0
  349. package/dist/lib/services/realtime-session-review.service.js.map +1 -0
  350. package/dist/lib/services/realtime-session.service.d.ts +739 -0
  351. package/dist/lib/services/realtime-session.service.d.ts.map +1 -0
  352. package/dist/lib/services/realtime-session.service.js +1647 -0
  353. package/dist/lib/services/realtime-session.service.js.map +1 -0
  354. package/dist/lib/services/realtime-sessions-adapter.d.ts +54 -0
  355. package/dist/lib/services/realtime-sessions-adapter.d.ts.map +1 -0
  356. package/dist/lib/services/realtime-sessions-adapter.js +154 -0
  357. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -0
  358. package/dist/lib/services/user-authorization.d.ts +67 -0
  359. package/dist/lib/services/user-authorization.d.ts.map +1 -0
  360. package/dist/lib/services/user-authorization.js +66 -0
  361. package/dist/lib/services/user-authorization.js.map +1 -0
  362. package/dist/lib/utils/realtime-session-timeline.d.ts +84 -0
  363. package/dist/lib/utils/realtime-session-timeline.d.ts.map +1 -0
  364. package/dist/lib/utils/realtime-session-timeline.js +94 -0
  365. package/dist/lib/utils/realtime-session-timeline.js.map +1 -0
  366. package/dist/public-api.d.ts +41 -0
  367. package/dist/public-api.d.ts.map +1 -1
  368. package/dist/public-api.js +50 -0
  369. package/dist/public-api.js.map +1 -1
  370. package/package.json +27 -24
  371. package/dist/__tests__/conversation-bridge.service.test.d.ts +0 -2
  372. package/dist/__tests__/conversation-bridge.service.test.d.ts.map +0 -1
  373. package/dist/__tests__/conversation-bridge.service.test.js +0 -98
  374. package/dist/__tests__/conversation-bridge.service.test.js.map +0 -1
  375. package/dist/__tests__/mention-parser.test.d.ts +0 -2
  376. package/dist/__tests__/mention-parser.test.d.ts.map +0 -1
  377. package/dist/__tests__/mention-parser.test.js +0 -154
  378. package/dist/__tests__/mention-parser.test.js.map +0 -1
@@ -0,0 +1,309 @@
1
+ /**
2
+ * The CLIENT-SIDE player for the Remote Browser tab-audio stream — the audio sibling of the surface's
3
+ * screencast canvas. The channel feeds it each pushed {@link RemoteBrowserAudioChunkInput} (a base64
4
+ * `audio/webm;codecs=opus` slice); the player appends them to a `MediaSource` `SourceBuffer` feeding a
5
+ * hidden `<audio>` element so the user HEARS what the co-agent is playing in the browser.
6
+ *
7
+ * The append/queue/seq logic is split from the DOM so it's unit-testable without a browser:
8
+ * - {@link IAudioSink} is the thin DOM seam (create/append/error/muted) — faked in tests, wired to a real
9
+ * `MediaSource` + `<audio>` in {@link MediaSourceAudioSink}.
10
+ * - {@link RemoteBrowserAudioPlayer} owns the queue, sequential append (a `SourceBuffer` can only take one
11
+ * append at a time), overflow drop, and sequence-gap resync — all framework- and DOM-free.
12
+ *
13
+ * Codec note: only `'webm-opus'` chunks are appendable to the `audio/webm;codecs=opus` SourceBuffer; other
14
+ * codecs (reserved for future backend capture paths) are dropped with a one-time warning.
15
+ */
16
+ /** Max chunks buffered ahead of the sink before the OLDEST are dropped (keeps latency + memory bounded). */
17
+ const MAX_QUEUE_DEPTH = 64;
18
+ /**
19
+ * Sequential, overflow-bounded player for a continuous webm-opus chunk stream. Construct it with an
20
+ * {@link IAudioSink}; call {@link Enqueue} for each pushed chunk and {@link SetMuted} for the speaker
21
+ * toggle; call {@link Dispose} on teardown.
22
+ */
23
+ export class RemoteBrowserAudioPlayer {
24
+ /** The DOM (or fake) sink chunks are appended to. */
25
+ sink;
26
+ /** Pending decoded chunks awaiting append, oldest first. Bounded by {@link MAX_QUEUE_DEPTH}. */
27
+ queue = [];
28
+ /** True while an append is in flight (a SourceBuffer accepts one append at a time). */
29
+ appending = false;
30
+ /** The sequence number of the last chunk we ACCEPTED, or `null` before the first. Drives gap detection. */
31
+ lastSeq = null;
32
+ /** True once {@link Open} has been kicked off, so we don't re-open on every chunk. */
33
+ opening = false;
34
+ /** True after {@link Dispose} so a late append / open resolution becomes a no-op. */
35
+ disposed = false;
36
+ /** Pending muted state to apply once the sink opens (the toggle can fire before the first chunk). */
37
+ muted = false;
38
+ /** Set once we've warned about an unplayable codec, so the log isn't spammed per chunk. */
39
+ warnedUnplayableCodec = false;
40
+ /**
41
+ * Constructs a player over the given sink.
42
+ *
43
+ * @param sink The audio sink (real {@link MediaSourceAudioSink} in the app, a fake in tests).
44
+ */
45
+ constructor(sink) {
46
+ this.sink = sink;
47
+ }
48
+ /** The current queue depth — exposed for tests / diagnostics. */
49
+ get QueueDepth() {
50
+ return this.queue.length;
51
+ }
52
+ /**
53
+ * Enqueues one pushed audio chunk for playback. Drops chunks whose codec isn't playable, opens the sink
54
+ * lazily on the first playable chunk, detects sequence gaps (logged; the webm stream self-recovers on the
55
+ * next keyframe), and bounds the queue by dropping the oldest on overflow. Never throws.
56
+ *
57
+ * @param chunk The pushed chunk to play.
58
+ */
59
+ Enqueue(chunk) {
60
+ if (this.disposed || !chunk || typeof chunk.dataBase64 !== 'string' || chunk.dataBase64.length === 0) {
61
+ return;
62
+ }
63
+ if (chunk.codec !== 'webm-opus') {
64
+ if (!this.warnedUnplayableCodec) {
65
+ this.warnedUnplayableCodec = true;
66
+ console.warn(`[RemoteBrowserAudio] dropping chunk with unplayable codec '${chunk.codec}' (only webm-opus is supported).`);
67
+ }
68
+ return;
69
+ }
70
+ this.noteSequence(chunk.seq);
71
+ const bytes = this.decodeBase64(chunk.dataBase64);
72
+ if (!bytes) {
73
+ return;
74
+ }
75
+ this.pushBounded(bytes);
76
+ void this.ensureOpenThenDrain();
77
+ }
78
+ /**
79
+ * Mutes / unmutes playback (the speaker toggle). Safe to call before the sink opens — the state is
80
+ * applied once it does.
81
+ *
82
+ * @param muted `true` to mute, `false` to unmute.
83
+ */
84
+ SetMuted(muted) {
85
+ this.muted = muted;
86
+ if (this.sink.IsOpen) {
87
+ this.sink.SetMuted(muted);
88
+ }
89
+ }
90
+ /** Tears the player down: clears the queue and closes the sink. Idempotent. */
91
+ Dispose() {
92
+ this.disposed = true;
93
+ this.queue.length = 0;
94
+ this.sink.Close();
95
+ }
96
+ /**
97
+ * Records a chunk's sequence number and logs a one-line note when a gap is detected (a non-consecutive
98
+ * seq). We don't try to reorder — webm-opus self-recovers at the next cluster — but the note aids
99
+ * diagnosis of a lossy transport.
100
+ *
101
+ * @param seq The incoming chunk's sequence number.
102
+ */
103
+ noteSequence(seq) {
104
+ if (this.lastSeq !== null && seq > this.lastSeq + 1) {
105
+ console.warn(`[RemoteBrowserAudio] audio sequence gap: expected ${this.lastSeq + 1}, got ${seq} (dropped ${seq - this.lastSeq - 1}).`);
106
+ }
107
+ // Track the highest seq seen so an out-of-order late chunk doesn't reset the expectation backwards.
108
+ this.lastSeq = this.lastSeq === null ? seq : Math.max(this.lastSeq, seq);
109
+ }
110
+ /** Pushes bytes onto the queue, dropping the OLDEST when the queue is at {@link MAX_QUEUE_DEPTH}. */
111
+ pushBounded(bytes) {
112
+ if (this.queue.length >= MAX_QUEUE_DEPTH) {
113
+ this.queue.shift(); // drop-oldest: bound latency + memory under a slow sink
114
+ }
115
+ this.queue.push(bytes);
116
+ }
117
+ /**
118
+ * Ensures the sink is open (opening it lazily on first use) and then drains the queue. The open is
119
+ * kicked off once; subsequent calls just drain. Best-effort — an open/append failure is logged and the
120
+ * player keeps trying on the next chunk.
121
+ */
122
+ async ensureOpenThenDrain() {
123
+ if (!this.sink.IsOpen && !this.opening) {
124
+ this.opening = true;
125
+ try {
126
+ await this.sink.Open();
127
+ if (this.disposed) {
128
+ this.sink.Close();
129
+ return;
130
+ }
131
+ this.sink.SetMuted(this.muted);
132
+ }
133
+ catch (err) {
134
+ this.opening = false;
135
+ console.warn(`[RemoteBrowserAudio] failed to open audio sink: ${err instanceof Error ? err.message : String(err)}`);
136
+ return;
137
+ }
138
+ }
139
+ void this.drain();
140
+ }
141
+ /**
142
+ * Appends queued chunks to the sink one at a time (a SourceBuffer rejects concurrent appends). Re-entrant
143
+ * safe via the {@link appending} guard; stops when the queue empties, the sink closes, or on disposal.
144
+ */
145
+ async drain() {
146
+ if (this.appending || this.disposed || !this.sink.IsOpen) {
147
+ return;
148
+ }
149
+ this.appending = true;
150
+ try {
151
+ while (this.queue.length > 0 && this.sink.IsOpen && !this.disposed) {
152
+ const bytes = this.queue.shift();
153
+ try {
154
+ await this.sink.AppendChunk(bytes);
155
+ }
156
+ catch (err) {
157
+ // A single rejected append (e.g. a quota hiccup) shouldn't kill playback — drop it and continue.
158
+ console.warn(`[RemoteBrowserAudio] dropped an audio chunk on append: ${err instanceof Error ? err.message : String(err)}`);
159
+ }
160
+ }
161
+ }
162
+ finally {
163
+ this.appending = false;
164
+ }
165
+ }
166
+ /**
167
+ * Decodes a base64 string to bytes WITHOUT the DOM `atob` so the queueing logic is testable in Node too.
168
+ * Returns `null` on malformed input (the chunk is then skipped).
169
+ *
170
+ * @param base64 The base64 string to decode.
171
+ * @returns The decoded bytes, or `null` when the input can't be decoded.
172
+ */
173
+ decodeBase64(base64) {
174
+ try {
175
+ // Prefer the platform decoder when present (browser `atob`), else Node's Buffer.
176
+ const globalAtob = globalThis.atob;
177
+ if (typeof globalAtob === 'function') {
178
+ const binary = globalAtob(base64);
179
+ const bytes = new Uint8Array(binary.length);
180
+ for (let i = 0; i < binary.length; i++) {
181
+ bytes[i] = binary.charCodeAt(i);
182
+ }
183
+ return bytes;
184
+ }
185
+ const nodeBuffer = globalThis.Buffer;
186
+ if (nodeBuffer) {
187
+ return Uint8Array.from(nodeBuffer.from(base64, 'base64'));
188
+ }
189
+ return null;
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
195
+ }
196
+ /**
197
+ * The real, DOM-backed {@link IAudioSink}: a `MediaSource` whose single `audio/webm;codecs=opus`
198
+ * `SourceBuffer` is fed appended chunks, attached to a hidden `<audio>` element that auto-plays. Autoplay
199
+ * is permitted because the realtime call the user already started is the activating gesture.
200
+ *
201
+ * Kept out of the player's hot path so the queueing logic stays unit-testable; this class is exercised in
202
+ * the live app (it can't be meaningfully unit-tested without a browser media stack).
203
+ */
204
+ export class MediaSourceAudioSink {
205
+ /** MIME the SourceBuffer is created with — the in-page recorder's output container/codec. */
206
+ static MIME = 'audio/webm;codecs=opus';
207
+ /** The hidden audio element playback is routed through; `null` before {@link Open} / after {@link Close}. */
208
+ audio = null;
209
+ /** The MediaSource backing the audio element; `null` before {@link Open} / after {@link Close}. */
210
+ mediaSource = null;
211
+ /** The single source buffer chunks are appended to; `null` until `sourceopen` wires it. */
212
+ sourceBuffer = null;
213
+ /** Object URL bound to the audio element's `src`, revoked on {@link Close}. */
214
+ objectUrl = null;
215
+ /** Latched muted state applied to the audio element as it (re)opens. */
216
+ mutedState = false;
217
+ /** @inheritdoc */
218
+ get IsOpen() {
219
+ return this.sourceBuffer !== null && this.mediaSource?.readyState === 'open';
220
+ }
221
+ /** @inheritdoc */
222
+ async Open() {
223
+ if (this.audio) {
224
+ return; // already open / opening
225
+ }
226
+ if (typeof MediaSource === 'undefined' || !MediaSource.isTypeSupported(MediaSourceAudioSink.MIME)) {
227
+ throw new Error(`MediaSource webm-opus playback is not supported in this browser.`);
228
+ }
229
+ const audio = new Audio();
230
+ audio.autoplay = true;
231
+ audio.muted = this.mutedState;
232
+ const mediaSource = new MediaSource();
233
+ const objectUrl = URL.createObjectURL(mediaSource);
234
+ audio.src = objectUrl;
235
+ this.audio = audio;
236
+ this.mediaSource = mediaSource;
237
+ this.objectUrl = objectUrl;
238
+ await new Promise((resolve, reject) => {
239
+ mediaSource.addEventListener('sourceopen', () => {
240
+ try {
241
+ this.sourceBuffer = mediaSource.addSourceBuffer(MediaSourceAudioSink.MIME);
242
+ this.sourceBuffer.mode = 'sequence';
243
+ resolve();
244
+ }
245
+ catch (err) {
246
+ reject(err instanceof Error ? err : new Error(String(err)));
247
+ }
248
+ }, { once: true });
249
+ });
250
+ // Best-effort start — autoplay is allowed (the call is the user gesture); ignore a rejected play().
251
+ void audio.play().catch(() => undefined);
252
+ }
253
+ /** @inheritdoc */
254
+ async AppendChunk(bytes) {
255
+ const buffer = this.sourceBuffer;
256
+ if (!buffer || !this.IsOpen) {
257
+ throw new Error('Audio sink is not open.');
258
+ }
259
+ await new Promise((resolve, reject) => {
260
+ const onUpdateEnd = () => {
261
+ buffer.removeEventListener('updateend', onUpdateEnd);
262
+ buffer.removeEventListener('error', onError);
263
+ resolve();
264
+ };
265
+ const onError = () => {
266
+ buffer.removeEventListener('updateend', onUpdateEnd);
267
+ buffer.removeEventListener('error', onError);
268
+ reject(new Error('SourceBuffer append error.'));
269
+ };
270
+ buffer.addEventListener('updateend', onUpdateEnd);
271
+ buffer.addEventListener('error', onError);
272
+ try {
273
+ // Copy into a fresh, definitely-non-shared ArrayBuffer so the typed `BufferSource` signature is
274
+ // satisfied across TS lib versions (a plain Uint8Array may be typed as ArrayBufferLike).
275
+ const copy = new Uint8Array(bytes.length);
276
+ copy.set(bytes);
277
+ buffer.appendBuffer(copy);
278
+ }
279
+ catch (err) {
280
+ buffer.removeEventListener('updateend', onUpdateEnd);
281
+ buffer.removeEventListener('error', onError);
282
+ reject(err instanceof Error ? err : new Error(String(err)));
283
+ }
284
+ });
285
+ }
286
+ /** @inheritdoc */
287
+ SetMuted(muted) {
288
+ this.mutedState = muted;
289
+ if (this.audio) {
290
+ this.audio.muted = muted;
291
+ }
292
+ }
293
+ /** @inheritdoc */
294
+ Close() {
295
+ if (this.audio) {
296
+ this.audio.pause();
297
+ this.audio.removeAttribute('src');
298
+ this.audio.load();
299
+ this.audio = null;
300
+ }
301
+ if (this.objectUrl) {
302
+ URL.revokeObjectURL(this.objectUrl);
303
+ this.objectUrl = null;
304
+ }
305
+ this.sourceBuffer = null;
306
+ this.mediaSource = null;
307
+ }
308
+ }
309
+ //# sourceMappingURL=remote-browser-audio-player.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-browser-audio-player.js","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-audio-player.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The CLIENT-SIDE player for the Remote Browser tab-audio stream — the audio sibling of the surface's\n * screencast canvas. The channel feeds it each pushed {@link RemoteBrowserAudioChunkInput} (a base64\n * `audio/webm;codecs=opus` slice); the player appends them to a `MediaSource` `SourceBuffer` feeding a\n * hidden `<audio>` element so the user HEARS what the co-agent is playing in the browser.\n *\n * The append/queue/seq logic is split from the DOM so it's unit-testable without a browser:\n * - {@link IAudioSink} is the thin DOM seam (create/append/error/muted) — faked in tests, wired to a real\n * `MediaSource` + `<audio>` in {@link MediaSourceAudioSink}.\n * - {@link RemoteBrowserAudioPlayer} owns the queue, sequential append (a `SourceBuffer` can only take one\n * append at a time), overflow drop, and sequence-gap resync — all framework- and DOM-free.\n *\n * Codec note: only `'webm-opus'` chunks are appendable to the `audio/webm;codecs=opus` SourceBuffer; other\n * codecs (reserved for future backend capture paths) are dropped with a one-time warning.\n */\n\n/** The narrow shape of a pushed audio chunk the player consumes (mirrors the server envelope fields). */\nexport interface RemoteBrowserAudioChunkInput {\n /** The encoded audio slice as raw base64 (no `data:` prefix). */\n dataBase64: string;\n /** The codec / container. Only `'webm-opus'` is currently playable. */\n codec: string;\n /** Sample rate in Hz (informational; the webm container is self-describing). */\n sampleRate: number;\n /** Channel count (informational). */\n channels: number;\n /** Monotonic sequence number for ordering / gap detection. */\n seq: number;\n}\n\n/**\n * The DOM seam the {@link RemoteBrowserAudioPlayer} drives. A real implementation wires a `MediaSource` +\n * `SourceBuffer` + hidden `<audio>`; tests provide a fake that records appends. Kept deliberately tiny so\n * the player's queueing logic is the part under test, not the browser media stack.\n */\nexport interface IAudioSink {\n /**\n * Prepares the sink for playback: creates the media source / source buffer / audio element and resolves\n * once it is ready to accept {@link AppendChunk}. Idempotent — a second call while ready is a no-op.\n */\n Open(): Promise<void>;\n\n /**\n * Appends one decoded-container byte slice to the underlying buffer. Rejects if the sink isn't open or\n * the underlying buffer rejects the append (the player then drops the chunk and continues).\n *\n * @param bytes The raw container bytes (e.g. a webm-opus slice) to append.\n */\n AppendChunk(bytes: Uint8Array): Promise<void>;\n\n /** Whether the sink can currently accept an append (open and not mid-error / closed). */\n readonly IsOpen: boolean;\n\n /** Mutes or unmutes playback (the speaker toggle). */\n SetMuted(muted: boolean): void;\n\n /** Tears the sink down: stops playback and releases the media source / audio element. Idempotent. */\n Close(): void;\n}\n\n/** Max chunks buffered ahead of the sink before the OLDEST are dropped (keeps latency + memory bounded). */\nconst MAX_QUEUE_DEPTH = 64;\n\n/**\n * Sequential, overflow-bounded player for a continuous webm-opus chunk stream. Construct it with an\n * {@link IAudioSink}; call {@link Enqueue} for each pushed chunk and {@link SetMuted} for the speaker\n * toggle; call {@link Dispose} on teardown.\n */\nexport class RemoteBrowserAudioPlayer {\n /** The DOM (or fake) sink chunks are appended to. */\n private readonly sink: IAudioSink;\n\n /** Pending decoded chunks awaiting append, oldest first. Bounded by {@link MAX_QUEUE_DEPTH}. */\n private readonly queue: Uint8Array[] = [];\n\n /** True while an append is in flight (a SourceBuffer accepts one append at a time). */\n private appending = false;\n\n /** The sequence number of the last chunk we ACCEPTED, or `null` before the first. Drives gap detection. */\n private lastSeq: number | null = null;\n\n /** True once {@link Open} has been kicked off, so we don't re-open on every chunk. */\n private opening = false;\n\n /** True after {@link Dispose} so a late append / open resolution becomes a no-op. */\n private disposed = false;\n\n /** Pending muted state to apply once the sink opens (the toggle can fire before the first chunk). */\n private muted = false;\n\n /** Set once we've warned about an unplayable codec, so the log isn't spammed per chunk. */\n private warnedUnplayableCodec = false;\n\n /**\n * Constructs a player over the given sink.\n *\n * @param sink The audio sink (real {@link MediaSourceAudioSink} in the app, a fake in tests).\n */\n constructor(sink: IAudioSink) {\n this.sink = sink;\n }\n\n /** The current queue depth — exposed for tests / diagnostics. */\n public get QueueDepth(): number {\n return this.queue.length;\n }\n\n /**\n * Enqueues one pushed audio chunk for playback. Drops chunks whose codec isn't playable, opens the sink\n * lazily on the first playable chunk, detects sequence gaps (logged; the webm stream self-recovers on the\n * next keyframe), and bounds the queue by dropping the oldest on overflow. Never throws.\n *\n * @param chunk The pushed chunk to play.\n */\n public Enqueue(chunk: RemoteBrowserAudioChunkInput): void {\n if (this.disposed || !chunk || typeof chunk.dataBase64 !== 'string' || chunk.dataBase64.length === 0) {\n return;\n }\n if (chunk.codec !== 'webm-opus') {\n if (!this.warnedUnplayableCodec) {\n this.warnedUnplayableCodec = true;\n console.warn(`[RemoteBrowserAudio] dropping chunk with unplayable codec '${chunk.codec}' (only webm-opus is supported).`);\n }\n return;\n }\n this.noteSequence(chunk.seq);\n\n const bytes = this.decodeBase64(chunk.dataBase64);\n if (!bytes) {\n return;\n }\n this.pushBounded(bytes);\n void this.ensureOpenThenDrain();\n }\n\n /**\n * Mutes / unmutes playback (the speaker toggle). Safe to call before the sink opens — the state is\n * applied once it does.\n *\n * @param muted `true` to mute, `false` to unmute.\n */\n public SetMuted(muted: boolean): void {\n this.muted = muted;\n if (this.sink.IsOpen) {\n this.sink.SetMuted(muted);\n }\n }\n\n /** Tears the player down: clears the queue and closes the sink. Idempotent. */\n public Dispose(): void {\n this.disposed = true;\n this.queue.length = 0;\n this.sink.Close();\n }\n\n /**\n * Records a chunk's sequence number and logs a one-line note when a gap is detected (a non-consecutive\n * seq). We don't try to reorder — webm-opus self-recovers at the next cluster — but the note aids\n * diagnosis of a lossy transport.\n *\n * @param seq The incoming chunk's sequence number.\n */\n private noteSequence(seq: number): void {\n if (this.lastSeq !== null && seq > this.lastSeq + 1) {\n console.warn(`[RemoteBrowserAudio] audio sequence gap: expected ${this.lastSeq + 1}, got ${seq} (dropped ${seq - this.lastSeq - 1}).`);\n }\n // Track the highest seq seen so an out-of-order late chunk doesn't reset the expectation backwards.\n this.lastSeq = this.lastSeq === null ? seq : Math.max(this.lastSeq, seq);\n }\n\n /** Pushes bytes onto the queue, dropping the OLDEST when the queue is at {@link MAX_QUEUE_DEPTH}. */\n private pushBounded(bytes: Uint8Array): void {\n if (this.queue.length >= MAX_QUEUE_DEPTH) {\n this.queue.shift(); // drop-oldest: bound latency + memory under a slow sink\n }\n this.queue.push(bytes);\n }\n\n /**\n * Ensures the sink is open (opening it lazily on first use) and then drains the queue. The open is\n * kicked off once; subsequent calls just drain. Best-effort — an open/append failure is logged and the\n * player keeps trying on the next chunk.\n */\n private async ensureOpenThenDrain(): Promise<void> {\n if (!this.sink.IsOpen && !this.opening) {\n this.opening = true;\n try {\n await this.sink.Open();\n if (this.disposed) {\n this.sink.Close();\n return;\n }\n this.sink.SetMuted(this.muted);\n } catch (err) {\n this.opening = false;\n console.warn(`[RemoteBrowserAudio] failed to open audio sink: ${err instanceof Error ? err.message : String(err)}`);\n return;\n }\n }\n void this.drain();\n }\n\n /**\n * Appends queued chunks to the sink one at a time (a SourceBuffer rejects concurrent appends). Re-entrant\n * safe via the {@link appending} guard; stops when the queue empties, the sink closes, or on disposal.\n */\n private async drain(): Promise<void> {\n if (this.appending || this.disposed || !this.sink.IsOpen) {\n return;\n }\n this.appending = true;\n try {\n while (this.queue.length > 0 && this.sink.IsOpen && !this.disposed) {\n const bytes = this.queue.shift()!;\n try {\n await this.sink.AppendChunk(bytes);\n } catch (err) {\n // A single rejected append (e.g. a quota hiccup) shouldn't kill playback — drop it and continue.\n console.warn(`[RemoteBrowserAudio] dropped an audio chunk on append: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } finally {\n this.appending = false;\n }\n }\n\n /**\n * Decodes a base64 string to bytes WITHOUT the DOM `atob` so the queueing logic is testable in Node too.\n * Returns `null` on malformed input (the chunk is then skipped).\n *\n * @param base64 The base64 string to decode.\n * @returns The decoded bytes, or `null` when the input can't be decoded.\n */\n private decodeBase64(base64: string): Uint8Array | null {\n try {\n // Prefer the platform decoder when present (browser `atob`), else Node's Buffer.\n const globalAtob = (globalThis as { atob?: (s: string) => string }).atob;\n if (typeof globalAtob === 'function') {\n const binary = globalAtob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n }\n const nodeBuffer = (globalThis as { Buffer?: { from(s: string, enc: string): Uint8Array } }).Buffer;\n if (nodeBuffer) {\n return Uint8Array.from(nodeBuffer.from(base64, 'base64'));\n }\n return null;\n } catch {\n return null;\n }\n }\n}\n\n/**\n * The real, DOM-backed {@link IAudioSink}: a `MediaSource` whose single `audio/webm;codecs=opus`\n * `SourceBuffer` is fed appended chunks, attached to a hidden `<audio>` element that auto-plays. Autoplay\n * is permitted because the realtime call the user already started is the activating gesture.\n *\n * Kept out of the player's hot path so the queueing logic stays unit-testable; this class is exercised in\n * the live app (it can't be meaningfully unit-tested without a browser media stack).\n */\nexport class MediaSourceAudioSink implements IAudioSink {\n /** MIME the SourceBuffer is created with — the in-page recorder's output container/codec. */\n private static readonly MIME = 'audio/webm;codecs=opus';\n\n /** The hidden audio element playback is routed through; `null` before {@link Open} / after {@link Close}. */\n private audio: HTMLAudioElement | null = null;\n /** The MediaSource backing the audio element; `null` before {@link Open} / after {@link Close}. */\n private mediaSource: MediaSource | null = null;\n /** The single source buffer chunks are appended to; `null` until `sourceopen` wires it. */\n private sourceBuffer: SourceBuffer | null = null;\n /** Object URL bound to the audio element's `src`, revoked on {@link Close}. */\n private objectUrl: string | null = null;\n /** Latched muted state applied to the audio element as it (re)opens. */\n private mutedState = false;\n\n /** @inheritdoc */\n public get IsOpen(): boolean {\n return this.sourceBuffer !== null && this.mediaSource?.readyState === 'open';\n }\n\n /** @inheritdoc */\n public async Open(): Promise<void> {\n if (this.audio) {\n return; // already open / opening\n }\n if (typeof MediaSource === 'undefined' || !MediaSource.isTypeSupported(MediaSourceAudioSink.MIME)) {\n throw new Error(`MediaSource webm-opus playback is not supported in this browser.`);\n }\n const audio = new Audio();\n audio.autoplay = true;\n audio.muted = this.mutedState;\n const mediaSource = new MediaSource();\n const objectUrl = URL.createObjectURL(mediaSource);\n audio.src = objectUrl;\n\n this.audio = audio;\n this.mediaSource = mediaSource;\n this.objectUrl = objectUrl;\n\n await new Promise<void>((resolve, reject) => {\n mediaSource.addEventListener(\n 'sourceopen',\n () => {\n try {\n this.sourceBuffer = mediaSource.addSourceBuffer(MediaSourceAudioSink.MIME);\n this.sourceBuffer.mode = 'sequence';\n resolve();\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n },\n { once: true },\n );\n });\n // Best-effort start — autoplay is allowed (the call is the user gesture); ignore a rejected play().\n void audio.play().catch(() => undefined);\n }\n\n /** @inheritdoc */\n public async AppendChunk(bytes: Uint8Array): Promise<void> {\n const buffer = this.sourceBuffer;\n if (!buffer || !this.IsOpen) {\n throw new Error('Audio sink is not open.');\n }\n await new Promise<void>((resolve, reject) => {\n const onUpdateEnd = (): void => {\n buffer.removeEventListener('updateend', onUpdateEnd);\n buffer.removeEventListener('error', onError);\n resolve();\n };\n const onError = (): void => {\n buffer.removeEventListener('updateend', onUpdateEnd);\n buffer.removeEventListener('error', onError);\n reject(new Error('SourceBuffer append error.'));\n };\n buffer.addEventListener('updateend', onUpdateEnd);\n buffer.addEventListener('error', onError);\n try {\n // Copy into a fresh, definitely-non-shared ArrayBuffer so the typed `BufferSource` signature is\n // satisfied across TS lib versions (a plain Uint8Array may be typed as ArrayBufferLike).\n const copy = new Uint8Array(bytes.length);\n copy.set(bytes);\n buffer.appendBuffer(copy);\n } catch (err) {\n buffer.removeEventListener('updateend', onUpdateEnd);\n buffer.removeEventListener('error', onError);\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n }\n\n /** @inheritdoc */\n public SetMuted(muted: boolean): void {\n this.mutedState = muted;\n if (this.audio) {\n this.audio.muted = muted;\n }\n }\n\n /** @inheritdoc */\n public Close(): void {\n if (this.audio) {\n this.audio.pause();\n this.audio.removeAttribute('src');\n this.audio.load();\n this.audio = null;\n }\n if (this.objectUrl) {\n URL.revokeObjectURL(this.objectUrl);\n this.objectUrl = null;\n }\n this.sourceBuffer = null;\n this.mediaSource = null;\n }\n}\n"]}
@@ -0,0 +1,168 @@
1
+ import type { Type } from '@angular/core';
2
+ import { RealtimeToolDefinition } from '@memberjunction/ai';
3
+ import { BaseRealtimeChannelClient, ChannelOnboardingDetails } from '../channels/base-realtime-channel-client';
4
+ import { RemoteBrowserSurfaceComponent } from './remote-browser-surface.component';
5
+ import { RemoteBrowserAudioChunkInput } from './remote-browser-audio-player';
6
+ /**
7
+ * The REMOTE BROWSER as a pluggable interactive channel — a {@link BaseRealtimeChannelClient}
8
+ * resolved from the `MJ: AI Agent Channels` registry row whose `ClientPluginClass` is
9
+ * `'RealtimeRemoteBrowserChannel'`. One instance per session (ClassFactory-created at session
10
+ * start).
11
+ *
12
+ * Topology (CLIENT-DIRECT, like the whiteboard — the browser just happens to live on the
13
+ * server):
14
+ * - **Action**: the `browser_*` client-executed tool set ({@link REMOTE_BROWSER_TOOL_DEFINITIONS}).
15
+ * {@link ApplyAgentTool} maps a tool call → a normalized {@link RemoteBrowserAction}
16
+ * ({@link MapToolToAction}) and awaits the `ExecuteRemoteBrowserAction` GraphQL mutation
17
+ * (via {@link RealtimeChannelContext.ExecuteServerAction}) to drive the server browser,
18
+ * returning a concise JSON result for the model. Never throws — argument and transport
19
+ * failures map to a `{ success: false, error }` payload.
20
+ * - **Perception**: after a successful action, a `[browser]` context note carries the new
21
+ * page URL so the agent perceives where the page went; the surface independently polls a
22
+ * live screenshot.
23
+ * - **Surface**: {@link RemoteBrowserSurfaceComponent}, created dynamically in the channel
24
+ * tab; the plugin hands it the session's provider + id in {@link BindSurface} so it can
25
+ * poll `RemoteBrowserSnapshot`. HUMAN TAKEOVER is enabled by default (Collaborative): the
26
+ * user can click/type into the live canvas and those events relay to the server browser via
27
+ * {@link RELAY_HUMAN_INPUT_MUTATION} (server-gated on the backend's `HumanTakeover` capability).
28
+ *
29
+ * The channel keeps NO client-side state of record — the browser's state lives entirely on
30
+ * the server — so {@link SerializeState} / {@link RestoreState} use the base no-op behavior.
31
+ */
32
+ export declare class RemoteBrowserChannel extends BaseRealtimeChannelClient<RemoteBrowserSurfaceComponent> {
33
+ /** The live bound surface, when the channel tab's pane is instantiated. */
34
+ private surface;
35
+ /** Subscription to the bound surface's `HumanInput` output, torn down on unbind/dispose. */
36
+ private humanInputSub;
37
+ /**
38
+ * Whether the server confirmed it is PUSHING screencast frames for this session (`ScreenStreaming`
39
+ * supported). When `true` the surface renders pushed frames on its canvas and skips its poll, and
40
+ * {@link OnScreencastFrame} forwards each pushed frame to it. When `false` the surface keeps polling.
41
+ */
42
+ private streaming;
43
+ /**
44
+ * Whether the server confirmed it is PUSHING tab-audio chunks for this session (the backend can capture
45
+ * audio). When `true` {@link OnAudioChunk} feeds each pushed chunk to {@link audioPlayer}; when `false`
46
+ * no audio plays.
47
+ */
48
+ private audioStreaming;
49
+ /** The client-side audio player fed pushed chunks while {@link audioStreaming}; `null` until started. */
50
+ private audioPlayer;
51
+ /** Subscription to the bound surface's `AudioMutedChange` output (the speaker toggle), torn down on unbind. */
52
+ private audioMutedSub;
53
+ get ChannelName(): string;
54
+ get ToolNamePrefix(): string;
55
+ get TabTitle(): string;
56
+ get TabIcon(): string;
57
+ GetToolDefinitions(): RealtimeToolDefinition[];
58
+ GetSurfaceComponent(): Type<RemoteBrowserSurfaceComponent>;
59
+ /** First-run intro shown the first time the user opens the Browser tab (once per user). */
60
+ GetOnboardingDetails(): ChannelOnboardingDetails;
61
+ /**
62
+ * Wires the dynamically-created surface: hands it a snapshot fetcher closing over the
63
+ * channel context (session id + provider live there), so the surface stays transport-
64
+ * agnostic. Set BEFORE the surface's first change detection (the pane binds synchronously),
65
+ * so its `ngOnInit` poll has the fetcher.
66
+ *
67
+ * Also asks the server to start a live screencast. When the backend supports `ScreenStreaming` the
68
+ * server starts PUSHING frames and reports `Streaming: true`; we flip the surface to canvas mode (its
69
+ * poll is then skipped) and {@link OnScreencastFrame} paints each pushed frame. When the backend lacks
70
+ * the capability (`Streaming: false`) the surface keeps the snapshot poll already running — graceful
71
+ * fallback, no further action.
72
+ *
73
+ * HUMAN TAKEOVER is enabled BY DEFAULT (Collaborative): the surface is flipped `Interactive = true` and
74
+ * its `HumanInput` output subscribed, forwarding each pointer/keyboard event to the server via
75
+ * {@link RELAY_HUMAN_INPUT_MUTATION}. Takeover only takes effect on the canvas (screencast) path; the
76
+ * `<img>` poll fallback stays view-only. The server gates the actual routing on the backend's
77
+ * `HumanTakeover` capability, so this is safe to enable unconditionally.
78
+ */
79
+ BindSurface(instance: RemoteBrowserSurfaceComponent): void;
80
+ UnbindSurface(): void;
81
+ Dispose(): void;
82
+ /**
83
+ * Forwards one PUSHED screencast frame to the bound surface's canvas. Called by the session service
84
+ * when a `RemoteBrowserScreencastFrame` arrives on the push-status stream for THIS session. No-op when
85
+ * the channel isn't streaming or has no bound surface (e.g. the tab pane is collapsed).
86
+ *
87
+ * @param dataBase64 The frame image as raw base64 JPEG (no `data:` prefix).
88
+ */
89
+ OnScreencastFrame(dataBase64: string): void;
90
+ /**
91
+ * Feeds one PUSHED tab-audio chunk to the client-side audio player. Called by the session service when a
92
+ * `RemoteBrowserAudioChunk` arrives on the push-status stream for THIS session. No-op when the channel
93
+ * isn't audio-streaming or the player has been torn down.
94
+ *
95
+ * @param chunk The pushed audio chunk (base64 webm-opus + codec/seq metadata).
96
+ */
97
+ OnAudioChunk(chunk: RemoteBrowserAudioChunkInput): void;
98
+ /**
99
+ * Asks the server to start the live screencast and, on success, flips the surface to canvas mode so it
100
+ * stops polling and starts painting pushed frames. Best-effort: a `null`/`Streaming: false` result
101
+ * (capability absent or transport failure) leaves the surface on its poll fallback.
102
+ */
103
+ private startScreencast;
104
+ /**
105
+ * Asks the server to start the live tab-audio stream and, on success, spins up the client-side
106
+ * {@link RemoteBrowserAudioPlayer} (speaker ON by default — the call is the activating user gesture) so
107
+ * pushed chunks play. Best-effort: a `null`/`Streaming: false` result (no audio capability or transport
108
+ * failure) leaves the channel silent with no player.
109
+ *
110
+ * @param instance The bound surface, flipped to show the speaker toggle when audio starts.
111
+ */
112
+ private startAudioStream;
113
+ /** Asks the server to stop the tab-audio stream (best-effort), disposes the player, and clears state. */
114
+ private stopAudioStream;
115
+ /**
116
+ * Relays ONE human-takeover input from the surface to the server's {@link RELAY_HUMAN_INPUT_MUTATION}.
117
+ * Best-effort and fire-and-forget: never throws, and the boolean result is ignored (the server already
118
+ * gates routing on the backend's `HumanTakeover` capability). No-op when no live session id exists.
119
+ *
120
+ * @param input The flattened pointer/keyboard input the user performed on the live canvas.
121
+ */
122
+ private relayHumanInput;
123
+ /** Asks the server to stop the screencast (best-effort) and clears the streaming flag. */
124
+ private stopScreencast;
125
+ /**
126
+ * Fetches one live snapshot of the server browser for the surface — the PERCEPTION poll.
127
+ * Best-effort by contract ({@link RemoteBrowserSnapshotFetcher}): returns `null` when no
128
+ * session is live or the query fails (the surface keeps its last good frame).
129
+ */
130
+ private fetchSnapshot;
131
+ /**
132
+ * Executes ONE `browser_*` tool call: maps it to a server action and awaits the
133
+ * `ExecuteRemoteBrowserAction` mutation, then returns a concise JSON result the model reads.
134
+ * On success it also feeds the new page URL to the agent as a `[browser]` context note. Never
135
+ * throws — bad arguments, no live session, and server failures all become a failed-result
136
+ * payload so the model can narrate the problem.
137
+ */
138
+ ApplyAgentTool(toolName: string, argsJson: string): Promise<string>;
139
+ /** Builds the flat mutation variables from a normalized action (omitted fields ride as null). */
140
+ private buildVariables;
141
+ /**
142
+ * Runs the VISION-QUERY path for the `browser_DescribePage` / `browser_LocateElement` tools: awaits the
143
+ * `InterpretRemoteBrowserPage` mutation (server captures a screenshot + runs a fast vision model) and returns
144
+ * a concise JSON string the model reads. For a describe (no `query`) it returns `{ description }`; for a locate
145
+ * it returns `{ found, element, all }` so the agent can `browser_Click` the centroid. Never throws — a null
146
+ * session, no response, or a server detail-only result all map to a result string the model can narrate.
147
+ *
148
+ * @param query The visual target to locate, or `undefined` for a plain page description.
149
+ * @returns A JSON string describing the interpretation for the model.
150
+ */
151
+ private interpretPage;
152
+ /** Coerces a parsed tool-arg to a non-empty string, or `undefined` when absent / wrong-typed. */
153
+ private asArgString;
154
+ /** Parses the tool-args JSON into a plain object; returns `{}` for empty/malformed input. */
155
+ private parseArgs;
156
+ /** Builds a successful tool-result JSON string for the model. */
157
+ private ok;
158
+ /** Builds a failed tool-result JSON string for the model. */
159
+ private fail;
160
+ }
161
+ /**
162
+ * Tree-shaking prevention: the Remote Browser channel is resolved dynamically through the
163
+ * ClassFactory (by the registry row's `ClientPluginClass` key), so this static call is what
164
+ * keeps its `@RegisterClass` side effect from being eliminated by the bundler. Called from
165
+ * `conversations.module.ts` alongside {@link LoadRealtimeWhiteboardChannel}.
166
+ */
167
+ export declare function LoadRealtimeRemoteBrowserChannel(): void;
168
+ //# sourceMappingURL=remote-browser-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-browser-channel.d.ts","sourceRoot":"","sources":["../../../../../src/lib/components/realtime/remote-browser/remote-browser-channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG1C,OAAO,EAAa,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAC/G,OAAO,EAA2D,6BAA6B,EAAE,MAAM,oCAAoC,CAAC;AAS5I,OAAO,EAAwB,4BAA4B,EAA4B,MAAM,+BAA+B,CAAC;AA0K7H;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBACa,oBAAqB,SAAQ,yBAAyB,CAAC,6BAA6B,CAAC;IAChG,2EAA2E;IAC3E,OAAO,CAAC,OAAO,CAA8C;IAE7D,4FAA4F;IAC5F,OAAO,CAAC,aAAa,CAA6B;IAElD;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAS;IAE/B,yGAAyG;IACzG,OAAO,CAAC,WAAW,CAAyC;IAE5D,+GAA+G;IAC/G,OAAO,CAAC,aAAa,CAA6B;IAElD,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED,IAAW,cAAc,IAAI,MAAM,CAElC;IAED,IAAW,QAAQ,IAAI,MAAM,CAE5B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEM,kBAAkB,IAAI,sBAAsB,EAAE;IAIrC,mBAAmB,IAAI,IAAI,CAAC,6BAA6B,CAAC;IAI1E,2FAA2F;IAC3E,oBAAoB,IAAI,wBAAwB;IAgBhE;;;;;;;;;;;;;;;;;OAiBG;IACI,WAAW,CAAC,QAAQ,EAAE,6BAA6B,GAAG,IAAI;IAUjD,aAAa,IAAI,IAAI;IAUrB,OAAO,IAAI,IAAI;IAU/B;;;;;;OAMG;IACI,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMlD;;;;;;OAMG;IACI,YAAY,CAAC,KAAK,EAAE,4BAA4B,GAAG,IAAI;IAM9D;;;;OAIG;YACW,eAAe;IAY7B;;;;;;;OAOG;YACW,gBAAgB;IAe9B,yGAAyG;YAC3F,eAAe;IAY7B;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAmBvB,0FAA0F;YAC5E,cAAc;IAU5B;;;;OAIG;YACW,aAAa;IAS3B;;;;;;OAMG;IACU,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwDhF,iGAAiG;IACjG,OAAO,CAAC,cAAc;IAgBtB;;;;;;;;;OASG;YACW,aAAa;IAmC3B,iGAAiG;IACjG,OAAO,CAAC,WAAW;IAInB,6FAA6F;IAC7F,OAAO,CAAC,SAAS;IAajB,iEAAiE;IACjE,OAAO,CAAC,EAAE;IAWV,6DAA6D;IAC7D,OAAO,CAAC,IAAI;CAIb;AAED;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD"}