@memberjunction/ng-ai-test-harness 2.58.0 → 2.60.0

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.
@@ -1,5 +1,4 @@
1
1
  import { Component, Input, ViewChild, ChangeDetectionStrategy, ViewContainerRef, Output, EventEmitter } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
2
  import { Subject, interval } from 'rxjs';
4
3
  import { takeUntil } from 'rxjs/operators';
5
4
  import { ExecutionNodeComponent } from './agent-execution-node.component';
@@ -18,22 +17,17 @@ function AgentExecutionMonitorComponent_Conditional_7_Template(rf, ctx) { if (rf
18
17
  i0.ɵɵelementEnd();
19
18
  i0.ɵɵelementStart(3, "span", 21);
20
19
  i0.ɵɵtext(4);
21
- i0.ɵɵelementEnd();
22
- i0.ɵɵelementStart(5, "span", 22);
23
- i0.ɵɵtext(6);
24
20
  i0.ɵɵelementEnd()();
25
21
  } if (rf & 2) {
26
22
  const ctx_r1 = i0.ɵɵnextContext();
27
23
  i0.ɵɵadvance(4);
28
- i0.ɵɵtextInterpolate(ctx_r1.formatAgentPath(ctx_r1.currentStep.agentPath));
29
- i0.ɵɵadvance(2);
30
- i0.ɵɵtextInterpolate(ctx_r1.currentStep.name);
24
+ i0.ɵɵtextInterpolate(ctx_r1.currentStep.StepName);
31
25
  } }
32
26
  function AgentExecutionMonitorComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
33
27
  const _r3 = i0.ɵɵgetCurrentView();
34
- i0.ɵɵelementStart(0, "button", 23);
28
+ i0.ɵɵelementStart(0, "button", 22);
35
29
  i0.ɵɵlistener("click", function AgentExecutionMonitorComponent_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewRunClick()); });
36
- i0.ɵɵelement(1, "i", 24);
30
+ i0.ɵɵelement(1, "i", 23);
37
31
  i0.ɵɵtext(2);
38
32
  i0.ɵɵelementEnd();
