@memberjunction/ng-conversations 6.2.0-edge.6 → 6.2.0-edge.7

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 (131) hide show
  1. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  2. package/dist/lib/components/collection/collection-view.component.js +1 -0
  3. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  4. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
  5. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  6. package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
  7. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  8. package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
  9. package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
  10. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
  11. package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
  12. package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
  13. package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
  14. package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
  15. package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
  16. package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
  17. package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
  18. package/dist/lib/components/message/message-input.component.d.ts +80 -53
  19. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  20. package/dist/lib/components/message/message-input.component.js +381 -277
  21. package/dist/lib/components/message/message-input.component.js.map +1 -1
  22. package/dist/lib/components/message/message-item.component.d.ts +18 -1
  23. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  24. package/dist/lib/components/message/message-item.component.js +548 -426
  25. package/dist/lib/components/message/message-item.component.js.map +1 -1
  26. package/dist/lib/components/message/message-list.component.d.ts +8 -1
  27. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  28. package/dist/lib/components/message/message-list.component.js +40 -2
  29. package/dist/lib/components/message/message-list.component.js.map +1 -1
  30. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
  31. package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
  32. package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
  33. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
  34. package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
  35. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
  36. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
  37. package/dist/lib/components/realtime/media/media-channel.js +7 -2
  38. package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
  39. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
  40. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
  41. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
  42. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
  43. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
  44. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  45. package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
  46. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  47. package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
  48. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  49. package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
  50. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  51. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
  52. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  53. package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
  54. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  55. package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
  56. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
  57. package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
  58. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
  59. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
  60. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
  61. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
  62. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
  63. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
  64. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
  65. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
  66. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
  67. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
  68. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
  69. package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
  70. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
  71. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
  72. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
  73. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
  74. package/dist/lib/conversations.module.d.ts +2 -1
  75. package/dist/lib/conversations.module.d.ts.map +1 -1
  76. package/dist/lib/conversations.module.js +9 -4
  77. package/dist/lib/conversations.module.js.map +1 -1
  78. package/dist/lib/events/chat-events.d.ts +5 -4
  79. package/dist/lib/events/chat-events.d.ts.map +1 -1
  80. package/dist/lib/events/chat-events.js +2 -2
  81. package/dist/lib/events/chat-events.js.map +1 -1
  82. package/dist/lib/models/agent-turn.model.d.ts +11 -7
  83. package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
  84. package/dist/lib/models/agent-turn.model.js.map +1 -1
  85. package/dist/lib/services/active-tasks.service.d.ts +5 -0
  86. package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
  87. package/dist/lib/services/active-tasks.service.js +16 -0
  88. package/dist/lib/services/active-tasks.service.js.map +1 -1
  89. package/dist/lib/services/agent-state.service.d.ts.map +1 -1
  90. package/dist/lib/services/agent-state.service.js +7 -1
  91. package/dist/lib/services/agent-state.service.js.map +1 -1
  92. package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
  93. package/dist/lib/services/artifact-permission.service.js +2 -0
  94. package/dist/lib/services/artifact-permission.service.js.map +1 -1
  95. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  96. package/dist/lib/services/artifact-state.service.js +1 -0
  97. package/dist/lib/services/artifact-state.service.js.map +1 -1
  98. package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
  99. package/dist/lib/services/collection-permission.service.js +1 -0
  100. package/dist/lib/services/collection-permission.service.js.map +1 -1
  101. package/dist/lib/services/conversation-agent.service.d.ts +1 -4
  102. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  103. package/dist/lib/services/conversation-agent.service.js +7 -45
  104. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  105. package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
  106. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  107. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  108. package/dist/lib/services/search.service.d.ts.map +1 -1
  109. package/dist/lib/services/search.service.js +1 -0
  110. package/dist/lib/services/search.service.js.map +1 -1
  111. package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
  112. package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
  113. package/dist/lib/utils/agent-handoff-map.js +270 -0
  114. package/dist/lib/utils/agent-handoff-map.js.map +1 -0
  115. package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
  116. package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
  117. package/dist/lib/utils/agent-turn-routing.js +147 -6
  118. package/dist/lib/utils/agent-turn-routing.js.map +1 -1
  119. package/dist/lib/utils/decision-routing.d.ts +17 -9
  120. package/dist/lib/utils/decision-routing.d.ts.map +1 -1
  121. package/dist/lib/utils/decision-routing.js +23 -14
  122. package/dist/lib/utils/decision-routing.js.map +1 -1
  123. package/dist/lib/utils/thread-replies.d.ts +34 -0
  124. package/dist/lib/utils/thread-replies.d.ts.map +1 -0
  125. package/dist/lib/utils/thread-replies.js +49 -0
  126. package/dist/lib/utils/thread-replies.js.map +1 -0
  127. package/dist/public-api.d.ts +5 -0
  128. package/dist/public-api.d.ts.map +1 -1
  129. package/dist/public-api.js +5 -0
  130. package/dist/public-api.js.map +1 -1
  131. package/package.json +36 -36
@@ -1,20 +1,21 @@
1
1
  import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
2
2
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
3
3
  import { LogStatusEx } from '@memberjunction/core';
4
- import { MJEnvironmentEntityExtended, ConversationEngine, UserInfoEngine, TaskGraphSubmitOperation } from '@memberjunction/core-entities';
4
+ import { ConversationEngine, UserInfoEngine } from '@memberjunction/core-entities';
5
5
  import { BeforeAgentTurnEventArgs, AfterAgentTurnEventArgs } from '../../events/chat-events';
6
- import { ResolveAgentTurn, FindDisallowedTaskGraphAgents } from '../../utils/agent-turn-routing';
6
+ import { IsAddressedToHuman, ResolveAgentTurn, FindContinuityAgentMessage } from '../../utils/agent-turn-routing';
7
+ import { AgentAccentVar } from './agent-handoff-card.component';
7
8
  import { ApplyRoutingDecision, ArtifactVersionForTurn, RunRoutingDecision, ShouldRunRoutingDecision } from '../../utils/decision-routing';
8
9
  import { BuildRecentTurns, BuildRoutingArtifactVersions, CanAskRoutingDecision, CollectRoutingParticipants, IsAgentAllowed } from '@memberjunction/ai-core-plus';
9
10
  import { GenerateAndApplyConversationName } from '../../services/conversation-naming';
10
- import { AIEngineBase } from '@memberjunction/ai-engine-base';
11
+ import { AIEngineBase, ResolveConversationAgentPreset } from '@memberjunction/ai-engine-base';
11
12
  import { ConversationUtility, agentFailureDisposition, agentFailureMessage, ContinuationAnnouncementText } from '@memberjunction/ai-core-plus';
12
13
  import { MentionAutocompleteService } from '../../services/mention-autocomplete.service';
13
14
  import { PlanModePreference } from '../../utils/plan-mode-preference';
14
15
  import { MJNotificationService } from '@memberjunction/ng-notifications';
15
16
  import { ConversationCallStartOptions } from '../realtime/capture/realtime-capture-channel';
16
17
  import { BuildRealtimeConfigOverridesJson, FilterRealtimeCoAgents, LoadCoAgentPairings, PairingsAllowTarget } from '../../services/realtime-pairing';
17
- import { UUIDsEqual } from '@memberjunction/global';
18
+ import { IndexByUUID, UUIDSet, UUIDsEqual } from '@memberjunction/global';
18
19
  import { InjectFrameZone } from '../../util/frame-zone';
19
20
  import * as i0 from "@angular/core";
20
21
  import * as i1 from "../../services/dialog.service";
@@ -33,7 +34,7 @@ import * as i13 from "../composer/ai-composer.component";
33
34
  const _c0 = ["inputBox"];
34
35
  function MessageInputComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
35
36
  i0.ɵɵelementStart(0, "div", 3);
36
- i0.ɵɵelement(1, "i", 6);
37
+ i0.ɵɵelement(1, "i", 7);
37
38
  i0.ɵɵelementStart(2, "span");
38
39
  i0.ɵɵtext(3);
39
40
  i0.ɵɵelementEnd()();
@@ -42,10 +43,39 @@ function MessageInputComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
42
43
  i0.ɵɵadvance(3);
43
44
  i0.ɵɵtextInterpolate(ctx_r0.processingMessage);
44
45
  } }
