@agenticmail/enterprise 0.5.211 → 0.5.212

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.
@@ -2,129 +2,154 @@ import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCa
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
 
5
- // ─── Layout Constants (matches org-chart) ────────────────
6
- const NODE_W = 240;
7
- const NODE_H = 80;
8
- const H_GAP = 32;
9
- const V_GAP = 64;
10
- const PAD = 60;
11
-
12
- // ─── Colors ──────────────────────────────────────────────
13
- const STATUS_COLORS = {
14
- created: '#6366f1',
15
- assigned: '#f59e0b',
16
- in_progress: '#06b6d4',
17
- completed: '#22c55e',
18
- failed: '#ef4444',
19
- cancelled: '#6b7394',
20
- };
21
- const PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
22
- const ACCENT = '#6366f1';
23
- const EDGE_COLOR = 'rgba(255,255,255,0.25)';
24
- const EDGE_HIGHLIGHT = 'rgba(99,102,241,0.7)';
25
- const BG = '#0a0c14';
26
-
27
- // ─── Tree Layout ─────────────────────────────────────────
28
- // Groups tasks by agent → builds hierarchy: agent nodes at top, task nodes as children
29
- // Sub-tasks appear as children of their parent task
30
- function layoutPipeline(tasks, agents) {
31
- if (!tasks || !tasks.length) return { positioned: [], edges: [], width: 0, height: 0 };
32
-
33
- // Group tasks by agent
34
- var agentMap = new Map();
5
+ // ─── Constants ───────────────────────────────────────────
6
+ var NODE_W = 200;
7
+ var NODE_H = 64;
8
+ var AGENT_W = 160;
9
+ var AGENT_H = 52;
10
+ var H_GAP = 48; // horizontal gap (left→right flow)
11
+ var V_GAP = 24; // vertical gap between lanes
12
+ var PAD = 40;
13
+
14
+ var STATUS_COLORS = { created: '#6366f1', assigned: '#f59e0b', in_progress: '#06b6d4', completed: '#22c55e', failed: '#ef4444', cancelled: '#6b7394' };
15
+ var PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
16
+ var DELEGATION_COLORS = { delegation: '#6366f1', review: '#f59e0b', revision: '#f97316', escalation: '#ef4444', return: '#22c55e' };
17
+ var BG = '#0a0c14';
18
+ var EDGE_COLOR = 'rgba(255,255,255,0.18)';
19
+ var EDGE_HL = 'rgba(99,102,241,0.7)';
20
+
21
+ // ─── CSS Keyframes (injected once) ──────────────────────
22
+ var _injected = false;
23
+ function injectCSS() {
24
+ if (_injected) return; _injected = true;
25
+ var style = document.createElement('style');
26
+ style.textContent = `
27
+ @keyframes flowDash { to { stroke-dashoffset: -24; } }
28
+ @keyframes flowPulse { 0%,100% { opacity: 0.4; } 50% { opacity: 1; } }
29
+ @keyframes taskPulse { 0%,100% { box-shadow: 0 0 0 0 rgba(6,182,212,0.3); } 50% { box-shadow: 0 0 8px 2px rgba(6,182,212,0.2); } }
30
+ .tp-flow-active { animation: flowDash 1.2s linear infinite; }
31
+ .tp-node-active { animation: taskPulse 2s ease-in-out infinite; }
32
+ .tp-node:hover { transform: scale(1.03); z-index: 10; }
33
+ .tp-customer-badge { background: linear-gradient(135deg, #6366f1, #a855f7); color: #fff; font-size: 9px; padding: 1px 6px; border-radius: 8px; font-weight: 600; }
34
+ .tp-chain-tag { font-size: 9px; padding: 1px 5px; border-radius: 4px; font-weight: 600; letter-spacing: 0.02em; }
35
+ `;
36
+ document.head.appendChild(style);
37
+ }
38
+
39
+ // ─── Layout: Left-to-Right Chain Flow ────────────────────
40
+ // Tasks flow horizontally. Each chain = a horizontal row.
41
+ // Multiple chains stack vertically. Circular flows curve back.
42
+
43
+ function layoutChains(tasks) {
44
+ if (!tasks.length) return { nodes: [], edges: [], width: 0, height: 0, chains: [] };
45
+
46
+ // Group by chainId
47
+ var chainMap = new Map();
48
+ var orphans = [];
35
49
  tasks.forEach(function(t) {
50
+ if (t.chainId) {
51
+ if (!chainMap.has(t.chainId)) chainMap.set(t.chainId, []);
52
+ chainMap.get(t.chainId).push(t);
53
+ } else {
54
+ orphans.push(t);
55
+ }
56
+ });
57
+
58
+ // Sort each chain by chainSeq
59
+ chainMap.forEach(function(arr) { arr.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); }); });
60
+
61
+ // Also group orphans by agent for a simpler layout
62
+ var orphansByAgent = new Map();
63
+ orphans.forEach(function(t) {
36
64
  var key = t.assignedTo || 'unassigned';
37
- if (!agentMap.has(key)) agentMap.set(key, { agentId: key, name: t.assignedToName || key, tasks: [], children: [], x: 0, y: 0, subtreeW: 0, isAgent: true });
38
- agentMap.get(key).tasks.push(t);
65
+ if (!orphansByAgent.has(key)) orphansByAgent.set(key, []);
66
+ orphansByAgent.get(key).push(t);
39
67
  });
40
68
 
41
- // Build task nodes under each agent
42
69
  var allNodes = [];
