@agenticmail/enterprise 0.5.209 → 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.
@@ -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,152 @@ function TaskDetail(props) {
234
308
  );
235
309
  }
236
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
+
237
438
  // ─── Main Page ───────────────────────────────────────────
238
439
  export function TaskPipelinePage() {
440
+ injectCSS();
239
441
  var app = useApp();
240
442
  var toast = app.toast;
241
443
  var _tasks = useState([]);
242
444
  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 });
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: [] });
244
446
  var stats = _stats[0]; var setStats = _stats[1];
447
+ var _expandedTaskId = useState(null);
448
+ var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
245
449
  var _loading = useState(true);
246
450
  var loading = _loading[0]; var setLoading = _loading[1];
247
451
  var _selectedTask = useState(null);
248
452
  var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
453
+ var _selectedChain = useState(null);
454
+ var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
249
455
  var _hoveredId = useState(null);
250
456
  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
457
  var _zoom = useState(1);
254
458
  var zoom = _zoom[0]; var setZoom = _zoom[1];
255
459
  var _pan = useState({ x: 0, y: 0 });
@@ -260,6 +464,8 @@ export function TaskPipelinePage() {
260
464
  var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
261
465
  var _filter = useState('active');
262
466
  var filter = _filter[0]; var setFilter = _filter[1];
467
+ var _mousePos = useState({ x: 0, y: 0 });
468
+ var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
263
469
  var containerRef = useRef(null);
264
470
 
265
471
  var loadData = useCallback(function() {
@@ -270,9 +476,8 @@ export function TaskPipelinePage() {
270
476
  ]).then(function(res) {
271
477
  setTasks(res[0]?.tasks || []);
272
478
  setStats(res[1] || stats);
273
- }).catch(function(err) {
274
- console.error('[TaskPipeline] load:', err);
275
- }).finally(function() { setLoading(false); });
479
+ }).catch(function(err) { console.error('[TaskPipeline]', err); })
480
+ .finally(function() { setLoading(false); });
276
481
  }, []);
277
482
 
278
483
  // SSE
@@ -299,10 +504,9 @@ export function TaskPipelinePage() {
299
504
  if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
300
505
  return [event.task].concat(prev);
301
506
  });
302
- setSelectedTask(function(prev) {
303
- if (prev && prev.id === event.task.id) return event.task;
304
- return prev;
305
- });
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() {});
306
510
  }
307
511
  } catch (err) {}
308
512
  };
@@ -310,11 +514,10 @@ export function TaskPipelinePage() {
310
514
  return function() { if (es) es.close(); };
311
515
  }, []);
312
516
 
313
- // Periodic stats
314
517
  useEffect(function() {
315
518
  var iv = setInterval(function() {
316
519
  engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
317
- }, 30000);
520
+ }, 15000);
318
521
  return function() { clearInterval(iv); };
319
522
  }, []);
320
523
 
@@ -326,6 +529,34 @@ export function TaskPipelinePage() {
326
529
  }).catch(function(err) { toast(err.message || 'Failed', 'error'); });
327
530
  }, []);
328
531
 
532
+ function openTaskDetail(t) {
533
+ setSelectedTask(t);
534
+ if (t.chainId) {
535
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
536
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
537
+ setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
538
+ } else {
539
+ setSelectedChain(null);
540
+ }
541
+ }
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
+
329
560
  // Filter
330
561
  var filtered = tasks.filter(function(t) {
331
562
  if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
@@ -335,34 +566,28 @@ export function TaskPipelinePage() {
335
566
  });
336
567
 
337
568
  // Layout
338
- var layout = layoutPipeline(filtered);
339
- var positioned = layout.positioned;
569
+ var layout = layoutChains(filtered);
570
+ var nodes = layout.nodes;
340
571
  var edges = layout.edges;
341
572
  var treeW = layout.width;
342
573
  var treeH = layout.height;
574
+ var chainInfos = layout.chains;
343
575
 
344
- // Zoom
576
+ // Zoom/Pan handlers
345
577
  var handleWheel = useCallback(function(e) {
346
578
  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)); });
