@agenticmail/enterprise 0.5.210 → 0.5.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.210",
3
+ "version": "0.5.211",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -308,6 +308,133 @@ function TaskDetail(props) {
308
308
  );
309
309
  }
310
310
 
311
+ // ─── Metrics Bar ─────────────────────────────────────────
312
+ function MetricsBar(props) {
313
+ var s = props.stats;
314
+ var metricStyle = { display: 'flex', flexDirection: 'column', alignItems: 'center', padding: '8px 16px', minWidth: 80 };
315
+ var numStyle = function(color) { return { fontSize: 20, fontWeight: 700, color: color, lineHeight: 1 }; };
316
+ var lblStyle = { fontSize: 9, color: 'rgba(255,255,255,0.4)', marginTop: 3, textTransform: 'uppercase', letterSpacing: '0.06em' };
317
+ var divider = h('div', { style: { width: 1, height: 32, background: 'rgba(255,255,255,0.08)', flexShrink: 0 } });
318
+
319
+ return h('div', { style: { display: 'flex', alignItems: 'center', padding: '6px 16px', borderBottom: '1px solid rgba(255,255,255,0.06)', background: 'rgba(0,0,0,0.15)', flexShrink: 0, overflowX: 'auto', gap: 0 } },
320
+ h('div', metricStyle, h('div', numStyle('#22c55e'), s.todayCompleted || 0), h('div', lblStyle, 'Done Today')),
321
+ divider,
322
+ h('div', metricStyle, h('div', numStyle('#06b6d4'), s.inProgress || 0), h('div', lblStyle, 'In Progress')),
323
+ divider,
324
+ h('div', metricStyle, h('div', numStyle('#f59e0b'), s.todayCreated || 0), h('div', lblStyle, 'Created Today')),
325
+ divider,
326
+ h('div', metricStyle, h('div', numStyle('#ef4444'), s.todayFailed || 0), h('div', lblStyle, 'Failed Today')),
327
+ divider,
328
+ h('div', metricStyle, h('div', numStyle('#fff'), formatDuration(s.avgDurationMs)), h('div', lblStyle, 'Avg Duration')),
329
+ divider,
330
+ h('div', metricStyle, h('div', numStyle('#a855f7'), (s.totalTokens || 0) > 999999 ? ((s.totalTokens / 1000000).toFixed(1) + 'M') : (s.totalTokens || 0) > 999 ? ((s.totalTokens / 1000).toFixed(1) + 'K') : (s.totalTokens || 0)), h('div', lblStyle, 'Tokens')),
331
+ divider,
332
+ h('div', metricStyle, h('div', numStyle('#22c55e'), '$' + (s.totalCost || 0).toFixed(2)), h('div', lblStyle, 'Total Cost')),
333
+ divider,
334
+ h('div', metricStyle, h('div', numStyle('rgba(255,255,255,0.6)'), s.total || 0), h('div', lblStyle, 'All Time')),
335
+ // Top agents mini
336
+ s.topAgents && s.topAgents.length > 0 && h(Fragment, null,
337
+ divider,
338
+ h('div', { style: { display: 'flex', gap: 6, padding: '4px 12px', alignItems: 'center' } },
339
+ h('div', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginRight: 4 } }, 'TOP'),
340
+ s.topAgents.slice(0, 3).map(function(a) {
341
+ return h('div', { key: a.agent, style: { display: 'flex', alignItems: 'center', gap: 4, padding: '2px 8px', background: 'rgba(255,255,255,0.04)', borderRadius: 6, fontSize: 10 } },
342
+ h('div', { style: { width: 14, height: 14, borderRadius: '50%', background: '#6366f1' + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: '#6366f1' } }, (a.name || '?').charAt(0).toUpperCase()),
343
+ h('span', { style: { color: '#fff', fontWeight: 600 } }, a.name),
344
+ h('span', { style: { color: 'rgba(255,255,255,0.3)' } }, a.completed + '/' + a.active)
345
+ );
346
+ })
347
+ )
348
+ )
349
+ );
350
+ }
351
+
352
+ // ─── Inline Chain Flowchart (appears below canvas when task expanded) ─
353
+ function ChainFlowInline(props) {
354
+ var chain = props.chain;
355
+ var taskId = props.taskId;
356
+ var onClose = props.onClose;
357
+ var onClickTask = props.onClickTask;
358
+ if (!chain || chain.length === 0) return null;
359
+
360
+ var MINI_W = 180;
361
+ var MINI_H = 56;
362
+ var MINI_GAP = 40;
363
+ var totalW = chain.length * MINI_W + (chain.length - 1) * MINI_GAP + 40;
364
+
365
+ return h('div', { style: { borderTop: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.25)', padding: '12px 16px', flexShrink: 0, overflow: 'auto' } },
366
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 } },
367
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
368
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.06em' } }, 'TASK FLOW'),
369
+ h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.3)', fontFamily: 'var(--font-mono)' } }, chain[0].chainId ? '#' + chain[0].chainId.slice(0, 8) : ''),
370
+ h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.3)' } }, chain.length + ' step' + (chain.length > 1 ? 's' : ''))
371
+ ),
372
+ h('button', { onClick: onClose, style: { background: 'none', border: 'none', color: 'rgba(255,255,255,0.3)', cursor: 'pointer', fontSize: 16, padding: '0 4px' } }, '\u00D7')
373
+ ),
374
+ h('div', { style: { position: 'relative', height: MINI_H + 20, minWidth: totalW } },
375
+ // SVG arrows
376
+ h('svg', { width: totalW, height: MINI_H + 20, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
377
+ h('defs', null,
378
+ h('marker', { id: 'cf-arr', markerWidth: 6, markerHeight: 4, refX: 6, refY: 2, orient: 'auto' },
379
+ h('polygon', { points: '0 0, 6 2, 0 4', fill: 'rgba(99,102,241,0.5)' })
380
+ )
381
+ ),
382
+ chain.map(function(ct, i) {
383
+ if (i === 0) return null;
384
+ var x1 = 20 + (i - 1) * (MINI_W + MINI_GAP) + MINI_W;
385
+ var x2 = 20 + i * (MINI_W + MINI_GAP);
386
+ var y = 10 + MINI_H / 2;
387
+ var dType = ct.delegationType || 'delegation';
388
+ var color = DELEGATION_COLORS[dType] || 'rgba(99,102,241,0.5)';
389
+ var isActive = chain[i - 1].status === 'in_progress' || ct.status === 'in_progress';
390
+ return h(Fragment, { key: 'e' + i },
391
+ h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: color + '88', strokeWidth: 2, markerEnd: 'url(#cf-arr)' }),
392
+ isActive && h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, strokeDasharray: '4 12', className: 'tp-flow-active', style: { opacity: 0.7 } }),
393
+ dType !== 'delegation' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: color, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, dType)
394
+ );
395
+ })
396
+ ),
397
+ // Task nodes
398
+ chain.map(function(ct, i) {
399
+ var x = 20 + i * (MINI_W + MINI_GAP);
400
+ var sc = STATUS_COLORS[ct.status] || '#6b7394';
401
+ var isMe = ct.id === taskId;
402
+ var isActive = ct.status === 'in_progress';
403
+ return h('div', {
404
+ key: ct.id,
405
+ onClick: function() { if (onClickTask) onClickTask(ct); },
406
+ style: {
407
+ position: 'absolute', left: x, top: 10, width: MINI_W, height: MINI_H,
408
+ background: isMe ? sc + '15' : 'rgba(255,255,255,0.02)',
409
+ border: '1.5px solid ' + (isMe ? sc : 'rgba(255,255,255,0.1)'),
410
+ borderLeft: '3px solid ' + sc,
411
+ borderRadius: 8, padding: '6px 10px', cursor: 'pointer',
412
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3,
413
+ transition: 'all 0.15s',
414
+ },
415
+ onMouseEnter: function(e) { e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; },
416
+ onMouseLeave: function(e) { e.currentTarget.style.background = isMe ? sc + '15' : 'rgba(255,255,255,0.02)'; },
417
+ },
418
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
419
+ h('div', { style: { width: 16, height: 16, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: sc, flexShrink: 0 } },
420
+ (ct.assignedToName || '?').charAt(0).toUpperCase()
421
+ ),
422
+ h('span', { style: { fontSize: 10, fontWeight: 600, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 } }, ct.assignedToName || ct.assignedTo)
423
+ ),
424
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
425
+ tag(sc, ct.status.replace('_', ' ')),
426
+ h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)' } }, timeAgo(ct.createdAt)),
427
+ ct.actualDurationMs && h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, formatDuration(ct.actualDurationMs))
428
+ ),
429
+ isActive && ct.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden' } },
430
+ h('div', { style: { height: '100%', width: ct.progress + '%', background: sc, borderRadius: 1 } })
431
+ )
432
+ );
433
+ })
434
+ )
435
+ );
436
+ }
437
+
311
438
  // ─── Main Page ───────────────────────────────────────────
