@memberjunction/ng-conversations 6.2.0-edge.0 → 6.2.0-edge.2
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 +43 -2
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +19 -3
- package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js +39 -7
- package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
- package/dist/lib/components/agent/active-agent-indicator.component.d.ts +74 -6
- package/dist/lib/components/agent/active-agent-indicator.component.d.ts.map +1 -1
- package/dist/lib/components/agent/active-agent-indicator.component.js +111 -21
- package/dist/lib/components/agent/active-agent-indicator.component.js.map +1 -1
- package/dist/lib/components/agent/agent-process-panel.component.d.ts +31 -5
- package/dist/lib/components/agent/agent-process-panel.component.d.ts.map +1 -1
- package/dist/lib/components/agent/agent-process-panel.component.js +71 -15
- package/dist/lib/components/agent/agent-process-panel.component.js.map +1 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.d.ts +65 -9
- package/dist/lib/components/artifact/artifact-share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/artifact/artifact-share-modal.component.js +169 -57
- package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
- package/dist/lib/components/attachment/image-viewer.component.d.ts +77 -11
- package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/attachment/image-viewer.component.js +181 -47
- package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
- package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts +129 -19
- package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/artifact-collection-picker-modal.component.js +350 -116
- package/dist/lib/components/collection/artifact-collection-picker-modal.component.js.map +1 -1
- package/dist/lib/components/collection/artifact-create-modal.component.d.ts +60 -8
- package/dist/lib/components/collection/artifact-create-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/artifact-create-modal.component.js +130 -46
- package/dist/lib/components/collection/artifact-create-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-artifact-card.component.d.ts +63 -3
- package/dist/lib/components/collection/collection-artifact-card.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-artifact-card.component.js +113 -19
- package/dist/lib/components/collection/collection-artifact-card.component.js.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.d.ts +54 -8
- package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-form-modal.component.js +130 -46
- package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-share-modal.component.d.ts +70 -10
- package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-share-modal.component.js +175 -53
- package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
- package/dist/lib/components/collection/collection-tree.component.d.ts +89 -9
- package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-tree.component.js +220 -70
- package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
- package/dist/lib/components/collection/collection-view.component.d.ts +89 -11
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +154 -40
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.d.ts +390 -38
- package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collections-full-view.component.js +951 -367
- package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.d.ts +213 -18
- package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
- package/dist/lib/components/composer/ai-composer.component.js +388 -44
- package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +12 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-agent-picker.component.js +26 -4
- package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +1191 -124
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +2816 -785
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +139 -19
- package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-empty-state.component.js +296 -59
- package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-list.component.d.ts +579 -78
- package/dist/lib/components/conversation/conversation-list.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-list.component.js +2322 -823
- package/dist/lib/components/conversation/conversation-list.component.js.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts +42 -4
- package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/pinned-messages-panel.component.js +93 -21
- package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
- package/dist/lib/components/dialogs/input-dialog.component.d.ts +57 -11
- package/dist/lib/components/dialogs/input-dialog.component.d.ts.map +1 -1
- package/dist/lib/components/dialogs/input-dialog.component.js +128 -16
- package/dist/lib/components/dialogs/input-dialog.component.js.map +1 -1
- package/dist/lib/components/dialogs/rating-dialog.component.d.ts +68 -10
- package/dist/lib/components/dialogs/rating-dialog.component.d.ts.map +1 -1
- package/dist/lib/components/dialogs/rating-dialog.component.js +151 -27
- package/dist/lib/components/dialogs/rating-dialog.component.js.map +1 -1
- package/dist/lib/components/export/export-modal.component.d.ts +84 -10
- package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
- package/dist/lib/components/export/export-modal.component.js +173 -49
- package/dist/lib/components/export/export-modal.component.js.map +1 -1
- package/dist/lib/components/global-tasks/global-tasks-panel.component.d.ts +29 -3
- package/dist/lib/components/global-tasks/global-tasks-panel.component.d.ts.map +1 -1
- package/dist/lib/components/global-tasks/global-tasks-panel.component.js +58 -12
- package/dist/lib/components/global-tasks/global-tasks-panel.component.js.map +1 -1
- package/dist/lib/components/library/library-full-view.component.d.ts +57 -8
- package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/library/library-full-view.component.js +119 -35
- package/dist/lib/components/library/library-full-view.component.js.map +1 -1
- package/dist/lib/components/members/members-modal.component.d.ts +76 -10
- package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
- package/dist/lib/components/members/members-modal.component.js +164 -50
- package/dist/lib/components/members/members-modal.component.js.map +1 -1
- package/dist/lib/components/message/actionable-commands.component.d.ts +49 -5
- package/dist/lib/components/message/actionable-commands.component.d.ts.map +1 -1
- package/dist/lib/components/message/actionable-commands.component.js +140 -26
- package/dist/lib/components/message/actionable-commands.component.js.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.d.ts +46 -10
- package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
- package/dist/lib/components/message/conversation-message-rating.component.js +120 -36
- package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
- package/dist/lib/components/message/message-input.component.d.ts +613 -74
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +1390 -422
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +490 -29
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +1088 -244
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +297 -29
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +598 -123
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/navigation/conversation-navigation.component.d.ts +64 -5
- package/dist/lib/components/navigation/conversation-navigation.component.d.ts.map +1 -1
- package/dist/lib/components/navigation/conversation-navigation.component.js +101 -11
- package/dist/lib/components/navigation/conversation-navigation.component.js.map +1 -1
- package/dist/lib/components/notification/activity-indicator.component.d.ts +6 -2
- package/dist/lib/components/notification/activity-indicator.component.d.ts.map +1 -1
- package/dist/lib/components/notification/activity-indicator.component.js +13 -3
- package/dist/lib/components/notification/activity-indicator.component.js.map +1 -1
- package/dist/lib/components/notification/notification-badge.component.d.ts +15 -3
- package/dist/lib/components/notification/notification-badge.component.d.ts.map +1 -1
- package/dist/lib/components/notification/notification-badge.component.js +36 -8
- package/dist/lib/components/notification/notification-badge.component.js.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.js +4 -4
- package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
- package/dist/lib/components/project/project-form-modal.component.d.ts +93 -11
- package/dist/lib/components/project/project-form-modal.component.d.ts.map +1 -1
- package/dist/lib/components/project/project-form-modal.component.js +348 -87
- package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
- package/dist/lib/components/project/project-selector.component.d.ts +78 -8
- package/dist/lib/components/project/project-selector.component.d.ts.map +1 -1
- package/dist/lib/components/project/project-selector.component.js +177 -46
- package/dist/lib/components/project/project-selector.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +1 -1
- package/dist/lib/components/realtime/realtime-activity-rail.component.js +2 -2
- package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +1 -1
- package/dist/lib/components/realtime/realtime-delegation-card.component.js +2 -2
- package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +2 -2
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +18 -18
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +47 -23
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +136 -92
- package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +2 -2
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js +4 -4
- package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +1 -1
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js +3 -3
- package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +13 -11
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +29 -25
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +3 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +6 -2
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/components/search/search-panel.component.d.ts +133 -22
- package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
- package/dist/lib/components/search/search-panel.component.js +312 -83
- package/dist/lib/components/search/search-panel.component.js.map +1 -1
- package/dist/lib/components/share/share-modal.component.d.ts +69 -9
- package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
- package/dist/lib/components/share/share-modal.component.js +139 -31
- package/dist/lib/components/share/share-modal.component.js.map +1 -1
- package/dist/lib/components/shared/user-picker.component.d.ts +48 -8
- package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
- package/dist/lib/components/shared/user-picker.component.js +110 -30
- package/dist/lib/components/shared/user-picker.component.js.map +1 -1
- package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts +114 -11
- package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts.map +1 -1
- package/dist/lib/components/sidebar/conversation-sidebar.component.js +199 -21
- package/dist/lib/components/sidebar/conversation-sidebar.component.js.map +1 -1
- package/dist/lib/components/sidebar/routines-section.component.d.ts +20 -1
- package/dist/lib/components/sidebar/routines-section.component.d.ts.map +1 -1
- package/dist/lib/components/sidebar/routines-section.component.js +25 -5
- package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
- package/dist/lib/components/slots/slot-interfaces.d.ts +19 -0
- package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -1
- package/dist/lib/components/slots/slot-interfaces.js.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.d.ts +95 -13
- package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
- package/dist/lib/components/task/tasks-full-view.component.js +223 -75
- package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
- package/dist/lib/components/tasks/task-widget.component.d.ts +46 -6
- package/dist/lib/components/tasks/task-widget.component.d.ts.map +1 -1
- package/dist/lib/components/tasks/task-widget.component.js +100 -16
- package/dist/lib/components/tasks/task-widget.component.js.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +64 -7
- package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
- package/dist/lib/components/tasks/tasks-dropdown.component.js +127 -27
- package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
- package/dist/lib/components/thread/thread-panel.component.d.ts +66 -8
- package/dist/lib/components/thread/thread-panel.component.d.ts.map +1 -1
- package/dist/lib/components/thread/thread-panel.component.js +146 -36
- package/dist/lib/components/thread/thread-panel.component.js.map +1 -1
- package/dist/lib/components/toast/toast.component.d.ts +11 -1
- package/dist/lib/components/toast/toast.component.d.ts.map +1 -1
- package/dist/lib/components/toast/toast.component.js +25 -5
- package/dist/lib/components/toast/toast.component.js.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts +385 -42
- package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
- package/dist/lib/components/workspace/conversation-workspace.component.js +891 -292
- package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
- package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts +8 -0
- package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts.map +1 -1
- package/dist/lib/composer-plugins/base-conversation-mention.provider.js +8 -1
- package/dist/lib/composer-plugins/base-conversation-mention.provider.js.map +1 -1
- package/dist/lib/directives/chat-slot.directive.d.ts +5 -1
- package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -1
- package/dist/lib/directives/chat-slot.directive.js.map +1 -1
- package/dist/lib/directives/search-shortcut.directive.d.ts +9 -1
- package/dist/lib/directives/search-shortcut.directive.d.ts.map +1 -1
- package/dist/lib/directives/search-shortcut.directive.js +14 -4
- package/dist/lib/directives/search-shortcut.directive.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +34 -7
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +37 -7
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +113 -0
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -0
- package/dist/lib/models/agent-turn.model.js +12 -0
- package/dist/lib/models/agent-turn.model.js.map +1 -0
- package/dist/lib/models/collection-view.model.js.map +1 -1
- package/dist/lib/models/conversation-state.model.d.ts +8 -0
- 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/models/lazy-artifact-info.d.ts +44 -12
- package/dist/lib/models/lazy-artifact-info.d.ts.map +1 -1
- package/dist/lib/models/lazy-artifact-info.js +98 -34
- package/dist/lib/models/lazy-artifact-info.js.map +1 -1
- package/dist/lib/services/active-tasks.service.d.ts +30 -4
- package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
- package/dist/lib/services/active-tasks.service.js +71 -19
- package/dist/lib/services/active-tasks.service.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts +17 -1
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +48 -16
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-permission.service.d.ts +39 -0
- package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-permission.service.js +152 -17
- package/dist/lib/services/artifact-permission.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts +58 -5
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +135 -35
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/collection-permission.service.d.ts +28 -0
- package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
- package/dist/lib/services/collection-permission.service.js +88 -32
- package/dist/lib/services/collection-permission.service.js.map +1 -1
- package/dist/lib/services/collection-state.service.d.ts +11 -1
- package/dist/lib/services/collection-state.service.d.ts.map +1 -1
- package/dist/lib/services/collection-state.service.js +25 -5
- package/dist/lib/services/collection-state.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +47 -4
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +85 -20
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-attachment.service.d.ts +17 -0
- package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-attachment.service.js +38 -10
- package/dist/lib/services/conversation-attachment.service.js.map +1 -1
- package/dist/lib/services/conversation-liveness-dom.service.d.ts +42 -0
- package/dist/lib/services/conversation-liveness-dom.service.d.ts.map +1 -0
- package/dist/lib/services/conversation-liveness-dom.service.js +74 -0
- package/dist/lib/services/conversation-liveness-dom.service.js.map +1 -0
- package/dist/lib/services/conversation-streaming.service.d.ts +22 -0
- package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-streaming.service.js +50 -10
- package/dist/lib/services/conversation-streaming.service.js.map +1 -1
- package/dist/lib/services/data-cache.service.d.ts +31 -0
- package/dist/lib/services/data-cache.service.d.ts.map +1 -1
- package/dist/lib/services/data-cache.service.js +71 -15
- package/dist/lib/services/data-cache.service.js.map +1 -1
- package/dist/lib/services/dialog.service.d.ts +13 -0
- package/dist/lib/services/dialog.service.d.ts.map +1 -1
- package/dist/lib/services/dialog.service.js +25 -5
- package/dist/lib/services/dialog.service.js.map +1 -1
- package/dist/lib/services/export.service.d.ts +2 -0
- package/dist/lib/services/export.service.d.ts.map +1 -1
- package/dist/lib/services/export.service.js +5 -1
- package/dist/lib/services/export.service.js.map +1 -1
- package/dist/lib/services/mention-parser.service.d.ts +10 -0
- package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
- package/dist/lib/services/mention-parser.service.js +25 -5
- package/dist/lib/services/mention-parser.service.js.map +1 -1
- package/dist/lib/services/notification.service.d.ts +54 -6
- package/dist/lib/services/notification.service.d.ts.map +1 -1
- package/dist/lib/services/notification.service.js +123 -27
- package/dist/lib/services/notification.service.js.map +1 -1
- package/dist/lib/services/realtime-audio-recorder.d.ts +4 -4
- package/dist/lib/services/realtime-audio-recorder.js +12 -12
- package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
- package/dist/lib/services/realtime-pcm-wav.d.ts +4 -0
- package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -1
- package/dist/lib/services/realtime-pcm-wav.js +10 -2
- package/dist/lib/services/realtime-pcm-wav.js.map +1 -1
- package/dist/lib/services/search.service.d.ts +42 -9
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +103 -23
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/services/toast.service.d.ts +19 -1
- package/dist/lib/services/toast.service.d.ts.map +1 -1
- package/dist/lib/services/toast.service.js +45 -9
- package/dist/lib/services/toast.service.js.map +1 -1
- package/dist/lib/services/ui-command-handler.service.d.ts +56 -5
- package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
- package/dist/lib/services/ui-command-handler.service.js +113 -16
- package/dist/lib/services/ui-command-handler.service.js.map +1 -1
- package/dist/lib/utils/agent-artifact-summary.d.ts +2 -2
- package/dist/lib/utils/agent-artifact-summary.js +3 -3
- package/dist/lib/utils/agent-artifact-summary.js.map +1 -1
- package/dist/lib/utils/agent-turn-routing.d.ts +70 -0
- package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -0
- package/dist/lib/utils/agent-turn-routing.js +84 -0
- package/dist/lib/utils/agent-turn-routing.js.map +1 -0
- package/dist/lib/utils/artifact-panel-action.d.ts +15 -4
- package/dist/lib/utils/artifact-panel-action.d.ts.map +1 -1
- package/dist/lib/utils/artifact-panel-action.js +10 -2
- package/dist/lib/utils/artifact-panel-action.js.map +1 -1
- package/dist/lib/utils/decision-routing.d.ts +68 -0
- package/dist/lib/utils/decision-routing.d.ts.map +1 -0
- package/dist/lib/utils/decision-routing.js +101 -0
- package/dist/lib/utils/decision-routing.js.map +1 -0
- package/dist/lib/utils/distinct-artifacts.d.ts +2 -0
- package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
- package/dist/lib/utils/distinct-artifacts.js +5 -1
- package/dist/lib/utils/distinct-artifacts.js.map +1 -1
- package/dist/lib/utils/plan-mode-preference.d.ts +2 -2
- package/dist/lib/utils/plan-mode-preference.js +6 -6
- package/dist/lib/utils/plan-mode-preference.js.map +1 -1
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +8 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +32 -32
|
@@ -3,6 +3,9 @@ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
|
3
3
|
import { LogStatusEx } from '@memberjunction/core';
|
|
4
4
|
import { MJEnvironmentEntityExtended, ConversationEngine, UserInfoEngine, TaskGraphSubmitOperation } from '@memberjunction/core-entities';
|
|
5
5
|
import { BeforeAgentTurnEventArgs, AfterAgentTurnEventArgs } from '../../events/chat-events';
|
|
6
|
+
import { ResolveAgentTurn, FindDisallowedTaskGraphAgents } from '../../utils/agent-turn-routing';
|
|
7
|
+
import { ApplyRoutingDecision, ArtifactVersionForTurn, RunRoutingDecision, ShouldRunRoutingDecision } from '../../utils/decision-routing';
|
|
8
|
+
import { BuildRecentTurns, BuildRoutingArtifactVersions, CanAskRoutingDecision, CollectRoutingParticipants, IsAgentAllowed } from '@memberjunction/ai-core-plus';
|
|
6
9
|
import { GenerateAndApplyConversationName } from '../../services/conversation-naming';
|
|
7
10
|
import { AIEngineBase } from '@memberjunction/ai-engine-base';
|
|
8
11
|
import { ConversationUtility, agentFailureDisposition, agentFailureMessage } from '@memberjunction/ai-core-plus';
|
|
@@ -59,30 +62,202 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
59
62
|
realtimeSession;
|
|
60
63
|
// Default artifact type ID for JSON (when agent doesn't specify DefaultArtifactTypeID)
|
|
61
64
|
JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
ConversationId;
|
|
66
|
+
/** @deprecated Use {@link ConversationId}. */
|
|
67
|
+
set conversationId(value) {
|
|
68
|
+
this.ConversationId = value;
|
|
69
|
+
}
|
|
70
|
+
/** @deprecated Use {@link ConversationId}. */
|
|
71
|
+
get conversationId() {
|
|
72
|
+
return this.ConversationId;
|
|
73
|
+
}
|
|
74
|
+
ConversationName;
|
|
75
|
+
/** @deprecated Use {@link ConversationName}. */
|
|
76
|
+
set conversationName(value) {
|
|
77
|
+
this.ConversationName = value;
|
|
78
|
+
}
|
|
79
|
+
/** @deprecated Use {@link ConversationName}. */
|
|
80
|
+
get conversationName() {
|
|
81
|
+
return this.ConversationName;
|
|
82
|
+
} // For task tracking display
|
|
83
|
+
CurrentUser;
|
|
84
|
+
/** @deprecated Use {@link CurrentUser}. */
|
|
85
|
+
set currentUser(value) {
|
|
86
|
+
this.CurrentUser = value;
|
|
87
|
+
}
|
|
88
|
+
/** @deprecated Use {@link CurrentUser}. */
|
|
89
|
+
get currentUser() {
|
|
90
|
+
return this.CurrentUser;
|
|
91
|
+
}
|
|
92
|
+
Disabled = false;
|
|
93
|
+
/** @deprecated Use {@link Disabled}. */
|
|
94
|
+
set disabled(value) {
|
|
95
|
+
this.Disabled = value;
|
|
96
|
+
}
|
|
97
|
+
/** @deprecated Use {@link Disabled}. */
|
|
98
|
+
get disabled() {
|
|
99
|
+
return this.Disabled;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Blocks sending. Distinct from {@link Disabled}, which means the composer is busy.
|
|
103
|
+
*/
|
|
104
|
+
ReadOnly = false;
|
|
105
|
+
Placeholder = 'Type a message... (Ctrl+Enter to send)';
|
|
106
|
+
/** @deprecated Use {@link Placeholder}. */
|
|
107
|
+
set placeholder(value) {
|
|
108
|
+
this.Placeholder = value;
|
|
109
|
+
}
|
|
110
|
+
/** @deprecated Use {@link Placeholder}. */
|
|
111
|
+
get placeholder() {
|
|
112
|
+
return this.Placeholder;
|
|
113
|
+
}
|
|
114
|
+
ParentMessageId;
|
|
115
|
+
/** @deprecated Use {@link ParentMessageId}. */
|
|
116
|
+
set parentMessageId(value) {
|
|
117
|
+
this.ParentMessageId = value;
|
|
118
|
+
}
|
|
119
|
+
/** @deprecated Use {@link ParentMessageId}. */
|
|
120
|
+
get parentMessageId() {
|
|
121
|
+
return this.ParentMessageId;
|
|
122
|
+
} // Optional: for replying in threads
|
|
123
|
+
EnableAttachments = true;
|
|
124
|
+
/** @deprecated Use {@link EnableAttachments}. */
|
|
125
|
+
set enableAttachments(value) {
|
|
126
|
+
this.EnableAttachments = value;
|
|
127
|
+
}
|
|
128
|
+
/** @deprecated Use {@link EnableAttachments}. */
|
|
129
|
+
get enableAttachments() {
|
|
130
|
+
return this.EnableAttachments;
|
|
131
|
+
} // Whether to show attachment button (based on agent modality support)
|
|
132
|
+
EnableMentions = true;
|
|
133
|
+
/** @deprecated Use {@link EnableMentions}. */
|
|
134
|
+
set enableMentions(value) {
|
|
135
|
+
this.EnableMentions = value;
|
|
136
|
+
}
|
|
137
|
+
/** @deprecated Use {@link EnableMentions}. */
|
|
138
|
+
get enableMentions() {
|
|
139
|
+
return this.EnableMentions;
|
|
140
|
+
} // Whether to enable @-mention autocomplete (agents/users). Hosts addressing a single fixed agent (e.g. Form Builder cockpit) typically set false.
|
|
70
141
|
// Per-type caps under enableMentions (all default true) — forwarded to the AI composer's
|
|
71
142
|
// EnableAgentMentions/EnableEntityMentions/EnableSkillCommands. Let a host keep '/' skill
|
|
72
143
|
// commands while dropping '@' agent mentions (which would override a pinned default agent).
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
144
|
+
EnableAgentMentions = true;
|
|
145
|
+
/** @deprecated Use {@link EnableAgentMentions}. */
|
|
146
|
+
set enableAgentMentions(value) {
|
|
147
|
+
this.EnableAgentMentions = value;
|
|
148
|
+
}
|
|
149
|
+
/** @deprecated Use {@link EnableAgentMentions}. */
|
|
150
|
+
get enableAgentMentions() {
|
|
151
|
+
return this.EnableAgentMentions;
|
|
152
|
+
}
|
|
153
|
+
EnableEntityMentions = true;
|
|
154
|
+
/** @deprecated Use {@link EnableEntityMentions}. */
|
|
155
|
+
set enableEntityMentions(value) {
|
|
156
|
+
this.EnableEntityMentions = value;
|
|
157
|
+
}
|
|
158
|
+
/** @deprecated Use {@link EnableEntityMentions}. */
|
|
159
|
+
get enableEntityMentions() {
|
|
160
|
+
return this.EnableEntityMentions;
|
|
161
|
+
}
|
|
162
|
+
EnableSkillCommands = true;
|
|
163
|
+
/** @deprecated Use {@link EnableSkillCommands}. */
|
|
164
|
+
set enableSkillCommands(value) {
|
|
165
|
+
this.EnableSkillCommands = value;
|
|
166
|
+
}
|
|
167
|
+
/** @deprecated Use {@link EnableSkillCommands}. */
|
|
168
|
+
get enableSkillCommands() {
|
|
169
|
+
return this.EnableSkillCommands;
|
|
170
|
+
}
|
|
171
|
+
EnablePlanMode = true;
|
|
172
|
+
/** @deprecated Use {@link EnablePlanMode}. */
|
|
173
|
+
set enablePlanMode(value) {
|
|
174
|
+
this.EnablePlanMode = value;
|
|
175
|
+
}
|
|
176
|
+
/** @deprecated Use {@link EnablePlanMode}. */
|
|
177
|
+
get enablePlanMode() {
|
|
178
|
+
return this.EnablePlanMode;
|
|
179
|
+
} // Whether the composer shows the Plan Mode toggle. Hosts that don't expose plan-mode workflows set false.
|
|
180
|
+
EnableRealtime = true;
|
|
181
|
+
/** @deprecated Use {@link EnableRealtime}. */
|
|
182
|
+
set enableRealtime(value) {
|
|
183
|
+
this.EnableRealtime = value;
|
|
184
|
+
}
|
|
185
|
+
/** @deprecated Use {@link EnableRealtime}. */
|
|
186
|
+
get enableRealtime() {
|
|
187
|
+
return this.EnableRealtime;
|
|
188
|
+
} // Whether the composer shows the realtime voice-call launcher/options. Hosts without a voice experience set false.
|
|
189
|
+
MaxAttachments = 10;
|
|
190
|
+
/** @deprecated Use {@link MaxAttachments}. */
|
|
191
|
+
set maxAttachments(value) {
|
|
192
|
+
this.MaxAttachments = value;
|
|
193
|
+
}
|
|
194
|
+
/** @deprecated Use {@link MaxAttachments}. */
|
|
195
|
+
get maxAttachments() {
|
|
196
|
+
return this.MaxAttachments;
|
|
197
|
+
} // Maximum number of attachments per message
|
|
198
|
+
MaxAttachmentSizeBytes = 20 * 1024 * 1024;
|
|
199
|
+
/** @deprecated Use {@link MaxAttachmentSizeBytes}. */
|
|
200
|
+
set maxAttachmentSizeBytes(value) {
|
|
201
|
+
this.MaxAttachmentSizeBytes = value;
|
|
202
|
+
}
|
|
203
|
+
/** @deprecated Use {@link MaxAttachmentSizeBytes}. */
|
|
204
|
+
get maxAttachmentSizeBytes() {
|
|
205
|
+
return this.MaxAttachmentSizeBytes;
|
|
206
|
+
} // Maximum size per attachment (20MB default)
|
|
207
|
+
AcceptedFileTypes = 'image/*';
|
|
208
|
+
/** @deprecated Use {@link AcceptedFileTypes}. */
|
|
209
|
+
set acceptedFileTypes(value) {
|
|
210
|
+
this.AcceptedFileTypes = value;
|
|
211
|
+
}
|
|
212
|
+
/** @deprecated Use {@link AcceptedFileTypes}. */
|
|
213
|
+
get acceptedFileTypes() {
|
|
214
|
+
return this.AcceptedFileTypes;
|
|
215
|
+
} // Accepted MIME types pattern
|
|
216
|
+
ArtifactsByDetailId;
|
|
217
|
+
/** @deprecated Use {@link ArtifactsByDetailId}. */
|
|
218
|
+
set artifactsByDetailId(value) {
|
|
219
|
+
this.ArtifactsByDetailId = value;
|
|
220
|
+
}
|
|
221
|
+
/** @deprecated Use {@link ArtifactsByDetailId}. */
|
|
222
|
+
get artifactsByDetailId() {
|
|
223
|
+
return this.ArtifactsByDetailId;
|
|
224
|
+
} // Pre-loaded artifact data for performance
|
|
225
|
+
SystemArtifactsByDetailId;
|
|
226
|
+
/** @deprecated Use {@link SystemArtifactsByDetailId}. */
|
|
227
|
+
set systemArtifactsByDetailId(value) {
|
|
228
|
+
this.SystemArtifactsByDetailId = value;
|
|
229
|
+
}
|
|
230
|
+
/** @deprecated Use {@link SystemArtifactsByDetailId}. */
|
|
231
|
+
get systemArtifactsByDetailId() {
|
|
232
|
+
return this.SystemArtifactsByDetailId;
|
|
233
|
+
} // Pre-loaded system artifact data (Visibility='System Only')
|
|
234
|
+
AgentRunsByDetailId;
|
|
235
|
+
/** @deprecated Use {@link AgentRunsByDetailId}. */
|
|
236
|
+
set agentRunsByDetailId(value) {
|
|
237
|
+
this.AgentRunsByDetailId = value;
|
|
238
|
+
}
|
|
239
|
+
/** @deprecated Use {@link AgentRunsByDetailId}. */
|
|
240
|
+
get agentRunsByDetailId() {
|
|
241
|
+
return this.AgentRunsByDetailId;
|
|
242
|
+
} // Pre-loaded agent run data for performance
|
|
243
|
+
EmptyStateMode = false;
|
|
244
|
+
/** @deprecated Use {@link EmptyStateMode}. */
|
|
245
|
+
set emptyStateMode(value) {
|
|
246
|
+
this.EmptyStateMode = value;
|
|
247
|
+
}
|
|
248
|
+
/** @deprecated Use {@link EmptyStateMode}. */
|
|
249
|
+
get emptyStateMode() {
|
|
250
|
+
return this.EmptyStateMode;
|
|
251
|
+
} // When true, emits emptyStateSubmit instead of creating messages directly
|
|
252
|
+
AppContext = null;
|
|
253
|
+
/** @deprecated Use {@link AppContext}. */
|
|
254
|
+
set appContext(value) {
|
|
255
|
+
this.AppContext = value;
|
|
256
|
+
}
|
|
257
|
+
/** @deprecated Use {@link AppContext}. */
|
|
258
|
+
get appContext() {
|
|
259
|
+
return this.AppContext;
|
|
260
|
+
} // Application context for AI agent awareness
|
|
86
261
|
/**
|
|
87
262
|
* Plan Mode toggle state — sticky PER CONVERSATION, OFF by default (no behavior change unless
|
|
88
263
|
* the user turns it on). When on, the user's next message(s) in THIS conversation request Plan
|
|
@@ -99,7 +274,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
99
274
|
* conversation's flag OFF automatically (see message-item's plan-decision handling).
|
|
100
275
|
*/
|
|
101
276
|
get PlanModeEnabled() {
|
|
102
|
-
return PlanModePreference.IsEnabled(this.
|
|
277
|
+
return PlanModePreference.IsEnabled(this.ConversationId);
|
|
103
278
|
}
|
|
104
279
|
/**
|
|
105
280
|
* Skill IDs the user requested via `/skill-name` mentions in the message being routed. Collected
|
|
@@ -114,7 +289,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
114
289
|
* start of routing so the value is stable for the whole message dispatch.
|
|
115
290
|
*/
|
|
116
291
|
collectRequestedSkillIDs() {
|
|
117
|
-
const chipData = this.
|
|
292
|
+
const chipData = this.InputBox?.getMentionChipsData() || [];
|
|
118
293
|
return chipData.filter(chip => chip.type === 'skill').map(chip => chip.id);
|
|
119
294
|
}
|
|
120
295
|
/**
|
|
@@ -130,7 +305,15 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
130
305
|
* context and don't need Sage to route. Leave unset to preserve the
|
|
131
306
|
* standard Sage-fronted UX of the main Chat app.
|
|
132
307
|
*/
|
|
133
|
-
|
|
308
|
+
DefaultAgentId = null;
|
|
309
|
+
/** @deprecated Use {@link DefaultAgentId}. */
|
|
310
|
+
set defaultAgentId(value) {
|
|
311
|
+
this.DefaultAgentId = value;
|
|
312
|
+
}
|
|
313
|
+
/** @deprecated Use {@link DefaultAgentId}. */
|
|
314
|
+
get defaultAgentId() {
|
|
315
|
+
return this.DefaultAgentId;
|
|
316
|
+
}
|
|
134
317
|
/**
|
|
135
318
|
* Per-conversation pinned default agent — sourced from the loaded
|
|
136
319
|
* `MJConversationEntity.DefaultAgentID`. When set, this agent is used in
|
|
@@ -144,7 +327,15 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
144
327
|
* 4. defaultAgentId (embedder-supplied)
|
|
145
328
|
* 5. Sage fallback
|
|
146
329
|
*/
|
|
147
|
-
|
|
330
|
+
ConversationDefaultAgentId = null;
|
|
331
|
+
/** @deprecated Use {@link ConversationDefaultAgentId}. */
|
|
332
|
+
set conversationDefaultAgentId(value) {
|
|
333
|
+
this.ConversationDefaultAgentId = value;
|
|
334
|
+
}
|
|
335
|
+
/** @deprecated Use {@link ConversationDefaultAgentId}. */
|
|
336
|
+
get conversationDefaultAgentId() {
|
|
337
|
+
return this.ConversationDefaultAgentId;
|
|
338
|
+
}
|
|
148
339
|
/**
|
|
149
340
|
* The `MJ: AI Agent Configurations.ID` selected via the chat header's
|
|
150
341
|
* mode picker (Draft / Standard / High). Applied to **non-mention**
|
|
@@ -162,14 +353,22 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
162
353
|
* messages already in flight or already in history. Affects "what
|
|
163
354
|
* happens next."
|
|
164
355
|
*/
|
|
165
|
-
|
|
356
|
+
AgentConfigurationPresetId = null;
|
|
357
|
+
/** @deprecated Use {@link AgentConfigurationPresetId}. */
|
|
358
|
+
set agentConfigurationPresetId(value) {
|
|
359
|
+
this.AgentConfigurationPresetId = value;
|
|
360
|
+
}
|
|
361
|
+
/** @deprecated Use {@link AgentConfigurationPresetId}. */
|
|
362
|
+
get agentConfigurationPresetId() {
|
|
363
|
+
return this.AgentConfigurationPresetId;
|
|
364
|
+
}
|
|
166
365
|
// Initial message to send automatically - using getter/setter for precise control
|
|
167
366
|
_initialMessage = null;
|
|
168
367
|
_initialAttachments = null;
|
|
169
368
|
_isComponentReady = false; // Track if component is ready to send
|
|
170
369
|
/** Conversation this input has already auto-sent its pending first message for. */
|
|
171
370
|
_autoSentForConversationId = null;
|
|
172
|
-
set
|
|
371
|
+
set InitialMessage(value) {
|
|
173
372
|
// Handle case where an object with {text, attachments} is passed instead of just a string
|
|
174
373
|
// This can happen if there's a type mismatch in the binding chain
|
|
175
374
|
let actualValue = value;
|
|
@@ -183,31 +382,55 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
183
382
|
this.triggerInitialSend();
|
|
184
383
|
}
|
|
185
384
|
}
|
|
186
|
-
get
|
|
385
|
+
get InitialMessage() {
|
|
187
386
|
return this._initialMessage;
|
|
188
387
|
}
|
|
189
|
-
|
|
388
|
+
/** @deprecated Use {@link InitialMessage}. */
|
|
389
|
+
get initialMessage() {
|
|
390
|
+
return this.InitialMessage;
|
|
391
|
+
}
|
|
392
|
+
/** @deprecated Use {@link InitialMessage}. */
|
|
393
|
+
set initialMessage(value) {
|
|
394
|
+
this.InitialMessage = value;
|
|
395
|
+
}
|
|
396
|
+
set InitialAttachments(value) {
|
|
190
397
|
this._initialAttachments = value;
|
|
191
398
|
}
|
|
192
|
-
get
|
|
399
|
+
get InitialAttachments() {
|
|
193
400
|
return this._initialAttachments;
|
|
194
401
|
}
|
|
402
|
+
/** @deprecated Use {@link InitialAttachments}. */
|
|
403
|
+
get initialAttachments() {
|
|
404
|
+
return this.InitialAttachments;
|
|
405
|
+
}
|
|
406
|
+
/** @deprecated Use {@link InitialAttachments}. */
|
|
407
|
+
set initialAttachments(value) {
|
|
408
|
+
this.InitialAttachments = value;
|
|
409
|
+
}
|
|
195
410
|
_conversationHistory = [];
|
|
196
|
-
get
|
|
411
|
+
get ConversationHistory() {
|
|
197
412
|
return this._conversationHistory;
|
|
198
413
|
}
|
|
199
|
-
set
|
|
414
|
+
set ConversationHistory(value) {
|
|
200
415
|
this._conversationHistory = value;
|
|
201
416
|
}
|
|
417
|
+
/** @deprecated Use {@link ConversationHistory}. */
|
|
418
|
+
get conversationHistory() {
|
|
419
|
+
return this.ConversationHistory;
|
|
420
|
+
}
|
|
421
|
+
/** @deprecated Use {@link ConversationHistory}. */
|
|
422
|
+
set conversationHistory(value) {
|
|
423
|
+
this.ConversationHistory = value;
|
|
424
|
+
}
|
|
202
425
|
// Message IDs that are in-progress and need streaming reconnection
|
|
203
426
|
// Using getter/setter to react immediately when value changes (avoids timing issues with ngOnChanges)
|
|
204
427
|
_inProgressMessageIds;
|
|
205
|
-
set
|
|
428
|
+
set InProgressMessageIds(value) {
|
|
206
429
|
this._inProgressMessageIds = value;
|
|
207
430
|
// React immediately when input changes (after component initialized)
|
|
208
431
|
// This ensures callbacks are registered without relying on ngOnChanges timing
|
|
209
432
|
if (this.streamingService && value && value.length > 0) {
|
|
210
|
-
this.
|
|
433
|
+
this.ReconnectInProgressMessages();
|
|
211
434
|
}
|
|
212
435
|
else if (this.streamingService) {
|
|
213
436
|
// Empty/undefined — e.g. this input was backgrounded by a conversation swap
|
|
@@ -217,48 +440,251 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
217
440
|
this.unregisterAllCallbacks();
|
|
218
441
|
}
|
|
219
442
|
}
|
|
220
|
-
get
|
|
443
|
+
get InProgressMessageIds() {
|
|
221
444
|
return this._inProgressMessageIds;
|
|
222
445
|
}
|
|
446
|
+
/** @deprecated Use {@link InProgressMessageIds}. */
|
|
447
|
+
get inProgressMessageIds() {
|
|
448
|
+
return this.InProgressMessageIds;
|
|
449
|
+
}
|
|
450
|
+
/** @deprecated Use {@link InProgressMessageIds}. */
|
|
451
|
+
set inProgressMessageIds(value) {
|
|
452
|
+
this.InProgressMessageIds = value;
|
|
453
|
+
}
|
|
223
454
|
/**
|
|
224
455
|
* Application context for the current conversation. Threaded through from the
|
|
225
456
|
* chat-area component for inclusion in the {@link beforeAgentTurn} event payload —
|
|
226
457
|
* lets listeners reason about which app's chat surface is invoking the agent.
|
|
227
458
|
* Optional; defaults to null for surfaces with no app context.
|
|
228
459
|
*/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
460
|
+
ApplicationId = null;
|
|
461
|
+
/** @deprecated Use {@link ApplicationId}. */
|
|
462
|
+
set applicationId(value) {
|
|
463
|
+
this.ApplicationId = value;
|
|
464
|
+
}
|
|
465
|
+
/** @deprecated Use {@link ApplicationId}. */
|
|
466
|
+
get applicationId() {
|
|
467
|
+
return this.ApplicationId;
|
|
468
|
+
}
|
|
469
|
+
// ── Host rules for agent turns ──────────────────────────────────────────
|
|
470
|
+
// All opt-in. Left at their defaults, every message is answered exactly as before.
|
|
471
|
+
/**
|
|
472
|
+
* When a message starts an agent turn. `'Always'` (the default) answers every message, as
|
|
473
|
+
* before. `'MentionOnly'` answers only a message that tags an agent; any other message is
|
|
474
|
+
* posted with no turn at all — no reply row, no placeholder, no turn events — which is what a
|
|
475
|
+
* chat between several people needs.
|
|
476
|
+
*/
|
|
477
|
+
AgentReplyMode = 'Always';
|
|
478
|
+
/**
|
|
479
|
+
* The agents that may answer here. Narrows the composer's '@' list, every route (a tagged
|
|
480
|
+
* agent, continuity, the pinned and host default agents, the conversation manager) and the
|
|
481
|
+
* conversation manager's delegation, including each agent step of a workflow it plans. A tagged
|
|
482
|
+
* agent outside the list is ignored. Null (the default) allows every agent the user can run;
|
|
483
|
+
* an empty list allows none.
|
|
484
|
+
*/
|
|
485
|
+
AllowedAgentIDs = null;
|
|
486
|
+
/**
|
|
487
|
+
* The people the composer's '@' list offers, such as the chat's members. Null (the default)
|
|
488
|
+
* offers only the current user, as before.
|
|
489
|
+
*/
|
|
490
|
+
MentionPeople = null;
|
|
491
|
+
/**
|
|
492
|
+
* The first moment of the conversation an agent turn may read. The server loads the agent's
|
|
493
|
+
* history from there and uses no summary of earlier messages; the run's own reads of the
|
|
494
|
+
* conversation (history tools, attached artifacts, the previous output it continues from)
|
|
495
|
+
* start there too, and so does the continuity route, which considers only replies written at
|
|
496
|
+
* or after it. Sent to the server only when set, so leaving it null keeps working against an
|
|
497
|
+
* MJAPI that predates it (one that predates it rejects a turn that sets it, rather than
|
|
498
|
+
* reading past the floor). Null (the default) reads the whole conversation.
|
|
499
|
+
*/
|
|
500
|
+
AgentHistoryFrom = null;
|
|
501
|
+
/**
|
|
502
|
+
* Runs agent turns on the host's server instead of MJ's own path. The chat area still picks
|
|
503
|
+
* the agent and fires {@link BeforeAgentTurn}; it then calls the handler once per turn, before
|
|
504
|
+
* any reply row exists, and shows the rows the handler reports. Null (the default) runs turns
|
|
505
|
+
* on MJ's path.
|
|
506
|
+
*/
|
|
507
|
+
AgentTurnHandler = null;
|
|
508
|
+
/**
|
|
509
|
+
* Whether MJ names a new conversation from its first message. True (the default) keeps
|
|
510
|
+
* today's behavior; a host that names its own chats turns it off.
|
|
511
|
+
*/
|
|
512
|
+
AutoNameConversation = true;
|
|
513
|
+
/**
|
|
514
|
+
* Whether an unmentioned message asks one typed decision which agent in the conversation should
|
|
515
|
+
* answer it, instead of always going back to the last agent that answered. The same call can
|
|
516
|
+
* name the artifact version the message modifies. The answer replaces continuity only when it
|
|
517
|
+
* is confident and arrives in time; an error, a slow or unsure answer keeps today's routing, and
|
|
518
|
+
* a tagged message or a form response makes no call (see `decision-routing.ts`). False (the
|
|
519
|
+
* default) changes nothing: no call, and today's routing.
|
|
520
|
+
*
|
|
521
|
+
* What goes to the model: each qualifying message sends these to the model behind the
|
|
522
|
+
* `Default Decision` prompt, which can be a different vendor from the agents' own: the first
|
|
523
|
+
* 1,000 characters of the new message, the last 6 turns (150 characters each), each participant's
|
|
524
|
+
* name, description and last reply, and the names of their artifact versions. Each call writes an
|
|
525
|
+
* `MJ: AI Prompt Runs` row, even when the answer comes too late to be used.
|
|
526
|
+
*/
|
|
527
|
+
EnableDecisionRouting = false;
|
|
528
|
+
MessageSent = new EventEmitter();
|
|
529
|
+
/**
|
|
530
|
+
* @deprecated Use {@link MessageSent}.
|
|
531
|
+
*
|
|
532
|
+
* The same emitter under the old binding name, so a template still binding
|
|
533
|
+
* (messageSent) keeps working. Must stay AFTER MessageSent: class fields
|
|
534
|
+
* initialise in order, and the other way round this captures undefined.
|
|
535
|
+
*/
|
|
536
|
+
messageSent = this.MessageSent;
|
|
537
|
+
AgentResponse = new EventEmitter();
|
|
538
|
+
/**
|
|
539
|
+
* @deprecated Use {@link AgentResponse}.
|
|
540
|
+
*
|
|
541
|
+
* The same emitter under the old binding name, so a template still binding
|
|
542
|
+
* (agentResponse) keeps working. Must stay AFTER AgentResponse: class fields
|
|
543
|
+
* initialise in order, and the other way round this captures undefined.
|
|
544
|
+
*/
|
|
545
|
+
agentResponse = this.AgentResponse;
|
|
546
|
+
/**
|
|
547
|
+
* Cancelable — fired once per agent turn, on every route, after routing has picked the agent
|
|
548
|
+
* and BEFORE any reply row exists. Listeners may set `event.Cancel = true` to stop the turn
|
|
549
|
+
* (nothing more is written, and {@link AfterAgentTurn} does not fire) or set
|
|
550
|
+
* `event.RedirectAgentId` to send it to another allowed agent. A message that starts no turn
|
|
551
|
+
* fires nothing. Follows MJ's established Before/After cancelable event pattern.
|
|
552
|
+
*/
|
|
553
|
+
BeforeAgentTurn = new EventEmitter();
|
|
554
|
+
/**
|
|
555
|
+
* @deprecated Use {@link BeforeAgentTurn}.
|
|
556
|
+
*
|
|
557
|
+
* The same emitter under the old binding name, so a template still binding
|
|
558
|
+
* (beforeAgentTurn) keeps working. Must stay AFTER BeforeAgentTurn: class fields
|
|
559
|
+
* initialise in order, and the other way round this captures undefined.
|
|
560
|
+
*/
|
|
561
|
+
beforeAgentTurn = this.BeforeAgentTurn;
|
|
232
562
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* {@link afterAgentTurn} event is NOT fired and the running task is cleared.
|
|
237
|
-
* Follows MJ's established Before/After cancelable event pattern.
|
|
563
|
+
* Fired AFTER a successful agent turn, on every route. Carries the agent run id and the
|
|
564
|
+
* full agent result. Not fired when {@link BeforeAgentTurn} was canceled, when the turn
|
|
565
|
+
* failed, or when a host {@link AgentTurnHandler} reported no result.
|
|
238
566
|
*/
|
|
239
|
-
|
|
567
|
+
AfterAgentTurn = new EventEmitter();
|
|
240
568
|
/**
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* the
|
|
569
|
+
* @deprecated Use {@link AfterAgentTurn}.
|
|
570
|
+
*
|
|
571
|
+
* The same emitter under the old binding name, so a template still binding
|
|
572
|
+
* (afterAgentTurn) keeps working. Must stay AFTER AfterAgentTurn: class fields
|
|
573
|
+
* initialise in order, and the other way round this captures undefined.
|
|
244
574
|
*/
|
|
245
|
-
afterAgentTurn =
|
|
575
|
+
afterAgentTurn = this.AfterAgentTurn;
|
|
246
576
|
// conversationId is carried on every agent-lifecycle event so the parent chat-area can drop
|
|
247
577
|
// events emitted by a BACKGROUND conversation's (hidden, still-streaming) input after the user
|
|
248
578
|
// has swapped conversations — preventing cross-conversation state/cache bleed. Sourced from the
|
|
249
579
|
// ConversationDetail entity's ConversationID (the captured, immutable value), never this.conversationId.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
580
|
+
AgentRunDetected = new EventEmitter();
|
|
581
|
+
/**
|
|
582
|
+
* @deprecated Use {@link AgentRunDetected}.
|
|
583
|
+
*
|
|
584
|
+
* The same emitter under the old binding name, so a template still binding
|
|
585
|
+
* (agentRunDetected) keeps working. Must stay AFTER AgentRunDetected: class fields
|
|
586
|
+
* initialise in order, and the other way round this captures undefined.
|
|
587
|
+
*/
|
|
588
|
+
agentRunDetected = this.AgentRunDetected;
|
|
589
|
+
AgentRunUpdate = new EventEmitter();
|
|
590
|
+
/**
|
|
591
|
+
* @deprecated Use {@link AgentRunUpdate}.
|
|
592
|
+
*
|
|
593
|
+
* The same emitter under the old binding name, so a template still binding
|
|
594
|
+
* (agentRunUpdate) keeps working. Must stay AFTER AgentRunUpdate: class fields
|
|
595
|
+
* initialise in order, and the other way round this captures undefined.
|
|
596
|
+
*/
|
|
597
|
+
agentRunUpdate = this.AgentRunUpdate; // Emits when agent run data updates during progress
|
|
598
|
+
MessageComplete = new EventEmitter();
|
|
599
|
+
/**
|
|
600
|
+
* @deprecated Use {@link MessageComplete}.
|
|
601
|
+
*
|
|
602
|
+
* The same emitter under the old binding name, so a template still binding
|
|
603
|
+
* (messageComplete) keeps working. Must stay AFTER MessageComplete: class fields
|
|
604
|
+
* initialise in order, and the other way round this captures undefined.
|
|
605
|
+
*/
|
|
606
|
+
messageComplete = this.MessageComplete; // Emits when message completes (success or error)
|
|
607
|
+
ArtifactCreated = new EventEmitter();
|
|
608
|
+
/**
|
|
609
|
+
* @deprecated Use {@link ArtifactCreated}.
|
|
610
|
+
*
|
|
611
|
+
* The same emitter under the old binding name, so a template still binding
|
|
612
|
+
* (artifactCreated) keeps working. Must stay AFTER ArtifactCreated: class fields
|
|
613
|
+
* initialise in order, and the other way round this captures undefined.
|
|
614
|
+
*/
|
|
615
|
+
artifactCreated = this.ArtifactCreated;
|
|
616
|
+
ConversationRenamed = new EventEmitter();
|
|
617
|
+
/**
|
|
618
|
+
* @deprecated Use {@link ConversationRenamed}.
|
|
619
|
+
*
|
|
620
|
+
* The same emitter under the old binding name, so a template still binding
|
|
621
|
+
* (conversationRenamed) keeps working. Must stay AFTER ConversationRenamed: class fields
|
|
622
|
+
* initialise in order, and the other way round this captures undefined.
|
|
623
|
+
*/
|
|
624
|
+
conversationRenamed = this.ConversationRenamed;
|
|
625
|
+
IntentCheckStarted = new EventEmitter();
|
|
626
|
+
/**
|
|
627
|
+
* @deprecated Use {@link IntentCheckStarted}.
|
|
628
|
+
*
|
|
629
|
+
* The same emitter under the old binding name, so a template still binding
|
|
630
|
+
* (intentCheckStarted) keeps working. Must stay AFTER IntentCheckStarted: class fields
|
|
631
|
+
* initialise in order, and the other way round this captures undefined.
|
|
632
|
+
*/
|
|
633
|
+
intentCheckStarted = this.IntentCheckStarted; // Emits when intent checking starts
|
|
634
|
+
IntentCheckCompleted = new EventEmitter();
|
|
635
|
+
/**
|
|
636
|
+
* @deprecated Use {@link IntentCheckCompleted}.
|
|
637
|
+
*
|
|
638
|
+
* The same emitter under the old binding name, so a template still binding
|
|
639
|
+
* (intentCheckCompleted) keeps working. Must stay AFTER IntentCheckCompleted: class fields
|
|
640
|
+
* initialise in order, and the other way round this captures undefined.
|
|
641
|
+
*/
|
|
642
|
+
intentCheckCompleted = this.IntentCheckCompleted; // Emits when intent checking completes (carries conversationId so the parent can drop a background conversation's completion after a swap — symmetric with intentCheckStarted)
|
|
643
|
+
InitialMessageAutoSendStarted = new EventEmitter();
|
|
644
|
+
/**
|
|
645
|
+
* @deprecated Use {@link InitialMessageAutoSendStarted}.
|
|
646
|
+
*
|
|
647
|
+
* The same emitter under the old binding name, so a template still binding
|
|
648
|
+
* (initialMessageAutoSendStarted) keeps working. Must stay AFTER InitialMessageAutoSendStarted: class fields
|
|
649
|
+
* initialise in order, and the other way round this captures undefined.
|
|
650
|
+
*/
|
|
651
|
+
initialMessageAutoSendStarted = this.InitialMessageAutoSendStarted; // Emitted when this input latches the pending first message for auto-send
|
|
652
|
+
InitialMessageAutoSendFailed = new EventEmitter();
|
|
653
|
+
/**
|
|
654
|
+
* @deprecated Use {@link InitialMessageAutoSendFailed}.
|
|
655
|
+
*
|
|
656
|
+
* The same emitter under the old binding name, so a template still binding
|
|
657
|
+
* (initialMessageAutoSendFailed) keeps working. Must stay AFTER InitialMessageAutoSendFailed: class fields
|
|
658
|
+
* initialise in order, and the other way round this captures undefined.
|
|
659
|
+
*/
|
|
660
|
+
initialMessageAutoSendFailed = this.InitialMessageAutoSendFailed; // Emitted when a latched pending first message fails before messageSent
|
|
661
|
+
EmptyStateSubmit = new EventEmitter();
|
|
662
|
+
/**
|
|
663
|
+
* @deprecated Use {@link EmptyStateSubmit}.
|
|
664
|
+
*
|
|
665
|
+
* The same emitter under the old binding name, so a template still binding
|
|
666
|
+
* (emptyStateSubmit) keeps working. Must stay AFTER EmptyStateSubmit: class fields
|
|
667
|
+
* initialise in order, and the other way round this captures undefined.
|
|
668
|
+
*/
|
|
669
|
+
emptyStateSubmit = this.EmptyStateSubmit; // Emitted when in emptyStateMode
|
|
670
|
+
UploadStateChanged = new EventEmitter();
|
|
671
|
+
/**
|
|
672
|
+
* @deprecated Use {@link UploadStateChanged}.
|
|
673
|
+
*
|
|
674
|
+
* The same emitter under the old binding name, so a template still binding
|
|
675
|
+
* (uploadStateChanged) keeps working. Must stay AFTER UploadStateChanged: class fields
|
|
676
|
+
* initialise in order, and the other way round this captures undefined.
|
|
677
|
+
*/
|
|
678
|
+
uploadStateChanged = this.UploadStateChanged; // Emits when attachment upload state changes
|
|
679
|
+
InputBox;
|
|
680
|
+
/** @deprecated Use {@link InputBox}. */
|
|
681
|
+
get inputBox() {
|
|
682
|
+
return this.InputBox;
|
|
683
|
+
}
|
|
684
|
+
/** @deprecated Use {@link InputBox}. */
|
|
685
|
+
set inputBox(value) {
|
|
686
|
+
this.InputBox = value;
|
|
687
|
+
}
|
|
262
688
|
_messageText = '';
|
|
263
689
|
/**
|
|
264
690
|
* The composer's text. An accessor pair rather than a plain field because every write reaches the
|
|
@@ -274,13 +700,21 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
274
700
|
* Read is a plain field read; there is no two-way `ngModel` on this property (the template binds
|
|
275
701
|
* `[value]="messageText"` one-way), so the pair is transparent to callers.
|
|
276
702
|
*/
|
|
277
|
-
get
|
|
703
|
+
get MessageText() {
|
|
278
704
|
return this._messageText;
|
|
279
705
|
}
|
|
280
|
-
set
|
|
706
|
+
set MessageText(value) {
|
|
281
707
|
this._messageText = value;
|
|
282
708
|
this.mentionedAgentId = undefined;
|
|
283
709
|
}
|
|
710
|
+
/** @deprecated Use {@link MessageText}. */
|
|
711
|
+
get messageText() {
|
|
712
|
+
return this.MessageText;
|
|
713
|
+
}
|
|
714
|
+
/** @deprecated Use {@link MessageText}. */
|
|
715
|
+
set messageText(value) {
|
|
716
|
+
this.MessageText = value;
|
|
717
|
+
}
|
|
284
718
|
/**
|
|
285
719
|
* Prefills the composer with draft text WITHOUT sending (unlike pendingMessage,
|
|
286
720
|
* which auto-sends) and focuses the input — e.g. the omnibar's '@agent' flow
|
|
@@ -292,24 +726,40 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
292
726
|
* view isn't up yet, ngAfterViewInit applies it. Emits initialDraftApplied so the
|
|
293
727
|
* host can clear its pending state.
|
|
294
728
|
*/
|
|
295
|
-
set
|
|
729
|
+
set InitialDraft(value) {
|
|
296
730
|
if (value && value !== this.appliedInitialDraft) {
|
|
297
731
|
this.appliedInitialDraft = value;
|
|
298
|
-
if (this.
|
|
732
|
+
if (this.InputBox) {
|
|
299
733
|
this.SetDraft(value, true);
|
|
300
|
-
this.
|
|
734
|
+
this.InitialDraftApplied.emit();
|
|
301
735
|
}
|
|
302
736
|
else {
|
|
303
737
|
this.pendingInitialDraft = value;
|
|
304
738
|
}
|
|
305
739
|
}
|
|
306
740
|
}
|
|
307
|
-
get
|
|
741
|
+
get InitialDraft() {
|
|
308
742
|
return this.appliedInitialDraft;
|
|
309
743
|
}
|
|
744
|
+
/** @deprecated Use {@link InitialDraft}. */
|
|
745
|
+
get initialDraft() {
|
|
746
|
+
return this.InitialDraft;
|
|
747
|
+
}
|
|
748
|
+
/** @deprecated Use {@link InitialDraft}. */
|
|
749
|
+
set initialDraft(value) {
|
|
750
|
+
this.InitialDraft = value;
|
|
751
|
+
}
|
|
310
752
|
appliedInitialDraft = null;
|
|
311
753
|
pendingInitialDraft = null;
|
|
312
|
-
|
|
754
|
+
InitialDraftApplied = new EventEmitter();
|
|
755
|
+
/**
|
|
756
|
+
* @deprecated Use {@link InitialDraftApplied}.
|
|
757
|
+
*
|
|
758
|
+
* The same emitter under the old binding name, so a template still binding
|
|
759
|
+
* (initialDraftApplied) keeps working. Must stay AFTER InitialDraftApplied: class fields
|
|
760
|
+
* initialise in order, and the other way round this captures undefined.
|
|
761
|
+
*/
|
|
762
|
+
initialDraftApplied = this.InitialDraftApplied;
|
|
313
763
|
/**
|
|
314
764
|
* Live draft-state signal: fires on every composer value change with the
|
|
315
765
|
* SERIALIZED content (mention pills encoded via getPlainTextWithJsonMentions,
|
|
@@ -320,12 +770,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
320
770
|
ComposerBlurred = new EventEmitter();
|
|
321
771
|
/** Handles the composer's value stream: keeps messageText in sync + emits draft state. */
|
|
322
772
|
OnComposerValueChanged(value) {
|
|
323
|
-
this.
|
|
773
|
+
this.MessageText = value;
|
|
324
774
|
this.DraftStateChanged.emit(this.GetSerializedDraft());
|
|
325
775
|
}
|
|
326
776
|
/** Current composer content in the lossless serialized form ('' when empty). */
|
|
327
777
|
GetSerializedDraft() {
|
|
328
|
-
const serialized = this.
|
|
778
|
+
const serialized = this.InputBox?.getPlainTextWithJsonMentions() ?? this.MessageText ?? '';
|
|
329
779
|
return serialized.trim().length === 0 ? '' : serialized;
|
|
330
780
|
}
|
|
331
781
|
/**
|
|
@@ -338,27 +788,27 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
338
788
|
* @returns false while the composer view isn't mounted yet — callers may retry.
|
|
339
789
|
*/
|
|
340
790
|
async InsertAgentMention(agentName, focus = true, clearExisting = true) {
|
|
341
|
-
if (!this.
|
|
791
|
+
if (!this.InputBox) {
|
|
342
792
|
console.log(`[Omnibar→Chat] InsertAgentMention('${agentName}'): input box not mounted yet — caller will retry`);
|
|
343
793
|
return false;
|
|
344
794
|
}
|
|
345
795
|
if (clearExisting) {
|
|
346
796
|
// Pre-addressing REPLACES any un-sent draft (tagging agent B after agent A
|
|
347
797
|
// must not stack pills).
|
|
348
|
-
this.
|
|
349
|
-
this.
|
|
798
|
+
this.InputBox.mentionEditor?.clear();
|
|
799
|
+
this.MessageText = '';
|
|
350
800
|
}
|
|
351
801
|
try {
|
|
352
|
-
if (!this.mentionAutocomplete.IsInitialized && this.
|
|
802
|
+
if (!this.mentionAutocomplete.IsInitialized && this.CurrentUser) {
|
|
353
803
|
console.log(`[Omnibar→Chat] InsertAgentMention('${agentName}'): initializing mention autocomplete…`);
|
|
354
|
-
await this.mentionAutocomplete.initialize(this.
|
|
804
|
+
await this.mentionAutocomplete.initialize(this.CurrentUser);
|
|
355
805
|
}
|
|
356
806
|
const wanted = agentName.trim().toLowerCase();
|
|
357
807
|
const suggestion = this.mentionAutocomplete
|
|
358
808
|
.getSuggestions(agentName, false, '@')
|
|
359
809
|
.find(s => s.type === 'agent' && s.name.trim().toLowerCase() === wanted);
|
|
360
810
|
if (suggestion) {
|
|
361
|
-
const inserted = this.
|
|
811
|
+
const inserted = this.InputBox.InsertMention(suggestion, focus);
|
|
362
812
|
console.log(`[Omnibar→Chat] InsertAgentMention('${agentName}'): resolved to pill (id=${suggestion.id}) — insert ${inserted ? 'OK' : 'FAILED (editor view not ready)'}`);
|
|
363
813
|
if (inserted) {
|
|
364
814
|
if (focus) {
|
|
@@ -385,7 +835,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
385
835
|
scheduleFocusReassert(context) {
|
|
386
836
|
for (const delay of [300, 900, 1800]) {
|
|
387
837
|
setTimeout(() => {
|
|
388
|
-
const editor = this.
|
|
838
|
+
const editor = this.InputBox?.mentionEditor;
|
|
389
839
|
if (editor && !editor.HasFocus) {
|
|
390
840
|
const ok = editor.FocusCaretAtEnd();
|
|
391
841
|
console.log(`[Omnibar→Chat] focus re-assert (+${delay}ms) for '${context}': ${ok ? 'refocused' : 'editor gone'}`);
|
|
@@ -394,18 +844,66 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
394
844
|
}
|
|
395
845
|
}
|
|
396
846
|
SetDraft(text, focus = true) {
|
|
397
|
-
this.
|
|
847
|
+
this.MessageText = text;
|
|
398
848
|
if (focus) {
|
|
399
849
|
// The composer mounts/binds on the next tick after messageText flows down.
|
|
400
|
-
setTimeout(() => this.
|
|
401
|
-
}
|
|
850
|
+
setTimeout(() => this.InputBox?.focus(), 50);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
IsSending = false;
|
|
854
|
+
/** @deprecated Use {@link IsSending}. */
|
|
855
|
+
get isSending() {
|
|
856
|
+
return this.IsSending;
|
|
857
|
+
}
|
|
858
|
+
/** @deprecated Use {@link IsSending}. */
|
|
859
|
+
set isSending(value) {
|
|
860
|
+
this.IsSending = value;
|
|
861
|
+
}
|
|
862
|
+
IsProcessing = false;
|
|
863
|
+
/** @deprecated Use {@link IsProcessing}. */
|
|
864
|
+
get isProcessing() {
|
|
865
|
+
return this.IsProcessing;
|
|
866
|
+
}
|
|
867
|
+
/** @deprecated Use {@link IsProcessing}. */
|
|
868
|
+
set isProcessing(value) {
|
|
869
|
+
this.IsProcessing = value;
|
|
870
|
+
} // True when waiting for agent/naming response
|
|
871
|
+
ProcessingMessage = 'AI is responding...';
|
|
872
|
+
/** @deprecated Use {@link ProcessingMessage}. */
|
|
873
|
+
get processingMessage() {
|
|
874
|
+
return this.ProcessingMessage;
|
|
875
|
+
}
|
|
876
|
+
/** @deprecated Use {@link ProcessingMessage}. */
|
|
877
|
+
set processingMessage(value) {
|
|
878
|
+
this.ProcessingMessage = value;
|
|
879
|
+
} // Message shown during processing
|
|
880
|
+
IsUploadingAttachments = false;
|
|
881
|
+
/** @deprecated Use {@link IsUploadingAttachments}. */
|
|
882
|
+
get isUploadingAttachments() {
|
|
883
|
+
return this.IsUploadingAttachments;
|
|
884
|
+
}
|
|
885
|
+
/** @deprecated Use {@link IsUploadingAttachments}. */
|
|
886
|
+
set isUploadingAttachments(value) {
|
|
887
|
+
this.IsUploadingAttachments = value;
|
|
888
|
+
} // True when uploading attachments to server
|
|
889
|
+
UploadingMessage = 'Uploading attachments...';
|
|
890
|
+
/** @deprecated Use {@link UploadingMessage}. */
|
|
891
|
+
get uploadingMessage() {
|
|
892
|
+
return this.UploadingMessage;
|
|
893
|
+
}
|
|
894
|
+
/** @deprecated Use {@link UploadingMessage}. */
|
|
895
|
+
set uploadingMessage(value) {
|
|
896
|
+
this.UploadingMessage = value;
|
|
897
|
+
} // Message shown during upload
|
|
898
|
+
ConverationManagerAgent = null;
|
|
899
|
+
/** @deprecated Use {@link ConverationManagerAgent}. */
|
|
900
|
+
get converationManagerAgent() {
|
|
901
|
+
return this.ConverationManagerAgent;
|
|
902
|
+
}
|
|
903
|
+
/** @deprecated Use {@link ConverationManagerAgent}. */
|
|
904
|
+
set converationManagerAgent(value) {
|
|
905
|
+
this.ConverationManagerAgent = value;
|
|
402
906
|
}
|
|
403
|
-
isSending = false;
|
|
404
|
-
isProcessing = false; // True when waiting for agent/naming response
|
|
405
|
-
processingMessage = 'AI is responding...'; // Message shown during processing
|
|
406
|
-
isUploadingAttachments = false; // True when uploading attachments to server
|
|
407
|
-
uploadingMessage = 'Uploading attachments...'; // Message shown during upload
|
|
408
|
-
converationManagerAgent = null;
|
|
409
907
|
// Track completion timestamps to prevent race conditions with late progress updates
|
|
410
908
|
completionTimestamps = new Map();
|
|
411
909
|
// Track registered streaming callbacks for cleanup
|
|
@@ -437,7 +935,15 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
437
935
|
}
|
|
438
936
|
// ── Voice session (Realtime Co-Agent) ───────────────────────────────
|
|
439
937
|
/** True while a live voice session is active — drives the overlay + mic state. */
|
|
440
|
-
|
|
938
|
+
VoiceActive = false;
|
|
939
|
+
/** @deprecated Use {@link VoiceActive}. */
|
|
940
|
+
get voiceActive() {
|
|
941
|
+
return this.VoiceActive;
|
|
942
|
+
}
|
|
943
|
+
/** @deprecated Use {@link VoiceActive}. */
|
|
944
|
+
set voiceActive(value) {
|
|
945
|
+
this.VoiceActive = value;
|
|
946
|
+
}
|
|
441
947
|
realtimeActiveSub;
|
|
442
948
|
async ngOnInit() {
|
|
443
949
|
// Bind provider-aware services to this component's provider.
|
|
@@ -449,16 +955,16 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
449
955
|
this.realtimeSession.Provider = p;
|
|
450
956
|
// Reflect the live voice-session Active flag into a local field for the template.
|
|
451
957
|
this.realtimeActiveSub = this.realtimeSession.Active$.subscribe(active => {
|
|
452
|
-
this.
|
|
958
|
+
this.VoiceActive = active;
|
|
453
959
|
});
|
|
454
|
-
this.
|
|
960
|
+
this.ConverationManagerAgent = await this.agentService.getConversationManagerAgent();
|
|
455
961
|
// Warm UserInfoEngine so the PlanModeEnabled getter has the cached settings available
|
|
456
962
|
// (no-op when already loaded; failure just leaves the toggle at its default OFF).
|
|
457
963
|
PlanModePreference.Warm();
|
|
458
964
|
// Initialize mention autocomplete (needed for parsing mentions in messages)
|
|
459
|
-
await this.mentionAutocomplete.initialize(this.
|
|
965
|
+
await this.mentionAutocomplete.initialize(this.CurrentUser);
|
|
460
966
|
// Reconnect to any in-progress messages for streaming updates (via global streaming service)
|
|
461
|
-
this.
|
|
967
|
+
this.ReconnectInProgressMessages();
|
|
462
968
|
}
|
|
463
969
|
ngOnChanges(changes) {
|
|
464
970
|
// When conversation changes, focus the input
|
|
@@ -474,7 +980,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
474
980
|
// next tick — the composer's own view finishes mounting first
|
|
475
981
|
setTimeout(() => {
|
|
476
982
|
this.SetDraft(draft, true);
|
|
477
|
-
this.
|
|
983
|
+
this.InitialDraftApplied.emit();
|
|
478
984
|
}, 50);
|
|
479
985
|
}
|
|
480
986
|
// Focus input on initial load
|
|
@@ -491,26 +997,29 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
491
997
|
* Called from setter or ngAfterViewInit when conditions are met.
|
|
492
998
|
*/
|
|
493
999
|
triggerInitialSend() {
|
|
1000
|
+
if (this.ReadOnly) {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
494
1003
|
const message = this._initialMessage;
|
|
495
1004
|
const attachments = this._initialAttachments;
|
|
496
1005
|
const hasContent = !!message || !!(attachments && attachments.length > 0);
|
|
497
|
-
if (!hasContent || !this.
|
|
1006
|
+
if (!hasContent || !this.ConversationId || UUIDsEqual(this._autoSentForConversationId, this.ConversationId)) {
|
|
498
1007
|
return;
|
|
499
1008
|
}
|
|
500
|
-
this._autoSentForConversationId = this.
|
|
1009
|
+
this._autoSentForConversationId = this.ConversationId;
|
|
501
1010
|
// Set pending attachments before sending
|
|
502
1011
|
if (attachments && attachments.length > 0) {
|
|
503
1012
|
this.pendingAttachments = [...attachments];
|
|
504
1013
|
}
|
|
505
1014
|
Promise.resolve().then(() => {
|
|
506
|
-
this.
|
|
1015
|
+
this.InitialMessageAutoSendStarted.emit({ conversationId: this.ConversationId });
|
|
507
1016
|
});
|
|
508
1017
|
// Use setTimeout to ensure we're outside of change detection cycle
|
|
509
1018
|
setTimeout(async () => {
|
|
510
|
-
const sent = await this.
|
|
1019
|
+
const sent = await this.SendMessageWithText(message || '');
|
|
511
1020
|
if (!sent) {
|
|
512
1021
|
this._autoSentForConversationId = null;
|
|
513
|
-
this.
|
|
1022
|
+
this.InitialMessageAutoSendFailed.emit({ conversationId: this.ConversationId });
|
|
514
1023
|
}
|
|
515
1024
|
}, 100);
|
|
516
1025
|
}
|
|
@@ -531,14 +1040,17 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
531
1040
|
* 2. per-conversation pinned default
|
|
532
1041
|
* 3. embedder-supplied default
|
|
533
1042
|
* 4. Sage fallback
|
|
534
|
-
*
|
|
1043
|
+
* skipping any agent outside {@link AllowedAgentIDs}. {@link AgentReplyMode} does not apply:
|
|
1044
|
+
* starting a call is itself a choice to talk to an agent. Returns null when Sage failed to
|
|
1045
|
+
* load, or when the host allows none of these agents.
|
|
535
1046
|
*/
|
|
1047
|
+
ResolveCurrentAgentId() {
|
|
1048
|
+
const target = ResolveAgentTurn(this.agentTurnCandidates([]), { ReplyMode: 'Always', AllowedAgentIDs: this.AllowedAgentIDs }, () => true);
|
|
1049
|
+
return target?.AgentId ?? null;
|
|
1050
|
+
}
|
|
1051
|
+
/** @deprecated Use {@link ResolveCurrentAgentId}. */
|
|
536
1052
|
resolveCurrentAgentId() {
|
|
537
|
-
return this.
|
|
538
|
-
?? this.conversationDefaultAgentId
|
|
539
|
-
?? this.defaultAgentId
|
|
540
|
-
?? this.converationManagerAgent?.ID
|
|
541
|
-
?? null;
|
|
1053
|
+
return this.ResolveCurrentAgentId();
|
|
542
1054
|
}
|
|
543
1055
|
/**
|
|
544
1056
|
* The agent the '/' skill picker should narrow to. Mirrors routing's priority: an explicit
|
|
@@ -546,12 +1058,16 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
546
1058
|
* would otherwise go to ({@link resolveCurrentAgentId}). Bound to `mj-ai-composer`'s
|
|
547
1059
|
* `TargetAgentId`; null = unknown, no narrowing.
|
|
548
1060
|
*/
|
|
549
|
-
get
|
|
1061
|
+
get PickerTargetAgentId() {
|
|
550
1062
|
if (this.mentionedAgentId === undefined) {
|
|
551
|
-
const chips = this.
|
|
1063
|
+
const chips = this.InputBox?.getMentionChipsData() || [];
|
|
552
1064
|
this.mentionedAgentId = chips.find(chip => chip.type === 'agent')?.id ?? null;
|
|
553
1065
|
}
|
|
554
|
-
return this.mentionedAgentId ?? this.
|
|
1066
|
+
return this.mentionedAgentId ?? this.ResolveCurrentAgentId();
|
|
1067
|
+
}
|
|
1068
|
+
/** @deprecated Use {@link PickerTargetAgentId}. */
|
|
1069
|
+
get pickerTargetAgentId() {
|
|
1070
|
+
return this.PickerTargetAgentId;
|
|
555
1071
|
}
|
|
556
1072
|
/**
|
|
557
1073
|
* Memo for the first `@agent` chip in the draft, so the template-bound
|
|
@@ -582,8 +1098,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
582
1098
|
*/
|
|
583
1099
|
mentionedAgentId = undefined;
|
|
584
1100
|
/** True when the mic button should be enabled (have an agent + not disabled). */
|
|
1101
|
+
get CanStartRealtime() {
|
|
1102
|
+
return !this.ReadOnly && !this.Disabled && !this.VoiceActive && !!this.ResolveCurrentAgentId();
|
|
1103
|
+
}
|
|
1104
|
+
/** @deprecated Use {@link CanStartRealtime}. */
|
|
585
1105
|
get canStartRealtime() {
|
|
586
|
-
return
|
|
1106
|
+
return this.CanStartRealtime;
|
|
587
1107
|
}
|
|
588
1108
|
/**
|
|
589
1109
|
* Display name of the agent the voice session fronts. Resolved here (this component
|
|
@@ -591,7 +1111,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
591
1111
|
* session start so the chat-area-hosted overlay can read it from the service.
|
|
592
1112
|
*/
|
|
593
1113
|
resolveRealtimeAgentName() {
|
|
594
|
-
const agentId = this.
|
|
1114
|
+
const agentId = this.ResolveCurrentAgentId();
|
|
595
1115
|
if (agentId) {
|
|
596
1116
|
const match = this.mentionAutocomplete
|
|
597
1117
|
.getAvailableAgents()
|
|
@@ -600,10 +1120,18 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
600
1120
|
return match.Name;
|
|
601
1121
|
}
|
|
602
1122
|
}
|
|
603
|
-
return this.
|
|
1123
|
+
return this.ConverationManagerAgent?.Name ?? 'Sage';
|
|
604
1124
|
}
|
|
605
1125
|
/** True while the "Start a voice call with…" agent picker popover is open. */
|
|
606
|
-
|
|
1126
|
+
ShowRealtimeAgentPicker = false;
|
|
1127
|
+
/** @deprecated Use {@link ShowRealtimeAgentPicker}. */
|
|
1128
|
+
get showRealtimeAgentPicker() {
|
|
1129
|
+
return this.ShowRealtimeAgentPicker;
|
|
1130
|
+
}
|
|
1131
|
+
/** @deprecated Use {@link ShowRealtimeAgentPicker}. */
|
|
1132
|
+
set showRealtimeAgentPicker(value) {
|
|
1133
|
+
this.ShowRealtimeAgentPicker = value;
|
|
1134
|
+
}
|
|
607
1135
|
/**
|
|
608
1136
|
* CDK connected-overlay positions for the voice agent picker. Preferred: open UPWARD,
|
|
609
1137
|
* right edge aligned to the composer's right edge (matching the old absolute placement).
|
|
@@ -611,40 +1139,79 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
611
1139
|
* body-level CDK overlay container (with `cdkConnectedOverlayPush`), it escapes the chat
|
|
612
1140
|
* overlay's `overflow: hidden` border and can never clip at the top of a narrow overlay.
|
|
613
1141
|
*/
|
|
614
|
-
|
|
1142
|
+
PickerOverlayPositions = [
|
|
615
1143
|
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -8 },
|
|
616
1144
|
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 8 },
|
|
617
1145
|
];
|
|
1146
|
+
/** @deprecated Use {@link PickerOverlayPositions}. */
|
|
1147
|
+
get pickerOverlayPositions() {
|
|
1148
|
+
return this.PickerOverlayPositions;
|
|
1149
|
+
}
|
|
618
1150
|
/**
|
|
619
1151
|
* `MJ: User Settings` key persisting the user's co-agent choice for realtime calls
|
|
620
1152
|
* (server-side, cross-device — never localStorage). Stored shape: `{"coAgentId":
|
|
621
1153
|
* string | null}` — `null` is an explicit "Auto" choice that overwrites an older pick.
|
|
622
1154
|
*/
|
|
623
|
-
static
|
|
1155
|
+
static coAgentPrefKey = 'mj.realtimeVoice.coAgent.v1';
|
|
624
1156
|
/**
|
|
625
1157
|
* The persisted co-agent preference, loaded just before the picker opens (and read by
|
|
626
1158
|
* the instant-start path). `null` = no preference / explicit "Auto".
|
|
627
1159
|
*/
|
|
628
|
-
|
|
1160
|
+
VoicePickerDefaultCoAgentId = null;
|
|
1161
|
+
/** @deprecated Use {@link VoicePickerDefaultCoAgentId}. */
|
|
1162
|
+
get voicePickerDefaultCoAgentId() {
|
|
1163
|
+
return this.VoicePickerDefaultCoAgentId;
|
|
1164
|
+
}
|
|
1165
|
+
/** @deprecated Use {@link VoicePickerDefaultCoAgentId}. */
|
|
1166
|
+
set voicePickerDefaultCoAgentId(value) {
|
|
1167
|
+
this.VoicePickerDefaultCoAgentId = value;
|
|
1168
|
+
}
|
|
629
1169
|
/**
|
|
630
1170
|
* Agents the voice picker offers — the same cached set the @mention
|
|
631
|
-
* autocomplete and {@link resolveRealtimeAgentName} use,
|
|
632
|
-
* never offer an agent the
|
|
633
|
-
|
|
1171
|
+
* autocomplete and {@link resolveRealtimeAgentName} use, narrowed to
|
|
1172
|
+
* {@link AllowedAgentIDs}, so the picker can never offer an agent the
|
|
1173
|
+
* conversation couldn't otherwise route to.
|
|
1174
|
+
*/
|
|
1175
|
+
get VoicePickerAgents() {
|
|
1176
|
+
const agents = this.mentionAutocomplete.getAvailableAgents();
|
|
1177
|
+
const allowed = this.AllowedAgentIDs;
|
|
1178
|
+
if (allowed == null) {
|
|
1179
|
+
return agents;
|
|
1180
|
+
}
|
|
1181
|
+
// Memoized on both inputs: the picker binds this getter, and a fresh array on every
|
|
1182
|
+
// change-detection pass would look like a new input each time.
|
|
1183
|
+
const memo = this.voicePickerAgentsMemo;
|
|
1184
|
+
if (memo && memo.Source === agents && memo.Allowed === allowed) {
|
|
1185
|
+
return memo.Result;
|
|
1186
|
+
}
|
|
1187
|
+
const result = agents.filter(agent => IsAgentAllowed(agent.ID, allowed));
|
|
1188
|
+
this.voicePickerAgentsMemo = { Source: agents, Allowed: allowed, Result: result };
|
|
1189
|
+
return result;
|
|
1190
|
+
}
|
|
1191
|
+
voicePickerAgentsMemo = null;
|
|
1192
|
+
/** @deprecated Use {@link VoicePickerAgents}. */
|
|
634
1193
|
get voicePickerAgents() {
|
|
635
|
-
return this.
|
|
1194
|
+
return this.VoicePickerAgents;
|
|
636
1195
|
}
|
|
637
1196
|
/**
|
|
638
1197
|
* The ACTIVE Realtime-type co-agent candidates — the same run-permission-filtered
|
|
639
1198
|
* cached set as {@link voicePickerAgents}, narrowed to the Realtime agent type. The
|
|
640
1199
|
* picker shows its co-agent selector only when more than one exists.
|
|
641
1200
|
*/
|
|
642
|
-
get
|
|
1201
|
+
get VoicePickerCoAgents() {
|
|
643
1202
|
return FilterRealtimeCoAgents(this.mentionAutocomplete.getAvailableAgents());
|
|
644
1203
|
}
|
|
1204
|
+
/** @deprecated Use {@link VoicePickerCoAgents}. */
|
|
1205
|
+
get voicePickerCoAgents() {
|
|
1206
|
+
return this.VoicePickerCoAgents;
|
|
1207
|
+
}
|
|
645
1208
|
/** The agent the default resolution would call — preselected in the picker. */
|
|
1209
|
+
get VoicePickerDefaultAgentId() {
|
|
1210
|
+
return this.ResolveCurrentAgentId();
|
|
1211
|
+
}
|
|
1212
|
+
/** @deprecated Use {@link VoicePickerDefaultAgentId}. */
|
|
646
1213
|
get voicePickerDefaultAgentId() {
|
|
647
|
-
return this.
|
|
1214
|
+
return this.VoicePickerDefaultAgentId;
|
|
648
1215
|
}
|
|
649
1216
|
/**
|
|
650
1217
|
* Start a real-time voice session fronting the conversation's current agent.
|
|
@@ -660,18 +1227,18 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
660
1227
|
* the user never chose — so show a compact agent picker instead and start
|
|
661
1228
|
* with whichever agent they pick.
|
|
662
1229
|
*/
|
|
663
|
-
async
|
|
664
|
-
if (!this.
|
|
1230
|
+
async OnStartRealtime() {
|
|
1231
|
+
if (!this.CanStartRealtime) {
|
|
665
1232
|
return;
|
|
666
1233
|
}
|
|
667
1234
|
// New/empty conversation (no prior agent turn): let the user choose who
|
|
668
1235
|
// to call. Falls through to the immediate path if the agent cache is
|
|
669
1236
|
// empty (nothing to pick from — the resolved default is the only option).
|
|
670
|
-
if (!this.findLastNonSageAgentId() && this.
|
|
1237
|
+
if (!this.findLastNonSageAgentId() && this.VoicePickerAgents.length > 0) {
|
|
671
1238
|
await this.openRealtimeAgentPicker();
|
|
672
1239
|
return;
|
|
673
1240
|
}
|
|
674
|
-
const targetAgentId = this.
|
|
1241
|
+
const targetAgentId = this.ResolveCurrentAgentId();
|
|
675
1242
|
if (!targetAgentId) {
|
|
676
1243
|
this.toastService.error('No agent available for a voice session.');
|
|
677
1244
|
return;
|
|
@@ -679,6 +1246,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
679
1246
|
const coAgentId = await this.resolveInstantCoAgentId(targetAgentId);
|
|
680
1247
|
await this.startRealtimeWithAgent(targetAgentId, this.resolveRealtimeAgentName(), null, coAgentId);
|
|
681
1248
|
}
|
|
1249
|
+
/** @deprecated Use {@link OnStartRealtime}. */
|
|
1250
|
+
async onStartRealtime() {
|
|
1251
|
+
return this.OnStartRealtime();
|
|
1252
|
+
}
|
|
682
1253
|
/**
|
|
683
1254
|
* Caret-next-to-the-phone click: open the agent/co-agent/model picker ON DEMAND, even
|
|
684
1255
|
* when the conversation already has agent history (where the plain phone click
|
|
@@ -687,27 +1258,35 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
687
1258
|
* choice one click away. Falls through to the instant path when there is nothing to
|
|
688
1259
|
* pick from.
|
|
689
1260
|
*/
|
|
690
|
-
async
|
|
691
|
-
if (!this.
|
|
1261
|
+
async OnRealtimeOptions() {
|
|
1262
|
+
if (!this.CanStartRealtime) {
|
|
692
1263
|
return;
|
|
693
1264
|
}
|
|
694
|
-
if (this.
|
|
1265
|
+
if (this.VoicePickerAgents.length > 0) {
|
|
695
1266
|
await this.openRealtimeAgentPicker();
|
|
696
1267
|
return;
|
|
697
1268
|
}
|
|
698
|
-
void this.
|
|
1269
|
+
void this.OnStartRealtime();
|
|
1270
|
+
}
|
|
1271
|
+
/** @deprecated Use {@link OnRealtimeOptions}. */
|
|
1272
|
+
async onRealtimeOptions() {
|
|
1273
|
+
return this.OnRealtimeOptions();
|
|
699
1274
|
}
|
|
700
1275
|
/** Loads the persisted co-agent preference, then shows the picker (pref preselected). */
|
|
701
1276
|
async openRealtimeAgentPicker() {
|
|
702
|
-
this.
|
|
703
|
-
this.
|
|
1277
|
+
this.VoicePickerDefaultCoAgentId = await this.loadPersistedCoAgentId();
|
|
1278
|
+
this.ShowRealtimeAgentPicker = true;
|
|
704
1279
|
}
|
|
705
1280
|
/** User confirmed an agent (+ optional co-agent / voice model) in the voice picker — start the call. */
|
|
706
|
-
async
|
|
707
|
-
this.
|
|
1281
|
+
async OnRealtimeAgentPicked(pick) {
|
|
1282
|
+
this.ShowRealtimeAgentPicker = false;
|
|
708
1283
|
this.persistCoAgentChoice(pick.CoAgentId);
|
|
709
1284
|
await this.startRealtimeWithAgent(pick.Agent.ID, pick.Agent.Name || this.resolveRealtimeAgentName(), pick.PreferredModelId, pick.CoAgentId, BuildRealtimeConfigOverridesJson(pick.PreferredModelId, pick.PreferredVoice), pick.RecordingConsent);
|
|
710
1285
|
}
|
|
1286
|
+
/** @deprecated Use {@link OnRealtimeAgentPicked}. */
|
|
1287
|
+
async onRealtimeAgentPicked(pick) {
|
|
1288
|
+
return this.OnRealtimeAgentPicked(pick);
|
|
1289
|
+
}
|
|
711
1290
|
/**
|
|
712
1291
|
* Reads the persisted co-agent preference from `MJ: User Settings` (via
|
|
713
1292
|
* `UserInfoEngine`'s cached settings). Defensive: any failure or malformed payload
|
|
@@ -716,7 +1295,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
716
1295
|
async loadPersistedCoAgentId() {
|
|
717
1296
|
try {
|
|
718
1297
|
await UserInfoEngine.Instance.Config();
|
|
719
|
-
const raw = UserInfoEngine.Instance.GetSetting(MessageInputComponent.
|
|
1298
|
+
const raw = UserInfoEngine.Instance.GetSetting(MessageInputComponent.coAgentPrefKey);
|
|
720
1299
|
if (!raw) {
|
|
721
1300
|
return null;
|
|
722
1301
|
}
|
|
@@ -730,7 +1309,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
730
1309
|
/** Persists the user's co-agent choice (including explicit "Auto" = null) cross-device. */
|
|
731
1310
|
persistCoAgentChoice(coAgentId) {
|
|
732
1311
|
try {
|
|
733
|
-
UserInfoEngine.Instance.SetSettingDebounced(MessageInputComponent.
|
|
1312
|
+
UserInfoEngine.Instance.SetSettingDebounced(MessageInputComponent.coAgentPrefKey, JSON.stringify({ coAgentId: coAgentId ?? null }));
|
|
734
1313
|
}
|
|
735
1314
|
catch (error) {
|
|
736
1315
|
console.warn('[MessageInput] Failed to persist co-agent preference:', error);
|
|
@@ -749,7 +1328,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
749
1328
|
if (!preferred) {
|
|
750
1329
|
return null;
|
|
751
1330
|
}
|
|
752
|
-
const isValidCandidate = this.
|
|
1331
|
+
const isValidCandidate = this.VoicePickerCoAgents.some(a => UUIDsEqual(a.ID, preferred));
|
|
753
1332
|
if (!isValidCandidate) {
|
|
754
1333
|
return null;
|
|
755
1334
|
}
|
|
@@ -757,8 +1336,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
757
1336
|
return PairingsAllowTarget(pairings, targetAgentId) ? preferred : null;
|
|
758
1337
|
}
|
|
759
1338
|
/** User dismissed the voice picker without starting a call. */
|
|
1339
|
+
OnRealtimeAgentPickerCancelled() {
|
|
1340
|
+
this.ShowRealtimeAgentPicker = false;
|
|
1341
|
+
}
|
|
1342
|
+
/** @deprecated Use {@link OnRealtimeAgentPickerCancelled}. */
|
|
760
1343
|
onRealtimeAgentPickerCancelled() {
|
|
761
|
-
this.
|
|
1344
|
+
return this.OnRealtimeAgentPickerCancelled();
|
|
762
1345
|
}
|
|
763
1346
|
/**
|
|
764
1347
|
* Shared session-start path for both the immediate (existing conversation)
|
|
@@ -777,11 +1360,11 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
777
1360
|
*/
|
|
778
1361
|
async startRealtimeWithAgent(agentId, agentName, preferredModelId, coAgentId, configOverridesJson, recordingConsent) {
|
|
779
1362
|
try {
|
|
780
|
-
await this.realtimeSession.StartRealtimeSession(agentId, this.
|
|
1363
|
+
await this.realtimeSession.StartRealtimeSession(agentId, this.ConversationId, null, agentName, preferredModelId ?? null, null, coAgentId ?? null, configOverridesJson ?? null, recordingConsent ?? null, null,
|
|
781
1364
|
// App awareness: the app the session runs in + the live app-context snapshot (where the
|
|
782
1365
|
// user is, what they see, capability manifest) — drives the server-side app cascade + the
|
|
783
1366
|
// mint-time prompt injection, and seeds the ClientContextChannel's streaming.
|
|
784
|
-
this.
|
|
1367
|
+
this.ApplicationId, this.AppContext);
|
|
785
1368
|
}
|
|
786
1369
|
catch (error) {
|
|
787
1370
|
console.error('Failed to start voice session:', error);
|
|
@@ -794,8 +1377,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
794
1377
|
focusInput() {
|
|
795
1378
|
// Use setTimeout to ensure DOM is ready
|
|
796
1379
|
setTimeout(() => {
|
|
797
|
-
if (this.
|
|
798
|
-
this.
|
|
1380
|
+
if (this.InputBox) {
|
|
1381
|
+
this.InputBox.focus();
|
|
799
1382
|
}
|
|
800
1383
|
}, 100);
|
|
801
1384
|
}
|
|
@@ -807,14 +1390,14 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
807
1390
|
* 3. User returns to a conversation with in-progress messages
|
|
808
1391
|
* 4. Parent component explicitly triggers reconnection
|
|
809
1392
|
*/
|
|
810
|
-
|
|
811
|
-
if (!this.
|
|
1393
|
+
ReconnectInProgressMessages() {
|
|
1394
|
+
if (!this.InProgressMessageIds || this.InProgressMessageIds.length === 0) {
|
|
812
1395
|
return;
|
|
813
1396
|
}
|
|
814
1397
|
// Unregister any previously registered callbacks for this component
|
|
815
1398
|
this.unregisterAllCallbacks();
|
|
816
1399
|
// Register new callbacks for each in-progress message
|
|
817
|
-
for (const messageId of this.
|
|
1400
|
+
for (const messageId of this.InProgressMessageIds) {
|
|
818
1401
|
// Create callback bound to this message ID
|
|
819
1402
|
const callback = this.createMessageProgressCallback(messageId);
|
|
820
1403
|
// Store reference for cleanup
|
|
@@ -823,6 +1406,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
823
1406
|
this.streamingService.registerMessageCallback(messageId, callback);
|
|
824
1407
|
}
|
|
825
1408
|
}
|
|
1409
|
+
/** @deprecated Use {@link ReconnectInProgressMessages}. */
|
|
1410
|
+
reconnectInProgressMessages() {
|
|
1411
|
+
return this.ReconnectInProgressMessages();
|
|
1412
|
+
}
|
|
826
1413
|
/**
|
|
827
1414
|
* Create a progress callback for a specific message ID.
|
|
828
1415
|
* This callback will be invoked by the streaming service when progress updates arrive.
|
|
@@ -835,7 +1422,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
835
1422
|
return async (progress) => {
|
|
836
1423
|
try {
|
|
837
1424
|
// Get message from cache (single source of truth)
|
|
838
|
-
const message = (resolvedMessage ??= await this.dataCache.getConversationDetail(messageId, this.
|
|
1425
|
+
const message = (resolvedMessage ??= await this.dataCache.getConversationDetail(messageId, this.CurrentUser));
|
|
839
1426
|
if (!message) {
|
|
840
1427
|
console.warn(`[StreamingCallback] Message ${messageId} not found in cache`);
|
|
841
1428
|
return;
|
|
@@ -857,7 +1444,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
857
1444
|
// bubble with the server-saved final message, so no append/merge is needed here.
|
|
858
1445
|
if (progress.streaming) {
|
|
859
1446
|
message.Message = progress.streaming.content;
|
|
860
|
-
this.
|
|
1447
|
+
this.MessageSent.emit(message);
|
|
861
1448
|
// Keep the tasks dropdown on a stable status line rather than the growing reply text.
|
|
862
1449
|
this.activeTasks.updateStatusByConversationDetailId(message.ID, 'Responding…');
|
|
863
1450
|
return;
|
|
@@ -875,7 +1462,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
875
1462
|
// Server now saves progress - client only updates in-memory and emits for UI
|
|
876
1463
|
// (Prevents race condition where client's late save overwrites server's final Status)
|
|
877
1464
|
// CRITICAL: Emit update to trigger UI refresh
|
|
878
|
-
this.
|
|
1465
|
+
this.MessageSent.emit(message);
|
|
879
1466
|
// CRITICAL: Update ActiveTasksService to keep the tasks dropdown in sync
|
|
880
1467
|
this.activeTasks.updateStatusByConversationDetailId(message.ID, progress.message);
|
|
881
1468
|
console.log(`[StreamingCallback] Updated message ${messageId}: ${progress.taskName || 'Agent'}`);
|
|
@@ -899,25 +1486,40 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
899
1486
|
}
|
|
900
1487
|
this.registeredCallbacks.clear();
|
|
901
1488
|
}
|
|
1489
|
+
get CanSend() {
|
|
1490
|
+
return !this.ReadOnly && !this.Disabled && !this.IsSending && this.MessageText.trim().length > 0;
|
|
1491
|
+
}
|
|
1492
|
+
/** @deprecated Use {@link CanSend}. */
|
|
902
1493
|
get canSend() {
|
|
903
|
-
return
|
|
1494
|
+
return this.CanSend;
|
|
904
1495
|
}
|
|
905
1496
|
/**
|
|
906
1497
|
* Handle attachments changed from the input box
|
|
907
1498
|
*/
|
|
908
|
-
|
|
1499
|
+
OnAttachmentsChanged(attachments) {
|
|
909
1500
|
this.pendingAttachments = attachments;
|
|
910
1501
|
}
|
|
1502
|
+
/** @deprecated Use {@link OnAttachmentsChanged}. */
|
|
1503
|
+
onAttachmentsChanged(attachments) {
|
|
1504
|
+
return this.OnAttachmentsChanged(attachments);
|
|
1505
|
+
}
|
|
911
1506
|
/**
|
|
912
1507
|
* Handle attachment errors from the input box
|
|
913
1508
|
*/
|
|
914
|
-
|
|
1509
|
+
OnAttachmentError(error) {
|
|
915
1510
|
this.toastService.error(error);
|
|
916
1511
|
}
|
|
1512
|
+
/** @deprecated Use {@link OnAttachmentError}. */
|
|
1513
|
+
onAttachmentError(error) {
|
|
1514
|
+
return this.OnAttachmentError(error);
|
|
1515
|
+
}
|
|
917
1516
|
/**
|
|
918
1517
|
* Handle text submitted from the input box
|
|
919
1518
|
*/
|
|
920
|
-
async
|
|
1519
|
+
async OnTextSubmitted(text) {
|
|
1520
|
+
if (this.ReadOnly) {
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
921
1523
|
// Check if we have either text or attachments
|
|
922
1524
|
const hasText = text && text.trim().length > 0;
|
|
923
1525
|
const hasAttachments = this.pendingAttachments.length > 0;
|
|
@@ -925,14 +1527,14 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
925
1527
|
return;
|
|
926
1528
|
}
|
|
927
1529
|
// In empty state mode, just emit the data and let parent handle conversation creation
|
|
928
|
-
if (this.
|
|
1530
|
+
if (this.EmptyStateMode) {
|
|
929
1531
|
const attachmentsToEmit = [...this.pendingAttachments];
|
|
930
1532
|
this.pendingAttachments = [];
|
|
931
|
-
this.
|
|
932
|
-
this.
|
|
1533
|
+
this.MessageText = '';
|
|
1534
|
+
this.EmptyStateSubmit.emit({ text: text?.trim() || '', attachments: attachmentsToEmit });
|
|
933
1535
|
return;
|
|
934
1536
|
}
|
|
935
|
-
this.
|
|
1537
|
+
this.IsSending = true;
|
|
936
1538
|
// Store attachments locally since we'll clear them after send
|
|
937
1539
|
const attachmentsToSave = [...this.pendingAttachments];
|
|
938
1540
|
try {
|
|
@@ -944,12 +1546,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
944
1546
|
// separately when building AI messages - no need to add tokens to Message field
|
|
945
1547
|
if (attachmentsToSave.length > 0) {
|
|
946
1548
|
// Show upload indicator for attachments
|
|
947
|
-
this.
|
|
948
|
-
this.
|
|
949
|
-
this.
|
|
1549
|
+
this.IsUploadingAttachments = true;
|
|
1550
|
+
this.UploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
|
|
1551
|
+
this.UploadStateChanged.emit({ isUploading: true, message: this.UploadingMessage });
|
|
950
1552
|
let attachmentRejection = null;
|
|
951
1553
|
try {
|
|
952
|
-
await this.attachmentService.saveAttachments(messageDetail.ID, attachmentsToSave, this.
|
|
1554
|
+
await this.attachmentService.saveAttachments(messageDetail.ID, attachmentsToSave, this.CurrentUser);
|
|
953
1555
|
}
|
|
954
1556
|
catch (attachmentError) {
|
|
955
1557
|
console.error('Failed to save attachments:', attachmentError);
|
|
@@ -958,8 +1560,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
958
1560
|
: 'Some attachments could not be saved';
|
|
959
1561
|
}
|
|
960
1562
|
finally {
|
|
961
|
-
this.
|
|
962
|
-
this.
|
|
1563
|
+
this.IsUploadingAttachments = false;
|
|
1564
|
+
this.UploadStateChanged.emit({ isUploading: false, message: '' });
|
|
963
1565
|
}
|
|
964
1566
|
// Plan §6: when attachments are rejected, the message itself must
|
|
965
1567
|
// not go through. Roll back the ConversationDetail and notify the
|
|
@@ -974,7 +1576,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
974
1576
|
catch (rollbackErr) {
|
|
975
1577
|
console.error('Failed to roll back conversation detail after attachment rejection:', rollbackErr);
|
|
976
1578
|
}
|
|
977
|
-
this.
|
|
1579
|
+
this.IsSending = false;
|
|
978
1580
|
return;
|
|
979
1581
|
}
|
|
980
1582
|
}
|
|
@@ -990,9 +1592,13 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
990
1592
|
this.handleSendError(error);
|
|
991
1593
|
}
|
|
992
1594
|
finally {
|
|
993
|
-
this.
|
|
1595
|
+
this.IsSending = false;
|
|
994
1596
|
}
|
|
995
1597
|
}
|
|
1598
|
+
/** @deprecated Use {@link OnTextSubmitted}. */
|
|
1599
|
+
async onTextSubmitted(text) {
|
|
1600
|
+
return this.OnTextSubmitted(text);
|
|
1601
|
+
}
|
|
996
1602
|
/**
|
|
997
1603
|
* Toggle sticky Plan Mode for THIS conversation (or the pending-new bucket on the
|
|
998
1604
|
* new-conversation composer). Writes through {@link PlanModePreference} — the
|
|
@@ -1001,12 +1607,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1001
1607
|
* until the user turns it off or approves a plan.
|
|
1002
1608
|
*/
|
|
1003
1609
|
TogglePlanMode() {
|
|
1004
|
-
PlanModePreference.Set(this.
|
|
1610
|
+
PlanModePreference.Set(this.ConversationId, !this.PlanModeEnabled);
|
|
1005
1611
|
}
|
|
1006
|
-
async
|
|
1007
|
-
if (!this.
|
|
1612
|
+
async OnSend() {
|
|
1613
|
+
if (!this.CanSend)
|
|
1008
1614
|
return;
|
|
1009
|
-
this.
|
|
1615
|
+
this.IsSending = true;
|
|
1010
1616
|
try {
|
|
1011
1617
|
const messageDetail = await this.createMessageDetail();
|
|
1012
1618
|
const saved = await messageDetail.Save();
|
|
@@ -1021,9 +1627,13 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1021
1627
|
this.handleSendError(error);
|
|
1022
1628
|
}
|
|
1023
1629
|
finally {
|
|
1024
|
-
this.
|
|
1630
|
+
this.IsSending = false;
|
|
1025
1631
|
}
|
|
1026
1632
|
}
|
|
1633
|
+
/** @deprecated Use {@link OnSend}. */
|
|
1634
|
+
async onSend() {
|
|
1635
|
+
return this.OnSend();
|
|
1636
|
+
}
|
|
1027
1637
|
/**
|
|
1028
1638
|
* Send a message with custom text WITHOUT modifying the visible messageText input
|
|
1029
1639
|
* Used for suggested responses and initial messages from empty state.
|
|
@@ -1035,7 +1645,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1035
1645
|
* `this.pendingAttachments` may not contain the attachment. Pass it in
|
|
1036
1646
|
* explicitly and we merge + dedupe (by `id`) before saving.
|
|
1037
1647
|
*/
|
|
1038
|
-
async
|
|
1648
|
+
async SendMessageWithText(text, extraAttachments) {
|
|
1649
|
+
if (this.ReadOnly) {
|
|
1650
|
+
return false;
|
|
1651
|
+
}
|
|
1039
1652
|
const merged = (() => {
|
|
1040
1653
|
if (!extraAttachments || extraAttachments.length === 0) {
|
|
1041
1654
|
return [...this.pendingAttachments];
|
|
@@ -1055,31 +1668,31 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1055
1668
|
if (!hasText && !hasAttachments) {
|
|
1056
1669
|
return false;
|
|
1057
1670
|
}
|
|
1058
|
-
if (this.
|
|
1671
|
+
if (this.IsSending) {
|
|
1059
1672
|
return false;
|
|
1060
1673
|
}
|
|
1061
|
-
this.
|
|
1674
|
+
this.IsSending = true;
|
|
1062
1675
|
const attachmentsToSave = merged;
|
|
1063
1676
|
try {
|
|
1064
|
-
const detail = await this.dataCache.createConversationDetail(this.
|
|
1065
|
-
detail.ConversationID = this.
|
|
1677
|
+
const detail = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1678
|
+
detail.ConversationID = this.ConversationId;
|
|
1066
1679
|
detail.Message = text?.trim() || '';
|
|
1067
1680
|
detail.Role = 'User';
|
|
1068
|
-
detail.UserID = this.
|
|
1069
|
-
if (this.
|
|
1070
|
-
detail.ParentID = this.
|
|
1681
|
+
detail.UserID = this.CurrentUser.ID; // Set the user who sent the message
|
|
1682
|
+
if (this.ParentMessageId) {
|
|
1683
|
+
detail.ParentID = this.ParentMessageId;
|
|
1071
1684
|
}
|
|
1072
1685
|
const saved = await detail.Save();
|
|
1073
1686
|
if (saved) {
|
|
1074
1687
|
// Save attachments if any were pending
|
|
1075
1688
|
if (attachmentsToSave.length > 0) {
|
|
1076
1689
|
// Show upload indicator for attachments
|
|
1077
|
-
this.
|
|
1078
|
-
this.
|
|
1079
|
-
this.
|
|
1690
|
+
this.IsUploadingAttachments = true;
|
|
1691
|
+
this.UploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
|
|
1692
|
+
this.UploadStateChanged.emit({ isUploading: true, message: this.UploadingMessage });
|
|
1080
1693
|
let attachmentRejection = null;
|
|
1081
1694
|
try {
|
|
1082
|
-
await this.attachmentService.saveAttachments(detail.ID, attachmentsToSave, this.
|
|
1695
|
+
await this.attachmentService.saveAttachments(detail.ID, attachmentsToSave, this.CurrentUser);
|
|
1083
1696
|
}
|
|
1084
1697
|
catch (attachmentError) {
|
|
1085
1698
|
console.error('Failed to save attachments:', attachmentError);
|
|
@@ -1088,8 +1701,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1088
1701
|
: 'Some attachments could not be saved';
|
|
1089
1702
|
}
|
|
1090
1703
|
finally {
|
|
1091
|
-
this.
|
|
1092
|
-
this.
|
|
1704
|
+
this.IsUploadingAttachments = false;
|
|
1705
|
+
this.UploadStateChanged.emit({ isUploading: false, message: '' });
|
|
1093
1706
|
}
|
|
1094
1707
|
// Plan §6: roll back the message when attachments are rejected so
|
|
1095
1708
|
// the user sees the rejection clearly instead of the agent answering
|
|
@@ -1102,7 +1715,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1102
1715
|
catch (rollbackErr) {
|
|
1103
1716
|
console.error('Failed to roll back conversation detail after attachment rejection:', rollbackErr);
|
|
1104
1717
|
}
|
|
1105
|
-
this.
|
|
1718
|
+
this.IsSending = false;
|
|
1106
1719
|
return false;
|
|
1107
1720
|
}
|
|
1108
1721
|
}
|
|
@@ -1112,10 +1725,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1112
1725
|
// The user-initiated send path (MessageInputBoxComponent.onSendClick)
|
|
1113
1726
|
// calls mentionEditor.clear() — we bypass that path here, so the chips
|
|
1114
1727
|
// would otherwise stay on screen after the message goes out.
|
|
1115
|
-
this.
|
|
1116
|
-
this.
|
|
1728
|
+
this.InputBox?.mentionEditor?.clear();
|
|
1729
|
+
this.MessageSent.emit(detail);
|
|
1117
1730
|
const mentionResult = this.parseMentionsFromMessage(detail.Message);
|
|
1118
|
-
const isFirstMessage = this.
|
|
1731
|
+
const isFirstMessage = this.ConversationHistory.length === 0;
|
|
1119
1732
|
await this.routeMessage(detail, mentionResult, isFirstMessage);
|
|
1120
1733
|
return true;
|
|
1121
1734
|
}
|
|
@@ -1129,20 +1742,24 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1129
1742
|
return false;
|
|
1130
1743
|
}
|
|
1131
1744
|
finally {
|
|
1132
|
-
this.
|
|
1745
|
+
this.IsSending = false;
|
|
1133
1746
|
}
|
|
1134
1747
|
}
|
|
1748
|
+
/** @deprecated Use {@link SendMessageWithText}. */
|
|
1749
|
+
async sendMessageWithText(text, extraAttachments) {
|
|
1750
|
+
return this.SendMessageWithText(text, extraAttachments);
|
|
1751
|
+
}
|
|
1135
1752
|
/**
|
|
1136
1753
|
* Creates and configures a new conversation detail message
|
|
1137
1754
|
*/
|
|
1138
1755
|
async createMessageDetail() {
|
|
1139
|
-
const detail = await this.dataCache.createConversationDetail(this.
|
|
1140
|
-
detail.ConversationID = this.
|
|
1141
|
-
detail.Message = this.
|
|
1756
|
+
const detail = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1757
|
+
detail.ConversationID = this.ConversationId;
|
|
1758
|
+
detail.Message = this.MessageText.trim();
|
|
1142
1759
|
detail.Role = 'User';
|
|
1143
|
-
detail.UserID = this.
|
|
1144
|
-
if (this.
|
|
1145
|
-
detail.ParentID = this.
|
|
1760
|
+
detail.UserID = this.CurrentUser.ID; // Set the user who sent the message
|
|
1761
|
+
if (this.ParentMessageId) {
|
|
1762
|
+
detail.ParentID = this.ParentMessageId;
|
|
1146
1763
|
}
|
|
1147
1764
|
return detail;
|
|
1148
1765
|
}
|
|
@@ -1150,13 +1767,13 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1150
1767
|
* Creates and configures a new conversation detail message from provided text
|
|
1151
1768
|
*/
|
|
1152
1769
|
async createMessageDetailFromText(text) {
|
|
1153
|
-
const detail = await this.dataCache.createConversationDetail(this.
|
|
1154
|
-
detail.ConversationID = this.
|
|
1770
|
+
const detail = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1771
|
+
detail.ConversationID = this.ConversationId;
|
|
1155
1772
|
detail.Message = text;
|
|
1156
1773
|
detail.Role = 'User';
|
|
1157
|
-
detail.UserID = this.
|
|
1158
|
-
if (this.
|
|
1159
|
-
detail.ParentID = this.
|
|
1774
|
+
detail.UserID = this.CurrentUser.ID; // Set the user who sent the message
|
|
1775
|
+
if (this.ParentMessageId) {
|
|
1776
|
+
detail.ParentID = this.ParentMessageId;
|
|
1160
1777
|
}
|
|
1161
1778
|
return detail;
|
|
1162
1779
|
}
|
|
@@ -1164,10 +1781,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1164
1781
|
* Handles successful message send - routes to appropriate agent
|
|
1165
1782
|
*/
|
|
1166
1783
|
async handleSuccessfulSend(messageDetail) {
|
|
1167
|
-
this.
|
|
1168
|
-
this.
|
|
1784
|
+
this.MessageSent.emit(messageDetail);
|
|
1785
|
+
this.MessageText = '';
|
|
1169
1786
|
const mentionResult = this.parseMentionsFromMessage(messageDetail.Message);
|
|
1170
|
-
const isFirstMessage = this.
|
|
1787
|
+
const isFirstMessage = this.ConversationHistory.length === 0;
|
|
1171
1788
|
await this.routeMessage(messageDetail, mentionResult, isFirstMessage);
|
|
1172
1789
|
this.refocusTextarea();
|
|
1173
1790
|
}
|
|
@@ -1179,9 +1796,15 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1179
1796
|
return mentionResult;
|
|
1180
1797
|
}
|
|
1181
1798
|
/**
|
|
1182
|
-
* Routes
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1799
|
+
* Routes a saved message to the agent that answers it — or to none.
|
|
1800
|
+
*
|
|
1801
|
+
* Routing picks the agent ({@link resolveAgentTurnTarget}): a tagged agent, then (under
|
|
1802
|
+
* `AgentReplyMode` `'Always'`) the last agent that answered, the conversation's pinned agent,
|
|
1803
|
+
* the host's default agent, and the conversation manager, each only if the host allows it.
|
|
1804
|
+
* With {@link EnableDecisionRouting} on, a routing decision may first put another agent in the
|
|
1805
|
+
* last agent's place ({@link decideAgentRouting}).
|
|
1806
|
+
* {@link BeforeAgentTurn} is then fired once, before any reply row exists, and the turn runs on
|
|
1807
|
+
* MJ's path or through the host's {@link AgentTurnHandler}.
|
|
1185
1808
|
*/
|
|
1186
1809
|
async routeMessage(messageDetail, mentionResult, isFirstMessage) {
|
|
1187
1810
|
// A Plan Mode choice made on the new-conversation composer (no conversation yet) lives in a
|
|
@@ -1199,80 +1822,213 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1199
1822
|
this._pendingRequestedSkillIDs = mentionResult.skillMentions?.length
|
|
1200
1823
|
? mentionResult.skillMentions.map(m => m.id)
|
|
1201
1824
|
: this.collectRequestedSkillIDs();
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
|
|
1825
|
+
// Naming is about the conversation, not the turn, so it doesn't wait on (or depend on) one.
|
|
1826
|
+
// It runs in the background: naming can take minutes to time out.
|
|
1827
|
+
if (isFirstMessage && this.AutoNameConversation) {
|
|
1828
|
+
void this.nameConversation(messageDetail.Message, messageDetail.ConversationID)
|
|
1829
|
+
.catch(error => console.error('Conversation naming failed:', error));
|
|
1830
|
+
}
|
|
1831
|
+
// Read the candidates before any await. This composer stays cached while the person opens
|
|
1832
|
+
// another conversation, and the chat area then rebinds its history and pinned agent, so a
|
|
1833
|
+
// read after the routing decision could see another conversation's state (or none).
|
|
1834
|
+
const candidates = this.agentTurnCandidates(this.agentMentionIds(mentionResult));
|
|
1835
|
+
const routing = await this.decideAgentRouting(messageDetail, candidates);
|
|
1836
|
+
const target = this.resolveAgentTurnTarget(candidates, routing);
|
|
1837
|
+
if (!target) {
|
|
1838
|
+
await this.finishWithoutAgentTurn(messageDetail, 'NoAgent');
|
|
1205
1839
|
return;
|
|
1206
1840
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
await this.handleAgentContinuity(messageDetail, lastAgentId, mentionResult, isFirstMessage);
|
|
1841
|
+
const turn = this.announceAgentTurn(messageDetail, target);
|
|
1842
|
+
if (!turn) {
|
|
1843
|
+
await this.finishWithoutAgentTurn(messageDetail, 'Declined');
|
|
1211
1844
|
return;
|
|
1212
1845
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1846
|
+
await this.runAgentTurn(messageDetail, mentionResult, turn, routing);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Asks the routing decision for this message when {@link EnableDecisionRouting} is on and the
|
|
1850
|
+
* message qualifies (see `ShouldRunRoutingDecision`). Returns null, with no call, otherwise, and
|
|
1851
|
+
* when anything fails: the message then keeps today's routing.
|
|
1852
|
+
*
|
|
1853
|
+
* @param candidates The turn's candidates, read before any await (see {@link routeMessage}).
|
|
1854
|
+
*/
|
|
1855
|
+
async decideAgentRouting(message, candidates) {
|
|
1856
|
+
const continuityAgentId = candidates.ContinuityAgentId;
|
|
1857
|
+
const qualifies = ShouldRunRoutingDecision({
|
|
1858
|
+
Enabled: this.EnableDecisionRouting,
|
|
1859
|
+
ReplyMode: this.AgentReplyMode,
|
|
1860
|
+
MentionedAgentIds: candidates.MentionedAgentIds,
|
|
1861
|
+
Message: message.Message ?? '',
|
|
1862
|
+
ContinuityAgentId: continuityAgentId
|
|
1863
|
+
});
|
|
1864
|
+
if (!qualifies || !continuityAgentId) {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
try {
|
|
1868
|
+
// Built before the first await, so it reads the same conversation as the candidates.
|
|
1869
|
+
const input = this.buildRoutingDecisionInput(message, continuityAgentId);
|
|
1870
|
+
if (!CanAskRoutingDecision(input)) {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
input.ArtifactVersions = await this.loadRoutingArtifactVersions(message.ConversationID, input.Participants);
|
|
1874
|
+
const outcome = await RunRoutingDecision(input, params => this.agentService.RunDecision(params));
|
|
1875
|
+
LogStatusEx({
|
|
1876
|
+
message: `Decision routing: ${outcome.Verdict}, ${outcome.Reason} (prompt run ${outcome.PromptRunID ?? 'none'})`,
|
|
1877
|
+
verboseOnly: true
|
|
1878
|
+
});
|
|
1879
|
+
return outcome;
|
|
1880
|
+
}
|
|
1881
|
+
catch (error) {
|
|
1882
|
+
console.warn('Decision routing failed, so the message keeps continuity:', error);
|
|
1883
|
+
return null;
|
|
1220
1884
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* The routing decision's input, rebuilt from the conversation as it is now: the agents that
|
|
1888
|
+
* have answered within the history floor, the last few turns, and the conversation manager.
|
|
1889
|
+
* Artifact versions are added after, once the input is known to be worth asking about.
|
|
1890
|
+
*
|
|
1891
|
+
* Participants come only from the '@' list's agents (`GetAvailableAgents`): active, top-level,
|
|
1892
|
+
* unrestricted agents this person has run permission for. An agent that answered here but that
|
|
1893
|
+
* the person can't run (a shared conversation, a revoked permission) is never offered, so routing
|
|
1894
|
+
* can't send the turn to an agent the server would refuse. Before that list has loaded there are
|
|
1895
|
+
* no participants, so no call is made and the message keeps today's routing. Speaker names in
|
|
1896
|
+
* the recent turns still come from the full catalog, since any agent may have spoken.
|
|
1897
|
+
*/
|
|
1898
|
+
buildRoutingDecisionInput(message, continuityAgentId) {
|
|
1899
|
+
const history = this.ConversationHistory.filter(row => this.isWithinHistoryFloor(row) && !UUIDsEqual(row.ID, message.ID));
|
|
1900
|
+
const findAgent = (agentId) => AIEngineBase.Instance.Agents.find(a => UUIDsEqual(a.ID, agentId));
|
|
1901
|
+
const runnable = this.mentionAutocomplete.GetAvailableAgents();
|
|
1902
|
+
const findRunnable = (agentId) => runnable.find(a => UUIDsEqual(a.ID, agentId));
|
|
1903
|
+
const manager = this.ConverationManagerAgent;
|
|
1904
|
+
return {
|
|
1905
|
+
Message: message.Message ?? '',
|
|
1906
|
+
ContinuityAgentId: continuityAgentId,
|
|
1907
|
+
Participants: CollectRoutingParticipants(history, manager?.ID ?? null, this.AllowedAgentIDs, findRunnable),
|
|
1908
|
+
ConversationManager: manager?.ID && IsAgentAllowed(manager.ID, this.AllowedAgentIDs) ? manager : null,
|
|
1909
|
+
RecentTurns: BuildRecentTurns(history, findAgent),
|
|
1910
|
+
ArtifactVersions: [],
|
|
1911
|
+
AllowedAgentIDs: this.AllowedAgentIDs
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
/** Every participant's artifact versions in this conversation, for the artifact question. */
|
|
1915
|
+
async loadRoutingArtifactVersions(conversationId, participants) {
|
|
1916
|
+
const artifactsByAgent = await Promise.all(participants.map(async (participant) => ({
|
|
1917
|
+
Agent: participant.Agent,
|
|
1918
|
+
Artifacts: await this.agentService.FindAgentArtifacts(conversationId, participant.Agent.ID, this.AgentHistoryFrom)
|
|
1919
|
+
})));
|
|
1920
|
+
return BuildRoutingArtifactVersions(artifactsByAgent);
|
|
1921
|
+
}
|
|
1922
|
+
/** The host's rules, as the routing functions take them. */
|
|
1923
|
+
get agentTurnRules() {
|
|
1924
|
+
return { ReplyMode: this.AgentReplyMode, AllowedAgentIDs: this.AllowedAgentIDs };
|
|
1925
|
+
}
|
|
1926
|
+
/** The agent each route would use for this conversation, before the host's rules apply. */
|
|
1927
|
+
agentTurnCandidates(mentionedAgentIds) {
|
|
1928
|
+
return {
|
|
1929
|
+
MentionedAgentIds: mentionedAgentIds,
|
|
1930
|
+
ContinuityAgentId: this.findLastNonSageAgentId(),
|
|
1931
|
+
ConversationDefaultAgentId: this.ConversationDefaultAgentId,
|
|
1932
|
+
HostDefaultAgentId: this.DefaultAgentId,
|
|
1933
|
+
ConversationManagerAgentId: this.ConverationManagerAgent?.ID ?? null
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Picks the agent that answers this message and the route that chose it, or null for no turn.
|
|
1938
|
+
* A routing decision, when there was one, is applied to the candidates first.
|
|
1939
|
+
*/
|
|
1940
|
+
resolveAgentTurnTarget(candidates, routing) {
|
|
1941
|
+
return ResolveAgentTurn(ApplyRoutingDecision(candidates, routing), this.agentTurnRules, agentId => this.isKnownAgent(agentId));
|
|
1942
|
+
}
|
|
1943
|
+
/** The agents a message tags, in the order they appear. */
|
|
1944
|
+
agentMentionIds(mentionResult) {
|
|
1945
|
+
return mentionResult.mentions.filter(m => m.type === 'agent').map(m => m.id);
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Fires {@link BeforeAgentTurn} for the turn routing picked — before any reply row exists —
|
|
1949
|
+
* and applies what its listeners decided. Returns the turn to run, or null when a listener
|
|
1950
|
+
* canceled it or redirected it to an agent this chat can't use.
|
|
1951
|
+
*/
|
|
1952
|
+
announceAgentTurn(userMessage, target) {
|
|
1953
|
+
const args = new BeforeAgentTurnEventArgs(userMessage.ConversationID, userMessage.Message ?? '', this.ApplicationId, { ...target, AgentName: this.agentNameFor(target.AgentId), UserMessageId: userMessage.ID });
|
|
1954
|
+
this.BeforeAgentTurn.emit(args);
|
|
1955
|
+
if (args.Cancel) {
|
|
1956
|
+
LogStatusEx({ message: `Agent turn canceled by a BeforeAgentTurn listener${args.CancelReason ? `: ${args.CancelReason}` : ''}` });
|
|
1957
|
+
return null;
|
|
1958
|
+
}
|
|
1959
|
+
const redirectAgentId = args.RedirectAgentId;
|
|
1960
|
+
if (!redirectAgentId || UUIDsEqual(redirectAgentId, target.AgentId)) {
|
|
1961
|
+
return target;
|
|
1962
|
+
}
|
|
1963
|
+
if (!IsAgentAllowed(redirectAgentId, this.AllowedAgentIDs) || !this.isKnownAgent(redirectAgentId)) {
|
|
1964
|
+
this.notifyAgentTurnProblem('That agent isn\'t available in this chat, so it didn\'t answer.', 'warning');
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
return { AgentId: redirectAgentId, Route: 'Redirect' };
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* Settles a message that starts no agent turn — `MentionOnly` with no tagged agent, no allowed
|
|
1971
|
+
* agent left, or a turn declined in {@link BeforeAgentTurn}. Nothing new is written; the
|
|
1972
|
+
* person's own row is only confirmed Complete.
|
|
1973
|
+
*/
|
|
1974
|
+
async finishWithoutAgentTurn(userMessage, reason) {
|
|
1975
|
+
// Under the default rules a turn always has somewhere to go, so reaching here with no agent
|
|
1976
|
+
// means MJ's conversation manager failed to load. Say so rather than sit silent.
|
|
1977
|
+
if (reason === 'NoAgent' && this.AgentReplyMode === 'Always' && this.AllowedAgentIDs == null) {
|
|
1978
|
+
this.notifyAgentTurnProblem('No agent is available to answer this message.', 'error');
|
|
1979
|
+
}
|
|
1980
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Runs a turn that {@link BeforeAgentTurn} let through, on the host's handler or MJ's path.
|
|
1984
|
+
* The routing decision, when there was one, supplies the artifact version the turn's agent
|
|
1985
|
+
* continues from; a host's handler receives it as `TargetArtifactVersionId`.
|
|
1986
|
+
*/
|
|
1987
|
+
async runAgentTurn(userMessage, mentionResult, turn, routing = null) {
|
|
1988
|
+
const mention = turn.Route === 'Mention' ? this.findAgentMention(mentionResult, turn.AgentId) : null;
|
|
1989
|
+
const targetArtifactVersionId = ArtifactVersionForTurn(routing, turn.AgentId);
|
|
1990
|
+
if (this.AgentTurnHandler) {
|
|
1991
|
+
await this.runHostAgentTurn(this.AgentTurnHandler, userMessage, turn, mention, targetArtifactVersionId);
|
|
1229
1992
|
return;
|
|
1230
1993
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
await this.executeRouteWithNaming(() => this.executeAgentContinuation(messageDetail, this.converationManagerAgent.ID, this.converationManagerAgent.Name || 'Sage', messageDetail.ConversationID, null, // Sage doesn't use payload continuity
|
|
1241
|
-
null, // Sage doesn't use artifact info
|
|
1242
|
-
sageConfigPreset // Pass the already-found config preset
|
|
1243
|
-
), messageDetail.Message, isFirstMessage, messageDetail.ConversationID);
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1994
|
+
if (mention) {
|
|
1995
|
+
await this.invokeAgentDirectly(userMessage, mention, userMessage.ConversationID);
|
|
1996
|
+
}
|
|
1997
|
+
else if (turn.Route === 'ConversationManager' || (turn.Route === 'Redirect' && this.isConversationManager(turn.AgentId))) {
|
|
1998
|
+
await this.runConversationManagerTurn(userMessage, mentionResult);
|
|
1999
|
+
}
|
|
2000
|
+
else {
|
|
2001
|
+
// Pinned and host defaults keep their direct call even when they name the manager, as before.
|
|
2002
|
+
await this.handleAgentContinuity(userMessage, turn.AgentId, targetArtifactVersionId);
|
|
1246
2003
|
}
|
|
1247
|
-
// Priority 6: No context - use Sage with default config
|
|
1248
|
-
await this.handleNoAgentContext(messageDetail, mentionResult, isFirstMessage);
|
|
1249
2004
|
}
|
|
1250
2005
|
/**
|
|
1251
|
-
*
|
|
2006
|
+
* The tagged agent's mention, with its configuration preset. A mention saved without one
|
|
2007
|
+
* (legacy text format) takes the preset from its chip in the composer, when there is one.
|
|
1252
2008
|
*/
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
2009
|
+
findAgentMention(mentionResult, agentId) {
|
|
2010
|
+
const mention = mentionResult.mentions.find(m => m.type === 'agent' && UUIDsEqual(m.id, agentId));
|
|
2011
|
+
if (!mention) {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
if (!mention.configurationId) {
|
|
2015
|
+
const chipData = this.InputBox?.getMentionChipsData() || [];
|
|
2016
|
+
const agentChip = chipData.find(chip => chip.id === mention.id && chip.type === 'agent');
|
|
1259
2017
|
if (agentChip?.presetId) {
|
|
1260
|
-
|
|
2018
|
+
mention.configurationId = agentChip.presetId;
|
|
1261
2019
|
}
|
|
1262
2020
|
}
|
|
1263
|
-
|
|
1264
|
-
//console.log(`🎯 Agent mention has configuration ID: ${agentMention.configurationId}`);
|
|
1265
|
-
}
|
|
1266
|
-
await this.executeRouteWithNaming(() => this.invokeAgentDirectly(messageDetail, agentMention, messageDetail.ConversationID), messageDetail.Message, isFirstMessage, messageDetail.ConversationID);
|
|
2021
|
+
return mention;
|
|
1267
2022
|
}
|
|
1268
2023
|
/**
|
|
1269
|
-
*
|
|
2024
|
+
* Continues with an agent chosen without a mention: the last agent that answered, the pinned
|
|
2025
|
+
* or host default agent, or a redirect.
|
|
1270
2026
|
*
|
|
1271
2027
|
* LATENCY OPTIMIZATION (PR #2309 / plans/agent-latency-optimization.md — Opt #1):
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
2028
|
+
* This used to make a separate LLM call via checkContinuityIntent() to decide whether the
|
|
2029
|
+
* user's new message was still directed at the previous agent or should be routed to Sage.
|
|
2030
|
+
* That call added ~300ms of latency on every message in a conversation with an active agent —
|
|
2031
|
+
* the single largest source of non-inference overhead on the client.
|
|
1276
2032
|
*
|
|
1277
2033
|
* The heuristic replacement is simple: if a previous non-Sage agent exists, always continue
|
|
1278
2034
|
* with it. The user can @mention a different agent (or Sage) to explicitly switch. This is
|
|
@@ -1283,50 +2039,163 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1283
2039
|
* method are preserved (not deleted) so we can reintroduce intent checking in the future
|
|
1284
2040
|
* when browser-local inference is fast enough (~20-50ms) to do this without blocking the
|
|
1285
2041
|
* user. See PR #2309 for the full discussion.
|
|
2042
|
+
*
|
|
2043
|
+
* With {@link EnableDecisionRouting} on, a typed routing decision runs before routing instead
|
|
2044
|
+
* ({@link decideAgentRouting}); the prompt-based check stays unused. A confident answer from it
|
|
2045
|
+
* may name the artifact version this agent should continue from.
|
|
2046
|
+
*
|
|
2047
|
+
* @param targetArtifactVersionId The artifact version the routing decision named, or null.
|
|
2048
|
+
*/
|
|
2049
|
+
async handleAgentContinuity(messageDetail, agentId, targetArtifactVersionId = null) {
|
|
2050
|
+
await this.continueWithAgent(messageDetail, agentId, messageDetail.ConversationID, targetArtifactVersionId ?? undefined // set only by a confident routing decision
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* The conversation manager's turn. When an earlier message @mentioned the manager with a
|
|
2055
|
+
* configuration preset, the preset keeps applying and the manager answers directly;
|
|
2056
|
+
* otherwise it evaluates the message and may delegate.
|
|
2057
|
+
*/
|
|
2058
|
+
async runConversationManagerTurn(messageDetail, mentionResult) {
|
|
2059
|
+
const manager = this.ConverationManagerAgent;
|
|
2060
|
+
const managerPreset = manager?.ID && messageDetail.ConversationID
|
|
2061
|
+
? await this.agentService.FindConfigurationPresetForAgent(messageDetail.ConversationID, manager.ID)
|
|
2062
|
+
: undefined;
|
|
2063
|
+
if (manager?.ID && managerPreset) {
|
|
2064
|
+
await this.executeAgentContinuation(messageDetail, manager.ID, manager.Name || 'Sage', messageDetail.ConversationID, null, // Sage doesn't use payload continuity
|
|
2065
|
+
null, // Sage doesn't use artifact info
|
|
2066
|
+
managerPreset // Pass the already-found config preset
|
|
2067
|
+
);
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
await this.processMessageThroughAgent(messageDetail, mentionResult);
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* The newest reply from an agent other than the conversation manager — among the replies a
|
|
2074
|
+
* turn may read, so from {@link AgentHistoryFrom} onward when it's set.
|
|
1286
2075
|
*/
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
// const intentResult = await this.checkContinuityIntent(lastAgentId, messageDetail.Message);
|
|
1290
|
-
//
|
|
1291
|
-
// if (intentResult.decision === 'YES') {
|
|
1292
|
-
// await this.executeRouteWithNaming(
|
|
1293
|
-
// () => this.continueWithAgent(
|
|
1294
|
-
// messageDetail,
|
|
1295
|
-
// lastAgentId,
|
|
1296
|
-
// this.conversationId,
|
|
1297
|
-
// intentResult.targetArtifactVersionId
|
|
1298
|
-
// ),
|
|
1299
|
-
// messageDetail.Message,
|
|
1300
|
-
// isFirstMessage
|
|
1301
|
-
// );
|
|
1302
|
-
// } else {
|
|
1303
|
-
// await this.executeRouteWithNaming(
|
|
1304
|
-
// () => this.processMessageThroughAgent(messageDetail, mentionResult),
|
|
1305
|
-
// messageDetail.Message,
|
|
1306
|
-
// isFirstMessage
|
|
1307
|
-
// );
|
|
1308
|
-
// }
|
|
1309
|
-
// Always continue with the previous agent — user can @mention another agent to switch.
|
|
1310
|
-
await this.executeRouteWithNaming(() => this.continueWithAgent(messageDetail, lastAgentId, messageDetail.ConversationID, undefined // artifact version targeting unavailable without intent check
|
|
1311
|
-
), messageDetail.Message, isFirstMessage, messageDetail.ConversationID);
|
|
1312
|
-
}
|
|
1313
|
-
/**
|
|
1314
|
-
* Handles routing when there's no previous agent context
|
|
1315
|
-
*/
|
|
1316
|
-
async handleNoAgentContext(messageDetail, mentionResult, isFirstMessage) {
|
|
1317
|
-
await this.executeRouteWithNaming(() => this.processMessageThroughAgent(messageDetail, mentionResult), messageDetail.Message, isFirstMessage, messageDetail.ConversationID);
|
|
1318
|
-
}
|
|
1319
|
-
/**
|
|
1320
|
-
* Finds the last agent ID that isn't Sage
|
|
1321
|
-
*/
|
|
1322
|
-
findLastNonSageAgentId() {
|
|
1323
|
-
const lastAIMessage = this.conversationHistory
|
|
2076
|
+
findLastNonSageReply() {
|
|
2077
|
+
return this.ConversationHistory
|
|
1324
2078
|
.slice()
|
|
1325
2079
|
.reverse()
|
|
1326
2080
|
.find(msg => msg.Role === 'AI' &&
|
|
1327
|
-
msg.AgentID &&
|
|
1328
|
-
!UUIDsEqual(msg.AgentID, this.
|
|
1329
|
-
|
|
2081
|
+
!!msg.AgentID &&
|
|
2082
|
+
!UUIDsEqual(msg.AgentID, this.ConverationManagerAgent?.ID) &&
|
|
2083
|
+
this.isWithinHistoryFloor(msg)) ?? null;
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Finds the last agent ID that isn't Sage (see {@link findLastNonSageReply}).
|
|
2087
|
+
*/
|
|
2088
|
+
findLastNonSageAgentId() {
|
|
2089
|
+
return this.findLastNonSageReply()?.AgentID || null;
|
|
2090
|
+
}
|
|
2091
|
+
/** True when a row was written at or after {@link AgentHistoryFrom}; always true without one. */
|
|
2092
|
+
isWithinHistoryFloor(detail) {
|
|
2093
|
+
const floor = this.AgentHistoryFrom;
|
|
2094
|
+
if (!floor) {
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
const createdAt = detail.__mj_CreatedAt;
|
|
2098
|
+
return createdAt != null && new Date(createdAt).getTime() >= floor.getTime();
|
|
2099
|
+
}
|
|
2100
|
+
/** True when the agent is in the client's agent catalog. */
|
|
2101
|
+
isKnownAgent(agentId) {
|
|
2102
|
+
return AIEngineBase.Instance.Agents.some(a => UUIDsEqual(a.ID, agentId));
|
|
2103
|
+
}
|
|
2104
|
+
/** The agent's name, when the client's agent catalog has it. */
|
|
2105
|
+
agentNameFor(agentId) {
|
|
2106
|
+
return AIEngineBase.Instance.Agents.find(a => UUIDsEqual(a.ID, agentId))?.Name ?? null;
|
|
2107
|
+
}
|
|
2108
|
+
/** The ID of the agent with this name, or null when there is none. */
|
|
2109
|
+
agentIdByName(agentName) {
|
|
2110
|
+
return AIEngineBase.Instance.Agents.find(a => a.Name === agentName)?.ID ?? null;
|
|
2111
|
+
}
|
|
2112
|
+
/** True when the agent is MJ's conversation manager. */
|
|
2113
|
+
isConversationManager(agentId) {
|
|
2114
|
+
return !!this.ConverationManagerAgent?.ID && UUIDsEqual(agentId, this.ConverationManagerAgent.ID);
|
|
2115
|
+
}
|
|
2116
|
+
/** Shows the person why an agent turn didn't happen, or didn't finish. */
|
|
2117
|
+
notifyAgentTurnProblem(message, style) {
|
|
2118
|
+
MJNotificationService.Instance?.CreateSimpleNotification(message, style, 5000);
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Refuses agents the host doesn't allow — named by the conversation manager's delegation or
|
|
2122
|
+
* by a workflow it planned — and settles the person's message.
|
|
2123
|
+
*/
|
|
2124
|
+
async refuseDisallowedAgents(userMessage, agentNames) {
|
|
2125
|
+
const subject = agentNames.join(', ');
|
|
2126
|
+
this.notifyAgentTurnProblem(agentNames.length === 1
|
|
2127
|
+
? `${subject} isn't available in this chat, so it didn't run.`
|
|
2128
|
+
: `${subject} aren't available in this chat, so they didn't run.`, 'warning');
|
|
2129
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2130
|
+
}
|
|
2131
|
+
/**
|
|
2132
|
+
* Runs the turn through the host's {@link AgentTurnHandler} instead of MJ's path. Nothing is
|
|
2133
|
+
* written here: the host writes the reply rows and reports them, and each is shown like a row
|
|
2134
|
+
* MJ wrote — one still In-Progress is followed like any other in-progress reply.
|
|
2135
|
+
*/
|
|
2136
|
+
async runHostAgentTurn(handler, userMessage, turn, mention, targetArtifactVersionId) {
|
|
2137
|
+
const request = this.buildAgentTurnRequest(userMessage, turn, mention, targetArtifactVersionId);
|
|
2138
|
+
const result = await this.callAgentTurnHandler(handler, request);
|
|
2139
|
+
if (!result.Success) {
|
|
2140
|
+
this.notifyAgentTurnProblem(result.ErrorMessage || 'The agent could not answer this message.', 'error');
|
|
2141
|
+
}
|
|
2142
|
+
else {
|
|
2143
|
+
await this.showHostTurnRows(result.ReplyDetailIds ?? []);
|
|
2144
|
+
}
|
|
2145
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2146
|
+
if (result.Success && result.Result) {
|
|
2147
|
+
this.emitAfterAgentTurn(userMessage.ConversationID, result.Result, result.AgentRunId);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
/** Calls the host's handler; a throw is reported as a failed turn, never swallowed. */
|
|
2151
|
+
async callAgentTurnHandler(handler, request) {
|
|
2152
|
+
try {
|
|
2153
|
+
return await handler(request);
|
|
2154
|
+
}
|
|
2155
|
+
catch (error) {
|
|
2156
|
+
console.error('AgentTurnHandler failed:', error);
|
|
2157
|
+
return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* What the host's handler is told about the turn.
|
|
2162
|
+
*
|
|
2163
|
+
* @param targetArtifactVersionId The artifact version the routing decision named for this
|
|
2164
|
+
* turn's agent, or null.
|
|
2165
|
+
*/
|
|
2166
|
+
buildAgentTurnRequest(userMessage, turn, mention, targetArtifactVersionId) {
|
|
2167
|
+
return {
|
|
2168
|
+
ConversationId: userMessage.ConversationID,
|
|
2169
|
+
UserMessageId: userMessage.ID,
|
|
2170
|
+
MessageText: userMessage.Message ?? '',
|
|
2171
|
+
AgentId: turn.AgentId,
|
|
2172
|
+
AgentName: this.agentNameFor(turn.AgentId),
|
|
2173
|
+
Route: turn.Route,
|
|
2174
|
+
ApplicationId: this.ApplicationId,
|
|
2175
|
+
AppContext: this.AppContext,
|
|
2176
|
+
AgentHistoryFrom: this.AgentHistoryFrom,
|
|
2177
|
+
// A mention carries its own preset (or none); every other route follows the header picker.
|
|
2178
|
+
ConfigurationPresetId: turn.Route === 'Mention' ? (mention?.configurationId ?? null) : this.AgentConfigurationPresetId,
|
|
2179
|
+
RequestedSkillIDs: [...this._pendingRequestedSkillIDs],
|
|
2180
|
+
PlanMode: this.PlanModeEnabled,
|
|
2181
|
+
TargetArtifactVersionId: targetArtifactVersionId
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
/** Shows the rows a host's handler reported for its turn, oldest first. */
|
|
2185
|
+
async showHostTurnRows(detailIds) {
|
|
2186
|
+
for (const detailId of detailIds) {
|
|
2187
|
+
const detail = await this.dataCache.getConversationDetail(detailId, this.CurrentUser);
|
|
2188
|
+
if (detail) {
|
|
2189
|
+
this.MessageSent.emit(detail);
|
|
2190
|
+
}
|
|
2191
|
+
else {
|
|
2192
|
+
console.warn(`AgentTurnHandler reported conversation detail ${detailId}, but it could not be loaded`);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
/** Fires {@link AfterAgentTurn} for a turn that succeeded. */
|
|
2197
|
+
emitAfterAgentTurn(conversationId, result, agentRunId) {
|
|
2198
|
+
this.AfterAgentTurn.emit(new AfterAgentTurnEventArgs(conversationId, agentRunId ?? result.agentRun?.ID ?? '', result));
|
|
1330
2199
|
}
|
|
1331
2200
|
/**
|
|
1332
2201
|
* Checks if message should continue with the previous agent
|
|
@@ -1344,17 +2213,17 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1344
2213
|
};
|
|
1345
2214
|
}
|
|
1346
2215
|
// Emit event to show temporary "Analyzing intent..." message in conversation
|
|
1347
|
-
this.
|
|
2216
|
+
this.IntentCheckStarted.emit({ conversationId: this.ConversationId });
|
|
1348
2217
|
try {
|
|
1349
2218
|
// The pre-loaded artifact/agent-run maps are no longer passed: they are scoped to the
|
|
1350
2219
|
// loaded transcript window, and the service now queries for this agent's artifacts so
|
|
1351
2220
|
// the classifier reasons over the whole conversation. A conversation id is what it
|
|
1352
2221
|
// needs instead, and without one there is nothing to query.
|
|
1353
|
-
if (!this.
|
|
2222
|
+
if (!this.ConversationId) {
|
|
1354
2223
|
console.warn('⚠️ No conversation id available for intent check');
|
|
1355
2224
|
return { decision: 'UNSURE', reasoning: 'Conversation not available' };
|
|
1356
2225
|
}
|
|
1357
|
-
const intent = await this.agentService.checkAgentContinuityIntent(this.
|
|
2226
|
+
const intent = await this.agentService.checkAgentContinuityIntent(this.ConversationId, agentId, message, this.ConversationHistory);
|
|
1358
2227
|
return intent;
|
|
1359
2228
|
}
|
|
1360
2229
|
catch (error) {
|
|
@@ -1363,25 +2232,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1363
2232
|
}
|
|
1364
2233
|
finally {
|
|
1365
2234
|
// Emit event to remove temporary intent checking message
|
|
1366
|
-
this.
|
|
1367
|
-
}
|
|
1368
|
-
}
|
|
1369
|
-
/**
|
|
1370
|
-
* Executes a routing function, optionally with conversation naming for first message
|
|
1371
|
-
*
|
|
1372
|
-
* IMPORTANT: Conversation naming runs asynchronously in the background and does NOT
|
|
1373
|
-
* block the agent invocation. This prevents UI blocking if naming times out.
|
|
1374
|
-
*/
|
|
1375
|
-
async executeRouteWithNaming(routeFunction, userMessage, isFirstMessage, conversationId) {
|
|
1376
|
-
if (isFirstMessage) {
|
|
1377
|
-
// Fire conversation naming in background (don't await)
|
|
1378
|
-
// This prevents 2+ minute UI blocking if naming times out
|
|
1379
|
-
this.nameConversation(userMessage, conversationId);
|
|
1380
|
-
// Execute route immediately (don't wait for naming)
|
|
1381
|
-
await routeFunction();
|
|
1382
|
-
}
|
|
1383
|
-
else {
|
|
1384
|
-
await routeFunction();
|
|
2235
|
+
this.IntentCheckCompleted.emit({ conversationId: this.ConversationId });
|
|
1385
2236
|
}
|
|
1386
2237
|
}
|
|
1387
2238
|
/**
|
|
@@ -1389,8 +2240,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1389
2240
|
*/
|
|
1390
2241
|
refocusTextarea() {
|
|
1391
2242
|
setTimeout(() => {
|
|
1392
|
-
if (this.
|
|
1393
|
-
this.
|
|
2243
|
+
if (this.InputBox) {
|
|
2244
|
+
this.InputBox.focus();
|
|
1394
2245
|
}
|
|
1395
2246
|
}, 100);
|
|
1396
2247
|
}
|
|
@@ -1450,7 +2301,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1450
2301
|
// CRITICAL FIX: Emit FULL agent run object for incremental updates
|
|
1451
2302
|
// This contains live timestamps, status, and other fields that change during execution
|
|
1452
2303
|
if (progressAgentRun || progressAgentRunId) {
|
|
1453
|
-
this.
|
|
2304
|
+
this.AgentRunUpdate.emit({
|
|
1454
2305
|
conversationId: conversationDetail.ConversationID,
|
|
1455
2306
|
conversationDetailId: conversationDetail.ID,
|
|
1456
2307
|
agentRun: progressAgentRun,
|
|
@@ -1460,7 +2311,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1460
2311
|
else if (progressAgentRunId && !capturedAgentRunId) {
|
|
1461
2312
|
// Fallback: If we don't have the full object but have the ID, emit agentRunDetected
|
|
1462
2313
|
// This will trigger a database query to load the agent run
|
|
1463
|
-
this.
|
|
2314
|
+
this.AgentRunDetected.emit({
|
|
1464
2315
|
conversationId: conversationDetail.ConversationID,
|
|
1465
2316
|
conversationDetailId: conversationDetail.ID,
|
|
1466
2317
|
agentRunId: progressAgentRunId
|
|
@@ -1470,7 +2321,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1470
2321
|
conversationDetail.Message = progressText;
|
|
1471
2322
|
// Server now saves progress - client only updates in-memory and emits for UI
|
|
1472
2323
|
// (Prevents race condition where client's late save overwrites server's final Status)
|
|
1473
|
-
this.
|
|
2324
|
+
this.MessageSent.emit(conversationDetail);
|
|
1474
2325
|
}
|
|
1475
2326
|
}
|
|
1476
2327
|
}
|
|
@@ -1481,7 +2332,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1481
2332
|
}
|
|
1482
2333
|
/**
|
|
1483
2334
|
* Process the message through agents (multi-stage: Sage -> possible sub-agent)
|
|
1484
|
-
* Only called when there's no @mention and no implicit agent context
|
|
2335
|
+
* Only called when there's no @mention and no implicit agent context.
|
|
2336
|
+
* {@link BeforeAgentTurn} has already fired — routing announces the turn before any row exists.
|
|
1485
2337
|
*/
|
|
1486
2338
|
async processMessageThroughAgent(userMessage, mentionResult) {
|
|
1487
2339
|
let taskId = null;
|
|
@@ -1491,7 +2343,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1491
2343
|
const conversationId = userMessage.ConversationID;
|
|
1492
2344
|
try {
|
|
1493
2345
|
// Create AI message for Sage BEFORE invoking
|
|
1494
|
-
conversationManagerMessage = await this.dataCache.createConversationDetail(this.
|
|
2346
|
+
conversationManagerMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1495
2347
|
conversationManagerMessage.ConversationID = conversationId;
|
|
1496
2348
|
conversationManagerMessage.Role = 'AI';
|
|
1497
2349
|
conversationManagerMessage.Message = '⏳ Starting...';
|
|
@@ -1499,11 +2351,11 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1499
2351
|
conversationManagerMessage.Status = 'In-Progress';
|
|
1500
2352
|
conversationManagerMessage.HiddenToUser = false;
|
|
1501
2353
|
// Use the preloaded Sage agent instead of looking it up
|
|
1502
|
-
if (this.
|
|
1503
|
-
conversationManagerMessage.AgentID = this.
|
|
2354
|
+
if (this.ConverationManagerAgent?.ID) {
|
|
2355
|
+
conversationManagerMessage.AgentID = this.ConverationManagerAgent.ID;
|
|
1504
2356
|
}
|
|
1505
2357
|
await conversationManagerMessage.Save();
|
|
1506
|
-
this.
|
|
2358
|
+
this.MessageSent.emit(conversationManagerMessage);
|
|
1507
2359
|
// Use Sage to evaluate and route
|
|
1508
2360
|
// Stage 1: Sage evaluates the message
|
|
1509
2361
|
taskId = this.activeTasks.add({
|
|
@@ -1512,39 +2364,22 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1512
2364
|
relatedMessageId: userMessage.ID,
|
|
1513
2365
|
conversationDetailId: conversationManagerMessage.ID,
|
|
1514
2366
|
conversationId,
|
|
1515
|
-
conversationName: this.
|
|
2367
|
+
conversationName: this.ConversationName
|
|
1516
2368
|
});
|
|
1517
|
-
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1520
|
-
|
|
1521
|
-
// subscriber's final answer.
|
|
1522
|
-
const beforeEvent = new BeforeAgentTurnEventArgs(conversationId, userMessage.Message ?? '', this.applicationId);
|
|
1523
|
-
this.beforeAgentTurn.emit(beforeEvent);
|
|
1524
|
-
if (beforeEvent.Cancel) {
|
|
1525
|
-
// Mark the conversation-manager message as canceled + clear its task so the
|
|
1526
|
-
// UI doesn't show a forever-pending spinner. afterAgentTurn is NOT emitted.
|
|
1527
|
-
await this.updateConversationDetail(conversationManagerMessage, beforeEvent.CancelReason ?? '⛔ Turn canceled before agent invocation', 'Error');
|
|
1528
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1529
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
1530
|
-
if (taskId) {
|
|
1531
|
-
this.activeTasks.remove(taskId);
|
|
1532
|
-
taskId = null;
|
|
1533
|
-
}
|
|
1534
|
-
return;
|
|
1535
|
-
}
|
|
1536
|
-
const result = await this.agentService.processMessage(conversationId, userMessage, this.conversationHistory, conversationManagerMessage.ID, this.createProgressCallback(conversationManagerMessage, 'Sage'), this.appContext, this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
1537
|
-
this._pendingRequestedSkillIDs);
|
|
2369
|
+
const result = await this.agentService.ProcessMessage(conversationId, userMessage, this.ConversationHistory, conversationManagerMessage.ID, this.createProgressCallback(conversationManagerMessage, 'Sage'), this.AppContext, this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2370
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2371
|
+
this.AllowedAgentIDs, // the agents the manager may delegate to
|
|
2372
|
+
this.AgentHistoryFrom);
|
|
1538
2373
|
// Emit afterAgentTurn on the happy path only — the error/failure branch
|
|
1539
2374
|
// immediately below handles its own cleanup and skips this emit.
|
|
1540
2375
|
if (result && result.success) {
|
|
1541
|
-
this.
|
|
2376
|
+
this.emitAfterAgentTurn(conversationId, result);
|
|
1542
2377
|
}
|
|
1543
2378
|
// Task will be removed automatically in markMessageComplete()
|
|
1544
2379
|
// DO NOT remove here - agent may still be streaming/processing
|
|
1545
2380
|
taskId = null; // Clear reference but don't remove from service
|
|
1546
2381
|
if (!result || !result.success) {
|
|
1547
|
-
await this.applyAgentFailureToDetail(conversationManagerMessage, userMessage, this.
|
|
2382
|
+
await this.applyAgentFailureToDetail(conversationManagerMessage, userMessage, this.ConverationManagerAgent?.Name || 'Sage', result, 'failed');
|
|
1548
2383
|
console.warn('⚠️ Sage failed:', agentFailureMessage(result, 'Agent evaluation failed'));
|
|
1549
2384
|
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
1550
2385
|
return;
|
|
@@ -1576,7 +2411,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1576
2411
|
// Server already created artifacts - just emit event to trigger UI reload
|
|
1577
2412
|
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
1578
2413
|
this.emitArtifactReload(conversationManagerMessage);
|
|
1579
|
-
this.
|
|
2414
|
+
this.MessageSent.emit(conversationManagerMessage);
|
|
1580
2415
|
}
|
|
1581
2416
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1582
2417
|
// Remove CM from active tasks
|
|
@@ -1595,7 +2430,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1595
2430
|
conversationManagerMessage.HiddenToUser = false;
|
|
1596
2431
|
// use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
|
|
1597
2432
|
await this.updateConversationDetail(conversationManagerMessage, result.agentRun.Message, 'Complete', result);
|
|
1598
|
-
this.
|
|
2433
|
+
this.MessageSent.emit(conversationManagerMessage);
|
|
1599
2434
|
// Clean up completion timestamp after delay
|
|
1600
2435
|
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
1601
2436
|
}
|
|
@@ -1606,7 +2441,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1606
2441
|
conversationManagerMessage.HiddenToUser = true;
|
|
1607
2442
|
// use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
|
|
1608
2443
|
await this.updateConversationDetail(conversationManagerMessage, conversationManagerMessage.Message, 'Complete', result);
|
|
1609
|
-
this.
|
|
2444
|
+
this.MessageSent.emit(conversationManagerMessage);
|
|
1610
2445
|
await this.handleSilentObservation(userMessage, conversationId);
|
|
1611
2446
|
// Clean up completion timestamp after delay
|
|
1612
2447
|
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
@@ -1666,11 +2501,18 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1666
2501
|
const taskGraph = managerResult.payload?.taskGraph;
|
|
1667
2502
|
if (!taskGraph)
|
|
1668
2503
|
return;
|
|
2504
|
+
// The host's allowed list holds for every agent step of the plan, not only for a single
|
|
2505
|
+
// delegation: a workflow naming an agent the chat doesn't allow is not submitted at all.
|
|
2506
|
+
const disallowedAgents = FindDisallowedTaskGraphAgents(Array.isArray(taskGraph.tasks) ? taskGraph.tasks : [], this.AllowedAgentIDs, agentName => this.agentIdByName(agentName));
|
|
2507
|
+
if (disallowedAgents.length > 0) {
|
|
2508
|
+
await this.refuseDisallowedAgents(userMessage, disallowedAgents);
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
1669
2511
|
const workflowName = taskGraph.workflowName || 'Workflow';
|
|
1670
2512
|
const reasoning = taskGraph.reasoning || 'Executing multi-step workflow';
|
|
1671
2513
|
const taskCount = Array.isArray(taskGraph.tasks) ? taskGraph.tasks.length : 0;
|
|
1672
2514
|
// A message the user can watch. Progress frames from the dispatcher land against this ID.
|
|
1673
|
-
const taskExecutionMessage = await this.dataCache.createConversationDetail(this.
|
|
2515
|
+
const taskExecutionMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1674
2516
|
taskExecutionMessage.ConversationID = conversationId;
|
|
1675
2517
|
taskExecutionMessage.Role = 'AI';
|
|
1676
2518
|
taskExecutionMessage.Message = `⏳ **${workflowName}**\n\n${reasoning}\n\nSubmitting ${taskCount} task(s)…`;
|
|
@@ -1678,7 +2520,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1678
2520
|
taskExecutionMessage.Status = 'In-Progress';
|
|
1679
2521
|
taskExecutionMessage.HiddenToUser = false;
|
|
1680
2522
|
await taskExecutionMessage.Save();
|
|
1681
|
-
this.
|
|
2523
|
+
this.MessageSent.emit(taskExecutionMessage);
|
|
1682
2524
|
const callback = this.createMessageProgressCallback(taskExecutionMessage.ID);
|
|
1683
2525
|
this.registeredCallbacks.set(taskExecutionMessage.ID, callback);
|
|
1684
2526
|
this.streamingService.registerMessageCallback(taskExecutionMessage.ID, callback);
|
|
@@ -1728,7 +2570,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1728
2570
|
await convoDetail.Load(convoDetail.ID);
|
|
1729
2571
|
if (convoDetail.Status === 'Complete' || convoDetail.Status === 'Error') {
|
|
1730
2572
|
this.markMessageComplete(convoDetail);
|
|
1731
|
-
this.
|
|
2573
|
+
this.MessageSent.emit(convoDetail);
|
|
1732
2574
|
return;
|
|
1733
2575
|
}
|
|
1734
2576
|
}
|
|
@@ -1755,7 +2597,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1755
2597
|
await convoDetail.Save();
|
|
1756
2598
|
if (convoDetail.Message === message && convoDetail.Status === status) {
|
|
1757
2599
|
done = true;
|
|
1758
|
-
this.
|
|
2600
|
+
this.MessageSent.emit(convoDetail);
|
|
1759
2601
|
}
|
|
1760
2602
|
else {
|
|
1761
2603
|
console.warn(` ⚠️ ConversationDetail update attempt ${attempts + 1} did not persist. ${attempts + 1 < maxAttempts ? 'Retrying...' : 'Giving up.'}`);
|
|
@@ -1778,10 +2620,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1778
2620
|
* Direction, not on Visibility.
|
|
1779
2621
|
*/
|
|
1780
2622
|
async loadPreviousPayloadForAgent(agentId) {
|
|
1781
|
-
if (!this.
|
|
2623
|
+
if (!this.ConversationId) {
|
|
1782
2624
|
return { payload: null, artifactInfo: null };
|
|
1783
2625
|
}
|
|
1784
|
-
const source = await this.agentService.FindLatestAgentOutputVersion(this.
|
|
2626
|
+
const source = await this.agentService.FindLatestAgentOutputVersion(this.ConversationId, agentId, this.AgentHistoryFrom);
|
|
1785
2627
|
if (!source || source.payload == null) {
|
|
1786
2628
|
console.log(`📦 No previous payload found for agent ${agentId}`);
|
|
1787
2629
|
return { payload: null, artifactInfo: null };
|
|
@@ -1809,8 +2651,14 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1809
2651
|
try {
|
|
1810
2652
|
// Look up the agent to get its ID
|
|
1811
2653
|
const agent = AIEngineBase.Instance.Agents.find(a => a.Name === agentName);
|
|
2654
|
+
// The manager routes only among the allowed agents, but its answer is model output: hold
|
|
2655
|
+
// the host's list here too, before any row is written for the delegate.
|
|
2656
|
+
if (this.AllowedAgentIDs != null && !IsAgentAllowed(agent?.ID, this.AllowedAgentIDs)) {
|
|
2657
|
+
await this.refuseDisallowedAgents(userMessage, [agentName]);
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
1812
2660
|
// Create AI response message BEFORE invoking agent (for duration tracking)
|
|
1813
|
-
agentResponseMessage = await this.dataCache.createConversationDetail(this.
|
|
2661
|
+
agentResponseMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1814
2662
|
agentResponseMessage.ConversationID = conversationId;
|
|
1815
2663
|
agentResponseMessage.Role = 'AI';
|
|
1816
2664
|
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
@@ -1823,7 +2671,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1823
2671
|
}
|
|
1824
2672
|
// Save the record to establish __mj_CreatedAt timestamp
|
|
1825
2673
|
await agentResponseMessage.Save();
|
|
1826
|
-
this.
|
|
2674
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
1827
2675
|
// Add sub-agent to active tasks
|
|
1828
2676
|
const newTaskId = this.activeTasks.add({
|
|
1829
2677
|
agentName: agentName,
|
|
@@ -1831,7 +2679,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1831
2679
|
relatedMessageId: userMessage.ID,
|
|
1832
2680
|
conversationDetailId: agentResponseMessage.ID,
|
|
1833
2681
|
conversationId,
|
|
1834
|
-
conversationName: this.
|
|
2682
|
+
conversationName: this.ConversationName
|
|
1835
2683
|
});
|
|
1836
2684
|
// Load previous payload if agent has been invoked before
|
|
1837
2685
|
const { payload: previousPayload, artifactInfo } = agent?.ID
|
|
@@ -1842,11 +2690,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1842
2690
|
? await this.agentService.FindConfigurationPresetForAgent(conversationId, agent.ID)
|
|
1843
2691
|
: undefined;
|
|
1844
2692
|
// Invoke the sub-agent with progress callback
|
|
1845
|
-
const subResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.
|
|
2693
|
+
const subResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
|
|
1846
2694
|
this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId, // Pass configuration from previous @mention for continuity
|
|
1847
|
-
this.
|
|
2695
|
+
this.AppContext, // Embedder-supplied app/form context
|
|
1848
2696
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
1849
|
-
this._pendingRequestedSkillIDs)
|
|
2697
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2698
|
+
this.AgentHistoryFrom);
|
|
1850
2699
|
// Task will be removed automatically in markMessageComplete() when status changes to Complete/Error
|
|
1851
2700
|
// DO NOT remove here - allows UI to show task during entire execution
|
|
1852
2701
|
if (subResult && subResult.success) {
|
|
@@ -1860,7 +2709,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1860
2709
|
// artifacts even when the result payload is empty (e.g., remote stage server).
|
|
1861
2710
|
// onArtifactCreated will reload from DB and discover any artifacts that exist.
|
|
1862
2711
|
this.emitArtifactReload(agentResponseMessage);
|
|
1863
|
-
this.
|
|
2712
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
1864
2713
|
// Mark user message as complete
|
|
1865
2714
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1866
2715
|
}
|
|
@@ -1877,11 +2726,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1877
2726
|
// Update the existing agentResponseMessage to show retry status
|
|
1878
2727
|
await this.updateConversationDetail(agentResponseMessage, "Retrying...", agentResponseMessage.Status);
|
|
1879
2728
|
// Retry the sub-agent (reuse previously loaded payload and config from first attempt)
|
|
1880
|
-
const retryResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.
|
|
2729
|
+
const retryResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass same payload as first attempt
|
|
1881
2730
|
this.createProgressCallback(agentResponseMessage, `${agentName} (retry)`), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId, // Pass same config as first attempt
|
|
1882
|
-
this.
|
|
2731
|
+
this.AppContext, // Embedder-supplied app/form context
|
|
1883
2732
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
1884
|
-
this._pendingRequestedSkillIDs)
|
|
2733
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2734
|
+
this.AgentHistoryFrom);
|
|
1885
2735
|
if (retryResult && retryResult.success) {
|
|
1886
2736
|
// Retry succeeded - update the same message
|
|
1887
2737
|
if (retryResult.agentRun.AgentID) {
|
|
@@ -1890,7 +2740,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1890
2740
|
await this.updateConversationDetail(agentResponseMessage, retryResult.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', retryResult);
|
|
1891
2741
|
// Always emit artifactCreated to trigger UI reload (same as initial attempt)
|
|
1892
2742
|
this.emitArtifactReload(agentResponseMessage);
|
|
1893
|
-
this.
|
|
2743
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
1894
2744
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1895
2745
|
}
|
|
1896
2746
|
else {
|
|
@@ -1929,19 +2779,19 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1929
2779
|
* check if we should continue with the last agent for iterative refinement
|
|
1930
2780
|
*/
|
|
1931
2781
|
async handleSilentObservation(userMessage, conversationId) {
|
|
1932
|
-
// Find the last AI message (excluding Sage)
|
|
1933
|
-
const lastAIMessage = this.
|
|
1934
|
-
.slice()
|
|
1935
|
-
.reverse()
|
|
1936
|
-
.find(msg => msg.Role === 'AI' &&
|
|
1937
|
-
msg.AgentID &&
|
|
1938
|
-
!UUIDsEqual(msg.AgentID, this.converationManagerAgent?.ID));
|
|
2782
|
+
// Find the last AI message (excluding Sage) this turn may read
|
|
2783
|
+
const lastAIMessage = this.findLastNonSageReply();
|
|
1939
2784
|
if (!lastAIMessage || !lastAIMessage.AgentID) {
|
|
1940
2785
|
// No previous specialist agent - just mark user message as complete
|
|
1941
2786
|
console.log('🔇 No previous specialist agent found - marking complete');
|
|
1942
2787
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
1943
2788
|
return;
|
|
1944
2789
|
}
|
|
2790
|
+
if (!IsAgentAllowed(lastAIMessage.AgentID, this.AllowedAgentIDs)) {
|
|
2791
|
+
console.log('🔇 Previous specialist agent is not allowed in this chat - marking complete');
|
|
2792
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2793
|
+
return;
|
|
2794
|
+
}
|
|
1945
2795
|
// Load the agent entity to get its name
|
|
1946
2796
|
const previousAgent = AIEngineBase.Instance.Agents.find(a => UUIDsEqual(a.ID, lastAIMessage.AgentID));
|
|
1947
2797
|
if (!previousAgent) {
|
|
@@ -1955,7 +2805,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1955
2805
|
// Resolved by QUERY, not from the window's artifact maps: `lastAIMessage` can sit inside
|
|
1956
2806
|
// the loaded window while its artifact does not, and this path silently degrades to a
|
|
1957
2807
|
// null payload when the lookup misses.
|
|
1958
|
-
const source = await this.agentService.FindLatestAgentOutputVersion(conversationId, lastAIMessage.AgentID);
|
|
2808
|
+
const source = await this.agentService.FindLatestAgentOutputVersion(conversationId, lastAIMessage.AgentID, this.AgentHistoryFrom);
|
|
1959
2809
|
if (source && source.payload != null) {
|
|
1960
2810
|
previousPayload = source.payload;
|
|
1961
2811
|
previousArtifactInfo = {
|
|
@@ -1966,16 +2816,16 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1966
2816
|
console.log('📦 Loaded previous OUTPUT artifact as payload for continuity', previousArtifactInfo);
|
|
1967
2817
|
}
|
|
1968
2818
|
// Create status message showing agent continuity
|
|
1969
|
-
const statusMessage = await this.dataCache.createConversationDetail(this.
|
|
2819
|
+
const statusMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1970
2820
|
statusMessage.ConversationID = conversationId;
|
|
1971
2821
|
statusMessage.Role = 'AI';
|
|
1972
2822
|
statusMessage.Message = `Continuing with **${agentName}** for refinement...`;
|
|
1973
2823
|
statusMessage.ParentID = userMessage.ID;
|
|
1974
2824
|
statusMessage.Status = 'Complete';
|
|
1975
2825
|
statusMessage.HiddenToUser = false;
|
|
1976
|
-
statusMessage.AgentID = this.
|
|
2826
|
+
statusMessage.AgentID = this.ConverationManagerAgent?.ID || null;
|
|
1977
2827
|
await statusMessage.Save();
|
|
1978
|
-
this.
|
|
2828
|
+
this.MessageSent.emit(statusMessage);
|
|
1979
2829
|
// Add agent to active tasks
|
|
1980
2830
|
const taskId = this.activeTasks.add({
|
|
1981
2831
|
agentName: agentName,
|
|
@@ -1983,19 +2833,20 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1983
2833
|
relatedMessageId: userMessage.ID,
|
|
1984
2834
|
conversationDetailId: statusMessage.ID,
|
|
1985
2835
|
conversationId,
|
|
1986
|
-
conversationName: this.
|
|
2836
|
+
conversationName: this.ConversationName
|
|
1987
2837
|
});
|
|
1988
2838
|
try {
|
|
1989
2839
|
// Invoke the agent with the previous payload
|
|
1990
|
-
const continuityResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.
|
|
1991
|
-
this.
|
|
2840
|
+
const continuityResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous work based on user feedback', statusMessage.ID, previousPayload, this.createProgressCallback(statusMessage, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, undefined, // configurationPresetId not used in this path
|
|
2841
|
+
this.AppContext, // Embedder-supplied app/form context
|
|
1992
2842
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
1993
|
-
this._pendingRequestedSkillIDs)
|
|
2843
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2844
|
+
this.AgentHistoryFrom);
|
|
1994
2845
|
// Remove from active tasks
|
|
1995
2846
|
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
1996
2847
|
if (continuityResult && continuityResult.success) {
|
|
1997
2848
|
// Create response message
|
|
1998
|
-
const agentResponseMessage = await this.dataCache.createConversationDetail(this.
|
|
2849
|
+
const agentResponseMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1999
2850
|
agentResponseMessage.ConversationID = conversationId;
|
|
2000
2851
|
agentResponseMessage.Role = 'AI';
|
|
2001
2852
|
agentResponseMessage.Message = continuityResult.agentRun?.Message || `✅ **${agentName}** completed refinement`;
|
|
@@ -2004,12 +2855,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2004
2855
|
agentResponseMessage.HiddenToUser = false;
|
|
2005
2856
|
agentResponseMessage.AgentID = lastAIMessage.AgentID;
|
|
2006
2857
|
await agentResponseMessage.Save();
|
|
2007
|
-
this.
|
|
2858
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2008
2859
|
// Server created artifacts (handles versioning automatically) - emit event to trigger UI reload
|
|
2009
2860
|
if (continuityResult.payload && Object.keys(continuityResult.payload).length > 0) {
|
|
2010
2861
|
this.emitArtifactReload(agentResponseMessage);
|
|
2011
2862
|
console.log('🎨 Server created artifact (versioned) from agent continuity');
|
|
2012
|
-
this.
|
|
2863
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2013
2864
|
}
|
|
2014
2865
|
// Mark user message as complete
|
|
2015
2866
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
@@ -2037,7 +2888,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2037
2888
|
relatedMessageId: userMessage.ID,
|
|
2038
2889
|
conversationDetailId: userMessage.ID,
|
|
2039
2890
|
conversationId,
|
|
2040
|
-
conversationName: this.
|
|
2891
|
+
conversationName: this.ConversationName
|
|
2041
2892
|
});
|
|
2042
2893
|
// Declare agentResponseMessage outside try block so it's accessible in catch
|
|
2043
2894
|
let agentResponseMessage = undefined;
|
|
@@ -2046,11 +2897,11 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2046
2897
|
// (No UI uses User message 'In-Progress' - only AI messages need that status)
|
|
2047
2898
|
userMessage.Status = 'Complete';
|
|
2048
2899
|
await userMessage.Save();
|
|
2049
|
-
this.
|
|
2900
|
+
this.MessageSent.emit(userMessage);
|
|
2050
2901
|
// Look up the agent to get its ID
|
|
2051
2902
|
const agent = AIEngineBase.Instance.Agents.find(a => a.Name === agentName);
|
|
2052
2903
|
// Create AI response message BEFORE invoking agent (for duration tracking)
|
|
2053
|
-
agentResponseMessage = await this.dataCache.createConversationDetail(this.
|
|
2904
|
+
agentResponseMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
2054
2905
|
agentResponseMessage.ConversationID = conversationId;
|
|
2055
2906
|
agentResponseMessage.Role = 'AI';
|
|
2056
2907
|
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
@@ -2063,23 +2914,25 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2063
2914
|
}
|
|
2064
2915
|
// Save the record to establish __mj_CreatedAt timestamp
|
|
2065
2916
|
await agentResponseMessage.Save();
|
|
2066
|
-
this.
|
|
2917
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2067
2918
|
// Load previous payload if agent has been invoked before
|
|
2068
2919
|
const { payload: previousPayload, artifactInfo } = agent?.ID
|
|
2069
2920
|
? await this.loadPreviousPayloadForAgent(agent.ID)
|
|
2070
2921
|
: { payload: null, artifactInfo: null };
|
|
2071
2922
|
// Invoke the agent directly
|
|
2072
|
-
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.
|
|
2923
|
+
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, `User mentioned agent directly with @${agentName}`, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
|
|
2073
2924
|
this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId, agentMention.configurationId, // Pass configuration preset ID
|
|
2074
|
-
this.
|
|
2925
|
+
this.AppContext, // Embedder-supplied app/form context
|
|
2075
2926
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2076
|
-
this._pendingRequestedSkillIDs)
|
|
2927
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2928
|
+
this.AgentHistoryFrom);
|
|
2077
2929
|
// Remove from active tasks
|
|
2078
2930
|
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2079
2931
|
if (result && result.success) {
|
|
2080
2932
|
if (result.agentRun.AgentID) {
|
|
2081
2933
|
agentResponseMessage.AgentID = result.agentRun.AgentID;
|
|
2082
2934
|
}
|
|
2935
|
+
this.emitAfterAgentTurn(conversationId, result);
|
|
2083
2936
|
// Multi-stage response handling (same logic as ambient Sage)
|
|
2084
2937
|
// Stage 1: Check for task graph (multi-step orchestration)
|
|
2085
2938
|
if (result.payload?.taskGraph) {
|
|
@@ -2097,7 +2950,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2097
2950
|
// Server created artifacts - emit event to trigger UI reload
|
|
2098
2951
|
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
2099
2952
|
this.emitArtifactReload(agentResponseMessage);
|
|
2100
|
-
this.
|
|
2953
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2101
2954
|
}
|
|
2102
2955
|
// Mark user message as complete
|
|
2103
2956
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
@@ -2127,6 +2980,11 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2127
2980
|
// Load the agent entity to get its name
|
|
2128
2981
|
const agent = AIEngineBase.Instance.Agents.find(a => UUIDsEqual(a.ID, agentId));
|
|
2129
2982
|
if (!agent) {
|
|
2983
|
+
if (!IsAgentAllowed(this.ConverationManagerAgent?.ID, this.AllowedAgentIDs)) {
|
|
2984
|
+
console.warn('⚠️ Could not load agent for continuation, and Sage is not allowed in this chat');
|
|
2985
|
+
await this.finishWithoutAgentTurn(userMessage, 'Declined');
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2130
2988
|
console.warn('⚠️ Could not load agent for continuation - falling back to Sage');
|
|
2131
2989
|
await this.processMessageThroughAgent(userMessage, { mentions: [], agentMention: null, userMentions: [], entityMentions: [], skillMentions: [] });
|
|
2132
2990
|
return;
|
|
@@ -2163,8 +3021,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2163
3021
|
// High) and applies to all subsequent non-mention routes. A
|
|
2164
3022
|
// history-derived preset still wins because it represents an
|
|
2165
3023
|
// explicit per-message intent the user expressed earlier.
|
|
2166
|
-
if (!previousConfigurationId && this.
|
|
2167
|
-
previousConfigurationId = this.
|
|
3024
|
+
if (!previousConfigurationId && this.AgentConfigurationPresetId) {
|
|
3025
|
+
previousConfigurationId = this.AgentConfigurationPresetId;
|
|
2168
3026
|
}
|
|
2169
3027
|
// Fall back to searching through all agent messages for an artifact
|
|
2170
3028
|
// This ensures payload continuity even after clarifying exchanges without artifacts
|
|
@@ -2173,7 +3031,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2173
3031
|
// used to walk is the loaded window, so the artifact it is looking for is exactly the
|
|
2174
3032
|
// one most likely to be missing from it.
|
|
2175
3033
|
if (!previousPayload) {
|
|
2176
|
-
const source = await this.agentService.FindLatestAgentOutputVersion(conversationId, agentId);
|
|
3034
|
+
const source = await this.agentService.FindLatestAgentOutputVersion(conversationId, agentId, this.AgentHistoryFrom);
|
|
2177
3035
|
if (source && source.payload != null) {
|
|
2178
3036
|
previousPayload = source.payload;
|
|
2179
3037
|
previousArtifactInfo = {
|
|
@@ -2210,7 +3068,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2210
3068
|
relatedMessageId: userMessage.ID,
|
|
2211
3069
|
conversationDetailId: userMessage.ID,
|
|
2212
3070
|
conversationId,
|
|
2213
|
-
conversationName: this.
|
|
3071
|
+
conversationName: this.ConversationName
|
|
2214
3072
|
});
|
|
2215
3073
|
// Declare agentResponseMessage outside try block so it's accessible in catch
|
|
2216
3074
|
let agentResponseMessage = undefined;
|
|
@@ -2219,9 +3077,9 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2219
3077
|
// (No UI uses User message 'In-Progress' - only AI messages need that status)
|
|
2220
3078
|
userMessage.Status = 'Complete';
|
|
2221
3079
|
await userMessage.Save();
|
|
2222
|
-
this.
|
|
3080
|
+
this.MessageSent.emit(userMessage);
|
|
2223
3081
|
// Create AI response message BEFORE invoking agent (for duration tracking)
|
|
2224
|
-
agentResponseMessage = await this.dataCache.createConversationDetail(this.
|
|
3082
|
+
agentResponseMessage = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
2225
3083
|
agentResponseMessage.ConversationID = conversationId;
|
|
2226
3084
|
agentResponseMessage.Role = 'AI';
|
|
2227
3085
|
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
@@ -2231,26 +3089,28 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2231
3089
|
agentResponseMessage.AgentID = agentId;
|
|
2232
3090
|
// Save the record to establish __mj_CreatedAt timestamp
|
|
2233
3091
|
await agentResponseMessage.Save();
|
|
2234
|
-
this.
|
|
3092
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2235
3093
|
// Invoke the agent directly (continuation) with previous payload if available.
|
|
2236
3094
|
// `this.appContext` is forwarded so direct-routed sub-agents (e.g. Form
|
|
2237
3095
|
// Builder via [defaultAgentId]) see the embedder's ActiveForm/Schema/
|
|
2238
3096
|
// OverrideID block in their prompt — same flow Sage gets via
|
|
2239
3097
|
// `processMessage`.
|
|
2240
|
-
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.
|
|
3098
|
+
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous conversation with user', agentResponseMessage.ID, previousPayload, // Pass previous OUTPUT artifact payload for continuity
|
|
2241
3099
|
this.createProgressCallback(agentResponseMessage, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, configurationId, // Pass configuration for continuity
|
|
2242
|
-
this.
|
|
3100
|
+
this.AppContext, // Embedder-supplied app/form context
|
|
2243
3101
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2244
|
-
this._pendingRequestedSkillIDs)
|
|
3102
|
+
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
3103
|
+
this.AgentHistoryFrom);
|
|
2245
3104
|
// Remove from active tasks
|
|
2246
3105
|
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2247
3106
|
if (result && result.success) {
|
|
3107
|
+
this.emitAfterAgentTurn(conversationId, result);
|
|
2248
3108
|
// Update the response message with agent result
|
|
2249
3109
|
await this.updateConversationDetail(agentResponseMessage, result.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', result);
|
|
2250
3110
|
// Server created artifacts (handles versioning) - emit event to trigger UI reload
|
|
2251
3111
|
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
2252
3112
|
this.emitArtifactReload(agentResponseMessage);
|
|
2253
|
-
this.
|
|
3113
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
2254
3114
|
}
|
|
2255
3115
|
// Mark user message as complete
|
|
2256
3116
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
@@ -2284,11 +3144,11 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2284
3144
|
ConversationId: conversationId,
|
|
2285
3145
|
MessageText: plainTextMessage,
|
|
2286
3146
|
Provider: this.ProviderToUse,
|
|
2287
|
-
CurrentUser: this.
|
|
3147
|
+
CurrentUser: this.CurrentUser
|
|
2288
3148
|
});
|
|
2289
3149
|
if (result) {
|
|
2290
3150
|
// Emit event for animation in conversation list
|
|
2291
|
-
this.
|
|
3151
|
+
this.ConversationRenamed.emit({
|
|
2292
3152
|
conversationId,
|
|
2293
3153
|
name: result.Name,
|
|
2294
3154
|
description: result.Description
|
|
@@ -2352,7 +3212,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2352
3212
|
if (detail.Status === 'Complete' || detail.Status === 'Error') {
|
|
2353
3213
|
this.stopInFlightDetailWatch(detail.ID);
|
|
2354
3214
|
this.markMessageComplete(detail);
|
|
2355
|
-
this.
|
|
3215
|
+
this.MessageSent.emit(detail);
|
|
2356
3216
|
return;
|
|
2357
3217
|
}
|
|
2358
3218
|
}
|
|
@@ -2444,7 +3304,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2444
3304
|
LogStatusEx({ message: `[MarkComplete] No task registered against completed message ${conversationDetail.ID} — expected for any message that did not start the turn`, verboseOnly: true });
|
|
2445
3305
|
}
|
|
2446
3306
|
// Emit completion event to parent so it can refresh agent run data
|
|
2447
|
-
this.
|
|
3307
|
+
this.MessageComplete.emit({
|
|
2448
3308
|
conversationId: conversationDetail.ConversationID,
|
|
2449
3309
|
conversationDetailId: conversationDetail.ID,
|
|
2450
3310
|
agentId: conversationDetail.AgentID || undefined
|
|
@@ -2458,7 +3318,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2458
3318
|
* detail entity's immutable ConversationID, never this.conversationId.
|
|
2459
3319
|
*/
|
|
2460
3320
|
emitArtifactReload(detail) {
|
|
2461
|
-
this.
|
|
3321
|
+
this.ArtifactCreated.emit({
|
|
2462
3322
|
conversationId: detail.ConversationID,
|
|
2463
3323
|
artifactId: '',
|
|
2464
3324
|
versionId: '',
|
|
@@ -2481,8 +3341,8 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2481
3341
|
i0.ɵɵviewQuery(_c0, 5);
|
|
2482
3342
|
} if (rf & 2) {
|
|
2483
3343
|
let _t;
|
|
2484
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.
|
|
2485
|
-
} }, inputs: { conversationId: "conversationId", conversationName: "conversationName", currentUser: "currentUser", disabled: "disabled", placeholder: "placeholder", parentMessageId: "parentMessageId", enableAttachments: "enableAttachments", enableMentions: "enableMentions", enableAgentMentions: "enableAgentMentions", enableEntityMentions: "enableEntityMentions", enableSkillCommands: "enableSkillCommands", enablePlanMode: "enablePlanMode", enableRealtime: "enableRealtime", maxAttachments: "maxAttachments", maxAttachmentSizeBytes: "maxAttachmentSizeBytes", acceptedFileTypes: "acceptedFileTypes", artifactsByDetailId: "artifactsByDetailId", systemArtifactsByDetailId: "systemArtifactsByDetailId", agentRunsByDetailId: "agentRunsByDetailId", emptyStateMode: "emptyStateMode", appContext: "appContext", defaultAgentId: "defaultAgentId", conversationDefaultAgentId: "conversationDefaultAgentId", agentConfigurationPresetId: "agentConfigurationPresetId", initialMessage: "initialMessage", initialAttachments: "initialAttachments", conversationHistory: "conversationHistory", inProgressMessageIds: "inProgressMessageIds", applicationId: "applicationId", initialDraft: "initialDraft" }, outputs: { messageSent: "messageSent", agentResponse: "agentResponse", beforeAgentTurn: "beforeAgentTurn", afterAgentTurn: "afterAgentTurn", agentRunDetected: "agentRunDetected", agentRunUpdate: "agentRunUpdate", messageComplete: "messageComplete", artifactCreated: "artifactCreated", conversationRenamed: "conversationRenamed", intentCheckStarted: "intentCheckStarted", intentCheckCompleted: "intentCheckCompleted", initialMessageAutoSendStarted: "initialMessageAutoSendStarted", initialMessageAutoSendFailed: "initialMessageAutoSendFailed", emptyStateSubmit: "emptyStateSubmit", uploadStateChanged: "uploadStateChanged", initialDraftApplied: "initialDraftApplied", DraftStateChanged: "DraftStateChanged", ComposerBlurred: "ComposerBlurred" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 6, vars:
|
|
3344
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.InputBox = _t.first);
|
|
3345
|
+
} }, inputs: { ConversationId: "ConversationId", conversationId: "conversationId", ConversationName: "ConversationName", conversationName: "conversationName", CurrentUser: "CurrentUser", currentUser: "currentUser", Disabled: "Disabled", disabled: "disabled", ReadOnly: "ReadOnly", Placeholder: "Placeholder", placeholder: "placeholder", ParentMessageId: "ParentMessageId", parentMessageId: "parentMessageId", EnableAttachments: "EnableAttachments", enableAttachments: "enableAttachments", EnableMentions: "EnableMentions", enableMentions: "enableMentions", EnableAgentMentions: "EnableAgentMentions", enableAgentMentions: "enableAgentMentions", EnableEntityMentions: "EnableEntityMentions", enableEntityMentions: "enableEntityMentions", EnableSkillCommands: "EnableSkillCommands", enableSkillCommands: "enableSkillCommands", EnablePlanMode: "EnablePlanMode", enablePlanMode: "enablePlanMode", EnableRealtime: "EnableRealtime", enableRealtime: "enableRealtime", MaxAttachments: "MaxAttachments", maxAttachments: "maxAttachments", MaxAttachmentSizeBytes: "MaxAttachmentSizeBytes", maxAttachmentSizeBytes: "maxAttachmentSizeBytes", AcceptedFileTypes: "AcceptedFileTypes", acceptedFileTypes: "acceptedFileTypes", ArtifactsByDetailId: "ArtifactsByDetailId", artifactsByDetailId: "artifactsByDetailId", SystemArtifactsByDetailId: "SystemArtifactsByDetailId", systemArtifactsByDetailId: "systemArtifactsByDetailId", AgentRunsByDetailId: "AgentRunsByDetailId", agentRunsByDetailId: "agentRunsByDetailId", EmptyStateMode: "EmptyStateMode", emptyStateMode: "emptyStateMode", AppContext: "AppContext", appContext: "appContext", DefaultAgentId: "DefaultAgentId", defaultAgentId: "defaultAgentId", ConversationDefaultAgentId: "ConversationDefaultAgentId", conversationDefaultAgentId: "conversationDefaultAgentId", AgentConfigurationPresetId: "AgentConfigurationPresetId", agentConfigurationPresetId: "agentConfigurationPresetId", InitialMessage: "InitialMessage", initialMessage: "initialMessage", InitialAttachments: "InitialAttachments", initialAttachments: "initialAttachments", ConversationHistory: "ConversationHistory", conversationHistory: "conversationHistory", InProgressMessageIds: "InProgressMessageIds", inProgressMessageIds: "inProgressMessageIds", ApplicationId: "ApplicationId", applicationId: "applicationId", AgentReplyMode: "AgentReplyMode", AllowedAgentIDs: "AllowedAgentIDs", MentionPeople: "MentionPeople", AgentHistoryFrom: "AgentHistoryFrom", AgentTurnHandler: "AgentTurnHandler", AutoNameConversation: "AutoNameConversation", EnableDecisionRouting: "EnableDecisionRouting", InitialDraft: "InitialDraft", initialDraft: "initialDraft" }, outputs: { MessageSent: "MessageSent", messageSent: "messageSent", AgentResponse: "AgentResponse", agentResponse: "agentResponse", BeforeAgentTurn: "BeforeAgentTurn", beforeAgentTurn: "beforeAgentTurn", AfterAgentTurn: "AfterAgentTurn", afterAgentTurn: "afterAgentTurn", AgentRunDetected: "AgentRunDetected", agentRunDetected: "agentRunDetected", AgentRunUpdate: "AgentRunUpdate", agentRunUpdate: "agentRunUpdate", MessageComplete: "MessageComplete", messageComplete: "messageComplete", ArtifactCreated: "ArtifactCreated", artifactCreated: "artifactCreated", ConversationRenamed: "ConversationRenamed", conversationRenamed: "conversationRenamed", IntentCheckStarted: "IntentCheckStarted", intentCheckStarted: "intentCheckStarted", IntentCheckCompleted: "IntentCheckCompleted", intentCheckCompleted: "intentCheckCompleted", InitialMessageAutoSendStarted: "InitialMessageAutoSendStarted", initialMessageAutoSendStarted: "initialMessageAutoSendStarted", InitialMessageAutoSendFailed: "InitialMessageAutoSendFailed", initialMessageAutoSendFailed: "initialMessageAutoSendFailed", EmptyStateSubmit: "EmptyStateSubmit", emptyStateSubmit: "emptyStateSubmit", UploadStateChanged: "UploadStateChanged", uploadStateChanged: "uploadStateChanged", InitialDraftApplied: "InitialDraftApplied", initialDraftApplied: "initialDraftApplied", DraftStateChanged: "DraftStateChanged", ComposerBlurred: "ComposerBlurred" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 6, vars: 29, consts: [["pickerOrigin", "cdkOverlayOrigin"], ["inputBox", ""], ["cdkOverlayOrigin", "", 1, "message-input-wrapper"], [1, "processing-indicator"], [3, "valueChange", "blurred", "textSubmitted", "attachmentsChanged", "attachmentError", "voiceRequested", "voiceOptionsRequested", "planModeToggle", "placeholder", "disabled", "showCharacterCount", "enableMentions", "EnableAgentMentions", "EnableEntityMentions", "EnableSkillCommands", "TargetAgentId", "AllowedAgentIDs", "MentionPeople", "enableAttachments", "maxAttachments", "maxAttachmentSizeBytes", "acceptedFileTypes", "currentUser", "Provider", "rows", "enableRealtime", "voiceActive", "canStartRealtime", "enablePlanMode", "planModeActive", "value"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "cdk-overlay-transparent-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPush", "cdkConnectedOverlayHasBackdrop"], [1, "fas", "fa-circle-notch", "fa-spin"], [3, "AgentPicked", "Cancelled", "Provider", "CurrentUser", "Agents", "DefaultAgentId", "CoAgents", "DefaultCoAgentId"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
|
|
2486
3346
|
i0.ɵɵelementStart(0, "div", 2, 0);
|
|
2487
3347
|
i0.ɵɵconditionalCreate(2, MessageInputComponent_Conditional_2_Template, 4, 1, "div", 3);
|
|
2488
3348
|
i0.ɵɵelementStart(3, "mj-ai-composer", 4, 1);
|
|
@@ -2496,107 +3356,215 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2496
3356
|
i0.ɵɵadvance(2);
|
|
2497
3357
|
i0.ɵɵconditional(ctx.isProcessing ? 2 : -1);
|
|
2498
3358
|
i0.ɵɵadvance();
|
|
2499
|
-
i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isProcessing)("showCharacterCount", false)("enableMentions", ctx.enableMentions)("EnableAgentMentions", ctx.enableAgentMentions)("EnableEntityMentions", ctx.enableEntityMentions)("EnableSkillCommands", ctx.enableSkillCommands)("TargetAgentId", ctx.pickerTargetAgentId)("enableAttachments", ctx.enableAttachments)("maxAttachments", ctx.maxAttachments)("maxAttachmentSizeBytes", ctx.maxAttachmentSizeBytes)("acceptedFileTypes", ctx.acceptedFileTypes)("currentUser", ctx.currentUser)("Provider", ctx.Provider)("rows", 3)("enableRealtime", ctx.enableRealtime)("voiceActive", ctx.voiceActive)("canStartRealtime", ctx.canStartRealtime)("enablePlanMode", ctx.enablePlanMode)("planModeActive", ctx.PlanModeEnabled)("value", ctx.messageText);
|
|
3359
|
+
i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isProcessing)("showCharacterCount", false)("enableMentions", ctx.enableMentions)("EnableAgentMentions", ctx.enableAgentMentions)("EnableEntityMentions", ctx.enableEntityMentions)("EnableSkillCommands", ctx.enableSkillCommands)("TargetAgentId", ctx.pickerTargetAgentId)("AllowedAgentIDs", ctx.AllowedAgentIDs)("MentionPeople", ctx.MentionPeople)("enableAttachments", ctx.enableAttachments)("maxAttachments", ctx.maxAttachments)("maxAttachmentSizeBytes", ctx.maxAttachmentSizeBytes)("acceptedFileTypes", ctx.acceptedFileTypes)("currentUser", ctx.currentUser)("Provider", ctx.Provider)("rows", 3)("enableRealtime", ctx.enableRealtime)("voiceActive", ctx.voiceActive)("canStartRealtime", ctx.canStartRealtime)("enablePlanMode", ctx.enablePlanMode)("planModeActive", ctx.PlanModeEnabled)("value", ctx.messageText);
|
|
2500
3360
|
i0.ɵɵadvance(2);
|
|
2501
3361
|
i0.ɵɵproperty("cdkConnectedOverlayOrigin", pickerOrigin_r3)("cdkConnectedOverlayOpen", ctx.showRealtimeAgentPicker)("cdkConnectedOverlayPositions", ctx.pickerOverlayPositions)("cdkConnectedOverlayPush", true)("cdkConnectedOverlayHasBackdrop", true);
|
|
2502
3362
|
} }, dependencies: [i11.CdkConnectedOverlay, i11.CdkOverlayOrigin, i12.RealtimeAgentPickerComponent, i13.AiComposerComponent], styles: [".message-input-wrapper[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\n}\n\n\n\n\n.processing-indicator[_ngcontent-%COMP%] {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n background: color-mix(in srgb, var(--mj-bg-surface-card) 95%, transparent);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.processing-indicator[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}"] });
|
|
2503
3363
|
}
|
|
2504
3364
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputComponent, [{
|
|
2505
3365
|
type: Component,
|
|
2506
|
-
args: [{ standalone: false, selector: 'mj-message-input', template: "<div class=\"message-input-wrapper\" cdkOverlayOrigin #pickerOrigin=\"cdkOverlayOrigin\">\n <!-- Processing Indicator Overlay -->\n @if (isProcessing) {\n <div class=\"processing-indicator\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>{{ processingMessage }}</span>\n </div>\n }\n\n <!-- AI composer (mic + Plan Mode buttons live in its in-composer toolbar): wraps the\n generic mj-message-input-box with the '@' agent / '#' record / '/' skill trigger\n plugins built in \u2014 all three on by default, preserving the standard chat behavior. -->\n <mj-ai-composer\n #inputBox\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isProcessing\"\n [showCharacterCount]=\"false\"\n [enableMentions]=\"enableMentions\"\n [EnableAgentMentions]=\"enableAgentMentions\"\n [EnableEntityMentions]=\"enableEntityMentions\"\n [EnableSkillCommands]=\"enableSkillCommands\"\n [TargetAgentId]=\"pickerTargetAgentId\"\n [enableAttachments]=\"enableAttachments\"\n [maxAttachments]=\"maxAttachments\"\n [maxAttachmentSizeBytes]=\"maxAttachmentSizeBytes\"\n [acceptedFileTypes]=\"acceptedFileTypes\"\n [currentUser]=\"currentUser\"\n [Provider]=\"Provider\"\n [rows]=\"3\"\n [enableRealtime]=\"enableRealtime\"\n [voiceActive]=\"voiceActive\"\n [canStartRealtime]=\"canStartRealtime\"\n [enablePlanMode]=\"enablePlanMode\"\n [planModeActive]=\"PlanModeEnabled\"\n [value]=\"messageText\"\n (valueChange)=\"OnComposerValueChanged($event)\"\n (blurred)=\"ComposerBlurred.emit()\"\n (textSubmitted)=\"onTextSubmitted($event)\"\n (attachmentsChanged)=\"onAttachmentsChanged($event)\"\n (attachmentError)=\"onAttachmentError($event)\"\n (voiceRequested)=\"onStartRealtime()\"\n (voiceOptionsRequested)=\"onRealtimeOptions()\"\n (planModeToggle)=\"TogglePlanMode()\">\n </mj-ai-composer>\n\n <!-- Voice agent/co-agent/model picker \u2014 shown when starting a call on a conversation\n with NO prior agent participation, or on demand via the caret next to the phone\n button (any conversation). Rendered in a body-level CDK overlay anchored above the\n composer so it POPS OUT of the chat overlay's clipping border instead of being cut\n off at the top of a narrow (~390px) overlay. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"pickerOrigin\"\n [cdkConnectedOverlayOpen]=\"showRealtimeAgentPicker\"\n [cdkConnectedOverlayPositions]=\"pickerOverlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (backdropClick)=\"onRealtimeAgentPickerCancelled()\"\n (detach)=\"onRealtimeAgentPickerCancelled()\">\n <mj-realtime-agent-picker\n [Provider]=\"ProviderToUse\"\n [CurrentUser]=\"currentUser\"\n [Agents]=\"voicePickerAgents\"\n [DefaultAgentId]=\"voicePickerDefaultAgentId\"\n [CoAgents]=\"voicePickerCoAgents\"\n [DefaultCoAgentId]=\"voicePickerDefaultCoAgentId\"\n (AgentPicked)=\"onRealtimeAgentPicked($event)\"\n (Cancelled)=\"onRealtimeAgentPickerCancelled()\">\n </mj-realtime-agent-picker>\n </ng-template>\n</div>\n\n<!-- The voice \"call mode\" overlay is hosted by <mj-conversation-chat-area> (it fills the\n whole conversation panel in place); this component only owns the trigger wiring. -->\n", styles: [".message-input-wrapper {\n position: relative;\n width: 100%;\n}\n\n/* Plan Mode moved into the in-composer toolbar (message-input-box) as a toggle icon button\n next to the attachment/voice buttons \u2014 see .plan-mode-button-icon there. */\n\n.processing-indicator {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n background: color-mix(in srgb, var(--mj-bg-surface-card) 95%, transparent);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator i {\n color: var(--mj-brand-primary);\n}\n.processing-indicator span {\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n"] }]
|
|
2507
|
-
}], () => [{ type: i1.DialogService }, { type: i2.ToastService }, { type: i3.ConversationAgentService }, { type: i4.DataCacheService }, { type: i5.ActiveTasksService }, { type: i6.ConversationStreamingService }, { type: i7.MentionParserService }, { type: i8.ConversationAttachmentService }, { type: i9.ConversationBridgeService }, { type: i10.RealtimeSessionService }], {
|
|
3366
|
+
args: [{ standalone: false, selector: 'mj-message-input', template: "<div class=\"message-input-wrapper\" cdkOverlayOrigin #pickerOrigin=\"cdkOverlayOrigin\">\n <!-- Processing Indicator Overlay -->\n @if (isProcessing) {\n <div class=\"processing-indicator\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>{{ processingMessage }}</span>\n </div>\n }\n\n <!-- AI composer (mic + Plan Mode buttons live in its in-composer toolbar): wraps the\n generic mj-message-input-box with the '@' agent / '#' record / '/' skill trigger\n plugins built in \u2014 all three on by default, preserving the standard chat behavior. -->\n <mj-ai-composer\n #inputBox\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isProcessing\"\n [showCharacterCount]=\"false\"\n [enableMentions]=\"enableMentions\"\n [EnableAgentMentions]=\"enableAgentMentions\"\n [EnableEntityMentions]=\"enableEntityMentions\"\n [EnableSkillCommands]=\"enableSkillCommands\"\n [TargetAgentId]=\"pickerTargetAgentId\"\n [AllowedAgentIDs]=\"AllowedAgentIDs\"\n [MentionPeople]=\"MentionPeople\"\n [enableAttachments]=\"enableAttachments\"\n [maxAttachments]=\"maxAttachments\"\n [maxAttachmentSizeBytes]=\"maxAttachmentSizeBytes\"\n [acceptedFileTypes]=\"acceptedFileTypes\"\n [currentUser]=\"currentUser\"\n [Provider]=\"Provider\"\n [rows]=\"3\"\n [enableRealtime]=\"enableRealtime\"\n [voiceActive]=\"voiceActive\"\n [canStartRealtime]=\"canStartRealtime\"\n [enablePlanMode]=\"enablePlanMode\"\n [planModeActive]=\"PlanModeEnabled\"\n [value]=\"messageText\"\n (valueChange)=\"OnComposerValueChanged($event)\"\n (blurred)=\"ComposerBlurred.emit()\"\n (textSubmitted)=\"onTextSubmitted($event)\"\n (attachmentsChanged)=\"onAttachmentsChanged($event)\"\n (attachmentError)=\"onAttachmentError($event)\"\n (voiceRequested)=\"onStartRealtime()\"\n (voiceOptionsRequested)=\"onRealtimeOptions()\"\n (planModeToggle)=\"TogglePlanMode()\">\n </mj-ai-composer>\n\n <!-- Voice agent/co-agent/model picker \u2014 shown when starting a call on a conversation\n with NO prior agent participation, or on demand via the caret next to the phone\n button (any conversation). Rendered in a body-level CDK overlay anchored above the\n composer so it POPS OUT of the chat overlay's clipping border instead of being cut\n off at the top of a narrow (~390px) overlay. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"pickerOrigin\"\n [cdkConnectedOverlayOpen]=\"showRealtimeAgentPicker\"\n [cdkConnectedOverlayPositions]=\"pickerOverlayPositions\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (backdropClick)=\"onRealtimeAgentPickerCancelled()\"\n (detach)=\"onRealtimeAgentPickerCancelled()\">\n <mj-realtime-agent-picker\n [Provider]=\"ProviderToUse\"\n [CurrentUser]=\"currentUser\"\n [Agents]=\"voicePickerAgents\"\n [DefaultAgentId]=\"voicePickerDefaultAgentId\"\n [CoAgents]=\"voicePickerCoAgents\"\n [DefaultCoAgentId]=\"voicePickerDefaultCoAgentId\"\n (AgentPicked)=\"onRealtimeAgentPicked($event)\"\n (Cancelled)=\"onRealtimeAgentPickerCancelled()\">\n </mj-realtime-agent-picker>\n </ng-template>\n</div>\n\n<!-- The voice \"call mode\" overlay is hosted by <mj-conversation-chat-area> (it fills the\n whole conversation panel in place); this component only owns the trigger wiring. -->\n", styles: [".message-input-wrapper {\n position: relative;\n width: 100%;\n}\n\n/* Plan Mode moved into the in-composer toolbar (message-input-box) as a toggle icon button\n next to the attachment/voice buttons \u2014 see .plan-mode-button-icon there. */\n\n.processing-indicator {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1.25rem;\n background: color-mix(in srgb, var(--mj-bg-surface-card) 95%, transparent);\n border-radius: 8px;\n box-shadow: var(--mj-shadow-sm);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator i {\n color: var(--mj-brand-primary);\n}\n.processing-indicator span {\n font-size: 0.9rem;\n color: var(--mj-text-primary);\n}\n"] }]
|
|
3367
|
+
}], () => [{ type: i1.DialogService }, { type: i2.ToastService }, { type: i3.ConversationAgentService }, { type: i4.DataCacheService }, { type: i5.ActiveTasksService }, { type: i6.ConversationStreamingService }, { type: i7.MentionParserService }, { type: i8.ConversationAttachmentService }, { type: i9.ConversationBridgeService }, { type: i10.RealtimeSessionService }], { ConversationId: [{
|
|
3368
|
+
type: Input
|
|
3369
|
+
}], conversationId: [{
|
|
3370
|
+
type: Input
|
|
3371
|
+
}], ConversationName: [{
|
|
2508
3372
|
type: Input
|
|
2509
3373
|
}], conversationName: [{
|
|
2510
3374
|
type: Input
|
|
3375
|
+
}], CurrentUser: [{
|
|
3376
|
+
type: Input
|
|
2511
3377
|
}], currentUser: [{
|
|
2512
3378
|
type: Input
|
|
3379
|
+
}], Disabled: [{
|
|
3380
|
+
type: Input
|
|
2513
3381
|
}], disabled: [{
|
|
2514
3382
|
type: Input
|
|
3383
|
+
}], ReadOnly: [{
|
|
3384
|
+
type: Input
|
|
3385
|
+
}], Placeholder: [{
|
|
3386
|
+
type: Input
|
|
2515
3387
|
}], placeholder: [{
|
|
2516
3388
|
type: Input
|
|
3389
|
+
}], ParentMessageId: [{
|
|
3390
|
+
type: Input
|
|
2517
3391
|
}], parentMessageId: [{
|
|
2518
3392
|
type: Input
|
|
3393
|
+
}], EnableAttachments: [{
|
|
3394
|
+
type: Input
|
|
2519
3395
|
}], enableAttachments: [{
|
|
2520
3396
|
type: Input
|
|
3397
|
+
}], EnableMentions: [{
|
|
3398
|
+
type: Input
|
|
2521
3399
|
}], enableMentions: [{
|
|
2522
3400
|
type: Input
|
|
3401
|
+
}], EnableAgentMentions: [{
|
|
3402
|
+
type: Input
|
|
2523
3403
|
}], enableAgentMentions: [{
|
|
2524
3404
|
type: Input
|
|
3405
|
+
}], EnableEntityMentions: [{
|
|
3406
|
+
type: Input
|
|
2525
3407
|
}], enableEntityMentions: [{
|
|
2526
3408
|
type: Input
|
|
3409
|
+
}], EnableSkillCommands: [{
|
|
3410
|
+
type: Input
|
|
2527
3411
|
}], enableSkillCommands: [{
|
|
2528
3412
|
type: Input
|
|
3413
|
+
}], EnablePlanMode: [{
|
|
3414
|
+
type: Input
|
|
2529
3415
|
}], enablePlanMode: [{
|
|
2530
3416
|
type: Input
|
|
3417
|
+
}], EnableRealtime: [{
|
|
3418
|
+
type: Input
|
|
2531
3419
|
}], enableRealtime: [{
|
|
2532
3420
|
type: Input
|
|
3421
|
+
}], MaxAttachments: [{
|
|
3422
|
+
type: Input
|
|
2533
3423
|
}], maxAttachments: [{
|
|
2534
3424
|
type: Input
|
|
3425
|
+
}], MaxAttachmentSizeBytes: [{
|
|
3426
|
+
type: Input
|
|
2535
3427
|
}], maxAttachmentSizeBytes: [{
|
|
2536
3428
|
type: Input
|
|
3429
|
+
}], AcceptedFileTypes: [{
|
|
3430
|
+
type: Input
|
|
2537
3431
|
}], acceptedFileTypes: [{
|
|
2538
3432
|
type: Input
|
|
3433
|
+
}], ArtifactsByDetailId: [{
|
|
3434
|
+
type: Input
|
|
2539
3435
|
}], artifactsByDetailId: [{
|
|
2540
3436
|
type: Input
|
|
3437
|
+
}], SystemArtifactsByDetailId: [{
|
|
3438
|
+
type: Input
|
|
2541
3439
|
}], systemArtifactsByDetailId: [{
|
|
2542
3440
|
type: Input
|
|
3441
|
+
}], AgentRunsByDetailId: [{
|
|
3442
|
+
type: Input
|
|
2543
3443
|
}], agentRunsByDetailId: [{
|
|
2544
3444
|
type: Input
|
|
3445
|
+
}], EmptyStateMode: [{
|
|
3446
|
+
type: Input
|
|
2545
3447
|
}], emptyStateMode: [{
|
|
2546
3448
|
type: Input
|
|
3449
|
+
}], AppContext: [{
|
|
3450
|
+
type: Input
|
|
2547
3451
|
}], appContext: [{
|
|
2548
3452
|
type: Input
|
|
3453
|
+
}], DefaultAgentId: [{
|
|
3454
|
+
type: Input
|
|
2549
3455
|
}], defaultAgentId: [{
|
|
2550
3456
|
type: Input
|
|
3457
|
+
}], ConversationDefaultAgentId: [{
|
|
3458
|
+
type: Input
|
|
2551
3459
|
}], conversationDefaultAgentId: [{
|
|
2552
3460
|
type: Input
|
|
3461
|
+
}], AgentConfigurationPresetId: [{
|
|
3462
|
+
type: Input
|
|
2553
3463
|
}], agentConfigurationPresetId: [{
|
|
2554
3464
|
type: Input
|
|
3465
|
+
}], InitialMessage: [{
|
|
3466
|
+
type: Input
|
|
2555
3467
|
}], initialMessage: [{
|
|
2556
3468
|
type: Input
|
|
3469
|
+
}], InitialAttachments: [{
|
|
3470
|
+
type: Input
|
|
2557
3471
|
}], initialAttachments: [{
|
|
2558
3472
|
type: Input
|
|
3473
|
+
}], ConversationHistory: [{
|
|
3474
|
+
type: Input
|
|
2559
3475
|
}], conversationHistory: [{
|
|
2560
3476
|
type: Input
|
|
3477
|
+
}], InProgressMessageIds: [{
|
|
3478
|
+
type: Input
|
|
2561
3479
|
}], inProgressMessageIds: [{
|
|
2562
3480
|
type: Input
|
|
3481
|
+
}], ApplicationId: [{
|
|
3482
|
+
type: Input
|
|
2563
3483
|
}], applicationId: [{
|
|
2564
3484
|
type: Input
|
|
3485
|
+
}], AgentReplyMode: [{
|
|
3486
|
+
type: Input
|
|
3487
|
+
}], AllowedAgentIDs: [{
|
|
3488
|
+
type: Input
|
|
3489
|
+
}], MentionPeople: [{
|
|
3490
|
+
type: Input
|
|
3491
|
+
}], AgentHistoryFrom: [{
|
|
3492
|
+
type: Input
|
|
3493
|
+
}], AgentTurnHandler: [{
|
|
3494
|
+
type: Input
|
|
3495
|
+
}], AutoNameConversation: [{
|
|
3496
|
+
type: Input
|
|
3497
|
+
}], EnableDecisionRouting: [{
|
|
3498
|
+
type: Input
|
|
3499
|
+
}], MessageSent: [{
|
|
3500
|
+
type: Output
|
|
2565
3501
|
}], messageSent: [{
|
|
2566
3502
|
type: Output
|
|
3503
|
+
}], AgentResponse: [{
|
|
3504
|
+
type: Output
|
|
2567
3505
|
}], agentResponse: [{
|
|
2568
3506
|
type: Output
|
|
3507
|
+
}], BeforeAgentTurn: [{
|
|
3508
|
+
type: Output
|
|
2569
3509
|
}], beforeAgentTurn: [{
|
|
2570
3510
|
type: Output
|
|
3511
|
+
}], AfterAgentTurn: [{
|
|
3512
|
+
type: Output
|
|
2571
3513
|
}], afterAgentTurn: [{
|
|
2572
3514
|
type: Output
|
|
3515
|
+
}], AgentRunDetected: [{
|
|
3516
|
+
type: Output
|
|
2573
3517
|
}], agentRunDetected: [{
|
|
2574
3518
|
type: Output
|
|
3519
|
+
}], AgentRunUpdate: [{
|
|
3520
|
+
type: Output
|
|
2575
3521
|
}], agentRunUpdate: [{
|
|
2576
3522
|
type: Output
|
|
3523
|
+
}], MessageComplete: [{
|
|
3524
|
+
type: Output
|
|
2577
3525
|
}], messageComplete: [{
|
|
2578
3526
|
type: Output
|
|
3527
|
+
}], ArtifactCreated: [{
|
|
3528
|
+
type: Output
|
|
2579
3529
|
}], artifactCreated: [{
|
|
2580
3530
|
type: Output
|
|
3531
|
+
}], ConversationRenamed: [{
|
|
3532
|
+
type: Output
|
|
2581
3533
|
}], conversationRenamed: [{
|
|
2582
3534
|
type: Output
|
|
3535
|
+
}], IntentCheckStarted: [{
|
|
3536
|
+
type: Output
|
|
2583
3537
|
}], intentCheckStarted: [{
|
|
2584
3538
|
type: Output
|
|
3539
|
+
}], IntentCheckCompleted: [{
|
|
3540
|
+
type: Output
|
|
2585
3541
|
}], intentCheckCompleted: [{
|
|
2586
3542
|
type: Output
|
|
3543
|
+
}], InitialMessageAutoSendStarted: [{
|
|
3544
|
+
type: Output
|
|
2587
3545
|
}], initialMessageAutoSendStarted: [{
|
|
2588
3546
|
type: Output
|
|
3547
|
+
}], InitialMessageAutoSendFailed: [{
|
|
3548
|
+
type: Output
|
|
2589
3549
|
}], initialMessageAutoSendFailed: [{
|
|
2590
3550
|
type: Output
|
|
3551
|
+
}], EmptyStateSubmit: [{
|
|
3552
|
+
type: Output
|
|
2591
3553
|
}], emptyStateSubmit: [{
|
|
2592
3554
|
type: Output
|
|
3555
|
+
}], UploadStateChanged: [{
|
|
3556
|
+
type: Output
|
|
2593
3557
|
}], uploadStateChanged: [{
|
|
2594
3558
|
type: Output
|
|
2595
|
-
}],
|
|
3559
|
+
}], InputBox: [{
|
|
2596
3560
|
type: ViewChild,
|
|
2597
3561
|
args: ['inputBox']
|
|
3562
|
+
}], InitialDraft: [{
|
|
3563
|
+
type: Input
|
|
2598
3564
|
}], initialDraft: [{
|
|
2599
3565
|
type: Input
|
|
3566
|
+
}], InitialDraftApplied: [{
|
|
3567
|
+
type: Output
|
|
2600
3568
|
}], initialDraftApplied: [{
|
|
2601
3569
|
type: Output
|
|
2602
3570
|
}], DraftStateChanged: [{
|
|
@@ -2604,5 +3572,5 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2604
3572
|
}], ComposerBlurred: [{
|
|
2605
3573
|
type: Output
|
|
2606
3574
|
}] }); })();
|
|
2607
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber:
|
|
3575
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber: 66 }); })();
|
|
2608
3576
|
//# sourceMappingURL=message-input.component.js.map
|