579
+ setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
349
580
  }, []);
350
-
351
- // Pan
352
581
  var handleMouseDown = useCallback(function(e) {
353
- if (e.button !== 0) return;
354
- if (e.target.closest('.task-node')) return;
582
+ if (e.button !== 0 || e.target.closest('.tp-node')) return;
355
583
  setDragging(true);
356
584
  setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
357
585
  }, [pan]);
358
-
359
586
  var handleMouseMove = useCallback(function(e) {
360
587
  if (!dragging) return;
361
588
  setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
362
589
  }, [dragging, dragStart]);
363
-
364
590
  var handleMouseUp = useCallback(function() { setDragging(false); }, []);
365
-
366
591
  useEffect(function() {
367
592
  if (dragging) {
368
593
  window.addEventListener('mousemove', handleMouseMove);
@@ -371,7 +596,6 @@ export function TaskPipelinePage() {
371
596
  }
372
597
  }, [dragging, handleMouseMove, handleMouseUp]);
373
598
 
374
- // Fit
375
599
  var fitToView = useCallback(function() {
376
600
  if (!containerRef.current || !treeW || !treeH) return;
377
601
  var rect = containerRef.current.getBoundingClientRect();
@@ -379,128 +603,96 @@ export function TaskPipelinePage() {
379
603
  var scaleY = (rect.height - 40) / treeH;
380
604
  var scale = Math.min(scaleX, scaleY, 1.5);
381
605
  setZoom(scale);
382
- setPan({ x: (rect.width - treeW * scale) / 2, y: (rect.height - treeH * scale) / 2 });
606
+ setPan({ x: (rect.width - treeW * scale) / 2, y: 20 });
383
607
  }, [treeW, treeH]);
608
+ useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
384
609
 
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;
610
+ // Highlight connected chain on hover
611
+ var hoveredChainId = null;
612
+ if (hoveredId) {
613
+ var hn = nodes.find(function(n) { return n.id === hoveredId; });
614
+ if (hn) hoveredChainId = hn.chainId;
615
+ }
408
616
 
409
- // Hovered node for tooltip
410
- var hoveredNode = hoveredId ? positioned.find(function(n) { return (n.id || n.agentId) === hoveredId; }) : null;
617
+ var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
618
+
619
+ // ─── Toolbar ─────────────────────────────────────────
620
+ 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' } },
621
+ h('div', { style: { fontWeight: 700, fontSize: 14, color: '#fff', display: 'flex', alignItems: 'center', gap: 6 } },
622
+ I.workflow(), 'Task Pipeline',
623
+ h(HelpButton, { label: 'Task Pipeline' },
624
+ h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
625
+ h('h4', { style: _h4 }, 'Features'),
626
+ h('ul', { style: _ul },
627
+ h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
628
+ h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
629
+ h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
630
+ h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
631
+ h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
632
+ h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
633
+ ),
634
+ h('h4', { style: _h4 }, 'Interactions'),
635
+ h('ul', { style: _ul },
636
+ h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
637
+ h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
638
+ h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
639
+ h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
640
+ ),
641
+ 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.')
642
+ )
643
+ ),
644
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
645
+ h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'flowPulse 2s infinite' } }),
646
+ h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } }, 'Live')
647
+ ),
648
+ h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } },
649
+ (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
650
+ ),
651
+ h('div', { style: { flex: 1 } }),
652
+ // Filters
653
+ ['active', 'all', 'completed', 'failed'].map(function(f) {
654
+ 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));
655
+ }),
656
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
657
+ legendDot(STATUS_COLORS.in_progress, 'Active'),
658
+ legendDot(STATUS_COLORS.completed, 'Done'),
659
+ legendDot(STATUS_COLORS.failed, 'Failed'),
660
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
661
+ h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
662
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11, minWidth: 36, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
663
+ h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
664
+ h('button', { onClick: fitToView, style: toolbarBtnStyle }, 'Fit'),
665
+ h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
666
+ );
411
667
 