43
- var edgeList = [];
44
- var agentRoots = [];
45
-
46
- agentMap.forEach(function(agent) {
47
- agent.subtreeW = 0;
48
- var taskNodes = [];
49
- var taskById = new Map();
50
-
51
- // Create task nodes
52
- agent.tasks.forEach(function(t) {
53
- var node = { id: t.id, task: t, children: [], x: 0, y: 0, subtreeW: 0, isAgent: false, agentId: agent.agentId };
54
- taskById.set(t.id, node);
55
- taskNodes.push(node);
70
+ var allEdges = [];
71
+ var chainInfos = [];
72
+ var y = PAD;
73
+
74
+ // Layout each chain as a horizontal row
75
+ chainMap.forEach(function(chainTasks, chainId) {
76
+ var x = PAD;
77
+ var rowNodes = [];
78
+ var maxH = NODE_H;
79
+
80
+ chainTasks.forEach(function(t, i) {
81
+ var node = { id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: chainId };
82
+ rowNodes.push(node);
83
+ allNodes.push(node);
84
+
85
+ if (i > 0) {
86
+ var prev = rowNodes[i - 1];
87
+ var dtype = t.delegationType || 'delegation';
88
+ var isReturn = dtype === 'return' || dtype === 'revision';
89
+ // Check for circular: does this task go back to an agent already seen?
90
+ var seenAgents = chainTasks.slice(0, i).map(function(ct) { return ct.assignedTo; });
91
+ var isCircular = seenAgents.indexOf(t.assignedTo) !== -1 && isReturn;
92
+
93
+ allEdges.push({
94
+ from: prev, to: node,
95
+ delegationType: dtype,
96
+ isCircular: isCircular,
97
+ isActive: prev.task.status === 'in_progress' || t.status === 'in_progress',
98
+ });
99
+ }
100
+
101
+ x += NODE_W + H_GAP;
56
102
  });
57
103
 
58
- // Link sub-tasks to parents
59
- var rootTasks = [];
60
- taskNodes.forEach(function(tn) {
61
- if (tn.task.parentTaskId && taskById.has(tn.task.parentTaskId)) {
62
- taskById.get(tn.task.parentTaskId).children.push(tn);
63
- } else {
64
- rootTasks.push(tn);
65
- }
104
+ // Customer context badge on first node
105
+ var firstTask = chainTasks[0];
106
+ chainInfos.push({
107
+ chainId: chainId,
108
+ y: y,
109
+ taskCount: chainTasks.length,
110
+ customer: firstTask.customerContext,
111
+ status: chainTasks[chainTasks.length - 1].status,
112
+ title: firstTask.title,
66
113
  });
67
114
 
68
- agent.children = rootTasks;
69
- agentRoots.push(agent);
115
+ y += maxH + V_GAP + 16; // extra space between chains
70
116
  });
71
117
 
72
- // Compute subtree widths
73
- function computeW(node) {
74
- var w = node.isAgent ? NODE_W : NODE_W;
75
- if (node.children.length === 0) { node.subtreeW = w; return w; }
76
- var total = 0;
77
- node.children.forEach(function(c) { total += computeW(c); });
78
- total += (node.children.length - 1) * H_GAP;
79
- node.subtreeW = Math.max(w, total);
80
- return node.subtreeW;
81
- }
82
-
83
- // Assign positions
84
- function assignPos(node, x, y) {
85
- var w = node.isAgent ? NODE_W : NODE_W;
86
- node.x = x + node.subtreeW / 2 - w / 2;
87
- node.y = y;
88
- if (node.children.length === 0) return;
89
- var childrenW = node.children.reduce(function(s, c) { return s + c.subtreeW; }, 0) + (node.children.length - 1) * H_GAP;
90
- var cx = node.x + w / 2 - childrenW / 2;
91
- node.children.forEach(function(c) {
92
- assignPos(c, cx, y + NODE_H + V_GAP);
93
- cx += c.subtreeW + H_GAP;
118
+ // Layout orphans as simple rows per agent
119
+ orphansByAgent.forEach(function(agentTasks, agentId) {
120
+ var x = PAD;
121
+ agentTasks.forEach(function(t) {
122
+ allNodes.push({ id: t.id, task: t, x: x, y: y, w: NODE_W, h: NODE_H, isAgent: false, chainId: null });
123
+ x += NODE_W + H_GAP;
94
124
  });
95
- }
96
-
97
- // Layout agent roots side by side
98
- agentRoots.forEach(function(r) { computeW(r); });
99
- var cx = PAD;
100
- agentRoots.forEach(function(r) {
101
- assignPos(r, cx, PAD);
102
- cx += r.subtreeW + H_GAP * 2;
125
+ y += NODE_H + V_GAP;
103
126
  });
104
127
 
105
- // Flatten + collect edges
106
- var maxX = 0, maxY = 0;
107
- function flatten(node, parent) {
108
- allNodes.push(node);
109
- var w = NODE_W;
110
- maxX = Math.max(maxX, node.x + w);
111
- maxY = Math.max(maxY, node.y + NODE_H);
112
- if (parent) edgeList.push({ parent: parent, child: node });
113
- node.children.forEach(function(c) { flatten(c, node); });
114
- }
115
- agentRoots.forEach(function(r) { flatten(r, null); });
128
+ var maxX = 0;
129
+ allNodes.forEach(function(n) { maxX = Math.max(maxX, n.x + n.w); });
116
130
 
117
- return { positioned: allNodes, edges: edgeList, width: maxX + PAD, height: maxY + PAD + 40 };
131
+ return { nodes: allNodes, edges: allEdges, width: maxX + PAD, height: y + PAD, chains: chainInfos };
118
132
  }
119
133
 
120
- // ─── SVG Edge (curved, matches org-chart) ────────────────
121
- function edgePath(parent, child) {
122
- var x1 = parent.x + NODE_W / 2;
123
- var y1 = parent.y + NODE_H;
124
- var x2 = child.x + NODE_W / 2;
125
- var y2 = child.y;
126
- var midY = y1 + (y2 - y1) * 0.5;
127
- return 'M ' + x1 + ' ' + y1 + ' C ' + x1 + ' ' + midY + ', ' + x2 + ' ' + midY + ', ' + x2 + ' ' + y2;
134
+ // ─── SVG Edge Paths ──────────────────────────────────────
135
+ function horizontalPath(from, to) {
136
+ var x1 = from.x + from.w;
137
+ var y1 = from.y + from.h / 2;
138
+ var x2 = to.x;
139
+ var y2 = to.y + to.h / 2;
140
+ var midX = x1 + (x2 - x1) * 0.5;
141
+ return 'M ' + x1 + ' ' + y1 + ' C ' + midX + ' ' + y1 + ', ' + midX + ' ' + y2 + ', ' + x2 + ' ' + y2;
142
+ }
143
+
144
+ function circularPath(from, to) {
145
+ // Arc back: goes up and curves back left
146
+ var x1 = from.x + from.w;
147
+ var y1 = from.y + from.h / 2;
148
+ var x2 = to.x;
149
+ var y2 = to.y + to.h / 2;
150
+ var lift = 40;
151
+ var topY = Math.min(y1, y2) - lift;
152
+ return 'M ' + x1 + ' ' + y1 + ' C ' + (x1 + 50) + ' ' + topY + ', ' + (x2 - 50) + ' ' + topY + ', ' + x2 + ' ' + y2;
128
153
  }
129
154
 
130
155
  // ─── Helpers ─────────────────────────────────────────────
@@ -137,7 +162,6 @@ function timeAgo(ts) {
137
162
  if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
138
163
  return Math.floor(diff / 86400000) + 'd ago';
139
164
  }
140
-
141
165
  function formatDuration(ms) {
142
166
  if (!ms) return '-';
143
167
  var s = Math.floor(ms / 1000);
@@ -146,86 +170,136 @@ function formatDuration(ms) {
146
170
  if (m < 60) return m + 'm ' + (s % 60) + 's';
147
171
  return Math.floor(m / 60) + 'h ' + (m % 60) + 'm';
148
172
  }
149
-
150
- var CATEGORY_ICONS = { email: '\u2709', research: '\uD83D\uDD0D', meeting: '\uD83D\uDCC5', workflow: '\u2699', writing: '\u270F', deployment: '\uD83D\uDE80', review: '\u2714', monitoring: '\uD83D\uDCE1', custom: '\uD83D\uDCCB' };
151
-
152
- function tagStyle(color) {
153
- return { fontSize: 9, fontWeight: 600, padding: '1px 5px', borderRadius: 4, background: color + '22', color: color, letterSpacing: '0.02em' };
154
- }
173
+ function tag(color, text) { return h('span', { className: 'tp-chain-tag', style: { background: color + '22', color: color } }, text); }
155
174
 
156
175
  var toolbarBtnStyle = {
157
- background: 'rgba(255,255,255,0.08)',
158
- border: '1px solid rgba(255,255,255,0.12)',
159
- borderRadius: 6,
160
- color: '#fff',
161
- fontSize: 14,
162
- fontWeight: 600,
163
- padding: '4px 8px',
164
- cursor: 'pointer',
165
- lineHeight: '1.2',
176
+ background: 'rgba(255,255,255,0.08)', border: '1px solid rgba(255,255,255,0.12)',
177
+ borderRadius: 6, color: '#fff', fontSize: 12, fontWeight: 600, padding: '4px 10px', cursor: 'pointer',
166
178
  };
167
-
168
179
  function legendDot(color, label) {
169
180
  return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
170
181
  h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: color } }),
171
- h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 12 } }, label)
172
- );
173
- }
174
-
175
- function tooltipRow(label, value, color) {
176
- return h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: 11 } },
177
- h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, label),
178
- h('span', { style: { fontWeight: 600, color: color || '#fff' } }, value)
182
+ h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11 } }, label)
179
183
  );
180
184
  }
181
-
182
- // ─── Help tooltip styles ─────────────────────────────────
183
185
  var _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
184
186
  var _ul = { paddingLeft: 20, margin: '4px 0 8px' };
185
187
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
186
188
 
189
+ // ─── Customer Profile Mini-Card ──────────────────────────
190
+ function CustomerBadge(props) {
191
+ var c = props.customer;
192
+ if (!c) return null;
193
+ return h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px', background: 'rgba(99,102,241,0.08)', border: '1px solid rgba(99,102,241,0.2)', borderRadius: 8, fontSize: 11, marginBottom: 4 } },
194
+ h('div', { style: { width: 20, height: 20, borderRadius: '50%', background: 'linear-gradient(135deg, #6366f1, #a855f7)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 9, fontWeight: 700, flexShrink: 0 } }, (c.name || '?').charAt(0).toUpperCase()),
195
+ h('div', { style: { overflow: 'hidden' } },
196
+ h('div', { style: { fontWeight: 600, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, c.name || 'Unknown'),
197
+ h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 9 } },
198
+ c.isNew ? 'New customer' : (c.company || c.email || c.channel || '')
199
+ )
200
+ )
201
+ );
202
+ }
203
+
187
204
  // ─── Task Detail Modal ───────────────────────────────────
188
205
  function TaskDetail(props) {
189
206
  var task = props.task;
207
+ var chain = props.chain;
190
208
  var onClose = props.onClose;
191
209
  var onCancel = props.onCancel;
192
210
  if (!task) return null;
193
211
  var statusColor = STATUS_COLORS[task.status] || '#6b7394';
194
212
 
195
213
  return h('div', { className: 'modal-overlay', onClick: onClose },
196
- h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 560, maxHeight: '80vh', overflow: 'auto' } },
214
+ h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 640, maxHeight: '85vh', overflow: 'auto' } },
197
215
  h('div', { className: 'modal-header' },
198
- h('h2', null, task.title),
216
+ h('h2', { style: { fontSize: 16 } }, task.title),
199
217
  h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
200
218
  ),