312
439
  export function TaskPipelinePage() {
313
440
  injectCSS();
@@ -315,8 +442,10 @@ export function TaskPipelinePage() {
315
442
  var toast = app.toast;
316
443
  var _tasks = useState([]);
317
444
  var tasks = _tasks[0]; var setTasks = _tasks[1];
318
- var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0 });
445
+ var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] });
319
446
  var stats = _stats[0]; var setStats = _stats[1];
447
+ var _expandedTaskId = useState(null);
448
+ var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
320
449
  var _loading = useState(true);
321
450
  var loading = _loading[0]; var setLoading = _loading[1];
322
451
  var _selectedTask = useState(null);
@@ -376,6 +505,8 @@ export function TaskPipelinePage() {
376
505
  return [event.task].concat(prev);
377
506
  });
378
507
  setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
508
+ // Refresh stats on every task event for real-time metrics
509
+ engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
379
510
  }
380
511
  } catch (err) {}
381
512
  };
@@ -386,7 +517,7 @@ export function TaskPipelinePage() {
386
517
  useEffect(function() {
387
518
  var iv = setInterval(function() {
388
519
  engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
389
- }, 30000);
520
+ }, 15000);
390
521
  return function() { clearInterval(iv); };
391
522
  }, []);
392
523
 