39
33
  } if (rf & 2) {
@@ -43,7 +37,7 @@ function AgentExecutionMonitorComponent_Conditional_8_Template(rf, ctx) { if (rf
43
37
  } }
44
38
  function AgentExecutionMonitorComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
45
39
  i0.ɵɵelementStart(0, "div", 11);
46
- i0.ɵɵelement(1, "i", 25);
40
+ i0.ɵɵelement(1, "i", 24);
47
41
  i0.ɵɵelementStart(2, "p");
48
42
  i0.ɵɵtext(3, "Waiting for execution to begin...");
49
43
  i0.ɵɵelementEnd()();
@@ -70,18 +64,18 @@ function AgentExecutionMonitorComponent_Conditional_37_Template(rf, ctx) { if (r
70
64
  i0.ɵɵtextInterpolate(ctx_r1.formatDuration(ctx_r1.stats.totalDuration));
71
65
  } }
72
66
  function AgentExecutionMonitorComponent_Conditional_38_For_2_Template(rf, ctx) { if (rf & 1) {
73
- i0.ɵɵelementStart(0, "span", 26);
67
+ i0.ɵɵelementStart(0, "span", 25);
74
68
  i0.ɵɵtext(1);
75
69
  i0.ɵɵelementEnd();
76
70
  } if (rf & 2) {
77
71
  const type_r4 = ctx.$implicit;
78
72
  const ctx_r1 = i0.ɵɵnextContext(2);
79
73
  i0.ɵɵadvance();
80
- i0.ɵɵtextInterpolate2(" ", ctx_r1.stats.stepsByType[type_r4], " ", type_r4, " ");
74
+ i0.ɵɵtextInterpolate2(" ", ctx_r1.stats.stepsByType[type_r4], " ", ctx_r1.pluralizeStepType(type_r4, ctx_r1.stats.stepsByType[type_r4]), " ");
81
75
  } }
82
76
  function AgentExecutionMonitorComponent_Conditional_38_Template(rf, ctx) { if (rf & 1) {
83
77
  i0.ɵɵelementStart(0, "div", 18);
84
- i0.ɵɵrepeaterCreate(1, AgentExecutionMonitorComponent_Conditional_38_For_2_Template, 2, 2, "span", 26, i0.ɵɵrepeaterTrackByIdentity);
78
+ i0.ɵɵrepeaterCreate(1, AgentExecutionMonitorComponent_Conditional_38_For_2_Template, 2, 2, "span", 25, i0.ɵɵrepeaterTrackByIdentity);
85
79
  i0.ɵɵelementEnd();
86
80
  } if (rf & 2) {
87
81
  const ctx_r1 = i0.ɵɵnextContext();
@@ -108,15 +102,19 @@ export class AgentExecutionMonitorComponent {
108
102
  constructor(cdr) {
109
103
  this.cdr = cdr;
110
104
  this.mode = 'historical';
111
- this.executionData = null; // Can be live updates or historical data
105
+ this.agentRun = null; // For historical mode
106
+ this.liveSteps = []; // For live mode streaming
112
107
  this.autoExpand = true; // Auto-expand nodes in live mode
113
108
  this.runId = null; // ID of the run (agent or prompt)
114
109
  this.runType = 'agent'; // Type of run
115
110
  this.viewRunClick = new EventEmitter();
116
- this.executionTree = [];
111
+ // Store the currently rendered steps for UI state management
117
112
  this.currentStep = null;
118
113
  // Track component references for dynamic components
119
114
  this.nodeComponentMap = new Map();
115
+ // UI state management - track expanded states separately from entities
116
+ this.expandedStates = new Map();
117
+ this.detailsExpandedStates = new Map();
120
118
  this.stats = {
121
119
  totalSteps: 0,
122
120
  completedSteps: 0,
@@ -144,51 +142,98 @@ export class AgentExecutionMonitorComponent {
144
142
  this.destroy$ = new Subject();
145
143
  }
146
144
  ngOnChanges(changes) {
147
- if (changes['executionData']) {
148
- // Check if we're starting a new live execution (empty liveSteps array)
149
- if (this.mode === 'live' &&
150
- this.executionData &&
151
- this.executionData.liveSteps &&
152
- this.executionData.liveSteps.length === 0 &&
153
- this.executionTree.length > 0) {
154
- // Clear everything for new run
145
+ console.log('🔄 Execution Monitor ngOnChanges:', {
146
+ agentRunChanged: !!changes['agentRun'],
147
+ liveStepsChanged: !!changes['liveSteps'],
148
+ modeChanged: !!changes['mode'],
149
+ hasAgentRun: !!this.agentRun,
150
+ liveStepsCount: this.liveSteps?.length || 0
151
+ });
152
+ // Handle agent run changes (historical mode)
153
+ if (changes['agentRun'] && this.mode === 'historical') {
154
+ const oldRun = changes['agentRun'].previousValue;
155
+ const newRun = changes['agentRun'].currentValue;
156
+ console.log('📊 Agent run changed:', {
157
+ oldRunExists: !!oldRun,
158
+ newRunExists: !!newRun,
159
+ oldRunId: oldRun?.ID,
160
+ newRunId: newRun?.ID,
161
+ stepsCount: newRun?.Steps?.length
162
+ });
163
+ // Only clear if it's actually a different execution (different ID)
164
+ const isDifferentExecution = (!oldRun && newRun) ||
165
+ (oldRun && newRun && oldRun.ID !== newRun.ID) ||
166
+ (oldRun && !newRun);
167
+ if (isDifferentExecution) {
168
+ console.log('🗑️ Clearing for different agent run');
155
169
  this.processedStepIds.clear();
156
170
  this.newlyAddedNodeIds.clear();
171
+ this.expandedStates.clear();
172
+ this.detailsExpandedStates.clear();
157
173
  this.clearNodeComponents();
158
- this.executionTree = [];
159
174
  this.currentStep = null;
160
- this.calculateStats();
161
175
  }
162
- // Process data even if it's empty/null (for initial setup)
163
- this.processExecutionData();
176
+ // Always process the agent run data (will handle updates to existing run)
177
+ if (newRun) {
178
+ this.processAgentRun();
179
+ }
180
+ }
181
+ // Handle live steps changes (live mode)
182
+ if (changes['liveSteps'] && this.mode === 'live') {
183
+ const oldSteps = changes['liveSteps'].previousValue;
184
+ const newSteps = changes['liveSteps'].currentValue;
185
+ console.log('📊 Live steps changed:', {
186
+ oldCount: oldSteps?.length || 0,
187
+ newCount: newSteps?.length || 0
188
+ });
189
+ // Process live steps
190
+ this.processLiveSteps();
164
191
  }
165
192
  // Handle mode changes
166
193
  if (changes['mode'] && !changes['mode'].firstChange) {
167
- // Clear live nodes when switching from live to historical
168
- if (changes['mode'].previousValue === 'live' && changes['mode'].currentValue === 'historical') {
169
- // Stop live updates
170
- if (this.updateSubscription) {
171
- this.updateSubscription.unsubscribe();
172
- this.updateSubscription = undefined;
173
- }
174
- // Clear all tracking data from live mode
175
- this.processedStepIds.clear();
176
- this.newlyAddedNodeIds.clear();
177
- this.clearNodeComponents();
178
- this.executionTree = [];
194
+ const previousMode = changes['mode'].previousValue;
195
+ const currentMode = changes['mode'].currentValue;
196
+ console.log('🔄 Mode changed:', { previousMode, currentMode });
197
+ // Clear everything when switching modes
198
+ this.processedStepIds.clear();
199
+ this.newlyAddedNodeIds.clear();
200
+ this.expandedStates.clear();
201
+ this.detailsExpandedStates.clear();
202
+ this.clearNodeComponents();
203
+ this.currentStep = null;
204
+ // Stop live updates when switching away from live mode
205
+ if (previousMode === 'live' && this.updateSubscription) {
206
+ this.updateSubscription.unsubscribe();
207
+ this.updateSubscription = undefined;
208
+ }
209
+ // Process data for the new mode
210
+ if (currentMode === 'historical' && this.agentRun) {
211
+ this.processAgentRun();
212
+ }
213
+ else if (currentMode === 'live') {
214
+ this.setupLiveUpdates();
179
215
  }
180
- this.processExecutionData();
181
216
  }
182
217
  }
183
218
  ngAfterViewInit() {
184
219
  this.viewInitialized = true;
220
+ console.log('🎯 View initialized, checking for pending data:', {
221
+ hasAgentRun: !!this.agentRun,
222
+ hasLiveSteps: this.liveSteps?.length > 0,
223
+ hasContainer: !!this.executionNodesContainer
224
+ });
185
225
  // Initial setup for scroll behavior
186
226
  if (this.mode === 'live') {
187
227
  this.checkIfUserAtBottom();
188
228
  }
189
229
  // If we have data waiting to be rendered, render it now
190
- if (this.executionData && this.executionTree.length === 0) {
191
- this.processExecutionData();
230
+ if (this.mode === 'historical' && this.agentRun && this.nodeComponentMap.size === 0) {
231
+ console.log('⚡ Processing agent run after view init');
232
+ this.processAgentRun();
233
+ }
234
+ else if (this.mode === 'live' && this.liveSteps?.length > 0) {
235
+ console.log('⚡ Processing live steps after view init');
236
+ this.processLiveSteps();
192
237
  }
193
238
  }
194
239
  ngOnDestroy() {
@@ -211,237 +256,192 @@ export class AgentExecutionMonitorComponent {
211
256
  }
212
257
  }
213
258
  /**
214
- * Process execution data based on mode
259
+ * Process agent run for historical mode
215
260
  */
216
- processExecutionData() {
217
- if (this.mode === 'historical') {
218
- // Clear processed step IDs when processing historical data
219
- this.processedStepIds.clear();
220
- // Process historical data
221
- this.buildTreeFromHistoricalData();
261
+ processAgentRun() {
262
+ console.log('⚙️ Processing agent run:', {
263
+ hasAgentRun: !!this.agentRun,
264
+ stepsCount: this.agentRun?.Steps?.length || 0,
265
+ viewInitialized: this.viewInitialized,
266
+ hasContainer: !!this.executionNodesContainer
267
+ });
268
+ if (!this.agentRun || !this.viewInitialized || !this.executionNodesContainer) {
269
+ console.warn('⚠️ Cannot process agent run:', {
270
+ agentRun: !this.agentRun ? 'missing' : 'present',
271
+ viewInitialized: this.viewInitialized ? 'yes' : 'no',
272
+ container: !this.executionNodesContainer ? 'missing' : 'present'
273
+ });
274
+ return;
222
275
  }
223
- else {
224
- // For live mode, only clear if we don't have existing data
225
- // This prevents losing state when switching tabs
226
- if (this.executionTree.length === 0) {
227
- this.processedStepIds.clear();
228
- this.newlyAddedNodeIds.clear();
229
- }
230
- // Set up live updates
231
- this.setupLiveUpdates();
276
+ // Clear existing components
277
+ this.clearNodeComponents();
278
+ // Render the agent run steps
279
+ if (this.agentRun.Steps && this.agentRun.Steps.length > 0) {
280
+ console.log('🎨 Rendering', this.agentRun.Steps.length, 'steps');
281
+ this.renderSteps(this.agentRun.Steps, 0, []);
282
+ console.log('✅ Finished rendering, container now has', this.executionNodesContainer.length, 'components');
232
283
  }
233
- // Only calculate stats if we have data
234
- if (this.executionTree.length > 0) {
235
- this.calculateStats();
284
+ else {
285
+ console.warn('⚠️ No steps to render');
236
286
  }
287
+ // Calculate statistics
288
+ this.calculateStats();
289
+ // Trigger change detection after rendering all components
290
+ this.cdr.detectChanges();
237
291
  }
238
292
  /**
239
- * Build tree from historical execution data
293
+ * Process live steps for live mode
240
294
  */
241
- buildTreeFromHistoricalData() {
242
- if (!this.executionData) {
243
- this.clearNodeComponents();
244
- this.executionTree = [];
295
+ processLiveSteps() {
296
+ console.log('⚙️ Processing live steps:', {
297
+ stepsCount: this.liveSteps?.length || 0,
298
+ viewInitialized: this.viewInitialized,
299
+ hasContainer: !!this.executionNodesContainer
300
+ });
301
+ if (!this.liveSteps || !this.viewInitialized || !this.executionNodesContainer) {
245
302
  return;
246
303
  }
247
- // Clear existing components for full rebuild
248
- this.clearNodeComponents();
249
- // Handle executionTree format - historical mode should only use saved database data
250
- if (this.executionData.executionTree) {
251
- const allNodes = this.convertExecutionTree(this.executionData.executionTree);
252
- // Filter out live-only nodes in historical mode
253
- this.executionTree = this.filterNodesForMode(allNodes, 'historical');
254
- }
255
- else if (this.executionData.steps) {
256
- // Handle legacy steps format
257
- const allNodes = this.convertLegacySteps(this.executionData.steps);
258
- this.executionTree = this.filterNodesForMode(allNodes, 'historical');
259
- }
260
- else {
261
- this.executionTree = [];
262
- }
263
- // Render the tree using dynamic components
264
- if (this.executionNodesContainer) {
265
- this.renderTree();
304
+ // Append new steps without clearing existing ones
305
+ this.appendNewLiveSteps(this.liveSteps);
306
+ // Trigger change detection after appending live steps
307
+ this.cdr.detectChanges();
308
+ // Set up live update monitoring if not already set up
309
+ if (!this.updateSubscription) {
310
+ this.setupLiveUpdates();
266
311
  }
267
312
  }
268
313
  /**
269
- * Render the execution tree using dynamic components
314
+ * Render steps recursively with proper hierarchy
270
315
  */
271
- renderTree() {
272
- if (!this.executionNodesContainer) {
273
- console.warn('📊 Execution Monitor: No container available for rendering');
274
- return;
275
- }
276
- // Clear and recreate all components for historical view
277
- this.clearNodeComponents();
278
- // Create components for each root node
279
- for (const node of this.executionTree) {
280
- this.createNodeComponent(node, this.executionNodesContainer);
316
+ renderSteps(steps, depth, agentPath) {
317
+ console.log('🎨 Rendering steps:', {
318
+ count: steps.length,
319
+ depth,
320
+ agentPath
321
+ });
322
+ // Sort steps by StepNumber to ensure proper ordering
323
+ const sortedSteps = [...steps].sort((a, b) => (a.StepNumber || 0) - (b.StepNumber || 0));
324
+ for (const step of sortedSteps) {
325
+ // Skip if already processed in live mode
326
+ if (this.processedStepIds.has(step.ID)) {
327
+ continue;
328
+ }
329
+ // Create component for this step
330
+ this.createStepComponent(step, depth, agentPath);
331
+ // Mark as processed
332
+ this.processedStepIds.add(step.ID);
333
+ // Handle sub-agent recursion
334
+ if (step.StepType === 'Sub-Agent' && step.SubAgentRun?.Steps) {
335
+ const subAgentPath = [...agentPath, step.SubAgentRun.Agent || 'Sub-Agent'];
336
+ this.renderSteps(step.SubAgentRun.Steps, depth + 1, subAgentPath);
337
+ }
281
338
  }
282
339
  }
283
340
  /**
284
- * Create a component for a node and its children
341
+ * Create a component for a step
285
342
  */
286
- createNodeComponent(node, container) {
287
- const componentRef = container.createComponent(ExecutionNodeComponent);
343
+ createStepComponent(step, depth, agentPath) {
344
+ // Ensure container exists
345
+ if (!this.executionNodesContainer) {
346
+ console.error('❌ executionNodesContainer not available');
347
+ throw new Error('executionNodesContainer ViewContainerRef not initialized');
348
+ }
349
+ const componentRef = this.executionNodesContainer.createComponent(ExecutionNodeComponent);
288
350
  const instance = componentRef.instance;
289
- // Set inputs
290
- instance.node = node;
291
- // No need to apply margin here - it's handled in the component template
351
+ console.log('🔨 Creating component for step:', {
352
+ stepId: step.ID,
353
+ stepName: step.StepName,
354
+ depth,
355
+ containerLength: this.executionNodesContainer.length,
356
+ hostElement: componentRef.location.nativeElement
357
+ });
358
+ // Pass the step data with UI state information
359
+ instance.step = step;
360
+ instance.depth = depth;
361
+ instance.agentPath = agentPath;
362
+ instance.expanded = this.expandedStates.get(step.ID) || false;
363
+ instance.detailsExpanded = this.detailsExpandedStates.get(step.ID) || false;
292
364
  // Subscribe to outputs
293
- instance.toggleNode.subscribe((n) => this.toggleNode(n));
365
+ instance.toggleNode.subscribe(() => this.toggleStepExpansion(step));
366
+ instance.toggleDetails.subscribe(() => this.toggleStepDetails(step));
294
367
  instance.userInteracted.subscribe(() => this.onUserInteraction());
295
368
  // Store reference
296
- this.nodeComponentMap.set(node.id, componentRef);
297
- // Force immediate change detection to ensure styles are applied
298
- componentRef.changeDetectorRef.markForCheck();
299
- componentRef.changeDetectorRef.detectChanges();
369
+ this.nodeComponentMap.set(step.ID, componentRef);
370
+ // Log the component state after setup
371
+ console.log('✅ Component created and configured:', {
372
+ stepId: step.ID,
373
+ hasData: !!instance.step,
374
+ isAttached: componentRef.hostView.destroyed === false
375
+ });
300
376
  return componentRef;
301
377
  }
302
378
  /**
303
- * Convert new execution tree format to display nodes
379
+ * Toggle step expansion
304
380
  */
305
- convertExecutionTree(nodes, depth = 0) {
306
- // Sort nodes by StepNumber before processing
307
- const sortedNodes = [...nodes].sort((a, b) => {
308
- const aStepNum = a.step?.StepNumber || 0;
309
- const bStepNum = b.step?.StepNumber || 0;
310
- return aStepNum - bStepNum;
311
- });
312
- return sortedNodes.map((node, index) => {
313
- // Parse the step name for markdown content (same as live steps)
314
- const stepName = node.step?.StepName || 'Unknown Step';
315
- let name = stepName;
316
- let detailsMarkdown;
317
- // Check if the step name contains markdown (indicated by newlines or markdown syntax)
318
- if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
319
- // Split by newline and use first line as name
320
- const lines = stepName.split('\n');
321
- name = lines[0].trim();
322
- // Rest is markdown details
323
- if (lines.length > 1) {
324
- detailsMarkdown = lines.slice(1).join('\n').trim();
325
- }
326
- }
327
- // Determine display mode based on the step content
328
- let displayMode = 'both';
329
- // Progress messages that should only show in live mode
330
- if (name.includes('Executing') && name.includes('action') && !name.startsWith('Execute Action:')) {
331
- displayMode = 'live';
332
- }
333
- else if (name.includes("Executing agent's initial prompt")) {
334
- displayMode = 'live';
335
- }
336
- else if (name.includes('Re-executing agent prompt with additional context')) {
337
- displayMode = 'live';
338
- }
339
- // Clear metadata storage before creating previews
340
- this.lastParsedAgentData = null;
341
- this.lastParsedActionData = null;
342
- // Create previews which will populate metadata if available
343
- const inputPreview = this.createPreview(node.inputData);
344
- const outputPreview = this.createPreview(node.outputData);
345
- const treeNode = {
346
- id: node.step?.ID || `node-${Date.now()}-${index}`,
347
- name: name,
348
- type: this.mapStepType(node.executionType || node.step?.StepType),
349
- status: this.mapStepStatus(node.step?.Status),
350
- startTime: node.startTime ? new Date(node.startTime) : undefined,
351
- endTime: node.endTime ? new Date(node.endTime) : undefined,
352
- duration: node.durationMs,
353
- agentPath: node.agentHierarchy || [],
354
- inputPreview: inputPreview,
355
- outputPreview: outputPreview,
356
- error: node.step?.ErrorMessage,
357
- expanded: false,
358
- depth: node.depth !== undefined ? node.depth : depth, // Use node.depth if available, fallback to recursion depth
359
- tokensUsed: this.extractTokens(node.outputData),
360
- cost: this.extractCost(node.outputData),
361
- detailsMarkdown: detailsMarkdown,
362
- detailsExpanded: false,
363
- children: node.children ? this.convertExecutionTree(node.children, node.depth !== undefined ? node.depth + 1 : depth + 1) : undefined,
364
- displayMode: displayMode
365
- };
366
- // Add agent metadata if this is a sub-agent node
367
- if (treeNode.type === 'sub-agent' && this.lastParsedAgentData) {
368
- treeNode.agentName = this.lastParsedAgentData.agentName;
369
- treeNode.agentId = this.lastParsedAgentData.agentId;
370
- treeNode.agentIconClass = this.lastParsedAgentData.agentIconClass;
371
- treeNode.agentLogoURL = this.lastParsedAgentData.agentLogoURL;
372
- }
373
- // Add action metadata if this is an action node
374
- if (treeNode.type === 'action' && this.lastParsedActionData) {
375
- treeNode.actionName = this.lastParsedActionData.actionName;
376
- treeNode.actionId = this.lastParsedActionData.actionId;
377
- treeNode.actionIconClass = this.lastParsedActionData.actionIconClass;
378
- }
379
- return treeNode;
380
- });
381
+ toggleStepExpansion(step) {
382
+ const currentState = this.expandedStates.get(step.ID) || false;
383
+ this.expandedStates.set(step.ID, !currentState);
384
+ this.userHasInteracted = true;
385
+ // Update the component
386
+ const componentRef = this.nodeComponentMap.get(step.ID);
387
+ if (componentRef) {
388
+ componentRef.instance.expanded = !currentState;
389
+ componentRef.changeDetectorRef.detectChanges();
390
+ }
391
+ this.cdr.markForCheck();
381
392
  }
382
393
  /**
383
- * Convert legacy step format to tree nodes
394
+ * Toggle step details expansion
384
395
  */
385
- convertLegacySteps(steps) {
386
- return steps.map((step, index) => {
387
- // Parse the step name for markdown content
388
- const stepName = step.StepName || '';
389
- let name = stepName;
390
- let detailsMarkdown;
391
- // Check if the step name contains markdown
392
- if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
393
- const lines = stepName.split('\n');
394
- name = lines[0].trim();
395
- if (lines.length > 1) {
396
- detailsMarkdown = lines.slice(1).join('\n').trim();
397
- }
398
- }
399
- return {
400
- id: step.ID || `step-${index}`,
401
- name: name,
402
- type: this.mapStepType(step.StepType),
403
- status: this.mapStepStatus(step.Status),
404
- startTime: step.StartedAt ? new Date(step.StartedAt) : undefined,
405
- endTime: step.CompletedAt ? new Date(step.CompletedAt) : undefined,
406
- duration: this.calculateDuration(step.StartedAt, step.CompletedAt),
407
- agentPath: [],
408
- inputPreview: this.createPreview(step.InputData),
409
- outputPreview: this.createPreview(step.OutputData),
410
- error: step.ErrorMessage,
411
- expanded: false,
412
- depth: 0,
413
- detailsMarkdown: detailsMarkdown,
414
- detailsExpanded: false
415
- };
416
- });
396
+ toggleStepDetails(step) {
397
+ const currentState = this.detailsExpandedStates.get(step.ID) || false;
398
+ this.detailsExpandedStates.set(step.ID, !currentState);
399
+ // Update the component
400
+ const componentRef = this.nodeComponentMap.get(step.ID);
401
+ if (componentRef) {
402
+ componentRef.instance.detailsExpanded = !currentState;
403
+ componentRef.changeDetectorRef.detectChanges();
404
+ }
417
405
  }
418
406
  /**
419
- * Map step type strings to our type enum
407
+ * Get step type CSS class for styling
420
408
  */
421
- mapStepType(type) {
409
+ getStepTypeClass(stepType) {
422
410
  const typeMap = {
423
- 'validation': 'validation',
424
- 'prompt': 'prompt',
425
- 'action': 'action',
426
- 'subagent': 'sub-agent',
427
- 'sub-agent': 'sub-agent',
428
- 'decision': 'decision',
429
- 'chat': 'chat'
411
+ 'Validation': 'validation',
412
+ 'Prompt': 'prompt',
413
+ 'Actions': 'action',
414
+ 'Sub-Agent': 'sub-agent',
415
+ 'Decision': 'decision',
416
+ 'Chat': 'chat'
430
417
  };
431
- return typeMap[type?.toLowerCase()] || 'prompt';
418
+ return typeMap[stepType] || 'prompt';
432
419
  }
433
420
  /**
434
- * Map status strings to our status enum
421
+ * Get status CSS class for styling
435
422
  */
436
- mapStepStatus(status) {
423
+ getStatusClass(status) {
437
424
  const statusMap = {
438
- 'pending': 'pending',
439
- 'running': 'running',
440
- 'completed': 'completed',
441
- 'failed': 'failed',
442
- 'error': 'failed'
425
+ 'Pending': 'pending',
426
+ 'Running': 'running',
427
+ 'Completed': 'completed',
428
+ 'Failed': 'failed',
429
+ 'Cancelled': 'failed',
430
+ 'Paused': 'pending'
443
431
  };
444
- return statusMap[status?.toLowerCase()] || 'pending';
432
+ return statusMap[status] || 'pending';
433
+ }
434
+ /**
435
+ * Helper function to safely convert a value to a preview string
436
+ */
437
+ valueToPreviewString(value) {
438
+ if (value === null || value === undefined) {
439
+ return '';
440
+ }
441
+ if (typeof value === 'object') {
442
+ return JSON.stringify(value, null, 2);
443
+ }
444
+ return String(value);
445
445
  }
446
446
  /**
447
447
  * Create a preview string from data
@@ -474,9 +474,9 @@ export class AgentExecutionMonitorComponent {
474
474
  return `Sub-agent: ${parsed.subAgentName}`;
475
475
  }
476
476
  if (parsed.message)
477
- return parsed.message;
477
+ return this.valueToPreviewString(parsed.message);
478
478
  if (parsed.userMessage)
479
- return parsed.userMessage;
479
+ return this.valueToPreviewString(parsed.userMessage);
480
480
  // Show action results clearly
481
481
  if (parsed.actionResult) {
482
482
  const result = parsed.actionResult;
@@ -488,7 +488,7 @@ export class AgentExecutionMonitorComponent {
488
488
  preview += `Result Code: ${result.resultCode}\n`;
489
489
  }
490
490
  if (result.message) {
491
- preview += `Message: ${result.message}\n`;
491
+ preview += `Message: ${this.valueToPreviewString(result.message)}\n`;
492
492
  }
493
493
  if (result.result) {
494
494
  preview += `Result: ${typeof result.result === 'object' ? JSON.stringify(result.result, null, 2) : result.result}`;
@@ -497,10 +497,7 @@ export class AgentExecutionMonitorComponent {
497
497
  }
498
498
  // Legacy action result format
499
499
  if (parsed.result) {
500
- if (typeof parsed.result === 'object') {
501
- return JSON.stringify(parsed.result, null, 2);
502
- }
503
- return String(parsed.result);
500
+ return this.valueToPreviewString(parsed.result);
504
501
  }
505
502
  // Show prompt results
506
503
  if (parsed.promptResult) {
@@ -510,7 +507,7 @@ export class AgentExecutionMonitorComponent {
510
507
  preview += `Success: ${result.success}\n`;
511
508
  }
512
509
  if (result.content) {
513
- preview += `Content: ${typeof result.content === 'string' ? result.content : JSON.stringify(result.content, null, 2)}`;
510
+ preview += `Content: ${this.valueToPreviewString(result.content)}`;
514
511
  }
515
512
  return preview;
516
513
  }
@@ -564,153 +561,83 @@ export class AgentExecutionMonitorComponent {
564
561
  * Set up live updates for real-time execution
565
562
  */
566
563
  setupLiveUpdates() {
567
- // Handle live streaming data format
568
- if (this.executionData && this.executionData.liveSteps) {
569
- // Only clear and rebuild if we don't have existing data
570
- if (this.executionTree.length === 0) {
571
- this.clearNodeComponents();
572
- this.executionTree = [];
573
- // Then append all steps
574
- this.appendNewLiveSteps(this.executionData.liveSteps);
575
- }
576
- else {
577
- // Just append new steps without clearing
578
- this.appendNewLiveSteps(this.executionData.liveSteps);
579
- }
564
+ console.log('⚡ Setting up live updates');
565
+ // Process initial live steps if any
566
+ if (this.liveSteps && this.liveSteps.length > 0) {
567
+ this.appendNewLiveSteps(this.liveSteps);
580
568
  }
581
- // Set up interval to monitor for changes (if not already set up)
569
+ // Set up interval to check for updates (if not already set up)
582
570
  if (this.mode === 'live' && !this.updateSubscription) {
583
571
  this.updateSubscription = interval(500)
584
572
  .pipe(takeUntil(this.destroy$))
585
573
  .subscribe(() => {
586
- // Check for new live steps and update tree
587
- if (this.executionData && this.executionData.liveSteps) {
588
- // Instead of replacing entire tree, append new nodes
589
- this.appendNewLiveSteps(this.executionData.liveSteps);
590
- this.calculateStats();
591
- }
574
+ // Update current step indicator
592
575
  this.updateCurrentStep();
576
+ // Recalculate stats periodically
577
+ this.calculateStats();
593
578
  });
594
579
  }
595
580
  }
596
- /**
597
- * Convert live steps format to execution tree format
598
- */
599
- convertLiveStepsToTree(liveSteps) {
600
- const treeNodes = [];
601
- const nodeMap = new Map();
602
- // Sort steps by StepNumber to ensure proper ordering
603
- const sortedSteps = [...liveSteps].sort((a, b) => {
604
- const aStepNum = a.step?.StepNumber || 0;
605
- const bStepNum = b.step?.StepNumber || 0;
606
- return aStepNum - bStepNum;
607
- });
608
- for (const step of sortedSteps) {
609
- // Parse the step name for markdown content
610
- const stepName = step.step?.StepName || 'Processing...';
611
- let name = stepName;
612
- let detailsMarkdown;
613
- // Check if the step name contains markdown (indicated by newlines or markdown syntax)
614
- if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
615
- // Split by newline and use first line as name
616
- const lines = stepName.split('\n');
617
- name = lines[0].trim();
618
- // Rest is markdown details
619
- if (lines.length > 1) {
620
- detailsMarkdown = lines.slice(1).join('\n').trim();
621
- }
622
- }
623
- const node = {
624
- id: step.step?.ID || `live-${Date.now()}-${Math.random()}`,
625
- name: name,
626
- type: this.mapStepType(step.executionType || step.step?.StepType),
627
- status: 'running', // Live steps are always running
628
- startTime: step.startTime ? new Date(step.startTime) : new Date(),
629
- agentPath: step.agentHierarchy || [],
630
- expanded: false,
631
- depth: step.depth || 0,
632
- detailsMarkdown: detailsMarkdown,
633
- detailsExpanded: false
634
- };
635
- // Store in map for hierarchy building
636
- nodeMap.set(node.id, node);
637
- // Handle hierarchy by depth
638
- if (step.depth === 0) {
639
- treeNodes.push(node);
640
- }
641
- else {
642
- // Find parent based on depth and agent hierarchy
643
- let parent;
644
- // Look for a node at depth-1 that could be the parent
645
- nodeMap.forEach((candidate) => {
646
- if (candidate.depth === step.depth - 1) {
647
- parent = candidate;
648
- }
649
- });
650
- if (parent) {
651
- if (!parent.children) {
652
- parent.children = [];
653
- }
654
- parent.children.push(node);
655
- }
656
- }
657
- nodeMap.set(node.id, node);
658
- }
659
- return treeNodes;
660
- }
661
581
  /**
662
582
  * Update current step indicator
663
583
  */
664
584
  updateCurrentStep() {
665
- // Find the first running step
666
- const findRunning = (nodes) => {
667
- for (const node of nodes) {
668
- if (node.status === 'running')
669
- return node;
670
- if (node.children) {
671
- const child = findRunning(node.children);
672
- if (child)
673
- return child;
674
- }
585
+ // Find the first running step from all rendered components
586
+ let runningStep = null;
587
+ this.nodeComponentMap.forEach((componentRef, stepId) => {
588
+ const step = componentRef.instance.step;
589
+ if (step && step.Status === 'Running' && !runningStep) {
590
+ runningStep = step;
675
591
  }
676
- return null;
677
- };
678
- this.currentStep = findRunning(this.executionTree);
592
+ });
593
+ this.currentStep = runningStep;
679
594
  this.cdr.markForCheck();
680
595
  }
681
596
  /**
682
597
  * Mark previously new nodes as completed
683
598
  */
684
599
  markPreviouslyNewNodesAsComplete() {
685
- // Only mark nodes that were previously marked as newly added
686
- for (const nodeId of this.newlyAddedNodeIds) {
687
- const node = this.findNodeById(this.executionTree, nodeId);
688
- if (node && node.status === 'running') {
689
- node.status = 'completed';
690
- // Update the component if it exists
691
- const componentRef = this.nodeComponentMap.get(nodeId);
692
- if (componentRef) {
693
- componentRef.instance.node = { ...node };
694
- componentRef.changeDetectorRef.detectChanges();
695
- }
600
+ // Mark ALL running steps as completed (not just previously new ones)
601
+ // This ensures only the latest step shows as running
602
+ this.nodeComponentMap.forEach((componentRef, stepId) => {
603
+ const step = componentRef.instance.step;
604
+ if (step.Status === 'Running') {
605
+ // Create a copy with updated status
606
+ const updatedStep = Object.assign({}, step, { Status: 'Completed' });
607
+ componentRef.instance.step = updatedStep;
608
+ componentRef.changeDetectorRef.detectChanges();
696
609
  }
697
- }
610
+ });
698
611
  }
699
612
  /**
700
- * Find a node by ID in the tree
613
+ * Ensure only the last step in the execution order shows as running
701
614
  */
702
- findNodeById(nodes, id) {
703
- for (const node of nodes) {
704
- if (node.id === id) {
705
- return node;
615
+ ensureOnlyLastStepIsRunning() {
616
+ // Find the last step by step number
617
+ let lastRunningStep = null;
618
+ this.nodeComponentMap.forEach((componentRef, stepId) => {
619
+ const step = componentRef.instance.step;
620
+ if (step.Status === 'Running') {
621
+ const stepNumber = step.StepNumber || 0;
622
+ if (!lastRunningStep || stepNumber > lastRunningStep.stepNumber) {
623
+ lastRunningStep = { stepId, stepNumber, componentRef };
624
+ }
706
625
  }
707
- if (node.children) {
708
- const found = this.findNodeById(node.children, id);
709
- if (found)
710
- return found;
626
+ });
627
+ // Set override display status for all running steps
628
+ this.nodeComponentMap.forEach((componentRef, stepId) => {
629
+ const step = componentRef.instance.step;
630
+ if (step.Status === 'Running') {
631
+ // Override display to show as completed, except for the last one
632
+ if (lastRunningStep && stepId === lastRunningStep.stepId) {
633
+ componentRef.instance.overrideDisplayStatus = 'Running';
634
+ }
635
+ else {
636
+ componentRef.instance.overrideDisplayStatus = 'Completed';
637
+ }
638
+ componentRef.changeDetectorRef.detectChanges();
711
639
  }
712
- }
713
- return null;
640
+ });
714
641
  }
715
642
  /**
716
643
  * Calculate execution statistics
@@ -726,67 +653,49 @@ export class AgentExecutionMonitorComponent {
726
653
  totalDuration: 0,
727
654
  totalPrompts: 0
728
655
  };
729
- // Check if we have agent run data with token information
730
- if (this.executionData?.agentRun) {
731
- // Use token data from agent run if available
732
- this.stats.totalTokens = this.executionData.agentRun.TotalTokensUsed || 0;
733
- this.stats.totalCost = this.executionData.agentRun.TotalCost || 0;
656
+ // Use token data from agent run if available
657
+ if (this.agentRun) {
658
+ this.stats.totalTokens = this.agentRun.TotalTokensUsed || 0;
659
+ this.stats.totalCost = this.agentRun.TotalCost || 0;
660
+ // Calculate duration
661
+ if (this.agentRun.StartedAt && this.agentRun.CompletedAt) {
662
+ this.stats.totalDuration = new Date(this.agentRun.CompletedAt).getTime() -
663
+ new Date(this.agentRun.StartedAt).getTime();
664
+ }
665
+ // Count steps recursively
666
+ if (this.agentRun.Steps) {
667
+ this.countSteps(this.agentRun.Steps);
668
+ }
734
669
  }
735
- const processNode = (node) => {
670
+ console.log('📈 Stats calculated:', {
671
+ totalSteps: this.stats.totalSteps,
672
+ totalTokens: this.stats.totalTokens,
673
+ totalCost: this.stats.totalCost
674
+ });
675
+ }
676
+ /**
677
+ * Count steps recursively
678
+ */
679
+ countSteps(steps) {
680
+ for (const step of steps) {
736
681
  this.stats.totalSteps++;
737
- if (node.status === 'completed')
682
+ // Map to display types for consistency
683
+ const displayType = this.getStepTypeClass(step.StepType);
684
+ this.stats.stepsByType[displayType] = (this.stats.stepsByType[displayType] || 0) + 1;
685
+ if (step.Status === 'Completed')
738
686
  this.stats.completedSteps++;
739
- if (node.status === 'failed')
687
+ if (step.Status === 'Failed' || step.Status === 'Cancelled')
740
688
  this.stats.failedSteps++;
741
- // Only accumulate tokens if we don't have agent run data
742
- if (!this.executionData?.agentRun) {
743
- if (node.tokensUsed)
744
- this.stats.totalTokens += node.tokensUsed;
745
- if (node.cost)
746
- this.stats.totalCost += node.cost;
747
- }
748
- if (node.duration)
749
- this.stats.totalDuration += node.duration;
750
- this.stats.stepsByType[node.type] = (this.stats.stepsByType[node.type] || 0) + 1;
751
689
  // Count prompts
752
- if (node.type === 'prompt' || node.promptCount) {
753
- this.stats.totalPrompts += node.promptCount || 1;
690
+ if (step.StepType === 'Prompt') {
691
+ this.stats.totalPrompts++;
754
692
  }
755
- if (node.children) {
756
- node.children.forEach(child => processNode(child));
693
+ // Recurse for sub-agents
694
+ if (step.StepType === 'Sub-Agent' && step.SubAgentRun?.Steps) {
695
+ this.countSteps(step.SubAgentRun.Steps);
757
696
  }
758
- };
759
- this.executionTree.forEach(node => processNode(node));
760
- }
761
- /**
762
- * Toggle node expansion
763
- */
764
- toggleNode(node) {
765
- const nodeInTree = this.findNodeById(this.executionTree, node.id);
766
- if (nodeInTree && this.hasExpandableContent(nodeInTree)) {
767
- nodeInTree.expanded = !nodeInTree.expanded;
768
- this.userHasInteracted = true;
769
- // Update the component's node reference to trigger change detection
770
- const componentRef = this.nodeComponentMap.get(node.id);
771
- if (componentRef) {
772
- componentRef.instance.node = { ...nodeInTree };
773
- componentRef.changeDetectorRef.detectChanges();
774
- }
775
- this.cdr.markForCheck();
776
697
  }
777
698
  }
778
- /**
779
- * Check if node has expandable content
780
- */
781
- hasExpandableContent(node) {
782
- return !!(node.children && node.children.length > 0) ||
783
- node.type === 'sub-agent' ||
784
- node.type === 'action' ||
785
- !!node.inputPreview ||
786
- !!node.outputPreview ||
787
- !!node.error ||
788
- !!node.detailsMarkdown;
789
- }
790
699
  /**
791
700
  * Handle scroll events
792
701
  */
@@ -877,6 +786,24 @@ export class AgentExecutionMonitorComponent {
877
786
  getStepTypes() {
878
787
  return Object.keys(this.stats.stepsByType).sort();
879
788
  }
789
+ /**
790
+ * Pluralize step type based on count
791
+ */
792
+ pluralizeStepType(type, count) {
793
+ if (count === 1) {
794
+ return type;
795
+ }
796
+ // Handle special cases
797
+ switch (type.toLowerCase()) {
798
+ case 'analysis':
799
+ return 'Analyses';
800
+ case 'summary':
801
+ return 'Summaries';
802
+ default:
803
+ // Default pluralization - just add 's'
804
+ return type + 's';
805
+ }
806
+ }
880
807
  /**
881
808
  * Format markdown content for display
882
809
  */
@@ -916,134 +843,52 @@ export class AgentExecutionMonitorComponent {
916
843
  /**
917
844
  * Append new live steps without re-rendering entire tree
918
845
  */
919
- appendNewLiveSteps(liveSteps) {
920
- if (!liveSteps || liveSteps.length === 0)
921
- return;
922
- // If view is not initialized yet, wait for it
923
- if (!this.viewInitialized || !this.executionNodesContainer) {
846
+ appendNewLiveSteps(newSteps) {
847
+ if (!newSteps || newSteps.length === 0 || !this.viewInitialized || !this.executionNodesContainer) {
924
848
  return;
925
849
  }
926
- // Sort live steps by stepNumber to ensure proper ordering
927
- const sortedSteps = [...liveSteps].sort((a, b) => {
928
- const aStepNum = a.step?.StepNumber || 0;
929
- const bStepNum = b.step?.StepNumber || 0;
930
- return aStepNum - bStepNum;
931
- });
932
- // Create a map of existing nodes by ID for quick lookup
933
- const nodeMap = new Map();
934
- this.buildNodeMap(this.executionTree, nodeMap);
935
- // First pass: identify which steps are truly NEW (not in processedStepIds)
936
- const trulyNewStepIds = new Set();
937
- for (const step of sortedSteps) {
938
- const stepId = step.step?.ID || this.generateStepId(step);
939
- if (!this.processedStepIds.has(stepId)) {
940
- trulyNewStepIds.add(stepId);
941
- }
942
- }
943
- // Only mark previously new nodes as complete if we have truly new steps
944
- if (trulyNewStepIds.size > 0 && this.newlyAddedNodeIds.size > 0) {
850
+ console.log('📝 Appending', newSteps.length, 'live steps');
851
+ // Sort steps by StepNumber
852
+ const sortedSteps = [...newSteps].sort((a, b) => (a.StepNumber || 0) - (b.StepNumber || 0));
853
+ // Mark previously new steps as completed
854
+ if (this.newlyAddedNodeIds.size > 0) {
945
855
  this.markPreviouslyNewNodesAsComplete();
946
- // Clear the set for new additions
947
856
  this.newlyAddedNodeIds.clear();
948
857
  }
949
- let hasNewNodes = false;
858
+ let hasNewSteps = false;
950
859
  for (const step of sortedSteps) {
951
- // Generate a stable ID based on step content to avoid duplicates
952
- const stepId = step.step?.ID || this.generateStepId(step);
953
- // Skip if we've already processed this exact step
954
- if (this.processedStepIds.has(stepId)) {
955
- continue;
956
- }
957
- // Mark as processed
958
- this.processedStepIds.add(stepId);
959
- // Check if this node already exists
960
- const existingNode = nodeMap.get(stepId);
961
- if (existingNode) {
962
- // Update existing node in the component
963
- const componentRef = this.nodeComponentMap.get(stepId);
964
- if (componentRef) {
965
- // Preserve current UI state
966
- const currentExpanded = existingNode.expanded;
967
- const currentDetailsExpanded = existingNode.detailsExpanded;
968
- const updatedStatus = step.step?.Status ?
969
- this.mapStepStatus(step.step.Status) :
970
- (step.endTime || step.outputData ? 'completed' : existingNode.status);
971
- if (existingNode.status !== updatedStatus) {
972
- existingNode.status = updatedStatus;
973
- }
974
- if (step.endTime && !existingNode.endTime) {
975
- existingNode.endTime = new Date(step.endTime);
976
- existingNode.duration = step.durationMs;
977
- }
978
- // Update output data if available
979
- if (step.outputData && !existingNode.outputPreview) {
980
- existingNode.outputPreview = this.createPreview(step.outputData);
981
- existingNode.tokensUsed = this.extractTokens(step.outputData);
982
- existingNode.cost = this.extractCost(step.outputData);
983
- }
984
- // Restore UI state
985
- existingNode.expanded = currentExpanded;
986
- existingNode.detailsExpanded = currentDetailsExpanded;
987
- // Update the component's node reference
988
- // Important: We need to update the node object directly to ensure
989
- // Angular detects changes to style bindings that depend on node.depth
990
- const nodeInstance = componentRef.instance.node;
991
- Object.assign(nodeInstance, existingNode);
992
- // Mark for check to ensure Angular updates the view
993
- componentRef.changeDetectorRef.markForCheck();
860
+ // Skip if already processed
861
+ if (this.processedStepIds.has(step.ID)) {
862
+ // Update existing step if status changed
863
+ const componentRef = this.nodeComponentMap.get(step.ID);
864
+ if (componentRef && componentRef.instance.step.Status !== step.Status) {
865
+ componentRef.instance.step = step;
994
866
  componentRef.changeDetectorRef.detectChanges();
995
867
  }
868
+ continue;
996
869
  }
997
- else {
998
- // Create new node
999
- const newNode = this.createNodeFromLiveStep(step);
1000
- // Debug: Log if depth is missing
1001
- if (newNode.depth === 0 && step.depth !== 0) {
1002
- console.warn('Node depth was reset to 0 for step:', step);
1003
- }
1004
- if (step.depth === 0) {
1005
- // Add to tree and create component
1006
- this.executionTree.push(newNode);
1007
- this.createNodeComponent(newNode, this.executionNodesContainer);
1008
- }
1009
- else {
1010
- // Find parent and add as child
1011
- const parentNode = this.findParentNode(this.executionTree, newNode);
1012
- if (parentNode) {
1013
- if (!parentNode.children) {
1014
- parentNode.children = [];
1015
- }
1016
- parentNode.children.push(newNode);
1017
- // Update parent component to show new child
1018
- const parentComponentRef = this.nodeComponentMap.get(parentNode.id);
1019
- if (parentComponentRef) {
1020
- // Update the parent node object directly
1021
- const parentNodeInstance = parentComponentRef.instance.node;
1022
- Object.assign(parentNodeInstance, parentNode);
1023
- parentComponentRef.changeDetectorRef.markForCheck();
1024
- parentComponentRef.changeDetectorRef.detectChanges();
1025
- }
1026
- }
1027
- else {
1028
- // If no parent found, add as root
1029
- this.executionTree.push(newNode);
1030
- this.createNodeComponent(newNode, this.executionNodesContainer);
1031
- }
1032
- }
1033
- hasNewNodes = true;
1034
- // Only track as newly added if it's truly new
1035
- if (trulyNewStepIds.has(stepId)) {
1036
- this.newlyAddedNodeIds.add(newNode.id);
1037
- }
870
+ // Mark as processed and newly added
871
+ this.processedStepIds.add(step.ID);
872
+ this.newlyAddedNodeIds.add(step.ID);
873
+ hasNewSteps = true;
874
+ // Determine depth based on parent hierarchy
875
+ const depth = this.calculateStepDepth(step);
876
+ const agentPath = this.buildAgentPath(step);
877
+ // Create component for new step
878
+ this.createStepComponent(step, depth, agentPath);
879
+ // Handle sub-agent steps recursively
880
+ if (step.StepType === 'Sub-Agent' && step.SubAgentRun?.Steps) {
881
+ const subAgentPath = [...agentPath, step.SubAgentRun.Agent || 'Sub-Agent'];
882
+ this.renderSteps(step.SubAgentRun.Steps, depth + 1, subAgentPath);
1038
883
  }
1039
884
  }
1040
- if (hasNewNodes) {
1041
- // Update current step
885
+ if (hasNewSteps) {
886
+ // After processing all new steps, ensure only the last one shows as running
887
+ this.ensureOnlyLastStepIsRunning();
1042
888
  this.updateCurrentStep();
1043
- // Recalculate stats
1044
889
  this.calculateStats();
1045
- // Trigger change detection
1046
- this.cdr.markForCheck();
890
+ // Force change detection to ensure new components are rendered
891
+ this.cdr.detectChanges();
1047
892
  // Auto-scroll if user hasn't interacted
1048
893
  setTimeout(() => {
1049
894
  this.autoScrollToBottom();
@@ -1051,213 +896,20 @@ export class AgentExecutionMonitorComponent {
1051
896
  }
1052
897
  }
1053
898
  /**
1054
- * Generate a stable ID for a step based on its content
899
+ * Calculate step depth based on parent hierarchy
1055
900
  */
1056
- generateStepId(step) {
1057
- // Create a stable ID based on step properties that should be unique
1058
- const parts = [
1059
- step.executionType || 'unknown',
1060
- step.depth || 0,
1061
- step.step?.StepName || '',
1062
- step.startTime || Date.now()
1063
- ];
1064
- return `live-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '')}`;
901
+ calculateStepDepth(step) {
902
+ // For now, return 0 for top-level steps
903
+ // In live mode, depth information should come from the streaming data
904
+ return 0;
1065
905
  }
1066
906
  /**
1067
- * Find parent node based on depth
907
+ * Build agent path for a step
1068
908
  */
1069
- findParentNode(nodes, childNode) {
1070
- // We need to find the most recent node at the parent depth
1071
- // that could logically be this node's parent
1072
- let potentialParent = null;
1073
- const findParentRecursive = (searchNodes) => {
1074
- // Process nodes in reverse order to find the most recent one
1075
- for (let i = searchNodes.length - 1; i >= 0; i--) {
1076
- const node = searchNodes[i];
1077
- // Check if this node could be the parent based on depth
1078
- if (node.depth === childNode.depth - 1) {
1079
- // Special handling for sub-agent execution steps
1080
- if (childNode.agentPath && childNode.agentPath.length > node.agentPath.length) {
1081
- // This child belongs to a sub-agent
1082
- // Look for the most recent sub-agent delegation node
1083
- if (node.type === 'sub-agent' || node.name.includes('Delegating to')) {
1084
- return node;
1085
- }
1086
- }
1087
- // Keep track of the most recent node at the correct depth
1088
- if (!potentialParent) {
1089
- potentialParent = node;
1090
- }
1091
- }
1092
- // Search children recursively
1093
- if (node.children && node.children.length > 0) {
1094
- const foundInChildren = findParentRecursive(node.children);
1095
- if (foundInChildren)
1096
- return foundInChildren;
1097
- }
1098
- }
1099
- return null;
1100
- };
1101
- const found = findParentRecursive(nodes);
1102
- return found || potentialParent;
1103
- }
1104
- /**
1105
- * Filter nodes based on display mode
1106
- */
1107
- filterNodesForMode(nodes, mode) {
1108
- return nodes.filter(node => {
1109
- // If no displayMode is set, default to showing in both modes
1110
- if (!node.displayMode) {
1111
- return true;
1112
- }
1113
- // Show if displayMode is 'both' or matches current mode
1114
- if (node.displayMode === 'both' || node.displayMode === mode) {
1115
- // Filter children recursively
1116
- if (node.children) {
1117
- node.children = this.filterNodesForMode(node.children, mode);
1118
- }
1119
- return true;
1120
- }
1121
- return false;
1122
- });
1123
- }
1124
- /**
1125
- * Build a map of nodes by ID for quick lookup
1126
- */
1127
- buildNodeMap(nodes, map) {
1128
- for (const node of nodes) {
1129
- map.set(node.id, node);
1130
- if (node.children) {
1131
- this.buildNodeMap(node.children, map);
1132
- }
1133
- }
1134
- }
1135
- /**
1136
- * Create a single node from a live step
1137
- */
1138
- createNodeFromLiveStep(step) {
1139
- const stepName = step.step?.StepName || 'Processing...';
1140
- let name = stepName;
1141
- let detailsMarkdown;
1142
- // Check if the step name contains markdown
1143
- if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
1144
- const lines = stepName.split('\n');
1145
- name = lines[0].trim();
1146
- if (lines.length > 1) {
1147
- detailsMarkdown = lines.slice(1).join('\n').trim();
1148
- }
1149
- }
1150
- // For live steps, always start as running
1151
- // The status will be updated to completed in markPreviouslyNewNodesAsComplete
1152
- let status = 'running';
1153
- if (step.step?.Status) {
1154
- // Only use explicit status if it's failed
1155
- const mappedStatus = this.mapStepStatus(step.step.Status);
1156
- if (mappedStatus === 'failed') {
1157
- status = 'failed';
1158
- }
1159
- }
1160
- // Determine display mode based on the step content
1161
- let displayMode = 'both';
1162
- // Progress messages that should only show in live mode
1163
- if (name.includes('Executing') && name.includes('action') && !name.startsWith('Execute Action:')) {
1164
- displayMode = 'live';
1165
- }
1166
- else if (name.includes("Executing agent's initial prompt")) {
1167
- displayMode = 'live';
1168
- }
1169
- else if (name.includes('Re-executing agent prompt with additional context')) {
1170
- displayMode = 'live';
1171
- }
1172
- // Check displayMode from metadata if available
1173
- if (step.metadata?.displayMode) {
1174
- displayMode = step.metadata.displayMode;
1175
- }
1176
- // Clear metadata storage before creating previews
1177
- this.lastParsedAgentData = null;
1178
- this.lastParsedActionData = null;
1179
- // Create previews which will populate metadata if available
1180
- const inputPreview = this.createPreview(step.inputData);
1181
- const outputPreview = this.createPreview(step.outputData);
1182
- const nodeType = this.mapStepType(step.executionType || step.step?.StepType);
1183
- const node = {
1184
- id: step.step?.ID || this.generateStepId(step),
1185
- name: name,
1186
- type: nodeType,
1187
- status: status,
1188
- startTime: step.startTime ? new Date(step.startTime) : new Date(),
1189
- endTime: step.endTime ? new Date(step.endTime) : undefined,
1190
- duration: step.durationMs,
1191
- agentPath: step.agentHierarchy || [],
1192
- expanded: false,
1193
- depth: step.depth || 0,
1194
- detailsMarkdown: detailsMarkdown,
1195
- detailsExpanded: false,
1196
- inputPreview: inputPreview,
1197
- outputPreview: outputPreview,
1198
- tokensUsed: this.extractTokens(step.outputData),
1199
- cost: this.extractCost(step.outputData),
1200
- displayMode: displayMode
1201
- };
1202
- // Add agent metadata if this is a sub-agent node
1203
- if (nodeType === 'sub-agent' && this.lastParsedAgentData) {
1204
- node.agentName = this.lastParsedAgentData.agentName;
1205
- node.agentId = this.lastParsedAgentData.agentId;
1206
- node.agentIconClass = this.lastParsedAgentData.agentIconClass;
1207
- node.agentLogoURL = this.lastParsedAgentData.agentLogoURL;
1208
- }
1209
- // Add action metadata if this is an action node
1210
- if (nodeType === 'action' && this.lastParsedActionData) {
1211
- node.actionName = this.lastParsedActionData.actionName;
1212
- node.actionId = this.lastParsedActionData.actionId;
1213
- node.actionIconClass = this.lastParsedActionData.actionIconClass;
1214
- }
1215
- return node;
1216
- }
1217
- /**
1218
- * Collect all node IDs in the tree
1219
- */
1220
- collectNodeIds(nodes, ids) {
1221
- for (const node of nodes) {
1222
- ids.add(node.id);
1223
- if (node.children) {
1224
- this.collectNodeIds(node.children, ids);
1225
- }
1226
- }
1227
- }
1228
- /**
1229
- * Preserve expanded state of all nodes
1230
- */
1231
- preserveExpandedState(nodes, expandedState, detailsExpandedState) {
1232
- for (const node of nodes) {
1233
- if (node.expanded !== undefined) {
1234
- expandedState.set(node.id, node.expanded);
1235
- }
1236
- if (node.detailsExpanded !== undefined) {
1237
- detailsExpandedState.set(node.id, node.detailsExpanded);
1238
- }
1239
- if (node.children) {
1240
- this.preserveExpandedState(node.children, expandedState, detailsExpandedState);
1241
- }
1242
- }
1243
- }
1244
- /**
1245
- * Restore expanded state of all nodes
1246
- */
1247
- restoreExpandedState(nodes, expandedState, detailsExpandedState) {
1248
- for (const node of nodes) {
1249
- const expanded = expandedState.get(node.id);
1250
- if (expanded !== undefined) {
1251
- node.expanded = expanded;
1252
- }
1253
- const detailsExpanded = detailsExpandedState.get(node.id);
1254
- if (detailsExpanded !== undefined) {
1255
- node.detailsExpanded = detailsExpanded;
1256
- }
1257
- if (node.children) {
1258
- this.restoreExpandedState(node.children, expandedState, detailsExpandedState);
1259
- }
1260
- }
909
+ buildAgentPath(step) {
910
+ // In live mode, agent path should come from streaming data
911
+ // For now, return empty array
912
+ return [];
1261
913
  }
1262
914
  /**
1263
915
  * Check if execution is complete
@@ -1270,27 +922,50 @@ export class AgentExecutionMonitorComponent {
1270
922
  * Handle view run button click
1271
923
  */
1272
924
  onViewRunClick() {
1273
- if (this.runId) {
1274
- this.viewRunClick.emit({ runId: this.runId, runType: this.runType });
925
+ if (this.agentRun) {
926
+ this.viewRunClick.emit({ runId: this.agentRun.ID, runType: 'agent' });
1275
927
  }
1276
928
  }
1277
929
  /**
1278
- * Append a node to its parent based on depth
930
+ * Expands all nodes that have sub-agent children
1279
931
  */
1280
- appendToParent(nodes, newNode) {
1281
- for (const node of nodes) {
1282
- if (node.depth === newNode.depth - 1) {
1283
- if (!node.children) {
1284
- node.children = [];
1285
- }
1286
- node.children.push(newNode);
1287
- return true;
1288
- }
1289
- if (node.children && this.appendToParent(node.children, newNode)) {
1290
- return true;
932
+ expandAllNodes() {
933
+ console.log('🔄 Auto-expanding nodes with sub-agents');
934
+ // Expand all steps that have sub-agent runs
935
+ this.nodeComponentMap.forEach((componentRef, stepId) => {
936
+ const step = componentRef.instance.step;
937
+ if (step.StepType === 'Sub-Agent' && step.SubAgentRun?.Steps?.length) {
938
+ this.expandedStates.set(stepId, true);
939
+ componentRef.instance.expanded = true;
940
+ componentRef.changeDetectorRef.detectChanges();
941
+ console.log(`Expanded sub-agent: ${step.StepName}`);
1291
942
  }
943
+ });
944
+ // Trigger change detection
945
+ this.cdr.markForCheck();
946
+ this.cdr.detectChanges();
947
+ // Scroll to top after expansion
948
+ setTimeout(() => {
949
+ this.scrollToTop();
950
+ }, 100);
951
+ }
952
+ /**
953
+ * Scroll the execution tree to the top
954
+ */
955
+ scrollToTop() {
956
+ if (this.executionTreeContainer) {
957
+ const element = this.executionTreeContainer.nativeElement;
958
+ element.scrollTo({
959
+ top: 0,
960
+ behavior: 'smooth'
961
+ });
1292
962
  }
1293
- return false;
963
+ }
964
+ /**
965
+ * Check if there is content to display
966
+ */
967
+ hasContent() {
968
+ return this.nodeComponentMap.size > 0;
1294
969
  }
1295
970
  static { this.ɵfac = function AgentExecutionMonitorComponent_Factory(t) { return new (t || AgentExecutionMonitorComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; }
1296
971
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AgentExecutionMonitorComponent, selectors: [["mj-agent-execution-monitor"]], viewQuery: function AgentExecutionMonitorComponent_Query(rf, ctx) { if (rf & 1) {
@@ -1300,7 +975,7 @@ export class AgentExecutionMonitorComponent {
1300
975
  let _t;
1301
976
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.executionTreeContainer = _t.first);
1302
977
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.executionNodesContainer = _t.first);
1303
- } }, inputs: { mode: "mode", executionData: "executionData", autoExpand: "autoExpand", runId: "runId", runType: "runType" }, outputs: { viewRunClick: "viewRunClick" }, standalone: true, features: [i0.ɵɵNgOnChangesFeature, i0.ɵɵStandaloneFeature], decls: 39, vars: 16, consts: [["executionTreeContainer", ""], ["executionNodesContainer", ""], [1, "execution-monitor"], [1, "monitor-header"], [1, "header-title"], [1, "fa-solid", "fa-diagram-project"], [1, "live-indicator"], [1, "current-status"], [1, "view-run-btn"], [1, "execution-tree", 3, "scroll", "click"], [1, "nodes-container"], [1, "empty-state"], [1, "monitor-footer"], [1, "stats-grid"], [1, "stat-item"], [1, "stat-label"], [1, "stat-value"], [1, "failed-count"], [1, "step-types"], [1, "pulse"], [1, "status-label"], [1, "agent-path"], [1, "step-name"], [1, "view-run-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "fa-solid", "fa-hourglass-start"], [1, "type-badge"]], template: function AgentExecutionMonitorComponent_Template(rf, ctx) { if (rf & 1) {
978
+ } }, inputs: { mode: "mode", agentRun: "agentRun", liveSteps: "liveSteps", autoExpand: "autoExpand", runId: "runId", runType: "runType" }, outputs: { viewRunClick: "viewRunClick" }, features: [i0.ɵɵNgOnChangesFeature], decls: 39, vars: 16, consts: [["executionTreeContainer", ""], ["executionNodesContainer", ""], [1, "execution-monitor"], [1, "monitor-header"], [1, "header-title"], [1, "fa-solid", "fa-diagram-project"], [1, "live-indicator"], [1, "current-status"], [1, "view-run-btn"], [1, "execution-tree", 3, "scroll", "click"], [1, "nodes-container"], [1, "empty-state"], [1, "monitor-footer"], [1, "stats-grid"], [1, "stat-item"], [1, "stat-label"], [1, "stat-value"], [1, "failed-count"], [1, "step-types"], [1, "pulse"], [1, "status-label"], [1, "step-name"], [1, "view-run-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "fa-solid", "fa-hourglass-start"], [1, "type-badge"]], template: function AgentExecutionMonitorComponent_Template(rf, ctx) { if (rf & 1) {
1304
979
  const _r1 = i0.ɵɵgetCurrentView();
1305
980
  i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "div", 4);
1306
981
  i0.ɵɵelement(3, "i", 5);
@@ -1309,13 +984,13 @@ export class AgentExecutionMonitorComponent {
1309
984
  i0.ɵɵelementEnd();
1310
985
  i0.ɵɵtemplate(6, AgentExecutionMonitorComponent_Conditional_6_Template, 3, 0, "span", 6);
1311
986
  i0.ɵɵelementEnd();
1312
- i0.ɵɵtemplate(7, AgentExecutionMonitorComponent_Conditional_7_Template, 7, 2, "div", 7)(8, AgentExecutionMonitorComponent_Conditional_8_Template, 3, 1, "button", 8);
987
+ i0.ɵɵtemplate(7, AgentExecutionMonitorComponent_Conditional_7_Template, 5, 1, "div", 7)(8, AgentExecutionMonitorComponent_Conditional_8_Template, 3, 1, "button", 8);
1313
988
  i0.ɵɵelementEnd();
1314
989
  i0.ɵɵelementStart(9, "div", 9, 0);
1315
990
  i0.ɵɵlistener("scroll", function AgentExecutionMonitorComponent_Template_div_scroll_9_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onScroll($event)); })("click", function AgentExecutionMonitorComponent_Template_div_click_9_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onUserInteraction()); });
1316
- i0.ɵɵelementStart(11, "div", 10, 1);
991
+ i0.ɵɵelement(11, "div", 10, 1);
1317
992
  i0.ɵɵtemplate(13, AgentExecutionMonitorComponent_Conditional_13_Template, 4, 0, "div", 11);
1318
- i0.ɵɵelementEnd()();
993
+ i0.ɵɵelementEnd();
1319
994
  i0.ɵɵelementStart(14, "div", 12)(15, "div", 13)(16, "div", 14)(17, "span", 15);
1320
995
  i0.ɵɵtext(18, "Steps");
1321
996
  i0.ɵɵelementEnd();
@@ -1352,11 +1027,11 @@ export class AgentExecutionMonitorComponent {
1352
1027
  i0.ɵɵadvance();
1353
1028
  i0.ɵɵconditional(ctx.currentStep && ctx.mode === "live" ? 7 : -1);
1354
1029
  i0.ɵɵadvance();
1355
- i0.ɵɵconditional(ctx.mode === "historical" && ctx.runId && ctx.isExecutionComplete() ? 8 : -1);
1030
+ i0.ɵɵconditional(ctx.mode === "historical" && ctx.agentRun && ctx.isExecutionComplete() ? 8 : -1);
1356
1031
  i0.ɵɵadvance();
1357
- i0.ɵɵclassProp("has-content", ctx.executionTree.length > 0);
1032
+ i0.ɵɵclassProp("has-content", ctx.hasContent());
1358
1033
  i0.ɵɵadvance(4);
1359
- i0.ɵɵconditional(ctx.executionTree.length === 0 ? 13 : -1);
1034
+ i0.ɵɵconditional(!ctx.agentRun && ctx.liveSteps.length === 0 ? 13 : -1);
1360
1035
  i0.ɵɵadvance(7);
1361
1036
  i0.ɵɵtextInterpolate2(" ", ctx.stats.completedSteps, "/", ctx.stats.totalSteps, " ");
1362
1037
  i0.ɵɵadvance();
@@ -1371,11 +1046,11 @@ export class AgentExecutionMonitorComponent {
1371
1046
  i0.ɵɵconditional(ctx.stats.totalDuration ? 37 : -1);
1372
1047
  i0.ɵɵadvance();
1373
1048
  i0.ɵɵconditional(ctx.getStepTypes().length > 0 ? 38 : -1);
1374
- } }, dependencies: [CommonModule], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n \n\n .monitor-header[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .live-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse[_ngcontent-%COMP%] {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_pulse 2s infinite;\n }\n\n @keyframes _ngcontent-%COMP%_pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label[_ngcontent-%COMP%] {\n font-weight: 500;\n }\n\n .agent-path[_ngcontent-%COMP%] {\n color: #999;\n }\n\n .step-name[_ngcontent-%COMP%] {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n \n\n .execution-tree[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container[_ngcontent-%COMP%] {\n \n\n }\n\n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n }\n\n \n\n .tree-node[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n }\n\n .node-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n user-select: none;\n }\n\n .node-header[_ngcontent-%COMP%]:hover {\n background: #e8f0fe;\n border-color: #2196f3;\n }\n\n .expand-icon[_ngcontent-%COMP%], .expand-spacer[_ngcontent-%COMP%] {\n width: 16px;\n text-align: center;\n color: #666;\n font-size: 10px;\n }\n\n .status-icon[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n font-size: 14px;\n }\n\n .status-pending[_ngcontent-%COMP%] { color: #999; }\n .status-running[_ngcontent-%COMP%] { color: #2196f3; }\n .status-completed[_ngcontent-%COMP%] { color: #4caf50; }\n .status-failed[_ngcontent-%COMP%] { color: #f44336; }\n\n .type-icon[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n font-size: 14px;\n color: #666;\n }\n\n .node-name[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: #1a1a1a;\n }\n\n .node-duration[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #666;\n font-weight: 500;\n }\n\n .node-metrics[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n font-size: 12px;\n }\n\n .tokens[_ngcontent-%COMP%] {\n color: #666;\n }\n\n .cost[_ngcontent-%COMP%] {\n color: #2196f3;\n font-weight: 500;\n }\n\n \n\n .node-details[_ngcontent-%COMP%] {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f5f5f5;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .detail-section[_ngcontent-%COMP%] {\n margin-bottom: 8px;\n }\n\n .detail-section[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n }\n\n .detail-section.error[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .detail-label[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n margin-bottom: 4px;\n color: #666;\n }\n\n .detail-section.error[_ngcontent-%COMP%] .detail-label[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .detail-label[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n }\n\n .detail-content[_ngcontent-%COMP%] {\n padding-left: 18px;\n color: #333;\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n \n\n .monitor-footer[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item[_ngcontent-%COMP%] {\n text-align: center;\n }\n\n .stat-label[_ngcontent-%COMP%] {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value[_ngcontent-%COMP%] {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count[_ngcontent-%COMP%] {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n\n \n\n .details-toggle[_ngcontent-%COMP%] {\n margin-left: 8px;\n padding: 2px 6px;\n background: transparent;\n border: 1px solid #ddd;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n color: #666;\n transition: all 0.2s ease;\n }\n \n .details-toggle[_ngcontent-%COMP%]:hover {\n background: #f0f0f0;\n border-color: #2196f3;\n color: #2196f3;\n }\n \n \n\n .markdown-details[_ngcontent-%COMP%] {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f9fafb;\n border-left: 3px solid #2196f3;\n border-radius: 0 4px 4px 0;\n font-size: 13px;\n line-height: 1.6;\n }\n \n .markdown-details[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n font-size: 16px;\n margin: 0 0 8px 0;\n color: #1a1a1a;\n }\n \n .markdown-details[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 14px;\n margin: 8px 0 6px 0;\n color: #333;\n }\n \n .markdown-details[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n font-size: 13px;\n margin: 6px 0 4px 0;\n color: #555;\n }\n \n .markdown-details[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] {\n margin: 4px 0;\n padding-left: 20px;\n }\n \n .markdown-details[_ngcontent-%COMP%] li[_ngcontent-%COMP%] {\n margin: 2px 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: #e8f0fe;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n \n .markdown-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] {\n background: #f5f5f5;\n padding: 8px;\n border-radius: 4px;\n overflow-x: auto;\n margin: 8px 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: none;\n padding: 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n font-weight: 600;\n color: #1a1a1a;\n }\n \n .markdown-details[_ngcontent-%COMP%] em[_ngcontent-%COMP%] {\n font-style: italic;\n color: #555;\n }\n\n \n\n .execution-history-toggle[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n padding: 4px 8px;\n margin-left: 4px;\n cursor: pointer;\n color: #666;\n font-size: 12px;\n border-radius: 3px;\n transition: all 0.2s ease;\n }\n \n .execution-history-toggle[_ngcontent-%COMP%]:hover {\n background: #e3f2fd;\n color: #2196f3;\n }\n \n \n\n .view-run-btn[_ngcontent-%COMP%] {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn[_ngcontent-%COMP%]:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n }\n\n \n\n @media (max-width: 768px) {\n .monitor-header[_ngcontent-%COMP%] {\n padding: 12px;\n }\n\n .execution-tree[_ngcontent-%COMP%] {\n padding: 12px;\n }\n\n .node-header[_ngcontent-%COMP%] {\n padding: 6px 8px;\n gap: 6px;\n }\n\n .node-metrics[_ngcontent-%COMP%] {\n display: none;\n }\n \n .markdown-details[_ngcontent-%COMP%] {\n margin-left: 20px;\n font-size: 12px;\n }\n }"], changeDetection: 0 }); }
1049
+ } }, styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n }\n\n \n\n .monitor-header[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .live-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse[_ngcontent-%COMP%] {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_pulse 2s infinite;\n }\n\n @keyframes _ngcontent-%COMP%_pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label[_ngcontent-%COMP%] {\n font-weight: 500;\n }\n\n .agent-path[_ngcontent-%COMP%] {\n color: #999;\n }\n\n .step-name[_ngcontent-%COMP%] {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n \n\n .execution-tree[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container[_ngcontent-%COMP%] {\n \n\n }\n\n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n }\n\n \n\n .tree-node[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n }\n\n \n\n .monitor-footer[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item[_ngcontent-%COMP%] {\n text-align: center;\n }\n\n .stat-label[_ngcontent-%COMP%] {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value[_ngcontent-%COMP%] {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count[_ngcontent-%COMP%] {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n \n \n\n .view-run-btn[_ngcontent-%COMP%] {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn[_ngcontent-%COMP%]:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n }\n\n \n\n @media (max-width: 768px) {\n .monitor-header[_ngcontent-%COMP%] {\n padding: 12px;\n }\n\n .execution-tree[_ngcontent-%COMP%] {\n padding: 12px;\n }\n }"], changeDetection: 0 }); }
1375
1050
  }
1376
1051
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AgentExecutionMonitorComponent, [{
1377
1052
  type: Component,
1378
- args: [{ selector: 'mj-agent-execution-monitor', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
1053
+ args: [{ selector: 'mj-agent-execution-monitor', changeDetection: ChangeDetectionStrategy.OnPush, template: `
1379
1054
  <div class="execution-monitor" [class.live-mode]="mode === 'live'">
1380
1055
  <!-- Header -->
1381
1056
  <div class="monitor-header">
@@ -1392,11 +1067,10 @@ export class AgentExecutionMonitorComponent {
1392
1067
  @if (currentStep && mode === 'live') {
1393
1068
  <div class="current-status">
1394
1069
  <span class="status-label">Current:</span>
1395
- <span class="agent-path">{{ formatAgentPath(currentStep.agentPath) }}</span>
1396
- <span class="step-name">{{ currentStep.name }}</span>
1070
+ <span class="step-name">{{ currentStep.StepName }}</span>
1397
1071
  </div>
1398
1072
  }
1399
- @if (mode === 'historical' && runId && isExecutionComplete()) {
1073
+ @if (mode === 'historical' && agentRun && isExecutionComplete()) {
1400
1074
  <button class="view-run-btn" (click)="onViewRunClick()">
1401
1075
  <i class="fa-solid fa-external-link-alt"></i>
1402
1076
  View {{ runType === 'agent' ? 'Agent' : 'Prompt' }} Run
@@ -1407,18 +1081,18 @@ export class AgentExecutionMonitorComponent {
1407
1081
  <!-- Execution Tree -->
1408
1082
  <div class="execution-tree"
1409
1083
  #executionTreeContainer
1410
- [class.has-content]="executionTree.length > 0"
1084
+ [class.has-content]="hasContent()"
1411
1085
  (scroll)="onScroll($event)"
1412
1086
  (click)="onUserInteraction()">
1413
1087
  <!-- Always render the container, but show empty state when no data -->
1414
1088
  <div #executionNodesContainer class="nodes-container">
1415
- @if (executionTree.length === 0) {
1416
- <div class="empty-state">
1417
- <i class="fa-solid fa-hourglass-start"></i>
1418
- <p>Waiting for execution to begin...</p>
1419
- </div>
1420
- }
1421
1089
  </div>
1090
+ @if (!agentRun && liveSteps.length === 0) {
1091
+ <div class="empty-state">
1092
+ <i class="fa-solid fa-hourglass-start"></i>
1093
+ <p>Waiting for execution to begin...</p>
1094
+ </div>
1095
+ }
1422
1096
  </div>
1423
1097
 
1424
1098
  <!-- Statistics Footer -->
@@ -1456,17 +1130,19 @@ export class AgentExecutionMonitorComponent {
1456
1130
  <div class="step-types">
1457
1131
  @for (type of getStepTypes(); track type) {
1458
1132
  <span class="type-badge">
1459
- {{ stats.stepsByType[type] }} {{ type }}
1133
+ {{ stats.stepsByType[type] }} {{ pluralizeStepType(type, stats.stepsByType[type]) }}
1460
1134
  </span>
1461
1135
  }
1462
1136
  </div>
1463
1137
  }
1464
1138
  </div>
1465
1139
  </div>
1466
- `, styles: ["\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n /* Header */\n .monitor-header {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title i {\n color: #2196f3;\n }\n\n .live-indicator {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: pulse 2s infinite;\n }\n\n @keyframes pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label {\n font-weight: 500;\n }\n\n .agent-path {\n color: #999;\n }\n\n .step-name {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n /* Execution Tree */\n .execution-tree {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container {\n /* No extra padding needed, indentation handled by node margins */\n }\n\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 14px;\n }\n\n /* Tree Nodes */\n .tree-node {\n margin-bottom: 4px;\n }\n\n .node-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n user-select: none;\n }\n\n .node-header:hover {\n background: #e8f0fe;\n border-color: #2196f3;\n }\n\n .expand-icon, .expand-spacer {\n width: 16px;\n text-align: center;\n color: #666;\n font-size: 10px;\n }\n\n .status-icon {\n width: 20px;\n text-align: center;\n font-size: 14px;\n }\n\n .status-pending { color: #999; }\n .status-running { color: #2196f3; }\n .status-completed { color: #4caf50; }\n .status-failed { color: #f44336; }\n\n .type-icon {\n width: 20px;\n text-align: center;\n font-size: 14px;\n color: #666;\n }\n\n .node-name {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: #1a1a1a;\n }\n\n .node-duration {\n font-size: 12px;\n color: #666;\n font-weight: 500;\n }\n\n .node-metrics {\n display: flex;\n gap: 12px;\n font-size: 12px;\n }\n\n .tokens {\n color: #666;\n }\n\n .cost {\n color: #2196f3;\n font-weight: 500;\n }\n\n /* Node Details */\n .node-details {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f5f5f5;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .detail-section {\n margin-bottom: 8px;\n }\n\n .detail-section:last-child {\n margin-bottom: 0;\n }\n\n .detail-section.error {\n color: #d32f2f;\n }\n\n .detail-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n margin-bottom: 4px;\n color: #666;\n }\n\n .detail-section.error .detail-label {\n color: #d32f2f;\n }\n\n .detail-label i {\n font-size: 12px;\n }\n\n .detail-content {\n padding-left: 18px;\n color: #333;\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n /* Footer */\n .monitor-footer {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item {\n text-align: center;\n }\n\n .stat-label {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n\n /* Details Toggle Button */\n .details-toggle {\n margin-left: 8px;\n padding: 2px 6px;\n background: transparent;\n border: 1px solid #ddd;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n color: #666;\n transition: all 0.2s ease;\n }\n \n .details-toggle:hover {\n background: #f0f0f0;\n border-color: #2196f3;\n color: #2196f3;\n }\n \n /* Markdown Details Section */\n .markdown-details {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f9fafb;\n border-left: 3px solid #2196f3;\n border-radius: 0 4px 4px 0;\n font-size: 13px;\n line-height: 1.6;\n }\n \n .markdown-details h2 {\n font-size: 16px;\n margin: 0 0 8px 0;\n color: #1a1a1a;\n }\n \n .markdown-details h3 {\n font-size: 14px;\n margin: 8px 0 6px 0;\n color: #333;\n }\n \n .markdown-details h4 {\n font-size: 13px;\n margin: 6px 0 4px 0;\n color: #555;\n }\n \n .markdown-details ul {\n margin: 4px 0;\n padding-left: 20px;\n }\n \n .markdown-details li {\n margin: 2px 0;\n }\n \n .markdown-details code {\n background: #e8f0fe;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n \n .markdown-details pre {\n background: #f5f5f5;\n padding: 8px;\n border-radius: 4px;\n overflow-x: auto;\n margin: 8px 0;\n }\n \n .markdown-details pre code {\n background: none;\n padding: 0;\n }\n \n .markdown-details strong {\n font-weight: 600;\n color: #1a1a1a;\n }\n \n .markdown-details em {\n font-style: italic;\n color: #555;\n }\n\n /* Button styling for execution history toggle */\n .execution-history-toggle {\n background: transparent;\n border: none;\n padding: 4px 8px;\n margin-left: 4px;\n cursor: pointer;\n color: #666;\n font-size: 12px;\n border-radius: 3px;\n transition: all 0.2s ease;\n }\n \n .execution-history-toggle:hover {\n background: #e3f2fd;\n color: #2196f3;\n }\n \n /* View Run Button */\n .view-run-btn {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn i {\n font-size: 12px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .monitor-header {\n padding: 12px;\n }\n\n .execution-tree {\n padding: 12px;\n }\n\n .node-header {\n padding: 6px 8px;\n gap: 6px;\n }\n\n .node-metrics {\n display: none;\n }\n \n .markdown-details {\n margin-left: 20px;\n font-size: 12px;\n }\n }\n "] }]
1140
+ `, styles: ["\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n }\n\n /* Header */\n .monitor-header {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title i {\n color: #2196f3;\n }\n\n .live-indicator {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: pulse 2s infinite;\n }\n\n @keyframes pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label {\n font-weight: 500;\n }\n\n .agent-path {\n color: #999;\n }\n\n .step-name {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n /* Execution Tree */\n .execution-tree {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container {\n /* No extra padding needed, indentation handled by node margins */\n }\n\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 14px;\n }\n\n /* Tree Nodes - These styles are for reference only, actual rendering is done by ExecutionNodeComponent */\n .tree-node {\n margin-bottom: 4px;\n }\n\n /* Footer */\n .monitor-footer {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item {\n text-align: center;\n }\n\n .stat-label {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n \n /* View Run Button */\n .view-run-btn {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn i {\n font-size: 12px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .monitor-header {\n padding: 12px;\n }\n\n .execution-tree {\n padding: 12px;\n }\n }\n "] }]
1467
1141
  }], () => [{ type: i0.ChangeDetectorRef }], { mode: [{
1468
1142
  type: Input
1469
- }], executionData: [{
1143
+ }], agentRun: [{
1144
+ type: Input
1145
+ }], liveSteps: [{
1470
1146
  type: Input
1471
1147
  }], autoExpand: [{
1472
1148
  type: Input
@@ -1483,5 +1159,5 @@ export class AgentExecutionMonitorComponent {
1483
1159
  type: ViewChild,
1484
1160
  args: ['executionNodesContainer', { read: ViewContainerRef }]
1485
1161
  }] }); })();
1486
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AgentExecutionMonitorComponent, { className: "AgentExecutionMonitorComponent", filePath: "lib/agent-execution-monitor.component.ts", lineNumber: 643 }); })();
1162
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AgentExecutionMonitorComponent, { className: "AgentExecutionMonitorComponent", filePath: "lib/agent-execution-monitor.component.ts", lineNumber: 367 }); })();
1487
1163
  //# sourceMappingURL=agent-execution-monitor.component.js.map