@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.
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +0 -1
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js +56 -52
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.d.ts +3 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.js +52 -52
- package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.js +10 -14
- package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-share-modal.component.d.ts +3 -1
- package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-share-modal.component.js +56 -56
- package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-tree.component.d.ts +3 -1
- package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-tree.component.js +10 -7
- package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
- package/dist/lib/components/collection/collection-view.component.d.ts +3 -1
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +29 -41
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.d.ts +4 -0
- package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.js +134 -166
- package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.d.ts +103 -0
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -0
- package/dist/lib/components/composer/ai-composer.component.js +263 -0
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -0
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +76 -12
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +522 -158
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.js +6 -9
- package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
- package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/library/library-full-view.component.js +20 -24
- package/dist/lib/components/library/library-full-view.component.js.map +1 -1
- package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
- package/dist/lib/components/members/members-modal.component.js +10 -11
- package/dist/lib/components/members/members-modal.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +87 -23
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +245 -158
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +11 -3
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +73 -21
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.js +6 -9
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts +4 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.js +9 -4
- package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +45 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +10 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts +46 -0
- package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/channels/client-context-channel.js +174 -0
- package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts +123 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +397 -0
- package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +103 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-state.js +143 -0
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-tools.d.ts +24 -0
- package/dist/lib/components/realtime/media/media-channel-tools.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel-tools.js +107 -0
- package/dist/lib/components/realtime/media/media-channel-tools.js.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts +89 -0
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/media-channel.js +253 -0
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +141 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +574 -0
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +59 -14
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.js +277 -74
- package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +28 -27
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +144 -143
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +105 -43
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +2 -2
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +41 -7
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +216 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +0 -2
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.js +67 -71
- package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +364 -44
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +839 -153
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.d.ts +5 -5
- package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts +70 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.js +115 -0
- package/dist/lib/components/realtime/realtime-surface-tab-style.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +55 -57
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +165 -143
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +80 -71
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +83 -113
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +270 -0
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-ui-config.js +212 -0
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -0
- package/dist/lib/components/search/search-panel.component.d.ts +2 -0
- package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
- package/dist/lib/components/search/search-panel.component.js +92 -101
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/share/share-modal.component.js +10 -11
- package/dist/lib/components/share/share-modal.component.js.map +1 -1
- package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
- package/dist/lib/components/shared/user-picker.component.js +12 -15
- package/dist/lib/components/shared/user-picker.component.js.map +1 -1
- package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts +11 -3
- package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts.map +1 -1
- package/dist/lib/components/sidebar/conversation-sidebar.component.js +34 -10
- package/dist/lib/components/sidebar/conversation-sidebar.component.js.map +1 -1
- package/dist/lib/components/sidebar/routines-section.component.d.ts +73 -0
- package/dist/lib/components/sidebar/routines-section.component.d.ts.map +1 -0
- package/dist/lib/components/sidebar/routines-section.component.js +211 -0
- package/dist/lib/components/sidebar/routines-section.component.js.map +1 -0
- package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +12 -0
- package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js +37 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +16 -5
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +51 -31
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/composer-plugins/agent-mention.provider.d.ts +16 -0
- package/dist/lib/composer-plugins/agent-mention.provider.d.ts.map +1 -0
- package/dist/lib/composer-plugins/agent-mention.provider.js +28 -0
- package/dist/lib/composer-plugins/agent-mention.provider.js.map +1 -0
- package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts +20 -0
- package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts.map +1 -0
- package/dist/lib/composer-plugins/base-conversation-mention.provider.js +32 -0
- package/dist/lib/composer-plugins/base-conversation-mention.provider.js.map +1 -0
- package/dist/lib/composer-plugins/load-composer-plugins.d.ts +9 -0
- package/dist/lib/composer-plugins/load-composer-plugins.d.ts.map +1 -0
- package/dist/lib/composer-plugins/load-composer-plugins.js +17 -0
- package/dist/lib/composer-plugins/load-composer-plugins.js.map +1 -0
- package/dist/lib/composer-plugins/record-mention.provider.d.ts +14 -0
- package/dist/lib/composer-plugins/record-mention.provider.d.ts.map +1 -0
- package/dist/lib/composer-plugins/record-mention.provider.js +26 -0
- package/dist/lib/composer-plugins/record-mention.provider.js.map +1 -0
- package/dist/lib/composer-plugins/skill-command.provider.d.ts +15 -0
- package/dist/lib/composer-plugins/skill-command.provider.d.ts.map +1 -0
- package/dist/lib/composer-plugins/skill-command.provider.js +27 -0
- package/dist/lib/composer-plugins/skill-command.provider.js.map +1 -0
- package/dist/lib/conversations.module.d.ts +68 -64
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +69 -18
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/models/conversation-state.model.d.ts +6 -3
- package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
- package/dist/lib/models/conversation-state.model.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +9 -2
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +26 -2
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-attachment.service.d.ts +1 -1
- package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-attachment.service.js.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.d.ts +42 -19
- package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.js +113 -18
- package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +143 -0
- package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -0
- package/dist/lib/services/realtime-audio-recorder.js +379 -0
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -0
- package/dist/lib/services/realtime-pcm-wav.d.ts +75 -0
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -0
- package/dist/lib/services/realtime-pcm-wav.js +205 -0
- package/dist/lib/services/realtime-pcm-wav.js.map +1 -0
- package/dist/lib/services/realtime-session-review.service.d.ts +28 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js +12 -3
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/realtime-session.service.d.ts +210 -20
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +452 -21
- package/dist/lib/services/realtime-session.service.js.map +1 -1
- package/dist/lib/services/realtime-sessions-adapter.js +1 -1
- package/dist/lib/services/realtime-sessions-adapter.js.map +1 -1
- package/dist/lib/services/ui-command-handler.service.d.ts +7 -2
- package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
- package/dist/lib/services/ui-command-handler.service.js +6 -2
- package/dist/lib/services/ui-command-handler.service.js.map +1 -1
- package/dist/lib/utils/plan-mode-preference.d.ts +62 -0
- package/dist/lib/utils/plan-mode-preference.d.ts.map +1 -0
- package/dist/lib/utils/plan-mode-preference.js +130 -0
- package/dist/lib/utils/plan-mode-preference.js.map +1 -0
- package/dist/public-api.d.ts +29 -3
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +46 -3
- package/dist/public-api.js.map +1 -1
- package/package.json +30 -26
- package/dist/__tests__/attachment-badge.test.d.ts +0 -2
- package/dist/__tests__/attachment-badge.test.d.ts.map +0 -1
- package/dist/__tests__/attachment-badge.test.js +0 -66
- package/dist/__tests__/attachment-badge.test.js.map +0 -1
- package/dist/__tests__/channel-optional-surface.test.d.ts +0 -2
- package/dist/__tests__/channel-optional-surface.test.d.ts.map +0 -1
- package/dist/__tests__/channel-optional-surface.test.js +0 -53
- package/dist/__tests__/channel-optional-surface.test.js.map +0 -1
- package/dist/__tests__/chat-events.test.d.ts +0 -14
- package/dist/__tests__/chat-events.test.d.ts.map +0 -1
- package/dist/__tests__/chat-events.test.js +0 -109
- package/dist/__tests__/chat-events.test.js.map +0 -1
- package/dist/__tests__/collections-full-view.test.d.ts +0 -2
- package/dist/__tests__/collections-full-view.test.d.ts.map +0 -1
- package/dist/__tests__/collections-full-view.test.js +0 -225
- package/dist/__tests__/collections-full-view.test.js.map +0 -1
- package/dist/__tests__/conversation-cache.test.d.ts +0 -2
- package/dist/__tests__/conversation-cache.test.d.ts.map +0 -1
- package/dist/__tests__/conversation-cache.test.js +0 -233
- package/dist/__tests__/conversation-cache.test.js.map +0 -1
- package/dist/__tests__/conversation-naming.test.d.ts +0 -2
- package/dist/__tests__/conversation-naming.test.d.ts.map +0 -1
- package/dist/__tests__/conversation-naming.test.js +0 -110
- package/dist/__tests__/conversation-naming.test.js.map +0 -1
- package/dist/__tests__/delegation-result-parser.test.d.ts +0 -2
- package/dist/__tests__/delegation-result-parser.test.d.ts.map +0 -1
- package/dist/__tests__/delegation-result-parser.test.js +0 -107
- package/dist/__tests__/delegation-result-parser.test.js.map +0 -1
- package/dist/__tests__/distinct-artifacts.test.d.ts +0 -2
- package/dist/__tests__/distinct-artifacts.test.d.ts.map +0 -1
- package/dist/__tests__/distinct-artifacts.test.js +0 -56
- package/dist/__tests__/distinct-artifacts.test.js.map +0 -1
- package/dist/__tests__/event-wiring.test.d.ts +0 -15
- package/dist/__tests__/event-wiring.test.d.ts.map +0 -1
- package/dist/__tests__/event-wiring.test.js +0 -100
- package/dist/__tests__/event-wiring.test.js.map +0 -1
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -7
- package/dist/__tests__/index.test.js.map +0 -1
- package/dist/__tests__/narration-template.test.d.ts +0 -2
- package/dist/__tests__/narration-template.test.d.ts.map +0 -1
- package/dist/__tests__/narration-template.test.js +0 -76
- package/dist/__tests__/narration-template.test.js.map +0 -1
- package/dist/__tests__/notification-model.test.d.ts +0 -2
- package/dist/__tests__/notification-model.test.d.ts.map +0 -1
- package/dist/__tests__/notification-model.test.js +0 -68
- package/dist/__tests__/notification-model.test.js.map +0 -1
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts +0 -2
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-agent-picker-models.test.js +0 -49
- package/dist/__tests__/realtime-agent-picker-models.test.js.map +0 -1
- package/dist/__tests__/realtime-audio-visuals.test.d.ts +0 -2
- package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-audio-visuals.test.js +0 -123
- package/dist/__tests__/realtime-audio-visuals.test.js.map +0 -1
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +0 -2
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-delegation-card-cancel.test.js +0 -48
- package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +0 -1
- package/dist/__tests__/realtime-disclosure.test.d.ts +0 -2
- package/dist/__tests__/realtime-disclosure.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-disclosure.test.js +0 -164
- package/dist/__tests__/realtime-disclosure.test.js.map +0 -1
- package/dist/__tests__/realtime-pairing.test.d.ts +0 -2
- package/dist/__tests__/realtime-pairing.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-pairing.test.js +0 -207
- package/dist/__tests__/realtime-pairing.test.js.map +0 -1
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts +0 -2
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-review-lifecycle.test.js +0 -154
- package/dist/__tests__/realtime-review-lifecycle.test.js.map +0 -1
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-cancel-usage.test.js +0 -230
- package/dist/__tests__/realtime-session-cancel-usage.test.js.map +0 -1
- package/dist/__tests__/realtime-session-channels.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-channels.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-channels.test.js +0 -252
- package/dist/__tests__/realtime-session-channels.test.js.map +0 -1
- package/dist/__tests__/realtime-session-client-tools.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-client-tools.test.js +0 -103
- package/dist/__tests__/realtime-session-client-tools.test.js.map +0 -1
- package/dist/__tests__/realtime-session-minimized.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-minimized.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-minimized.test.js +0 -32
- package/dist/__tests__/realtime-session-minimized.test.js.map +0 -1
- package/dist/__tests__/realtime-session-mint.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-mint.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-mint.test.js +0 -69
- package/dist/__tests__/realtime-session-mint.test.js.map +0 -1
- package/dist/__tests__/realtime-session-policy.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-policy.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-policy.test.js +0 -303
- package/dist/__tests__/realtime-session-policy.test.js.map +0 -1
- package/dist/__tests__/realtime-session-review.service.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-review.service.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-review.service.test.js +0 -743
- package/dist/__tests__/realtime-session-review.service.test.js.map +0 -1
- package/dist/__tests__/realtime-session-state.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-state.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-state.test.js +0 -83
- package/dist/__tests__/realtime-session-state.test.js.map +0 -1
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-timeline-card.test.js +0 -106
- package/dist/__tests__/realtime-session-timeline-card.test.js.map +0 -1
- package/dist/__tests__/realtime-session-timeline.test.d.ts +0 -2
- package/dist/__tests__/realtime-session-timeline.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-session-timeline.test.js +0 -142
- package/dist/__tests__/realtime-session-timeline.test.js.map +0 -1
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts +0 -19
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-sessions-adapter.test.js +0 -188
- package/dist/__tests__/realtime-sessions-adapter.test.js.map +0 -1
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +0 -2
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-surface-panel-prefs.test.js +0 -100
- package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +0 -1
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +0 -2
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +0 -1
- package/dist/__tests__/realtime-surface-tabs-model.test.js +0 -193
- package/dist/__tests__/realtime-surface-tabs-model.test.js.map +0 -1
- package/dist/__tests__/remote-browser-audio-player.test.d.ts +0 -2
- package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +0 -1
- package/dist/__tests__/remote-browser-audio-player.test.js +0 -137
- package/dist/__tests__/remote-browser-audio-player.test.js.map +0 -1
- package/dist/__tests__/remote-browser-channel.test.d.ts +0 -2
- package/dist/__tests__/remote-browser-channel.test.d.ts.map +0 -1
- package/dist/__tests__/remote-browser-channel.test.js +0 -585
- package/dist/__tests__/remote-browser-channel.test.js.map +0 -1
- package/dist/__tests__/search-service.test.d.ts +0 -2
- package/dist/__tests__/search-service.test.d.ts.map +0 -1
- package/dist/__tests__/search-service.test.js +0 -114
- package/dist/__tests__/search-service.test.js.map +0 -1
- package/dist/__tests__/slot-defaults.test.d.ts +0 -24
- package/dist/__tests__/slot-defaults.test.d.ts.map +0 -1
- package/dist/__tests__/slot-defaults.test.js +0 -63
- package/dist/__tests__/slot-defaults.test.js.map +0 -1
- package/dist/__tests__/toast-service.test.d.ts +0 -2
- package/dist/__tests__/toast-service.test.d.ts.map +0 -1
- package/dist/__tests__/toast-service.test.js +0 -143
- package/dist/__tests__/toast-service.test.js.map +0 -1
- package/dist/__tests__/user-authorization.test.d.ts +0 -2
- package/dist/__tests__/user-authorization.test.d.ts.map +0 -1
- package/dist/__tests__/user-authorization.test.js +0 -97
- package/dist/__tests__/user-authorization.test.js.map +0 -1
- package/dist/__tests__/voice-session-narration.test.d.ts +0 -2
- package/dist/__tests__/voice-session-narration.test.d.ts.map +0 -1
- package/dist/__tests__/voice-session-narration.test.js +0 -609
- package/dist/__tests__/voice-session-narration.test.js.map +0 -1
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-artifact-viewer.test.js +0 -101
- package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +0 -1
- package/dist/__tests__/whiteboard-channel.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-channel.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-channel.test.js +0 -260
- package/dist/__tests__/whiteboard-channel.test.js.map +0 -1
- package/dist/__tests__/whiteboard-restore-state.test.d.ts +0 -2
- package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +0 -1
- package/dist/__tests__/whiteboard-restore-state.test.js +0 -108
- package/dist/__tests__/whiteboard-restore-state.test.js.map +0 -1
- package/dist/lib/components/mention/mention-dropdown.component.d.ts +0 -53
- package/dist/lib/components/mention/mention-dropdown.component.d.ts.map +0 -1
- package/dist/lib/components/mention/mention-dropdown.component.js +0 -276
- package/dist/lib/components/mention/mention-dropdown.component.js.map +0 -1
- package/dist/lib/components/mention/mention-editor.component.d.ts +0 -256
- package/dist/lib/components/mention/mention-editor.component.d.ts.map +0 -1
- package/dist/lib/components/mention/mention-editor.component.js +0 -1233
- package/dist/lib/components/mention/mention-editor.component.js.map +0 -1
- package/dist/lib/components/message/message-input-box.component.d.ts +0 -137
- package/dist/lib/components/message/message-input-box.component.d.ts.map +0 -1
- package/dist/lib/components/message/message-input-box.component.js +0 -321
- 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
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
825
|
-
* (the client already drives the speaking state)
|
|
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
|
|
981
|
-
* ride along as {@link
|
|
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
|
|
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() {
|