@memberjunction/ng-conversations 5.40.2 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/__tests__/channel-optional-surface.test.d.ts +2 -0
- package/dist/__tests__/channel-optional-surface.test.d.ts.map +1 -0
- package/dist/__tests__/channel-optional-surface.test.js +53 -0
- package/dist/__tests__/channel-optional-surface.test.js.map +1 -0
- package/dist/__tests__/chat-events.test.d.ts +14 -0
- package/dist/__tests__/chat-events.test.d.ts.map +1 -0
- package/dist/__tests__/chat-events.test.js +109 -0
- package/dist/__tests__/chat-events.test.js.map +1 -0
- package/dist/__tests__/conversation-naming.test.d.ts +2 -0
- package/dist/__tests__/conversation-naming.test.d.ts.map +1 -0
- package/dist/__tests__/conversation-naming.test.js +110 -0
- package/dist/__tests__/conversation-naming.test.js.map +1 -0
- package/dist/__tests__/delegation-result-parser.test.d.ts +2 -0
- package/dist/__tests__/delegation-result-parser.test.d.ts.map +1 -0
- package/dist/__tests__/delegation-result-parser.test.js +107 -0
- package/dist/__tests__/delegation-result-parser.test.js.map +1 -0
- package/dist/__tests__/event-wiring.test.d.ts +15 -0
- package/dist/__tests__/event-wiring.test.d.ts.map +1 -0
- package/dist/__tests__/event-wiring.test.js +100 -0
- package/dist/__tests__/event-wiring.test.js.map +1 -0
- package/dist/__tests__/narration-template.test.d.ts +2 -0
- package/dist/__tests__/narration-template.test.d.ts.map +1 -0
- package/dist/__tests__/narration-template.test.js +76 -0
- package/dist/__tests__/narration-template.test.js.map +1 -0
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts +2 -0
- package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-agent-picker-models.test.js +49 -0
- package/dist/__tests__/realtime-agent-picker-models.test.js.map +1 -0
- package/dist/__tests__/realtime-audio-visuals.test.d.ts +2 -0
- package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-audio-visuals.test.js +123 -0
- package/dist/__tests__/realtime-audio-visuals.test.js.map +1 -0
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +2 -0
- package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-delegation-card-cancel.test.js +48 -0
- package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +1 -0
- package/dist/__tests__/realtime-disclosure.test.d.ts +2 -0
- package/dist/__tests__/realtime-disclosure.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-disclosure.test.js +164 -0
- package/dist/__tests__/realtime-disclosure.test.js.map +1 -0
- package/dist/__tests__/realtime-pairing.test.d.ts +2 -0
- package/dist/__tests__/realtime-pairing.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-pairing.test.js +207 -0
- package/dist/__tests__/realtime-pairing.test.js.map +1 -0
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts +2 -0
- package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-review-lifecycle.test.js +154 -0
- package/dist/__tests__/realtime-review-lifecycle.test.js.map +1 -0
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-cancel-usage.test.js +230 -0
- package/dist/__tests__/realtime-session-cancel-usage.test.js.map +1 -0
- package/dist/__tests__/realtime-session-channels.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-channels.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-channels.test.js +252 -0
- package/dist/__tests__/realtime-session-channels.test.js.map +1 -0
- package/dist/__tests__/realtime-session-client-tools.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-client-tools.test.js +103 -0
- package/dist/__tests__/realtime-session-client-tools.test.js.map +1 -0
- package/dist/__tests__/realtime-session-minimized.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-minimized.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-minimized.test.js +32 -0
- package/dist/__tests__/realtime-session-minimized.test.js.map +1 -0
- package/dist/__tests__/realtime-session-mint.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-mint.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-mint.test.js +69 -0
- package/dist/__tests__/realtime-session-mint.test.js.map +1 -0
- package/dist/__tests__/realtime-session-policy.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-policy.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-policy.test.js +303 -0
- package/dist/__tests__/realtime-session-policy.test.js.map +1 -0
- package/dist/__tests__/realtime-session-review.service.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-review.service.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-review.service.test.js +743 -0
- package/dist/__tests__/realtime-session-review.service.test.js.map +1 -0
- package/dist/__tests__/realtime-session-state.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-state.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-state.test.js +83 -0
- package/dist/__tests__/realtime-session-state.test.js.map +1 -0
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-timeline-card.test.js +106 -0
- package/dist/__tests__/realtime-session-timeline-card.test.js.map +1 -0
- package/dist/__tests__/realtime-session-timeline.test.d.ts +2 -0
- package/dist/__tests__/realtime-session-timeline.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-session-timeline.test.js +142 -0
- package/dist/__tests__/realtime-session-timeline.test.js.map +1 -0
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts +19 -0
- package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-sessions-adapter.test.js +188 -0
- package/dist/__tests__/realtime-sessions-adapter.test.js.map +1 -0
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +2 -0
- package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-surface-panel-prefs.test.js +100 -0
- package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +1 -0
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +2 -0
- package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +1 -0
- package/dist/__tests__/realtime-surface-tabs-model.test.js +193 -0
- package/dist/__tests__/realtime-surface-tabs-model.test.js.map +1 -0
- package/dist/__tests__/remote-browser-audio-player.test.d.ts +2 -0
- package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +1 -0
- package/dist/__tests__/remote-browser-audio-player.test.js +137 -0
- package/dist/__tests__/remote-browser-audio-player.test.js.map +1 -0
- package/dist/__tests__/remote-browser-channel.test.d.ts +2 -0
- package/dist/__tests__/remote-browser-channel.test.d.ts.map +1 -0
- package/dist/__tests__/remote-browser-channel.test.js +423 -0
- package/dist/__tests__/remote-browser-channel.test.js.map +1 -0
- package/dist/__tests__/slot-defaults.test.d.ts +24 -0
- package/dist/__tests__/slot-defaults.test.d.ts.map +1 -0
- package/dist/__tests__/slot-defaults.test.js +63 -0
- package/dist/__tests__/slot-defaults.test.js.map +1 -0
- package/dist/__tests__/user-authorization.test.d.ts +2 -0
- package/dist/__tests__/user-authorization.test.d.ts.map +1 -0
- package/dist/__tests__/user-authorization.test.js +97 -0
- package/dist/__tests__/user-authorization.test.js.map +1 -0
- package/dist/__tests__/voice-session-narration.test.d.ts +2 -0
- package/dist/__tests__/voice-session-narration.test.d.ts.map +1 -0
- package/dist/__tests__/voice-session-narration.test.js +609 -0
- package/dist/__tests__/voice-session-narration.test.js.map +1 -0
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +2 -0
- package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +1 -0
- package/dist/__tests__/whiteboard-artifact-viewer.test.js +101 -0
- package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +1 -0
- package/dist/__tests__/whiteboard-channel.test.d.ts +2 -0
- package/dist/__tests__/whiteboard-channel.test.d.ts.map +1 -0
- package/dist/__tests__/whiteboard-channel.test.js +260 -0
- package/dist/__tests__/whiteboard-channel.test.js.map +1 -0
- package/dist/__tests__/whiteboard-restore-state.test.d.ts +2 -0
- package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +1 -0
- package/dist/__tests__/whiteboard-restore-state.test.js +108 -0
- package/dist/__tests__/whiteboard-restore-state.test.js.map +1 -0
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +205 -3
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +911 -342
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/mention/mention-dropdown.component.js +35 -17
- package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
- package/dist/lib/components/mention/mention-editor.component.d.ts +4 -0
- package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
- package/dist/lib/components/mention/mention-editor.component.js +43 -19
- package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
- package/dist/lib/components/message/message-input-box.component.d.ts +17 -1
- package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input-box.component.js +73 -15
- package/dist/lib/components/message/message-input-box.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +142 -6
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +328 -82
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +28 -3
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +180 -108
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +81 -2
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +252 -87
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +282 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +158 -0
- package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -0
- package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts +25 -0
- package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js +140 -0
- package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js.map +1 -0
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +35 -0
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +58 -0
- package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +63 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.js +260 -0
- package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +117 -0
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +504 -0
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +168 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.js +556 -0
- package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +97 -0
- package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-audio-visuals.js +139 -0
- package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -0
- package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts +29 -0
- package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-channel-strip.component.js +69 -0
- package/dist/lib/components/realtime/realtime-channel-strip.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +65 -0
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-composer.component.js +256 -0
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +71 -0
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-delegation-card.component.js +324 -0
- package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-disclosure.d.ts +135 -0
- package/dist/lib/components/realtime/realtime-disclosure.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-disclosure.js +188 -0
- package/dist/lib/components/realtime/realtime-disclosure.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +491 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +1274 -0
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-state.d.ts +191 -0
- package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-session-state.js +244 -0
- package/dist/lib/components/realtime/realtime-session-state.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-thread.component.d.ts +56 -0
- package/dist/lib/components/realtime/realtime-session-thread.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-session-thread.component.js +246 -0
- package/dist/lib/components/realtime/realtime-session-thread.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +51 -0
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +193 -0
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts +77 -0
- package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-panel-prefs.js +114 -0
- package/dist/lib/components/realtime/realtime-surface-panel-prefs.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +173 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +496 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +181 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +223 -0
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts +163 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js +309 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +168 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +524 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +346 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +851 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +86 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +210 -0
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts +48 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js +180 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +119 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +274 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts +11 -0
- package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js +98 -0
- package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts +9 -0
- package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js +35 -0
- package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts +28 -0
- package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-empty-state-default.component.js +104 -0
- package/dist/lib/components/slots/mj-chat-empty-state-default.component.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-header-default.component.d.ts +11 -0
- package/dist/lib/components/slots/mj-chat-header-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-header-default.component.js +103 -0
- package/dist/lib/components/slots/mj-chat-header-default.component.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts +15 -0
- package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js +73 -0
- package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js.map +1 -0
- package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts +9 -0
- package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts.map +1 -0
- package/dist/lib/components/slots/mj-chat-message-extra-default.component.js +34 -0
- package/dist/lib/components/slots/mj-chat-message-extra-default.component.js.map +1 -0
- package/dist/lib/components/slots/slot-interfaces.d.ts +95 -0
- package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -0
- package/dist/lib/components/slots/slot-interfaces.js +18 -0
- package/dist/lib/components/slots/slot-interfaces.js.map +1 -0
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -0
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +28 -4
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +12 -1
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +93 -5
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/directives/chat-slot.directive.d.ts +44 -0
- package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -0
- package/dist/lib/directives/chat-slot.directive.js +54 -0
- package/dist/lib/directives/chat-slot.directive.js.map +1 -0
- package/dist/lib/events/chat-events.d.ts +137 -0
- package/dist/lib/events/chat-events.d.ts.map +1 -0
- package/dist/lib/events/chat-events.js +189 -0
- package/dist/lib/events/chat-events.js.map +1 -0
- package/dist/lib/models/conversation-state.model.d.ts +2 -1
- 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/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +23 -6
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +60 -74
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +100 -313
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-bridge.service.d.ts +11 -70
- package/dist/lib/services/conversation-bridge.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-bridge.service.js +51 -85
- package/dist/lib/services/conversation-bridge.service.js.map +1 -1
- package/dist/lib/services/conversation-naming.d.ts +63 -0
- package/dist/lib/services/conversation-naming.d.ts.map +1 -0
- package/dist/lib/services/conversation-naming.js +58 -0
- package/dist/lib/services/conversation-naming.js.map +1 -0
- package/dist/lib/services/conversation-streaming.service.d.ts +24 -154
- package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-streaming.service.js +39 -361
- package/dist/lib/services/conversation-streaming.service.js.map +1 -1
- package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts +10 -0
- package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts.map +1 -0
- package/dist/lib/services/conversations-runtime-bootstrap.service.js +104 -0
- package/dist/lib/services/conversations-runtime-bootstrap.service.js.map +1 -0
- package/dist/lib/services/delegation-result-parser.d.ts +45 -0
- package/dist/lib/services/delegation-result-parser.d.ts.map +1 -0
- package/dist/lib/services/delegation-result-parser.js +48 -0
- package/dist/lib/services/delegation-result-parser.js.map +1 -0
- package/dist/lib/services/mention-autocomplete.service.d.ts +19 -4
- package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
- package/dist/lib/services/mention-autocomplete.service.js +65 -4
- package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
- package/dist/lib/services/mention-parser.service.d.ts +8 -53
- package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
- package/dist/lib/services/mention-parser.service.js +32 -243
- package/dist/lib/services/mention-parser.service.js.map +1 -1
- package/dist/lib/services/narration-template.d.ts +42 -0
- package/dist/lib/services/narration-template.d.ts.map +1 -0
- package/dist/lib/services/narration-template.js +73 -0
- package/dist/lib/services/narration-template.js.map +1 -0
- package/dist/lib/services/realtime-pairing.d.ts +120 -0
- package/dist/lib/services/realtime-pairing.d.ts.map +1 -0
- package/dist/lib/services/realtime-pairing.js +150 -0
- package/dist/lib/services/realtime-pairing.js.map +1 -0
- package/dist/lib/services/realtime-session-review.service.d.ts +233 -0
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -0
- package/dist/lib/services/realtime-session-review.service.js +417 -0
- package/dist/lib/services/realtime-session-review.service.js.map +1 -0
- package/dist/lib/services/realtime-session.service.d.ts +739 -0
- package/dist/lib/services/realtime-session.service.d.ts.map +1 -0
- package/dist/lib/services/realtime-session.service.js +1647 -0
- package/dist/lib/services/realtime-session.service.js.map +1 -0
- package/dist/lib/services/realtime-sessions-adapter.d.ts +54 -0
- package/dist/lib/services/realtime-sessions-adapter.d.ts.map +1 -0
- package/dist/lib/services/realtime-sessions-adapter.js +154 -0
- package/dist/lib/services/realtime-sessions-adapter.js.map +1 -0
- package/dist/lib/services/user-authorization.d.ts +67 -0
- package/dist/lib/services/user-authorization.d.ts.map +1 -0
- package/dist/lib/services/user-authorization.js +66 -0
- package/dist/lib/services/user-authorization.js.map +1 -0
- package/dist/lib/utils/realtime-session-timeline.d.ts +84 -0
- package/dist/lib/utils/realtime-session-timeline.d.ts.map +1 -0
- package/dist/lib/utils/realtime-session-timeline.js +94 -0
- package/dist/lib/utils/realtime-session-timeline.js.map +1 -0
- package/dist/public-api.d.ts +41 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +50 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +27 -24
- package/dist/__tests__/conversation-bridge.service.test.d.ts +0 -2
- package/dist/__tests__/conversation-bridge.service.test.d.ts.map +0 -1
- package/dist/__tests__/conversation-bridge.service.test.js +0 -98
- package/dist/__tests__/conversation-bridge.service.test.js.map +0 -1
- package/dist/__tests__/mention-parser.test.d.ts +0 -2
- package/dist/__tests__/mention-parser.test.d.ts.map +0 -1
- package/dist/__tests__/mention-parser.test.js +0 -154
- package/dist/__tests__/mention-parser.test.js.map +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the realtime overlay's PROGRESSIVE DISCLOSURE engine — the
|
|
3
|
+
* framework-free levels/milestones model behind the pure-audio-first call UX
|
|
4
|
+
* (persisted per-user via UserInfoEngine under `mj.realtimeVoice.uxMilestones.v1`).
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { ClampUxLevel, DefaultUxMilestones, EffectiveBaseLevel, ParseUxMilestones, RatchetedMilestones, RealtimeDisclosureModel, SerializeUxMilestones, REALTIME_UX_MAX_LEVEL, REALTIME_UX_PREF_KEY, REALTIME_UX_PRO_CALLS } from '../lib/components/realtime/realtime-disclosure';
|
|
8
|
+
describe('pref key', () => {
|
|
9
|
+
it('uses the versioned mj.* naming convention', () => {
|
|
10
|
+
expect(REALTIME_UX_PREF_KEY).toBe('mj.realtimeVoice.uxMilestones.v1');
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('ClampUxLevel', () => {
|
|
14
|
+
it('passes valid levels through and clamps the band edges', () => {
|
|
15
|
+
expect(ClampUxLevel(0)).toBe(0);
|
|
16
|
+
expect(ClampUxLevel(3)).toBe(3);
|
|
17
|
+
expect(ClampUxLevel(-2)).toBe(0);
|
|
18
|
+
expect(ClampUxLevel(9)).toBe(REALTIME_UX_MAX_LEVEL);
|
|
19
|
+
});
|
|
20
|
+
it('rounds fractional and zeroes non-finite levels', () => {
|
|
21
|
+
expect(ClampUxLevel(2.6)).toBe(3);
|
|
22
|
+
expect(ClampUxLevel(NaN)).toBe(0);
|
|
23
|
+
expect(ClampUxLevel(Infinity)).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('ParseUxMilestones / SerializeUxMilestones', () => {
|
|
27
|
+
it('round-trips milestones', () => {
|
|
28
|
+
const m = { Level: 3, Calls: 7, Density: 'pro' };
|
|
29
|
+
expect(ParseUxMilestones(SerializeUxMilestones(m))).toEqual(m);
|
|
30
|
+
});
|
|
31
|
+
it.each([
|
|
32
|
+
['missing', undefined],
|
|
33
|
+
['null', null],
|
|
34
|
+
['blank', ''],
|
|
35
|
+
['malformed JSON', '{level:'],
|
|
36
|
+
['non-object', '"3"'],
|
|
37
|
+
['JSON null', 'null']
|
|
38
|
+
])('falls back to defaults (never throws) for %s', (_label, raw) => {
|
|
39
|
+
expect(ParseUxMilestones(raw)).toEqual(DefaultUxMilestones());
|
|
40
|
+
});
|
|
41
|
+
it('repairs field-wise: bad level/calls/density fall back individually', () => {
|
|
42
|
+
expect(ParseUxMilestones('{"level":"high","calls":2,"density":"standard"}'))
|
|
43
|
+
.toEqual({ Level: 0, Calls: 2, Density: 'standard' });
|
|
44
|
+
expect(ParseUxMilestones('{"level":3,"calls":-4,"density":"ultra"}'))
|
|
45
|
+
.toEqual({ Level: 3, Calls: 0, Density: 'auto' });
|
|
46
|
+
expect(ParseUxMilestones('{"level":99,"calls":1.7,"density":"simple"}'))
|
|
47
|
+
.toEqual({ Level: REALTIME_UX_MAX_LEVEL, Calls: 2, Density: 'simple' });
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('EffectiveBaseLevel', () => {
|
|
51
|
+
it('day one: a brand-new user starts at PURE AUDIO (level 0)', () => {
|
|
52
|
+
expect(EffectiveBaseLevel(DefaultUxMilestones())).toBe(0);
|
|
53
|
+
});
|
|
54
|
+
it('auto density follows the earned ratchet', () => {
|
|
55
|
+
expect(EffectiveBaseLevel({ Level: 2, Calls: 1, Density: 'auto' })).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
it(`auto density promotes to the full console after ${REALTIME_UX_PRO_CALLS} completed calls`, () => {
|
|
58
|
+
expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS, Density: 'auto' })).toBe(REALTIME_UX_MAX_LEVEL);
|
|
59
|
+
expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS - 1, Density: 'auto' })).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
it('manual density overrides win outright (the escape hatch)', () => {
|
|
62
|
+
const earned = { Level: 4, Calls: 10, Density: 'simple' };
|
|
63
|
+
expect(EffectiveBaseLevel(earned)).toBe(0);
|
|
64
|
+
expect(EffectiveBaseLevel({ ...earned, Density: 'standard' })).toBe(2);
|
|
65
|
+
expect(EffectiveBaseLevel({ Level: 0, Calls: 0, Density: 'pro' })).toBe(REALTIME_UX_MAX_LEVEL);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('RatchetedMilestones (call end)', () => {
|
|
69
|
+
it('increments calls and ratchets the level to the session peak', () => {
|
|
70
|
+
expect(RatchetedMilestones({ Level: 1, Calls: 1, Density: 'auto' }, 3))
|
|
71
|
+
.toEqual({ Level: 3, Calls: 2, Density: 'auto' });
|
|
72
|
+
});
|
|
73
|
+
it('a completed first call always earns at least level 1 (text next time)', () => {
|
|
74
|
+
expect(RatchetedMilestones(DefaultUxMilestones(), 0))
|
|
75
|
+
.toEqual({ Level: 1, Calls: 1, Density: 'auto' });
|
|
76
|
+
});
|
|
77
|
+
it('NEVER lowers the earned level (ratchet)', () => {
|
|
78
|
+
expect(RatchetedMilestones({ Level: 4, Calls: 5, Density: 'auto' }, 0).Level).toBe(4);
|
|
79
|
+
});
|
|
80
|
+
it('preserves the density override', () => {
|
|
81
|
+
expect(RatchetedMilestones({ Level: 0, Calls: 0, Density: 'simple' }, 2).Density).toBe('simple');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('RealtimeDisclosureModel', () => {
|
|
85
|
+
it('Load resets the session level to the effective base', () => {
|
|
86
|
+
const m = new RealtimeDisclosureModel();
|
|
87
|
+
m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));
|
|
88
|
+
expect(m.SessionLevel).toBe(2);
|
|
89
|
+
expect(m.ShowThread).toBe(true);
|
|
90
|
+
expect(m.ShowComposer).toBe(true);
|
|
91
|
+
expect(m.ShowPanel).toBe(true);
|
|
92
|
+
expect(m.PowerLevel).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it('day one exposes NOTHING beyond pure audio', () => {
|
|
95
|
+
const m = new RealtimeDisclosureModel();
|
|
96
|
+
m.Load(null);
|
|
97
|
+
expect(m.SessionLevel).toBe(0);
|
|
98
|
+
expect(m.ShowThread).toBe(false);
|
|
99
|
+
expect(m.ShowComposer).toBe(false);
|
|
100
|
+
expect(m.ShowPanel).toBe(false);
|
|
101
|
+
expect(m.ShowGear).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
it('Raise only raises — and reports whether anything changed', () => {
|
|
104
|
+
const m = new RealtimeDisclosureModel();
|
|
105
|
+
m.Load(null);
|
|
106
|
+
expect(m.Raise('engaged')).toBe(true);
|
|
107
|
+
expect(m.SessionLevel).toBe(2);
|
|
108
|
+
expect(m.Raise('text')).toBe(false); // lower event after a higher one: no-op
|
|
109
|
+
expect(m.SessionLevel).toBe(2);
|
|
110
|
+
expect(m.Raise('channel')).toBe(true);
|
|
111
|
+
expect(m.SessionLevel).toBe(3);
|
|
112
|
+
});
|
|
113
|
+
it('emits Changed$ on raises that change the level (and not on no-ops)', () => {
|
|
114
|
+
const m = new RealtimeDisclosureModel();
|
|
115
|
+
m.Load(null);
|
|
116
|
+
let emissions = 0;
|
|
117
|
+
m.Changed$.subscribe(() => emissions++);
|
|
118
|
+
m.Raise('text');
|
|
119
|
+
m.Raise('text');
|
|
120
|
+
expect(emissions).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
it('BeginSession resets the volatile level back to the base', () => {
|
|
123
|
+
const m = new RealtimeDisclosureModel();
|
|
124
|
+
m.Load(null);
|
|
125
|
+
m.Raise('channel');
|
|
126
|
+
m.BeginSession();
|
|
127
|
+
expect(m.SessionLevel).toBe(0);
|
|
128
|
+
});
|
|
129
|
+
it('ReturnToPureAudio steps the volatile level back to 0 WITHOUT touching the ratchet', () => {
|
|
130
|
+
const m = new RealtimeDisclosureModel();
|
|
131
|
+
m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));
|
|
132
|
+
m.Raise('power');
|
|
133
|
+
let emissions = 0;
|
|
134
|
+
m.Changed$.subscribe(() => emissions++);
|
|
135
|
+
m.ReturnToPureAudio();
|
|
136
|
+
expect(m.SessionLevel).toBe(0);
|
|
137
|
+
expect(m.ShowThread).toBe(false);
|
|
138
|
+
expect(m.Milestones.Level).toBe(2); // the cross-session ratchet is untouched
|
|
139
|
+
m.ReturnToPureAudio(); // already there — no extra emission
|
|
140
|
+
expect(emissions).toBe(1);
|
|
141
|
+
// the user can step back up afterward — the door swings both ways
|
|
142
|
+
expect(m.Raise('text')).toBe(true);
|
|
143
|
+
expect(m.SessionLevel).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
it('SetDensity applies immediately — simple retracts even a mid-call console', () => {
|
|
146
|
+
const m = new RealtimeDisclosureModel();
|
|
147
|
+
m.Load(SerializeUxMilestones({ Level: 4, Calls: 9, Density: 'auto' }));
|
|
148
|
+
expect(m.SessionLevel).toBe(4);
|
|
149
|
+
m.SetDensity('simple');
|
|
150
|
+
expect(m.SessionLevel).toBe(0);
|
|
151
|
+
expect(m.Milestones.Density).toBe('simple');
|
|
152
|
+
m.SetDensity('pro');
|
|
153
|
+
expect(m.SessionLevel).toBe(REALTIME_UX_MAX_LEVEL);
|
|
154
|
+
});
|
|
155
|
+
it('RatchetOnSessionEnd replaces milestones and returns the persistable payload', () => {
|
|
156
|
+
const m = new RealtimeDisclosureModel();
|
|
157
|
+
m.Load(null);
|
|
158
|
+
m.Raise('channel');
|
|
159
|
+
const serialized = m.RatchetOnSessionEnd();
|
|
160
|
+
expect(m.Milestones).toEqual({ Level: 3, Calls: 1, Density: 'auto' });
|
|
161
|
+
expect(ParseUxMilestones(serialized)).toEqual(m.Milestones);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
//# sourceMappingURL=realtime-disclosure.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-disclosure.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-disclosure.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,gDAAgD,CAAC;AAExD,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,2CAA2C,EAAE,GAAG,EAAE;IACzD,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,KAAc,EAAE,CAAC;QAC1D,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,SAAS,EAAE,SAAS,CAAC;QACtB,CAAC,MAAM,EAAE,IAAI,CAAC;QACd,CAAC,OAAO,EAAE,EAAE,CAAC;QACb,CAAC,gBAAgB,EAAE,SAAS,CAAC;QAC7B,CAAC,YAAY,EAAE,KAAK,CAAC;QACrB,CAAC,WAAW,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC,8CAA8C,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACjE,MAAM,CAAC,iBAAiB,CAAC,GAAgC,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAC7F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,iBAAiB,CAAC,iDAAiD,CAAC,CAAC;aACzE,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,iBAAiB,CAAC,0CAA0C,CAAC,CAAC;aAClE,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,iBAAiB,CAAC,6CAA6C,CAAC,CAAC;aACrE,OAAO,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,kBAAkB,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,qBAAqB,kBAAkB,EAAE,GAAG,EAAE;QAClG,MAAM,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QACpH,MAAM,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,qBAAqB,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,QAAiB,EAAE,CAAC;QACnE,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,kBAAkB,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACjG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;aACpE,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC;aAClD,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,wCAAwC;QAC7E,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;QACxC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChB,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChB,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnB,CAAC,CAAC,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAC3F,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;QAExC,CAAC,CAAC,iBAAiB,EAAE,CAAC;QACtB,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,yCAAyC;QAC7E,CAAC,CAAC,iBAAiB,EAAE,CAAC,CAAC,oCAAoC;QAC3D,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE1B,kEAAkE;QAClE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvB,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACrF,MAAM,CAAC,GAAG,IAAI,uBAAuB,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACnB,MAAM,UAAU,GAAG,CAAC,CAAC,mBAAmB,EAAE,CAAC;QAC3C,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,MAAM,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Unit tests for the realtime overlay's PROGRESSIVE DISCLOSURE engine — the\n * framework-free levels/milestones model behind the pure-audio-first call UX\n * (persisted per-user via UserInfoEngine under `mj.realtimeVoice.uxMilestones.v1`).\n */\nimport { describe, it, expect } from 'vitest';\nimport {\n ClampUxLevel,\n DefaultUxMilestones,\n EffectiveBaseLevel,\n ParseUxMilestones,\n RatchetedMilestones,\n RealtimeDisclosureModel,\n SerializeUxMilestones,\n REALTIME_UX_MAX_LEVEL,\n REALTIME_UX_PREF_KEY,\n REALTIME_UX_PRO_CALLS\n} from '../lib/components/realtime/realtime-disclosure';\n\ndescribe('pref key', () => {\n it('uses the versioned mj.* naming convention', () => {\n expect(REALTIME_UX_PREF_KEY).toBe('mj.realtimeVoice.uxMilestones.v1');\n });\n});\n\ndescribe('ClampUxLevel', () => {\n it('passes valid levels through and clamps the band edges', () => {\n expect(ClampUxLevel(0)).toBe(0);\n expect(ClampUxLevel(3)).toBe(3);\n expect(ClampUxLevel(-2)).toBe(0);\n expect(ClampUxLevel(9)).toBe(REALTIME_UX_MAX_LEVEL);\n });\n\n it('rounds fractional and zeroes non-finite levels', () => {\n expect(ClampUxLevel(2.6)).toBe(3);\n expect(ClampUxLevel(NaN)).toBe(0);\n expect(ClampUxLevel(Infinity)).toBe(0);\n });\n});\n\ndescribe('ParseUxMilestones / SerializeUxMilestones', () => {\n it('round-trips milestones', () => {\n const m = { Level: 3, Calls: 7, Density: 'pro' as const };\n expect(ParseUxMilestones(SerializeUxMilestones(m))).toEqual(m);\n });\n\n it.each([\n ['missing', undefined],\n ['null', null],\n ['blank', ''],\n ['malformed JSON', '{level:'],\n ['non-object', '\"3\"'],\n ['JSON null', 'null']\n ])('falls back to defaults (never throws) for %s', (_label, raw) => {\n expect(ParseUxMilestones(raw as string | null | undefined)).toEqual(DefaultUxMilestones());\n });\n\n it('repairs field-wise: bad level/calls/density fall back individually', () => {\n expect(ParseUxMilestones('{\"level\":\"high\",\"calls\":2,\"density\":\"standard\"}'))\n .toEqual({ Level: 0, Calls: 2, Density: 'standard' });\n expect(ParseUxMilestones('{\"level\":3,\"calls\":-4,\"density\":\"ultra\"}'))\n .toEqual({ Level: 3, Calls: 0, Density: 'auto' });\n expect(ParseUxMilestones('{\"level\":99,\"calls\":1.7,\"density\":\"simple\"}'))\n .toEqual({ Level: REALTIME_UX_MAX_LEVEL, Calls: 2, Density: 'simple' });\n });\n});\n\ndescribe('EffectiveBaseLevel', () => {\n it('day one: a brand-new user starts at PURE AUDIO (level 0)', () => {\n expect(EffectiveBaseLevel(DefaultUxMilestones())).toBe(0);\n });\n\n it('auto density follows the earned ratchet', () => {\n expect(EffectiveBaseLevel({ Level: 2, Calls: 1, Density: 'auto' })).toBe(2);\n });\n\n it(`auto density promotes to the full console after ${REALTIME_UX_PRO_CALLS} completed calls`, () => {\n expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS, Density: 'auto' })).toBe(REALTIME_UX_MAX_LEVEL);\n expect(EffectiveBaseLevel({ Level: 1, Calls: REALTIME_UX_PRO_CALLS - 1, Density: 'auto' })).toBe(1);\n });\n\n it('manual density overrides win outright (the escape hatch)', () => {\n const earned = { Level: 4, Calls: 10, Density: 'simple' as const };\n expect(EffectiveBaseLevel(earned)).toBe(0);\n expect(EffectiveBaseLevel({ ...earned, Density: 'standard' })).toBe(2);\n expect(EffectiveBaseLevel({ Level: 0, Calls: 0, Density: 'pro' })).toBe(REALTIME_UX_MAX_LEVEL);\n });\n});\n\ndescribe('RatchetedMilestones (call end)', () => {\n it('increments calls and ratchets the level to the session peak', () => {\n expect(RatchetedMilestones({ Level: 1, Calls: 1, Density: 'auto' }, 3))\n .toEqual({ Level: 3, Calls: 2, Density: 'auto' });\n });\n\n it('a completed first call always earns at least level 1 (text next time)', () => {\n expect(RatchetedMilestones(DefaultUxMilestones(), 0))\n .toEqual({ Level: 1, Calls: 1, Density: 'auto' });\n });\n\n it('NEVER lowers the earned level (ratchet)', () => {\n expect(RatchetedMilestones({ Level: 4, Calls: 5, Density: 'auto' }, 0).Level).toBe(4);\n });\n\n it('preserves the density override', () => {\n expect(RatchetedMilestones({ Level: 0, Calls: 0, Density: 'simple' }, 2).Density).toBe('simple');\n });\n});\n\ndescribe('RealtimeDisclosureModel', () => {\n it('Load resets the session level to the effective base', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));\n expect(m.SessionLevel).toBe(2);\n expect(m.ShowThread).toBe(true);\n expect(m.ShowComposer).toBe(true);\n expect(m.ShowPanel).toBe(true);\n expect(m.PowerLevel).toBe(false);\n });\n\n it('day one exposes NOTHING beyond pure audio', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n expect(m.SessionLevel).toBe(0);\n expect(m.ShowThread).toBe(false);\n expect(m.ShowComposer).toBe(false);\n expect(m.ShowPanel).toBe(false);\n expect(m.ShowGear).toBe(false);\n });\n\n it('Raise only raises — and reports whether anything changed', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n expect(m.Raise('engaged')).toBe(true);\n expect(m.SessionLevel).toBe(2);\n expect(m.Raise('text')).toBe(false); // lower event after a higher one: no-op\n expect(m.SessionLevel).toBe(2);\n expect(m.Raise('channel')).toBe(true);\n expect(m.SessionLevel).toBe(3);\n });\n\n it('emits Changed$ on raises that change the level (and not on no-ops)', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n let emissions = 0;\n m.Changed$.subscribe(() => emissions++);\n m.Raise('text');\n m.Raise('text');\n expect(emissions).toBe(1);\n });\n\n it('BeginSession resets the volatile level back to the base', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n m.Raise('channel');\n m.BeginSession();\n expect(m.SessionLevel).toBe(0);\n });\n\n it('ReturnToPureAudio steps the volatile level back to 0 WITHOUT touching the ratchet', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 2, Calls: 1, Density: 'auto' }));\n m.Raise('power');\n let emissions = 0;\n m.Changed$.subscribe(() => emissions++);\n\n m.ReturnToPureAudio();\n expect(m.SessionLevel).toBe(0);\n expect(m.ShowThread).toBe(false);\n expect(m.Milestones.Level).toBe(2); // the cross-session ratchet is untouched\n m.ReturnToPureAudio(); // already there — no extra emission\n expect(emissions).toBe(1);\n\n // the user can step back up afterward — the door swings both ways\n expect(m.Raise('text')).toBe(true);\n expect(m.SessionLevel).toBe(1);\n });\n\n it('SetDensity applies immediately — simple retracts even a mid-call console', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(SerializeUxMilestones({ Level: 4, Calls: 9, Density: 'auto' }));\n expect(m.SessionLevel).toBe(4);\n m.SetDensity('simple');\n expect(m.SessionLevel).toBe(0);\n expect(m.Milestones.Density).toBe('simple');\n m.SetDensity('pro');\n expect(m.SessionLevel).toBe(REALTIME_UX_MAX_LEVEL);\n });\n\n it('RatchetOnSessionEnd replaces milestones and returns the persistable payload', () => {\n const m = new RealtimeDisclosureModel();\n m.Load(null);\n m.Raise('channel');\n const serialized = m.RatchetOnSessionEnd();\n expect(m.Milestones).toEqual({ Level: 3, Calls: 1, Density: 'auto' });\n expect(ParseUxMilestones(serialized)).toEqual(m.Milestones);\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-pairing.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-pairing.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
3
|
+
import { BuildRealtimeConfigOverridesJson, ConstrainTargetsToPairings, DefaultPairedTargetId, FilterRealtimeCoAgents, LoadCoAgentPairings, PairingsAllowTarget, SortPairings, } from '../lib/services/realtime-pairing';
|
|
4
|
+
/**
|
|
5
|
+
* Pure co-agent pairing helpers — the client side of the `MJ: AI Agent Co Agents`
|
|
6
|
+
* semantics: a co-agent with ZERO rows is universal (today's flow untouched); with rows,
|
|
7
|
+
* its allowed targets are exactly those rows (Sequence order, IsDefault preselected).
|
|
8
|
+
* Also covers the pinned `configOverridesJson` envelope builder and the Realtime-type
|
|
9
|
+
* co-agent filter.
|
|
10
|
+
*/
|
|
11
|
+
const CO_AGENT = 'C0000000-0000-0000-0000-000000000001';
|
|
12
|
+
function pairing(overrides) {
|
|
13
|
+
return {
|
|
14
|
+
ID: `P-${overrides.TargetAgentID}`,
|
|
15
|
+
CoAgentID: CO_AGENT,
|
|
16
|
+
TargetAgent: null,
|
|
17
|
+
IsDefault: false,
|
|
18
|
+
Sequence: 0,
|
|
19
|
+
Type: 'CoAgent',
|
|
20
|
+
Status: 'Active',
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const TARGET_A = 'A0000000-0000-0000-0000-00000000000A';
|
|
25
|
+
const TARGET_B = 'B0000000-0000-0000-0000-00000000000B';
|
|
26
|
+
const TARGET_C = 'D0000000-0000-0000-0000-00000000000D';
|
|
27
|
+
const agents = [
|
|
28
|
+
{ ID: TARGET_A, Name: 'Alpha' },
|
|
29
|
+
{ ID: TARGET_B, Name: 'Beta' },
|
|
30
|
+
{ ID: TARGET_C, Name: 'Gamma' },
|
|
31
|
+
];
|
|
32
|
+
describe('ConstrainTargetsToPairings', () => {
|
|
33
|
+
it('returns the candidates unchanged when there are zero pairing rows (universal co-agent)', () => {
|
|
34
|
+
const result = ConstrainTargetsToPairings(agents, []);
|
|
35
|
+
expect(result.map(a => a.ID)).toEqual([TARGET_A, TARGET_B, TARGET_C]);
|
|
36
|
+
});
|
|
37
|
+
it('constrains to exactly the paired targets, in pairing Sequence order', () => {
|
|
38
|
+
const pairings = [
|
|
39
|
+
pairing({ TargetAgentID: TARGET_C, Sequence: 2 }),
|
|
40
|
+
pairing({ TargetAgentID: TARGET_B, Sequence: 1 }),
|
|
41
|
+
];
|
|
42
|
+
const result = ConstrainTargetsToPairings(agents, pairings);
|
|
43
|
+
expect(result.map(a => a.Name)).toEqual(['Beta', 'Gamma']);
|
|
44
|
+
});
|
|
45
|
+
it('silently drops paired targets the user cannot see/run (absent from the candidate list)', () => {
|
|
46
|
+
const pairings = [
|
|
47
|
+
pairing({ TargetAgentID: 'F0000000-0000-0000-0000-0000000000FF', Sequence: 1 }),
|
|
48
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: 2 }),
|
|
49
|
+
];
|
|
50
|
+
const result = ConstrainTargetsToPairings(agents, pairings);
|
|
51
|
+
expect(result.map(a => a.ID)).toEqual([TARGET_A]);
|
|
52
|
+
});
|
|
53
|
+
it('matches target ids across UUID casing and de-duplicates repeated targets', () => {
|
|
54
|
+
const pairings = [
|
|
55
|
+
pairing({ TargetAgentID: TARGET_B.toLowerCase(), Sequence: 1 }),
|
|
56
|
+
pairing({ TargetAgentID: TARGET_B.toUpperCase(), Sequence: 2 }),
|
|
57
|
+
];
|
|
58
|
+
const result = ConstrainTargetsToPairings(agents, pairings);
|
|
59
|
+
expect(result.map(a => a.ID)).toEqual([TARGET_B]);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('SortPairings', () => {
|
|
63
|
+
it('orders by Sequence ascending with null sequences last, ties by target name', () => {
|
|
64
|
+
const rows = [
|
|
65
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: null, TargetAgent: 'Alpha' }),
|
|
66
|
+
pairing({ TargetAgentID: TARGET_C, Sequence: 1, TargetAgent: 'Gamma' }),
|
|
67
|
+
pairing({ TargetAgentID: TARGET_B, Sequence: 1, TargetAgent: 'Beta' }),
|
|
68
|
+
];
|
|
69
|
+
expect(SortPairings(rows).map(r => r.TargetAgentID)).toEqual([TARGET_B, TARGET_C, TARGET_A]);
|
|
70
|
+
});
|
|
71
|
+
it('does not mutate the input array', () => {
|
|
72
|
+
const rows = [pairing({ TargetAgentID: TARGET_B, Sequence: 2 }), pairing({ TargetAgentID: TARGET_A, Sequence: 1 })];
|
|
73
|
+
SortPairings(rows);
|
|
74
|
+
expect(rows[0].TargetAgentID).toBe(TARGET_B);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('DefaultPairedTargetId', () => {
|
|
78
|
+
it('returns the IsDefault row target', () => {
|
|
79
|
+
const pairings = [
|
|
80
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: 1 }),
|
|
81
|
+
pairing({ TargetAgentID: TARGET_B, Sequence: 2, IsDefault: true }),
|
|
82
|
+
];
|
|
83
|
+
expect(DefaultPairedTargetId(pairings)).toBe(TARGET_B);
|
|
84
|
+
});
|
|
85
|
+
it('returns the FIRST default in Sequence order when (defensively) more than one exists', () => {
|
|
86
|
+
const pairings = [
|
|
87
|
+
pairing({ TargetAgentID: TARGET_B, Sequence: 2, IsDefault: true }),
|
|
88
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: 1, IsDefault: true }),
|
|
89
|
+
];
|
|
90
|
+
expect(DefaultPairedTargetId(pairings)).toBe(TARGET_A);
|
|
91
|
+
});
|
|
92
|
+
it('returns null when no row is marked default or there are no rows', () => {
|
|
93
|
+
expect(DefaultPairedTargetId([pairing({ TargetAgentID: TARGET_A })])).toBeNull();
|
|
94
|
+
expect(DefaultPairedTargetId([])).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('PairingsAllowTarget', () => {
|
|
98
|
+
it('always allows when there are zero rows (universal co-agent)', () => {
|
|
99
|
+
expect(PairingsAllowTarget([], TARGET_A)).toBe(true);
|
|
100
|
+
expect(PairingsAllowTarget([], null)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
it('allows only the paired targets when rows exist (UUID-casing safe)', () => {
|
|
103
|
+
const pairings = [pairing({ TargetAgentID: TARGET_A })];
|
|
104
|
+
expect(PairingsAllowTarget(pairings, TARGET_A.toLowerCase())).toBe(true);
|
|
105
|
+
expect(PairingsAllowTarget(pairings, TARGET_B)).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
it('never allows a missing target against a constrained co-agent', () => {
|
|
108
|
+
expect(PairingsAllowTarget([pairing({ TargetAgentID: TARGET_A })], null)).toBe(false);
|
|
109
|
+
expect(PairingsAllowTarget([pairing({ TargetAgentID: TARGET_A })], '')).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('FilterRealtimeCoAgents', () => {
|
|
113
|
+
it('keeps only agents whose Type is Realtime (trim + case-insensitive)', () => {
|
|
114
|
+
const candidates = [
|
|
115
|
+
{ ID: '1', Type: 'Realtime' },
|
|
116
|
+
{ ID: '2', Type: ' realtime ' },
|
|
117
|
+
{ ID: '3', Type: 'Loop' },
|
|
118
|
+
{ ID: '4', Type: null },
|
|
119
|
+
];
|
|
120
|
+
expect(FilterRealtimeCoAgents(candidates).map(a => a.ID)).toEqual(['1', '2']);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe('LoadCoAgentPairings', () => {
|
|
124
|
+
afterEach(() => {
|
|
125
|
+
vi.restoreAllMocks();
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Stubs the provider-scoped AIEngineBase the loader reads (suite convention: spy on the
|
|
129
|
+
* static `GetProviderInstance`, the way other suites spy `AIEngineBase.Instance`). The
|
|
130
|
+
* engine cache — not a RunView — is the only data surface the loader touches.
|
|
131
|
+
*/
|
|
132
|
+
function engineReturning(rows) {
|
|
133
|
+
const config = vi.fn(async () => {
|
|
134
|
+
if (rows instanceof Error) {
|
|
135
|
+
throw rows;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const fakeEngine = {
|
|
139
|
+
Config: config,
|
|
140
|
+
get AgentCoAgents() {
|
|
141
|
+
if (rows instanceof Error) {
|
|
142
|
+
throw rows;
|
|
143
|
+
}
|
|
144
|
+
return rows;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const getProviderInstance = vi
|
|
148
|
+
.spyOn(AIEngineBase, 'GetProviderInstance')
|
|
149
|
+
.mockReturnValue(fakeEngine);
|
|
150
|
+
return { provider: {}, config, getProviderInstance };
|
|
151
|
+
}
|
|
152
|
+
it('reads the engine cache filtered to the co-agent (UUID-casing safe) and returns Sequence order', async () => {
|
|
153
|
+
const { provider, config, getProviderInstance } = engineReturning([
|
|
154
|
+
pairing({ TargetAgentID: TARGET_B, Sequence: 2, CoAgentID: CO_AGENT.toLowerCase() }),
|
|
155
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: 1 }),
|
|
156
|
+
// Another co-agent's row — must be filtered out client-side.
|
|
157
|
+
pairing({ TargetAgentID: TARGET_C, Sequence: 0, CoAgentID: 'E0000000-0000-0000-0000-0000000000EE' }),
|
|
158
|
+
// Disabled row — ignored by resolution/pickers.
|
|
159
|
+
pairing({ TargetAgentID: TARGET_C, Sequence: 0, Status: 'Disabled' }),
|
|
160
|
+
// Reserved relationship type — ignored until its feature ships.
|
|
161
|
+
pairing({ TargetAgentID: TARGET_C, Sequence: 0, Type: 'Peer' }),
|
|
162
|
+
]);
|
|
163
|
+
const rows = await LoadCoAgentPairings(provider, CO_AGENT);
|
|
164
|
+
expect(rows.map(r => r.TargetAgentID)).toEqual([TARGET_A, TARGET_B]);
|
|
165
|
+
// Provider-scoped engine instance, lazily configured against the SAME provider.
|
|
166
|
+
expect(getProviderInstance).toHaveBeenCalledWith(provider, AIEngineBase);
|
|
167
|
+
expect(config).toHaveBeenCalledWith(false, undefined, provider);
|
|
168
|
+
});
|
|
169
|
+
it('returns plain projected rows carrying the pairing fields the UI consumes', async () => {
|
|
170
|
+
const { provider } = engineReturning([
|
|
171
|
+
pairing({ TargetAgentID: TARGET_A, Sequence: 3, IsDefault: true, TargetAgent: 'Alpha' }),
|
|
172
|
+
]);
|
|
173
|
+
const rows = await LoadCoAgentPairings(provider, CO_AGENT);
|
|
174
|
+
expect(rows).toEqual([
|
|
175
|
+
{
|
|
176
|
+
ID: `P-${TARGET_A}`,
|
|
177
|
+
CoAgentID: CO_AGENT,
|
|
178
|
+
TargetAgentID: TARGET_A,
|
|
179
|
+
TargetAgent: 'Alpha',
|
|
180
|
+
IsDefault: true,
|
|
181
|
+
Sequence: 3,
|
|
182
|
+
},
|
|
183
|
+
]);
|
|
184
|
+
});
|
|
185
|
+
it('degrades to an empty list (no constraint) when the engine load throws', async () => {
|
|
186
|
+
const { provider } = engineReturning(new Error('offline'));
|
|
187
|
+
await expect(LoadCoAgentPairings(provider, CO_AGENT)).resolves.toEqual([]);
|
|
188
|
+
});
|
|
189
|
+
it('returns an empty list without touching the engine when the co-agent id is blank', async () => {
|
|
190
|
+
const { provider, getProviderInstance } = engineReturning([]);
|
|
191
|
+
await expect(LoadCoAgentPairings(provider, ' ')).resolves.toEqual([]);
|
|
192
|
+
expect(getProviderInstance).not.toHaveBeenCalled();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
describe('BuildRealtimeConfigOverridesJson', () => {
|
|
196
|
+
it('returns null when nothing was overridden', () => {
|
|
197
|
+
expect(BuildRealtimeConfigOverridesJson(null)).toBeNull();
|
|
198
|
+
expect(BuildRealtimeConfigOverridesJson(undefined)).toBeNull();
|
|
199
|
+
expect(BuildRealtimeConfigOverridesJson(' ')).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
it('emits the pinned override envelope for an explicit model pick', () => {
|
|
202
|
+
const json = BuildRealtimeConfigOverridesJson('model-77');
|
|
203
|
+
expect(json).toBe('{"realtime":{"modelPreference":"model-77"}}');
|
|
204
|
+
expect(JSON.parse(json)).toEqual({ realtime: { modelPreference: 'model-77' } });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
//# sourceMappingURL=realtime-pairing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-pairing.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-pairing.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EACL,gCAAgC,EAChC,0BAA0B,EAC1B,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,GAEb,MAAM,kCAAkC,CAAC;AAE1C;;;;;;GAMG;AAEH,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AASxD,SAAS,OAAO,CAAC,SAAoE;IACnF,OAAO;QACL,EAAE,EAAE,KAAK,SAAS,CAAC,aAAa,EAAE;QAClC,SAAS,EAAE,QAAQ;QACnB,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,KAAK;QAChB,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,QAAQ;QAChB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AACxD,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AACxD,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AAGxD,MAAM,MAAM,GAAgB;IAC1B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;IAC/B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;IAC9B,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE;CAChC,CAAC;AAEF,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,MAAM,MAAM,GAAG,0BAA0B,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,QAAQ,GAAG;YACf,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;SAClD,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,MAAM,QAAQ,GAAG;YACf,OAAO,CAAC,EAAE,aAAa,EAAE,sCAAsC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC/E,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;SAClD,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,QAAQ,GAAG;YACf,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;SAChE,CAAC;QACF,MAAM,MAAM,GAAG,0BAA0B,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,4EAA4E,EAAE,GAAG,EAAE;QACpF,MAAM,IAAI,GAAG;YACX,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;YAC1E,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;YACvE,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;SACvE,CAAC;QACF,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACpH,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,QAAQ,GAAG;YACf,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SACnE,CAAC;QACF,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qFAAqF,EAAE,GAAG,EAAE;QAC7F,MAAM,QAAQ,GAAG;YACf,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;YAClE,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;SACnE,CAAC;QACF,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,qBAAqB,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACjF,MAAM,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzE,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtF,MAAM,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,UAAU,GAAG;YACjB,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE;YAC7B,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE;YAC/B,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE;YACzB,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE;SACxB,CAAC;QACF,MAAM,CAAC,sBAAsB,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH;;;;OAIG;IACH,SAAS,eAAe,CAAC,IAAoC;QAK3D,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE;YAC9B,IAAI,IAAI,YAAY,KAAK,EAAE,CAAC;gBAC1B,MAAM,IAAI,CAAC;YACb,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,UAAU,GAAG;YACjB,MAAM,EAAE,MAAM;YACd,IAAI,aAAa;gBACf,IAAI,IAAI,YAAY,KAAK,EAAE,CAAC;oBAC1B,MAAM,IAAI,CAAC;gBACb,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;SACF,CAAC;QACF,MAAM,mBAAmB,GAAG,EAAE;aAC3B,KAAK,CAAC,YAAY,EAAE,qBAAqB,CAAC;aAC1C,eAAe,CAAC,UAAqC,CAAC,CAAC;QAC1D,OAAO,EAAE,QAAQ,EAAE,EAAuB,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC5E,CAAC;IAED,EAAE,CAAC,+FAA+F,EAAE,KAAK,IAAI,EAAE;QAC7G,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,mBAAmB,EAAE,GAAG,eAAe,CAAC;YAChE,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;YACpF,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;YACjD,6DAA6D;YAC7D,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,sCAAsC,EAAE,CAAC;YACpG,gDAAgD;YAChD,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;YACrE,gEAAgE;YAChE,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SAChE,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QACrE,gFAAgF;QAChF,MAAM,CAAC,mBAAmB,CAAC,CAAC,oBAAoB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACzE,MAAM,CAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC;YACnC,OAAO,CAAC,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;SACzF,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;YACnB;gBACE,EAAE,EAAE,KAAK,QAAQ,EAAE;gBACnB,SAAS,EAAE,QAAQ;gBACnB,aAAa,EAAE,QAAQ;gBACvB,WAAW,EAAE,OAAO;gBACpB,SAAS,EAAE,IAAI;gBACf,QAAQ,EAAE,CAAC;aACZ;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uEAAuE,EAAE,KAAK,IAAI,EAAE;QACrF,MAAM,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QAC3D,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iFAAiF,EAAE,KAAK,IAAI,EAAE;QAC/F,MAAM,EAAE,QAAQ,EAAE,mBAAmB,EAAE,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QAC9D,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1D,MAAM,CAAC,gCAAgC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/D,MAAM,CAAC,gCAAgC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,IAAI,GAAG,gCAAgC,CAAC,UAAU,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAc,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["import { describe, it, expect, vi, afterEach } from 'vitest';\nimport { IMetadataProvider } from '@memberjunction/core';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport {\n BuildRealtimeConfigOverridesJson,\n ConstrainTargetsToPairings,\n DefaultPairedTargetId,\n FilterRealtimeCoAgents,\n LoadCoAgentPairings,\n PairingsAllowTarget,\n SortPairings,\n RealtimePairedAgentRow,\n} from '../lib/services/realtime-pairing';\n\n/**\n * Pure co-agent pairing helpers — the client side of the `MJ: AI Agent Co Agents`\n * semantics: a co-agent with ZERO rows is universal (today's flow untouched); with rows,\n * its allowed targets are exactly those rows (Sequence order, IsDefault preselected).\n * Also covers the pinned `configOverridesJson` envelope builder and the Realtime-type\n * co-agent filter.\n */\n\nconst CO_AGENT = 'C0000000-0000-0000-0000-000000000001';\n\n/** The engine-cache row shape LoadCoAgentPairings filters on (the new AIAgentCoAgent contract). */\ninterface FakeCoAgentEngineRow extends RealtimePairedAgentRow {\n Type: 'CoAgent' | 'Peer';\n Status: 'Active' | 'Disabled';\n TargetAgentTypeID?: string | null;\n}\n\nfunction pairing(overrides: Partial<FakeCoAgentEngineRow> & { TargetAgentID: string }): FakeCoAgentEngineRow {\n return {\n ID: `P-${overrides.TargetAgentID}`,\n CoAgentID: CO_AGENT,\n TargetAgent: null,\n IsDefault: false,\n Sequence: 0,\n Type: 'CoAgent',\n Status: 'Active',\n ...overrides,\n };\n}\n\nconst TARGET_A = 'A0000000-0000-0000-0000-00000000000A';\nconst TARGET_B = 'B0000000-0000-0000-0000-00000000000B';\nconst TARGET_C = 'D0000000-0000-0000-0000-00000000000D';\n\ninterface FakeAgent { ID: string; Name: string }\nconst agents: FakeAgent[] = [\n { ID: TARGET_A, Name: 'Alpha' },\n { ID: TARGET_B, Name: 'Beta' },\n { ID: TARGET_C, Name: 'Gamma' },\n];\n\ndescribe('ConstrainTargetsToPairings', () => {\n it('returns the candidates unchanged when there are zero pairing rows (universal co-agent)', () => {\n const result = ConstrainTargetsToPairings(agents, []);\n expect(result.map(a => a.ID)).toEqual([TARGET_A, TARGET_B, TARGET_C]);\n });\n\n it('constrains to exactly the paired targets, in pairing Sequence order', () => {\n const pairings = [\n pairing({ TargetAgentID: TARGET_C, Sequence: 2 }),\n pairing({ TargetAgentID: TARGET_B, Sequence: 1 }),\n ];\n const result = ConstrainTargetsToPairings(agents, pairings);\n expect(result.map(a => a.Name)).toEqual(['Beta', 'Gamma']);\n });\n\n it('silently drops paired targets the user cannot see/run (absent from the candidate list)', () => {\n const pairings = [\n pairing({ TargetAgentID: 'F0000000-0000-0000-0000-0000000000FF', Sequence: 1 }),\n pairing({ TargetAgentID: TARGET_A, Sequence: 2 }),\n ];\n const result = ConstrainTargetsToPairings(agents, pairings);\n expect(result.map(a => a.ID)).toEqual([TARGET_A]);\n });\n\n it('matches target ids across UUID casing and de-duplicates repeated targets', () => {\n const pairings = [\n pairing({ TargetAgentID: TARGET_B.toLowerCase(), Sequence: 1 }),\n pairing({ TargetAgentID: TARGET_B.toUpperCase(), Sequence: 2 }),\n ];\n const result = ConstrainTargetsToPairings(agents, pairings);\n expect(result.map(a => a.ID)).toEqual([TARGET_B]);\n });\n});\n\ndescribe('SortPairings', () => {\n it('orders by Sequence ascending with null sequences last, ties by target name', () => {\n const rows = [\n pairing({ TargetAgentID: TARGET_A, Sequence: null, TargetAgent: 'Alpha' }),\n pairing({ TargetAgentID: TARGET_C, Sequence: 1, TargetAgent: 'Gamma' }),\n pairing({ TargetAgentID: TARGET_B, Sequence: 1, TargetAgent: 'Beta' }),\n ];\n expect(SortPairings(rows).map(r => r.TargetAgentID)).toEqual([TARGET_B, TARGET_C, TARGET_A]);\n });\n\n it('does not mutate the input array', () => {\n const rows = [pairing({ TargetAgentID: TARGET_B, Sequence: 2 }), pairing({ TargetAgentID: TARGET_A, Sequence: 1 })];\n SortPairings(rows);\n expect(rows[0].TargetAgentID).toBe(TARGET_B);\n });\n});\n\ndescribe('DefaultPairedTargetId', () => {\n it('returns the IsDefault row target', () => {\n const pairings = [\n pairing({ TargetAgentID: TARGET_A, Sequence: 1 }),\n pairing({ TargetAgentID: TARGET_B, Sequence: 2, IsDefault: true }),\n ];\n expect(DefaultPairedTargetId(pairings)).toBe(TARGET_B);\n });\n\n it('returns the FIRST default in Sequence order when (defensively) more than one exists', () => {\n const pairings = [\n pairing({ TargetAgentID: TARGET_B, Sequence: 2, IsDefault: true }),\n pairing({ TargetAgentID: TARGET_A, Sequence: 1, IsDefault: true }),\n ];\n expect(DefaultPairedTargetId(pairings)).toBe(TARGET_A);\n });\n\n it('returns null when no row is marked default or there are no rows', () => {\n expect(DefaultPairedTargetId([pairing({ TargetAgentID: TARGET_A })])).toBeNull();\n expect(DefaultPairedTargetId([])).toBeNull();\n });\n});\n\ndescribe('PairingsAllowTarget', () => {\n it('always allows when there are zero rows (universal co-agent)', () => {\n expect(PairingsAllowTarget([], TARGET_A)).toBe(true);\n expect(PairingsAllowTarget([], null)).toBe(true);\n });\n\n it('allows only the paired targets when rows exist (UUID-casing safe)', () => {\n const pairings = [pairing({ TargetAgentID: TARGET_A })];\n expect(PairingsAllowTarget(pairings, TARGET_A.toLowerCase())).toBe(true);\n expect(PairingsAllowTarget(pairings, TARGET_B)).toBe(false);\n });\n\n it('never allows a missing target against a constrained co-agent', () => {\n expect(PairingsAllowTarget([pairing({ TargetAgentID: TARGET_A })], null)).toBe(false);\n expect(PairingsAllowTarget([pairing({ TargetAgentID: TARGET_A })], '')).toBe(false);\n });\n});\n\ndescribe('FilterRealtimeCoAgents', () => {\n it('keeps only agents whose Type is Realtime (trim + case-insensitive)', () => {\n const candidates = [\n { ID: '1', Type: 'Realtime' },\n { ID: '2', Type: ' realtime ' },\n { ID: '3', Type: 'Loop' },\n { ID: '4', Type: null },\n ];\n expect(FilterRealtimeCoAgents(candidates).map(a => a.ID)).toEqual(['1', '2']);\n });\n});\n\ndescribe('LoadCoAgentPairings', () => {\n afterEach(() => {\n vi.restoreAllMocks();\n });\n\n /**\n * Stubs the provider-scoped AIEngineBase the loader reads (suite convention: spy on the\n * static `GetProviderInstance`, the way other suites spy `AIEngineBase.Instance`). The\n * engine cache — not a RunView — is the only data surface the loader touches.\n */\n function engineReturning(rows: FakeCoAgentEngineRow[] | Error): {\n provider: IMetadataProvider;\n config: ReturnType<typeof vi.fn>;\n getProviderInstance: ReturnType<typeof vi.spyOn>;\n } {\n const config = vi.fn(async () => {\n if (rows instanceof Error) {\n throw rows;\n }\n });\n const fakeEngine = {\n Config: config,\n get AgentCoAgents(): FakeCoAgentEngineRow[] {\n if (rows instanceof Error) {\n throw rows;\n }\n return rows;\n },\n };\n const getProviderInstance = vi\n .spyOn(AIEngineBase, 'GetProviderInstance')\n .mockReturnValue(fakeEngine as unknown as AIEngineBase);\n return { provider: {} as IMetadataProvider, config, getProviderInstance };\n }\n\n it('reads the engine cache filtered to the co-agent (UUID-casing safe) and returns Sequence order', async () => {\n const { provider, config, getProviderInstance } = engineReturning([\n pairing({ TargetAgentID: TARGET_B, Sequence: 2, CoAgentID: CO_AGENT.toLowerCase() }),\n pairing({ TargetAgentID: TARGET_A, Sequence: 1 }),\n // Another co-agent's row — must be filtered out client-side.\n pairing({ TargetAgentID: TARGET_C, Sequence: 0, CoAgentID: 'E0000000-0000-0000-0000-0000000000EE' }),\n // Disabled row — ignored by resolution/pickers.\n pairing({ TargetAgentID: TARGET_C, Sequence: 0, Status: 'Disabled' }),\n // Reserved relationship type — ignored until its feature ships.\n pairing({ TargetAgentID: TARGET_C, Sequence: 0, Type: 'Peer' }),\n ]);\n const rows = await LoadCoAgentPairings(provider, CO_AGENT);\n expect(rows.map(r => r.TargetAgentID)).toEqual([TARGET_A, TARGET_B]);\n // Provider-scoped engine instance, lazily configured against the SAME provider.\n expect(getProviderInstance).toHaveBeenCalledWith(provider, AIEngineBase);\n expect(config).toHaveBeenCalledWith(false, undefined, provider);\n });\n\n it('returns plain projected rows carrying the pairing fields the UI consumes', async () => {\n const { provider } = engineReturning([\n pairing({ TargetAgentID: TARGET_A, Sequence: 3, IsDefault: true, TargetAgent: 'Alpha' }),\n ]);\n const rows = await LoadCoAgentPairings(provider, CO_AGENT);\n expect(rows).toEqual([\n {\n ID: `P-${TARGET_A}`,\n CoAgentID: CO_AGENT,\n TargetAgentID: TARGET_A,\n TargetAgent: 'Alpha',\n IsDefault: true,\n Sequence: 3,\n },\n ]);\n });\n\n it('degrades to an empty list (no constraint) when the engine load throws', async () => {\n const { provider } = engineReturning(new Error('offline'));\n await expect(LoadCoAgentPairings(provider, CO_AGENT)).resolves.toEqual([]);\n });\n\n it('returns an empty list without touching the engine when the co-agent id is blank', async () => {\n const { provider, getProviderInstance } = engineReturning([]);\n await expect(LoadCoAgentPairings(provider, ' ')).resolves.toEqual([]);\n expect(getProviderInstance).not.toHaveBeenCalled();\n });\n});\n\ndescribe('BuildRealtimeConfigOverridesJson', () => {\n it('returns null when nothing was overridden', () => {\n expect(BuildRealtimeConfigOverridesJson(null)).toBeNull();\n expect(BuildRealtimeConfigOverridesJson(undefined)).toBeNull();\n expect(BuildRealtimeConfigOverridesJson(' ')).toBeNull();\n });\n\n it('emits the pinned override envelope for an explicit model pick', () => {\n const json = BuildRealtimeConfigOverridesJson('model-77');\n expect(json).toBe('{\"realtime\":{\"modelPreference\":\"model-77\"}}');\n expect(JSON.parse(json as string)).toEqual({ realtime: { modelPreference: 'model-77' } });\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime-review-lifecycle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-review-lifecycle.test.ts"],"names":[],"mappings":"AAEA,OAAO,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Angular components in this package are partial-compiled — load the JIT compiler first
|
|
2
|
+
// (same convention as the other component suites in this node test environment).
|
|
3
|
+
import '@angular/compiler';
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
+
import { ConversationChatAreaComponent } from '../lib/components/conversation/conversation-chat-area.component';
|
|
6
|
+
/**
|
|
7
|
+
* SESSION-REVIEW overlay lifecycle on the chat area — every path that must CLEAR (or
|
|
8
|
+
* refuse to host) a review so the overlay can never go stale:
|
|
9
|
+
*
|
|
10
|
+
* 1. Changing the active conversation clears any hosted review (the original stuck
|
|
11
|
+
* repro: an old session opened in review mode survived conversation switches).
|
|
12
|
+
* 2. The overlay's Close handler clears.
|
|
13
|
+
* 3. The async open path discards stale loads: a live call (before or during the
|
|
14
|
+
* load) wins; a conversation switch mid-load discards the review UNLESS the
|
|
15
|
+
* review belongs to the newly active conversation (the deep-link race).
|
|
16
|
+
*
|
|
17
|
+
* Class-level tests without TestBed: the component is created via
|
|
18
|
+
* `Object.create(prototype)` (skipping ctor/inject) and the few collaborators the
|
|
19
|
+
* review paths touch are stubbed directly.
|
|
20
|
+
*/
|
|
21
|
+
function review(overrides = {}) {
|
|
22
|
+
return {
|
|
23
|
+
SessionID: 'SESSION-1',
|
|
24
|
+
AgentID: 'AGENT-1',
|
|
25
|
+
AgentName: 'Voice Co-Agent',
|
|
26
|
+
TargetAgentID: 'AGENT-1',
|
|
27
|
+
ConversationID: 'CONV-A',
|
|
28
|
+
Status: 'Closed',
|
|
29
|
+
CloseReason: 'Explicit',
|
|
30
|
+
StartedAt: new Date(2026, 5, 1, 10, 0, 0),
|
|
31
|
+
LastActiveAt: null,
|
|
32
|
+
ClosedAt: new Date(2026, 5, 1, 10, 10, 0),
|
|
33
|
+
Turns: [],
|
|
34
|
+
DelegatedRuns: [],
|
|
35
|
+
ChannelStates: [],
|
|
36
|
+
Legs: [],
|
|
37
|
+
Artifacts: [],
|
|
38
|
+
...overrides
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Bare component over the real prototype: real lifecycle methods, stubbed collaborators. */
|
|
42
|
+
function createHarness() {
|
|
43
|
+
const component = Object.create(ConversationChatAreaComponent.prototype);
|
|
44
|
+
const stubs = {
|
|
45
|
+
voiceSession: { IsActive: false },
|
|
46
|
+
loadSessionReview: vi.fn(),
|
|
47
|
+
detectChanges: vi.fn()
|
|
48
|
+
};
|
|
49
|
+
const open = component;
|
|
50
|
+
open['RealtimeReview'] = null;
|
|
51
|
+
open['_conversationId'] = null;
|
|
52
|
+
open['isInitialized'] = false; // keep the setter from invoking the heavy load path
|
|
53
|
+
open['Provider'] = {}; // ProviderToUse resolves to this stub, never the global Metadata.Provider
|
|
54
|
+
open['RealtimeSession'] = stubs.voiceSession;
|
|
55
|
+
open['realtimeReviewService'] = { LoadSessionReview: stubs.loadSessionReview };
|
|
56
|
+
open['cdr'] = { detectChanges: stubs.detectChanges };
|
|
57
|
+
return { component, stubs };
|
|
58
|
+
}
|
|
59
|
+
describe('Conversation change clears the session review (the stuck-overlay repro)', () => {
|
|
60
|
+
it('clears a hosted review when the conversationId input changes', () => {
|
|
61
|
+
const { component } = createHarness();
|
|
62
|
+
component.conversationId = 'CONV-A';
|
|
63
|
+
component.RealtimeReview = review();
|
|
64
|
+
component.conversationId = 'CONV-B';
|
|
65
|
+
expect(component.RealtimeReview).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it('clears a hosted review when the conversation changes to NULL (no selection)', () => {
|
|
68
|
+
const { component } = createHarness();
|
|
69
|
+
component.conversationId = 'CONV-A';
|
|
70
|
+
component.RealtimeReview = review();
|
|
71
|
+
component.conversationId = null;
|
|
72
|
+
expect(component.RealtimeReview).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
it('keeps the review when the input re-fires with the SAME conversation', () => {
|
|
75
|
+
const { component } = createHarness();
|
|
76
|
+
component.conversationId = 'CONV-A';
|
|
77
|
+
const hosted = review();
|
|
78
|
+
component.RealtimeReview = hosted;
|
|
79
|
+
component.conversationId = 'CONV-A';
|
|
80
|
+
expect(component.RealtimeReview).toBe(hosted);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('Overlay Close + explicit clear', () => {
|
|
84
|
+
it('onReviewClosed (the overlay ReviewClosed output) clears the review', () => {
|
|
85
|
+
const { component } = createHarness();
|
|
86
|
+
component.RealtimeReview = review();
|
|
87
|
+
component.onReviewClosed();
|
|
88
|
+
expect(component.RealtimeReview).toBeNull();
|
|
89
|
+
});
|
|
90
|
+
it('ClearRealtimeSessionReview is a safe no-op when nothing is hosted', () => {
|
|
91
|
+
const { component } = createHarness();
|
|
92
|
+
expect(() => component.ClearRealtimeSessionReview()).not.toThrow();
|
|
93
|
+
expect(component.RealtimeReview).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('OpenRealtimeSessionReview — hosting and staleness guards', () => {
|
|
97
|
+
it('hosts the loaded review for the current conversation and reports success', async () => {
|
|
98
|
+
const { component, stubs } = createHarness();
|
|
99
|
+
component.conversationId = 'CONV-A';
|
|
100
|
+
stubs.loadSessionReview.mockResolvedValue(review({ ConversationID: 'CONV-A' }));
|
|
101
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
102
|
+
expect(opened).toBe(true);
|
|
103
|
+
expect(component.RealtimeReview?.SessionID).toBe('SESSION-1');
|
|
104
|
+
expect(stubs.detectChanges).toHaveBeenCalled();
|
|
105
|
+
});
|
|
106
|
+
it('refuses while a LIVE call is active (never fights the live overlay) and does not even load', async () => {
|
|
107
|
+
const { component, stubs } = createHarness();
|
|
108
|
+
stubs.voiceSession.IsActive = true;
|
|
109
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
110
|
+
expect(opened).toBe(false);
|
|
111
|
+
expect(stubs.loadSessionReview).not.toHaveBeenCalled();
|
|
112
|
+
expect(component.RealtimeReview).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
it('reports failure (and hosts nothing) when the session cannot be loaded', async () => {
|
|
115
|
+
const { component, stubs } = createHarness();
|
|
116
|
+
stubs.loadSessionReview.mockResolvedValue(null);
|
|
117
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
118
|
+
expect(opened).toBe(false);
|
|
119
|
+
expect(component.RealtimeReview).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
it('discards a load that resolves AFTER a live call started (the call wins)', async () => {
|
|
122
|
+
const { component, stubs } = createHarness();
|
|
123
|
+
stubs.loadSessionReview.mockImplementation(async () => {
|
|
124
|
+
stubs.voiceSession.IsActive = true; // call starts while the review loads
|
|
125
|
+
return review();
|
|
126
|
+
});
|
|
127
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
128
|
+
expect(opened).toBe(false);
|
|
129
|
+
expect(component.RealtimeReview).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
it('discards a load that resolves after the user switched to a DIFFERENT conversation', async () => {
|
|
132
|
+
const { component, stubs } = createHarness();
|
|
133
|
+
component.conversationId = 'CONV-A';
|
|
134
|
+
stubs.loadSessionReview.mockImplementation(async () => {
|
|
135
|
+
component.conversationId = 'CONV-B'; // user moved on mid-load
|
|
136
|
+
return review({ ConversationID: 'CONV-A' });
|
|
137
|
+
});
|
|
138
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
139
|
+
expect(opened).toBe(false);
|
|
140
|
+
expect(component.RealtimeReview).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
it('still hosts when the conversation switched mid-load but the review BELONGS to the new one (deep-link race)', async () => {
|
|
143
|
+
const { component, stubs } = createHarness();
|
|
144
|
+
component.conversationId = 'CONV-A';
|
|
145
|
+
stubs.loadSessionReview.mockImplementation(async () => {
|
|
146
|
+
component.conversationId = 'CONV-B'; // deep link selected the session's own conversation
|
|
147
|
+
return review({ ConversationID: 'conv-b' }); // case-insensitive UUID compare
|
|
148
|
+
});
|
|
149
|
+
const opened = await component.OpenRealtimeSessionReview('SESSION-1');
|
|
150
|
+
expect(opened).toBe(true);
|
|
151
|
+
expect(component.RealtimeReview?.ConversationID).toBe('conv-b');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=realtime-review-lifecycle.test.js.map
|