412
668
  if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
413
669
 
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
- ),
670
+ if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
671
+ toolbar,
672
+ h(MetricsBar, { stats: stats }),
439
673
  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.')
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()),
675
+ h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
676
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
443
677
  )
444
678
  );
445
679
 
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
- ),
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
+ }
503
691
 
692
+ return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
693
+ toolbar,
694
+ // Metrics bar
695
+ h(MetricsBar, { stats: stats }),
504
696
  // Canvas
505
697
  h('div', {
506
698
  ref: containerRef,
@@ -509,109 +701,110 @@ export function TaskPipelinePage() {
509
701
  onWheel: handleWheel,
510
702
  },
511
703
  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' } },
704
+
705
+ // Chain labels (left side)
706
+ chainInfos.map(function(ci, i) {
707
+ 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' } },
708
+ ci.customer && h(CustomerBadge, { customer: ci.customer })
709
+ );
710
+ }),
711
+
712
+ // SVG edges with animated flow
713
+ h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
514
714
  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 })
715
+ h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
716
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
717
+ ),
718
+ h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
719
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
517
720
  ),
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 })
721
+ // Animated glow filter
722
+ h('filter', { id: 'tp-glow' },
723
+ h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
724
+ h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
520
725
  )
521
726
  ),
522
727
  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
- });
728
+ var fromId = e.from.id;
729
+ var toId = e.to.id;
730
+ var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
731
+ var dim = hoveredChainId && !isHl;
732
+ var dType = e.delegationType || 'delegation';
733
+ var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
734
+ var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
735
+
736
+ return h(Fragment, { key: i },
737
+ // Base path
738
+ h('path', {
739
+ d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
740
+ markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
741
+ style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
742
+ }),
743
+ // Animated flow dash overlay for active tasks
744
+ e.isActive && h('path', {
745
+ d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
746
+ strokeDasharray: '6 18',
747
+ className: 'tp-flow-active',
748
+ filter: 'url(#tp-glow)',
749
+ style: { opacity: dim ? 0.1 : 0.7 },
750
+ }),
751
+ // Delegation type label on edge
752
+ !dim && dType !== 'delegation' && h('text', {
753
+ x: (e.from.x + e.from.w + e.to.x) / 2,
754
+ y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
755
+ fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
756
+ fontSize: 8, textAnchor: 'middle', fontWeight: 600,
757
+ }, dType)
758
+ );
536
759
  })
537
760
  ),
538
761
 
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
762
+ // Task nodes
763
+ nodes.map(function(node) {
578
764
  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';
765
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
766
+ var isHovered = hoveredId === node.id;
767
+ var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
768
+ var dim = hoveredChainId && !isChainHl;
769
+ var isActive = t.status === 'in_progress';
582
770
 
771
+ var isExpanded = expandedTaskId === node.id;
583
772
  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 }); },
773
+ key: node.id,
774
+ className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
775
+ onClick: function() { toggleExpand(t); },
776
+ onDoubleClick: function() { openTaskDetail(t); },
777
+ onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
778
+ onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
589
779
  onMouseLeave: function() { setHoveredId(null); },
590
780
  style: {
591
- position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
781
+ position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
592
782
  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',
783
+ border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
784
+ borderLeft: '3px solid ' + sc,
785
+ borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
786
+ transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
787
+ backdropFilter: 'blur(6px)',
788
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
599
789
  },
600
790
  },
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)
791
+ // Agent + title row
792
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
793
+ 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' } },
794
+ (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
795
+ ),
796
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
605
797
  ),
606
- // Status + priority + time
798
+ // Status + agent name + time
607
799
  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))
800
+ tag(sc, t.status.replace('_', ' ')),
801
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.4)' } }, t.assignedToName || t.assignedTo),
802
+ t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
803
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
611
804
  ),
612
805
  // 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 } })
806
+ isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
807
+ h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
615
808
  )
616
809
  );