45
- function MessageInputComponent_ng_template_5_Template(rf, ctx) { if (rf & 1) {
46
+ function MessageInputComponent_Conditional_3_Conditional_7_Template(rf, ctx) { if (rf & 1) {
46
47
  const _r2 = i0.ɵɵgetCurrentView();
47
- i0.ɵɵelementStart(0, "mj-realtime-agent-picker", 7);
48
- i0.ɵɵlistener("AgentPicked", function MessageInputComponent_ng_template_5_Template_mj_realtime_agent_picker_AgentPicked_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRealtimeAgentPicked($event)); })("Cancelled", function MessageInputComponent_ng_template_5_Template_mj_realtime_agent_picker_Cancelled_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRealtimeAgentPickerCancelled()); });
48
+ i0.ɵɵelementStart(0, "button", 12);
49
+ i0.ɵɵlistener("click", function MessageInputComponent_Conditional_3_Conditional_7_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r0.ReturnToSage()); });
50
+ i0.ɵɵtext(1);
51
+ i0.ɵɵelementEnd();
52
+ } if (rf & 2) {
53
+ const ctx_r0 = i0.ɵɵnextContext(2);
54
+ i0.ɵɵadvance();
55
+ i0.ɵɵtextInterpolate1(" Return to ", (ctx_r0.ConverationManagerAgent == null ? null : ctx_r0.ConverationManagerAgent.Name) || "Sage", " ");
56
+ } }
57
+ function MessageInputComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
58
+ i0.ɵɵelementStart(0, "div", 8)(1, "span", 9);
59
+ i0.ɵɵelement(2, "span", 10);
60
+ i0.ɵɵelementStart(3, "span");
61
+ i0.ɵɵtext(4, "Replying to ");
62
+ i0.ɵɵelementStart(5, "b");
63
+ i0.ɵɵtext(6);
64
+ i0.ɵɵelementEnd()()();
65
+ i0.ɵɵconditionalCreate(7, MessageInputComponent_Conditional_3_Conditional_7_Template, 2, 1, "button", 11);
66
+ i0.ɵɵelementEnd();
67
+ } if (rf & 2) {
68
+ const ctx_r0 = i0.ɵɵnextContext();
69
+ i0.ɵɵstyleProp("--agent-accent", ctx_r0.CurrentRouteAccentVar);
70
+ i0.ɵɵadvance(6);
71
+ i0.ɵɵtextInterpolate(ctx_r0.CurrentRouteAgentName);
72
+ i0.ɵɵadvance();
73
+ i0.ɵɵconditional(ctx_r0.ShowReturnToSage ? 7 : -1);
74
+ } }
75
+ function MessageInputComponent_ng_template_6_Template(rf, ctx) { if (rf & 1) {
76
+ const _r3 = i0.ɵɵgetCurrentView();
77
+ i0.ɵɵelementStart(0, "mj-realtime-agent-picker", 13);
78
+ i0.ɵɵlistener("AgentPicked", function MessageInputComponent_ng_template_6_Template_mj_realtime_agent_picker_AgentPicked_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRealtimeAgentPicked($event)); })("Cancelled", function MessageInputComponent_ng_template_6_Template_mj_realtime_agent_picker_Cancelled_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.onRealtimeAgentPickerCancelled()); });
49
79
  i0.ɵɵelementEnd();
