@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.
- package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
- package/dist/lib/components/collection/collection-view.component.js +1 -0
- package/dist/lib/components/collection/collection-view.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +174 -2
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +1266 -443
- package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-mode-picker.component.js +2 -2
- package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -1
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts +109 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.d.ts.map +1 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js +298 -0
- package/dist/lib/components/conversation/conversation-remote-browser-controller.js.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts +102 -0
- package/dist/lib/components/message/agent-handoff-card.component.d.ts.map +1 -0
- package/dist/lib/components/message/agent-handoff-card.component.js +439 -0
- package/dist/lib/components/message/agent-handoff-card.component.js.map +1 -0
- package/dist/lib/components/message/message-input.component.d.ts +80 -53
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +381 -277
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +18 -1
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +548 -426
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +8 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +40 -2
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js +1 -1
- package/dist/lib/components/realtime/interactive-component/run-view-component-artifact-source.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.d.ts +5 -0
- package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel-state.js +27 -1
- package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/media-channel.js +7 -2
- package/dist/lib/components/realtime/media/media-channel.js.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +19 -2
- package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js +59 -10
- package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +17 -6
- package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-agent-banner.component.js +61 -27
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.d.ts +26 -3
- package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-composer.component.js +196 -102
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +53 -15
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +125 -51
- package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.d.ts +9 -12
- package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-ui-config.js +38 -18
- package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +27 -4
- package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +8 -2
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -1
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +51 -10
- package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts +46 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.d.ts.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js +64 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel-config.js.map +1 -0
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +131 -9
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -1
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +347 -38
- package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -1
- package/dist/lib/conversations.module.d.ts +2 -1
- package/dist/lib/conversations.module.d.ts.map +1 -1
- package/dist/lib/conversations.module.js +9 -4
- package/dist/lib/conversations.module.js.map +1 -1
- package/dist/lib/events/chat-events.d.ts +5 -4
- package/dist/lib/events/chat-events.d.ts.map +1 -1
- package/dist/lib/events/chat-events.js +2 -2
- package/dist/lib/events/chat-events.js.map +1 -1
- package/dist/lib/models/agent-turn.model.d.ts +11 -7
- package/dist/lib/models/agent-turn.model.d.ts.map +1 -1
- package/dist/lib/models/agent-turn.model.js.map +1 -1
- package/dist/lib/services/active-tasks.service.d.ts +5 -0
- package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
- package/dist/lib/services/active-tasks.service.js +16 -0
- package/dist/lib/services/active-tasks.service.js.map +1 -1
- package/dist/lib/services/agent-state.service.d.ts.map +1 -1
- package/dist/lib/services/agent-state.service.js +7 -1
- package/dist/lib/services/agent-state.service.js.map +1 -1
- package/dist/lib/services/artifact-permission.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-permission.service.js +2 -0
- package/dist/lib/services/artifact-permission.service.js.map +1 -1
- package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
- package/dist/lib/services/artifact-state.service.js +1 -0
- package/dist/lib/services/artifact-state.service.js.map +1 -1
- package/dist/lib/services/collection-permission.service.d.ts.map +1 -1
- package/dist/lib/services/collection-permission.service.js +1 -0
- package/dist/lib/services/collection-permission.service.js.map +1 -1
- package/dist/lib/services/conversation-agent.service.d.ts +1 -4
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +7 -45
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/realtime-session-review.service.d.ts +2 -1
- package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session-review.service.js.map +1 -1
- package/dist/lib/services/search.service.d.ts.map +1 -1
- package/dist/lib/services/search.service.js +1 -0
- package/dist/lib/services/search.service.js.map +1 -1
- package/dist/lib/utils/agent-handoff-map.d.ts +52 -0
- package/dist/lib/utils/agent-handoff-map.d.ts.map +1 -0
- package/dist/lib/utils/agent-handoff-map.js +270 -0
- package/dist/lib/utils/agent-handoff-map.js.map +1 -0
- package/dist/lib/utils/agent-turn-routing.d.ts +62 -7
- package/dist/lib/utils/agent-turn-routing.d.ts.map +1 -1
- package/dist/lib/utils/agent-turn-routing.js +147 -6
- package/dist/lib/utils/agent-turn-routing.js.map +1 -1
- package/dist/lib/utils/decision-routing.d.ts +17 -9
- package/dist/lib/utils/decision-routing.d.ts.map +1 -1
- package/dist/lib/utils/decision-routing.js +23 -14
- package/dist/lib/utils/decision-routing.js.map +1 -1
- package/dist/lib/utils/thread-replies.d.ts +34 -0
- package/dist/lib/utils/thread-replies.d.ts.map +1 -0
- package/dist/lib/utils/thread-replies.js +49 -0
- package/dist/lib/utils/thread-replies.js.map +1 -0
- package/dist/public-api.d.ts +5 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +5 -0
- package/dist/public-api.js.map +1 -1
- 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 {
|
|
4
|
+
import { ConversationEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
5
5
|
import { BeforeAgentTurnEventArgs, AfterAgentTurnEventArgs } from '../../events/chat-events';
|
|
6
|
-
import { ResolveAgentTurn,
|
|
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",
|
|
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
|
|
46
|
+
function MessageInputComponent_Conditional_3_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
46
47
|
const _r2 = i0.ɵɵgetCurrentView();
|
|
47
|
-
i0.ɵɵelementStart(0, "
|
|
48
|
-
i0.ɵɵlistener("
|
|
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
|
-
//
|
|
1076
|
-
|
|
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
|
|
2160
|
+
const appliedCandidates = ApplyRoutingDecision(candidates, routing);
|
|
2161
|
+
const target = this.resolveAgentTurnTarget(appliedCandidates);
|
|
2006
2162
|
if (!target) {
|
|
2007
|
-
|
|
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 =
|
|
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`)
|
|
2062
|
-
*
|
|
2063
|
-
*
|
|
2064
|
-
* can't send the turn to an agent the server would refuse.
|
|
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) =>
|
|
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,
|
|
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
|
-
|
|
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
|
|
2111
|
-
return ResolveAgentTurn(
|
|
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,
|
|
2141
|
-
* agent left, or a turn declined in {@link BeforeAgentTurn}.
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
this.
|
|
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
|
|
2566
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
2588
|
-
|
|
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
|
-
//
|
|
2967
|
-
//
|
|
2968
|
-
if (result.payload
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
-
//
|
|
3046
|
-
//
|
|
3047
|
-
|
|
3048
|
-
//
|
|
3049
|
-
//
|
|
3050
|
-
|
|
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:
|
|
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.ɵɵ
|
|
3433
|
-
i0.ɵɵ
|
|
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(
|
|
3436
|
-
i0.ɵɵlistener("backdropClick", function
|
|
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
|
|
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",
|
|
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:
|
|
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
|