@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.
Files changed (351) hide show
  1. package/README.md +43 -2
  2. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +19 -3
  3. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
  4. package/dist/lib/components/active-tasks/active-tasks-panel.component.js +39 -7
  5. package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
  6. package/dist/lib/components/agent/active-agent-indicator.component.d.ts +74 -6
  7. package/dist/lib/components/agent/active-agent-indicator.component.d.ts.map +1 -1
  8. package/dist/lib/components/agent/active-agent-indicator.component.js +111 -21
  9. package/dist/lib/components/agent/active-agent-indicator.component.js.map +1 -1
  10. package/dist/lib/components/agent/agent-process-panel.component.d.ts +31 -5
  11. package/dist/lib/components/agent/agent-process-panel.component.d.ts.map +1 -1
  12. package/dist/lib/components/agent/agent-process-panel.component.js +71 -15
  13. package/dist/lib/components/agent/agent-process-panel.component.js.map +1 -1
  14. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts +65 -9
  15. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts.map +1 -1
  16. package/dist/lib/components/artifact/artifact-share-modal.component.js +169 -57
  17. package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
  18. package/dist/lib/components/attachment/image-viewer.component.d.ts +77 -11
  19. package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
  20. package/dist/lib/components/attachment/image-viewer.component.js +181 -47
  21. package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
  22. package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts +129 -19
  23. package/dist/lib/components/collection/artifact-collection-picker-modal.component.d.ts.map +1 -1
  24. package/dist/lib/components/collection/artifact-collection-picker-modal.component.js +350 -116
  25. package/dist/lib/components/collection/artifact-collection-picker-modal.component.js.map +1 -1
  26. package/dist/lib/components/collection/artifact-create-modal.component.d.ts +60 -8
  27. package/dist/lib/components/collection/artifact-create-modal.component.d.ts.map +1 -1
  28. package/dist/lib/components/collection/artifact-create-modal.component.js +130 -46
  29. package/dist/lib/components/collection/artifact-create-modal.component.js.map +1 -1
  30. package/dist/lib/components/collection/collection-artifact-card.component.d.ts +63 -3
  31. package/dist/lib/components/collection/collection-artifact-card.component.d.ts.map +1 -1
  32. package/dist/lib/components/collection/collection-artifact-card.component.js +113 -19
  33. package/dist/lib/components/collection/collection-artifact-card.component.js.map +1 -1
  34. package/dist/lib/components/collection/collection-form-modal.component.d.ts +54 -8
  35. package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
  36. package/dist/lib/components/collection/collection-form-modal.component.js +130 -46
  37. package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
  38. package/dist/lib/components/collection/collection-share-modal.component.d.ts +70 -10
  39. package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -1
  40. package/dist/lib/components/collection/collection-share-modal.component.js +175 -53
  41. package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
  42. package/dist/lib/components/collection/collection-tree.component.d.ts +89 -9
  43. package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
  44. package/dist/lib/components/collection/collection-tree.component.js +220 -70
  45. package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
  46. package/dist/lib/components/collection/collection-view.component.d.ts +89 -11
  47. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  48. package/dist/lib/components/collection/collection-view.component.js +154 -40
  49. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  50. package/dist/lib/components/collection/collections-full-view.component.d.ts +390 -38
  51. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  52. package/dist/lib/components/collection/collections-full-view.component.js +951 -367
  53. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  54. package/dist/lib/components/composer/ai-composer.component.d.ts +213 -18
  55. package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -1
  56. package/dist/lib/components/composer/ai-composer.component.js +388 -44
  57. package/dist/lib/components/composer/ai-composer.component.js.map +1 -1
  58. package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +12 -1
  59. package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts.map +1 -1
  60. package/dist/lib/components/conversation/conversation-agent-picker.component.js +26 -4
  61. package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -1
  62. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +1191 -124
  63. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  64. package/dist/lib/components/conversation/conversation-chat-area.component.js +2816 -785
  65. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  66. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +139 -19
  67. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
  68. package/dist/lib/components/conversation/conversation-empty-state.component.js +296 -59
  69. package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
  70. package/dist/lib/components/conversation/conversation-list.component.d.ts +579 -78
  71. package/dist/lib/components/conversation/conversation-list.component.d.ts.map +1 -1
  72. package/dist/lib/components/conversation/conversation-list.component.js +2322 -823
  73. package/dist/lib/components/conversation/conversation-list.component.js.map +1 -1
  74. package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts +42 -4
  75. package/dist/lib/components/conversation/pinned-messages-panel.component.d.ts.map +1 -1
  76. package/dist/lib/components/conversation/pinned-messages-panel.component.js +93 -21
  77. package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
  78. package/dist/lib/components/dialogs/input-dialog.component.d.ts +57 -11
  79. package/dist/lib/components/dialogs/input-dialog.component.d.ts.map +1 -1
  80. package/dist/lib/components/dialogs/input-dialog.component.js +128 -16
  81. package/dist/lib/components/dialogs/input-dialog.component.js.map +1 -1
  82. package/dist/lib/components/dialogs/rating-dialog.component.d.ts +68 -10
  83. package/dist/lib/components/dialogs/rating-dialog.component.d.ts.map +1 -1
  84. package/dist/lib/components/dialogs/rating-dialog.component.js +151 -27
  85. package/dist/lib/components/dialogs/rating-dialog.component.js.map +1 -1
  86. package/dist/lib/components/export/export-modal.component.d.ts +84 -10
  87. package/dist/lib/components/export/export-modal.component.d.ts.map +1 -1
  88. package/dist/lib/components/export/export-modal.component.js +173 -49
  89. package/dist/lib/components/export/export-modal.component.js.map +1 -1
  90. package/dist/lib/components/global-tasks/global-tasks-panel.component.d.ts +29 -3
  91. package/dist/lib/components/global-tasks/global-tasks-panel.component.d.ts.map +1 -1
  92. package/dist/lib/components/global-tasks/global-tasks-panel.component.js +58 -12
  93. package/dist/lib/components/global-tasks/global-tasks-panel.component.js.map +1 -1
  94. package/dist/lib/components/library/library-full-view.component.d.ts +57 -8
  95. package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
  96. package/dist/lib/components/library/library-full-view.component.js +119 -35
  97. package/dist/lib/components/library/library-full-view.component.js.map +1 -1
  98. package/dist/lib/components/members/members-modal.component.d.ts +76 -10
  99. package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
  100. package/dist/lib/components/members/members-modal.component.js +164 -50
  101. package/dist/lib/components/members/members-modal.component.js.map +1 -1
  102. package/dist/lib/components/message/actionable-commands.component.d.ts +49 -5
  103. package/dist/lib/components/message/actionable-commands.component.d.ts.map +1 -1
  104. package/dist/lib/components/message/actionable-commands.component.js +140 -26
  105. package/dist/lib/components/message/actionable-commands.component.js.map +1 -1
  106. package/dist/lib/components/message/conversation-message-rating.component.d.ts +46 -10
  107. package/dist/lib/components/message/conversation-message-rating.component.d.ts.map +1 -1
  108. package/dist/lib/components/message/conversation-message-rating.component.js +120 -36
  109. package/dist/lib/components/message/conversation-message-rating.component.js.map +1 -1
  110. package/dist/lib/components/message/message-input.component.d.ts +613 -74
  111. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  112. package/dist/lib/components/message/message-input.component.js +1390 -422
  113. package/dist/lib/components/message/message-input.component.js.map +1 -1
  114. package/dist/lib/components/message/message-item.component.d.ts +490 -29
  115. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  116. package/dist/lib/components/message/message-item.component.js +1088 -244
  117. package/dist/lib/components/message/message-item.component.js.map +1 -1
  118. package/dist/lib/components/message/message-list.component.d.ts +297 -29
  119. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  120. package/dist/lib/components/message/message-list.component.js +598 -123
  121. package/dist/lib/components/message/message-list.component.js.map +1 -1
  122. package/dist/lib/components/navigation/conversation-navigation.component.d.ts +64 -5
  123. package/dist/lib/components/navigation/conversation-navigation.component.d.ts.map +1 -1
  124. package/dist/lib/components/navigation/conversation-navigation.component.js +101 -11
  125. package/dist/lib/components/navigation/conversation-navigation.component.js.map +1 -1
  126. package/dist/lib/components/notification/activity-indicator.component.d.ts +6 -2
  127. package/dist/lib/components/notification/activity-indicator.component.d.ts.map +1 -1
  128. package/dist/lib/components/notification/activity-indicator.component.js +13 -3
  129. package/dist/lib/components/notification/activity-indicator.component.js.map +1 -1
  130. package/dist/lib/components/notification/notification-badge.component.d.ts +15 -3
  131. package/dist/lib/components/notification/notification-badge.component.d.ts.map +1 -1
  132. package/dist/lib/components/notification/notification-badge.component.js +36 -8
  133. package/dist/lib/components/notification/notification-badge.component.js.map +1 -1
  134. package/dist/lib/components/overlay/chat-overlay.component.d.ts +1 -1
  135. package/dist/lib/components/overlay/chat-overlay.component.js +4 -4
  136. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  137. package/dist/lib/components/project/project-form-modal.component.d.ts +93 -11
  138. package/dist/lib/components/project/project-form-modal.component.d.ts.map +1 -1
  139. package/dist/lib/components/project/project-form-modal.component.js +348 -87
  140. package/dist/lib/components/project/project-form-modal.component.js.map +1 -1
  141. package/dist/lib/components/project/project-selector.component.d.ts +78 -8
  142. package/dist/lib/components/project/project-selector.component.d.ts.map +1 -1
  143. package/dist/lib/components/project/project-selector.component.js +177 -46
  144. package/dist/lib/components/project/project-selector.component.js.map +1 -1
  145. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +1 -1
  146. package/dist/lib/components/realtime/realtime-activity-rail.component.js +2 -2
  147. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
  148. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +1 -1
  149. package/dist/lib/components/realtime/realtime-delegation-card.component.js +2 -2
  150. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
  151. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +2 -2
  152. package/dist/lib/components/realtime/realtime-session-overlay.component.js +18 -18
  153. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  154. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +47 -23
  155. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -1
  156. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +136 -92
  157. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -1
  158. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +2 -2
  159. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +4 -4
  160. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
  161. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +1 -1
  162. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +3 -3
  163. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
  164. package/dist/lib/components/realtime/realtime-ui-config.d.ts +13 -11
  165. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
  166. package/dist/lib/components/realtime/realtime-ui-config.js +29 -25
  167. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
  168. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +3 -1
  169. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
  170. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +6 -2
  171. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
  172. package/dist/lib/components/search/search-panel.component.d.ts +133 -22
  173. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  174. package/dist/lib/components/search/search-panel.component.js +312 -83
  175. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  176. package/dist/lib/components/share/share-modal.component.d.ts +69 -9
  177. package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
  178. package/dist/lib/components/share/share-modal.component.js +139 -31
  179. package/dist/lib/components/share/share-modal.component.js.map +1 -1
  180. package/dist/lib/components/shared/user-picker.component.d.ts +48 -8
  181. package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
  182. package/dist/lib/components/shared/user-picker.component.js +110 -30
  183. package/dist/lib/components/shared/user-picker.component.js.map +1 -1
  184. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts +114 -11
  185. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts.map +1 -1
  186. package/dist/lib/components/sidebar/conversation-sidebar.component.js +199 -21
  187. package/dist/lib/components/sidebar/conversation-sidebar.component.js.map +1 -1
  188. package/dist/lib/components/sidebar/routines-section.component.d.ts +20 -1
  189. package/dist/lib/components/sidebar/routines-section.component.d.ts.map +1 -1
  190. package/dist/lib/components/sidebar/routines-section.component.js +25 -5
  191. package/dist/lib/components/sidebar/routines-section.component.js.map +1 -1
  192. package/dist/lib/components/slots/slot-interfaces.d.ts +19 -0
  193. package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -1
  194. package/dist/lib/components/slots/slot-interfaces.js.map +1 -1
  195. package/dist/lib/components/task/tasks-full-view.component.d.ts +95 -13
  196. package/dist/lib/components/task/tasks-full-view.component.d.ts.map +1 -1
  197. package/dist/lib/components/task/tasks-full-view.component.js +223 -75
  198. package/dist/lib/components/task/tasks-full-view.component.js.map +1 -1
  199. package/dist/lib/components/tasks/task-widget.component.d.ts +46 -6
  200. package/dist/lib/components/tasks/task-widget.component.d.ts.map +1 -1
  201. package/dist/lib/components/tasks/task-widget.component.js +100 -16
  202. package/dist/lib/components/tasks/task-widget.component.js.map +1 -1
  203. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +64 -7
  204. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
  205. package/dist/lib/components/tasks/tasks-dropdown.component.js +127 -27
  206. package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
  207. package/dist/lib/components/thread/thread-panel.component.d.ts +66 -8
  208. package/dist/lib/components/thread/thread-panel.component.d.ts.map +1 -1
  209. package/dist/lib/components/thread/thread-panel.component.js +146 -36
  210. package/dist/lib/components/thread/thread-panel.component.js.map +1 -1
  211. package/dist/lib/components/toast/toast.component.d.ts +11 -1
  212. package/dist/lib/components/toast/toast.component.d.ts.map +1 -1
  213. package/dist/lib/components/toast/toast.component.js +25 -5
  214. package/dist/lib/components/toast/toast.component.js.map +1 -1
  215. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +385 -42
  216. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  217. package/dist/lib/components/workspace/conversation-workspace.component.js +891 -292
  218. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  219. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts +8 -0
  220. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts.map +1 -1
  221. package/dist/lib/composer-plugins/base-conversation-mention.provider.js +8 -1
  222. package/dist/lib/composer-plugins/base-conversation-mention.provider.js.map +1 -1
  223. package/dist/lib/directives/chat-slot.directive.d.ts +5 -1
  224. package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -1
  225. package/dist/lib/directives/chat-slot.directive.js.map +1 -1
  226. package/dist/lib/directives/search-shortcut.directive.d.ts +9 -1
  227. package/dist/lib/directives/search-shortcut.directive.d.ts.map +1 -1
  228. package/dist/lib/directives/search-shortcut.directive.js +14 -4
  229. package/dist/lib/directives/search-shortcut.directive.js.map +1 -1
  230. package/dist/lib/events/chat-events.d.ts +34 -7
  231. package/dist/lib/events/chat-events.d.ts.map +1 -1
  232. package/dist/lib/events/chat-events.js +37 -7
  233. package/dist/lib/events/chat-events.js.map +1 -1
  234. package/dist/lib/models/agent-turn.model.d.ts +113 -0
  235. package/dist/lib/models/agent-turn.model.d.ts.map +1 -0
  236. package/dist/lib/models/agent-turn.model.js +12 -0
  237. package/dist/lib/models/agent-turn.model.js.map +1 -0
  238. package/dist/lib/models/collection-view.model.js.map +1 -1
  239. package/dist/lib/models/conversation-state.model.d.ts +8 -0
  240. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  241. package/dist/lib/models/conversation-state.model.js.map +1 -1
  242. package/dist/lib/models/lazy-artifact-info.d.ts +44 -12
  243. package/dist/lib/models/lazy-artifact-info.d.ts.map +1 -1
  244. package/dist/lib/models/lazy-artifact-info.js +98 -34
  245. package/dist/lib/models/lazy-artifact-info.js.map +1 -1
  246. package/dist/lib/services/active-tasks.service.d.ts +30 -4
  247. package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
  248. package/dist/lib/services/active-tasks.service.js +71 -19
  249. package/dist/lib/services/active-tasks.service.js.map +1 -1
  250. package/dist/lib/services/agent-state.service.d.ts +17 -1
  251. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  252. package/dist/lib/services/agent-state.service.js +48 -16
  253. package/dist/lib/services/agent-state.service.js.map +1 -1
  254. package/dist/lib/services/artifact-permission.service.d.ts +39 -0
  255. package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
  256. package/dist/lib/services/artifact-permission.service.js +152 -17
  257. package/dist/lib/services/artifact-permission.service.js.map +1 -1
  258. package/dist/lib/services/artifact-state.service.d.ts +58 -5
  259. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  260. package/dist/lib/services/artifact-state.service.js +135 -35
  261. package/dist/lib/services/artifact-state.service.js.map +1 -1
  262. package/dist/lib/services/collection-permission.service.d.ts +28 -0
  263. package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
  264. package/dist/lib/services/collection-permission.service.js +88 -32
  265. package/dist/lib/services/collection-permission.service.js.map +1 -1
  266. package/dist/lib/services/collection-state.service.d.ts +11 -1
  267. package/dist/lib/services/collection-state.service.d.ts.map +1 -1
  268. package/dist/lib/services/collection-state.service.js +25 -5
  269. package/dist/lib/services/collection-state.service.js.map +1 -1
  270. package/dist/lib/services/conversation-agent.service.d.ts +47 -4
  271. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  272. package/dist/lib/services/conversation-agent.service.js +85 -20
  273. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  274. package/dist/lib/services/conversation-attachment.service.d.ts +17 -0
  275. package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
  276. package/dist/lib/services/conversation-attachment.service.js +38 -10
  277. package/dist/lib/services/conversation-attachment.service.js.map +1 -1
  278. package/dist/lib/services/conversation-liveness-dom.service.d.ts +42 -0
  279. package/dist/lib/services/conversation-liveness-dom.service.d.ts.map +1 -0
  280. package/dist/lib/services/conversation-liveness-dom.service.js +74 -0
  281. package/dist/lib/services/conversation-liveness-dom.service.js.map +1 -0
  282. package/dist/lib/services/conversation-streaming.service.d.ts +22 -0
  283. package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
  284. package/dist/lib/services/conversation-streaming.service.js +50 -10
  285. package/dist/lib/services/conversation-streaming.service.js.map +1 -1
  286. package/dist/lib/services/data-cache.service.d.ts +31 -0
  287. package/dist/lib/services/data-cache.service.d.ts.map +1 -1
  288. package/dist/lib/services/data-cache.service.js +71 -15
  289. package/dist/lib/services/data-cache.service.js.map +1 -1
  290. package/dist/lib/services/dialog.service.d.ts +13 -0
  291. package/dist/lib/services/dialog.service.d.ts.map +1 -1
  292. package/dist/lib/services/dialog.service.js +25 -5
  293. package/dist/lib/services/dialog.service.js.map +1 -1
  294. package/dist/lib/services/export.service.d.ts +2 -0
  295. package/dist/lib/services/export.service.d.ts.map +1 -1
  296. package/dist/lib/services/export.service.js +5 -1
  297. package/dist/lib/services/export.service.js.map +1 -1
  298. package/dist/lib/services/mention-parser.service.d.ts +10 -0
  299. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  300. package/dist/lib/services/mention-parser.service.js +25 -5
  301. package/dist/lib/services/mention-parser.service.js.map +1 -1
  302. package/dist/lib/services/notification.service.d.ts +54 -6
  303. package/dist/lib/services/notification.service.d.ts.map +1 -1
  304. package/dist/lib/services/notification.service.js +123 -27
  305. package/dist/lib/services/notification.service.js.map +1 -1
  306. package/dist/lib/services/realtime-audio-recorder.d.ts +4 -4
  307. package/dist/lib/services/realtime-audio-recorder.js +12 -12
  308. package/dist/lib/services/realtime-audio-recorder.js.map +1 -1
  309. package/dist/lib/services/realtime-pcm-wav.d.ts +4 -0
  310. package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -1
  311. package/dist/lib/services/realtime-pcm-wav.js +10 -2
  312. package/dist/lib/services/realtime-pcm-wav.js.map +1 -1
  313. package/dist/lib/services/search.service.d.ts +42 -9
  314. package/dist/lib/services/search.service.d.ts.map +1 -1
  315. package/dist/lib/services/search.service.js +103 -23
  316. package/dist/lib/services/search.service.js.map +1 -1
  317. package/dist/lib/services/toast.service.d.ts +19 -1
  318. package/dist/lib/services/toast.service.d.ts.map +1 -1
  319. package/dist/lib/services/toast.service.js +45 -9
  320. package/dist/lib/services/toast.service.js.map +1 -1
  321. package/dist/lib/services/ui-command-handler.service.d.ts +56 -5
  322. package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
  323. package/dist/lib/services/ui-command-handler.service.js +113 -16
  324. package/dist/lib/services/ui-command-handler.service.js.map +1 -1
  325. package/dist/lib/utils/agent-artifact-summary.d.ts +2 -2
  326. package/dist/lib/utils/agent-artifact-summary.js +3 -3
  327. package/dist/lib/utils/agent-artifact-summary.js.map +1 -1
  328. package/dist/lib/utils/agent-turn-routing.d.ts +70 -0
  329. package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -0
  330. package/dist/lib/utils/agent-turn-routing.js +84 -0
  331. package/dist/lib/utils/agent-turn-routing.js.map +1 -0
  332. package/dist/lib/utils/artifact-panel-action.d.ts +15 -4
  333. package/dist/lib/utils/artifact-panel-action.d.ts.map +1 -1
  334. package/dist/lib/utils/artifact-panel-action.js +10 -2
  335. package/dist/lib/utils/artifact-panel-action.js.map +1 -1
  336. package/dist/lib/utils/decision-routing.d.ts +68 -0
  337. package/dist/lib/utils/decision-routing.d.ts.map +1 -0
  338. package/dist/lib/utils/decision-routing.js +101 -0
  339. package/dist/lib/utils/decision-routing.js.map +1 -0
  340. package/dist/lib/utils/distinct-artifacts.d.ts +2 -0
  341. package/dist/lib/utils/distinct-artifacts.d.ts.map +1 -1
  342. package/dist/lib/utils/distinct-artifacts.js +5 -1
  343. package/dist/lib/utils/distinct-artifacts.js.map +1 -1
  344. package/dist/lib/utils/plan-mode-preference.d.ts +2 -2
  345. package/dist/lib/utils/plan-mode-preference.js +6 -6
  346. package/dist/lib/utils/plan-mode-preference.js.map +1 -1
  347. package/dist/public-api.d.ts +5 -0
  348. package/dist/public-api.d.ts.map +1 -1
  349. package/dist/public-api.js +8 -0
  350. package/dist/public-api.js.map +1 -1
  351. 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