201
219
  h('div', { className: 'modal-body', style: { padding: 20 } },
202
- h('div', { style: { display: 'flex', gap: 8, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' } },
203
- h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } }, task.status.replace('_', ' ').toUpperCase()),
204
- h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, task.priority.toUpperCase()),
205
- h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: 'var(--bg-tertiary)', color: 'var(--text-muted)' } }, (CATEGORY_ICONS[task.category] || '') + ' ' + task.category)
220
+ // Status badges
221
+ h('div', { style: { display: 'flex', gap: 6, marginBottom: 16, alignItems: 'center', flexWrap: 'wrap' } },
222
+ h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } }, task.status.replace('_', ' ').toUpperCase()),
223
+ h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } }, task.priority.toUpperCase()),
224
+ task.chainId && h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 11, background: 'rgba(99,102,241,0.1)', color: '#6366f1', fontFamily: 'var(--font-mono)' } }, 'Chain #' + task.chainId.slice(0, 8)),
225
+ task.delegationType && tag(DELEGATION_COLORS[task.delegationType] || '#6b7394', task.delegationType)
206
226
  ),
227
+
228
+ // Customer context
229
+ task.customerContext && h('div', { style: { padding: 12, background: 'rgba(99,102,241,0.06)', border: '1px solid rgba(99,102,241,0.15)', borderRadius: 'var(--radius)', marginBottom: 16 } },
230
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.5)', marginBottom: 8 } }, 'CUSTOMER'),
231
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px 16px', fontSize: 13 } },
232
+ task.customerContext.name && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Name'), h('div', null, task.customerContext.name)),
233
+ task.customerContext.email && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Email'), h('div', null, task.customerContext.email)),
234
+ task.customerContext.company && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Company'), h('div', null, task.customerContext.company)),
235
+ task.customerContext.channel && h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Channel'), h('div', null, task.customerContext.channel)),
236
+ h(Fragment, null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11 } }, 'Type'), h('div', null, task.customerContext.isNew ? 'New Customer' : 'Returning'))
237
+ )
238
+ ),
239
+
207
240
  task.description && h('div', { style: { marginBottom: 16, fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)' } }, task.description),
241
+
242
+ // Progress
208
243
  task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
209
244
  h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } }, h('span', null, 'Progress'), h('span', null, task.progress + '%')),
210
245
  h('div', { style: { height: 6, background: 'var(--border)', borderRadius: 3, overflow: 'hidden' } },
211
- h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3 } })
246
+ h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3, transition: 'width 0.3s' } })
212
247
  )
213
248
  ),
214
- h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 24px', fontSize: 13, marginBottom: 16 } },
249
+
250
+ // Grid details
251
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 24px', fontSize: 13, marginBottom: 16 } },
215
252
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
216
253
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
217
254
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created'), h('div', null, task.createdAt ? new Date(task.createdAt).toLocaleString() : '-')),
218
- h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Started'), h('div', null, task.startedAt ? new Date(task.startedAt).toLocaleString() : '-')),
219
- h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Completed'), h('div', null, task.completedAt ? new Date(task.completedAt).toLocaleString() : '-')),
220
255
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Duration'), h('div', null, formatDuration(task.actualDurationMs))),
221
256
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Model'), h('div', null, task.modelUsed || task.model || '-')),
222
257
  h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Tokens / Cost'), h('div', null, (task.tokensUsed || 0).toLocaleString() + ' / $' + (task.costUsd || 0).toFixed(4)))
223
258
  ),
224
- task.error && h('div', { style: { padding: 12, background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 'var(--radius)', marginBottom: 16, fontSize: 13, color: '#ef4444' } }, h('strong', null, 'Error: '), task.error),
225
- task.result && h('div', { style: { marginBottom: 16 } },
226
- h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Result'),
227
- h('pre', { style: { fontSize: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 200 } }, JSON.stringify(task.result, null, 2))
259
+
260
+ // Task chain timeline (if part of a chain)
261
+ chain && chain.length > 1 && h('div', { style: { marginBottom: 16 } },
262
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'DELEGATION CHAIN'),
263
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 0, overflow: 'auto', padding: '8px 0' } },
264
+ chain.map(function(ct, i) {
265
+ var isMe = ct.id === task.id;
266
+ var sc = STATUS_COLORS[ct.status] || '#6b7394';
267
+ return h(Fragment, { key: ct.id },
268
+ i > 0 && h('div', { style: { display: 'flex', alignItems: 'center', flexShrink: 0 } },
269
+ h('div', { style: { width: 32, height: 2, background: (DELEGATION_COLORS[ct.delegationType] || '#6366f1') + '66' } }),
270
+ h('div', { style: { fontSize: 8, color: 'rgba(255,255,255,0.4)', position: 'relative', top: -8 } }, ct.delegationType || '')
271
+ ),
272
+ h('div', { style: {
273
+ padding: '6px 10px', borderRadius: 8, fontSize: 11, flexShrink: 0,
274
+ background: isMe ? sc + '22' : 'rgba(255,255,255,0.03)',
275
+ border: '1px solid ' + (isMe ? sc : 'rgba(255,255,255,0.1)'),
276
+ fontWeight: isMe ? 700 : 400, color: isMe ? sc : 'rgba(255,255,255,0.6)',
277
+ } },
278
+ h('div', { style: { fontWeight: 600 } }, ct.assignedToName || ct.assignedTo),
279
+ h('div', { style: { fontSize: 9, marginTop: 2, opacity: 0.6 } }, ct.status.replace('_', ' '))
280
+ )
281
+ );
282
+ })
283
+ )
284
+ ),
285
+
286
+ // Activity log
287
+ task.activityLog && task.activityLog.length > 0 && h('div', { style: { marginBottom: 16 } },
288
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: 'var(--text-muted)', marginBottom: 8 } }, 'ACTIVITY LOG'),
289
+ h('div', { style: { maxHeight: 180, overflow: 'auto', border: '1px solid var(--border)', borderRadius: 'var(--radius)' } },
290
+ task.activityLog.map(function(entry, i) {
291
+ return h('div', { key: i, style: { display: 'flex', gap: 8, padding: '6px 10px', borderBottom: '1px solid var(--border)', fontSize: 11 } },
292
+ h('span', { style: { color: 'var(--text-muted)', flexShrink: 0, fontFamily: 'var(--font-mono)', fontSize: 10 } }, entry.ts ? new Date(entry.ts).toLocaleTimeString() : ''),
293
+ h('span', { style: { fontWeight: 600, flexShrink: 0, minWidth: 60 } }, entry.type),
294
+ h('span', { style: { color: 'var(--text-secondary)' } }, entry.detail)
295
+ );
296
+ })
297
+ )
228
298
  ),
299
+
300
+ task.error && h('div', { style: { padding: 12, background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)', borderRadius: 'var(--radius)', marginBottom: 16, fontSize: 13, color: '#ef4444' } }, h('strong', null, 'Error: '), task.error),
301
+
302
+ // Actions
229
303
  (task.status === 'created' || task.status === 'assigned' || task.status === 'in_progress') && h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 16, borderTop: '1px solid var(--border)', paddingTop: 16 } },
230
304
  h('button', { className: 'btn btn-danger btn-sm', onClick: function() { onCancel(task.id); } }, 'Cancel Task')
231
305
  )
@@ -234,22 +308,147 @@ function TaskDetail(props) {
234
308
  );
235
309
  }
236
310
 
