@memberjunction/ng-conversations 2.129.0 → 2.130.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/lib/components/attachment/image-viewer.component.d.ts +95 -0
  2. package/dist/lib/components/attachment/image-viewer.component.d.ts.map +1 -0
  3. package/dist/lib/components/attachment/image-viewer.component.js +293 -0
  4. package/dist/lib/components/attachment/image-viewer.component.js.map +1 -0
  5. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +76 -7
  6. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  7. package/dist/lib/components/conversation/conversation-chat-area.component.js +433 -193
  8. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  9. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +12 -3
  10. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
  11. package/dist/lib/components/conversation/conversation-empty-state.component.js +68 -55
  12. package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
  13. package/dist/lib/components/conversation/conversation-list.component.d.ts +2 -0
  14. package/dist/lib/components/conversation/conversation-list.component.d.ts.map +1 -1
  15. package/dist/lib/components/conversation/conversation-list.component.js +157 -145
  16. package/dist/lib/components/conversation/conversation-list.component.js.map +1 -1
  17. package/dist/lib/components/mention/mention-editor.component.d.ts +102 -5
  18. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  19. package/dist/lib/components/mention/mention-editor.component.js +349 -21
  20. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  21. package/dist/lib/components/message/message-input-box.component.d.ts +29 -2
  22. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  23. package/dist/lib/components/message/message-input-box.component.js +79 -12
  24. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  25. package/dist/lib/components/message/message-input.component.d.ts +56 -4
  26. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  27. package/dist/lib/components/message/message-input.component.js +227 -60
  28. package/dist/lib/components/message/message-input.component.js.map +1 -1
  29. package/dist/lib/components/message/message-item.component.d.ts +34 -1
  30. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  31. package/dist/lib/components/message/message-item.component.js +181 -93
  32. package/dist/lib/components/message/message-item.component.js.map +1 -1
  33. package/dist/lib/components/message/message-list.component.d.ts +4 -1
  34. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  35. package/dist/lib/components/message/message-list.component.js +12 -1
  36. package/dist/lib/components/message/message-list.component.js.map +1 -1
  37. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +44 -13
  38. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  39. package/dist/lib/components/workspace/conversation-workspace.component.js +82 -108
  40. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  41. package/dist/lib/conversations.module.d.ts +26 -25
  42. package/dist/lib/conversations.module.d.ts.map +1 -1
  43. package/dist/lib/conversations.module.js +7 -3
  44. package/dist/lib/conversations.module.js.map +1 -1
  45. package/dist/lib/services/active-tasks.service.d.ts +23 -0
  46. package/dist/lib/services/active-tasks.service.d.ts.map +1 -1
  47. package/dist/lib/services/active-tasks.service.js +91 -2
  48. package/dist/lib/services/active-tasks.service.js.map +1 -1
  49. package/dist/lib/services/conversation-agent.service.d.ts +36 -5
  50. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  51. package/dist/lib/services/conversation-agent.service.js +233 -71
  52. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  53. package/dist/lib/services/conversation-attachment.service.d.ts +79 -0
  54. package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -0
  55. package/dist/lib/services/conversation-attachment.service.js +327 -0
  56. package/dist/lib/services/conversation-attachment.service.js.map +1 -0
  57. package/dist/lib/services/conversation-data.service.d.ts +15 -1
  58. package/dist/lib/services/conversation-data.service.d.ts.map +1 -1
  59. package/dist/lib/services/conversation-data.service.js +23 -1
  60. package/dist/lib/services/conversation-data.service.js.map +1 -1
  61. package/dist/lib/services/conversation-streaming.service.d.ts +26 -1
  62. package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
  63. package/dist/lib/services/conversation-streaming.service.js +18 -3
  64. package/dist/lib/services/conversation-streaming.service.js.map +1 -1
  65. package/dist/lib/services/mention-parser.service.d.ts +15 -0
  66. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  67. package/dist/lib/services/mention-parser.service.js +30 -0
  68. package/dist/lib/services/mention-parser.service.js.map +1 -1
  69. package/dist/public-api.d.ts +2 -0
  70. package/dist/public-api.d.ts.map +1 -1
  71. package/dist/public-api.js +2 -0
  72. package/dist/public-api.js.map +1 -1
  73. package/package.json +17 -17
@@ -15,8 +15,9 @@ import * as i6 from "../../services/active-tasks.service";
15
15
  import * as i7 from "../../services/conversation-streaming.service";
16
16
  import * as i8 from "../../services/mention-parser.service";
17
17
  import * as i9 from "../../services/mention-autocomplete.service";
18
- import * as i10 from "@angular/common";
19
- import * as i11 from "./message-input-box.component";
18
+ import * as i10 from "../../services/conversation-attachment.service";
19
+ import * as i11 from "@angular/common";
20
+ import * as i12 from "./message-input-box.component";
20
21
  const _c0 = ["inputBox"];