- conversationId;
63
- conversationName; // For task tracking display
64
- currentUser;
65
- disabled = false;
66
- placeholder = 'Type a message... (Ctrl+Enter to send)';
67
- parentMessageId; // Optional: for replying in threads
68
- enableAttachments = true; // Whether to show attachment button (based on agent modality support)
69
- enableMentions = true; // Whether to enable @-mention autocomplete (agents/users). Hosts addressing a single fixed agent (e.g. Form Builder cockpit) typically set false.
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
- enableAgentMentions = true;
74
- enableEntityMentions = true;
75
- enableSkillCommands = true;
76
- enablePlanMode = true; // Whether the composer shows the Plan Mode toggle. Hosts that don't expose plan-mode workflows set false.
77
- enableRealtime = true; // Whether the composer shows the realtime voice-call launcher/options. Hosts without a voice experience set false.
78
- maxAttachments = 10; // Maximum number of attachments per message
79
- maxAttachmentSizeBytes = 20 * 1024 * 1024; // Maximum size per attachment (20MB default)
80
- acceptedFileTypes = 'image/*'; // Accepted MIME types pattern
81
- artifactsByDetailId; // Pre-loaded artifact data for performance
82
- systemArtifactsByDetailId; // Pre-loaded system artifact data (Visibility='System Only')
83
- agentRunsByDetailId; // Pre-loaded agent run data for performance
84
- emptyStateMode = false; // When true, emits emptyStateSubmit instead of creating messages directly
85
- appContext = null; // Application context for AI agent awareness
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.conversationId);
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.inputBox?.getMentionChipsData() || [];
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
- defaultAgentId = null;
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
- conversationDefaultAgentId = null;
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
- agentConfigurationPresetId = null;
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 initialMessage(value) {
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 initialMessage() {
385
+ get InitialMessage() {
187
386
  return this._initialMessage;
188
387
  }
189
- set initialAttachments(value) {
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 initialAttachments() {
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 conversationHistory() {
411
+ get ConversationHistory() {
197
412
  return this._conversationHistory;
198
413
  }
199
- set conversationHistory(value) {
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 inProgressMessageIds(value) {
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.reconnectInProgressMessages();
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 inProgressMessageIds() {
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
- applicationId = null;
230
- messageSent = new EventEmitter();
231
- agentResponse = new EventEmitter();
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
- * Cancelable — fired BEFORE `agentService.processMessage()` is called for a user turn.
234
- * Listeners may set `event.Cancel = true` to halt the agent invocation (e.g., a
235
- * client-side guardrail blocking the turn). When canceled, the corresponding
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
- beforeAgentTurn = new EventEmitter();
567
+ AfterAgentTurn = new EventEmitter();
240
568
  /**
241
- * Fired AFTER a successful agent turn completes. Carries the agent run id and the
242
- * full agent result. Not fired when {@link beforeAgentTurn} was canceled or when
243
- * the underlying `processMessage` errored.
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 = new EventEmitter();
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
- agentRunDetected = new EventEmitter();
251
- agentRunUpdate = new EventEmitter(); // Emits when agent run data updates during progress
252
- messageComplete = new EventEmitter(); // Emits when message completes (success or error)
253
- artifactCreated = new EventEmitter();
254
- conversationRenamed = new EventEmitter();
255
- intentCheckStarted = new EventEmitter(); // Emits when intent checking starts
256
- intentCheckCompleted = new EventEmitter(); // Emits when intent checking completes (carries conversationId so the parent can drop a background conversation's completion after a swap — symmetric with intentCheckStarted)
257
- initialMessageAutoSendStarted = new EventEmitter(); // Emitted when this input latches the pending first message for auto-send
258
- initialMessageAutoSendFailed = new EventEmitter(); // Emitted when a latched pending first message fails before messageSent
259
- emptyStateSubmit = new EventEmitter(); // Emitted when in emptyStateMode
260
- uploadStateChanged = new EventEmitter(); // Emits when attachment upload state changes
261
- inputBox;
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 messageText() {
703
+ get MessageText() {
278
704
  return this._messageText;
279
705
  }
280
- set messageText(value) {
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 initialDraft(value) {
729
+ set InitialDraft(value) {
296
730
  if (value && value !== this.appliedInitialDraft) {
297
731
  this.appliedInitialDraft = value;
298
- if (this.inputBox) {
732
+ if (this.InputBox) {
299
733
  this.SetDraft(value, true);
300
- this.initialDraftApplied.emit();
734
+ this.InitialDraftApplied.emit();
301
735
  }
302
736
  else {
303
737
  this.pendingInitialDraft = value;
304
738
  }
305
739
  }
306
740
  }
307
- get initialDraft() {
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
- initialDraftApplied = new EventEmitter();
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.messageText = value;
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.inputBox?.getPlainTextWithJsonMentions() ?? this.messageText ?? '';
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.inputBox) {
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.inputBox.mentionEditor?.clear();
349
- this.messageText = '';
798
+ this.InputBox.mentionEditor?.clear();
799
+ this.MessageText = '';
350
800
  }
351
801
  try {
352
- if (!this.mentionAutocomplete.IsInitialized && this.currentUser) {
802
+ if (!this.mentionAutocomplete.IsInitialized && this.CurrentUser) {
353
803
  console.log(`[Omnibar→Chat] InsertAgentMention('${agentName}'): initializing mention autocomplete…`);
354
- await this.mentionAutocomplete.initialize(this.currentUser);
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.inputBox.InsertMention(suggestion, focus);
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.inputBox?.mentionEditor;
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.messageText = text;
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.inputBox?.focus(), 50);
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
- voiceActive = false;
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.voiceActive = active;
958
+ this.VoiceActive = active;
453
959
  });
454
- this.converationManagerAgent = await this.agentService.getConversationManagerAgent();
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.currentUser);
965
+ await this.mentionAutocomplete.initialize(this.CurrentUser);
460
966
  // Reconnect to any in-progress messages for streaming updates (via global streaming service)
461
- this.reconnectInProgressMessages();
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.initialDraftApplied.emit();
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.conversationId || UUIDsEqual(this._autoSentForConversationId, this.conversationId)) {
1006
+ if (!hasContent || !this.ConversationId || UUIDsEqual(this._autoSentForConversationId, this.ConversationId)) {
498
1007
  return;
499
1008
  }
500
- this._autoSentForConversationId = this.conversationId;
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.initialMessageAutoSendStarted.emit({ conversationId: this.conversationId });
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.sendMessageWithText(message || '');
1019
+ const sent = await this.SendMessageWithText(message || '');
511
1020
  if (!sent) {
512
1021
  this._autoSentForConversationId = null;
513
- this.initialMessageAutoSendFailed.emit({ conversationId: this.conversationId });
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
- * Returns null only if Sage itself failed to load.
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.findLastNonSageAgentId()
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 pickerTargetAgentId() {
1061
+ get PickerTargetAgentId() {
550
1062
  if (this.mentionedAgentId === undefined) {
551
- const chips = this.inputBox?.getMentionChipsData() || [];
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.resolveCurrentAgentId();
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 !this.disabled && !this.voiceActive && !!this.resolveCurrentAgentId();
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.resolveCurrentAgentId();
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.converationManagerAgent?.Name ?? 'Sage';
1123
+ return this.ConverationManagerAgent?.Name ?? 'Sage';
604
1124
  }
605
1125
  /** True while the "Start a voice call with…" agent picker popover is open. */
606
- showRealtimeAgentPicker = false;
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
- pickerOverlayPositions = [
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 CoAgentPrefKey = 'mj.realtimeVoice.coAgent.v1';
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
- voicePickerDefaultCoAgentId = null;
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, so the picker can
632
- * never offer an agent the conversation couldn't otherwise route to.
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.mentionAutocomplete.getAvailableAgents();
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 voicePickerCoAgents() {
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.resolveCurrentAgentId();
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 onStartRealtime() {
664
- if (!this.canStartRealtime) {
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.voicePickerAgents.length > 0) {
1237
+ if (!this.findLastNonSageAgentId() && this.VoicePickerAgents.length > 0) {
671
1238
  await this.openRealtimeAgentPicker();
672
1239
  return;
673
1240
  }
674
- const targetAgentId = this.resolveCurrentAgentId();
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 onRealtimeOptions() {
691
- if (!this.canStartRealtime) {
1261
+ async OnRealtimeOptions() {
1262
+ if (!this.CanStartRealtime) {
692
1263
  return;
693
1264
  }
694
- if (this.voicePickerAgents.length > 0) {
1265
+ if (this.VoicePickerAgents.length > 0) {
695
1266
  await this.openRealtimeAgentPicker();
696
1267
  return;
697
1268
  }
698
- void this.onStartRealtime();
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.voicePickerDefaultCoAgentId = await this.loadPersistedCoAgentId();
703
- this.showRealtimeAgentPicker = true;
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 onRealtimeAgentPicked(pick) {
707
- this.showRealtimeAgentPicker = false;
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.CoAgentPrefKey);
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.CoAgentPrefKey, JSON.stringify({ coAgentId: coAgentId ?? null }));
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.voicePickerCoAgents.some(a => UUIDsEqual(a.ID, preferred));
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.showRealtimeAgentPicker = false;
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.conversationId, null, agentName, preferredModelId ?? null, null, coAgentId ?? null, configOverridesJson ?? null, recordingConsent ?? null, null,
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.applicationId, this.appContext);
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.inputBox) {
798
- this.inputBox.focus();
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
- reconnectInProgressMessages() {
811
- if (!this.inProgressMessageIds || this.inProgressMessageIds.length === 0) {
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.inProgressMessageIds) {
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.currentUser));
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.messageSent.emit(message);
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.messageSent.emit(message);
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 !this.disabled && !this.isSending && this.messageText.trim().length > 0;
1494
+ return this.CanSend;
904
1495
  }
905
1496
  /**
906
1497
  * Handle attachments changed from the input box
907
1498
  */
908
- onAttachmentsChanged(attachments) {
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
- onAttachmentError(error) {
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 onTextSubmitted(text) {
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.emptyStateMode) {
1530
+ if (this.EmptyStateMode) {
929
1531
  const attachmentsToEmit = [...this.pendingAttachments];
930
1532
  this.pendingAttachments = [];
931
- this.messageText = '';
932
- this.emptyStateSubmit.emit({ text: text?.trim() || '', attachments: attachmentsToEmit });
1533
+ this.MessageText = '';
1534
+ this.EmptyStateSubmit.emit({ text: text?.trim() || '', attachments: attachmentsToEmit });
933
1535
  return;
934
1536
  }
935
- this.isSending = true;
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.isUploadingAttachments = true;
948
- this.uploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
949
- this.uploadStateChanged.emit({ isUploading: true, message: this.uploadingMessage });
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.currentUser);
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.isUploadingAttachments = false;
962
- this.uploadStateChanged.emit({ isUploading: false, message: '' });
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.isSending = false;
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.isSending = false;
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.conversationId, !this.PlanModeEnabled);
1610
+ PlanModePreference.Set(this.ConversationId, !this.PlanModeEnabled);
1005
1611
  }
1006
- async onSend() {
1007
- if (!this.canSend)
1612
+ async OnSend() {
1613
+ if (!this.CanSend)
1008
1614
  return;
1009
- this.isSending = true;
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.isSending = false;
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 sendMessageWithText(text, extraAttachments) {
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.isSending) {
1671
+ if (this.IsSending) {
1059
1672
  return false;
1060
1673
  }
1061
- this.isSending = true;
1674
+ this.IsSending = true;
1062
1675
  const attachmentsToSave = merged;
1063
1676
  try {
1064
- const detail = await this.dataCache.createConversationDetail(this.currentUser);
1065
- detail.ConversationID = this.conversationId;
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.currentUser.ID; // Set the user who sent the message
1069
- if (this.parentMessageId) {
1070
- detail.ParentID = this.parentMessageId;
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.isUploadingAttachments = true;
1078
- this.uploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
1079
- this.uploadStateChanged.emit({ isUploading: true, message: this.uploadingMessage });
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.currentUser);
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.isUploadingAttachments = false;
1092
- this.uploadStateChanged.emit({ isUploading: false, message: '' });
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.isSending = false;
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.inputBox?.mentionEditor?.clear();
1116
- this.messageSent.emit(detail);
1728
+ this.InputBox?.mentionEditor?.clear();
1729
+ this.MessageSent.emit(detail);
1117
1730
  const mentionResult = this.parseMentionsFromMessage(detail.Message);
1118
- const isFirstMessage = this.conversationHistory.length === 0;
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.isSending = false;
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.currentUser);
1140
- detail.ConversationID = this.conversationId;
1141
- detail.Message = this.messageText.trim();
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.currentUser.ID; // Set the user who sent the message
1144
- if (this.parentMessageId) {
1145
- detail.ParentID = this.parentMessageId;
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.currentUser);
1154
- detail.ConversationID = this.conversationId;
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.currentUser.ID; // Set the user who sent the message
1158
- if (this.parentMessageId) {
1159
- detail.ParentID = this.parentMessageId;
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.messageSent.emit(messageDetail);
1168
- this.messageText = '';
1784
+ this.MessageSent.emit(messageDetail);
1785
+ this.MessageText = '';
1169
1786
  const mentionResult = this.parseMentionsFromMessage(messageDetail.Message);
1170
- const isFirstMessage = this.conversationHistory.length === 0;
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 the message to the appropriate agent or Sage based on context.
1183
- * Priority: explicit @mention > prior-agent continuity > embedder-supplied
1184
- * default agent > Sage fallback.
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
- // Priority 1: Direct @mention
1203
- if (mentionResult.agentMention) {
1204
- await this.handleDirectMention(messageDetail, mentionResult.agentMention, isFirstMessage);
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
- // Priority 2: Check for previous agent with intent check
1208
- const lastAgentId = this.findLastNonSageAgentId();
1209
- if (lastAgentId) {
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
- // Priority 3: User's per-conversation pinned default agent — sourced
1214
- // from MJConversationEntity.DefaultAgentID. Wins over the embedder's
1215
- // default because it represents an explicit user choice on this
1216
- // conversation (e.g. "always route to Research Agent for this thread").
1217
- if (this.conversationDefaultAgentId) {
1218
- await this.handleAgentContinuity(messageDetail, this.conversationDefaultAgentId, mentionResult, isFirstMessage);
1219
- return;
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
- // Priority 4: Embedder-supplied default agent. Set by chat surfaces
1222
- // that have a specialist agent for the context (e.g. Form Builder
1223
- // cockpit). Only kicks in when nothing more explicit is present —
1224
- // @mention always wins, conversation continuity always wins. The
1225
- // intent is to skip Sage's default delegation when the embedder
1226
- // already knows what agent owns this conversation.
1227
- if (this.defaultAgentId) {
1228
- await this.handleAgentContinuity(messageDetail, this.defaultAgentId, mentionResult, isFirstMessage);
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
- // Priority 5: Check if Sage was explicitly @mentioned with a config preset
1232
- // If so, treat it like agent continuity so the config preset is preserved
1233
- if (this.converationManagerAgent?.ID) {
1234
- const sageConfigPreset = this.conversationId
1235
- ? await this.agentService.FindConfigurationPresetForAgent(this.conversationId, this.converationManagerAgent.ID)
1236
- : undefined;
1237
- if (sageConfigPreset) {
1238
- // User explicitly @mentioned Sage with a config - use the shared execution helper directly
1239
- // Pass the already-found config preset to avoid redundant history search
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
- * Handles routing when user directly mentions an agent with @
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
- async handleDirectMention(messageDetail, agentMention, isFirstMessage) {
1254
- // The agentMention already has configurationId from JSON parsing
1255
- // If it wasn't in JSON (legacy format), try to get from chip data
1256
- if (!agentMention.configurationId) {
1257
- const chipData = this.inputBox?.getMentionChipsData() || [];
1258
- const agentChip = chipData.find(chip => chip.id === agentMention.id && chip.type === 'agent');
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
- agentMention.configurationId = agentChip.presetId;
2018
+ mention.configurationId = agentChip.presetId;
1261
2019
  }
1262
2020
  }
1263
- if (agentMention.configurationId) {
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
- * Handles routing when there's a previous non-Sage agent in the conversation.
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
- * Previously, this method made a separate LLM call via checkContinuityIntent() to decide
1273
- * whether the user's new message was still directed at the previous agent or should be
1274
- * routed to Sage. That call added ~300ms of latency on every message in a conversation
1275
- * with an active agent — the single largest source of non-inference overhead on the client.
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
- async handleAgentContinuity(messageDetail, lastAgentId, mentionResult, isFirstMessage) {
1288
- // COMMENTED OUT — LLM intent check removed for latency optimization (see JSDoc above).
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.converationManagerAgent?.ID));
1329
- return lastAIMessage?.AgentID || null;
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.intentCheckStarted.emit({ conversationId: this.conversationId });
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.conversationId) {
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.conversationId, agentId, message, this.conversationHistory);
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.intentCheckCompleted.emit({ conversationId: this.conversationId });
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.inputBox) {
1393
- this.inputBox.focus();
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.agentRunUpdate.emit({
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.agentRunDetected.emit({
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.messageSent.emit(conversationDetail);
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.currentUser);
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.converationManagerAgent?.ID) {
1503
- conversationManagerMessage.AgentID = this.converationManagerAgent.ID;
2354
+ if (this.ConverationManagerAgent?.ID) {
2355
+ conversationManagerMessage.AgentID = this.ConverationManagerAgent.ID;
1504
2356
  }
1505
2357
  await conversationManagerMessage.Save();
1506
- this.messageSent.emit(conversationManagerMessage);
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.conversationName
2367
+ conversationName: this.ConversationName
1516
2368
  });
1517
- // ── PR 2c follow-up: Before/After cancelable event wiring ──
1518
- // Emit beforeAgentTurn so consumers can veto the turn (rate-limit, guardrail,
1519
- // confirm-dialog, etc.). Cancel propagates synchronously through the chat-area
1520
- // re-emit binding, so by the time .emit() returns, event.Cancel reflects every
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.afterAgentTurn.emit(new AfterAgentTurnEventArgs(conversationId, (result.agentRun?.ID ?? ''), result));
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.converationManagerAgent?.Name || 'Sage', result, 'failed');
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.messageSent.emit(conversationManagerMessage);
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.messageSent.emit(conversationManagerMessage);
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.messageSent.emit(conversationManagerMessage);
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.currentUser);
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.messageSent.emit(taskExecutionMessage);
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.messageSent.emit(convoDetail);
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.messageSent.emit(convoDetail);
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.conversationId) {
2623
+ if (!this.ConversationId) {
1782
2624
  return { payload: null, artifactInfo: null };
1783
2625
  }
1784
- const source = await this.agentService.FindLatestAgentOutputVersion(this.conversationId, agentId);
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.currentUser);
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.messageSent.emit(agentResponseMessage);
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.conversationName
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.conversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
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.appContext, // Embedder-supplied app/form context
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.messageSent.emit(agentResponseMessage);
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.conversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass same payload as first attempt
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.appContext, // Embedder-supplied app/form context
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.messageSent.emit(agentResponseMessage);
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) in the conversation history
1933
- const lastAIMessage = this.conversationHistory
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.currentUser);
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.converationManagerAgent?.ID || null;
2826
+ statusMessage.AgentID = this.ConverationManagerAgent?.ID || null;
1977
2827
  await statusMessage.Save();
1978
- this.messageSent.emit(statusMessage);
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.conversationName
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.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
1991
- this.appContext, // Embedder-supplied app/form context
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.currentUser);
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.messageSent.emit(agentResponseMessage);
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.messageSent.emit(agentResponseMessage);
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.conversationName
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.messageSent.emit(userMessage);
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.currentUser);
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.messageSent.emit(agentResponseMessage);
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.conversationHistory, `User mentioned agent directly with @${agentName}`, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
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.appContext, // Embedder-supplied app/form context
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.messageSent.emit(agentResponseMessage);
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.agentConfigurationPresetId) {
2167
- previousConfigurationId = this.agentConfigurationPresetId;
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.conversationName
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.messageSent.emit(userMessage);
3080
+ this.MessageSent.emit(userMessage);
2223
3081
  // Create AI response message BEFORE invoking agent (for duration tracking)
2224
- agentResponseMessage = await this.dataCache.createConversationDetail(this.currentUser);
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.messageSent.emit(agentResponseMessage);
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.conversationHistory, 'Continuing previous conversation with user', agentResponseMessage.ID, previousPayload, // Pass previous OUTPUT artifact payload for continuity
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.appContext, // Embedder-supplied app/form context
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.messageSent.emit(agentResponseMessage);
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.currentUser
3147
+ CurrentUser: this.CurrentUser
2288
3148
  });
2289
3149
  if (result) {
2290
3150
  // Emit event for animation in conversation list
2291
- this.conversationRenamed.emit({
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.messageSent.emit(detail);
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.messageComplete.emit({
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.artifactCreated.emit({
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.inputBox = _t.first);
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: 27, 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", "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) {
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 }], { conversationId: [{
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
- }], inputBox: [{
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: 45 }); })();
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