@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
|
@@ -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
|
-
},
|
|
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() {
|
|
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
|
-
//
|
|
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
|
}
|
package/src/engine/task-queue.ts
CHANGED
|
@@ -385,8 +385,21 @@ export class TaskQueueManager {
|
|
|
385
385
|
return this.getAllTasks(orgId, limit);
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
getPipelineStats(orgId?: string): {
|
|
389
|
-
|
|
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
|
|