21
22
  function MessageInputComponent_div_1_Template(rf, ctx) { if (rf & 1) {
22
23
  i0.ɵɵelementStart(0, "div", 4);
@@ -39,6 +40,7 @@ export class MessageInputComponent {
39
40
  streamingService;
40
41
  mentionParser;
41
42
  mentionAutocomplete;
43
+ attachmentService;
42
44
  // Default artifact type ID for JSON (when agent doesn't specify DefaultArtifactTypeID)
43
45
  JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
44
46
  conversationId;
@@ -47,10 +49,41 @@ export class MessageInputComponent {
47
49
  disabled = false;
48
50
  placeholder = 'Type a message... (Ctrl+Enter to send)';
49
51
  parentMessageId; // Optional: for replying in threads
50
- initialMessage = null; // Message to send automatically when component initializes
52
+ enableAttachments = true; // Whether to show attachment button (based on agent modality support)
53
+ maxAttachments = 10; // Maximum number of attachments per message
54
+ maxAttachmentSizeBytes = 20 * 1024 * 1024; // Maximum size per attachment (20MB default)
55
+ acceptedFileTypes = 'image/*'; // Accepted MIME types pattern
51
56
  artifactsByDetailId; // Pre-loaded artifact data for performance
52
57
  systemArtifactsByDetailId; // Pre-loaded system artifact data (Visibility='System Only')
53
58
  agentRunsByDetailId; // Pre-loaded agent run data for performance
59
+ emptyStateMode = false; // When true, emits emptyStateSubmit instead of creating messages directly
60
+ // Initial message to send automatically - using getter/setter for precise control
61
+ _initialMessage = null;
62
+ _initialAttachments = null;
63
+ _isComponentReady = false; // Track if component is ready to send
64
+ set initialMessage(value) {
65
+ // Handle case where an object with {text, attachments} is passed instead of just a string
66
+ // This can happen if there's a type mismatch in the binding chain
67
+ let actualValue = value;
68
+ if (value && typeof value === 'object' && 'text' in value) {
69
+ actualValue = value.text;
70
+ }
71
+ const previousValue = this._initialMessage;
72
+ this._initialMessage = actualValue;
73
+ // If component is ready and we have a new non-null message, trigger send
74
+ if (this._isComponentReady && actualValue && actualValue !== previousValue) {
75
+ this.triggerInitialSend();
76
+ }
77
+ }
78
+ get initialMessage() {
79
+ return this._initialMessage;
80
+ }
81
+ set initialAttachments(value) {
82
+ this._initialAttachments = value;
83
+ }
84
+ get initialAttachments() {
85
+ return this._initialAttachments;
86
+ }
54
87
  _conversationHistory = [];
55
88
  get conversationHistory() {
56
89
  return this._conversationHistory;
@@ -81,17 +114,23 @@ export class MessageInputComponent {
81
114
  conversationRenamed = new EventEmitter();
82
115
  intentCheckStarted = new EventEmitter(); // Emits when intent checking starts
83
116
  intentCheckCompleted = new EventEmitter(); // Emits when intent checking completes
117
+ emptyStateSubmit = new EventEmitter(); // Emitted when in emptyStateMode
118
+ uploadStateChanged = new EventEmitter(); // Emits when attachment upload state changes
84
119
  inputBox;
85
120
  messageText = '';
86
121
  isSending = false;
87
122
  isProcessing = false; // True when waiting for agent/naming response
88
123
  processingMessage = 'AI is responding...'; // Message shown during processing
124
+ isUploadingAttachments = false; // True when uploading attachments to server
125
+ uploadingMessage = 'Uploading attachments...'; // Message shown during upload
89
126
  converationManagerAgent = null;
90
127
  // Track completion timestamps to prevent race conditions with late progress updates
91
128
  completionTimestamps = new Map();
92
129
  // Track registered streaming callbacks for cleanup
93
130
  registeredCallbacks = new Map();
94
- constructor(dialogService, toastService, agentService, conversationData, dataCache, activeTasks, streamingService, mentionParser, mentionAutocomplete) {
131
+ // Track pending attachments from the input box
132
+ pendingAttachments = [];
133
+ constructor(dialogService, toastService, agentService, conversationData, dataCache, activeTasks, streamingService, mentionParser, mentionAutocomplete, attachmentService) {
95
134
  this.dialogService = dialogService;
96
135
  this.toastService = toastService;
97
136
  this.agentService = agentService;
@@ -101,6 +140,7 @@ export class MessageInputComponent {
101
140
  this.streamingService = streamingService;
102
141
  this.mentionParser = mentionParser;
103
142
  this.mentionAutocomplete = mentionAutocomplete;
143
+ this.attachmentService = attachmentService;
104
144
  }
105
145
  async ngOnInit() {
106
146
  this.converationManagerAgent = await this.agentService.getConversationManagerAgent();
@@ -114,18 +154,34 @@ export class MessageInputComponent {
114
154
  if (changes['conversationId'] && !changes['conversationId'].firstChange) {
115
155
  this.focusInput();
116
156
  }
117
- // Note: inProgressMessageIds now handled by setter, not ngOnChanges
157
+ // Note: initialMessage/initialAttachments handled by setters, inProgressMessageIds handled by setter
118
158
  }
119
159
  ngAfterViewInit() {
120
160
  // Focus input on initial load
121
161
  this.focusInput();
162
+ // Mark component as ready
163
+ this._isComponentReady = true;
122
164
  // If there's an initial message to send (from empty state), send it automatically
123
- if (this.initialMessage) {
124
- setTimeout(() => {
125
- this.sendMessageWithText(this.initialMessage);
126
- }, 100);
165
+ if (this._initialMessage || (this._initialAttachments && this._initialAttachments.length > 0)) {
166
+ this.triggerInitialSend();
127
167
  }
128
168
  }
169
+ /**
170
+ * Triggers sending of initial message and attachments.
171
+ * Called from setter or ngAfterViewInit when conditions are met.
172
+ */
173
+ triggerInitialSend() {
174
+ const message = this._initialMessage;
175
+ const attachments = this._initialAttachments;
176
+ // Set pending attachments before sending
177
+ if (attachments && attachments.length > 0) {
178
+ this.pendingAttachments = [...attachments];
179
+ }
180
+ // Use setTimeout to ensure we're outside of change detection cycle
181
+ setTimeout(() => {
182
+ this.sendMessageWithText(message || '');
183
+ }, 100);
184
+ }
129
185
  ngOnDestroy() {
130
186
  // Unregister all streaming callbacks
131
187
  this.unregisterAllCallbacks();
@@ -189,28 +245,24 @@ export class MessageInputComponent {
189
245
  console.log(`[StreamingCallback] Message ${messageId} marked complete at ${new Date(completionTime).toISOString()}, ignoring late progress update`);
190
246
  return;
191
247
  }
192
- // Build formatted progress message
193
- const taskName = progress.taskName || 'Task';
194
- const progressMessage = progress.message;
248
+ // Default: plain message (used by RunAIAgentResolver and TaskOrchestrator without step info)
249
+ message.Message = progress.message;
250
+ // TaskOrchestrator with step info: add formatted header
195
251
  // Prefer hierarchical step (e.g., "2.1.3") over flat stepCount
196
- // Note: hierarchicalStep is nested inside metadata.progress from GraphQL
197
- const stepDisplay = progress.metadata?.progress?.hierarchicalStep || progress.stepCount;
198
- let updatedMessage;
199
- if (stepDisplay != null) {
200
- updatedMessage = `🔄 **${taskName}** • Step ${stepDisplay}\n\n${progressMessage}`;
201
- }
202
- else {
203
- updatedMessage = `🔄 **${taskName}**\n\n${progressMessage}`;
252
+ if (progress.resolver === 'TaskOrchestrator') {
253
+ const stepDisplay = progress.metadata?.progress?.hierarchicalStep || progress.stepCount;
254
+ if (stepDisplay != null) {
255
+ message.Message = `**Step ${stepDisplay}**\n\n${progress.message}`;
256
+ }
204
257
  }
205
- message.Message = updatedMessage;
206
258
  // Use safe save to prevent race conditions with completion
207
- const saved = await this.safeSaveConversationDetail(message, `StreamingProgress:${taskName}`);
259
+ const saved = await this.safeSaveConversationDetail(message, `StreamingProgress:${progress.taskName || 'Agent'}`);
208
260
  if (saved) {
209
261
  // CRITICAL: Emit update to trigger UI refresh
210
262
  this.messageSent.emit(message);
211
263
  // CRITICAL: Update ActiveTasksService to keep the tasks dropdown in sync
212
- this.activeTasks.updateStatusByConversationDetailId(message.ID, progressMessage);
213
- console.log(`[StreamingCallback] Updated message ${messageId}: ${taskName}`);
264
+ this.activeTasks.updateStatusByConversationDetailId(message.ID, progress.message);
265
+ console.log(`[StreamingCallback] Updated message ${messageId}: ${progress.taskName || 'Agent'}`);
214
266
  }
215
267
  }
216
268
  catch (error) {
@@ -235,20 +287,65 @@ export class MessageInputComponent {
235
287
  get canSend() {
236
288
  return !this.disabled && !this.isSending && this.messageText.trim().length > 0;
237
289
  }
290
+ /**
291
+ * Handle attachments changed from the input box
292
+ */
293
+ onAttachmentsChanged(attachments) {
294
+ this.pendingAttachments = attachments;
295
+ }
296
+ /**
297
+ * Handle attachment errors from the input box
298
+ */
299
+ onAttachmentError(error) {
300
+ this.toastService.error(error);
301
+ }
238
302
  /**
239
303
  * Handle text submitted from the input box
240
304
  */
241
305
  async onTextSubmitted(text) {
242
- // Use the text parameter directly since the box component already cleared its value
243
- if (!text || !text.trim()) {
244
- console.log('[MessageInput] Empty text, aborting');
306
+ // Check if we have either text or attachments
307
+ const hasText = text && text.trim().length > 0;
308
+ const hasAttachments = this.pendingAttachments.length > 0;
309
+ if (!hasText && !hasAttachments) {
310
+ return;
311
+ }
312
+ // In empty state mode, just emit the data and let parent handle conversation creation
313
+ if (this.emptyStateMode) {
314
+ const attachmentsToEmit = [...this.pendingAttachments];
315
+ this.pendingAttachments = [];
316
+ this.messageText = '';
317
+ this.emptyStateSubmit.emit({ text: text?.trim() || '', attachments: attachmentsToEmit });
245
318
  return;
246
319
  }
247
320
  this.isSending = true;
321
+ // Store attachments locally since we'll clear them after send
322
+ const attachmentsToSave = [...this.pendingAttachments];
248
323
  try {
249
- const messageDetail = await this.createMessageDetailFromText(text.trim());
324
+ const messageDetail = await this.createMessageDetailFromText(text?.trim() || '');
250
325
  const saved = await messageDetail.Save();
251
326
  if (saved) {
327
+ // Save attachments if any were pending
328
+ // Attachments are stored in ConversationDetailAttachment table and loaded
329
+ // separately when building AI messages - no need to add tokens to Message field
330
+ if (attachmentsToSave.length > 0) {
331
+ // Show upload indicator for attachments
332
+ this.isUploadingAttachments = true;
333
+ this.uploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
334
+ this.uploadStateChanged.emit({ isUploading: true, message: this.uploadingMessage });
335
+ try {
336
+ await this.attachmentService.saveAttachments(messageDetail.ID, attachmentsToSave, this.currentUser);
337
+ }
338
+ catch (attachmentError) {
339
+ console.error('Failed to save attachments:', attachmentError);
340
+ this.toastService.error('Some attachments could not be saved');
341
+ }
342
+ finally {
343
+ this.isUploadingAttachments = false;
344
+ this.uploadStateChanged.emit({ isUploading: false, message: '' });
345
+ }
346
+ }
347
+ // Clear pending attachments after successful send
348
+ this.pendingAttachments = [];
252
349
  await this.handleSuccessfulSend(messageDetail);
253
350
  }
254
351
  else {
@@ -285,20 +382,24 @@ export class MessageInputComponent {
285
382
  }
286
383
  /**
287
384
  * Send a message with custom text WITHOUT modifying the visible messageText input
288
- * Used for suggested responses - sends message silently without affecting user's current input
385
+ * Used for suggested responses and initial messages from empty state.
386
+ * Also saves any pending attachments.
289
387
  */
290
388
  async sendMessageWithText(text) {
291
- if (!text || !text.trim()) {
389
+ const hasText = text && text.trim().length > 0;
390
+ const hasAttachments = this.pendingAttachments.length > 0;
391
+ if (!hasText && !hasAttachments) {
292
392
  return;
293
393
  }
294
394
  if (this.isSending) {
295
395
  return;
296
396
  }
297
397
  this.isSending = true;
398
+ const attachmentsToSave = [...this.pendingAttachments];
298
399
  try {
299
400
  const detail = await this.dataCache.createConversationDetail(this.currentUser);
300
401
  detail.ConversationID = this.conversationId;
301
- detail.Message = text.trim();
402
+ detail.Message = text?.trim() || '';
302
403
  detail.Role = 'User';
303
404
  detail.UserID = this.currentUser.ID; // Set the user who sent the message
304
405
  if (this.parentMessageId) {
@@ -306,6 +407,26 @@ export class MessageInputComponent {
306
407
  }
307
408
  const saved = await detail.Save();
308
409
  if (saved) {
410
+ // Save attachments if any were pending
411
+ if (attachmentsToSave.length > 0) {
412
+ // Show upload indicator for attachments
413
+ this.isUploadingAttachments = true;
414
+ this.uploadingMessage = `Uploading ${attachmentsToSave.length} attachment${attachmentsToSave.length > 1 ? 's' : ''}...`;
415
+ this.uploadStateChanged.emit({ isUploading: true, message: this.uploadingMessage });
416
+ try {
417
+ await this.attachmentService.saveAttachments(detail.ID, attachmentsToSave, this.currentUser);
418
+ }
419
+ catch (attachmentError) {
420
+ console.error('Failed to save attachments:', attachmentError);
421
+ this.toastService.error('Some attachments could not be saved');
422
+ }
423
+ finally {
424
+ this.isUploadingAttachments = false;
425
+ this.uploadStateChanged.emit({ isUploading: false, message: '' });
426
+ }
427
+ }
428
+ // Clear pending attachments after successful send
429
+ this.pendingAttachments = [];
309
430
  this.messageSent.emit(detail);
310
431
  const mentionResult = this.parseMentionsFromMessage(detail.Message);
311
432
  const isFirstMessage = this.conversationHistory.length === 0;
@@ -384,7 +505,21 @@ export class MessageInputComponent {
384
505
  await this.handleAgentContinuity(messageDetail, lastAgentId, mentionResult, isFirstMessage);
385
506
  return;
386
507
  }
387
- // Priority 3: No context - use Sage
508
+ // Priority 3: Check if Sage was explicitly @mentioned with a config preset
509
+ // If so, treat it like agent continuity so the config preset is preserved
510
+ if (this.converationManagerAgent?.ID) {
511
+ const sageConfigPreset = this.agentService.findConfigurationPresetFromHistory(this.converationManagerAgent.ID, this.conversationHistory);
512
+ if (sageConfigPreset) {
513
+ // User explicitly @mentioned Sage with a config - use the shared execution helper directly
514
+ // Pass the already-found config preset to avoid redundant history search
515
+ await this.executeRouteWithNaming(() => this.executeAgentContinuation(messageDetail, this.converationManagerAgent.ID, this.converationManagerAgent.Name || 'Sage', this.conversationId, null, // Sage doesn't use payload continuity
516
+ null, // Sage doesn't use artifact info
517
+ sageConfigPreset // Pass the already-found config preset
518
+ ), messageDetail.Message, isFirstMessage);
519
+ return;
520
+ }
521
+ }
522
+ // Priority 4: No context - use Sage with default config
388
523
  await this.handleNoAgentContext(messageDetail, mentionResult, isFirstMessage);
389
524
  }
390
525
  /**
@@ -540,8 +675,9 @@ export class MessageInputComponent {
540
675
  // This allows us to filter out progress messages from other concurrent agents
541
676
  let capturedAgentRunId = null;
542
677
  return async (progress) => {
543
- let progressAgentRun = progress.metadata?.agentRun;
544
- const progressAgentRunId = progressAgentRun?.ID || progress.metadata?.agentRunId;
678
+ const metadata = progress.metadata;
679
+ const progressAgentRun = metadata?.agentRun;
680
+ const progressAgentRunId = metadata?.agentRun?.ID || metadata?.agentRunId;
545
681
  // Capture the agent run ID from the first progress message
546
682
  if (!capturedAgentRunId && progressAgentRunId) {
547
683
  capturedAgentRunId = progressAgentRunId;
@@ -1088,9 +1224,14 @@ export class MessageInputComponent {
1088
1224
  const { payload: previousPayload, artifactInfo } = agent?.ID
1089
1225
  ? await this.loadPreviousPayloadForAgent(agent.ID)
1090
1226
  : { payload: null, artifactInfo: null };
1227
+ // Find configuration preset from previous @mention in conversation history
1228
+ const configurationPresetId = agent?.ID
1229
+ ? this.agentService.findConfigurationPresetFromHistory(agent.ID, this.conversationHistory)
1230
+ : undefined;
1091
1231
  // Invoke the sub-agent with progress callback
1092
1232
  const subResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.conversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass previous payload for continuity
1093
- this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId);
1233
+ this.createProgressCallback(agentResponseMessage, agentName), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId // Pass configuration from previous @mention for continuity
1234
+ );
1094
1235
  // Task will be removed automatically in markMessageComplete() when status changes to Complete/Error
1095
1236
  // DO NOT remove here - allows UI to show task during entire execution
1096
1237
  if (subResult && subResult.success) {
@@ -1122,9 +1263,10 @@ export class MessageInputComponent {
1122
1263
  await this.updateConversationDetail(conversationManagerMessage, `👉 **${agentName}** will handle this request...\n\n⚠️ First attempt failed, retrying...`, conversationManagerMessage.Status);
1123
1264
  // Update the existing agentResponseMessage to show retry status
1124
1265
  await this.updateConversationDetail(agentResponseMessage, "Retrying...", agentResponseMessage.Status);
1125
- // Retry the sub-agent (reuse previously loaded payload from first attempt)
1266
+ // Retry the sub-agent (reuse previously loaded payload and config from first attempt)
1126
1267
  const retryResult = await this.agentService.invokeSubAgent(agentName, conversationId, userMessage, this.conversationHistory, reasoning, agentResponseMessage.ID, previousPayload, // Pass same payload as first attempt
1127
- this.createProgressCallback(agentResponseMessage, `${agentName} (retry)`), artifactInfo?.artifactId, artifactInfo?.versionId);
1268
+ this.createProgressCallback(agentResponseMessage, `${agentName} (retry)`), artifactInfo?.artifactId, artifactInfo?.versionId, configurationPresetId // Pass same config as first attempt
1269
+ );
1128
1270
  if (retryResult && retryResult.success) {
1129
1271
  // Retry succeeded - update the same message
1130
1272
  if (retryResult.agentRun.AgentID) {
@@ -1453,18 +1595,9 @@ export class MessageInputComponent {
1453
1595
  .slice()
1454
1596
  .reverse()
1455
1597
  .filter(msg => msg.Role === 'AI' && msg.AgentID === agentId);
1456
- const lastAIMessage = agentMessages.length > 0 ? agentMessages[0] : null;
1457
- // Extract configuration from previous agent run (for configuration continuity)
1458
- if (lastAIMessage && this.agentRunsByDetailId) {
1459
- const previousAgentRun = this.agentRunsByDetailId.get(lastAIMessage.ID);
1460
- if (previousAgentRun?.ConfigurationID) {
1461
- previousConfigurationId = previousAgentRun.ConfigurationID;
1462
- console.log(`🎯 Using configuration from previous agent run: ${previousConfigurationId}`);
1463
- }
1464
- else {
1465
- console.log('📝 No configuration found on previous agent run, will use agent default');
1466
- }
1467
- }
1598
+ // Extract configuration preset from the User message that @mentioned this agent
1599
+ // Uses the shared helper method in the agent service
1600
+ previousConfigurationId = this.agentService.findConfigurationPresetFromHistory(agentId, this.conversationHistory);
1468
1601
  // Fall back to searching through all agent messages for an artifact
1469
1602
  // This ensures payload continuity even after clarifying exchanges without artifacts
1470
1603
  if (!previousPayload && agentMessages.length > 0) {
@@ -1501,6 +1634,22 @@ export class MessageInputComponent {
1501
1634
  console.log(`📦 No artifact found after searching ${agentMessages.length} messages from agent`);
1502
1635
  }
1503
1636
  }
1637
+ // Execute the agent with the gathered context
1638
+ await this.executeAgentContinuation(userMessage, agentId, agentName, conversationId, previousPayload, previousArtifactInfo, previousConfigurationId);
1639
+ }
1640
+ /**
1641
+ * Executes agent continuation with all context already gathered.
1642
+ * This is the shared execution logic used by both continueWithAgent and direct Sage config path.
1643
+ *
1644
+ * @param userMessage The user's message entity
1645
+ * @param agentId The agent ID to invoke
1646
+ * @param agentName The agent's display name
1647
+ * @param conversationId The conversation ID
1648
+ * @param previousPayload Optional payload from previous artifact
1649
+ * @param previousArtifactInfo Optional artifact info (id, versionId, versionNumber)
1650
+ * @param configurationId Optional configuration preset ID to use
1651
+ */
1652
+ async executeAgentContinuation(userMessage, agentId, agentName, conversationId, previousPayload, previousArtifactInfo, configurationId) {
1504
1653
  // Add agent to active tasks
1505
1654
  const taskId = this.activeTasks.add({
1506
1655
  agentName: agentName,
@@ -1532,7 +1681,7 @@ export class MessageInputComponent {
1532
1681
  this.messageSent.emit(agentResponseMessage);
1533
1682
  // Invoke the agent directly (continuation) with previous payload if available
1534
1683
  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
1535
- this.createProgressCallback(agentResponseMessage, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, previousConfigurationId // Pass configuration from previous agent run for continuity
1684
+ this.createProgressCallback(agentResponseMessage, agentName), previousArtifactInfo?.artifactId, previousArtifactInfo?.versionId, configurationId // Pass configuration for continuity
1536
1685
  );
1537
1686
  // Remove from active tasks
1538
1687
  // Task removed in markMessageComplete() - this.activeTasks.remove(taskId);
@@ -1593,6 +1742,8 @@ export class MessageInputComponent {
1593
1742
  console.warn('⚠️ GraphQLDataProvider not available');
1594
1743
  return;
1595
1744
  }
1745
+ // Convert message to plain text (strips JSON-encoded mentions like @{"id":"...","name":"Sage"} to @Sage)
1746
+ const plainTextMessage = this.mentionParser.toPlainText(message, this.mentionAutocomplete.getAvailableAgents(), this.mentionAutocomplete.getAvailableUsers());
1596
1747
  const aiClient = new GraphQLAIClient(provider);
1597
1748
  // Add 30-second timeout to prevent long delays
1598
1749
  // If this times out, the conversation will keep its default name
@@ -1602,7 +1753,7 @@ export class MessageInputComponent {
1602
1753
  const result = await Promise.race([
1603
1754
  aiClient.RunAIPrompt({
1604
1755
  promptId: promptId,
1605
- messages: [{ role: 'user', content: message }],
1756
+ messages: [{ role: 'user', content: plainTextMessage }],
1606
1757
  }),
1607
1758
  timeoutPromise
1608
1759
  ]);
@@ -1685,32 +1836,32 @@ export class MessageInputComponent {
1685
1836
  this.completionTimestamps.delete(conversationDetailId);
1686
1837
  }, 5000); // 5 seconds should be more than enough
1687
1838
  }
1688
- static ɵfac = function MessageInputComponent_Factory(t) { return new (t || MessageInputComponent)(i0.ɵɵdirectiveInject(i1.DialogService), i0.ɵɵdirectiveInject(i2.ToastService), i0.ɵɵdirectiveInject(i3.ConversationAgentService), i0.ɵɵdirectiveInject(i4.ConversationDataService), i0.ɵɵdirectiveInject(i5.DataCacheService), i0.ɵɵdirectiveInject(i6.ActiveTasksService), i0.ɵɵdirectiveInject(i7.ConversationStreamingService), i0.ɵɵdirectiveInject(i8.MentionParserService), i0.ɵɵdirectiveInject(i9.MentionAutocompleteService)); };
1839
+ static ɵfac = function MessageInputComponent_Factory(t) { return new (t || MessageInputComponent)(i0.ɵɵdirectiveInject(i1.DialogService), i0.ɵɵdirectiveInject(i2.ToastService), i0.ɵɵdirectiveInject(i3.ConversationAgentService), i0.ɵɵdirectiveInject(i4.ConversationDataService), i0.ɵɵdirectiveInject(i5.DataCacheService), i0.ɵɵdirectiveInject(i6.ActiveTasksService), i0.ɵɵdirectiveInject(i7.ConversationStreamingService), i0.ɵɵdirectiveInject(i8.MentionParserService), i0.ɵɵdirectiveInject(i9.MentionAutocompleteService), i0.ɵɵdirectiveInject(i10.ConversationAttachmentService)); };
1689
1840
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MessageInputComponent, selectors: [["mj-message-input"]], viewQuery: function MessageInputComponent_Query(rf, ctx) { if (rf & 1) {
1690
1841
  i0.ɵɵviewQuery(_c0, 5);
1691
1842
  } if (rf & 2) {
1692
1843
  let _t;
1693
1844
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.inputBox = _t.first);
1694
- } }, inputs: { conversationId: "conversationId", conversationName: "conversationName", currentUser: "currentUser", disabled: "disabled", placeholder: "placeholder", parentMessageId: "parentMessageId", initialMessage: "initialMessage", artifactsByDetailId: "artifactsByDetailId", systemArtifactsByDetailId: "systemArtifactsByDetailId", agentRunsByDetailId: "agentRunsByDetailId", conversationHistory: "conversationHistory", inProgressMessageIds: "inProgressMessageIds" }, outputs: { messageSent: "messageSent", agentResponse: "agentResponse", agentRunDetected: "agentRunDetected", agentRunUpdate: "agentRunUpdate", messageComplete: "messageComplete", artifactCreated: "artifactCreated", conversationRenamed: "conversationRenamed", intentCheckStarted: "intentCheckStarted", intentCheckCompleted: "intentCheckCompleted" }, features: [i0.ɵɵNgOnChangesFeature], decls: 4, vars: 8, consts: [["inputBox", ""], [1, "message-input-wrapper"], ["class", "processing-indicator", 4, "ngIf"], [3, "valueChange", "textSubmitted", "placeholder", "disabled", "showCharacterCount", "enableMentions", "currentUser", "rows", "value"], [1, "processing-indicator"], [1, "fas", "fa-circle-notch", "fa-spin"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
1845
+ } }, inputs: { conversationId: "conversationId", conversationName: "conversationName", currentUser: "currentUser", disabled: "disabled", placeholder: "placeholder", parentMessageId: "parentMessageId", enableAttachments: "enableAttachments", maxAttachments: "maxAttachments", maxAttachmentSizeBytes: "maxAttachmentSizeBytes", acceptedFileTypes: "acceptedFileTypes", artifactsByDetailId: "artifactsByDetailId", systemArtifactsByDetailId: "systemArtifactsByDetailId", agentRunsByDetailId: "agentRunsByDetailId", emptyStateMode: "emptyStateMode", initialMessage: "initialMessage", initialAttachments: "initialAttachments", conversationHistory: "conversationHistory", inProgressMessageIds: "inProgressMessageIds" }, outputs: { messageSent: "messageSent", agentResponse: "agentResponse", agentRunDetected: "agentRunDetected", agentRunUpdate: "agentRunUpdate", messageComplete: "messageComplete", artifactCreated: "artifactCreated", conversationRenamed: "conversationRenamed", intentCheckStarted: "intentCheckStarted", intentCheckCompleted: "intentCheckCompleted", emptyStateSubmit: "emptyStateSubmit", uploadStateChanged: "uploadStateChanged" }, features: [i0.ɵɵNgOnChangesFeature], decls: 4, vars: 12, consts: [["inputBox", ""], [1, "message-input-wrapper"], ["class", "processing-indicator", 4, "ngIf"], [3, "valueChange", "textSubmitted", "attachmentsChanged", "attachmentError", "placeholder", "disabled", "showCharacterCount", "enableMentions", "enableAttachments", "maxAttachments", "maxAttachmentSizeBytes", "acceptedFileTypes", "currentUser", "rows", "value"], [1, "processing-indicator"], [1, "fas", "fa-circle-notch", "fa-spin"]], template: function MessageInputComponent_Template(rf, ctx) { if (rf & 1) {
1695
1846
  const _r1 = i0.ɵɵgetCurrentView();
1696
1847
  i0.ɵɵelementStart(0, "div", 1);
1697
1848
  i0.ɵɵtemplate(1, MessageInputComponent_div_1_Template, 4, 1, "div", 2);
1698
1849
  i0.ɵɵelementStart(2, "mj-message-input-box", 3, 0);
1699
1850
  i0.ɵɵtwoWayListener("valueChange", function MessageInputComponent_Template_mj_message_input_box_valueChange_2_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.messageText, $event) || (ctx.messageText = $event); return i0.ɵɵresetView($event); });
1700
- i0.ɵɵlistener("textSubmitted", function MessageInputComponent_Template_mj_message_input_box_textSubmitted_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onTextSubmitted($event)); });
1851
+ i0.ɵɵlistener("textSubmitted", function MessageInputComponent_Template_mj_message_input_box_textSubmitted_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onTextSubmitted($event)); })("attachmentsChanged", function MessageInputComponent_Template_mj_message_input_box_attachmentsChanged_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onAttachmentsChanged($event)); })("attachmentError", function MessageInputComponent_Template_mj_message_input_box_attachmentError_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onAttachmentError($event)); });
1701
1852
  i0.ɵɵelementEnd()();
1702
1853
  } if (rf & 2) {
1703
1854
  i0.ɵɵadvance();
1704
1855
  i0.ɵɵproperty("ngIf", ctx.isProcessing);
1705
1856
  i0.ɵɵadvance();
1706
- i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isProcessing)("showCharacterCount", false)("enableMentions", true)("currentUser", ctx.currentUser)("rows", 3);
1857
+ i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled || ctx.isProcessing)("showCharacterCount", false)("enableMentions", true)("enableAttachments", ctx.enableAttachments)("maxAttachments", ctx.maxAttachments)("maxAttachmentSizeBytes", ctx.maxAttachmentSizeBytes)("acceptedFileTypes", ctx.acceptedFileTypes)("currentUser", ctx.currentUser)("rows", 3);
1707
1858
  i0.ɵɵtwoWayProperty("value", ctx.messageText);
1708
- } }, dependencies: [i10.NgIf, i11.MessageInputBoxComponent], styles: [".message-input-wrapper[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\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: rgba(255, 255, 255, 0.95);\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--primary-color, #007bff);\n}\n.processing-indicator[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n color: var(--text-primary, #333);\n}"] });
1859
+ } }, dependencies: [i11.NgIf, i12.MessageInputBoxComponent], styles: [".message-input-wrapper[_ngcontent-%COMP%] {\n position: relative;\n width: 100%;\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: rgba(255, 255, 255, 0.95);\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--primary-color, #007bff);\n}\n.processing-indicator[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 0.9rem;\n color: var(--text-primary, #333);\n}"] });
1709
1860
  }
1710
1861
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputComponent, [{
1711
1862
  type: Component,
1712
- args: [{ selector: 'mj-message-input', template: "<div class=\"message-input-wrapper\">\n <!-- Processing Indicator Overlay -->\n <div class=\"processing-indicator\" *ngIf=\"isProcessing\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>{{ processingMessage }}</span>\n </div>\n\n <!-- Message Input Box -->\n <mj-message-input-box\n #inputBox\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isProcessing\"\n [showCharacterCount]=\"false\"\n [enableMentions]=\"true\"\n [currentUser]=\"currentUser\"\n [rows]=\"3\"\n [(value)]=\"messageText\"\n (textSubmitted)=\"onTextSubmitted($event)\">\n </mj-message-input-box>\n</div>", styles: [".message-input-wrapper {\n position: relative;\n width: 100%;\n}\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: rgba(255, 255, 255, 0.95);\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator i {\n color: var(--primary-color, #007bff);\n}\n.processing-indicator span {\n font-size: 0.9rem;\n color: var(--text-primary, #333);\n}\n"] }]
1713
- }], () => [{ type: i1.DialogService }, { type: i2.ToastService }, { type: i3.ConversationAgentService }, { type: i4.ConversationDataService }, { type: i5.DataCacheService }, { type: i6.ActiveTasksService }, { type: i7.ConversationStreamingService }, { type: i8.MentionParserService }, { type: i9.MentionAutocompleteService }], { conversationId: [{
1863
+ args: [{ selector: 'mj-message-input', template: "<div class=\"message-input-wrapper\">\n <!-- Processing Indicator Overlay -->\n <div class=\"processing-indicator\" *ngIf=\"isProcessing\">\n <i class=\"fas fa-circle-notch fa-spin\"></i>\n <span>{{ processingMessage }}</span>\n </div>\n\n <!-- Message Input Box -->\n <mj-message-input-box\n #inputBox\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled || isProcessing\"\n [showCharacterCount]=\"false\"\n [enableMentions]=\"true\"\n [enableAttachments]=\"enableAttachments\"\n [maxAttachments]=\"maxAttachments\"\n [maxAttachmentSizeBytes]=\"maxAttachmentSizeBytes\"\n [acceptedFileTypes]=\"acceptedFileTypes\"\n [currentUser]=\"currentUser\"\n [rows]=\"3\"\n [(value)]=\"messageText\"\n (textSubmitted)=\"onTextSubmitted($event)\"\n (attachmentsChanged)=\"onAttachmentsChanged($event)\"\n (attachmentError)=\"onAttachmentError($event)\">\n </mj-message-input-box>\n</div>", styles: [".message-input-wrapper {\n position: relative;\n width: 100%;\n}\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: rgba(255, 255, 255, 0.95);\n border-radius: 8px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n z-index: 10;\n pointer-events: none;\n}\n.processing-indicator i {\n color: var(--primary-color, #007bff);\n}\n.processing-indicator span {\n font-size: 0.9rem;\n color: var(--text-primary, #333);\n}\n"] }]
1864
+ }], () => [{ type: i1.DialogService }, { type: i2.ToastService }, { type: i3.ConversationAgentService }, { type: i4.ConversationDataService }, { type: i5.DataCacheService }, { type: i6.ActiveTasksService }, { type: i7.ConversationStreamingService }, { type: i8.MentionParserService }, { type: i9.MentionAutocompleteService }, { type: i10.ConversationAttachmentService }], { conversationId: [{
1714
1865
  type: Input
1715
1866
  }], conversationName: [{
1716
1867
  type: Input
@@ -1722,7 +1873,13 @@ export class MessageInputComponent {
1722
1873
  type: Input
1723
1874
  }], parentMessageId: [{
1724
1875
  type: Input
1725
- }], initialMessage: [{
1876
+ }], enableAttachments: [{
1877
+ type: Input
1878
+ }], maxAttachments: [{
1879
+ type: Input
1880
+ }], maxAttachmentSizeBytes: [{
1881
+ type: Input
1882
+ }], acceptedFileTypes: [{
1726
1883
  type: Input
1727
1884
  }], artifactsByDetailId: [{
1728
1885
  type: Input
@@ -1730,6 +1887,12 @@ export class MessageInputComponent {
1730
1887
  type: Input
1731
1888
  }], agentRunsByDetailId: [{
1732
1889
  type: Input
1890
+ }], emptyStateMode: [{
1891
+ type: Input
1892
+ }], initialMessage: [{
1893
+ type: Input
1894
+ }], initialAttachments: [{
1895
+ type: Input
1733
1896
  }], conversationHistory: [{
1734
1897
  type: Input
1735
1898
  }], inProgressMessageIds: [{
@@ -1752,9 +1915,13 @@ export class MessageInputComponent {
1752
1915
  type: Output
1753
1916
  }], intentCheckCompleted: [{
1754
1917
  type: Output
1918
+ }], emptyStateSubmit: [{
1919
+ type: Output
1920
+ }], uploadStateChanged: [{
1921
+ type: Output
1755
1922
  }], inputBox: [{
1756
1923
  type: ViewChild,
1757
1924
  args: ['inputBox']
1758
1925
  }] }); })();
1759
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber: 28 }); })();
1926
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputComponent, { className: "MessageInputComponent", filePath: "src/lib/components/message/message-input.component.ts", lineNumber: 30 }); })();
1760
1927
  //# sourceMappingURL=message-input.component.js.map