311
+ // ─── Metrics Bar ─────────────────────────────────────────
312
+ function MetricsBar(props) {
313
+ var s = props.stats;
314
+ function chip(label, value, color) {
315
+ return h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, padding: '3px 8px', background: 'rgba(255,255,255,0.04)', borderRadius: 6 } },
316
+ h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.35)' } }, label),
317
+ h('span', { style: { fontSize: 11, fontWeight: 700, color: color } }, value)
318
+ );
319
+ }
320
+ var hasActivity = s.total > 0;
321
+
322
+ return h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '6px 16px', borderBottom: '1px solid rgba(255,255,255,0.06)', background: 'rgba(0,0,0,0.12)', flexShrink: 0, overflowX: 'auto', fontSize: 11 } },
323
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.25)', fontWeight: 600, letterSpacing: '0.06em', marginRight: 2 } }, 'TODAY'),
324
+ chip('Done', s.todayCompleted || 0, '#22c55e'),
325
+ chip('Active', s.inProgress || 0, '#06b6d4'),
326
+ chip('New', s.todayCreated || 0, '#f59e0b'),
327
+ s.todayFailed > 0 && chip('Failed', s.todayFailed, '#ef4444'),
328
+ hasActivity && h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.08)' } }),
329
+ hasActivity && h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.25)', fontWeight: 600, letterSpacing: '0.06em' } }, 'ALL'),
330
+ hasActivity && chip('Total', s.total, 'rgba(255,255,255,0.6)'),
331
+ s.avgDurationMs > 0 && chip('Avg', formatDuration(s.avgDurationMs), '#fff'),
332
+ s.totalTokens > 0 && chip('Tokens', s.totalTokens > 999999 ? (s.totalTokens / 1000000).toFixed(1) + 'M' : s.totalTokens > 999 ? (s.totalTokens / 1000).toFixed(1) + 'K' : s.totalTokens, '#a855f7'),
333
+ s.totalCost > 0 && chip('Cost', '$' + s.totalCost.toFixed(2), '#22c55e'),
334
+ s.topAgents && s.topAgents.length > 0 && h(Fragment, null,
335
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.08)' } }),
336
+ s.topAgents.slice(0, 3).map(function(a) {
337
+ return h('div', { key: a.agent, style: { display: 'flex', alignItems: 'center', gap: 3, padding: '2px 6px', background: 'rgba(255,255,255,0.04)', borderRadius: 6 } },
338
+ h('div', { style: { width: 12, height: 12, borderRadius: '50%', background: '#6366f133', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 7, fontWeight: 700, color: '#6366f1' } }, (a.name || '?').charAt(0).toUpperCase()),
339
+ h('span', { style: { fontSize: 10, color: '#fff', fontWeight: 600 } }, a.name),
340
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)' } }, a.completed + '/' + a.active)
341
+ );
342
+ })
343
+ )
344
+ );
345
+ }
346
+
347
+ // ─── Inline Chain Flowchart (appears below canvas when task expanded) ─
348
+ function ChainFlowInline(props) {
349
+ var chain = props.chain;
350
+ var taskId = props.taskId;
351
+ var onClose = props.onClose;
352
+ var onClickTask = props.onClickTask;
353
+ if (!chain || chain.length === 0) return null;
354
+
355
+ var MINI_W = 180;
356
+ var MINI_H = 56;
357
+ var MINI_GAP = 40;
358
+ var totalW = chain.length * MINI_W + (chain.length - 1) * MINI_GAP + 40;
359
+
360
+ 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' } },
361
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 } },
362
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
363
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.06em' } }, 'TASK FLOW'),
364
+ 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) : ''),
365
+ h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.3)' } }, chain.length + ' step' + (chain.length > 1 ? 's' : ''))
366
+ ),
367
+ h('button', { onClick: onClose, style: { background: 'none', border: 'none', color: 'rgba(255,255,255,0.3)', cursor: 'pointer', fontSize: 16, padding: '0 4px' } }, '\u00D7')
368
+ ),
369
+ h('div', { style: { position: 'relative', height: MINI_H + 20, minWidth: totalW } },
370
+ // SVG arrows
371
+ h('svg', { width: totalW, height: MINI_H + 20, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
372
+ h('defs', null,
373
+ h('marker', { id: 'cf-arr', markerWidth: 6, markerHeight: 4, refX: 6, refY: 2, orient: 'auto' },
374
+ h('polygon', { points: '0 0, 6 2, 0 4', fill: 'rgba(99,102,241,0.5)' })
375
+ )
376
+ ),
377
+ chain.map(function(ct, i) {
378
+ if (i === 0) return null;
379
+ var x1 = 20 + (i - 1) * (MINI_W + MINI_GAP) + MINI_W;
380
+ var x2 = 20 + i * (MINI_W + MINI_GAP);
381
+ var y = 10 + MINI_H / 2;
382
+ var dType = ct.delegationType || 'delegation';
383
+ var color = DELEGATION_COLORS[dType] || 'rgba(99,102,241,0.5)';
384
+ var isActive = chain[i - 1].status === 'in_progress' || ct.status === 'in_progress';
385
+ return h(Fragment, { key: 'e' + i },
386
+ h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: color + '88', strokeWidth: 2, markerEnd: 'url(#cf-arr)' }),
387
+ 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 } }),
388
+ dType !== 'delegation' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: color, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, dType)
389
+ );
390
+ })
391
+ ),
392
+ // Task nodes
393
+ chain.map(function(ct, i) {
394
+ var x = 20 + i * (MINI_W + MINI_GAP);
395
+ var sc = STATUS_COLORS[ct.status] || '#6b7394';
396
+ var isMe = ct.id === taskId;
397
+ var isActive = ct.status === 'in_progress';
398
+ return h('div', {
399
+ key: ct.id,
400
+ onClick: function() { if (onClickTask) onClickTask(ct); },
401
+ style: {
402
+ position: 'absolute', left: x, top: 10, width: MINI_W, height: MINI_H,
403
+ background: isMe ? sc + '15' : 'rgba(255,255,255,0.02)',
404
+ border: '1.5px solid ' + (isMe ? sc : 'rgba(255,255,255,0.1)'),
405
+ borderLeft: '3px solid ' + sc,
406
+ borderRadius: 8, padding: '6px 10px', cursor: 'pointer',
407
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3,
408
+ transition: 'all 0.15s',
409
+ },
410
+ onMouseEnter: function(e) { e.currentTarget.style.background = 'rgba(255,255,255,0.05)'; },
411
+ onMouseLeave: function(e) { e.currentTarget.style.background = isMe ? sc + '15' : 'rgba(255,255,255,0.02)'; },
412
+ },
413
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
414
+ 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 } },
415
+ (ct.assignedToName || '?').charAt(0).toUpperCase()
416
+ ),
417
+ h('span', { style: { fontSize: 10, fontWeight: 600, color: '#fff', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 } }, ct.assignedToName || ct.assignedTo)
418
+ ),
419
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
420
+ tag(sc, ct.status.replace('_', ' ')),
421
+ h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)' } }, timeAgo(ct.createdAt)),
422
+ ct.actualDurationMs && h('span', { style: { fontSize: 8, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, formatDuration(ct.actualDurationMs))
423
+ ),
424
+ isActive && ct.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden' } },
425
+ h('div', { style: { height: '100%', width: ct.progress + '%', background: sc, borderRadius: 1 } })
426
+ )
427
+ );
428
+ })
429
+ )
430
+ );
431
+ }
432
+
237
433
  // ─── Main Page ───────────────────────────────────────────
238
434
  export function TaskPipelinePage() {
435
+ injectCSS();
239
436
  var app = useApp();
240
437
  var toast = app.toast;
241
438
  var _tasks = useState([]);
242
439
  var tasks = _tasks[0]; var setTasks = _tasks[1];
243
- var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0 });
440
+ 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: [] });
244
441
  var stats = _stats[0]; var setStats = _stats[1];
442
+ var _expandedTaskId = useState(null);
443
+ var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
245
444
  var _loading = useState(true);
246
445
  var loading = _loading[0]; var setLoading = _loading[1];
247
446
  var _selectedTask = useState(null);
248
447
  var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
448
+ var _selectedChain = useState(null);
449
+ var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
249
450
  var _hoveredId = useState(null);
250
451
  var hoveredId = _hoveredId[0]; var setHoveredId = _hoveredId[1];
