@memberjunction/ng-conversations 5.40.1 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive Angular component library for building conversation-based interfaces in MemberJunction, including messaging, artifact management, collections, projects, tasks, agent interaction panels, and collaboration features.
|
|
4
4
|
|
|
5
|
+
> **Layering note.** The orchestration logic (agent dispatch, default-agent resolution, mention parsing, bridge state, streaming, client tools, sessions observability) lives in **`@memberjunction/conversations-runtime`** — a pure-TS, framework-agnostic package. This widget is one consumer of the runtime; React/Vue/Node hosts are also intended consumers. The widget is automatically wired to the runtime via `ConversationsRuntimeBootstrap` (registered `providedIn: 'root'`), which injects adapters for notifications, active-task tracking, and realtime sessions. See [`guides/CONVERSATIONS_UX_STACK_GUIDE.md`](../../../../guides/CONVERSATIONS_UX_STACK_GUIDE.md) for the full architecture.
|
|
6
|
+
|
|
7
|
+
## Customization without forking
|
|
8
|
+
|
|
9
|
+
The widget exposes three layers of extension:
|
|
10
|
+
|
|
11
|
+
| Surface | What it lets you do |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **6 named slots** (`mjChatSlot` directive) | Replace the `header`, `emptyState`, `agentPresence`, `messageRenderer`, `messageExtra`, or `demonstrationSurface` regions with your own templates. Three consumption modes: project an ad-hoc template, wrap the exported default for containment, or subclass the default. |
|
|
14
|
+
| **Before/After cancelable events** | `(beforeAgentTurn)`, `(beforeToolInvoked)`, `(beforeResponseFormSubmitted)` let you observe AND veto (`event.Cancel = true`) before the action runs. Plus informational `(sessionStarted)` / `(sessionChannelStateChanged)` / `(sessionEnded)` for realtime lifecycle. |
|
|
15
|
+
| **`--mj-chat-*` design tokens** | Override bubble colors, composer chrome, character accents, and voice-state hues via standard CSS custom-property overrides. Defaults adapt to dark mode through semantic `--mj-*` tokens. |
|
|
16
|
+
|
|
17
|
+
Slot interfaces + cloneable default components + the `ChatSlotDirective` are exported — see the public API.
|
|
18
|
+
|
|
5
19
|
## Overview
|
|
6
20
|
|
|
7
21
|
The `@memberjunction/ng-conversations` package is a large, feature-rich module that powers MemberJunction's conversation UI. It provides 40+ components covering the entire conversation lifecycle: message composition and rendering (with markdown, mentions, code blocks, and artifacts), conversation navigation and history, threaded discussions, artifact collections and libraries, project/task management, agent execution panels, sharing/permission modals, search, notifications, and export.
|
|
@@ -192,6 +206,31 @@ The overlay is generic — it raises events for navigation and tool execution. T
|
|
|
192
206
|
</mj-active-agent-indicator>
|
|
193
207
|
```
|
|
194
208
|
|
|
209
|
+
### Real-Time Voice (Co-Agent) UX
|
|
210
|
+
|
|
211
|
+
The package hosts the full client UX for MJ's real-time co-agent sessions — live voice calls with the conversation's agent, with interactive channel surfaces (the live Whiteboard) docked beside the call. Architecture background: [guides/REALTIME_CO_AGENTS_GUIDE.md](../../../../guides/REALTIME_CO_AGENTS_GUIDE.md).
|
|
212
|
+
|
|
213
|
+
**`RealtimeSessionService`** (`services/realtime-session.service.ts`) is the provider-agnostic orchestrator, injectable at root. It drives a **client-direct** session: it calls the `StartRealtimeClientSession` mutation to mint a server-scoped ephemeral credential, resolves the matching `BaseRealtimeClient` driver (from `@memberjunction/ai-realtime-client` — OpenAI, Gemini, ElevenLabs, or AssemblyAI) through the ClassFactory by the server-reported `Provider` key, and connects the browser **directly** to the realtime provider — audio frames never transit the MJ server; only tool calls, final transcripts, channel state, and usage telemetry relay back over GraphQL. It exposes the reactive session state hosts consume: `ConnectionState$` (`connecting | listening | speaking | thinking | error | closed`), `Captions$`, `Active$`, `DelegationProgress$` / `DelegationResult$` / `DelegationNarration$`, `ActiveChannels$`, `ChannelFocus$`, `Minimized$`, plus `SendText` (typed input into the live call), `ToggleMute`, `RegisterClientToolHandler` (prefix-routed, client-executed UI tools), and the explicit delegation cancel channel — `CancelDelegation(callId)` / `CancelInFlightDelegations()` call the `CancelRealtimeSessionTool` mutation and flip the card to "Cancelled by user" (deliberate policy: **true barge-in never aborts delegated work** — only the per-card ✕ does). The client driver's `OnUsage` token deltas are accumulated and relayed onto the co-agent's observability prompt run via `RelayRealtimeUsage`, debounced (10 s) plus a teardown flush.
|
|
214
|
+
|
|
215
|
+
**Co-agent selection, pairing constraints & authorization-gated overrides**: the composer's voice picker (`mj-voice-agent-picker`, opened by the caret next to the phone button — or automatically on a conversation with no prior agent participation) lets every user choose which **co-agent** (ACTIVE Realtime-type agent, from the same run-permission-filtered cache the @mention routing uses) fronts the call when more than one exists; the choice rides the mint's `coAgentId` and persists cross-device via `UserInfoEngine` (`mj.realtimeVoice.coAgent.v1`), so the friction-free instant start honors it too. A co-agent with rows in `MJ: AI Agent Paired Agents` may only front its paired targets — the picker constrains the target list to those rows (Sequence order, `IsDefault` preselected; pure helpers in `services/voice-pairing.ts`) — while a co-agent with zero rows stays universal (today's flow untouched; pairings constrain a chosen co-agent, they never mandate one). The **voice-model selector** (and any future session-config override, carried as the mint's `configOverridesJson`, e.g. `{"realtime":{"modelPreference":…}}`) renders only for users holding the **`Realtime: Advanced Session Controls`** MJ Authorization — evaluated client-side by the pure `UserHoldsAuthorization` helper (`services/user-authorization.ts`) from cached metadata as a *disclosure* gate; the server independently enforces the authorization on the mint, and unauthorized users silently get server defaults. (The gear popover's interface-density control is UX disclosure, not session config — it is not gated.)
|
|
216
|
+
|
|
217
|
+
**The call overlay** (`components/realtime/`): `RealtimeSessionOverlayComponent` (`mj-realtime-session-overlay`) fills the conversation panel in place while a session is active — hosted by `ConversationChatAreaComponent` behind `Active$`, started from the mic button in `MessageInputComponent`. Two columns:
|
|
218
|
+
|
|
219
|
+
- **Main column** — the unified APP-BAR (`RealtimeAgentBannerComponent`: identity + turn state + model name + the disclosure-gated action cluster — captions, the gear popover hosting the interface-density escape hatch and developer links, minimize, End call; in review the Start-live + Close actions live here too), then the pure-audio hero orb OR the unified session thread (`RealtimeSessionThreadComponent`, fed by the shared `RealtimeSessionState` merge of caption/delegation/narration streams), the channel strip, and the bottom dock (`RealtimeComposerComponent`: phone-call strip at low disclosure levels ⇄ fused mute/captions minis + typed composer at level 2+; typed turns behave identically to spoken ones).
|
|
220
|
+
- **Right panel** — `RealtimeSurfaceTabsComponent`, the tabbed surface panel (`RealtimeSurfaceTabsModel` is the framework-free, unit-tested tab state): channel tabs lead the strip, then **one tab per artifact** a delegated run produces (added UNFOCUSED with a persistent violet "unseen" glow until visited — content never steals the screen), with the Activity rail pinned LAST.
|
|
221
|
+
|
|
222
|
+
**Progressive disclosure** (`realtime-disclosure.ts`): the console *grows with the user*. A first-ever call is PURE AUDIO — a breathing hero orb, mute / Details / End, nothing to read; the caption thread, composer dock, surface panel and gear unlock by level (0–4) as the user acts (the hero's "Show the conversation", the T-to-type hotkey, the Details peek) or across sessions via the per-user milestones ratchet (UserInfoEngine, `mj.realtimeVoice.uxMilestones.v1`; the gear's Simple/Standard/Pro/Auto density control is the manual escape hatch). Content never flips the console open — **the one auto-reveal is a channel's first agent activity** (`RealtimeSessionService.ChannelActivity$`): the panel opens as a peek with that channel's tab focused + flashed, while the left column stays exactly as it was. Review mode bypasses disclosure entirely.
|
|
223
|
+
|
|
224
|
+
**Audio-reactive visuals** (`realtime-audio-visuals.ts`): when the active driver meters its audio planes (`BaseRealtimeClient.GetAudioActivity()` — all four current drivers do, both directions), the overlay samples it on a requestAnimationFrame loop *outside Angular* and writes CSS variables directly: the hero orb scales with the smoothed output envelope (speaker-cone attack/decay), the EQ bars render the true 9-bin spectrum, and the visuals recolor by speaking direction (agent = brand, user = green) with hysteresis so syllable gaps never flicker. Un-metered drivers gracefully keep the turn-state-driven animations. See the guide's §11 for the full pipeline.
|
|
225
|
+
|
|
226
|
+
**Interactive channels are plugins** — the shell is channel-agnostic. `BaseRealtimeChannelClient` (`components/realtime/channels/base-realtime-channel-client.ts`) is the contract: a client-executed tool set declared to the realtime model at session mint, a perception serializer feeding coalesced state deltas into the model as context notes, a dynamically-created Angular surface component the plugin binds itself, a persisted state of record, prior-session restore (`RestoreState`), artifact snapshots (`SaveAsArtifact`), and focus-mode layout requests. Plugins resolve at session start from the `MJ: AI Agent Channels` registry by `ClientPluginClass` key.
|
|
227
|
+
|
|
228
|
+
**The live Whiteboard is a thin consumer of [`@memberjunction/ng-whiteboard`](../whiteboard/README.md)** — the board itself (the `WhiteboardState` engine, the `Whiteboard_*` tool API, the host/board/toolbar/zoom/popover/snapshot components, exports, the sandboxed-HTML-widget input bridge, the context menu) lives in that generic package; read its README for whiteboard details. This package contributes only the integration glue (`components/realtime/whiteboard/`): `RealtimeWhiteboardChannel`, the ~200-line channel plugin that declares `WHITEBOARD_TOOL_DEFINITIONS` to the model, routes `Whiteboard_*` calls to the bound host (or the pure engine call when the pane is collapsed), pipes the coalesced `SceneDelta` stream into the model as `[whiteboard]` context notes (with do-not-narrate-minor-edits etiquette inline), forwards widget submissions (`MJWhiteboard.submit` — the tutoring loop) and agent-undo events, persists/restores the board as the channel's state of record, and snapshots it to versioned `MJ: Artifacts`; plus `WhiteboardArtifactViewerPlugin` (`mj-whiteboard-artifact-viewer`), the saved-board artifact viewer rendered through the package's read-only snapshot component.
|
|
229
|
+
|
|
230
|
+
**Session review & resume carryover**: a past session replays through the same overlay in review mode — `ConversationChatAreaComponent.OpenRealtimeSessionReview(agentSessionId)` loads a `RealtimeSessionReview` via `RealtimeSessionReviewService`. The loader is **chain-aware**: a session resumed via `lastSessionId` chains legs, and the loader walks the chain backwards (capped, cycle-guarded), rendering every leg chronologically with a divider between legs that carries the previous leg's `CloseReason` as a chip; the chain's conversation-history artifacts load as unfocused artifact tabs, and a read-only Whiteboard tab appears when a board was saved. "Start live session" resumes as a new session chained via `lastSessionId` — saved channel states restore *and* the prior legs' transcript is hydrated into the new model's prompt server-side. The conversations resource also accepts a `realtimeSessionId` query param (the deep link the custom `MJ: AI Agent Sessions` form emits) and opens review mode directly.
|
|
231
|
+
|
|
232
|
+
This package never navigates (no Router): developer links emit a `RealtimeNavigateRequest` the host converts onto its `openEntityRecord` chain, and minimizing the call shows the host's floating "on call" pill while the session stays live.
|
|
233
|
+
|
|
195
234
|
### Collaboration
|
|
196
235
|
|
|
197
236
|
```html
|
|
@@ -272,6 +311,22 @@ The overlay is generic — it raises events for navigation and tool execution. T
|
|
|
272
311
|
| `ActiveAgentIndicatorComponent` | `mj-active-agent-indicator` | Active processing indicator |
|
|
273
312
|
| `ActiveTasksPanelComponent` | `mj-active-tasks-panel` | Active tasks panel |
|
|
274
313
|
|
|
314
|
+
### Real-Time Voice Components
|
|
315
|
+
|
|
316
|
+
| Component | Selector | Description |
|
|
317
|
+
|-----------|----------|-------------|
|
|
318
|
+
| `RealtimeSessionOverlayComponent` | `mj-realtime-session-overlay` | The in-place "call mode" overlay for a live voice session (progressive-disclosure console: pure-audio hero → full two-column; audio-reactive orb/EQ) |
|
|
319
|
+
| `RealtimeAgentBannerComponent` | `mj-realtime-agent-banner` | The unified app-bar: identity + turn-state + disclosure-gated actions (captions, gear popover w/ density + dev links, minimize, End; review Start-live + Close) |
|
|
320
|
+
| `RealtimeSessionThreadComponent` | `mj-realtime-session-thread` | Unified live thread (captions, delegation cards, ephemeral narration) |
|
|
321
|
+
| `RealtimeActivityRailComponent` | `mj-realtime-activity-rail` | Session activity rail (the surface panel's pinned-last tab) |
|
|
322
|
+
| `RealtimeDelegationCardComponent` | `mj-realtime-delegation-card` | "Working on it" → result card for a delegated agent run |
|
|
323
|
+
| `RealtimeChannelStripComponent` | `mj-realtime-channel-strip` | Chip-per-channel strip |
|
|
324
|
+
| `RealtimeComposerComponent` | `mj-realtime-composer` | The bottom dock: phone-call strip (levels 0–1: mute / captions / Details peek / End) ⇄ fused minis + typed composer (level 2+) |
|
|
325
|
+
| `RealtimeSurfaceTabsComponent` | `mj-realtime-surface-tabs` | Tabbed surface panel: channel tabs first, glowing unfocused artifact tabs, Activity pinned last (backed by the framework-free, unit-tested `RealtimeSurfaceTabsModel`) |
|
|
326
|
+
| `WhiteboardArtifactViewerComponent` | `mj-whiteboard-artifact-viewer` | Saved-board artifact viewer (registered as `WhiteboardArtifactViewerPlugin`); renders via `@memberjunction/ng-whiteboard`'s snapshot component |
|
|
327
|
+
|
|
328
|
+
> The live whiteboard surface itself (`mj-realtime-whiteboard-host`, board, toolbar, zoom, "What the agent sees" popover) ships in [`@memberjunction/ng-whiteboard`](../whiteboard/README.md); the `RealtimeWhiteboardChannel` plugin here creates it dynamically in a channel tab.
|
|
329
|
+
|
|
275
330
|
### Utility Components
|
|
276
331
|
|
|
277
332
|
| Component | Selector | Description |
|
|
@@ -325,6 +380,8 @@ RxJS `BehaviorSubject` instances for all state, with derived observables using `
|
|
|
325
380
|
| `@memberjunction/ng-markdown` | Markdown rendering |
|
|
326
381
|
| `@memberjunction/ng-shared-generic` | Shared generic components |
|
|
327
382
|
| `@memberjunction/ng-testing` | Testing framework components |
|
|
383
|
+
| `@memberjunction/ng-whiteboard` | The generic collaborative whiteboard (consumed by the realtime Whiteboard channel + artifact viewer) |
|
|
384
|
+
| `@memberjunction/ai-realtime-client` | Browser-side realtime drivers (OpenAI / Gemini / ElevenLabs / AssemblyAI) used by `RealtimeSessionService` |
|
|
328
385
|
|
|
329
386
|
### Kendo UI Packages
|
|
330
387
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-optional-surface.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/channel-optional-surface.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the OPTIONAL CLIENT SURFACE contract on {@link BaseRealtimeChannelClient} (Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* A bridge-contributed channel (e.g. meeting-controls or a platform-native whiteboard) may be
|
|
5
|
+
* SERVER-ONLY — no MJ Angular surface. The contract change makes `GetSurfaceComponent` optional
|
|
6
|
+
* (defaults to `null`) and adds `HasSurface()` so the overlay can skip a surfaceless channel's tab
|
|
7
|
+
* while still wiring its tools + perception. These tests pin both the server-only default and the
|
|
8
|
+
* surface-bearing override, without rendering Angular.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { BaseRealtimeChannelClient } from '../lib/components/realtime/channels/base-realtime-channel-client';
|
|
12
|
+
/** A server-only channel: contributes tools + perception, but renders NO surface. */
|
|
13
|
+
class ServerOnlyChannel extends BaseRealtimeChannelClient {
|
|
14
|
+
get ChannelName() { return 'ServerOnly'; }
|
|
15
|
+
get ToolNamePrefix() { return 'ServerOnly_'; }
|
|
16
|
+
get TabTitle() { return 'Server Only'; }
|
|
17
|
+
get TabIcon() { return 'fa-solid fa-gear'; }
|
|
18
|
+
GetToolDefinitions() {
|
|
19
|
+
return [{ Name: 'ServerOnly_Do', Description: 'do', ParametersSchema: { type: 'object' } }];
|
|
20
|
+
}
|
|
21
|
+
ApplyAgentTool() { return JSON.stringify({ success: true }); }
|
|
22
|
+
BindSurface() { }
|
|
23
|
+
}
|
|
24
|
+
/** A surface-bearing channel for the positive case. */
|
|
25
|
+
class FakeSurface {
|
|
26
|
+
}
|
|
27
|
+
class SurfaceChannel extends BaseRealtimeChannelClient {
|
|
28
|
+
get ChannelName() { return 'WithSurface'; }
|
|
29
|
+
get ToolNamePrefix() { return 'WithSurface_'; }
|
|
30
|
+
get TabTitle() { return 'With Surface'; }
|
|
31
|
+
get TabIcon() { return 'fa-solid fa-chalkboard'; }
|
|
32
|
+
GetToolDefinitions() { return []; }
|
|
33
|
+
ApplyAgentTool() { return '{}'; }
|
|
34
|
+
BindSurface() { }
|
|
35
|
+
GetSurfaceComponent() { return FakeSurface; }
|
|
36
|
+
}
|
|
37
|
+
describe('BaseRealtimeChannelClient — optional client surface', () => {
|
|
38
|
+
it('a server-only channel returns null from GetSurfaceComponent and false from HasSurface', () => {
|
|
39
|
+
const ch = new ServerOnlyChannel();
|
|
40
|
+
expect(ch.GetSurfaceComponent()).toBeNull();
|
|
41
|
+
expect(ch.HasSurface()).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('a server-only channel still contributes tools (tools/perception are unaffected by no surface)', () => {
|
|
44
|
+
const ch = new ServerOnlyChannel();
|
|
45
|
+
expect(ch.GetToolDefinitions().map((t) => t.Name)).toEqual(['ServerOnly_Do']);
|
|
46
|
+
});
|
|
47
|
+
it('a surface-bearing channel returns its component and reports HasSurface true', () => {
|
|
48
|
+
const ch = new SurfaceChannel();
|
|
49
|
+
expect(ch.GetSurfaceComponent()).toBe(FakeSurface);
|
|
50
|
+
expect(ch.HasSurface()).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=channel-optional-surface.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-optional-surface.test.js","sourceRoot":"","sources":["../../src/__tests__/channel-optional-surface.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,kEAAkE,CAAC;AAE7G,qFAAqF;AACrF,MAAM,iBAAkB,SAAQ,yBAAyB;IACvD,IAAW,WAAW,KAAa,OAAO,YAAY,CAAC,CAAC,CAAC;IACzD,IAAW,cAAc,KAAa,OAAO,aAAa,CAAC,CAAC,CAAC;IAC7D,IAAW,QAAQ,KAAa,OAAO,aAAa,CAAC,CAAC,CAAC;IACvD,IAAW,OAAO,KAAa,OAAO,kBAAkB,CAAC,CAAC,CAAC;IACpD,kBAAkB;QACvB,OAAO,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC9F,CAAC;IACM,cAAc,KAAa,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtE,WAAW,KAA2C,CAAC;CAE/D;AAED,uDAAuD;AACvD,MAAM,WAAW;CAAG;AACpB,MAAM,cAAe,SAAQ,yBAAsC;IACjE,IAAW,WAAW,KAAa,OAAO,aAAa,CAAC,CAAC,CAAC;IAC1D,IAAW,cAAc,KAAa,OAAO,cAAc,CAAC,CAAC,CAAC;IAC9D,IAAW,QAAQ,KAAa,OAAO,cAAc,CAAC,CAAC,CAAC;IACxD,IAAW,OAAO,KAAa,OAAO,wBAAwB,CAAC,CAAC,CAAC;IAC1D,kBAAkB,KAA+B,OAAO,EAAE,CAAC,CAAC,CAAC;IAC7D,cAAc,KAAa,OAAO,IAAI,CAAC,CAAC,CAAC;IACzC,WAAW,KAAuB,CAAC;IAC1B,mBAAmB,KAAwB,OAAO,WAAW,CAAC,CAAC,CAAC;CACjF;AAED,QAAQ,CAAC,qDAAqD,EAAE,GAAG,EAAE;IACnE,EAAE,CAAC,uFAAuF,EAAE,GAAG,EAAE;QAC/F,MAAM,EAAE,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+FAA+F,EAAE,GAAG,EAAE;QACvG,MAAM,EAAE,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,kBAAkB,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACrF,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;QAChC,MAAM,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC","sourcesContent":["/**\n * Unit tests for the OPTIONAL CLIENT SURFACE contract on {@link BaseRealtimeChannelClient} (Phase 2).\n *\n * A bridge-contributed channel (e.g. meeting-controls or a platform-native whiteboard) may be\n * SERVER-ONLY — no MJ Angular surface. The contract change makes `GetSurfaceComponent` optional\n * (defaults to `null`) and adds `HasSurface()` so the overlay can skip a surfaceless channel's tab\n * while still wiring its tools + perception. These tests pin both the server-only default and the\n * surface-bearing override, without rendering Angular.\n */\nimport { describe, it, expect } from 'vitest';\nimport type { Type } from '@angular/core';\nimport { RealtimeToolDefinition } from '@memberjunction/ai';\nimport { BaseRealtimeChannelClient } from '../lib/components/realtime/channels/base-realtime-channel-client';\n\n/** A server-only channel: contributes tools + perception, but renders NO surface. */\nclass ServerOnlyChannel extends BaseRealtimeChannelClient {\n public get ChannelName(): string { return 'ServerOnly'; }\n public get ToolNamePrefix(): string { return 'ServerOnly_'; }\n public get TabTitle(): string { return 'Server Only'; }\n public get TabIcon(): string { return 'fa-solid fa-gear'; }\n public GetToolDefinitions(): RealtimeToolDefinition[] {\n return [{ Name: 'ServerOnly_Do', Description: 'do', ParametersSchema: { type: 'object' } }];\n }\n public ApplyAgentTool(): string { return JSON.stringify({ success: true }); }\n public BindSurface(): void { /* never called — no surface */ }\n // Deliberately does NOT override GetSurfaceComponent — inherits the null default.\n}\n\n/** A surface-bearing channel for the positive case. */\nclass FakeSurface {}\nclass SurfaceChannel extends BaseRealtimeChannelClient<FakeSurface> {\n public get ChannelName(): string { return 'WithSurface'; }\n public get ToolNamePrefix(): string { return 'WithSurface_'; }\n public get TabTitle(): string { return 'With Surface'; }\n public get TabIcon(): string { return 'fa-solid fa-chalkboard'; }\n public GetToolDefinitions(): RealtimeToolDefinition[] { return []; }\n public ApplyAgentTool(): string { return '{}'; }\n public BindSurface(): void { /* no-op */ }\n public override GetSurfaceComponent(): Type<FakeSurface> { return FakeSurface; }\n}\n\ndescribe('BaseRealtimeChannelClient — optional client surface', () => {\n it('a server-only channel returns null from GetSurfaceComponent and false from HasSurface', () => {\n const ch = new ServerOnlyChannel();\n expect(ch.GetSurfaceComponent()).toBeNull();\n expect(ch.HasSurface()).toBe(false);\n });\n\n it('a server-only channel still contributes tools (tools/perception are unaffected by no surface)', () => {\n const ch = new ServerOnlyChannel();\n expect(ch.GetToolDefinitions().map((t) => t.Name)).toEqual(['ServerOnly_Do']);\n });\n\n it('a surface-bearing channel returns its component and reports HasSurface true', () => {\n const ch = new SurfaceChannel();\n expect(ch.GetSurfaceComponent()).toBe(FakeSurface);\n expect(ch.HasSurface()).toBe(true);\n });\n});\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the Before/After cancelable chat event arg classes.
|
|
3
|
+
*
|
|
4
|
+
* Asserts:
|
|
5
|
+
* - CancellableChatEventArgs base class default: Cancel = false.
|
|
6
|
+
* - Each Before* class extends CancellableChatEventArgs and carries its
|
|
7
|
+
* readonly payload fields.
|
|
8
|
+
* - Each After* class carries its payload as readonly fields.
|
|
9
|
+
* - Listeners can set Cancel = true on Before events.
|
|
10
|
+
*
|
|
11
|
+
* Pure logic — no Angular runtime needed. Vitest-ready.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=chat-events.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-events.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/chat-events.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for the Before/After cancelable chat event arg classes.
|
|
3
|
+
*
|
|
4
|
+
* Asserts:
|
|
5
|
+
* - CancellableChatEventArgs base class default: Cancel = false.
|
|
6
|
+
* - Each Before* class extends CancellableChatEventArgs and carries its
|
|
7
|
+
* readonly payload fields.
|
|
8
|
+
* - Each After* class carries its payload as readonly fields.
|
|
9
|
+
* - Listeners can set Cancel = true on Before events.
|
|
10
|
+
*
|
|
11
|
+
* Pure logic — no Angular runtime needed. Vitest-ready.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { CancellableChatEventArgs, BeforeAgentTurnEventArgs, AfterAgentTurnEventArgs, BeforeToolInvokedEventArgs, AfterToolInvokedEventArgs, BeforeResponseFormSubmittedEventArgs, AfterResponseFormSubmittedEventArgs, SessionStartedEventArgs, SessionChannelStateChangedEventArgs, SessionEndedEventArgs, } from '../lib/events/chat-events';
|
|
15
|
+
describe('CancellableChatEventArgs (base class)', () => {
|
|
16
|
+
it('defaults Cancel to false', () => {
|
|
17
|
+
const e = new CancellableChatEventArgs();
|
|
18
|
+
expect(e.Cancel).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
it('lets listeners flip Cancel to true', () => {
|
|
21
|
+
const e = new CancellableChatEventArgs();
|
|
22
|
+
e.Cancel = true;
|
|
23
|
+
e.CancelReason = 'rate-limited';
|
|
24
|
+
expect(e.Cancel).toBe(true);
|
|
25
|
+
expect(e.CancelReason).toBe('rate-limited');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('BeforeAgentTurnEventArgs', () => {
|
|
29
|
+
it('extends CancellableChatEventArgs and carries its payload', () => {
|
|
30
|
+
const e = new BeforeAgentTurnEventArgs('conv-1', 'hello', 'app-1');
|
|
31
|
+
expect(e).toBeInstanceOf(CancellableChatEventArgs);
|
|
32
|
+
expect(e.ConversationId).toBe('conv-1');
|
|
33
|
+
expect(e.MessageText).toBe('hello');
|
|
34
|
+
expect(e.ApplicationId).toBe('app-1');
|
|
35
|
+
expect(e.Cancel).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('applicationId defaults to null when omitted', () => {
|
|
38
|
+
const e = new BeforeAgentTurnEventArgs('conv-1', 'hello');
|
|
39
|
+
expect(e.ApplicationId).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('AfterAgentTurnEventArgs', () => {
|
|
43
|
+
it('carries its payload as readonly fields', () => {
|
|
44
|
+
const result = { success: true, executionTimeMs: 100 };
|
|
45
|
+
const e = new AfterAgentTurnEventArgs('conv-1', 'run-1', result);
|
|
46
|
+
expect(e.ConversationId).toBe('conv-1');
|
|
47
|
+
expect(e.AgentRunId).toBe('run-1');
|
|
48
|
+
expect(e.Result).toBe(result);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('BeforeToolInvokedEventArgs', () => {
|
|
52
|
+
it('extends CancellableChatEventArgs and carries tool name + args', () => {
|
|
53
|
+
const e = new BeforeToolInvokedEventArgs('NavigateToRecord', { id: '1' });
|
|
54
|
+
expect(e).toBeInstanceOf(CancellableChatEventArgs);
|
|
55
|
+
expect(e.ToolName).toBe('NavigateToRecord');
|
|
56
|
+
expect(e.Args).toEqual({ id: '1' });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('AfterToolInvokedEventArgs', () => {
|
|
60
|
+
it('carries tool name + args + result', () => {
|
|
61
|
+
const e = new AfterToolInvokedEventArgs('NavigateToRecord', { id: '1' }, { ok: true });
|
|
62
|
+
expect(e.ToolName).toBe('NavigateToRecord');
|
|
63
|
+
expect(e.Args).toEqual({ id: '1' });
|
|
64
|
+
expect(e.Result).toEqual({ ok: true });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('BeforeResponseFormSubmittedEventArgs', () => {
|
|
68
|
+
it('extends CancellableChatEventArgs and carries form id + values', () => {
|
|
69
|
+
const e = new BeforeResponseFormSubmittedEventArgs('form-1', { q1: 'yes' });
|
|
70
|
+
expect(e).toBeInstanceOf(CancellableChatEventArgs);
|
|
71
|
+
expect(e.FormId).toBe('form-1');
|
|
72
|
+
expect(e.Values).toEqual({ q1: 'yes' });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('AfterResponseFormSubmittedEventArgs', () => {
|
|
76
|
+
it('carries form id + values as readonly fields', () => {
|
|
77
|
+
const e = new AfterResponseFormSubmittedEventArgs('form-1', { q1: 'yes' });
|
|
78
|
+
expect(e.FormId).toBe('form-1');
|
|
79
|
+
expect(e.Values).toEqual({ q1: 'yes' });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('Session* informational events', () => {
|
|
83
|
+
it('SessionStartedEventArgs carries session id + channel kinds', () => {
|
|
84
|
+
const e = new SessionStartedEventArgs('sess-1', ['text', 'voice']);
|
|
85
|
+
expect(e.SessionId).toBe('sess-1');
|
|
86
|
+
expect(e.ChannelKinds).toEqual(['text', 'voice']);
|
|
87
|
+
});
|
|
88
|
+
it('SessionChannelStateChangedEventArgs carries session id + channel kind + state', () => {
|
|
89
|
+
const e = new SessionChannelStateChangedEventArgs('sess-1', 'voice', 'open');
|
|
90
|
+
expect(e.SessionId).toBe('sess-1');
|
|
91
|
+
expect(e.ChannelKind).toBe('voice');
|
|
92
|
+
expect(e.State).toBe('open');
|
|
93
|
+
});
|
|
94
|
+
it('SessionEndedEventArgs carries session id + narrowed reason', () => {
|
|
95
|
+
const e = new SessionEndedEventArgs('sess-1', 'explicit');
|
|
96
|
+
expect(e.SessionId).toBe('sess-1');
|
|
97
|
+
expect(e.Reason).toBe('explicit');
|
|
98
|
+
});
|
|
99
|
+
it('SessionEndedEventArgs accepts all narrowed reason values', () => {
|
|
100
|
+
// Compile-time check: the narrowed union should permit exactly these three.
|
|
101
|
+
const explicit = new SessionEndedEventArgs('sess-1', 'explicit');
|
|
102
|
+
const error = new SessionEndedEventArgs('sess-1', 'error');
|
|
103
|
+
const unknown = new SessionEndedEventArgs('sess-1', 'unknown');
|
|
104
|
+
expect(explicit.Reason).toBe('explicit');
|
|
105
|
+
expect(error.Reason).toBe('error');
|
|
106
|
+
expect(unknown.Reason).toBe('unknown');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
//# sourceMappingURL=chat-events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-events.test.js","sourceRoot":"","sources":["../../src/__tests__/chat-events.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Tests for the Before/After cancelable chat event arg classes.\n *\n * Asserts:\n * - CancellableChatEventArgs base class default: Cancel = false.\n * - Each Before* class extends CancellableChatEventArgs and carries its\n * readonly payload fields.\n * - Each After* class carries its payload as readonly fields.\n * - Listeners can set Cancel = true on Before events.\n *\n * Pure logic — no Angular runtime needed. Vitest-ready.\n */\n\nimport { describe, it, expect } from 'vitest';\n\nimport {\n CancellableChatEventArgs,\n BeforeAgentTurnEventArgs,\n AfterAgentTurnEventArgs,\n BeforeToolInvokedEventArgs,\n AfterToolInvokedEventArgs,\n BeforeResponseFormSubmittedEventArgs,\n AfterResponseFormSubmittedEventArgs,\n SessionStartedEventArgs,\n SessionChannelStateChangedEventArgs,\n SessionEndedEventArgs,\n} from '../lib/events/chat-events';\n\ndescribe('CancellableChatEventArgs (base class)', () => {\n it('defaults Cancel to false', () => {\n const e = new CancellableChatEventArgs();\n expect(e.Cancel).toBe(false);\n });\n\n it('lets listeners flip Cancel to true', () => {\n const e = new CancellableChatEventArgs();\n e.Cancel = true;\n e.CancelReason = 'rate-limited';\n expect(e.Cancel).toBe(true);\n expect(e.CancelReason).toBe('rate-limited');\n });\n});\n\ndescribe('BeforeAgentTurnEventArgs', () => {\n it('extends CancellableChatEventArgs and carries its payload', () => {\n const e = new BeforeAgentTurnEventArgs('conv-1', 'hello', 'app-1');\n expect(e).toBeInstanceOf(CancellableChatEventArgs);\n expect(e.ConversationId).toBe('conv-1');\n expect(e.MessageText).toBe('hello');\n expect(e.ApplicationId).toBe('app-1');\n expect(e.Cancel).toBe(false);\n });\n\n it('applicationId defaults to null when omitted', () => {\n const e = new BeforeAgentTurnEventArgs('conv-1', 'hello');\n expect(e.ApplicationId).toBeNull();\n });\n});\n\ndescribe('AfterAgentTurnEventArgs', () => {\n it('carries its payload as readonly fields', () => {\n const result = { success: true, executionTimeMs: 100 } as never;\n const e = new AfterAgentTurnEventArgs('conv-1', 'run-1', result);\n expect(e.ConversationId).toBe('conv-1');\n expect(e.AgentRunId).toBe('run-1');\n expect(e.Result).toBe(result);\n });\n});\n\ndescribe('BeforeToolInvokedEventArgs', () => {\n it('extends CancellableChatEventArgs and carries tool name + args', () => {\n const e = new BeforeToolInvokedEventArgs('NavigateToRecord', { id: '1' });\n expect(e).toBeInstanceOf(CancellableChatEventArgs);\n expect(e.ToolName).toBe('NavigateToRecord');\n expect(e.Args).toEqual({ id: '1' });\n });\n});\n\ndescribe('AfterToolInvokedEventArgs', () => {\n it('carries tool name + args + result', () => {\n const e = new AfterToolInvokedEventArgs('NavigateToRecord', { id: '1' }, { ok: true });\n expect(e.ToolName).toBe('NavigateToRecord');\n expect(e.Args).toEqual({ id: '1' });\n expect(e.Result).toEqual({ ok: true });\n });\n});\n\ndescribe('BeforeResponseFormSubmittedEventArgs', () => {\n it('extends CancellableChatEventArgs and carries form id + values', () => {\n const e = new BeforeResponseFormSubmittedEventArgs('form-1', { q1: 'yes' });\n expect(e).toBeInstanceOf(CancellableChatEventArgs);\n expect(e.FormId).toBe('form-1');\n expect(e.Values).toEqual({ q1: 'yes' });\n });\n});\n\ndescribe('AfterResponseFormSubmittedEventArgs', () => {\n it('carries form id + values as readonly fields', () => {\n const e = new AfterResponseFormSubmittedEventArgs('form-1', { q1: 'yes' });\n expect(e.FormId).toBe('form-1');\n expect(e.Values).toEqual({ q1: 'yes' });\n });\n});\n\ndescribe('Session* informational events', () => {\n it('SessionStartedEventArgs carries session id + channel kinds', () => {\n const e = new SessionStartedEventArgs('sess-1', ['text', 'voice']);\n expect(e.SessionId).toBe('sess-1');\n expect(e.ChannelKinds).toEqual(['text', 'voice']);\n });\n\n it('SessionChannelStateChangedEventArgs carries session id + channel kind + state', () => {\n const e = new SessionChannelStateChangedEventArgs('sess-1', 'voice', 'open');\n expect(e.SessionId).toBe('sess-1');\n expect(e.ChannelKind).toBe('voice');\n expect(e.State).toBe('open');\n });\n\n it('SessionEndedEventArgs carries session id + narrowed reason', () => {\n const e = new SessionEndedEventArgs('sess-1', 'explicit');\n expect(e.SessionId).toBe('sess-1');\n expect(e.Reason).toBe('explicit');\n });\n\n it('SessionEndedEventArgs accepts all narrowed reason values', () => {\n // Compile-time check: the narrowed union should permit exactly these three.\n const explicit = new SessionEndedEventArgs('sess-1', 'explicit');\n const error = new SessionEndedEventArgs('sess-1', 'error');\n const unknown = new SessionEndedEventArgs('sess-1', 'unknown');\n expect(explicit.Reason).toBe('explicit');\n expect(error.Reason).toBe('error');\n expect(unknown.Reason).toBe('unknown');\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-naming.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/conversation-naming.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the SHARED conversation auto-naming helper — the single
|
|
3
|
+
* implementation behind both the composer's first-message naming and the realtime
|
|
4
|
+
* session path (sessions that created their own conversation name it on call end).
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
8
|
+
import { ConversationEngine } from '@memberjunction/core-entities';
|
|
9
|
+
import { GenerateAndApplyConversationName, NAME_CONVERSATION_PROMPT } from '../lib/services/conversation-naming';
|
|
10
|
+
const USER = { ID: 'user-1' };
|
|
11
|
+
const PROVIDER = {};
|
|
12
|
+
describe('GenerateAndApplyConversationName', () => {
|
|
13
|
+
let saveSpy;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.spyOn(AIEngineBase.Instance, 'Config').mockResolvedValue(undefined);
|
|
16
|
+
vi.spyOn(AIEngineBase.Instance, 'Prompts', 'get').mockReturnValue([
|
|
17
|
+
{ ID: 'prompt-1', Name: NAME_CONVERSATION_PROMPT }
|
|
18
|
+
]);
|
|
19
|
+
saveSpy = vi.spyOn(ConversationEngine.Instance, 'SaveConversation').mockResolvedValue(true);
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
it('runs the Name Conversation prompt, saves the parsed name + description, and returns them', async () => {
|
|
25
|
+
const runPrompt = vi.fn().mockResolvedValue({
|
|
26
|
+
success: true,
|
|
27
|
+
parsedResult: { name: 'Ottawa Weather Briefing', description: 'Weather and stocks' }
|
|
28
|
+
});
|
|
29
|
+
const result = await GenerateAndApplyConversationName({
|
|
30
|
+
ConversationId: 'conv-1',
|
|
31
|
+
MessageText: 'whats the weather in ottawa?',
|
|
32
|
+
Provider: PROVIDER,
|
|
33
|
+
CurrentUser: USER,
|
|
34
|
+
RunPrompt: runPrompt
|
|
35
|
+
});
|
|
36
|
+
expect(runPrompt).toHaveBeenCalledWith('prompt-1', 'whats the weather in ottawa?');
|
|
37
|
+
expect(saveSpy).toHaveBeenCalledWith('conv-1', { Name: 'Ottawa Weather Briefing', Description: 'Weather and stocks' }, USER);
|
|
38
|
+
expect(result).toEqual({ Name: 'Ottawa Weather Briefing', Description: 'Weather and stocks' });
|
|
39
|
+
});
|
|
40
|
+
it('parses fenced raw output when parsedResult is absent', async () => {
|
|
41
|
+
const result = await GenerateAndApplyConversationName({
|
|
42
|
+
ConversationId: 'conv-2',
|
|
43
|
+
MessageText: 'hello',
|
|
44
|
+
Provider: PROVIDER,
|
|
45
|
+
CurrentUser: USER,
|
|
46
|
+
RunPrompt: vi.fn().mockResolvedValue({
|
|
47
|
+
success: true,
|
|
48
|
+
output: '```json\n{"name":"Quick Hello"}\n```'
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
expect(result).toEqual({ Name: 'Quick Hello', Description: '' });
|
|
52
|
+
expect(saveSpy).toHaveBeenCalledWith('conv-2', { Name: 'Quick Hello', Description: '' }, USER);
|
|
53
|
+
});
|
|
54
|
+
it('returns null (no save) on timeout — the conversation keeps its default name', async () => {
|
|
55
|
+
vi.useFakeTimers();
|
|
56
|
+
const never = new Promise(() => undefined);
|
|
57
|
+
const pending = GenerateAndApplyConversationName({
|
|
58
|
+
ConversationId: 'conv-3',
|
|
59
|
+
MessageText: 'slow',
|
|
60
|
+
Provider: PROVIDER,
|
|
61
|
+
CurrentUser: USER,
|
|
62
|
+
TimeoutMs: 5000,
|
|
63
|
+
RunPrompt: vi.fn().mockReturnValue(never)
|
|
64
|
+
});
|
|
65
|
+
await vi.advanceTimersByTimeAsync(5001);
|
|
66
|
+
const result = await pending;
|
|
67
|
+
vi.useRealTimers();
|
|
68
|
+
expect(result).toBeNull();
|
|
69
|
+
expect(saveSpy).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
it('returns null when the Name Conversation prompt is not seeded', async () => {
|
|
72
|
+
vi.spyOn(AIEngineBase.Instance, 'Prompts', 'get').mockReturnValue([]);
|
|
73
|
+
const result = await GenerateAndApplyConversationName({
|
|
74
|
+
ConversationId: 'conv-4',
|
|
75
|
+
MessageText: 'x',
|
|
76
|
+
Provider: PROVIDER,
|
|
77
|
+
CurrentUser: USER,
|
|
78
|
+
RunPrompt: vi.fn()
|
|
79
|
+
});
|
|
80
|
+
expect(result).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
it.each([
|
|
83
|
+
['failed run', { success: false }],
|
|
84
|
+
['empty result', null],
|
|
85
|
+
['unparseable output', { success: true, output: 'not json at all {{{' }],
|
|
86
|
+
['parsed without a name', { success: true, parsedResult: { description: 'only desc' } }]
|
|
87
|
+
])('returns null (no save) for %s', async (_label, runResult) => {
|
|
88
|
+
const result = await GenerateAndApplyConversationName({
|
|
89
|
+
ConversationId: 'conv-5',
|
|
90
|
+
MessageText: 'x',
|
|
91
|
+
Provider: PROVIDER,
|
|
92
|
+
CurrentUser: USER,
|
|
93
|
+
RunPrompt: vi.fn().mockResolvedValue(runResult)
|
|
94
|
+
});
|
|
95
|
+
expect(result).toBeNull();
|
|
96
|
+
expect(saveSpy).not.toHaveBeenCalled();
|
|
97
|
+
});
|
|
98
|
+
it('returns null when the save fails (name generated but not applied)', async () => {
|
|
99
|
+
saveSpy.mockResolvedValue(false);
|
|
100
|
+
const result = await GenerateAndApplyConversationName({
|
|
101
|
+
ConversationId: 'conv-6',
|
|
102
|
+
MessageText: 'x',
|
|
103
|
+
Provider: PROVIDER,
|
|
104
|
+
CurrentUser: USER,
|
|
105
|
+
RunPrompt: vi.fn().mockResolvedValue({ success: true, parsedResult: { name: 'N' } })
|
|
106
|
+
});
|
|
107
|
+
expect(result).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=conversation-naming.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-naming.test.js","sourceRoot":"","sources":["../../src/__tests__/conversation-naming.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unit tests for the SHARED conversation auto-naming helper — the single\n * implementation behind both the composer's first-message naming and the realtime\n * session path (sessions that created their own conversation name it on call end).\n */\nimport { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';\nimport { UserInfo } from '@memberjunction/core';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport { ConversationEngine } from '@memberjunction/core-entities';\nimport { GenerateAndApplyConversationName, NAME_CONVERSATION_PROMPT } from '../lib/services/conversation-naming';\n\nconst USER = { ID: 'user-1' } as unknown as UserInfo;\nconst PROVIDER = {} as unknown as GraphQLDataProvider;\n\ndescribe('GenerateAndApplyConversationName', () => {\n let saveSpy: ReturnType<typeof vi.spyOn>;\n\n beforeEach(() => {\n vi.spyOn(AIEngineBase.Instance, 'Config').mockResolvedValue(undefined as never);\n vi.spyOn(AIEngineBase.Instance, 'Prompts', 'get').mockReturnValue([\n { ID: 'prompt-1', Name: NAME_CONVERSATION_PROMPT }\n ] as never);\n saveSpy = vi.spyOn(ConversationEngine.Instance, 'SaveConversation').mockResolvedValue(true as never);\n });\n\n afterEach(() => {\n vi.restoreAllMocks();\n });\n\n it('runs the Name Conversation prompt, saves the parsed name + description, and returns them', async () => {\n const runPrompt = vi.fn().mockResolvedValue({\n success: true,\n parsedResult: { name: 'Ottawa Weather Briefing', description: 'Weather and stocks' }\n });\n\n const result = await GenerateAndApplyConversationName({\n ConversationId: 'conv-1',\n MessageText: 'whats the weather in ottawa?',\n Provider: PROVIDER,\n CurrentUser: USER,\n RunPrompt: runPrompt\n });\n\n expect(runPrompt).toHaveBeenCalledWith('prompt-1', 'whats the weather in ottawa?');\n expect(saveSpy).toHaveBeenCalledWith(\n 'conv-1',\n { Name: 'Ottawa Weather Briefing', Description: 'Weather and stocks' },\n USER\n );\n expect(result).toEqual({ Name: 'Ottawa Weather Briefing', Description: 'Weather and stocks' });\n });\n\n it('parses fenced raw output when parsedResult is absent', async () => {\n const result = await GenerateAndApplyConversationName({\n ConversationId: 'conv-2',\n MessageText: 'hello',\n Provider: PROVIDER,\n CurrentUser: USER,\n RunPrompt: vi.fn().mockResolvedValue({\n success: true,\n output: '```json\\n{\"name\":\"Quick Hello\"}\\n```'\n })\n });\n\n expect(result).toEqual({ Name: 'Quick Hello', Description: '' });\n expect(saveSpy).toHaveBeenCalledWith('conv-2', { Name: 'Quick Hello', Description: '' }, USER);\n });\n\n it('returns null (no save) on timeout — the conversation keeps its default name', async () => {\n vi.useFakeTimers();\n const never = new Promise<never>(() => undefined);\n const pending = GenerateAndApplyConversationName({\n ConversationId: 'conv-3',\n MessageText: 'slow',\n Provider: PROVIDER,\n CurrentUser: USER,\n TimeoutMs: 5000,\n RunPrompt: vi.fn().mockReturnValue(never)\n });\n await vi.advanceTimersByTimeAsync(5001);\n const result = await pending;\n vi.useRealTimers();\n\n expect(result).toBeNull();\n expect(saveSpy).not.toHaveBeenCalled();\n });\n\n it('returns null when the Name Conversation prompt is not seeded', async () => {\n vi.spyOn(AIEngineBase.Instance, 'Prompts', 'get').mockReturnValue([] as never);\n const result = await GenerateAndApplyConversationName({\n ConversationId: 'conv-4',\n MessageText: 'x',\n Provider: PROVIDER,\n CurrentUser: USER,\n RunPrompt: vi.fn()\n });\n expect(result).toBeNull();\n });\n\n it.each([\n ['failed run', { success: false }],\n ['empty result', null],\n ['unparseable output', { success: true, output: 'not json at all {{{' }],\n ['parsed without a name', { success: true, parsedResult: { description: 'only desc' } }]\n ])('returns null (no save) for %s', async (_label, runResult) => {\n const result = await GenerateAndApplyConversationName({\n ConversationId: 'conv-5',\n MessageText: 'x',\n Provider: PROVIDER,\n CurrentUser: USER,\n RunPrompt: vi.fn().mockResolvedValue(runResult)\n });\n expect(result).toBeNull();\n expect(saveSpy).not.toHaveBeenCalled();\n });\n\n it('returns null when the save fails (name generated but not applied)', async () => {\n saveSpy.mockResolvedValue(false as never);\n const result = await GenerateAndApplyConversationName({\n ConversationId: 'conv-6',\n MessageText: 'x',\n Provider: PROVIDER,\n CurrentUser: USER,\n RunPrompt: vi.fn().mockResolvedValue({ success: true, parsedResult: { name: 'N' } })\n });\n expect(result).toBeNull();\n });\n});\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation-result-parser.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/delegation-result-parser.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ParseDelegationResultJson } from '../lib/services/delegation-result-parser';
|
|
3
|
+
describe('ParseDelegationResultJson', () => {
|
|
4
|
+
it('parses a successful result with a runId', () => {
|
|
5
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'all done', runId: 'run-123' }));
|
|
6
|
+
expect(parsed).toEqual({ Success: true, Output: 'all done', RunID: 'run-123' });
|
|
7
|
+
});
|
|
8
|
+
it('parses a successful result without a runId (RunID undefined)', () => {
|
|
9
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'all done' }));
|
|
10
|
+
expect(parsed.Success).toBe(true);
|
|
11
|
+
expect(parsed.Output).toBe('all done');
|
|
12
|
+
expect(parsed.RunID).toBeUndefined();
|
|
13
|
+
});
|
|
14
|
+
it('ignores an empty-string runId', () => {
|
|
15
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'x', runId: '' }));
|
|
16
|
+
expect(parsed.RunID).toBeUndefined();
|
|
17
|
+
});
|
|
18
|
+
it('parses the structured error shape ({success:false, error})', () => {
|
|
19
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, error: 'boom' }));
|
|
20
|
+
expect(parsed).toEqual({ Success: false, Output: 'boom', RunID: undefined });
|
|
21
|
+
});
|
|
22
|
+
it('still carries runId on a failed result that reported one', () => {
|
|
23
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, error: 'boom', runId: 'run-9' }));
|
|
24
|
+
expect(parsed.Success).toBe(false);
|
|
25
|
+
expect(parsed.RunID).toBe('run-9');
|
|
26
|
+
});
|
|
27
|
+
it('treats success as true unless explicitly false', () => {
|
|
28
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ output: 'no flag' }));
|
|
29
|
+
expect(parsed.Success).toBe(true);
|
|
30
|
+
expect(parsed.Output).toBe('no flag');
|
|
31
|
+
});
|
|
32
|
+
it('surfaces a non-JSON payload as the raw output (success assumed)', () => {
|
|
33
|
+
const parsed = ParseDelegationResultJson('plain text result');
|
|
34
|
+
expect(parsed).toEqual({ Success: true, Output: 'plain text result' });
|
|
35
|
+
});
|
|
36
|
+
it('prefers output over error when both are present', () => {
|
|
37
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, output: 'partial', error: 'late failure' }));
|
|
38
|
+
expect(parsed.Output).toBe('partial');
|
|
39
|
+
});
|
|
40
|
+
describe('artifacts', () => {
|
|
41
|
+
it('parses artifacts into PascalCase entries', () => {
|
|
42
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({
|
|
43
|
+
success: true,
|
|
44
|
+
output: 'done',
|
|
45
|
+
runId: 'run-1',
|
|
46
|
+
artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: 'Weather Report' }]
|
|
47
|
+
}));
|
|
48
|
+
expect(parsed.Artifacts).toEqual([
|
|
49
|
+
{ ArtifactID: 'a-1', ArtifactVersionID: 'av-1', Name: 'Weather Report' }
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
it('leaves Artifacts undefined when the field is absent', () => {
|
|
53
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done' }));
|
|
54
|
+
expect(parsed.Artifacts).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
it('leaves Artifacts undefined for an empty array', () => {
|
|
57
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done', artifacts: [] }));
|
|
58
|
+
expect(parsed.Artifacts).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
it('leaves Artifacts undefined when the field is not an array', () => {
|
|
61
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done', artifacts: 'nope' }));
|
|
62
|
+
expect(parsed.Artifacts).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('drops malformed entries (missing ids) and keeps valid ones', () => {
|
|
65
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({
|
|
66
|
+
success: true,
|
|
67
|
+
output: 'done',
|
|
68
|
+
artifacts: [
|
|
69
|
+
{ artifactId: 'a-1' }, // no version id → dropped
|
|
70
|
+
{ artifactVersionId: 'av-2' }, // no artifact id → dropped
|
|
71
|
+
{ artifactId: 42, artifactVersionId: 'av-3' }, // non-string id → dropped
|
|
72
|
+
{ artifactId: 'a-4', artifactVersionId: 'av-4', name: 'Kept' }
|
|
73
|
+
]
|
|
74
|
+
}));
|
|
75
|
+
expect(parsed.Artifacts).toEqual([{ ArtifactID: 'a-4', ArtifactVersionID: 'av-4', Name: 'Kept' }]);
|
|
76
|
+
});
|
|
77
|
+
it('falls back to "Artifact" for a missing or blank name', () => {
|
|
78
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({
|
|
79
|
+
success: true,
|
|
80
|
+
output: 'done',
|
|
81
|
+
artifacts: [
|
|
82
|
+
{ artifactId: 'a-1', artifactVersionId: 'av-1' },
|
|
83
|
+
{ artifactId: 'a-2', artifactVersionId: 'av-2', name: ' ' }
|
|
84
|
+
]
|
|
85
|
+
}));
|
|
86
|
+
expect(parsed.Artifacts?.map(a => a.Name)).toEqual(['Artifact', 'Artifact']);
|
|
87
|
+
});
|
|
88
|
+
it('trims artifact names', () => {
|
|
89
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({
|
|
90
|
+
success: true,
|
|
91
|
+
output: 'done',
|
|
92
|
+
artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: ' Q3 Summary ' }]
|
|
93
|
+
}));
|
|
94
|
+
expect(parsed.Artifacts?.[0].Name).toBe('Q3 Summary');
|
|
95
|
+
});
|
|
96
|
+
it('still carries artifacts on a failed result that reported them', () => {
|
|
97
|
+
const parsed = ParseDelegationResultJson(JSON.stringify({
|
|
98
|
+
success: false,
|
|
99
|
+
error: 'partial failure',
|
|
100
|
+
artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: 'Partial' }]
|
|
101
|
+
}));
|
|
102
|
+
expect(parsed.Success).toBe(false);
|
|
103
|
+
expect(parsed.Artifacts).toHaveLength(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
//# sourceMappingURL=delegation-result-parser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delegation-result-parser.test.js","sourceRoot":"","sources":["../../src/__tests__/delegation-result-parser.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport { ParseDelegationResultJson } from '../lib/services/delegation-result-parser';\n\ndescribe('ParseDelegationResultJson', () => {\n it('parses a successful result with a runId', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'all done', runId: 'run-123' }));\n expect(parsed).toEqual({ Success: true, Output: 'all done', RunID: 'run-123' });\n });\n\n it('parses a successful result without a runId (RunID undefined)', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'all done' }));\n expect(parsed.Success).toBe(true);\n expect(parsed.Output).toBe('all done');\n expect(parsed.RunID).toBeUndefined();\n });\n\n it('ignores an empty-string runId', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'x', runId: '' }));\n expect(parsed.RunID).toBeUndefined();\n });\n\n it('parses the structured error shape ({success:false, error})', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, error: 'boom' }));\n expect(parsed).toEqual({ Success: false, Output: 'boom', RunID: undefined });\n });\n\n it('still carries runId on a failed result that reported one', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, error: 'boom', runId: 'run-9' }));\n expect(parsed.Success).toBe(false);\n expect(parsed.RunID).toBe('run-9');\n });\n\n it('treats success as true unless explicitly false', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ output: 'no flag' }));\n expect(parsed.Success).toBe(true);\n expect(parsed.Output).toBe('no flag');\n });\n\n it('surfaces a non-JSON payload as the raw output (success assumed)', () => {\n const parsed = ParseDelegationResultJson('plain text result');\n expect(parsed).toEqual({ Success: true, Output: 'plain text result' });\n });\n\n it('prefers output over error when both are present', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: false, output: 'partial', error: 'late failure' }));\n expect(parsed.Output).toBe('partial');\n });\n\n describe('artifacts', () => {\n it('parses artifacts into PascalCase entries', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({\n success: true,\n output: 'done',\n runId: 'run-1',\n artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: 'Weather Report' }]\n }));\n expect(parsed.Artifacts).toEqual([\n { ArtifactID: 'a-1', ArtifactVersionID: 'av-1', Name: 'Weather Report' }\n ]);\n });\n\n it('leaves Artifacts undefined when the field is absent', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done' }));\n expect(parsed.Artifacts).toBeUndefined();\n });\n\n it('leaves Artifacts undefined for an empty array', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done', artifacts: [] }));\n expect(parsed.Artifacts).toBeUndefined();\n });\n\n it('leaves Artifacts undefined when the field is not an array', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({ success: true, output: 'done', artifacts: 'nope' }));\n expect(parsed.Artifacts).toBeUndefined();\n });\n\n it('drops malformed entries (missing ids) and keeps valid ones', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({\n success: true,\n output: 'done',\n artifacts: [\n { artifactId: 'a-1' }, // no version id → dropped\n { artifactVersionId: 'av-2' }, // no artifact id → dropped\n { artifactId: 42, artifactVersionId: 'av-3' }, // non-string id → dropped\n { artifactId: 'a-4', artifactVersionId: 'av-4', name: 'Kept' }\n ]\n }));\n expect(parsed.Artifacts).toEqual([{ ArtifactID: 'a-4', ArtifactVersionID: 'av-4', Name: 'Kept' }]);\n });\n\n it('falls back to \"Artifact\" for a missing or blank name', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({\n success: true,\n output: 'done',\n artifacts: [\n { artifactId: 'a-1', artifactVersionId: 'av-1' },\n { artifactId: 'a-2', artifactVersionId: 'av-2', name: ' ' }\n ]\n }));\n expect(parsed.Artifacts?.map(a => a.Name)).toEqual(['Artifact', 'Artifact']);\n });\n\n it('trims artifact names', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({\n success: true,\n output: 'done',\n artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: ' Q3 Summary ' }]\n }));\n expect(parsed.Artifacts?.[0].Name).toBe('Q3 Summary');\n });\n\n it('still carries artifacts on a failed result that reported them', () => {\n const parsed = ParseDelegationResultJson(JSON.stringify({\n success: false,\n error: 'partial failure',\n artifacts: [{ artifactId: 'a-1', artifactVersionId: 'av-1', name: 'Partial' }]\n }));\n expect(parsed.Success).toBe(false);\n expect(parsed.Artifacts).toHaveLength(1);\n });\n });\n});\n"]}
|