@@ -400,7 +531,6 @@ export function TaskPipelinePage() {
400
531
 
401
532
  function openTaskDetail(t) {
402
533
  setSelectedTask(t);
403
- // Load full chain if task has one
404
534
  if (t.chainId) {
405
535
  var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
406
536
  chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
@@ -410,6 +540,23 @@ export function TaskPipelinePage() {
410
540
  }
411
541
  }
412
542
 
543
+ // Toggle inline chain flowchart (single click on node)
544
+ function toggleExpand(t) {
545
+ if (expandedTaskId === t.id) {
546
+ setExpandedTaskId(null);
547
+ setSelectedChain(null);
548
+ } else {
549
+ setExpandedTaskId(t.id);
550
+ if (t.chainId) {
551
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
552
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
553
+ setSelectedChain(chainTasks.length > 0 ? chainTasks : [t]);
554
+ } else {
555
+ setSelectedChain([t]);
556
+ }
557
+ }
558
+ }
559
+
413
560
  // Filter
414
561
  var filtered = tasks.filter(function(t) {
415
562
  if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
@@ -522,6 +669,7 @@ export function TaskPipelinePage() {
522
669
 
523
670
  if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
524
671
  toolbar,
672
+ h(MetricsBar, { stats: stats }),
525
673
  h('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' } },
526
674
  h('div', { style: { width: 48, height: 48, borderRadius: 12, background: 'rgba(99,102,241,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16, color: '#6366f1' } }, I.workflow()),
527
675
  h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
@@ -529,8 +677,22 @@ export function TaskPipelinePage() {
529
677
  )
530
678
  );
531
679
 
680
+ // Build expanded chain for inline flowchart
681
+ var expandedChain = null;
682
+ if (expandedTaskId) {
683
+ var et = tasks.find(function(t) { return t.id === expandedTaskId; });
684
+ if (et && et.chainId) {
685
+ expandedChain = tasks.filter(function(ct) { return ct.chainId === et.chainId; });
686
+ expandedChain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
687
+ } else if (et) {
688
+ expandedChain = [et];
689
+ }
690
+ }
691
+
532
692
  return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
533
693
  toolbar,
694
+ // Metrics bar
695
+ h(MetricsBar, { stats: stats }),
534
696
  // Canvas
535
697
  h('div', {
536
698
  ref: containerRef,
@@ -606,17 +768,19 @@ export function TaskPipelinePage() {
606
768
  var dim = hoveredChainId && !isChainHl;
607
769
  var isActive = t.status === 'in_progress';
608
770
 
771
+ var isExpanded = expandedTaskId === node.id;
609
772
  return h('div', {
610
773
  key: node.id,
611
774
  className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
612
- onClick: function() { openTaskDetail(t); },
775
+ onClick: function() { toggleExpand(t); },
776
+ onDoubleClick: function() { openTaskDetail(t); },
613
777
  onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
614
778
  onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
615
779
  onMouseLeave: function() { setHoveredId(null); },
616
780
  style: {
617
781
  position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
618
782
  background: isHovered ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
619
- border: '1px solid ' + (isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
783
+ border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
620
784
  borderLeft: '3px solid ' + sc,
621
785
  borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
622
786
  transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
@@ -665,7 +829,15 @@ export function TaskPipelinePage() {
665
829
  )
666
830
  ),
667
831
 
668
- // Detail modal
832
+ // Inline chain flowchart (shown when a task is clicked)
833
+ expandedChain && expandedChain.length > 0 && h(ChainFlowInline, {
834
+ chain: expandedChain,
835
+ taskId: expandedTaskId,
836
+ onClose: function() { setExpandedTaskId(null); },
837
+ onClickTask: function(t) { openTaskDetail(t); }
838
+ }),
839
+
840
+ // Detail modal (double-click)
669
841
  selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
670
842
  );
671
843
  }
@@ -385,8 +385,21 @@ export class TaskQueueManager {
385
385
  return this.getAllTasks(orgId, limit);
386
386
  }
387
387
 
388
- getPipelineStats(orgId?: string): { created: number; assigned: number; inProgress: number; completed: number; failed: number; cancelled: number; total: number } {
389
- const stats = { created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0 };
388
+ getPipelineStats(orgId?: string): {
389
+ created: number; assigned: number; inProgress: number; completed: number; failed: number; cancelled: number; total: number;
390
+ todayCompleted: number; todayFailed: number; todayCreated: number; avgDurationMs: number; totalCost: number; totalTokens: number;
391
+ topAgents: Array<{ agent: string; name: string; completed: number; active: number }>;
392
+ } {
393
+ const stats = {
394
+ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0,
395
+ todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0,
396
+ topAgents: [] as Array<{ agent: string; name: string; completed: number; active: number }>,
397
+ };
398
+ const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0);
399
+ const todayMs = todayStart.getTime();
400
+ let durationSum = 0; let durationCount = 0;
401
+ const agentMap = new Map<string, { name: string; completed: number; active: number }>();
402
+
390
403
  for (const t of this.tasks.values()) {
391
404
  if (orgId && t.orgId !== orgId) continue;
392
405
  stats.total++;
@@ -396,7 +409,31 @@ export class TaskQueueManager {
396
409
  else if (t.status === 'completed') stats.completed++;
397
410
  else if (t.status === 'failed') stats.failed++;
398
411
  else if (t.status === 'cancelled') stats.cancelled++;
412
+
413
+ // Today metrics
414
+ const createdMs = new Date(t.createdAt).getTime();
415
+ if (createdMs >= todayMs) stats.todayCreated++;
416
+ if (t.completedAt && new Date(t.completedAt).getTime() >= todayMs) {
417
+ if (t.status === 'completed') stats.todayCompleted++;
418
+ if (t.status === 'failed') stats.todayFailed++;
419
+ }
420
+ if (t.actualDurationMs) { durationSum += t.actualDurationMs; durationCount++; }
421
+ stats.totalCost += t.costUsd || 0;
422
+ stats.totalTokens += t.tokensUsed || 0;
423
+
424
+ // Per-agent
425
+ if (t.assignedTo) {
426
+ if (!agentMap.has(t.assignedTo)) agentMap.set(t.assignedTo, { name: t.assignedToName || t.assignedTo, completed: 0, active: 0 });
427
+ const a = agentMap.get(t.assignedTo)!;
428
+ if (t.status === 'completed') a.completed++;
429
+ if (t.status === 'in_progress' || t.status === 'assigned') a.active++;
430
+ }
399
431
  }
432
+ stats.avgDurationMs = durationCount > 0 ? Math.round(durationSum / durationCount) : 0;
433
+ stats.topAgents = Array.from(agentMap.entries())
434
+ .map(([agent, d]) => ({ agent, ...d }))
435
+ .sort((a, b) => (b.completed + b.active) - (a.completed + a.active))
436
+ .slice(0, 5);
400
437
  return stats;
401
438
  }
402
439