251
- var _mousePos = useState({ x: 0, y: 0 });
252
- var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
253
452
  var _zoom = useState(1);
254
453
  var zoom = _zoom[0]; var setZoom = _zoom[1];
255
454
  var _pan = useState({ x: 0, y: 0 });
@@ -260,6 +459,8 @@ export function TaskPipelinePage() {
260
459
  var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
261
460
  var _filter = useState('active');
262
461
  var filter = _filter[0]; var setFilter = _filter[1];
462
+ var _mousePos = useState({ x: 0, y: 0 });
463
+ var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
263
464
  var containerRef = useRef(null);
264
465
 
265
466
  var loadData = useCallback(function() {
@@ -270,9 +471,8 @@ export function TaskPipelinePage() {
270
471
  ]).then(function(res) {
271
472
  setTasks(res[0]?.tasks || []);
272
473
  setStats(res[1] || stats);
273
- }).catch(function(err) {
274
- console.error('[TaskPipeline] load:', err);
275
- }).finally(function() { setLoading(false); });
474
+ }).catch(function(err) { console.error('[TaskPipeline]', err); })
475
+ .finally(function() { setLoading(false); });
276
476
  }, []);
277
477
 
278
478
  // SSE
@@ -299,10 +499,9 @@ export function TaskPipelinePage() {
299
499
  if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
300
500
  return [event.task].concat(prev);
301
501
  });
302
- setSelectedTask(function(prev) {
303
- if (prev && prev.id === event.task.id) return event.task;
304
- return prev;
305
- });
502
+ setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
503
+ // Refresh stats on every task event for real-time metrics
504
+ engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
306
505
  }
307
506
  } catch (err) {}
308
507
  };
@@ -310,11 +509,10 @@ export function TaskPipelinePage() {
310
509
  return function() { if (es) es.close(); };
311
510
  }, []);
312
511
 
313
- // Periodic stats
314
512
  useEffect(function() {
315
513
  var iv = setInterval(function() {
316
514
  engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
317
- }, 30000);
515
+ }, 15000);
318
516
  return function() { clearInterval(iv); };
319
517
  }, []);
320
518
 
@@ -326,6 +524,34 @@ export function TaskPipelinePage() {
326
524
  }).catch(function(err) { toast(err.message || 'Failed', 'error'); });
327
525
  }, []);
328
526
 
527
+ function openTaskDetail(t) {
528
+ setSelectedTask(t);
529
+ if (t.chainId) {
530
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
531
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
532
+ setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
533
+ } else {
534
+ setSelectedChain(null);
535
+ }
536
+ }
537
+
538
+ // Toggle inline chain flowchart (single click on node)
539
+ function toggleExpand(t) {
540
+ if (expandedTaskId === t.id) {
541
+ setExpandedTaskId(null);
542
+ setSelectedChain(null);
543
+ } else {
544
+ setExpandedTaskId(t.id);
545
+ if (t.chainId) {
546
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
547
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
548
+ setSelectedChain(chainTasks.length > 0 ? chainTasks : [t]);
549
+ } else {
550
+ setSelectedChain([t]);
551
+ }
552
+ }
553
+ }
554
+
329
555
  // Filter
330
556
  var filtered = tasks.filter(function(t) {
331
557
  if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
@@ -335,34 +561,28 @@ export function TaskPipelinePage() {
335
561
  });
336
562
 
337
563
  // Layout
338
- var layout = layoutPipeline(filtered);
339
- var positioned = layout.positioned;
564
+ var layout = layoutChains(filtered);
565
+ var nodes = layout.nodes;
340
566
  var edges = layout.edges;
341
567
  var treeW = layout.width;
342
568
  var treeH = layout.height;
569
+ var chainInfos = layout.chains;
343
570
 
344
- // Zoom
571
+ // Zoom/Pan handlers
345
572
  var handleWheel = useCallback(function(e) {
346
573
  e.preventDefault();
347
- var delta = e.deltaY > 0 ? -0.08 : 0.08;
348
- setZoom(function(z) { return Math.min(3, Math.max(0.15, z + delta)); });
574
+ setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
349
575
  }, []);
350
-
351
- // Pan
352
576
  var handleMouseDown = useCallback(function(e) {
353
- if (e.button !== 0) return;
354
- if (e.target.closest('.task-node')) return;
577
+ if (e.button !== 0 || e.target.closest('.tp-node')) return;
355
578
  setDragging(true);
356
579
  setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
357
580
  }, [pan]);
358
-
359
581
  var handleMouseMove = useCallback(function(e) {
360
582
  if (!dragging) return;
361
583
  setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
362
584
  }, [dragging, dragStart]);
363
-
364
585
  var handleMouseUp = useCallback(function() { setDragging(false); }, []);
365
-
366
586
  useEffect(function() {
367
587
  if (dragging) {
368
588
  window.addEventListener('mousemove', handleMouseMove);
@@ -371,7 +591,6 @@ export function TaskPipelinePage() {
371
591
  }
372
592
  }, [dragging, handleMouseMove, handleMouseUp]);
373
593
 
374
- // Fit
375
594
  var fitToView = useCallback(function() {
376
595
  if (!containerRef.current || !treeW || !treeH) return;
377
596
  var rect = containerRef.current.getBoundingClientRect();
@@ -379,128 +598,96 @@ export function TaskPipelinePage() {
379
598
  var scaleY = (rect.height - 40) / treeH;
380
599
  var scale = Math.min(scaleX, scaleY, 1.5);
381
600
  setZoom(scale);
382
- setPan({ x: (rect.width - treeW * scale) / 2, y: (rect.height - treeH * scale) / 2 });
601
+ setPan({ x: (rect.width - treeW * scale) / 2, y: 20 });
383
602
  }, [treeW, treeH]);
603
+ useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
384
604
 
385
- useEffect(function() { if (positioned.length > 0) fitToView(); }, [positioned.length]);
386
-
387
- // Connected highlight
388
- var getConnected = useCallback(function(id) {
389
- var connected = new Set([id]);
390
- var byId = new Map();
391
- positioned.forEach(function(n) { byId.set(n.id || n.agentId, n); });
392
- // Children
393
- function addDesc(node) {
394
- (node.children || []).forEach(function(c) { connected.add(c.id || c.agentId); addDesc(c); });
395
- }
396
- var node = byId.get(id);
397
- if (node) addDesc(node);
398
- // Parent edges
399
- edges.forEach(function(e) {
400
- var cId = e.child.id || e.child.agentId;
401
- var pId = e.parent.id || e.parent.agentId;
402
- if (cId === id) connected.add(pId);
403
- });
404
- return connected;
405
- }, [positioned, edges]);
406
-
407
- var connected = hoveredId ? getConnected(hoveredId) : null;
605
+ // Highlight connected chain on hover
606
+ var hoveredChainId = null;
607
+ if (hoveredId) {
608
+ var hn = nodes.find(function(n) { return n.id === hoveredId; });
609
+ if (hn) hoveredChainId = hn.chainId;
610
+ }
408
611
 
409
- // Hovered node for tooltip
410
- var hoveredNode = hoveredId ? positioned.find(function(n) { return (n.id || n.agentId) === hoveredId; }) : null;
612
+ var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
613
+
614
+ // ─── Toolbar ─────────────────────────────────────────
615
+ var toolbar = h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)', flexShrink: 0, flexWrap: 'wrap' } },
616
+ h('div', { style: { fontWeight: 700, fontSize: 14, color: '#fff', display: 'flex', alignItems: 'center', gap: 6 } },
617
+ I.workflow(), 'Task Pipeline',
618
+ h(HelpButton, { label: 'Task Pipeline' },
619
+ h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
620
+ h('h4', { style: _h4 }, 'Features'),
621
+ h('ul', { style: _ul },
622
+ h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
623
+ h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
624
+ h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
625
+ h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
626
+ h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
627
+ h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
628
+ ),
629
+ h('h4', { style: _h4 }, 'Interactions'),
630
+ h('ul', { style: _ul },
631
+ h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
632
+ h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
633
+ h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
634
+ h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
635
+ ),
636
+ h('div', { style: _tip }, h('strong', null, 'Tip: '), 'Each task has a unique chain ID linking all delegation steps. Even circular review loops (agent A \u2192 B \u2192 A) are tracked.')
637
+ )
638
+ ),
639
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
640
+ h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'flowPulse 2s infinite' } }),
641
+ h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } }, 'Live')
642
+ ),
643
+ h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } },
644
+ (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
645
+ ),
646
+ h('div', { style: { flex: 1 } }),
647
+ // Filters
648
+ ['active', 'all', 'completed', 'failed'].map(function(f) {
649
+ return h('button', { key: f, onClick: function() { setFilter(f); }, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, background: filter === f ? 'rgba(99,102,241,0.3)' : toolbarBtnStyle.background }) }, f.charAt(0).toUpperCase() + f.slice(1));
650
+ }),
651
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
652
+ legendDot(STATUS_COLORS.in_progress, 'Active'),
653
+ legendDot(STATUS_COLORS.completed, 'Done'),
654
+ legendDot(STATUS_COLORS.failed, 'Failed'),
655
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
656
+ h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
657
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11, minWidth: 36, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
658
+ h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
659
+ h('button', { onClick: fitToView, style: toolbarBtnStyle }, 'Fit'),
660
+ h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
661
+ );
411
662
 