50
80
  } if (rf & 2) {
51
81
  const ctx_r0 = i0.ɵɵnextContext();
@@ -980,6 +1010,21 @@ export class MessageInputComponent extends BaseAngularComponent {
980
1010
  // ── Voice session (Realtime Co-Agent) ───────────────────────────────
981
1011
  /** True while a live voice session is active — drives the overlay + mic state. */
982
1012
  VoiceActive = false;
1013
+ /** True when THIS composer instance initiated the currently-open realtime call. */
1014
+ startedRealtimeSession = false;
1015
+ /**
1016
+ * True when this composer owns active work or unsent attachments that would be
1017
+ * disrupted or lost if the parent chat area unmounted this hidden instance.
1018
+ */
1019
+ get HasActiveWork() {
1020
+ return Boolean(this.IsSending ||
1021
+ this.IsProcessing ||
1022
+ this.IsUploadingAttachments ||
1023
+ (this.registeredCallbacks?.size ?? 0) > 0 ||
1024
+ (this.inFlightWatches?.size ?? 0) > 0 ||
1025
+ (this.pendingAttachments?.length ?? 0) > 0 ||
1026
+ (this.ConversationId && this.realtimeSession?.IsActiveFor?.(this.ConversationId)));
1027
+ }
983
1028
  /** @deprecated Use {@link VoiceActive}. */
984
1029
  get voiceActive() {
985
1030
  return this.VoiceActive;
@@ -1000,6 +1045,9 @@ export class MessageInputComponent extends BaseAngularComponent {
1000
1045
  // Reflect the live voice-session Active flag into a local field for the template.
1001
1046
  this.realtimeActiveSub = this.realtimeSession.Active$.subscribe(active => {
1002
1047
  this.VoiceActive = active;
1048
+ if (!active) {
1049
+ this.startedRealtimeSession = false;
1050
+ }
1003
1051
  });
1004
1052
  this.ConverationManagerAgent = await this.agentService.getConversationManagerAgent();
1005
1053
  // Warm UserInfoEngine so the PlanModeEnabled getter has the cached settings available
@@ -1072,8 +1120,15 @@ export class MessageInputComponent extends BaseAngularComponent {
1072
1120
  this.unregisterAllCallbacks();
1073
1121
  this.clearInFlightWatches();
1074
1122
  this.realtimeActiveSub?.unsubscribe();
1075
- // If the user navigates away mid-call, tear the session down.
1076
- if (this.realtimeSession.IsActive) {
1123
+ // Only end a live voice session if THIS composer owns it — cached hidden composers
1124
+ // for older conversations are unmounted on LRU eviction and must not hang up a
1125
+ // minimized call running on another conversation (or one minted from the empty state).
1126
+ const ownsActiveCall = this.ConversationId
1127
+ ? Boolean(this.realtimeSession?.IsActiveFor?.(this.ConversationId))
1128
+ : Boolean(this.startedRealtimeSession &&
1129
+ this.realtimeSession?.IsActive &&
1130
+ !this.realtimeSession.SessionCreatedConversationId);
1131
+ if (ownsActiveCall) {
1077
1132
  void this.realtimeSession.EndRealtimeSession();
1078
1133
  }
1079
1134
  }
@@ -1113,6 +1168,104 @@ export class MessageInputComponent extends BaseAngularComponent {
1113
1168
  get pickerTargetAgentId() {
1114
1169
  return this.PickerTargetAgentId;
1115
1170
  }
1171
+ _cachedRouteAgentKey;
1172
+ _cachedRouteAgent = null;
1173
+ /**
1174
+ * Agent that will receive the next turn based on an explicit `@agent` draft chip
1175
+ * or continuity / default routing ({@link PickerTargetAgentId}).
1176
+ * Memoized by message count and routing inputs so template evaluation does not
1177
+ * scan `ConversationHistory` on every change-detection cycle.
1178
+ */
1179
+ get CurrentRouteAgent() {
1180
+ if (this.mentionedAgentId === undefined) {
1181
+ const chips = this.InputBox?.getMentionChipsData() || [];
1182
+ this.mentionedAgentId = chips.find(chip => chip.type === 'agent')?.id ?? null;
1183
+ }
1184
+ const history = this.ConversationHistory ?? [];
1185
+ const lastRow = history.length > 0 ? history[history.length - 1] : null;
1186
+ const manager = this.ConverationManagerAgent;
1187
+ const floorMs = this.AgentHistoryFrom ? new Date(this.AgentHistoryFrom).getTime() : '';
1188
+ const allowedKey = this.AllowedAgentIDs ? this.AllowedAgentIDs.join(',') : '';
1189
+ const cacheKey = `${history.length}|${lastRow?.ID ?? ''}|${lastRow?.AgentID ?? ''}|${this.mentionedAgentId ?? ''}|${this.ConversationDefaultAgentId ?? ''}|${this.DefaultAgentId ?? ''}|${manager?.ID ?? ''}|${floorMs}|${allowedKey}`;
1190
+ if (this._cachedRouteAgentKey === cacheKey) {
1191
+ return this._cachedRouteAgent ?? null;
1192
+ }
1193
+ const targetId = this.PickerTargetAgentId;
1194
+ let resolved = null;
1195
+ if (!targetId) {
1196
+ if (manager?.ID) {
1197
+ resolved = { ID: manager.ID, Name: manager.Name || 'Sage', IsConversationManager: true };
1198
+ }
1199
+ }
1200
+ else if (manager?.ID && UUIDsEqual(targetId, manager.ID)) {
1201
+ resolved = { ID: manager.ID, Name: manager.Name || 'Sage', IsConversationManager: true };
1202
+ }
1203
+ else {
1204
+ const found = AIEngineBase.Instance.ReadableAgents?.find(a => UUIDsEqual(a.ID, targetId));
1205
+ if (found) {
1206
+ resolved = { ID: found.ID, Name: found.Name || 'Agent', IsConversationManager: false };
1207
+ }
1208
+ else {
1209
+ const chips = this.InputBox?.getMentionChipsData() || [];
1210
+ const chipMatch = chips.find(c => c.type === 'agent' && UUIDsEqual(c.id, targetId));
1211
+ if (chipMatch) {
1212
+ resolved = { ID: targetId, Name: chipMatch.name || 'Agent', IsConversationManager: false };
1213
+ }
1214
+ else {
1215
+ const lastReply = this.findLastNonSageReply();
1216
+ if (lastReply?.AgentID && UUIDsEqual(lastReply.AgentID, targetId)) {
1217
+ resolved = { ID: targetId, Name: lastReply.Agent || 'Agent', IsConversationManager: false };
1218
+ }
1219
+ else {
1220
+ resolved = { ID: targetId, Name: 'Agent', IsConversationManager: false };
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+ this._cachedRouteAgentKey = cacheKey;
1226
+ this._cachedRouteAgent = resolved;
1227
+ return resolved;
1228
+ }
1229
+ get CurrentRouteAgentName() {
1230
+ return this.CurrentRouteAgent?.Name || this.ConverationManagerAgent?.Name || 'Sage';
1231
+ }
1232
+ get CurrentRouteAccentVar() {
1233
+ return AgentAccentVar(this.CurrentRouteAgent?.ID);
1234
+ }
1235
+ get ShowReturnToSage() {
1236
+ const route = this.CurrentRouteAgent;
1237
+ return Boolean(!this.EmptyStateMode && route && !route.IsConversationManager && this.ConverationManagerAgent);
1238
+ }
1239
+ /**
1240
+ * Inserts a `@Sage` mention chip into the composer (preserving any typed draft text)
1241
+ * and focuses the editor so the next turn routes back to the conversation manager.
1242
+ */
1243
+ async ReturnToSage() {
1244
+ const manager = this.ConverationManagerAgent;
1245
+ if (!manager) {
1246
+ return false;
1247
+ }
1248
+ const name = manager.Name || 'Sage';
1249
+ if (this.InputBox) {
1250
+ const inserted = this.InputBox.InsertMention({
1251
+ id: manager.ID,
1252
+ type: 'agent',
1253
+ name,
1254
+ displayName: name,
1255
+ icon: manager.IconClass || undefined,
1256
+ imageUrl: manager.LogoURL || undefined,
1257
+ }, true);
1258
+ if (inserted) {
1259
+ this.mentionedAgentId = manager.ID;
1260
+ return true;
1261
+ }
1262
+ }
1263
+ const prefix = name.includes(' ') ? `@"${name}" ` : `@${name} `;
1264
+ const existing = this.MessageText ?? '';
1265
+ this.SetDraft(`${prefix}${existing}`, true);
1266
+ this.mentionedAgentId = manager.ID;
1267
+ return true;
1268
+ }
1116
1269
  /**
1117
1270
  * Memo for the first `@agent` chip in the draft, so the template-bound
1118
1271
  * {@link pickerTargetAgentId} does not walk the editor DOM on every change-detection cycle.
@@ -1404,6 +1557,7 @@ export class MessageInputComponent extends BaseAngularComponent {
1404
1557
  */
1405
1558
  async startRealtimeWithAgent(agentId, agentName, preferredModelId, coAgentId, configOverridesJson, recordingConsent) {
1406
1559
  try {
1560
+ this.startedRealtimeSession = true;
1407
1561
  await this.realtimeSession.StartRealtimeSession(agentId, this.ConversationId, null, agentName, preferredModelId ?? null, null, coAgentId ?? null, configOverridesJson ?? null, recordingConsent ?? null, null,
1408
1562
  // App awareness: the app the session runs in + the live app-context snapshot (where the
1409
1563
  // user is, what they see, capability manifest) — drives the server-side app cascade + the
@@ -1413,6 +1567,7 @@ export class MessageInputComponent extends BaseAngularComponent {
1413
1567
  { ...ConversationCallStartOptions(), ConversationBranchID: this.TargetBranchID });
1414
1568
  }
1415
1569
  catch (error) {
1570
+ this.startedRealtimeSession = false;
1416
1571
  console.error('Failed to start voice session:', error);
1417
1572
  this.toastService.error('Could not start the voice session.');
1418
1573
  }
@@ -2000,11 +2155,13 @@ export class MessageInputComponent extends BaseAngularComponent {
2000
2155
  // Read the candidates before any await. This composer stays cached while the person opens
2001
2156
  // another conversation, and the chat area then rebinds its history and pinned agent, so a
2002
2157
  // read after the routing decision could see another conversation's state (or none).
2003
- const candidates = this.agentTurnCandidates(this.agentMentionIds(mentionResult));
2158
+ const candidates = this.agentTurnCandidates(this.agentMentionIds(mentionResult), this.userMentionIds(mentionResult), messageDetail.ParentID ?? null, messageDetail.UserID ?? this.CurrentUser?.ID ?? null);
2004
2159
  const routing = await this.decideAgentRouting(messageDetail, candidates, branchId);
2005
- const target = this.resolveAgentTurnTarget(candidates, routing);
2160
+ const appliedCandidates = ApplyRoutingDecision(candidates, routing);
2161
+ const target = this.resolveAgentTurnTarget(appliedCandidates);
2006
2162
  if (!target) {
2007
- await this.finishWithoutAgentTurn(messageDetail, 'NoAgent');
2163
+ const reason = IsAddressedToHuman(appliedCandidates) ? 'AddressedToHuman' : 'NoAgent';
2164
+ await this.finishWithoutAgentTurn(messageDetail, reason);
2008
2165
  return;
2009
2166
  }
2010
2167
  const turn = this.announceAgentTurn(messageDetail, target);
@@ -2028,6 +2185,9 @@ export class MessageInputComponent extends BaseAngularComponent {
2028
2185
  Enabled: this.EnableDecisionRouting,
2029
2186
  ReplyMode: this.AgentReplyMode,
2030
2187
  MentionedAgentIds: candidates.MentionedAgentIds,
2188
+ MentionedUserIds: candidates.MentionedUserIds,
2189
+ ReplyParentAgentId: candidates.ReplyParentAgentId,
2190
+ ReplyParentIsHuman: candidates.ReplyParentIsHuman,
2031
2191
  Message: message.Message ?? '',
2032
2192
  ContinuityAgentId: continuityAgentId
2033
2193
  });
@@ -2040,7 +2200,9 @@ export class MessageInputComponent extends BaseAngularComponent {
2040
2200
  if (!CanAskRoutingDecision(input)) {
2041
2201
  return null;
2042
2202
  }
2043
- input.ArtifactVersions = await this.loadRoutingArtifactVersions(message.ConversationID, branchId, input.Participants);
2203
+ input.ArtifactVersions = this.AgentReplyMode === 'Always'
2204
+ ? await this.loadRoutingArtifactVersions(message.ConversationID, branchId, input.Participants)
2205
+ : [];
2044
2206
  const outcome = await RunRoutingDecision(input, params => this.agentService.RunDecision(params));
2045
2207
  LogStatusEx({
2046
2208
  message: `Decision routing: ${outcome.Verdict}, ${outcome.Reason} (prompt run ${outcome.PromptRunID ?? 'none'})`,
@@ -2058,29 +2220,79 @@ export class MessageInputComponent extends BaseAngularComponent {
2058
2220
  * have answered within the history floor, the last few turns, and the conversation manager.
2059
2221
  * Artifact versions are added after, once the input is known to be worth asking about.
2060
2222
  *
2061
- * Participants come only from the '@' list's agents (`GetAvailableAgents`): active, top-level,
2062
- * unrestricted agents this person has run permission for. An agent that answered here but that
2063
- * the person can't run (a shared conversation, a revoked permission) is never offered, so routing
2064
- * can't send the turn to an agent the server would refuse. Before that list has loaded there are
2065
- * no participants, so no call is made and the message keeps today's routing. Speaker names in
2066
- * the recent turns still come from the full catalog, since any agent may have spoken.
2223
+ * Participants come only from the '@' list's agents (`GetAvailableAgents`) or restricted agents
2224
+ * the host explicitly granted in `AllowedAgentIDs`. An agent that answered here but that the
2225
+ * person can't run (a shared conversation, a revoked permission) is never offered, so routing
2226
+ * can't send the turn to an agent the server would refuse.
2067
2227
  */
2068
2228
  buildRoutingDecisionInput(message, continuityAgentId) {
2069
2229
  const history = this.ConversationHistory.filter(row => this.isWithinHistoryFloor(row) && !UUIDsEqual(row.ID, message.ID));
2070
2230
  const findAgent = (agentId) => AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, agentId));
2071
- const runnable = this.mentionAutocomplete.GetAvailableAgents();
2072
- const findRunnable = (agentId) => runnable.find(a => UUIDsEqual(a.ID, agentId));
2231
+ const runnable = (this.mentionAutocomplete ?? MentionAutocompleteService.Instance).GetAvailableAgents();
2232
+ const findRunnable = (agentId) => this.findRoutingRunnableAgent(agentId, runnable, findAgent);
2233
+ const allowNoAgentOption = this.AgentReplyMode === 'MentionOnly';
2234
+ const peopleById = this.MentionPeople?.length ? IndexByUUID(this.MentionPeople) : null;
2235
+ const findPersonName = peopleById
2236
+ ? (userId) => peopleById.Get(userId)?.Name
2237
+ : undefined;
2073
2238
  const manager = this.ConverationManagerAgent;
2239
+ const managerIdToExclude = this.AgentReplyMode === 'Always' ? (manager?.ID ?? null) : null;
2074
2240
  return {
2075
2241
  Message: message.Message ?? '',
2076
2242
  ContinuityAgentId: continuityAgentId,
2077
- Participants: CollectRoutingParticipants(history, manager?.ID ?? null, this.AllowedAgentIDs, findRunnable),
2078
- ConversationManager: manager?.ID && IsAgentAllowed(manager.ID, this.AllowedAgentIDs) ? manager : null,
2079
- RecentTurns: BuildRecentTurns(history, findAgent),
2243
+ Participants: CollectRoutingParticipants(history, managerIdToExclude, this.AllowedAgentIDs, findRunnable),
2244
+ ConversationManager: this.AgentReplyMode === 'Always' && manager?.ID && IsAgentAllowed(manager.ID, this.AllowedAgentIDs) ? manager : null,
2245
+ RecentTurns: BuildRecentTurns(history, findAgent, findPersonName),
2080
2246
  ArtifactVersions: [],
2081
- AllowedAgentIDs: this.AllowedAgentIDs
2247
+ AllowedAgentIDs: this.AllowedAgentIDs,
2248
+ AllowNoAgentOption: allowNoAgentOption
2082
2249
  };
2083
2250
  }
2251
+ findRoutingRunnableAgent(agentId, runnable, findAgent) {
2252
+ const fromAutocomplete = runnable.find(a => UUIDsEqual(a.ID, agentId));
2253
+ if (fromAutocomplete) {
2254
+ return fromAutocomplete;
2255
+ }
2256
+ if (this.AllowedAgentIDs != null && IsAgentAllowed(agentId, this.AllowedAgentIDs)) {
2257
+ const candidate = findAgent(agentId);
2258
+ if (candidate && this.isRunnableRestrictedAgent(candidate)) {
2259
+ return candidate;
2260
+ }
2261
+ }
2262
+ return undefined;
2263
+ }
2264
+ /**
2265
+ * True when a restricted agent (`IsRestricted`) passes the same runnability checks the '@' list
2266
+ * (`MentionAutocomplete._initializeInternal`) applies to unrestricted agents: active, top-level,
2267
+ * not a sub-agent, and runnable by the current user.
2268
+ */
2269
+ isRunnableRestrictedAgent(candidate) {
2270
+ if (!candidate.IsRestricted || candidate.ParentID || candidate.Status !== 'Active' || candidate.InvocationMode === 'Sub-Agent') {
2271
+ return false;
2272
+ }
2273
+ return this.canCurrentUserRunAgent(candidate);
2274
+ }
2275
+ canCurrentUserRunAgent(agent) {
2276
+ const user = this.CurrentUser;
2277
+ if (!user) {
2278
+ return false;
2279
+ }
2280
+ if (UUIDsEqual(agent.OwnerUserID, user.ID)) {
2281
+ return true;
2282
+ }
2283
+ try {
2284
+ const agentPermissions = AIEngineBase.Instance.AgentPermissions.filter(p => UUIDsEqual(p.AgentID, agent.ID));
2285
+ if (agentPermissions.length === 0) {
2286
+ return true;
2287
+ }
2288
+ const userRoleIds = new UUIDSet(user.UserRoles?.map(ur => ur.RoleID) ?? []);
2289
+ return agentPermissions.some(p => (UUIDsEqual(p.UserID, user.ID) || (p.RoleID != null && userRoleIds.Has(p.RoleID))) &&
2290
+ (p.CanDelete === true || p.CanEdit === true || p.CanRun === true));
2291
+ }
2292
+ catch {
2293
+ return false;
2294
+ }
2295
+ }
2084
2296
  /** Every participant's artifact versions on the path of `branchId` in this conversation, for the artifact question. */
2085
2297
  async loadRoutingArtifactVersions(conversationId, branchId, participants) {
2086
2298
  const artifactsByAgent = await Promise.all(participants.map(async (participant) => ({
@@ -2094,10 +2306,18 @@ export class MessageInputComponent extends BaseAngularComponent {
2094
2306
  return { ReplyMode: this.AgentReplyMode, AllowedAgentIDs: this.AllowedAgentIDs };
2095
2307
  }
2096
2308
  /** The agent each route would use for this conversation, before the host's rules apply. */
2097
- agentTurnCandidates(mentionedAgentIds) {
2309
+ agentTurnCandidates(mentionedAgentIds, mentionedUserIds = [], parentId = null, senderUserId = this.CurrentUser?.ID ?? null) {
2310
+ const parentRow = parentId
2311
+ ? this.ConversationHistory.find(row => this.isWithinHistoryFloor(row) && UUIDsEqual(row.ID, parentId))
2312
+ : undefined;
2098
2313
  return {
2099
2314
  MentionedAgentIds: mentionedAgentIds,
2100
- ContinuityAgentId: this.findLastNonSageAgentId(),
2315
+ MentionedUserIds: mentionedUserIds,
2316
+ ReplyParentAgentId: parentRow?.Role === 'AI' ? (parentRow.AgentID ?? null) : null,
2317
+ ReplyParentIsHuman: parentRow?.Role === 'User' &&
2318
+ !!parentRow.UserID &&
2319
+ (!senderUserId || !UUIDsEqual(parentRow.UserID, senderUserId)),
2320
+ ContinuityAgentId: this.AgentReplyMode === 'MentionOnly' ? this.findLastReplyAgentId() : this.findLastNonSageAgentId(),
2101
2321
  ConversationDefaultAgentId: this.ConversationDefaultAgentId,
2102
2322
  HostDefaultAgentId: this.DefaultAgentId,
2103
2323
  ConversationManagerAgentId: this.ConverationManagerAgent?.ID ?? null
@@ -2105,15 +2325,19 @@ export class MessageInputComponent extends BaseAngularComponent {
2105
2325
  }
2106
2326
  /**
2107
2327
  * Picks the agent that answers this message and the route that chose it, or null for no turn.
2108
- * A routing decision, when there was one, is applied to the candidates first.
2109
2328
  */
2110
- resolveAgentTurnTarget(candidates, routing) {
2111
- return ResolveAgentTurn(ApplyRoutingDecision(candidates, routing), this.agentTurnRules, agentId => this.isKnownAgent(agentId));
2329
+ resolveAgentTurnTarget(candidates) {
2330
+ return ResolveAgentTurn(candidates, this.agentTurnRules, agentId => this.isKnownAgent(agentId), agentId => this.isRunnableAgent(agentId));
2112
2331
  }
2113
2332
  /** The agents a message tags, in the order they appear. */
2114
2333
  agentMentionIds(mentionResult) {
2115
2334
  return mentionResult.mentions.filter(m => m.type === 'agent').map(m => m.id);
2116
2335
  }
2336
+ /** The people a message tags (`@Person`), in the order they appear. */
2337
+ userMentionIds(mentionResult) {
2338
+ const fromMentions = mentionResult.mentions.filter(m => m.type === 'user').map(m => m.id);
2339
+ return fromMentions.length > 0 ? fromMentions : (mentionResult.userMentions ?? []).map(m => m.id);
2340
+ }
2117
2341
  /**
2118
2342
  * Fires {@link BeforeAgentTurn} for the turn routing picked — before any reply row exists —
2119
2343
  * and applies what its listeners decided. Returns the turn to run, or null when a listener
@@ -2137,9 +2361,9 @@ export class MessageInputComponent extends BaseAngularComponent {
2137
2361
  return { AgentId: redirectAgentId, Route: 'Redirect' };
2138
2362
  }
2139
2363
  /**
2140
- * Settles a message that starts no agent turn — `MentionOnly` with no tagged agent, no allowed
2141
- * agent left, or a turn declined in {@link BeforeAgentTurn}. Nothing new is written; the
2142
- * person's own row is only confirmed Complete.
2364
+ * Settles a message that starts no agent turn — `MentionOnly` with no tagged agent, a message
2365
+ * addressed to a person, no allowed agent left, or a turn declined in {@link BeforeAgentTurn}.
2366
+ * Nothing new is written; the person's own row is only confirmed Complete.
2143
2367
  */
2144
2368
  async finishWithoutAgentTurn(userMessage, reason) {
2145
2369
  // Under the default rules a turn always has somewhere to go, so reaching here with no agent
@@ -2245,18 +2469,30 @@ export class MessageInputComponent extends BaseAngularComponent {
2245
2469
  }
2246
2470
  await this.processMessageThroughAgent(messageDetail, mentionResult, branchId);
2247
2471
  }
2472
+ _cachedContinuityReplyKey;
2473
+ _cachedContinuityReply = null;
2248
2474
  /**
2249
2475
  * The newest reply from an agent other than the conversation manager — among the replies a
2250
- * turn may read, so from {@link AgentHistoryFrom} onward when it's set.
2476
+ * turn may read, so from {@link AgentHistoryFrom} onward when it's set. Resets to null when
2477
+ * a newer user message `@`-mentions the conversation manager or when the conversation manager
2478
+ * received a handback from another agent.
2251
2479
  */
2252
2480
  findLastNonSageReply() {
2253
- return this.ConversationHistory
2254
- .slice()
2255
- .reverse()
2256
- .find(msg => msg.Role === 'AI' &&
2257
- !!msg.AgentID &&
2258
- !UUIDsEqual(msg.AgentID, this.ConverationManagerAgent?.ID) &&
2259
- this.isWithinHistoryFloor(msg)) ?? null;
2481
+ const history = this.ConversationHistory ?? [];
2482
+ const lastRow = history.length > 0 ? history[history.length - 1] : null;
2483
+ const managerId = this.ConverationManagerAgent?.ID ?? null;
2484
+ const floorMs = this.AgentHistoryFrom ? new Date(this.AgentHistoryFrom).getTime() : '';
2485
+ const cacheKey = `${history.length}|${lastRow?.ID ?? ''}|${lastRow?.AgentID ?? ''}|${managerId ?? ''}|${floorMs}`;
2486
+ if (this._cachedContinuityReplyKey === cacheKey) {
2487
+ return this._cachedContinuityReply ?? null;
2488
+ }
2489
+ const reply = FindContinuityAgentMessage(history, managerId, {
2490
+ conversationManagerName: this.ConverationManagerAgent?.Name ?? 'Sage',
2491
+ isWithinFloor: msg => this.isWithinHistoryFloor(msg),
2492
+ });
2493
+ this._cachedContinuityReplyKey = cacheKey;
2494
+ this._cachedContinuityReply = reply;
2495
+ return reply;
2260
2496
  }
2261
2497
  /**
2262
2498
  * Finds the last agent ID that isn't Sage (see {@link findLastNonSageReply}).
@@ -2264,6 +2500,17 @@ export class MessageInputComponent extends BaseAngularComponent {
2264
2500
  findLastNonSageAgentId() {
2265
2501
  return this.findLastNonSageReply()?.AgentID || null;
2266
2502
  }
2503
+ /**
2504
+ * The newest reply from any agent (including Sage) within {@link AgentHistoryFrom} — used under
2505
+ * `MentionOnly`, where the conversation manager is not an implicit fallback and is only present
2506
+ * when explicitly tagged.
2507
+ */
2508
+ findLastReplyAgentId() {
2509
+ return this.ConversationHistory
2510
+ .slice()
2511
+ .reverse()
2512
+ .find(msg => msg.Role === 'AI' && !!msg.AgentID && this.isWithinHistoryFloor(msg))?.AgentID || null;
2513
+ }
2267
2514
  /** True when a row was written at or after {@link AgentHistoryFrom}; always true without one. */
2268
2515
  isWithinHistoryFloor(detail) {
2269
2516
  const floor = this.AgentHistoryFrom;
@@ -2277,14 +2524,20 @@ export class MessageInputComponent extends BaseAngularComponent {
2277
2524
  isKnownAgent(agentId) {
2278
2525
  return AIEngineBase.Instance.ReadableAgents.some(a => UUIDsEqual(a.ID, agentId));
2279
2526
  }
2527
+ /**
2528
+ * True when the current user can run the agent directly in this chat: it is in the '@' list
2529
+ * (`GetAvailableAgents`) or is an allowed restricted agent that passes the same active,
2530
+ * top-level, non-sub-agent, and run-permission checks.
2531
+ */
2532
+ isRunnableAgent(agentId) {
2533
+ const runnable = (this.mentionAutocomplete ?? MentionAutocompleteService.Instance).GetAvailableAgents();
2534
+ const findAgent = (id) => AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, id));
2535
+ return this.findRoutingRunnableAgent(agentId, runnable, findAgent) != null;
2536
+ }
2280
2537
  /** The agent's name, when the client's agent catalog has it. */
2281
2538
  agentNameFor(agentId) {
2282
2539
  return AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, agentId))?.Name ?? null;
2283
2540
  }
2284
- /** The ID of the agent with this name, or null when there is none. */
2285
- agentIdByName(agentName) {
2286
- return AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName)?.ID ?? null;
2287
- }
2288
2541
  /** True when the agent is MJ's conversation manager. */
2289
2542
  isConversationManager(agentId) {
2290
2543
  return !!this.ConverationManagerAgent?.ID && UUIDsEqual(agentId, this.ConverationManagerAgent.ID);
@@ -2293,17 +2546,6 @@ export class MessageInputComponent extends BaseAngularComponent {
2293
2546
  notifyAgentTurnProblem(message, style) {
2294
2547
  MJNotificationService.Instance?.CreateSimpleNotification(message, style, 5000);
2295
2548
  }
2296
- /**
2297
- * Refuses agents the host doesn't allow — named by the conversation manager's delegation or
2298
- * by a workflow it planned — and settles the person's message.
2299
- */
2300
- async refuseDisallowedAgents(userMessage, agentNames) {
2301
- const subject = agentNames.join(', ');
2302
- this.notifyAgentTurnProblem(agentNames.length === 1
2303
- ? `${subject} isn't available in this chat, so it didn't run.`
2304
- : `${subject} aren't available in this chat, so they didn't run.`, 'warning');
2305
- await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
2306
- }
2307
2549
  /**
2308
2550
  * Runs the turn through the host's {@link AgentTurnHandler} instead of MJ's path. Nothing is
2309
2551
  * written here: the host writes the reply rows and reports them, and each is shown like a row
@@ -2562,20 +2804,11 @@ export class MessageInputComponent extends BaseAngularComponent {
2562
2804
  }
2563
2805
  }
2564
2806
  /**
2565
- * Acts on the conversation manager's settled reply: a workflow, a delegation, a chat answer, or
2566
- * silence (its reply is hidden and the last agent may continue).
2807
+ * Acts on the conversation manager's settled reply: a chat answer or silence (its reply is
2808
+ * hidden and the last agent may continue). Handoffs and task graphs execute server-side.
2567
2809
  */
2568
2810
  async routeConversationManagerResult(userMessage, result, conversationId, branchId, managerReply) {
2569
- // Stage 2: Check for task graph (multi-step orchestration)
2570
- if (result.payload?.taskGraph) {
2571
- await this.handleTaskGraphExecution(userMessage, result, conversationId, branchId, managerReply);
2572
- }
2573
- // Stage 3: Check for sub-agent invocation (single-step delegation)
2574
- else if (result.agentRun.FinalStep === 'Success' && result.payload?.invokeAgent) {
2575
- await this.handleSubAgentInvocation(userMessage, result, conversationId, branchId, managerReply);
2576
- }
2577
- // Stage 4: Direct chat response from Agent
2578
- else if (result.agentRun.FinalStep === 'Chat' && result.agentRun.Message) {
2811
+ if (result.agentRun.FinalStep === 'Chat' && result.agentRun.Message) {
2579
2812
  // Handle artifacts if any (but NOT task graphs - those are intermediate work products)
2580
2813
  // Server already created artifacts - just emit event to trigger UI reload
2581
2814
  if (result.payload && Object.keys(result.payload).length > 0) {
@@ -2584,93 +2817,14 @@ export class MessageInputComponent extends BaseAngularComponent {
2584
2817
  }
2585
2818
  await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
2586
2819
  }
2587
- // Stage 5: Silent observation — Sage said nothing, so its reply is hidden and the last agent may continue
2588
- else if (!result.agentRun.Message) {
2820
+ else if (result.agentRun.Message) {
2821
+ await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
2822
+ }
2823
+ else {
2589
2824
  await this.hideReply(managerReply);
2590
2825
  await this.handleSilentObservation(userMessage, conversationId, branchId);
2591
2826
  }
2592
2827
  }
2593
- /**
2594
- * Submits a task graph to the server and returns — the client no longer drives execution.
2595
- *
2596
- * This used to call the `ExecuteTaskGraph` mutation and await the ENTIRE workflow inside one
2597
- * long-lived GraphQL request. That had three unfixable problems: a page reload lost the awaited
2598
- * promise (leaving a workflow running with nobody watching), a server restart orphaned every
2599
- * in-flight task, and no other channel could reach the substrate at all.
2600
- *
2601
- * Now submission returns as soon as the graph is durable and the server-side dispatcher executes
2602
- * it. The client is purely an observer: progress arrives over the existing PubSub frames, and
2603
- * because the work lives in Task rows rather than in a promise, a reload can re-attach to a
2604
- * workflow already in flight.
2605
- *
2606
- * Single-task graphs are no longer special-cased here. The old client-side fork ran them through
2607
- * a different code path entirely; they now submit like any other graph, and the decision about
2608
- * whether a one-node graph is worth durable machinery moves server-side where it can be recorded.
2609
- *
2610
- * @param branchId The turn's branch (null is Main); the task-graph message is written there.
2611
- */
2612
- async handleTaskGraphExecution(userMessage, managerResult, conversationId, branchId, conversationManagerMessage) {
2613
- // `payload` is untyped by construction (an agent's payload shape is agent-specific), so pin the
2614
- // graph to the operation's own input contract at the boundary rather than letting it stay loose.
2615
- const taskGraph = managerResult.payload?.taskGraph;
2616
- if (!taskGraph)
2617
- return;
2618
- // The host's allowed list holds for every agent step of the plan, not only for a single
2619
- // delegation: a workflow naming an agent the chat doesn't allow is not submitted at all.
2620
- const disallowedAgents = FindDisallowedTaskGraphAgents(Array.isArray(taskGraph.tasks) ? taskGraph.tasks : [], this.AllowedAgentIDs, agentName => this.agentIdByName(agentName));
2621
- if (disallowedAgents.length > 0) {
2622
- await this.refuseDisallowedAgents(userMessage, disallowedAgents);
2623
- return;
2624
- }
2625
- const workflowName = taskGraph.workflowName || 'Workflow';
2626
- try {
2627
- // `TaskGraph.Submit` is a Remote Operation, not a bespoke mutation: the same call site is
2628
- // reachable from MCP, an Action wrapper, and this UI. `Execute` marshals over the generic
2629
- // `ExecuteRemoteOperation` transport, so there is no hand-written GraphQL document here.
2630
- //
2631
- // The server writes the workflow's status bubble under the reply that planned it (only the
2632
- // server writes agent replies) and binds the graph to it, so progress frames land on it.
2633
- const input = {
2634
- spec: taskGraph,
2635
- environmentID: MJEnvironmentEntityExtended.DefaultEnvironmentID,
2636
- conversationDetailID: conversationManagerMessage.ID,
2637
- postStatusMessage: true,
2638
- };
2639
- const result = await new TaskGraphSubmitOperation().Execute(input);
2640
- const output = result.Output;
2641
- if (output?.statusConversationDetailID) {
2642
- await this.showWorkflowStatus(output.statusConversationDetailID);
2643
- }
2644
- if (!result.Success || !output?.success) {
2645
- // The bubble says it was rejected; the reason (it may quote the plan) is shown only to this person.
2646
- const errorMsg = output?.errorMessage || result.ErrorMessage || 'Unknown error';
2647
- console.error('Task graph submission rejected:', errorMsg);
2648
- this.notifyAgentTurnProblem(`${workflowName} was rejected: ${errorMsg}`, 'error');
2649
- }
2650
- }
2651
- catch (error) {
2652
- const msg = error instanceof Error ? error.message : String(error);
2653
- console.error('Task graph submission failed:', error);
2654
- this.notifyAgentTurnProblem(`${workflowName} failed to submit: ${msg}`, 'error');
2655
- }
2656
- }
2657
- /**
2658
- * Shows the workflow status bubble the server wrote, and follows the dispatcher's progress frames on
2659
- * it while it runs, as the chat followed its own status row.
2660
- */
2661
- async showWorkflowStatus(statusId) {
2662
- const status = await this.dataCache.getConversationDetail(statusId, this.CurrentUser);
2663
- if (!status) {
2664
- console.warn(`Workflow status ${statusId} could not be loaded`);
2665
- return;
2666
- }
2667
- this.MessageSent.emit(status);
2668
- if (status.Status === 'In-Progress') {
2669
- const callback = this.createMessageProgressCallback(status.ID);
2670
- this.registeredCallbacks.set(status.ID, callback);
2671
- this.streamingService.registerMessageCallback(status.ID, callback);
2672
- }
2673
- }
2674
2828
  /**
2675
2829
  * Saves the person's own message with its text and status (in MJ's chat, marking it Complete once its turn is
2676
2830
  * over). Never used for an agent's reply: only the server writes those, and the chat shows them with
@@ -2759,88 +2913,6 @@ export class MessageInputComponent extends BaseAngularComponent {
2759
2913
  }
2760
2914
  };
2761
2915
  }
2762
- /**
2763
- * Handle sub-agent invocation based on Sage's payload: the delegate answers the user's message
2764
- * with its own reply, which the server creates. A failed first attempt is retried once; its
2765
- * reply is hidden when the retry takes over.
2766
- *
2767
- * @param branchId The turn's branch (null is Main).
2768
- */
2769
- async handleSubAgentInvocation(userMessage, managerResult, conversationId, branchId, delegatingReply) {
2770
- const payload = managerResult.payload;
2771
- const agentName = payload.invokeAgent;
2772
- const reasoning = payload.reasoning || 'Delegating to specialist agent';
2773
- const agent = AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName);
2774
- // The manager routes only among the allowed agents, but its answer is model output: hold
2775
- // the host's list here too, before any run starts for the delegate.
2776
- if (this.AllowedAgentIDs != null && !IsAgentAllowed(agent?.ID, this.AllowedAgentIDs)) {
2777
- await this.refuseDisallowedAgents(userMessage, [agentName]);
2778
- return;
2779
- }
2780
- let agentResponseMessage = null;
2781
- try {
2782
- // Load previous payload if agent has been invoked before
2783
- const { payload: previousPayload, artifactInfo } = agent?.ID
2784
- ? await this.loadPreviousPayloadForAgent(agent.ID, branchId)
2785
- : { payload: null, artifactInfo: null };
2786
- // Find configuration preset from previous @mention in conversation history
2787
- const configurationPresetId = agent?.ID
2788
- ? await this.agentService.FindConfigurationPresetForAgent(conversationId, agent.ID, branchId)
2789
- : undefined;
2790
- // The delegate's reply threads under the delegating reply, as it always has; the server checks and sets it.
2791
- const threading = {
2792
- ReplyParentId: delegatingReply.ID,
2793
- ...(branchId ? { ReplyBranchId: branchId } : {}),
2794
- };
2795
- const invoke = async (label) => {
2796
- const reply = await this.createReplyPlaceholder(conversationId, branchId, delegatingReply.ID, agent?.ID ?? null);
2797
- agentResponseMessage = reply;
2798
- const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, reasoning, this.adoptReplyRow(reply, { agentName, status: 'Starting...', relatedMessageId: userMessage.ID, conversationId, conversationName: this.ConversationName }), previousPayload, // Pass previous payload for continuity
2799
- this.createProgressCallback(reply, label), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId, // Pass configuration from previous @mention for continuity
2800
- this.AppContext, // Embedder-supplied app/form context
2801
- this.PlanModeEnabled, // per-request Plan Mode toggle
2802
- this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
2803
- this.AgentHistoryFrom, // the first moment of the conversation the run may read
2804
- threading);
2805
- return { reply, result };
2806
- };
2807
- const first = await invoke(agentName);
2808
- if (first.result && first.result.success) {
2809
- await this.showDelegateReply(first.reply, userMessage);
2810
- return;
2811
- }
2812
- // A post-ACK disconnect means the first run may still be executing — do not start a second run.
2813
- if (agentFailureDisposition(first.result).status === 'In-Progress') {
2814
- await this.applyAgentFailureToDetail(first.reply, userMessage, agentName, first.result);
2815
- return;
2816
- }
2817
- // Sub-agent failed - attempt auto-retry once. The failed reply stays the server's record of
2818
- // the attempt; it is hidden so the retry's reply takes its place on screen.
2819
- LogStatusEx({ message: `⚠️ ${agentName} failed, attempting auto-retry...`, verboseOnly: true });
2820
- await this.settleReply(first.reply, `❌ **${agentName}** failed`);
2821
- await this.hideReply(first.reply);
2822
- const retry = await invoke(`${agentName} (retry)`);
2823
- if (retry.result && retry.result.success) {
2824
- await this.showDelegateReply(retry.reply, userMessage);
2825
- }
2826
- else {
2827
- await this.applyAgentFailureToDetail(retry.reply, userMessage, agentName, retry.result, 'failed after retry');
2828
- }
2829
- }
2830
- catch (error) {
2831
- console.error(`❌ Error invoking sub-agent ${agentName}:`, error);
2832
- const catchResult = {
2833
- success: false,
2834
- errorMessage: error instanceof Error ? error.message : String(error),
2835
- };
2836
- if (agentResponseMessage) {
2837
- await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, catchResult);
2838
- }
2839
- else {
2840
- await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
2841
- }
2842
- }
2843
- }
2844
2916
  /**
2845
2917
  * Shows a delegate's successful reply as the server wrote it, reloads its artifacts (the server
2846
2918
  * may have created some even when the result payload is empty, e.g. a remote stage server), and
@@ -2963,27 +3035,14 @@ export class MessageInputComponent extends BaseAngularComponent {
2963
3035
  if (result && result.success) {
2964
3036
  this.emitAfterAgentTurn(conversationId, result);
2965
3037
  await this.settleReply(agentResponseMessage, `❌ **${agentName}** could not start.`);
2966
- // Multi-stage response handling (same logic as ambient Sage)
2967
- // Stage 1: Check for task graph (multi-step orchestration)
2968
- if (result.payload?.taskGraph) {
2969
- LogStatusEx({ message: '📋 Task graph detected from @mention, starting task orchestration', verboseOnly: true });
2970
- await this.handleTaskGraphExecution(userMessage, result, conversationId, branchId, agentResponseMessage);
2971
- }
2972
- // Stage 2: Check for sub-agent invocation (single-step delegation)
2973
- else if (result.agentRun.FinalStep === 'Success' && result.payload?.invokeAgent) {
2974
- LogStatusEx({ message: '🎯 Sub-agent invocation detected from @mention', verboseOnly: true });
2975
- await this.handleSubAgentInvocation(userMessage, result, conversationId, branchId, agentResponseMessage);
2976
- }
2977
- // Stage 3: Normal chat response
2978
- else {
2979
- // Server created artifacts - emit event to trigger UI reload
2980
- if (result.payload && Object.keys(result.payload).length > 0) {
2981
- this.emitArtifactReload(agentResponseMessage);
2982
- this.MessageSent.emit(agentResponseMessage);
2983
- }
2984
- // Mark user message as complete
2985
- await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
3038
+ // Normal chat response (handoffs and task graphs now execute server-side in AgentRunner)
3039
+ // Server created artifacts - emit event to trigger UI reload
3040
+ if (result.payload && Object.keys(result.payload).length > 0) {
3041
+ this.emitArtifactReload(agentResponseMessage);
3042
+ this.MessageSent.emit(agentResponseMessage);
2986
3043
  }
3044
+ // Mark user message as complete
3045
+ await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
2987
3046
  }
2988
3047
  else {
2989
3048
  await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, result);
@@ -2999,6 +3058,40 @@ export class MessageInputComponent extends BaseAngularComponent {
2999
3058
  }
3000
3059
  }
3001
3060
  }
3061
+ /**
3062
+ * Resolves the configuration preset for an implicit continuation turn on `agentId` using
3063
+ * `ResolveConversationAgentPreset`. Uses `this.AgentConfigurationPresetId` only when that
3064
+ * preset belongs to `agentId` (`preset.AgentID`), else falls back to `agentId`'s resolved preset.
3065
+ */
3066
+ async resolveContinuationPresetForAgent(conversationId, agentId, branchId) {
3067
+ const resolved = await ResolveConversationAgentPreset({
3068
+ ConversationID: conversationId,
3069
+ AgentID: agentId,
3070
+ BranchID: branchId,
3071
+ ContextUser: this.CurrentUser,
3072
+ Provider: this.ProviderToUse,
3073
+ });
3074
+ if (resolved?.Preset?.ID && resolved.Source !== 'Default') {
3075
+ return resolved.Preset.ID;
3076
+ }
3077
+ const headerPresetId = this.headerPresetForAgent(agentId);
3078
+ if (headerPresetId) {
3079
+ return headerPresetId;
3080
+ }
3081
+ return resolved?.Preset?.ID ?? undefined;
3082
+ }
3083
+ /**
3084
+ * Returns `this.AgentConfigurationPresetId` only when it belongs to `agentId` (`preset.AgentID === agentId`).
3085
+ */
3086
+ headerPresetForAgent(agentId) {
3087
+ const candidateId = this.AgentConfigurationPresetId;
3088
+ if (!candidateId) {
3089
+ return undefined;
3090
+ }
3091
+ const allForAgent = AIEngineBase.Instance.GetAgentConfigurationPresets(agentId, false);
3092
+ const matched = (allForAgent ?? []).find(p => UUIDsEqual(p.ID, candidateId) && (!p.AgentID || UUIDsEqual(p.AgentID, agentId)));
3093
+ return matched?.ID ?? undefined;
3094
+ }
3002
3095
  /**
3003
3096
  * Continue with the same agent from previous message (implicit continuation)
3004
3097
  * Bypasses Sage - no status messages
@@ -3022,7 +3115,6 @@ export class MessageInputComponent extends BaseAngularComponent {
3022
3115
  const agentName = agent.Name || 'Agent';
3023
3116
  let previousPayload = null;
3024
3117
  let previousArtifactInfo = null;
3025
- let previousConfigurationId = undefined;
3026
3118
  // Use targetArtifactVersionId if specified (from intent check).
3027
3119
  // Resolved by ID against the database, not against artifactsByDetailId: the intent check
3028
3120
  // may well have named a version attached to a message BELOW the loaded window, which is
@@ -3042,18 +3134,12 @@ export class MessageInputComponent extends BaseAngularComponent {
3042
3134
  console.warn('⚠️ Could not load target artifact version:', targetArtifactVersionId);
3043
3135
  }
3044
3136
  }
3045
- // Extract configuration preset from the User message that @mentioned this agent
3046
- // Uses the shared helper method in the agent service
3047
- previousConfigurationId = await this.agentService.FindConfigurationPresetForAgent(conversationId, agentId, branchId);
3048
- // Fall back to the chat header's mode-picker selection when nothing
3049
- // in the message history pinned a preset. The picker reflects the
3050
- // user's persistent per-agent mode preference (Draft / Standard /
3051
- // High) and applies to all subsequent non-mention routes. A
3052
- // history-derived preset still wins because it represents an
3053
- // explicit per-message intent the user expressed earlier.
3054
- if (!previousConfigurationId && this.AgentConfigurationPresetId) {
3055
- previousConfigurationId = this.AgentConfigurationPresetId;
3056
- }
3137
+ // Resolve a preset for the continued agent via ResolveConversationAgentPreset (which checks
3138
+ // @mention pins first, then user settings, then the agent's default preset).
3139
+ // The header mode-picker selection (this.AgentConfigurationPresetId) is used only when it
3140
+ // belongs to this agent (preset.AgentID === agentId); otherwise the agent's own default
3141
+ // preset is resolved so a pre-handoff picker selection from another agent never leaks.
3142
+ const previousConfigurationId = await this.resolveContinuationPresetForAgent(conversationId, agentId, branchId);
3057
3143
  // Fall back to searching through all agent messages for an artifact
3058
3144
  // This ensures payload continuity even after clarifying exchanges without artifacts
3059
3145
  // Fall back to this agent's newest OUTPUT artifact when the intent check named no
@@ -3251,10 +3337,25 @@ export class MessageInputComponent extends BaseAngularComponent {
3251
3337
  * follows the row until the server writes a terminal status. A placeholder the server never
3252
3338
  * acknowledged shows the failure on screen only.
3253
3339
  *
3340
+ * A run in AwaitingFeedback/Paused is not a failure at all — the agent asked this user a
3341
+ * question, or is parked on a workflow. It only reaches here because the envelope says
3342
+ * `success: false`, and because such a run usually carries no ErrorMessage it used to render as
3343
+ * "❌ failed — Unknown error": a verdict the server never returned. That branch surfaces the
3344
+ * agent's question and completes the bubble instead.
3345
+ *
3254
3346
  * Always completes the user message — the user turn finished regardless of what the agent is doing.
3255
3347
  */
3256
3348
  async applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, result, failedVerb = 'failed') {
3257
3349
  const disposition = agentFailureDisposition(result);
3350
+ if (disposition.status === 'Awaiting-Input') {
3351
+ // Complete, not Error: the turn is over (the timer must stop) and the agent's question is
3352
+ // the reply. `result` is passed through so any ResponseForm the agent attached to its
3353
+ // question still renders. The user's answer resumes the run, which re-enters
3354
+ // AgentStateService's active set as Running.
3355
+ await this.updateConversationDetail(agentResponseMessage, disposition.message || `**${agentName}** needs more information to continue — please reply with details.`, 'Complete', result ?? undefined);
3356
+ await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
3357
+ return;
3358
+ }
3258
3359
  if (disposition.status === 'In-Progress' && agentResponseMessage.IsSaved) {
3259
3360
  await agentResponseMessage.Load(agentResponseMessage.ID);
3260
3361
  if (agentResponseMessage.Status === 'In-Progress') {
@@ -3426,28 +3527,31 @@ export class MessageInputComponent extends BaseAngularComponent {
3426
3527
  } if (rf & 2) {
3427
3528
  let _t;
3428
3529
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.InputBox = _t.first);
3429
- } }, inputs: { ConversationId: "ConversationId", conversationId: "conversationId", ConversationName: "ConversationName", conversationName: "conversationName", CurrentUser: "CurrentUser", currentUser: "currentUser", Disabled: "Disabled", disabled: "disabled", ReadOnly: "ReadOnly", SendSlotMode: "SendSlotMode", StopCount: "StopCount", StopLabel: "StopLabel", StatusText: "StatusText", StatusElapsed: "StatusElapsed", 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", TargetBranchID: "TargetBranchID", ResolveTargetBranch: "ResolveTargetBranch", AgentTurnHandler: "AgentTurnHandler", AutoNameConversation: "AutoNameConversation", EnableDecisionRouting: "EnableDecisionRouting", InitialDraft: "InitialDraft", initialDraft: "initialDraft" }, outputs: { StopClicked: "StopClicked", MessageSent: "MessageSent", messageSent: "messageSent", MessageStreamed: "MessageStreamed", 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: 34, consts: [["pickerOrigin", "cdkOverlayOrigin"], ["inputBox", ""], ["cdkOverlayOrigin", "", 1, "message-input-wrapper"], [1, "processing-indicator"], [3, "StopClicked", "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", "SendSlotMode", "StopCount", "StopLabel", "StatusText", "StatusElapsed", "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) {
3530
+ } }, inputs: { ConversationId: "ConversationId", conversationId: "conversationId", ConversationName: "ConversationName", conversationName: "conversationName", CurrentUser: "CurrentUser", currentUser: "currentUser", Disabled: "Disabled", disabled: "disabled", ReadOnly: "ReadOnly", SendSlotMode: "SendSlotMode", StopCount: "StopCount", StopLabel: "StopLabel", StatusText: "StatusText", StatusElapsed: "StatusElapsed", 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", TargetBranchID: "TargetBranchID", ResolveTargetBranch: "ResolveTargetBranch", AgentTurnHandler: "AgentTurnHandler", AutoNameConversation: "AutoNameConversation", EnableDecisionRouting: "EnableDecisionRouting", InitialDraft: "InitialDraft", initialDraft: "initialDraft" }, outputs: { StopClicked: "StopClicked", MessageSent: "MessageSent", messageSent: "messageSent", MessageStreamed: "MessageStreamed", 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: 7, vars: 35, consts: [["pickerOrigin", "cdkOverlayOrigin"], ["inputBox", ""], ["cdkOverlayOrigin", "", 1, "message-input-wrapper"], [1, "processing-indicator"], [1, "composer-route", "route", 3, "--agent-accent"], [3, "StopClicked", "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", "SendSlotMode", "StopCount", "StopLabel", "StatusText", "StatusElapsed", "value"], ["cdkConnectedOverlay", "", "cdkConnectedOverlayBackdropClass", "cdk-overlay-transparent-backdrop", 3, "backdropClick", "detach", "cdkConnectedOverlayOrigin", "cdkConnectedOverlayOpen", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPush", "cdkConnectedOverlayHasBackdrop"], [1, "fas", "fa-circle-notch", "fa-spin"], [1, "composer-route", "route"], [1, "to"], ["aria-hidden", "true", 1, "dot"], ["type", "button", 1, "linkish"], ["type", "button", 1, "linkish", 3, "click"], [3, "AgentPicked", "Cancelled", "Provider", "CurrentUser", "Agents", "DefaultAgentId", "CoAgents", "DefaultCoAgentId"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
3430
3531
  i0.ɵɵelementStart(0, "div", 2, 0);
3431
3532
  i0.ɵɵconditionalCreate(2, MessageInputComponent_Conditional_2_Template, 4, 1, "div", 3);
3432
- i0.ɵɵelementStart(3, "mj-ai-composer", 4, 1);
3433
- i0.ɵɵlistener("StopClicked", function MessageInputComponent_Template_mj_ai_composer_StopClicked_3_listener() { return ctx.StopClicked.emit(); })("valueChange", function MessageInputComponent_Template_mj_ai_composer_valueChange_3_listener($event) { return ctx.OnComposerValueChanged($event); })("blurred", function MessageInputComponent_Template_mj_ai_composer_blurred_3_listener() { return ctx.ComposerBlurred.emit(); })("textSubmitted", function MessageInputComponent_Template_mj_ai_composer_textSubmitted_3_listener($event) { return ctx.onTextSubmitted($event); })("attachmentsChanged", function MessageInputComponent_Template_mj_ai_composer_attachmentsChanged_3_listener($event) { return ctx.onAttachmentsChanged($event); })("attachmentError", function MessageInputComponent_Template_mj_ai_composer_attachmentError_3_listener($event) { return ctx.onAttachmentError($event); })("voiceRequested", function MessageInputComponent_Template_mj_ai_composer_voiceRequested_3_listener() { return ctx.onStartRealtime(); })("voiceOptionsRequested", function MessageInputComponent_Template_mj_ai_composer_voiceOptionsRequested_3_listener() { return ctx.onRealtimeOptions(); })("planModeToggle", function MessageInputComponent_Template_mj_ai_composer_planModeToggle_3_listener() { return ctx.TogglePlanMode(); });
3533
+ i0.ɵɵconditionalCreate(3, MessageInputComponent_Conditional_3_Template, 8, 4, "div", 4);
3534
+ i0.ɵɵelementStart(4, "mj-ai-composer", 5, 1);
3535
+ i0.ɵɵlistener("StopClicked", function MessageInputComponent_Template_mj_ai_composer_StopClicked_4_listener() { return ctx.StopClicked.emit(); })("valueChange", function MessageInputComponent_Template_mj_ai_composer_valueChange_4_listener($event) { return ctx.OnComposerValueChanged($event); })("blurred", function MessageInputComponent_Template_mj_ai_composer_blurred_4_listener() { return ctx.ComposerBlurred.emit(); })("textSubmitted", function MessageInputComponent_Template_mj_ai_composer_textSubmitted_4_listener($event) { return ctx.onTextSubmitted($event); })("attachmentsChanged", function MessageInputComponent_Template_mj_ai_composer_attachmentsChanged_4_listener($event) { return ctx.onAttachmentsChanged($event); })("attachmentError", function MessageInputComponent_Template_mj_ai_composer_attachmentError_4_listener($event) { return ctx.onAttachmentError($event); })("voiceRequested", function MessageInputComponent_Template_mj_ai_composer_voiceRequested_4_listener() { return ctx.onStartRealtime(); })("voiceOptionsRequested", function MessageInputComponent_Template_mj_ai_composer_voiceOptionsRequested_4_listener() { return ctx.onRealtimeOptions(); })("planModeToggle", function MessageInputComponent_Template_mj_ai_composer_planModeToggle_4_listener() { return ctx.TogglePlanMode(); });
3434
3536
  i0.ɵɵelementEnd();
3435
- i0.ɵɵtemplate(5, MessageInputComponent_ng_template_5_Template, 1, 6, "ng-template", 5);
3436
- i0.ɵɵlistener("backdropClick", function MessageInputComponent_Template_ng_template_backdropClick_5_listener() { return ctx.onRealtimeAgentPickerCancelled(); })("detach", function MessageInputComponent_Template_ng_template_detach_5_listener() { return ctx.onRealtimeAgentPickerCancelled(); });
3537
+ i0.ɵɵtemplate(6, MessageInputComponent_ng_template_6_Template, 1, 6, "ng-template", 6);
3538
+ i0.ɵɵlistener("backdropClick", function MessageInputComponent_Template_ng_template_backdropClick_6_listener() { return ctx.onRealtimeAgentPickerCancelled(); })("detach", function MessageInputComponent_Template_ng_template_detach_6_listener() { return ctx.onRealtimeAgentPickerCancelled(); });
3437
3539
  i0.ɵɵelementEnd();
3438
3540
  } if (rf & 2) {
3439
- const pickerOrigin_r3 = i0.ɵɵreference(1);
3541
+ const pickerOrigin_r4 = i0.ɵɵreference(1);
3440
3542
  i0.ɵɵadvance(2);
3441
3543
  i0.ɵɵconditional(ctx.isProcessing ? 2 : -1);
3442
3544
  i0.ɵɵadvance();
3545
+ i0.ɵɵconditional(!ctx.emptyStateMode && ctx.CurrentRouteAgent ? 3 : -1);
3546
+ i0.ɵɵadvance();
3443
3547
  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)("SendSlotMode", ctx.SendSlotMode)("StopCount", ctx.StopCount)("StopLabel", ctx.StopLabel)("StatusText", ctx.StatusText)("StatusElapsed", ctx.StatusElapsed)("value", ctx.messageText);
3444
3548
  i0.ɵɵadvance(2);
3445
- i0.ɵɵproperty("cdkConnectedOverlayOrigin", pickerOrigin_r3)("cdkConnectedOverlayOpen", ctx.showRealtimeAgentPicker)("cdkConnectedOverlayPositions", ctx.pickerOverlayPositions)("cdkConnectedOverlayPush", true)("cdkConnectedOverlayHasBackdrop", true);
3446
- } }, 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}"] });
3549
+ i0.ɵɵproperty("cdkConnectedOverlayOrigin", pickerOrigin_r4)("cdkConnectedOverlayOpen", ctx.showRealtimeAgentPicker)("cdkConnectedOverlayPositions", ctx.pickerOverlayPositions)("cdkConnectedOverlayPush", true)("cdkConnectedOverlayHasBackdrop", true);
3550
+ } }, 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}\n\n.composer-route[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 10px 6px;\n font-size: 11.5px;\n color: var(--mj-text-muted);\n}\n\n.composer-route[_ngcontent-%COMP%] .to[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.composer-route[_ngcontent-%COMP%] .to[_ngcontent-%COMP%] b[_ngcontent-%COMP%] {\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.composer-route[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--agent-accent, var(--mj-brand-primary));\n flex-shrink: 0;\n transition: background-color 240ms cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.composer-route[_ngcontent-%COMP%] .linkish[_ngcontent-%COMP%] {\n margin-left: auto;\n padding: 2px 4px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 500;\n text-decoration: underline;\n text-decoration-style: dotted;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n cursor: pointer;\n}\n\n.composer-route[_ngcontent-%COMP%] .linkish[_ngcontent-%COMP%]:hover {\n color: var(--mj-brand-primary-hover);\n}\n\n.composer-route[_ngcontent-%COMP%] .linkish[_ngcontent-%COMP%]:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n box-shadow: var(--mj-focus-ring, none);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .composer-route[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%] {\n transition: none;\n }\n}"] });
3447
3551
  }
3448
3552
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputComponent, [{
3449
3553
  type: Component,
3450
- 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 [SendSlotMode]=\"SendSlotMode\"\n [StopCount]=\"StopCount\"\n [StopLabel]=\"StopLabel\"\n [StatusText]=\"StatusText\"\n [StatusElapsed]=\"StatusElapsed\"\n (StopClicked)=\"StopClicked.emit()\"\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"] }]
3554
+ 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 @if (!emptyStateMode && CurrentRouteAgent) {\n <div class=\"composer-route route\" [style.--agent-accent]=\"CurrentRouteAccentVar\">\n <span class=\"to\">\n <span class=\"dot\" aria-hidden=\"true\"></span>\n <span>Replying to <b>{{ CurrentRouteAgentName }}</b></span>\n </span>\n @if (ShowReturnToSage) {\n <button type=\"button\" class=\"linkish\" (click)=\"ReturnToSage()\">\n Return to {{ ConverationManagerAgent?.Name || 'Sage' }}\n </button>\n }\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 [SendSlotMode]=\"SendSlotMode\"\n [StopCount]=\"StopCount\"\n [StopLabel]=\"StopLabel\"\n [StatusText]=\"StatusText\"\n [StatusElapsed]=\"StatusElapsed\"\n (StopClicked)=\"StopClicked.emit()\"\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\n.composer-route {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 10px 6px;\n font-size: 11.5px;\n color: var(--mj-text-muted);\n}\n\n.composer-route .to {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.composer-route .to b {\n font-weight: 600;\n color: var(--mj-text-secondary);\n}\n\n.composer-route .dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--agent-accent, var(--mj-brand-primary));\n flex-shrink: 0;\n transition: background-color 240ms cubic-bezier(0.22, 1, 0.36, 1);\n}\n\n.composer-route .linkish {\n margin-left: auto;\n padding: 2px 4px;\n border: none;\n border-radius: 4px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 500;\n text-decoration: underline;\n text-decoration-style: dotted;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n cursor: pointer;\n}\n\n.composer-route .linkish:hover {\n color: var(--mj-brand-primary-hover);\n}\n\n.composer-route .linkish:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n box-shadow: var(--mj-focus-ring, none);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .composer-route .dot {\n transition: none;\n }\n}\n\n"] }]
3451
3555
  }], () => [{ 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: [{
3452
3556
  type: Input
3453
3557
  }], conversationId: [{
@@ -3674,5 +3778,5 @@ export class MessageInputComponent extends BaseAngularComponent {
3674
3778
  }], ComposerBlurred: [{
3675
3779
  type: Output
3676
3780
  }] }); })();
3677
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber: 78 }); })();
3781
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber: 72 }); })();
3678
3782
  //# sourceMappingURL=message-input.component.js.map