@memberjunction/ng-conversations 6.2.0-edge.5 → 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/attachment/image-viewer.component.d.ts +3 -1
- package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -1
- package/dist/lib/components/attachment/image-viewer.component.js +5 -1
- package/dist/lib/components/attachment/image-viewer.component.js.map +1 -1
- 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 +188 -9
- package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
- package/dist/lib/components/conversation/conversation-chat-area.component.js +1333 -464
- 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 +126 -59
- package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-input.component.js +559 -554
- package/dist/lib/components/message/message-input.component.js.map +1 -1
- package/dist/lib/components/message/message-item.component.d.ts +34 -2
- package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-item.component.js +678 -520
- package/dist/lib/components/message/message-item.component.js.map +1 -1
- package/dist/lib/components/message/message-list.component.d.ts +13 -1
- package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
- package/dist/lib/components/message/message-list.component.js +92 -39
- package/dist/lib/components/message/message-list.component.js.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.d.ts +19 -0
- package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/overlay/chat-overlay.component.js +125 -13
- package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
- package/dist/lib/components/project/project-form-modal.component.js +2 -2
- package/dist/lib/components/project/project-form-modal.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 +22 -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 +160 -98
- package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts +13 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.d.ts.map +1 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.js +21 -0
- package/dist/lib/components/realtime/realtime-call-failure-notice.js.map +1 -0
- 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 +226 -120
- package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +73 -16
- package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
- package/dist/lib/components/realtime/realtime-session-overlay.component.js +219 -108
- 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 +34 -5
- 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 +38 -16
- package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
- package/dist/lib/services/conversation-agent.service.js +57 -71
- package/dist/lib/services/conversation-agent.service.js.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.d.ts +5 -0
- package/dist/lib/services/conversation-detail-window.store.d.ts.map +1 -1
- package/dist/lib/services/conversation-detail-window.store.js +52 -5
- package/dist/lib/services/conversation-detail-window.store.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/realtime-session.service.d.ts.map +1 -1
- package/dist/lib/services/realtime-session.service.js +2 -1
- package/dist/lib/services/realtime-session.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-failure-display.d.ts +14 -0
- package/dist/lib/utils/agent-failure-display.d.ts.map +1 -0
- package/dist/lib/utils/agent-failure-display.js +26 -0
- package/dist/lib/utils/agent-failure-display.js.map +1 -0
- 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/message-list-order.d.ts +13 -0
- package/dist/lib/utils/message-list-order.d.ts.map +1 -0
- package/dist/lib/utils/message-list-order.js +31 -0
- package/dist/lib/utils/message-list-order.js.map +1 -0
- package/dist/lib/utils/pin-state.d.ts +11 -0
- package/dist/lib/utils/pin-state.d.ts.map +1 -0
- package/dist/lib/utils/pin-state.js +9 -0
- package/dist/lib/utils/pin-state.js.map +1 -0
- 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 +6 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +6 -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 { ConversationUtility, agentFailureDisposition, agentFailureMessage } from '@memberjunction/ai-core-plus';
|
|
11
|
+
import { AIEngineBase, ResolveConversationAgentPreset } from '@memberjunction/ai-engine-base';
|
|
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();
|
|
@@ -824,7 +854,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
824
854
|
*/
|
|
825
855
|
async InsertAgentMention(agentName, focus = true, clearExisting = true) {
|
|
826
856
|
if (!this.InputBox) {
|
|
827
|
-
|
|
857
|
+
LogStatusEx({ message: `[Omnibar→Chat] InsertAgentMention('${agentName}'): input box not mounted yet — caller will retry`, verboseOnly: true });
|
|
828
858
|
return false;
|
|
829
859
|
}
|
|
830
860
|
if (clearExisting) {
|
|
@@ -835,7 +865,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
835
865
|
}
|
|
836
866
|
try {
|
|
837
867
|
if (!this.mentionAutocomplete.IsInitialized && this.CurrentUser) {
|
|
838
|
-
|
|
868
|
+
LogStatusEx({ message: `[Omnibar→Chat] InsertAgentMention('${agentName}'): initializing mention autocomplete…`, verboseOnly: true });
|
|
839
869
|
await this.mentionAutocomplete.initialize(this.CurrentUser);
|
|
840
870
|
}
|
|
841
871
|
const wanted = agentName.trim().toLowerCase();
|
|
@@ -844,7 +874,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
844
874
|
.find(s => s.type === 'agent' && s.name.trim().toLowerCase() === wanted);
|
|
845
875
|
if (suggestion) {
|
|
846
876
|
const inserted = this.InputBox.InsertMention(suggestion, focus);
|
|
847
|
-
|
|
877
|
+
LogStatusEx({ message: `[Omnibar→Chat] InsertAgentMention('${agentName}'): resolved to pill (id=${suggestion.id}) — insert ${inserted ? 'OK' : 'FAILED (editor view not ready)'}`, verboseOnly: true });
|
|
848
878
|
if (inserted) {
|
|
849
879
|
if (focus) {
|
|
850
880
|
this.scheduleFocusReassert(agentName);
|
|
@@ -873,7 +903,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
873
903
|
const editor = this.InputBox?.mentionEditor;
|
|
874
904
|
if (editor && !editor.HasFocus) {
|
|
875
905
|
const ok = editor.FocusCaretAtEnd();
|
|
876
|
-
|
|
906
|
+
LogStatusEx({ message: `[Omnibar→Chat] focus re-assert (+${delay}ms) for '${context}': ${ok ? 'refocused' : 'editor gone'}`, verboseOnly: true });
|
|
877
907
|
}
|
|
878
908
|
}, delay);
|
|
879
909
|
}
|
|
@@ -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
|
}
|
|
@@ -1515,13 +1670,13 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1515
1670
|
}
|
|
1516
1671
|
// Skip if already complete or errored
|
|
1517
1672
|
if (message.Status === 'Complete' || message.Status === 'Error') {
|
|
1518
|
-
|
|
1673
|
+
LogStatusEx({ message: `[StreamingCallback] Message ${messageId} is ${message.Status}, ignoring progress update`, verboseOnly: true });
|
|
1519
1674
|
return;
|
|
1520
1675
|
}
|
|
1521
1676
|
// Check if message was marked as completed (prevents race condition)
|
|
1522
1677
|
const completionTime = this.completionTimestamps.get(messageId);
|
|
1523
1678
|
if (completionTime) {
|
|
1524
|
-
|
|
1679
|
+
LogStatusEx({ message: `[StreamingCallback] Message ${messageId} marked complete at ${new Date(completionTime).toISOString()}, ignoring late progress update`, verboseOnly: true });
|
|
1525
1680
|
return;
|
|
1526
1681
|
}
|
|
1527
1682
|
// Streamed final-response content: the service accumulates deltas, so
|
|
@@ -1557,7 +1712,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1557
1712
|
this.MessageSent.emit(message);
|
|
1558
1713
|
// CRITICAL: Update ActiveTasksService to keep the tasks dropdown in sync
|
|
1559
1714
|
this.activeTasks.updateStatusByConversationDetailId(message.ID, progress.message);
|
|
1560
|
-
|
|
1715
|
+
LogStatusEx({ message: `[StreamingCallback] Updated message ${messageId}: ${progress.taskName || 'Agent'}`, verboseOnly: true });
|
|
1561
1716
|
}
|
|
1562
1717
|
catch (error) {
|
|
1563
1718
|
console.error(`[StreamingCallback] Error updating message ${messageId}:`, error);
|
|
@@ -1572,7 +1727,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1572
1727
|
if (this.registeredCallbacks.size === 0) {
|
|
1573
1728
|
return;
|
|
1574
1729
|
}
|
|
1575
|
-
|
|
1730
|
+
LogStatusEx({ message: `🧹 Unregistering ${this.registeredCallbacks.size} message callbacks`, verboseOnly: true });
|
|
1576
1731
|
for (const [messageId, callback] of this.registeredCallbacks) {
|
|
1577
1732
|
this.streamingService.unregisterMessageCallback(messageId, callback);
|
|
1578
1733
|
}
|
|
@@ -1910,18 +2065,6 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
1910
2065
|
return undefined;
|
|
1911
2066
|
}
|
|
1912
2067
|
}
|
|
1913
|
-
/**
|
|
1914
|
-
* A new AI row of an agent turn: in `conversationId`, on the turn's branch `branchId` (null is
|
|
1915
|
-
* Main), visible. The caller sets Message, ParentID, Status and AgentID, then saves it.
|
|
1916
|
-
*/
|
|
1917
|
-
async createTurnRow(conversationId, branchId) {
|
|
1918
|
-
const row = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
1919
|
-
row.ConversationID = conversationId;
|
|
1920
|
-
row.BranchID = branchId;
|
|
1921
|
-
row.Role = 'AI';
|
|
1922
|
-
row.HiddenToUser = false;
|
|
1923
|
-
return row;
|
|
1924
|
-
}
|
|
1925
2068
|
/**
|
|
1926
2069
|
* Creates and configures a new conversation detail message on `branchId` (null is Main)
|
|
1927
2070
|
*/
|
|
@@ -2012,11 +2155,13 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2012
2155
|
// Read the candidates before any await. This composer stays cached while the person opens
|
|
2013
2156
|
// another conversation, and the chat area then rebinds its history and pinned agent, so a
|
|
2014
2157
|
// read after the routing decision could see another conversation's state (or none).
|
|
2015
|
-
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);
|
|
2016
2159
|
const routing = await this.decideAgentRouting(messageDetail, candidates, branchId);
|
|
2017
|
-
const
|
|
2160
|
+
const appliedCandidates = ApplyRoutingDecision(candidates, routing);
|
|
2161
|
+
const target = this.resolveAgentTurnTarget(appliedCandidates);
|
|
2018
2162
|
if (!target) {
|
|
2019
|
-
|
|
2163
|
+
const reason = IsAddressedToHuman(appliedCandidates) ? 'AddressedToHuman' : 'NoAgent';
|
|
2164
|
+
await this.finishWithoutAgentTurn(messageDetail, reason);
|
|
2020
2165
|
return;
|
|
2021
2166
|
}
|
|
2022
2167
|
const turn = this.announceAgentTurn(messageDetail, target);
|
|
@@ -2040,6 +2185,9 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2040
2185
|
Enabled: this.EnableDecisionRouting,
|
|
2041
2186
|
ReplyMode: this.AgentReplyMode,
|
|
2042
2187
|
MentionedAgentIds: candidates.MentionedAgentIds,
|
|
2188
|
+
MentionedUserIds: candidates.MentionedUserIds,
|
|
2189
|
+
ReplyParentAgentId: candidates.ReplyParentAgentId,
|
|
2190
|
+
ReplyParentIsHuman: candidates.ReplyParentIsHuman,
|
|
2043
2191
|
Message: message.Message ?? '',
|
|
2044
2192
|
ContinuityAgentId: continuityAgentId
|
|
2045
2193
|
});
|
|
@@ -2052,7 +2200,9 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2052
2200
|
if (!CanAskRoutingDecision(input)) {
|
|
2053
2201
|
return null;
|
|
2054
2202
|
}
|
|
2055
|
-
input.ArtifactVersions =
|
|
2203
|
+
input.ArtifactVersions = this.AgentReplyMode === 'Always'
|
|
2204
|
+
? await this.loadRoutingArtifactVersions(message.ConversationID, branchId, input.Participants)
|
|
2205
|
+
: [];
|
|
2056
2206
|
const outcome = await RunRoutingDecision(input, params => this.agentService.RunDecision(params));
|
|
2057
2207
|
LogStatusEx({
|
|
2058
2208
|
message: `Decision routing: ${outcome.Verdict}, ${outcome.Reason} (prompt run ${outcome.PromptRunID ?? 'none'})`,
|
|
@@ -2070,29 +2220,79 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2070
2220
|
* have answered within the history floor, the last few turns, and the conversation manager.
|
|
2071
2221
|
* Artifact versions are added after, once the input is known to be worth asking about.
|
|
2072
2222
|
*
|
|
2073
|
-
* Participants come only from the '@' list's agents (`GetAvailableAgents`)
|
|
2074
|
-
*
|
|
2075
|
-
*
|
|
2076
|
-
* can't send the turn to an agent the server would refuse.
|
|
2077
|
-
* no participants, so no call is made and the message keeps today's routing. Speaker names in
|
|
2078
|
-
* 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.
|
|
2079
2227
|
*/
|
|
2080
2228
|
buildRoutingDecisionInput(message, continuityAgentId) {
|
|
2081
2229
|
const history = this.ConversationHistory.filter(row => this.isWithinHistoryFloor(row) && !UUIDsEqual(row.ID, message.ID));
|
|
2082
2230
|
const findAgent = (agentId) => AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, agentId));
|
|
2083
|
-
const runnable = this.mentionAutocomplete.GetAvailableAgents();
|
|
2084
|
-
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;
|
|
2085
2238
|
const manager = this.ConverationManagerAgent;
|
|
2239
|
+
const managerIdToExclude = this.AgentReplyMode === 'Always' ? (manager?.ID ?? null) : null;
|
|
2086
2240
|
return {
|
|
2087
2241
|
Message: message.Message ?? '',
|
|
2088
2242
|
ContinuityAgentId: continuityAgentId,
|
|
2089
|
-
Participants: CollectRoutingParticipants(history,
|
|
2090
|
-
ConversationManager: manager?.ID && IsAgentAllowed(manager.ID, this.AllowedAgentIDs) ? manager : null,
|
|
2091
|
-
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),
|
|
2092
2246
|
ArtifactVersions: [],
|
|
2093
|
-
AllowedAgentIDs: this.AllowedAgentIDs
|
|
2247
|
+
AllowedAgentIDs: this.AllowedAgentIDs,
|
|
2248
|
+
AllowNoAgentOption: allowNoAgentOption
|
|
2094
2249
|
};
|
|
2095
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
|
+
}
|
|
2096
2296
|
/** Every participant's artifact versions on the path of `branchId` in this conversation, for the artifact question. */
|
|
2097
2297
|
async loadRoutingArtifactVersions(conversationId, branchId, participants) {
|
|
2098
2298
|
const artifactsByAgent = await Promise.all(participants.map(async (participant) => ({
|
|
@@ -2106,10 +2306,18 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2106
2306
|
return { ReplyMode: this.AgentReplyMode, AllowedAgentIDs: this.AllowedAgentIDs };
|
|
2107
2307
|
}
|
|
2108
2308
|
/** The agent each route would use for this conversation, before the host's rules apply. */
|
|
2109
|
-
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;
|
|
2110
2313
|
return {
|
|
2111
2314
|
MentionedAgentIds: mentionedAgentIds,
|
|
2112
|
-
|
|
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(),
|
|
2113
2321
|
ConversationDefaultAgentId: this.ConversationDefaultAgentId,
|
|
2114
2322
|
HostDefaultAgentId: this.DefaultAgentId,
|
|
2115
2323
|
ConversationManagerAgentId: this.ConverationManagerAgent?.ID ?? null
|
|
@@ -2117,15 +2325,19 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2117
2325
|
}
|
|
2118
2326
|
/**
|
|
2119
2327
|
* Picks the agent that answers this message and the route that chose it, or null for no turn.
|
|
2120
|
-
* A routing decision, when there was one, is applied to the candidates first.
|
|
2121
2328
|
*/
|
|
2122
|
-
resolveAgentTurnTarget(candidates
|
|
2123
|
-
return ResolveAgentTurn(
|
|
2329
|
+
resolveAgentTurnTarget(candidates) {
|
|
2330
|
+
return ResolveAgentTurn(candidates, this.agentTurnRules, agentId => this.isKnownAgent(agentId), agentId => this.isRunnableAgent(agentId));
|
|
2124
2331
|
}
|
|
2125
2332
|
/** The agents a message tags, in the order they appear. */
|
|
2126
2333
|
agentMentionIds(mentionResult) {
|
|
2127
2334
|
return mentionResult.mentions.filter(m => m.type === 'agent').map(m => m.id);
|
|
2128
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
|
+
}
|
|
2129
2341
|
/**
|
|
2130
2342
|
* Fires {@link BeforeAgentTurn} for the turn routing picked — before any reply row exists —
|
|
2131
2343
|
* and applies what its listeners decided. Returns the turn to run, or null when a listener
|
|
@@ -2149,9 +2361,9 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2149
2361
|
return { AgentId: redirectAgentId, Route: 'Redirect' };
|
|
2150
2362
|
}
|
|
2151
2363
|
/**
|
|
2152
|
-
* Settles a message that starts no agent turn — `MentionOnly` with no tagged agent,
|
|
2153
|
-
* agent left, or a turn declined in {@link BeforeAgentTurn}.
|
|
2154
|
-
* 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.
|
|
2155
2367
|
*/
|
|
2156
2368
|
async finishWithoutAgentTurn(userMessage, reason) {
|
|
2157
2369
|
// Under the default rules a turn always has somewhere to go, so reaching here with no agent
|
|
@@ -2257,18 +2469,30 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2257
2469
|
}
|
|
2258
2470
|
await this.processMessageThroughAgent(messageDetail, mentionResult, branchId);
|
|
2259
2471
|
}
|
|
2472
|
+
_cachedContinuityReplyKey;
|
|
2473
|
+
_cachedContinuityReply = null;
|
|
2260
2474
|
/**
|
|
2261
2475
|
* The newest reply from an agent other than the conversation manager — among the replies a
|
|
2262
|
-
* 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.
|
|
2263
2479
|
*/
|
|
2264
2480
|
findLastNonSageReply() {
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
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;
|
|
2272
2496
|
}
|
|
2273
2497
|
/**
|
|
2274
2498
|
* Finds the last agent ID that isn't Sage (see {@link findLastNonSageReply}).
|
|
@@ -2276,6 +2500,17 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2276
2500
|
findLastNonSageAgentId() {
|
|
2277
2501
|
return this.findLastNonSageReply()?.AgentID || null;
|
|
2278
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
|
+
}
|
|
2279
2514
|
/** True when a row was written at or after {@link AgentHistoryFrom}; always true without one. */
|
|
2280
2515
|
isWithinHistoryFloor(detail) {
|
|
2281
2516
|
const floor = this.AgentHistoryFrom;
|
|
@@ -2289,14 +2524,20 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2289
2524
|
isKnownAgent(agentId) {
|
|
2290
2525
|
return AIEngineBase.Instance.ReadableAgents.some(a => UUIDsEqual(a.ID, agentId));
|
|
2291
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
|
+
}
|
|
2292
2537
|
/** The agent's name, when the client's agent catalog has it. */
|
|
2293
2538
|
agentNameFor(agentId) {
|
|
2294
2539
|
return AIEngineBase.Instance.ReadableAgents.find(a => UUIDsEqual(a.ID, agentId))?.Name ?? null;
|
|
2295
2540
|
}
|
|
2296
|
-
/** The ID of the agent with this name, or null when there is none. */
|
|
2297
|
-
agentIdByName(agentName) {
|
|
2298
|
-
return AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName)?.ID ?? null;
|
|
2299
|
-
}
|
|
2300
2541
|
/** True when the agent is MJ's conversation manager. */
|
|
2301
2542
|
isConversationManager(agentId) {
|
|
2302
2543
|
return !!this.ConverationManagerAgent?.ID && UUIDsEqual(agentId, this.ConverationManagerAgent.ID);
|
|
@@ -2305,17 +2546,6 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2305
2546
|
notifyAgentTurnProblem(message, style) {
|
|
2306
2547
|
MJNotificationService.Instance?.CreateSimpleNotification(message, style, 5000);
|
|
2307
2548
|
}
|
|
2308
|
-
/**
|
|
2309
|
-
* Refuses agents the host doesn't allow — named by the conversation manager's delegation or
|
|
2310
|
-
* by a workflow it planned — and settles the person's message.
|
|
2311
|
-
*/
|
|
2312
|
-
async refuseDisallowedAgents(userMessage, agentNames) {
|
|
2313
|
-
const subject = agentNames.join(', ');
|
|
2314
|
-
this.notifyAgentTurnProblem(agentNames.length === 1
|
|
2315
|
-
? `${subject} isn't available in this chat, so it didn't run.`
|
|
2316
|
-
: `${subject} aren't available in this chat, so they didn't run.`, 'warning');
|
|
2317
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2318
|
-
}
|
|
2319
2549
|
/**
|
|
2320
2550
|
* Runs the turn through the host's {@link AgentTurnHandler} instead of MJ's path. Nothing is
|
|
2321
2551
|
* written here: the host writes the reply rows and reports them, and each is shown like a row
|
|
@@ -2398,7 +2628,7 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2398
2628
|
// Form responses always continue with the agent that requested the form
|
|
2399
2629
|
// Don't show "Analyzing intent..." message for this obvious case
|
|
2400
2630
|
if (ConversationUtility.ContainsFormResponse(message)) {
|
|
2401
|
-
|
|
2631
|
+
LogStatusEx({ message: '✅ Form response detected, skipping intent check UI (fast path)', verboseOnly: true });
|
|
2402
2632
|
return {
|
|
2403
2633
|
decision: 'YES',
|
|
2404
2634
|
reasoning: 'User submitted a form response to the previous agent'
|
|
@@ -2530,221 +2760,76 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2530
2760
|
* @param branchId The turn's branch (null is Main).
|
|
2531
2761
|
*/
|
|
2532
2762
|
async processMessageThroughAgent(userMessage, mentionResult, branchId) {
|
|
2533
|
-
let taskId = null;
|
|
2534
2763
|
let conversationManagerMessage = null;
|
|
2535
2764
|
// CRITICAL: Capture conversationId from user message at start
|
|
2536
2765
|
// This prevents race condition when user switches conversations during async processing
|
|
2537
2766
|
const conversationId = userMessage.ConversationID;
|
|
2538
2767
|
try {
|
|
2539
|
-
//
|
|
2540
|
-
conversationManagerMessage = await this.
|
|
2541
|
-
conversationManagerMessage.Message = '⏳ Starting...';
|
|
2542
|
-
conversationManagerMessage.ParentID = userMessage.ID;
|
|
2543
|
-
conversationManagerMessage.Status = 'In-Progress';
|
|
2544
|
-
// Use the preloaded Sage agent instead of looking it up
|
|
2545
|
-
if (this.ConverationManagerAgent?.ID) {
|
|
2546
|
-
conversationManagerMessage.AgentID = this.ConverationManagerAgent.ID;
|
|
2547
|
-
}
|
|
2548
|
-
await conversationManagerMessage.Save();
|
|
2549
|
-
this.MessageSent.emit(conversationManagerMessage);
|
|
2768
|
+
// Sage's reply shows as a local placeholder until the server creates the row and names it.
|
|
2769
|
+
conversationManagerMessage = await this.createReplyPlaceholder(conversationId, branchId, userMessage.ID, this.ConverationManagerAgent?.ID ?? null);
|
|
2550
2770
|
// Use Sage to evaluate and route
|
|
2551
2771
|
// Stage 1: Sage evaluates the message
|
|
2552
|
-
|
|
2772
|
+
const result = await this.agentService.ProcessMessage(conversationId, userMessage, this.ConversationHistory, this.adoptReplyRow(conversationManagerMessage, {
|
|
2553
2773
|
agentName: 'Sage',
|
|
2554
2774
|
status: 'Evaluating message...',
|
|
2555
2775
|
relatedMessageId: userMessage.ID,
|
|
2556
|
-
conversationDetailId: conversationManagerMessage.ID,
|
|
2557
2776
|
conversationId,
|
|
2558
2777
|
conversationName: this.ConversationName
|
|
2559
|
-
})
|
|
2560
|
-
const result = await this.agentService.ProcessMessage(conversationId, userMessage, this.ConversationHistory, conversationManagerMessage.ID, this.createProgressCallback(conversationManagerMessage, 'Sage'), this.AppContext, this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2778
|
+
}), this.createProgressCallback(conversationManagerMessage, 'Sage'), this.AppContext, this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2561
2779
|
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2562
2780
|
this.AllowedAgentIDs, // the agents the manager may delegate to
|
|
2563
|
-
this.AgentHistoryFrom
|
|
2781
|
+
this.AgentHistoryFrom, // the first moment of the conversation the run may read
|
|
2782
|
+
branchId);
|
|
2564
2783
|
// Emit afterAgentTurn on the happy path only — the error/failure branch
|
|
2565
2784
|
// immediately below handles its own cleanup and skips this emit.
|
|
2566
2785
|
if (result && result.success) {
|
|
2567
2786
|
this.emitAfterAgentTurn(conversationId, result);
|
|
2568
2787
|
}
|
|
2569
|
-
// Task will be removed automatically in markMessageComplete()
|
|
2570
|
-
// DO NOT remove here - agent may still be streaming/processing
|
|
2571
|
-
taskId = null; // Clear reference but don't remove from service
|
|
2572
2788
|
if (!result || !result.success) {
|
|
2573
2789
|
await this.applyAgentFailureToDetail(conversationManagerMessage, userMessage, this.ConverationManagerAgent?.Name || 'Sage', result, 'failed');
|
|
2574
2790
|
console.warn('⚠️ Sage failed:', agentFailureMessage(result, 'Agent evaluation failed'));
|
|
2575
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
2576
2791
|
return;
|
|
2577
2792
|
}
|
|
2578
|
-
//
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
// Remove CM from active tasks
|
|
2582
|
-
if (taskId) {
|
|
2583
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
// Stage 3: Check for sub-agent invocation (single-step delegation)
|
|
2587
|
-
else if (result.agentRun.FinalStep === 'Success' && result.payload?.invokeAgent) {
|
|
2588
|
-
// Reuse the existing conversationManagerMessage instead of creating new ones
|
|
2589
|
-
await this.handleSubAgentInvocation(userMessage, result, conversationId, branchId, conversationManagerMessage);
|
|
2590
|
-
// Remove CM from active tasks
|
|
2591
|
-
if (taskId) {
|
|
2592
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
// Stage 4: Direct chat response from Agent
|
|
2596
|
-
else if (result.agentRun.FinalStep === 'Chat' && result.agentRun.Message) {
|
|
2597
|
-
// Normal chat response
|
|
2598
|
-
// use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
|
|
2599
|
-
// Note: updateConversationDetail will call markMessageComplete() for us
|
|
2600
|
-
await this.updateConversationDetail(conversationManagerMessage, result.agentRun.Message, 'Complete', result);
|
|
2601
|
-
// Handle artifacts if any (but NOT task graphs - those are intermediate work products)
|
|
2602
|
-
// Server already created artifacts - just emit event to trigger UI reload
|
|
2603
|
-
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
2604
|
-
this.emitArtifactReload(conversationManagerMessage);
|
|
2605
|
-
this.MessageSent.emit(conversationManagerMessage);
|
|
2606
|
-
}
|
|
2607
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2608
|
-
// Remove CM from active tasks
|
|
2609
|
-
if (taskId) {
|
|
2610
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2611
|
-
}
|
|
2612
|
-
// Clean up completion timestamp after delay
|
|
2613
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
2614
|
-
}
|
|
2615
|
-
// Stage 5: Silent observation - but check for message content first
|
|
2616
|
-
else {
|
|
2617
|
-
// Check if there's a message to display even without payload/taskGraph
|
|
2618
|
-
if (result.agentRun.Message) {
|
|
2619
|
-
// Mark message as completing BEFORE setting final content
|
|
2620
|
-
this.markMessageComplete(conversationManagerMessage);
|
|
2621
|
-
conversationManagerMessage.HiddenToUser = false;
|
|
2622
|
-
// use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
|
|
2623
|
-
await this.updateConversationDetail(conversationManagerMessage, result.agentRun.Message, 'Complete', result);
|
|
2624
|
-
this.MessageSent.emit(conversationManagerMessage);
|
|
2625
|
-
// Clean up completion timestamp after delay
|
|
2626
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
2627
|
-
}
|
|
2628
|
-
else {
|
|
2629
|
-
// Mark message as completing
|
|
2630
|
-
this.markMessageComplete(conversationManagerMessage);
|
|
2631
|
-
// Hide the Sage message
|
|
2632
|
-
conversationManagerMessage.HiddenToUser = true;
|
|
2633
|
-
// use update helper to ensure that if there is a race condition with more streaming updates we don't allow that to override this final message
|
|
2634
|
-
await this.updateConversationDetail(conversationManagerMessage, conversationManagerMessage.Message, 'Complete', result);
|
|
2635
|
-
this.MessageSent.emit(conversationManagerMessage);
|
|
2636
|
-
await this.handleSilentObservation(userMessage, conversationId, branchId);
|
|
2637
|
-
// Clean up completion timestamp after delay
|
|
2638
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
2639
|
-
}
|
|
2640
|
-
// Remove CM from active tasks
|
|
2641
|
-
if (taskId) {
|
|
2642
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2793
|
+
// The server wrote Sage's reply; show it as the server left it before routing on.
|
|
2794
|
+
await this.settleReply(conversationManagerMessage, '❌ Sage could not start.');
|
|
2795
|
+
await this.routeConversationManagerResult(userMessage, result, conversationId, branchId, conversationManagerMessage);
|
|
2645
2796
|
}
|
|
2646
2797
|
catch (error) {
|
|
2647
2798
|
console.error('❌ Error processing message through agents:', error);
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
// Use updateConversationDetail to ensure task cleanup
|
|
2651
|
-
conversationManagerMessage.Error = String(error);
|
|
2652
|
-
await this.updateConversationDetail(conversationManagerMessage, `❌ Error: ${String(error)}`, 'Error');
|
|
2653
|
-
// Clean up completion timestamp
|
|
2654
|
-
this.cleanupCompletionTimestamp(conversationManagerMessage.ID);
|
|
2799
|
+
if (conversationManagerMessage) {
|
|
2800
|
+
await this.settleReply(conversationManagerMessage, `❌ Error: ${String(error)}`);
|
|
2655
2801
|
}
|
|
2656
2802
|
// Mark user message as complete
|
|
2657
2803
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2658
|
-
// Clean up active task
|
|
2659
|
-
if (taskId) {
|
|
2660
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
/** Detaches the progress callback for one message — used when submission never starts. */
|
|
2665
|
-
releaseProgressCallback(messageId) {
|
|
2666
|
-
const callback = this.registeredCallbacks.get(messageId);
|
|
2667
|
-
if (callback) {
|
|
2668
|
-
this.streamingService.unregisterMessageCallback(messageId, callback);
|
|
2669
|
-
this.registeredCallbacks.delete(messageId);
|
|
2670
2804
|
}
|
|
2671
2805
|
}
|
|
2672
2806
|
/**
|
|
2673
|
-
*
|
|
2674
|
-
*
|
|
2675
|
-
* This used to call the `ExecuteTaskGraph` mutation and await the ENTIRE workflow inside one
|
|
2676
|
-
* long-lived GraphQL request. That had three unfixable problems: a page reload lost the awaited
|
|
2677
|
-
* promise (leaving a workflow running with nobody watching), a server restart orphaned every
|
|
2678
|
-
* in-flight task, and no other channel could reach the substrate at all.
|
|
2679
|
-
*
|
|
2680
|
-
* Now submission returns as soon as the graph is durable and the server-side dispatcher executes
|
|
2681
|
-
* it. The client is purely an observer: progress arrives over the existing PubSub frames, and
|
|
2682
|
-
* because the work lives in Task rows rather than in a promise, a reload can re-attach to a
|
|
2683
|
-
* workflow already in flight.
|
|
2684
|
-
*
|
|
2685
|
-
* Single-task graphs are no longer special-cased here. The old client-side fork ran them through
|
|
2686
|
-
* a different code path entirely; they now submit like any other graph, and the decision about
|
|
2687
|
-
* whether a one-node graph is worth durable machinery moves server-side where it can be recorded.
|
|
2688
|
-
*
|
|
2689
|
-
* @param branchId The turn's branch (null is Main); the task-graph message is written there.
|
|
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.
|
|
2690
2809
|
*/
|
|
2691
|
-
async
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
// delegation: a workflow naming an agent the chat doesn't allow is not submitted at all.
|
|
2699
|
-
const disallowedAgents = FindDisallowedTaskGraphAgents(Array.isArray(taskGraph.tasks) ? taskGraph.tasks : [], this.AllowedAgentIDs, agentName => this.agentIdByName(agentName));
|
|
2700
|
-
if (disallowedAgents.length > 0) {
|
|
2701
|
-
await this.refuseDisallowedAgents(userMessage, disallowedAgents);
|
|
2702
|
-
return;
|
|
2703
|
-
}
|
|
2704
|
-
const workflowName = taskGraph.workflowName || 'Workflow';
|
|
2705
|
-
const reasoning = taskGraph.reasoning || 'Executing multi-step workflow';
|
|
2706
|
-
const taskCount = Array.isArray(taskGraph.tasks) ? taskGraph.tasks.length : 0;
|
|
2707
|
-
// A message the user can watch. Progress frames from the dispatcher land against this ID.
|
|
2708
|
-
const taskExecutionMessage = await this.createTurnRow(conversationId, branchId);
|
|
2709
|
-
taskExecutionMessage.Message = `⏳ **${workflowName}**\n\n${reasoning}\n\nSubmitting ${taskCount} task(s)…`;
|
|
2710
|
-
taskExecutionMessage.ParentID = conversationManagerMessage.ID;
|
|
2711
|
-
taskExecutionMessage.Status = 'In-Progress';
|
|
2712
|
-
await taskExecutionMessage.Save();
|
|
2713
|
-
this.MessageSent.emit(taskExecutionMessage);
|
|
2714
|
-
const callback = this.createMessageProgressCallback(taskExecutionMessage.ID);
|
|
2715
|
-
this.registeredCallbacks.set(taskExecutionMessage.ID, callback);
|
|
2716
|
-
this.streamingService.registerMessageCallback(taskExecutionMessage.ID, callback);
|
|
2717
|
-
try {
|
|
2718
|
-
// `TaskGraph.Submit` is a Remote Operation, not a bespoke mutation: the same call site is
|
|
2719
|
-
// reachable from MCP, an Action wrapper, and this UI. `Execute` marshals over the generic
|
|
2720
|
-
// `ExecuteRemoteOperation` transport, so there is no hand-written GraphQL document here.
|
|
2721
|
-
const result = await new TaskGraphSubmitOperation().Execute({
|
|
2722
|
-
spec: taskGraph,
|
|
2723
|
-
environmentID: MJEnvironmentEntityExtended.DefaultEnvironmentID,
|
|
2724
|
-
conversationDetailID: taskExecutionMessage.ID,
|
|
2725
|
-
});
|
|
2726
|
-
if (result.Success && result.Output?.success) {
|
|
2727
|
-
// Deliberately NOT "completed" — submission means the work is durable and running, and
|
|
2728
|
-
// claiming completion here is exactly the lie the old await-everything path told when it
|
|
2729
|
-
// returned early. The dispatcher's progress frames update this message as tasks finish.
|
|
2730
|
-
await this.updateConversationDetail(taskExecutionMessage, `▶️ **${workflowName}** started — ${taskCount} task(s) running.`, 'In-Progress');
|
|
2731
|
-
}
|
|
2732
|
-
else {
|
|
2733
|
-
const errorMsg = result.Output?.errorMessage || result.ErrorMessage || 'Unknown error';
|
|
2734
|
-
console.error('Task graph submission rejected:', errorMsg);
|
|
2735
|
-
taskExecutionMessage.Error = errorMsg;
|
|
2736
|
-
await this.updateConversationDetail(taskExecutionMessage, `❌ **${workflowName}** rejected: ${errorMsg}`, 'Error');
|
|
2737
|
-
this.releaseProgressCallback(taskExecutionMessage.ID);
|
|
2810
|
+
async routeConversationManagerResult(userMessage, result, conversationId, branchId, managerReply) {
|
|
2811
|
+
if (result.agentRun.FinalStep === 'Chat' && result.agentRun.Message) {
|
|
2812
|
+
// Handle artifacts if any (but NOT task graphs - those are intermediate work products)
|
|
2813
|
+
// Server already created artifacts - just emit event to trigger UI reload
|
|
2814
|
+
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
2815
|
+
this.emitArtifactReload(managerReply);
|
|
2816
|
+
this.MessageSent.emit(managerReply);
|
|
2738
2817
|
}
|
|
2818
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2739
2819
|
}
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
await this.
|
|
2745
|
-
this.
|
|
2820
|
+
else if (result.agentRun.Message) {
|
|
2821
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2822
|
+
}
|
|
2823
|
+
else {
|
|
2824
|
+
await this.hideReply(managerReply);
|
|
2825
|
+
await this.handleSilentObservation(userMessage, conversationId, branchId);
|
|
2746
2826
|
}
|
|
2747
2827
|
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Saves the person's own message with its text and status (in MJ's chat, marking it Complete once its turn is
|
|
2830
|
+
* over). Never used for an agent's reply: only the server writes those, and the chat shows them with
|
|
2831
|
+
* {@link settleReply}.
|
|
2832
|
+
*/
|
|
2748
2833
|
async updateConversationDetail(convoDetail, message, status, result) {
|
|
2749
2834
|
// Mark as completing FIRST if status is Complete or Error
|
|
2750
2835
|
// This ensures task cleanup happens even if we return early due to guard clause
|
|
@@ -2815,10 +2900,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2815
2900
|
}
|
|
2816
2901
|
const source = await this.agentService.FindLatestAgentOutputVersion(this.ConversationId, agentId, this.AgentHistoryFrom, branchId);
|
|
2817
2902
|
if (!source || source.payload == null) {
|
|
2818
|
-
|
|
2903
|
+
LogStatusEx({ message: `📦 No previous payload found for agent ${agentId}`, verboseOnly: true });
|
|
2819
2904
|
return { payload: null, artifactInfo: null };
|
|
2820
2905
|
}
|
|
2821
|
-
|
|
2906
|
+
LogStatusEx({ message: `📦 Loaded previous payload for agent ${agentId} from artifact version ${source.versionId}`, verboseOnly: true });
|
|
2822
2907
|
return {
|
|
2823
2908
|
payload: source.payload,
|
|
2824
2909
|
artifactInfo: {
|
|
@@ -2829,139 +2914,15 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2829
2914
|
};
|
|
2830
2915
|
}
|
|
2831
2916
|
/**
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
2835
|
-
* @param branchId The turn's branch (null is Main).
|
|
2917
|
+
* Shows a delegate's successful reply as the server wrote it, reloads its artifacts (the server
|
|
2918
|
+
* may have created some even when the result payload is empty, e.g. a remote stage server), and
|
|
2919
|
+
* completes the user's message.
|
|
2836
2920
|
*/
|
|
2837
|
-
async
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
let agentResponseMessage = null;
|
|
2843
|
-
try {
|
|
2844
|
-
// Look up the agent to get its ID
|
|
2845
|
-
const agent = AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName);
|
|
2846
|
-
// The manager routes only among the allowed agents, but its answer is model output: hold
|
|
2847
|
-
// the host's list here too, before any row is written for the delegate.
|
|
2848
|
-
if (this.AllowedAgentIDs != null && !IsAgentAllowed(agent?.ID, this.AllowedAgentIDs)) {
|
|
2849
|
-
await this.refuseDisallowedAgents(userMessage, [agentName]);
|
|
2850
|
-
return;
|
|
2851
|
-
}
|
|
2852
|
-
// Create AI response message BEFORE invoking agent (for duration tracking)
|
|
2853
|
-
agentResponseMessage = await this.createTurnRow(conversationId, branchId);
|
|
2854
|
-
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
2855
|
-
agentResponseMessage.ParentID = conversationManagerMessage.ID; // Thread under delegation message
|
|
2856
|
-
agentResponseMessage.Status = 'In-Progress';
|
|
2857
|
-
// Set AgentID immediately for proper attribution
|
|
2858
|
-
if (agent?.ID) {
|
|
2859
|
-
agentResponseMessage.AgentID = agent.ID;
|
|
2860
|
-
}
|
|
2861
|
-
// Save the record to establish __mj_CreatedAt timestamp
|
|
2862
|
-
await agentResponseMessage.Save();
|
|
2863
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
2864
|
-
// Add sub-agent to active tasks
|
|
2865
|
-
const newTaskId = this.activeTasks.add({
|
|
2866
|
-
agentName: agentName,
|
|
2867
|
-
status: 'Starting...',
|
|
2868
|
-
relatedMessageId: userMessage.ID,
|
|
2869
|
-
conversationDetailId: agentResponseMessage.ID,
|
|
2870
|
-
conversationId,
|
|
2871
|
-
conversationName: this.ConversationName
|
|
2872
|
-
});
|
|
2873
|
-
// Load previous payload if agent has been invoked before
|
|
2874
|
-
const { payload: previousPayload, artifactInfo } = agent?.ID
|
|
2875
|
-
? await this.loadPreviousPayloadForAgent(agent.ID, branchId)
|
|
2876
|
-
: { payload: null, artifactInfo: null };
|
|
2877
|
-
// Find configuration preset from previous @mention in conversation history
|
|
2878
|
-
const configurationPresetId = agent?.ID
|
|
2879
|
-
? await this.agentService.FindConfigurationPresetForAgent(conversationId, agent.ID, branchId)
|
|
2880
|
-
: undefined;
|
|
2881
|
-
// Invoke the sub-agent with progress callback
|
|
2882
|
-
const subResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
|
|
2883
|
-
this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId, // Pass configuration from previous @mention for continuity
|
|
2884
|
-
this.AppContext, // Embedder-supplied app/form context
|
|
2885
|
-
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2886
|
-
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2887
|
-
this.AgentHistoryFrom);
|
|
2888
|
-
// Task will be removed automatically in markMessageComplete() when status changes to Complete/Error
|
|
2889
|
-
// DO NOT remove here - allows UI to show task during entire execution
|
|
2890
|
-
if (subResult && subResult.success) {
|
|
2891
|
-
// Update the response message with agent result
|
|
2892
|
-
// Store the agent ID for display
|
|
2893
|
-
if (subResult.agentRun.AgentID) {
|
|
2894
|
-
agentResponseMessage.AgentID = subResult.agentRun.AgentID;
|
|
2895
|
-
}
|
|
2896
|
-
await this.updateConversationDetail(agentResponseMessage, subResult.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', subResult);
|
|
2897
|
-
// Always emit artifactCreated to trigger UI reload — the server may have created
|
|
2898
|
-
// artifacts even when the result payload is empty (e.g., remote stage server).
|
|
2899
|
-
// onArtifactCreated will reload from DB and discover any artifacts that exist.
|
|
2900
|
-
this.emitArtifactReload(agentResponseMessage);
|
|
2901
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
2902
|
-
// Mark user message as complete
|
|
2903
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2904
|
-
}
|
|
2905
|
-
else {
|
|
2906
|
-
// A post-ACK disconnect means the first run may still be executing on this
|
|
2907
|
-
// detail — do not start a second run on the same conversationDetailId.
|
|
2908
|
-
if (agentFailureDisposition(subResult).status === 'In-Progress') {
|
|
2909
|
-
await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, subResult);
|
|
2910
|
-
return;
|
|
2911
|
-
}
|
|
2912
|
-
// Sub-agent failed - attempt auto-retry once
|
|
2913
|
-
console.log(`⚠️ ${agentName} failed, attempting auto-retry...`);
|
|
2914
|
-
await this.updateConversationDetail(conversationManagerMessage, `👉 **${agentName}** will handle this request...\n\n⚠️ First attempt failed, retrying...`, conversationManagerMessage.Status);
|
|
2915
|
-
// Update the existing agentResponseMessage to show retry status
|
|
2916
|
-
await this.updateConversationDetail(agentResponseMessage, "Retrying...", agentResponseMessage.Status);
|
|
2917
|
-
// Retry the sub-agent (reuse previously loaded payload and config from first attempt)
|
|
2918
|
-
const retryResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass same payload as first attempt
|
|
2919
|
-
this.createProgressCallback(agentResponseMessage, `${agentName} (retry)`), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId, // Pass same config as first attempt
|
|
2920
|
-
this.AppContext, // Embedder-supplied app/form context
|
|
2921
|
-
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
2922
|
-
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
2923
|
-
this.AgentHistoryFrom);
|
|
2924
|
-
if (retryResult && retryResult.success) {
|
|
2925
|
-
// Retry succeeded - update the same message
|
|
2926
|
-
if (retryResult.agentRun.AgentID) {
|
|
2927
|
-
agentResponseMessage.AgentID = retryResult.agentRun.AgentID;
|
|
2928
|
-
}
|
|
2929
|
-
await this.updateConversationDetail(agentResponseMessage, retryResult.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', retryResult);
|
|
2930
|
-
// Always emit artifactCreated to trigger UI reload (same as initial attempt)
|
|
2931
|
-
this.emitArtifactReload(agentResponseMessage);
|
|
2932
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
2933
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2934
|
-
}
|
|
2935
|
-
else {
|
|
2936
|
-
// Retry also failed — terminate the agent bubble (the red-pill timer lives here),
|
|
2937
|
-
// not only the Sage delegation message.
|
|
2938
|
-
await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, retryResult, 'failed after retry');
|
|
2939
|
-
const retryDisposition = agentFailureDisposition(retryResult);
|
|
2940
|
-
if (retryDisposition.status === 'Error') {
|
|
2941
|
-
conversationManagerMessage.Error = retryDisposition.message;
|
|
2942
|
-
await this.updateConversationDetail(conversationManagerMessage, `❌ **${agentName}** failed after retry\n\n${retryDisposition.message}`, 'Error');
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
catch (error) {
|
|
2948
|
-
console.error(`❌ Error invoking sub-agent ${agentName}:`, error);
|
|
2949
|
-
const catchResult = {
|
|
2950
|
-
success: false,
|
|
2951
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
2952
|
-
};
|
|
2953
|
-
if (agentResponseMessage) {
|
|
2954
|
-
await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, catchResult);
|
|
2955
|
-
}
|
|
2956
|
-
else {
|
|
2957
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2958
|
-
}
|
|
2959
|
-
const catchDisposition = agentFailureDisposition(catchResult);
|
|
2960
|
-
if (catchDisposition.status === 'Error') {
|
|
2961
|
-
conversationManagerMessage.Error = catchDisposition.message;
|
|
2962
|
-
await this.updateConversationDetail(conversationManagerMessage, `❌ **${agentName}** encountered an error\n\n${catchDisposition.message}`, 'Error');
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2921
|
+
async showDelegateReply(reply, userMessage) {
|
|
2922
|
+
await this.settleReply(reply, '❌ The agent could not start.');
|
|
2923
|
+
this.emitArtifactReload(reply);
|
|
2924
|
+
this.MessageSent.emit(reply);
|
|
2925
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2965
2926
|
}
|
|
2966
2927
|
/**
|
|
2967
2928
|
* Handle silent observation - when Sage stays silent,
|
|
@@ -2974,12 +2935,12 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
2974
2935
|
const lastAIMessage = this.findLastNonSageReply();
|
|
2975
2936
|
if (!lastAIMessage || !lastAIMessage.AgentID) {
|
|
2976
2937
|
// No previous specialist agent - just mark user message as complete
|
|
2977
|
-
|
|
2938
|
+
LogStatusEx({ message: '🔇 No previous specialist agent found - marking complete', verboseOnly: true });
|
|
2978
2939
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2979
2940
|
return;
|
|
2980
2941
|
}
|
|
2981
2942
|
if (!IsAgentAllowed(lastAIMessage.AgentID, this.AllowedAgentIDs)) {
|
|
2982
|
-
|
|
2943
|
+
LogStatusEx({ message: '🔇 Previous specialist agent is not allowed in this chat - marking complete', verboseOnly: true });
|
|
2983
2944
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2984
2945
|
return;
|
|
2985
2946
|
}
|
|
@@ -3004,60 +2965,39 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3004
2965
|
versionId: source.versionId,
|
|
3005
2966
|
versionNumber: source.versionNumber
|
|
3006
2967
|
};
|
|
3007
|
-
|
|
3008
|
-
}
|
|
3009
|
-
//
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
await
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
conversationDetailId: statusMessage.ID,
|
|
3023
|
-
conversationId,
|
|
3024
|
-
conversationName: this.ConversationName
|
|
3025
|
-
});
|
|
2968
|
+
LogStatusEx({ message: '📦 Loaded previous OUTPUT artifact as payload for continuity', verboseOnly: true, additionalArgs: [previousArtifactInfo] });
|
|
2969
|
+
}
|
|
2970
|
+
// The manager's line "Continuing with X for refinement..." and the continuing agent's reply, threaded under it,
|
|
2971
|
+
// both written by the server. Until it acknowledges the run they show as local placeholders.
|
|
2972
|
+
const managerId = this.ConverationManagerAgent?.ID ?? null;
|
|
2973
|
+
const announcement = managerId
|
|
2974
|
+
? await this.createReplyPlaceholder(conversationId, branchId, userMessage.ID, managerId, { Message: ContinuationAnnouncementText(agentName), Status: 'Complete' })
|
|
2975
|
+
: undefined;
|
|
2976
|
+
const reply = await this.createReplyPlaceholder(conversationId, branchId, announcement?.ID ?? userMessage.ID, lastAIMessage.AgentID);
|
|
2977
|
+
const threading = managerId || branchId
|
|
2978
|
+
? {
|
|
2979
|
+
...(managerId ? { AnnouncedByAgentId: managerId } : {}),
|
|
2980
|
+
...(branchId ? { ReplyBranchId: branchId } : {}),
|
|
2981
|
+
}
|
|
2982
|
+
: null;
|
|
3026
2983
|
try {
|
|
3027
2984
|
// Invoke the agent with the previous payload
|
|
3028
|
-
const continuityResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous work based on user feedback',
|
|
2985
|
+
const continuityResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous work based on user feedback', this.adoptReplyRow(reply, { agentName, status: 'Processing refinement...', relatedMessageId: userMessage.ID, conversationId, conversationName: this.ConversationName }, announcement), previousPayload, this.createProgressCallback(reply, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, undefined, // configurationPresetId not used in this path
|
|
3029
2986
|
this.AppContext, // Embedder-supplied app/form context
|
|
3030
2987
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
3031
2988
|
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
3032
|
-
this.AgentHistoryFrom
|
|
3033
|
-
|
|
3034
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
2989
|
+
this.AgentHistoryFrom, // the first moment of the conversation the run may read
|
|
2990
|
+
threading);
|
|
3035
2991
|
if (continuityResult && continuityResult.success) {
|
|
3036
|
-
|
|
3037
|
-
const agentResponseMessage = await this.createTurnRow(conversationId, branchId);
|
|
3038
|
-
agentResponseMessage.Message = continuityResult.agentRun?.Message || `✅ **${agentName}** completed refinement`;
|
|
3039
|
-
agentResponseMessage.ParentID = statusMessage.ID;
|
|
3040
|
-
agentResponseMessage.Status = 'Complete';
|
|
3041
|
-
agentResponseMessage.AgentID = lastAIMessage.AgentID;
|
|
3042
|
-
await agentResponseMessage.Save();
|
|
3043
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
3044
|
-
// Server created artifacts (handles versioning automatically) - emit event to trigger UI reload
|
|
3045
|
-
if (continuityResult.payload && Object.keys(continuityResult.payload).length > 0) {
|
|
3046
|
-
this.emitArtifactReload(agentResponseMessage);
|
|
3047
|
-
console.log('🎨 Server created artifact (versioned) from agent continuity');
|
|
3048
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
3049
|
-
}
|
|
3050
|
-
// Mark user message as complete
|
|
3051
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
2992
|
+
await this.showDelegateReply(reply, userMessage);
|
|
3052
2993
|
}
|
|
3053
2994
|
else {
|
|
3054
|
-
await this.applyAgentFailureToDetail(
|
|
2995
|
+
await this.applyAgentFailureToDetail(reply, userMessage, agentName, continuityResult, 'failed during refinement');
|
|
3055
2996
|
}
|
|
3056
2997
|
}
|
|
3057
2998
|
catch (error) {
|
|
3058
2999
|
console.error(`❌ Error in agent continuity with ${agentName}:`, error);
|
|
3059
|
-
|
|
3060
|
-
await this.applyAgentFailureToDetail(statusMessage, userMessage, agentName, { success: false, errorMessage: error instanceof Error ? error.message : String(error) }, 'encountered an error');
|
|
3000
|
+
await this.applyAgentFailureToDetail(reply, userMessage, agentName, { success: false, errorMessage: error instanceof Error ? error.message : String(error) }, 'encountered an error');
|
|
3061
3001
|
}
|
|
3062
3002
|
}
|
|
3063
3003
|
/**
|
|
@@ -3068,15 +3008,6 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3068
3008
|
*/
|
|
3069
3009
|
async invokeAgentDirectly(userMessage, agentMention, conversationId, branchId) {
|
|
3070
3010
|
const agentName = agentMention.name;
|
|
3071
|
-
// Add agent to active tasks
|
|
3072
|
-
const taskId = this.activeTasks.add({
|
|
3073
|
-
agentName: agentName,
|
|
3074
|
-
status: 'Processing...',
|
|
3075
|
-
relatedMessageId: userMessage.ID,
|
|
3076
|
-
conversationDetailId: userMessage.ID,
|
|
3077
|
-
conversationId,
|
|
3078
|
-
conversationName: this.ConversationName
|
|
3079
|
-
});
|
|
3080
3011
|
// Declare agentResponseMessage outside try block so it's accessible in catch
|
|
3081
3012
|
let agentResponseMessage = undefined;
|
|
3082
3013
|
try {
|
|
@@ -3087,58 +3018,31 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3087
3018
|
this.MessageSent.emit(userMessage);
|
|
3088
3019
|
// Look up the agent to get its ID
|
|
3089
3020
|
const agent = AIEngineBase.Instance.ReadableAgents.find(a => a.Name === agentName);
|
|
3090
|
-
//
|
|
3091
|
-
agentResponseMessage = await this.
|
|
3092
|
-
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
3093
|
-
agentResponseMessage.ParentID = userMessage.ID;
|
|
3094
|
-
agentResponseMessage.Status = 'In-Progress';
|
|
3095
|
-
// Set AgentID immediately for proper attribution
|
|
3096
|
-
if (agent?.ID) {
|
|
3097
|
-
agentResponseMessage.AgentID = agent.ID;
|
|
3098
|
-
}
|
|
3099
|
-
// Save the record to establish __mj_CreatedAt timestamp
|
|
3100
|
-
await agentResponseMessage.Save();
|
|
3101
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
3021
|
+
// The reply shows as a local placeholder until the server creates the row and names it.
|
|
3022
|
+
agentResponseMessage = await this.createReplyPlaceholder(conversationId, branchId, userMessage.ID, agent?.ID ?? null);
|
|
3102
3023
|
// Load previous payload if agent has been invoked before
|
|
3103
3024
|
const { payload: previousPayload, artifactInfo } = agent?.ID
|
|
3104
3025
|
? await this.loadPreviousPayloadForAgent(agent.ID, branchId)
|
|
3105
3026
|
: { payload: null, artifactInfo: null };
|
|
3106
3027
|
// Invoke the agent directly
|
|
3107
|
-
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, `User mentioned agent directly with @${agentName}`, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
|
|
3028
|
+
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, `User mentioned agent directly with @${agentName}`, this.adoptReplyRow(agentResponseMessage, { agentName, status: 'Processing...', relatedMessageId: userMessage.ID, conversationId, conversationName: this.ConversationName }), previousPayload, // Pass previous payload for continuity
|
|
3108
3029
|
this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId, agentMention.configurationId, // Pass configuration preset ID
|
|
3109
3030
|
this.AppContext, // Embedder-supplied app/form context
|
|
3110
3031
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
3111
3032
|
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
3112
|
-
this.AgentHistoryFrom
|
|
3113
|
-
|
|
3114
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
3033
|
+
this.AgentHistoryFrom, // the first moment of the conversation the run may read
|
|
3034
|
+
branchId ? { ReplyBranchId: branchId } : null);
|
|
3115
3035
|
if (result && result.success) {
|
|
3116
|
-
if (result.agentRun.AgentID) {
|
|
3117
|
-
agentResponseMessage.AgentID = result.agentRun.AgentID;
|
|
3118
|
-
}
|
|
3119
3036
|
this.emitAfterAgentTurn(conversationId, result);
|
|
3120
|
-
|
|
3121
|
-
//
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
// Stage 2: Check for sub-agent invocation (single-step delegation)
|
|
3127
|
-
else if (result.agentRun.FinalStep === 'Success' && result.payload?.invokeAgent) {
|
|
3128
|
-
console.log('🎯 Sub-agent invocation detected from @mention');
|
|
3129
|
-
await this.handleSubAgentInvocation(userMessage, result, conversationId, branchId, agentResponseMessage);
|
|
3130
|
-
}
|
|
3131
|
-
// Stage 3: Normal chat response
|
|
3132
|
-
else {
|
|
3133
|
-
await this.updateConversationDetail(agentResponseMessage, result.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', result);
|
|
3134
|
-
// Server created artifacts - emit event to trigger UI reload
|
|
3135
|
-
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
3136
|
-
this.emitArtifactReload(agentResponseMessage);
|
|
3137
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
3138
|
-
}
|
|
3139
|
-
// Mark user message as complete
|
|
3140
|
-
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
3037
|
+
await this.settleReply(agentResponseMessage, `❌ **${agentName}** could not start.`);
|
|
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);
|
|
3141
3043
|
}
|
|
3044
|
+
// Mark user message as complete
|
|
3045
|
+
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
3142
3046
|
}
|
|
3143
3047
|
else {
|
|
3144
3048
|
await this.applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, result);
|
|
@@ -3154,6 +3058,40 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3154
3058
|
}
|
|
3155
3059
|
}
|
|
3156
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
|
+
}
|
|
3157
3095
|
/**
|
|
3158
3096
|
* Continue with the same agent from previous message (implicit continuation)
|
|
3159
3097
|
* Bypasses Sage - no status messages
|
|
@@ -3177,7 +3115,6 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3177
3115
|
const agentName = agent.Name || 'Agent';
|
|
3178
3116
|
let previousPayload = null;
|
|
3179
3117
|
let previousArtifactInfo = null;
|
|
3180
|
-
let previousConfigurationId = undefined;
|
|
3181
3118
|
// Use targetArtifactVersionId if specified (from intent check).
|
|
3182
3119
|
// Resolved by ID against the database, not against artifactsByDetailId: the intent check
|
|
3183
3120
|
// may well have named a version attached to a message BELOW the loaded window, which is
|
|
@@ -3191,24 +3128,18 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3191
3128
|
versionId: target.versionId,
|
|
3192
3129
|
versionNumber: target.versionNumber
|
|
3193
3130
|
};
|
|
3194
|
-
|
|
3131
|
+
LogStatusEx({ message: '📦 Loaded target artifact version as payload', verboseOnly: true, additionalArgs: [previousArtifactInfo] });
|
|
3195
3132
|
}
|
|
3196
3133
|
else {
|
|
3197
3134
|
console.warn('⚠️ Could not load target artifact version:', targetArtifactVersionId);
|
|
3198
3135
|
}
|
|
3199
3136
|
}
|
|
3200
|
-
//
|
|
3201
|
-
//
|
|
3202
|
-
|
|
3203
|
-
//
|
|
3204
|
-
//
|
|
3205
|
-
|
|
3206
|
-
// High) and applies to all subsequent non-mention routes. A
|
|
3207
|
-
// history-derived preset still wins because it represents an
|
|
3208
|
-
// explicit per-message intent the user expressed earlier.
|
|
3209
|
-
if (!previousConfigurationId && this.AgentConfigurationPresetId) {
|
|
3210
|
-
previousConfigurationId = this.AgentConfigurationPresetId;
|
|
3211
|
-
}
|
|
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);
|
|
3212
3143
|
// Fall back to searching through all agent messages for an artifact
|
|
3213
3144
|
// This ensures payload continuity even after clarifying exchanges without artifacts
|
|
3214
3145
|
// Fall back to this agent's newest OUTPUT artifact when the intent check named no
|
|
@@ -3224,10 +3155,10 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3224
3155
|
versionId: source.versionId,
|
|
3225
3156
|
versionNumber: source.versionNumber
|
|
3226
3157
|
};
|
|
3227
|
-
|
|
3158
|
+
LogStatusEx({ message: '📦 Loaded artifact as payload for continuation', verboseOnly: true, additionalArgs: [previousArtifactInfo] });
|
|
3228
3159
|
}
|
|
3229
3160
|
else {
|
|
3230
|
-
|
|
3161
|
+
LogStatusEx({ message: `📦 No artifact found for agent ${agentId} in this conversation`, verboseOnly: true });
|
|
3231
3162
|
}
|
|
3232
3163
|
}
|
|
3233
3164
|
// Execute the agent with the gathered context
|
|
@@ -3247,15 +3178,6 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3247
3178
|
* @param configurationId Optional configuration preset ID to use
|
|
3248
3179
|
*/
|
|
3249
3180
|
async executeAgentContinuation(userMessage, agentId, agentName, conversationId, branchId, previousPayload, previousArtifactInfo, configurationId) {
|
|
3250
|
-
// Add agent to active tasks
|
|
3251
|
-
const taskId = this.activeTasks.add({
|
|
3252
|
-
agentName: agentName,
|
|
3253
|
-
status: 'Processing...',
|
|
3254
|
-
relatedMessageId: userMessage.ID,
|
|
3255
|
-
conversationDetailId: userMessage.ID,
|
|
3256
|
-
conversationId,
|
|
3257
|
-
conversationName: this.ConversationName
|
|
3258
|
-
});
|
|
3259
3181
|
// Declare agentResponseMessage outside try block so it's accessible in catch
|
|
3260
3182
|
let agentResponseMessage = undefined;
|
|
3261
3183
|
try {
|
|
@@ -3264,32 +3186,23 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3264
3186
|
userMessage.Status = 'Complete';
|
|
3265
3187
|
await userMessage.Save();
|
|
3266
3188
|
this.MessageSent.emit(userMessage);
|
|
3267
|
-
//
|
|
3268
|
-
agentResponseMessage = await this.
|
|
3269
|
-
agentResponseMessage.Message = '⏳ Starting...'; // Initial message
|
|
3270
|
-
agentResponseMessage.ParentID = userMessage.ID;
|
|
3271
|
-
agentResponseMessage.Status = 'In-Progress';
|
|
3272
|
-
agentResponseMessage.AgentID = agentId;
|
|
3273
|
-
// Save the record to establish __mj_CreatedAt timestamp
|
|
3274
|
-
await agentResponseMessage.Save();
|
|
3275
|
-
this.MessageSent.emit(agentResponseMessage);
|
|
3189
|
+
// The reply shows as a local placeholder until the server creates the row and names it.
|
|
3190
|
+
agentResponseMessage = await this.createReplyPlaceholder(conversationId, branchId, userMessage.ID, agentId);
|
|
3276
3191
|
// Invoke the agent directly (continuation) with previous payload if available.
|
|
3277
3192
|
// `this.appContext` is forwarded so direct-routed sub-agents (e.g. Form
|
|
3278
3193
|
// Builder via [defaultAgentId]) see the embedder's ActiveForm/Schema/
|
|
3279
3194
|
// OverrideID block in their prompt — same flow Sage gets via
|
|
3280
3195
|
// `processMessage`.
|
|
3281
|
-
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous conversation with user', agentResponseMessage.ID, previousPayload, // Pass previous OUTPUT artifact payload for continuity
|
|
3196
|
+
const result = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.ConversationHistory, 'Continuing previous conversation with user', this.adoptReplyRow(agentResponseMessage, { agentName, status: 'Processing...', relatedMessageId: userMessage.ID, conversationId, conversationName: this.ConversationName }), previousPayload, // Pass previous OUTPUT artifact payload for continuity
|
|
3282
3197
|
this.createProgressCallback(agentResponseMessage, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, configurationId, // Pass configuration for continuity
|
|
3283
3198
|
this.AppContext, // Embedder-supplied app/form context
|
|
3284
3199
|
this.PlanModeEnabled, // per-request Plan Mode toggle
|
|
3285
3200
|
this._pendingRequestedSkillIDs, // user-requested skills (/skill mentions)
|
|
3286
|
-
this.AgentHistoryFrom
|
|
3287
|
-
|
|
3288
|
-
// Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
|
|
3201
|
+
this.AgentHistoryFrom, // the first moment of the conversation the run may read
|
|
3202
|
+
branchId ? { ReplyBranchId: branchId } : null);
|
|
3289
3203
|
if (result && result.success) {
|
|
3290
3204
|
this.emitAfterAgentTurn(conversationId, result);
|
|
3291
|
-
|
|
3292
|
-
await this.updateConversationDetail(agentResponseMessage, result.agentRun?.Message || `✅ **${agentName}** completed`, 'Complete', result);
|
|
3205
|
+
await this.settleReply(agentResponseMessage, `❌ **${agentName}** could not start.`);
|
|
3293
3206
|
// Server created artifacts (handles versioning) - emit event to trigger UI reload
|
|
3294
3207
|
if (result.payload && Object.keys(result.payload).length > 0) {
|
|
3295
3208
|
this.emitArtifactReload(agentResponseMessage);
|
|
@@ -3336,39 +3249,128 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3336
3249
|
name: result.Name,
|
|
3337
3250
|
description: result.Description
|
|
3338
3251
|
});
|
|
3339
|
-
|
|
3252
|
+
LogStatusEx({ message: `✅ Conversation renamed to: "${result.Name}"`, verboseOnly: true });
|
|
3340
3253
|
}
|
|
3341
3254
|
}
|
|
3342
3255
|
/**
|
|
3343
|
-
*
|
|
3256
|
+
* An unsaved, local "Starting…" reply, shown from the moment the turn starts until the server
|
|
3257
|
+
* acknowledges the run and names the reply row it created. The chat never writes an agent's
|
|
3258
|
+
* reply: only the server does (A19). The placeholder is never saved; {@link adoptReplyRow}
|
|
3259
|
+
* turns it into the server's row.
|
|
3260
|
+
*/
|
|
3261
|
+
async createReplyPlaceholder(conversationId, branchId, parentId, agentId, line = { Message: '⏳ Starting...', Status: 'In-Progress' }) {
|
|
3262
|
+
const placeholder = await this.dataCache.createConversationDetail(this.CurrentUser);
|
|
3263
|
+
placeholder.ConversationID = conversationId;
|
|
3264
|
+
placeholder.BranchID = branchId; // the server writes the reply on its message's branch; the placeholder shows there too
|
|
3265
|
+
placeholder.Role = 'AI';
|
|
3266
|
+
placeholder.Message = line.Message;
|
|
3267
|
+
placeholder.ParentID = parentId;
|
|
3268
|
+
placeholder.Status = line.Status;
|
|
3269
|
+
placeholder.HiddenToUser = false;
|
|
3270
|
+
placeholder.AgentID = agentId;
|
|
3271
|
+
this.MessageSent.emit(placeholder);
|
|
3272
|
+
return placeholder;
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* The acknowledgement callback for a placeholder's run. When the server accepts the run it names
|
|
3276
|
+
* the reply row it created: the placeholder takes that row's ID and creation time (so the bubble's
|
|
3277
|
+
* timer starts when the server's row did), and the turn's active task is keyed by that ID, which
|
|
3278
|
+
* is what its progress and completion carry. Runs synchronously, before any progress is routed.
|
|
3279
|
+
*/
|
|
3280
|
+
adoptReplyRow(placeholder, task, announcement) {
|
|
3281
|
+
return (reply) => {
|
|
3282
|
+
if (announcement && reply.AnnouncementDetailId) {
|
|
3283
|
+
this.adoptRow(announcement, reply.AnnouncementDetailId, reply.CreatedAt);
|
|
3284
|
+
}
|
|
3285
|
+
this.adoptRow(placeholder, reply.ConversationDetailId, reply.CreatedAt);
|
|
3286
|
+
this.activeTasks.add({ ...task, conversationDetailId: reply.ConversationDetailId });
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
/** Makes a local placeholder the server's row: its ID and creation time. Synchronous, so nothing routes in between. */
|
|
3290
|
+
adoptRow(placeholder, id, createdAt) {
|
|
3291
|
+
void placeholder.LoadFromData({ ...placeholder.GetAll(), ID: id, __mj_CreatedAt: createdAt }, true);
|
|
3292
|
+
this.MessageSent.emit(placeholder);
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Shows a reply as the server left it once its run has returned. The server wrote the reply's
|
|
3296
|
+
* text and status (the chat no longer saves it), so the row is reloaded. A row still In-Progress
|
|
3297
|
+
* is watched until the server settles it. A placeholder the server never acknowledged has no row:
|
|
3298
|
+
* it is marked failed on screen only, with `failureText`, and never saved.
|
|
3299
|
+
*/
|
|
3300
|
+
async settleReply(reply, failureText) {
|
|
3301
|
+
if (!reply.IsSaved) {
|
|
3302
|
+
this.markMessageComplete(reply);
|
|
3303
|
+
reply.Status = 'Error';
|
|
3304
|
+
reply.Message = failureText;
|
|
3305
|
+
this.MessageSent.emit(reply);
|
|
3306
|
+
this.cleanupCompletionTimestamp(reply.ID);
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
await reply.Load(reply.ID);
|
|
3310
|
+
if (reply.Status === 'In-Progress') {
|
|
3311
|
+
this.MessageSent.emit(reply);
|
|
3312
|
+
this.startInFlightDetailWatch(reply);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
this.markMessageComplete(reply);
|
|
3316
|
+
this.MessageSent.emit(reply);
|
|
3317
|
+
this.cleanupCompletionTimestamp(reply.ID);
|
|
3318
|
+
}
|
|
3319
|
+
/**
|
|
3320
|
+
* Hides a reply from the transcript (bookkeeping a person may still write; what the reply says
|
|
3321
|
+
* stays the server's). Used for a manager's silent turn and for a delegate's failed first attempt
|
|
3322
|
+
* once its retry takes over.
|
|
3323
|
+
*/
|
|
3324
|
+
async hideReply(reply) {
|
|
3325
|
+
reply.HiddenToUser = true;
|
|
3326
|
+
if (reply.IsSaved && !(await reply.Save())) {
|
|
3327
|
+
console.warn(`Could not hide reply ${reply.ID}:`, reply.LatestResult?.CompleteMessage);
|
|
3328
|
+
}
|
|
3329
|
+
this.MessageSent.emit(reply);
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* Shows an agent failure on the reply bubble.
|
|
3344
3333
|
*
|
|
3345
|
-
*
|
|
3346
|
-
* the
|
|
3347
|
-
*
|
|
3348
|
-
*
|
|
3349
|
-
*
|
|
3350
|
-
* renders it. The GraphQLAIClient stall reconciler covers the wait inside
|
|
3351
|
-
* invokeSubAgent; once that returns, the watch observes the detail until the
|
|
3352
|
-
* server writes a terminal status (MaxTimePerRun).
|
|
3334
|
+
* The server writes the reply, failures included (the chat never saves it), so this reloads the
|
|
3335
|
+
* row the server wrote. A dropped HTTP/WebSocket path after the server ACKed the run may leave the
|
|
3336
|
+
* run still executing: the bubble then says so, on screen only, and {@link startInFlightDetailWatch}
|
|
3337
|
+
* follows the row until the server writes a terminal status. A placeholder the server never
|
|
3338
|
+
* acknowledged shows the failure on screen only.
|
|
3353
3339
|
*
|
|
3354
|
-
*
|
|
3355
|
-
*
|
|
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
|
+
*
|
|
3346
|
+
* Always completes the user message — the user turn finished regardless of what the agent is doing.
|
|
3356
3347
|
*/
|
|
3357
3348
|
async applyAgentFailureToDetail(agentResponseMessage, userMessage, agentName, result, failedVerb = 'failed') {
|
|
3358
3349
|
const disposition = agentFailureDisposition(result);
|
|
3359
|
-
if (disposition.status === '
|
|
3360
|
-
|
|
3361
|
-
//
|
|
3362
|
-
//
|
|
3363
|
-
//
|
|
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
|
+
}
|
|
3359
|
+
if (disposition.status === 'In-Progress' && agentResponseMessage.IsSaved) {
|
|
3360
|
+
await agentResponseMessage.Load(agentResponseMessage.ID);
|
|
3364
3361
|
if (agentResponseMessage.Status === 'In-Progress') {
|
|
3362
|
+
agentResponseMessage.Message = `⏳ **${agentName}** is still running on the server.\n\n${disposition.message}`;
|
|
3363
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
3365
3364
|
this.startInFlightDetailWatch(agentResponseMessage);
|
|
3366
3365
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3366
|
+
else {
|
|
3367
|
+
this.markMessageComplete(agentResponseMessage);
|
|
3368
|
+
this.MessageSent.emit(agentResponseMessage);
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
else {
|
|
3372
|
+
await this.settleReply(agentResponseMessage, `❌ **${agentName}** ${failedVerb}\n\n${disposition.message}`);
|
|
3369
3373
|
}
|
|
3370
|
-
agentResponseMessage.Error = disposition.message;
|
|
3371
|
-
await this.updateConversationDetail(agentResponseMessage, `❌ **${agentName}** ${failedVerb}\n\n${disposition.message}`, 'Error');
|
|
3372
3374
|
await this.updateConversationDetail(userMessage, userMessage.Message, 'Complete');
|
|
3373
3375
|
}
|
|
3374
3376
|
startInFlightDetailWatch(detail) {
|
|
@@ -3525,28 +3527,31 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3525
3527
|
} if (rf & 2) {
|
|
3526
3528
|
let _t;
|
|
3527
3529
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.InputBox = _t.first);
|
|
3528
|
-
} }, 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) {
|
|
3529
3531
|
i0.ɵɵelementStart(0, "div", 2, 0);
|
|
3530
3532
|
i0.ɵɵconditionalCreate(2, MessageInputComponent_Conditional_2_Template, 4, 1, "div", 3);
|
|
3531
|
-
i0.ɵɵ
|
|
3532
|
-
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(); });
|
|
3533
3536
|
i0.ɵɵelementEnd();
|
|
3534
|
-
i0.ɵɵtemplate(
|
|
3535
|
-
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(); });
|
|
3536
3539
|
i0.ɵɵelementEnd();
|
|
3537
3540
|
} if (rf & 2) {
|
|
3538
|
-
const
|
|
3541
|
+
const pickerOrigin_r4 = i0.ɵɵreference(1);
|
|
3539
3542
|
i0.ɵɵadvance(2);
|
|
3540
3543
|
i0.ɵɵconditional(ctx.isProcessing ? 2 : -1);
|
|
3541
3544
|
i0.ɵɵadvance();
|
|
3545
|
+
i0.ɵɵconditional(!ctx.emptyStateMode && ctx.CurrentRouteAgent ? 3 : -1);
|
|
3546
|
+
i0.ɵɵadvance();
|
|
3542
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);
|
|
3543
3548
|
i0.ɵɵadvance(2);
|
|
3544
|
-
i0.ɵɵproperty("cdkConnectedOverlayOrigin",
|
|
3545
|
-
} }, 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}"] });
|
|
3546
3551
|
}
|
|
3547
3552
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputComponent, [{
|
|
3548
3553
|
type: Component,
|
|
3549
|
-
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"] }]
|
|
3550
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: [{
|
|
3551
3556
|
type: Input
|
|
3552
3557
|
}], conversationId: [{
|
|
@@ -3773,5 +3778,5 @@ export class MessageInputComponent extends BaseAngularComponent {
|
|
|
3773
3778
|
}], ComposerBlurred: [{
|
|
3774
3779
|
type: Output
|
|
3775
3780
|
}] }); })();
|
|
3776
|
-
(() => { (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 }); })();
|
|
3777
3782
|
//# sourceMappingURL=message-input.component.js.map
|