412
663
  if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
413
664
 
414
- if (positioned.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
415
- // Toolbar even when empty
416
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '12px 20px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)' } },
417
- h('div', { style: { fontWeight: 700, fontSize: 16, color: '#fff', display: 'flex', alignItems: 'center', gap: 8 } }, I.workflow(), ' Task Pipeline',
418
- h(HelpButton, { label: 'Task Pipeline' },
419
- h('p', null, 'Visual hierarchy of all agent tasks in your organization. Tasks flow from agents (top) to individual tasks and sub-tasks (below), connected by arrows.'),
420
- h('h4', { style: _h4 }, 'How It Works'),
421
- h('ul', { style: _ul },
422
- h('li', null, 'Tasks are automatically recorded when agents are spawned for work'),
423
- h('li', null, 'The pipeline tracks task status from creation through completion'),
424
- h('li', null, 'Real-time updates via SSE — no need to refresh'),
425
- h('li', null, 'Smart metadata extraction categorizes tasks automatically')
426
- ),
427
- h('h4', { style: _h4 }, 'Task Lifecycle'),
428
- h('ul', { style: _ul },
429
- h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
430
- h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
431
- h('li', null, h('strong', null, 'In Progress'), ' — Actively executing'),
432
- h('li', null, h('strong', null, 'Completed'), ' — Finished successfully'),
433
- h('li', null, h('strong', null, 'Failed'), ' — Encountered an error')
434
- ),
435
- h('div', { style: _tip }, 'Tip: Tasks will appear here as agents are assigned work. Sub-tasks show as children connected by arrows.')
436
- )
437
- ),
438
- ),
665
+ if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
666
+ toolbar,
667
+ h(MetricsBar, { stats: stats }),
439
668
  h('div', { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' } },
440
- h('div', { style: { fontSize: 48, marginBottom: 16 } }, '\uD83D\uDCCB'),
441
- h('div', { style: { fontSize: 18, fontWeight: 600, marginBottom: 8, color: '#fff' } }, 'No Tasks in Pipeline'),
442
- h('div', { style: { color: 'rgba(255,255,255,0.5)' } }, 'Tasks will appear here as agents are assigned work.')
669
+ 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()),
670
+ h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
671
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
443
672
  )
444
673
  );
445
674
 