617
810
  })
@@ -619,43 +812,37 @@ export function TaskPipelinePage() {
619
812
  ),
620
813
 
621
814
  // Hover tooltip
622
- hoveredNode && !hoveredNode.isAgent && hoveredNode.task && h('div', { style: {
815
+ hoveredNode && hoveredNode.task && h('div', { style: {
623
816
  position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
624
817
  background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
625
818
  border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
626
- padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 200, maxWidth: 300,
819
+ padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
627
820
  }},
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)
821
+ hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
822
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.task.title),
823
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
824
+ 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 || '-')),
825
+ 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('_', ' '))),
826
+ 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))),
827
+ 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)),
828
+ 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
829
  )
639
830
  ),
640
831
 
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
- ),
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
+ }),
651
839
 
652
- // Task detail modal
653
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
840
+ // Detail modal (double-click)
841
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
654
842
  );
655
843
  }
656
844
 
657
- // ─── Agent Task Pipeline (for agent-detail workforce tab) ─
658
- // Reusable mini version scoped to a single agent
845
+ // ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
659
846
  export function AgentTaskPipeline(props) {
660
847
  var agentId = props.agentId;
661
848
  var _tasks = useState([]);
@@ -672,8 +859,6 @@ export function AgentTaskPipeline(props) {
672
859
  engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
673
860
  setTasks(res?.tasks || []);
674
861
  }).catch(function() {}).finally(function() { setLoading(false); });
675
-
676
- // SSE
677
862
  var baseUrl = window.__ENGINE_BASE || '/api/engine';
678
863
  var es;
679
864
  try {
@@ -709,57 +894,46 @@ export function AgentTaskPipeline(props) {
709
894
  var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
710
895
 
711
896
  function renderTaskRow(t) {
712
- var statusColor = STATUS_COLORS[t.status] || '#6b7394';
713
- var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
897
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
714
898
  return h('div', {
715
899
  key: t.id,
716
900
  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
- },
901
+ style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
722
902
  onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
723
903
  onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
724
904
  },
725
- h('span', { style: { fontSize: 14, flexShrink: 0 } }, catIcon),
905
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: sc, flexShrink: 0 } }),
726
906
  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))
907
+ h('div', { style: { fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
908
+ h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 4, alignItems: 'center' } },
909
+ t.category,
910
+ t.delegationType && h('span', { style: { color: DELEGATION_COLORS[t.delegationType] || '#6b7394' } }, '\u2192 ' + t.delegationType),
911
+ h('span', null, '\u00B7 ' + timeAgo(t.createdAt))
912
+ )
729
913
  ),
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))
914
+ t.status === 'in_progress' && t.progress > 0 && h('span', { style: { fontSize: 10, color: sc, fontWeight: 600 } }, t.progress + '%'),
915
+ h('span', { style: { padding: '2px 6px', borderRadius: 8, fontSize: 9, fontWeight: 600, background: sc + '22', color: sc, flexShrink: 0 } }, t.status.replace('_', ' ')),
916
+ t.actualDurationMs && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
733
917
  );
734
918
  }
735
919
 
736
920
  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' } }),
921
+ active.length > 0 && h('div', { style: { marginBottom: 12 } },
922
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
923
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
741
924
  'Active (' + active.length + ')'
742
925
  ),
743
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
744
- active.map(renderTaskRow)
745
- )
926
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, active.map(renderTaskRow))
746
927
  ),
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')
928
+ completed.length > 0 && h('div', { style: { marginBottom: 12 } },
929
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 6 } }, 'Completed (' + completed.length + ')'),
930
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, completed.slice(0, 10).map(renderTaskRow)),
931
+ completed.length > 10 && h('div', { style: { padding: 6, textAlign: 'center', fontSize: 11, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
754
932
  ),
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
- )
933
+ failed.length > 0 && h('div', { style: { marginBottom: 12 } },
934
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
935
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
761
936
  ),
762
- // Detail modal
763
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
937
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
764
938
  );
765
939
  }