446
- return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
447
- // Toolbar
448
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '12px 20px', borderBottom: '1px solid rgba(255,255,255,0.08)', background: 'rgba(0,0,0,0.3)', flexShrink: 0, flexWrap: 'wrap' } },
449
- h('div', { style: { fontWeight: 700, fontSize: 16, color: '#fff', display: 'flex', alignItems: 'center', gap: 8 } },
450
- I.workflow(), ' Task Pipeline',
451
- h(HelpButton, { label: 'Task Pipeline' },
452
- h('p', null, 'Visual hierarchy of all agent tasks in your organization. Tasks flow from agents (top) to individual tasks and sub-tasks (below), connected by arrows.'),
453
- h('h4', { style: _h4 }, 'Interactions'),
454
- h('ul', { style: _ul },
455
- h('li', null, h('strong', null, 'Hover'), ' — Highlights the task chain and shows detail tooltip'),
456
- h('li', null, h('strong', null, 'Click'), ' — Opens full task detail modal'),
457
- h('li', null, h('strong', null, 'Scroll'), ' — Zoom in/out'),
458
- h('li', null, h('strong', null, 'Drag'), ' — Pan the canvas')
459
- ),
460
- h('h4', { style: _h4 }, 'Task Lifecycle'),
461
- h('ul', { style: _ul },
462
- h('li', null, h('strong', null, 'Queued'), ' — Waiting to be picked up'),
463
- h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
464
- h('li', null, h('strong', null, 'In Progress'), ' — Actively executing'),
465
- h('li', null, h('strong', null, 'Completed'), ' — Finished successfully'),
466
- h('li', null, h('strong', null, 'Failed'), ' — Encountered an error')
467
- ),
468
- h('div', { style: _tip }, 'Tip: Sub-tasks appear as children of their parent task. The arrow system shows task delegation flow.')
469
- )
470
- ),
471
- // Live dot
472
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
473
- h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'pulse 2s infinite' } }),
474
- h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } }, 'Live')
475
- ),
476
- // Stats
477
- h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 12 } },
478
- (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
479
- ),
480
- h('div', { style: { flex: 1 } }),
481
- // Filter
482
- ['active', 'all', 'completed', 'failed'].map(function(f) {
483
- return h('button', {
484
- key: f,
485
- onClick: function() { setFilter(f); },
486
- style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px', background: filter === f ? 'rgba(99,102,241,0.3)' : toolbarBtnStyle.background, borderColor: filter === f ? 'rgba(99,102,241,0.5)' : toolbarBtnStyle.border })
487
- }, f.charAt(0).toUpperCase() + f.slice(1));
488
- }),
489
- h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
490
- // Legend
491
- legendDot(STATUS_COLORS.in_progress, 'Active'),
492
- legendDot(STATUS_COLORS.assigned, 'Assigned'),
493
- legendDot(STATUS_COLORS.completed, 'Done'),
494
- legendDot(STATUS_COLORS.failed, 'Failed'),
495
- h('div', { style: { width: 1, height: 16, background: 'rgba(255,255,255,0.12)' } }),
496
- // Zoom
497
- h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
498
- h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 12, minWidth: 40, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
499
- h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
500
- h('button', { onClick: fitToView, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Fit'),
501
- h('button', { onClick: loadData, style: Object.assign({}, toolbarBtnStyle, { fontSize: 11, padding: '4px 10px' }) }, 'Refresh'),
502
- ),
675
+ // Build expanded chain for inline flowchart
676
+ var expandedChain = null;
677
+ if (expandedTaskId) {
678
+ var et = tasks.find(function(t) { return t.id === expandedTaskId; });
679
+ if (et && et.chainId) {
680
+ expandedChain = tasks.filter(function(ct) { return ct.chainId === et.chainId; });
681
+ expandedChain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
682
+ } else if (et) {
683
+ expandedChain = [et];
684
+ }
685
+ }
503
686
 
687
+ return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
688
+ toolbar,
689
+ // Metrics bar
690
+ h(MetricsBar, { stats: stats }),
504
691
  // Canvas
505
692
  h('div', {
506
693
  ref: containerRef,
@@ -509,109 +696,110 @@ export function TaskPipelinePage() {
509
696
  onWheel: handleWheel,
510
697
  },
511
698
  h('div', { style: { transform: 'translate(' + pan.x + 'px, ' + pan.y + 'px) scale(' + zoom + ')', transformOrigin: '0 0', position: 'absolute', top: 0, left: 0 } },
512
- // SVG edges
513
- h('svg', { width: treeW, height: treeH, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
699
+
700
+ // Chain labels (left side)
701
+ chainInfos.map(function(ci, i) {
702
+ return h('div', { key: ci.chainId, style: { position: 'absolute', left: 4, top: ci.y - 2, fontSize: 9, color: 'rgba(255,255,255,0.25)', fontFamily: 'var(--font-mono)', letterSpacing: '0.04em', maxWidth: PAD - 8, overflow: 'hidden' } },
703
+ ci.customer && h(CustomerBadge, { customer: ci.customer })
704
+ );
705
+ }),
706
+
707
+ // SVG edges with animated flow
708
+ h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
514
709
  h('defs', null,
515
- h('marker', { id: 'task-arrow', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
516
- h('polygon', { points: '0 0, 8 3, 0 6', fill: EDGE_COLOR })
710
+ h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
711
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
712
+ ),
713
+ h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
714
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
517
715
  ),
518
- h('marker', { id: 'task-arrow-hl', markerWidth: 8, markerHeight: 6, refX: 8, refY: 3, orient: 'auto' },
519
- h('polygon', { points: '0 0, 8 3, 0 6', fill: EDGE_HIGHLIGHT })
716
+ // Animated glow filter
717
+ h('filter', { id: 'tp-glow' },
718
+ h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
719
+ h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
520
720
  )
521
721
  ),
522
722
  edges.map(function(e, i) {
523
- var pId = e.parent.id || e.parent.agentId;
524
- var cId = e.child.id || e.child.agentId;
525
- var isHl = connected && connected.has(pId) && connected.has(cId);
526
- var dim = connected && !isHl;
527
- return h('path', {
528
- key: i,
529
- d: edgePath(e.parent, e.child),
530
- stroke: isHl ? EDGE_HIGHLIGHT : dim ? 'rgba(255,255,255,0.06)' : EDGE_COLOR,
531
- strokeWidth: isHl ? 2.5 : 1.5,
532
- fill: 'none',
533
- markerEnd: isHl ? 'url(#task-arrow-hl)' : 'url(#task-arrow)',
534
- style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.3 : 1 },
535
- });
723
+ var fromId = e.from.id;
724
+ var toId = e.to.id;
725
+ var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
726
+ var dim = hoveredChainId && !isHl;
727
+ var dType = e.delegationType || 'delegation';
728
+ var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
729
+ var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
730
+
731
+ return h(Fragment, { key: i },
732
+ // Base path
733
+ h('path', {
734
+ d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
735
+ markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
736
+ style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
737
+ }),
738
+ // Animated flow dash overlay for active tasks
739
+ e.isActive && h('path', {
740
+ d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
741
+ strokeDasharray: '6 18',
742
+ className: 'tp-flow-active',
743
+ filter: 'url(#tp-glow)',
744
+ style: { opacity: dim ? 0.1 : 0.7 },
745
+ }),
746
+ // Delegation type label on edge
747
+ !dim && dType !== 'delegation' && h('text', {
748
+ x: (e.from.x + e.from.w + e.to.x) / 2,
749
+ y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
750
+ fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
751
+ fontSize: 8, textAnchor: 'middle', fontWeight: 600,
752
+ }, dType)
753
+ );
536
754
  })
537
755
  ),
538
756
 
539
- // Nodes
540
- positioned.map(function(node) {
541
- var nodeId = node.id || node.agentId;
542
- var isHovered = hoveredId === nodeId;
543
- var dim = connected && !connected.has(nodeId);
544
-
545
- if (node.isAgent) {
546
- // Agent node (top-level, like org-chart node)
547
- var taskCount = (node.children || []).length;
548
- return h('div', {
549
- key: nodeId,
550
- className: 'task-node',
551
- onMouseEnter: function(ev) { setHoveredId(nodeId); setMousePos({ x: ev.clientX, y: ev.clientY }); },
552
- onMouseMove: function(ev) { if (isHovered) setMousePos({ x: ev.clientX, y: ev.clientY }); },
553
- onMouseLeave: function() { setHoveredId(null); },
554
- style: {
555
- position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
556
- background: isHovered ? 'rgba(99,102,241,0.12)' : 'rgba(255,255,255,0.03)',
557
- border: '1.5px solid ' + (isHovered ? 'rgba(99,102,241,0.5)' : 'rgba(255,255,255,0.12)'),
558
- borderRadius: 12, padding: '10px 14px', cursor: 'default',
559
- transition: 'all 0.2s', opacity: dim ? 0.2 : 1,
560
- backdropFilter: 'blur(8px)',
561
- display: 'flex', alignItems: 'center', gap: 10, userSelect: 'none',
562
- },
563
- },
564
- h('div', { style: { width: 36, height: 36, borderRadius: '50%', background: 'linear-gradient(135deg, ' + ACCENT + '44, ' + ACCENT + '11)', border: '2px solid ' + ACCENT, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 14, fontWeight: 700, color: ACCENT, flexShrink: 0 } },
565
- (node.name || '?').charAt(0).toUpperCase()
566
- ),
567
- h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
568
- h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, node.name),
569
- h('div', { style: { fontSize: 11, color: 'rgba(255,255,255,0.45)', marginTop: 2 } }, 'Agent'),
570
- h('div', { style: { display: 'flex', gap: 4, marginTop: 4 } },
571
- h('span', { style: tagStyle(ACCENT) }, taskCount + ' task' + (taskCount !== 1 ? 's' : ''))
572
- )
573
- )
574
- );
575
- }
576
-
577
- // Task node
757
+ // Task nodes
758
+ nodes.map(function(node) {
578
759
  var t = node.task;
579
- var statusColor = STATUS_COLORS[t.status] || '#6b7394';
580
- var priColor = PRIORITY_COLORS[t.priority] || '#6366f1';
581
- var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
760
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
761
+ var isHovered = hoveredId === node.id;
762
+ var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
763
+ var dim = hoveredChainId && !isChainHl;
764
+ var isActive = t.status === 'in_progress';
582
765
 
766
+ var isExpanded = expandedTaskId === node.id;
583
767
  return h('div', {
584
- key: nodeId,
585
- className: 'task-node',
586
- onClick: function() { setSelectedTask(t); },
587
- onMouseEnter: function(ev) { setHoveredId(nodeId); setMousePos({ x: ev.clientX, y: ev.clientY }); },
588
- onMouseMove: function(ev) { if (isHovered) setMousePos({ x: ev.clientX, y: ev.clientY }); },
768
+ key: node.id,
769
+ className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
770
+ onClick: function() { toggleExpand(t); },
771
+ onDoubleClick: function() { openTaskDetail(t); },
772
+ onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
773
+ onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
589
774
  onMouseLeave: function() { setHoveredId(null); },
590
775
  style: {
591
- position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
776
+ position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
592
777
  background: isHovered ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
593
- border: '1.5px solid ' + (isHovered ? statusColor + '88' : 'rgba(255,255,255,0.12)'),
594
- borderLeft: '3px solid ' + statusColor,
595
- borderRadius: 12, padding: '8px 12px', cursor: 'pointer',
596
- transition: 'all 0.2s', opacity: dim ? 0.2 : 1,
597
- backdropFilter: 'blur(8px)',
598
- display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 4, userSelect: 'none',
778
+ border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
779
+ borderLeft: '3px solid ' + sc,
780
+ borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
781
+ transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
782
+ backdropFilter: 'blur(6px)',
783
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
599
784
  },
600
785
  },
601
- // Title row
602
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
603
- h('span', { style: { fontSize: 12 } }, catIcon),
604
- h('span', { style: { fontSize: 12, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
786
+ // Agent + title row
787
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
788
+ h('div', { style: { width: 18, height: 18, borderRadius: '50%', background: sc + '33', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 8, fontWeight: 700, color: sc, flexShrink: 0, border: '1px solid ' + sc + '44' } },
789
+ (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
790
+ ),
791
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
605
792
  ),
606
- // Status + priority + time
793
+ // Status + agent name + time
607
794
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' } },
608
- h('span', { style: tagStyle(statusColor) }, t.status.replace('_', ' ')),
609
- h('span', { style: tagStyle(priColor) }, t.priority),
610
- h('span', { style: { fontSize: 10, color: 'rgba(255,255,255,0.35)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
795
+ tag(sc, t.status.replace('_', ' ')),
796
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.4)' } }, t.assignedToName || t.assignedTo),
797
+ t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
798
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
611
799
  ),
612
800
  // Progress bar
613
- t.status === 'in_progress' && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.1)', borderRadius: 1, overflow: 'hidden', marginTop: 2 } },
614
- h('div', { style: { height: '100%', width: t.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 1 } })
801
+ isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
802
+ h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
615
803
  )
616
804
  );
617
805
  })
@@ -619,43 +807,37 @@ export function TaskPipelinePage() {
619
807
  ),
620
808
 
621
809
  // Hover tooltip
622
- hoveredNode && !hoveredNode.isAgent && hoveredNode.task && h('div', { style: {
810
+ hoveredNode && hoveredNode.task && h('div', { style: {
623
811
  position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
624
812
  background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
625
813
  border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
626
- padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 200, maxWidth: 300,
814
+ padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
627
815
  }},
628
- h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', marginBottom: 8 } }, hoveredNode.task.title),
629
- h('div', { style: { display: 'flex', flexDirection: 'column', gap: 4 } },
630
- tooltipRow('Status', hoveredNode.task.status.replace('_', ' '), STATUS_COLORS[hoveredNode.task.status]),
631
- tooltipRow('Priority', hoveredNode.task.priority, PRIORITY_COLORS[hoveredNode.task.priority]),
632
- tooltipRow('Agent', hoveredNode.task.assignedToName || '-'),
633
- tooltipRow('Category', hoveredNode.task.category),
634
- hoveredNode.task.modelUsed && tooltipRow('Model', hoveredNode.task.modelUsed),
635
- hoveredNode.task.startedAt && tooltipRow('Started', timeAgo(hoveredNode.task.startedAt)),
636
- hoveredNode.task.actualDurationMs && tooltipRow('Duration', formatDuration(hoveredNode.task.actualDurationMs)),
637
- hoveredNode.task.progress > 0 && tooltipRow('Progress', hoveredNode.task.progress + '%', STATUS_COLORS.in_progress)
816
+ hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
817
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.task.title),
818
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
819
+ h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Agent'), h('span', { style: { fontWeight: 600 } }, hoveredNode.task.assignedToName || '-')),
820
+ h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Status'), h('span', { style: { color: STATUS_COLORS[hoveredNode.task.status] } }, hoveredNode.task.status.replace('_', ' '))),
821
+ hoveredNode.task.chainId && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Chain Step'), h('span', null, '#' + ((hoveredNode.task.chainSeq || 0) + 1))),
822
+ hoveredNode.task.delegationType && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Type'), h('span', { style: { color: DELEGATION_COLORS[hoveredNode.task.delegationType] } }, hoveredNode.task.delegationType)),
823
+ hoveredNode.task.progress > 0 && h('div', { style: { display: 'flex', justifyContent: 'space-between' } }, h('span', { style: { color: 'rgba(255,255,255,0.4)' } }, 'Progress'), h('span', { style: { color: STATUS_COLORS.in_progress } }, hoveredNode.task.progress + '%'))
638
824
  )
639
825
  ),
640
826
 
641
- // Agent hover tooltip
642
- hoveredNode && hoveredNode.isAgent && h('div', { style: {
643
- position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
644
- background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
645
- border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
646
- padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 180,
647
- }},
648
- h('div', { style: { fontSize: 13, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.name),
649
- tooltipRow('Tasks', String((hoveredNode.children || []).length), ACCENT)
650
- ),
827
+ // Inline chain flowchart (shown when a task is clicked)
828
+ expandedChain && expandedChain.length > 0 && h(ChainFlowInline, {
829
+ chain: expandedChain,
830
+ taskId: expandedTaskId,
831
+ onClose: function() { setExpandedTaskId(null); },
832
+ onClickTask: function(t) { openTaskDetail(t); }
833
+ }),
651
834
 
652
- // Task detail modal
653
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
835
+ // Detail modal (double-click)
836
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
654
837
  );
655
838
  }
656
839
 
657
- // ─── Agent Task Pipeline (for agent-detail workforce tab) ─
658
- // Reusable mini version scoped to a single agent
840
+ // ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
659
841
  export function AgentTaskPipeline(props) {
660
842
  var agentId = props.agentId;
661
843
  var _tasks = useState([]);
@@ -672,8 +854,6 @@ export function AgentTaskPipeline(props) {
672
854
  engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
673
855
  setTasks(res?.tasks || []);
674
856
  }).catch(function() {}).finally(function() { setLoading(false); });
675
-
676
- // SSE
677
857
  var baseUrl = window.__ENGINE_BASE || '/api/engine';
678
858
  var es;
679
859
  try {
@@ -709,57 +889,46 @@ export function AgentTaskPipeline(props) {
709
889
  var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
710
890
 
711
891
  function renderTaskRow(t) {
712
- var statusColor = STATUS_COLORS[t.status] || '#6b7394';
713
- var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
892
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
714
893
  return h('div', {
715
894
  key: t.id,
716
895
  onClick: function() { setSelectedTask(t); },
717
- style: {
718
- display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px',
719
- borderBottom: '1px solid var(--border)', cursor: 'pointer',
720
- transition: 'background 0.15s',
721
- },
896
+ style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
722
897
  onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
723
898
  onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
724
899
  },
725
- h('span', { style: { fontSize: 14, flexShrink: 0 } }, catIcon),
900
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: sc, flexShrink: 0 } }),
726
901
  h('div', { style: { flex: 1, minWidth: 0 } },
727
- h('div', { style: { fontSize: 13, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
728
- h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 2 } }, t.category + ' \u00B7 ' + timeAgo(t.createdAt))
902
+ h('div', { style: { fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
903
+ h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 4, alignItems: 'center' } },
904
+ t.category,
905
+ t.delegationType && h('span', { style: { color: DELEGATION_COLORS[t.delegationType] || '#6b7394' } }, '\u2192 ' + t.delegationType),
906
+ h('span', null, '\u00B7 ' + timeAgo(t.createdAt))
907
+ )
729
908
  ),
730
- t.status === 'in_progress' && t.progress > 0 && h('div', { style: { width: 40, fontSize: 11, color: STATUS_COLORS.in_progress, fontWeight: 600 } }, t.progress + '%'),
731
- h('span', { style: { padding: '2px 8px', borderRadius: 10, fontSize: 10, fontWeight: 600, background: statusColor + '22', color: statusColor, flexShrink: 0 } }, t.status.replace('_', ' ')),
732
- t.actualDurationMs && h('span', { style: { fontSize: 11, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
909
+ t.status === 'in_progress' && t.progress > 0 && h('span', { style: { fontSize: 10, color: sc, fontWeight: 600 } }, t.progress + '%'),
910
+ h('span', { style: { padding: '2px 6px', borderRadius: 8, fontSize: 9, fontWeight: 600, background: sc + '22', color: sc, flexShrink: 0 } }, t.status.replace('_', ' ')),
911
+ t.actualDurationMs && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
733
912
  );
734
913
  }
735
914
 
736
915
  return h(Fragment, null,
737
- // Active
738
- active.length > 0 && h('div', { style: { marginBottom: 16 } },
739
- h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 8, display: 'flex', alignItems: 'center', gap: 6 } },
740
- h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'pulse 2s infinite' } }),
916
+ active.length > 0 && h('div', { style: { marginBottom: 12 } },
917
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
918
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
741
919
  'Active (' + active.length + ')'
742
920
  ),
743
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
744
- active.map(renderTaskRow)
745
- )
921
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, active.map(renderTaskRow))
746
922
  ),
747
- // Completed
748
- completed.length > 0 && h('div', { style: { marginBottom: 16 } },
749
- h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 8 } }, 'Completed (' + completed.length + ')'),
750
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
751
- completed.slice(0, 10).map(renderTaskRow)
752
- ),
753
- completed.length > 10 && h('div', { style: { padding: 8, textAlign: 'center', fontSize: 12, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
923
+ completed.length > 0 && h('div', { style: { marginBottom: 12 } },
924
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 6 } }, 'Completed (' + completed.length + ')'),
925
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, completed.slice(0, 10).map(renderTaskRow)),
926
+ completed.length > 10 && h('div', { style: { padding: 6, textAlign: 'center', fontSize: 11, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
754
927
  ),
755
- // Failed
756
- failed.length > 0 && h('div', { style: { marginBottom: 16 } },
757
- h('div', { style: { fontSize: 12, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 8 } }, 'Failed (' + failed.length + ')'),
758
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
759
- failed.slice(0, 5).map(renderTaskRow)
760
- )
928
+ failed.length > 0 && h('div', { style: { marginBottom: 12 } },
929
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
930
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
761
931
  ),
762
- // Detail modal
763
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
932
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
764
933
  );
765
934
  }