@agenticmail/enterprise 0.5.211 → 0.5.213

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); });
130
+
131
+ return { nodes: allNodes, edges: allEdges, width: maxX + PAD, height: y + PAD, chains: chainInfos };
132
+ }
116
133
 
117
- return { positioned: allNodes, edges: edgeList, width: maxX + PAD, height: maxY + PAD + 40 };
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;
118
142
  }
119
143
 
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;
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)
182
+ h('span', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11 } }, label)
172
183
  );
173
184
  }
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)
179
- );
180
- }
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,63 @@ 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
+ // (ChainFlowInline removed — chain flow now renders inline on canvas)
348
+
237
349
  // ─── Main Page ───────────────────────────────────────────
238
350
  export function TaskPipelinePage() {
351
+ injectCSS();
239
352
  var app = useApp();
240
353
  var toast = app.toast;
241
354
  var _tasks = useState([]);
242
355
  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 });
356
+ 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
357
  var stats = _stats[0]; var setStats = _stats[1];
358
+ var _expandedTaskId = useState(null);
359
+ var expandedTaskId = _expandedTaskId[0]; var setExpandedTaskId = _expandedTaskId[1];
245
360
  var _loading = useState(true);
246
361
  var loading = _loading[0]; var setLoading = _loading[1];
247
362
  var _selectedTask = useState(null);
248
363
  var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
364
+ var _selectedChain = useState(null);
365
+ var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
249
366
  var _hoveredId = useState(null);
250
367
  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
368
  var _zoom = useState(1);
254
369
  var zoom = _zoom[0]; var setZoom = _zoom[1];
255
370
  var _pan = useState({ x: 0, y: 0 });
@@ -260,6 +375,8 @@ export function TaskPipelinePage() {
260
375
  var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
261
376
  var _filter = useState('active');
262
377
  var filter = _filter[0]; var setFilter = _filter[1];
378
+ var _mousePos = useState({ x: 0, y: 0 });
379
+ var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
263
380
  var containerRef = useRef(null);
264
381
 
265
382
  var loadData = useCallback(function() {
@@ -270,9 +387,8 @@ export function TaskPipelinePage() {
270
387
  ]).then(function(res) {
271
388
  setTasks(res[0]?.tasks || []);
272
389
  setStats(res[1] || stats);
273
- }).catch(function(err) {
274
- console.error('[TaskPipeline] load:', err);
275
- }).finally(function() { setLoading(false); });
390
+ }).catch(function(err) { console.error('[TaskPipeline]', err); })
391
+ .finally(function() { setLoading(false); });
276
392
  }, []);
277
393
 
278
394
  // SSE
@@ -299,10 +415,9 @@ export function TaskPipelinePage() {
299
415
  if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
300
416
  return [event.task].concat(prev);
301
417
  });
302
- setSelectedTask(function(prev) {
303
- if (prev && prev.id === event.task.id) return event.task;
304
- return prev;
305
- });
418
+ setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
419
+ // Refresh stats on every task event for real-time metrics
420
+ engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
306
421
  }
307
422
  } catch (err) {}
308
423
  };
@@ -310,11 +425,10 @@ export function TaskPipelinePage() {
310
425
  return function() { if (es) es.close(); };
311
426
  }, []);
312
427
 
313
- // Periodic stats
314
428
  useEffect(function() {
315
429
  var iv = setInterval(function() {
316
430
  engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
317
- }, 30000);
431
+ }, 15000);
318
432
  return function() { clearInterval(iv); };
319
433
  }, []);
320
434
 
@@ -326,6 +440,34 @@ export function TaskPipelinePage() {
326
440
  }).catch(function(err) { toast(err.message || 'Failed', 'error'); });
327
441
  }, []);
328
442
 
443
+ function openTaskDetail(t) {
444
+ setSelectedTask(t);
445
+ if (t.chainId) {
446
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
447
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
448
+ setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
449
+ } else {
450
+ setSelectedChain(null);
451
+ }
452
+ }
453
+
454
+ // Toggle inline chain flowchart (single click on node)
455
+ function toggleExpand(t) {
456
+ if (expandedTaskId === t.id) {
457
+ setExpandedTaskId(null);
458
+ setSelectedChain(null);
459
+ } else {
460
+ setExpandedTaskId(t.id);
461
+ if (t.chainId) {
462
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
463
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
464
+ setSelectedChain(chainTasks.length > 0 ? chainTasks : [t]);
465
+ } else {
466
+ setSelectedChain([t]);
467
+ }
468
+ }
469
+ }
470
+
329
471
  // Filter
330
472
  var filtered = tasks.filter(function(t) {
331
473
  if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
@@ -335,34 +477,28 @@ export function TaskPipelinePage() {
335
477
  });
336
478
 
337
479
  // Layout
338
- var layout = layoutPipeline(filtered);
339
- var positioned = layout.positioned;
480
+ var layout = layoutChains(filtered);
481
+ var nodes = layout.nodes;
340
482
  var edges = layout.edges;
341
483
  var treeW = layout.width;
342
484
  var treeH = layout.height;
485
+ var chainInfos = layout.chains;
343
486
 
344
- // Zoom
487
+ // Zoom/Pan handlers
345
488
  var handleWheel = useCallback(function(e) {
346
489
  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)); });
490
+ setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
349
491
  }, []);
350
-
351
- // Pan
352
492
  var handleMouseDown = useCallback(function(e) {
353
- if (e.button !== 0) return;
354
- if (e.target.closest('.task-node')) return;
493
+ if (e.button !== 0 || e.target.closest('.tp-node')) return;
355
494
  setDragging(true);
356
495
  setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
357
496
  }, [pan]);
358
-
359
497
  var handleMouseMove = useCallback(function(e) {
360
498
  if (!dragging) return;
361
499
  setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
362
500
  }, [dragging, dragStart]);
363
-
364
501
  var handleMouseUp = useCallback(function() { setDragging(false); }, []);
365
-
366
502
  useEffect(function() {
367
503
  if (dragging) {
368
504
  window.addEventListener('mousemove', handleMouseMove);
@@ -371,7 +507,6 @@ export function TaskPipelinePage() {
371
507
  }
372
508
  }, [dragging, handleMouseMove, handleMouseUp]);
373
509
 
374
- // Fit
375
510
  var fitToView = useCallback(function() {
376
511
  if (!containerRef.current || !treeW || !treeH) return;
377
512
  var rect = containerRef.current.getBoundingClientRect();
@@ -379,128 +514,96 @@ export function TaskPipelinePage() {
379
514
  var scaleY = (rect.height - 40) / treeH;
380
515
  var scale = Math.min(scaleX, scaleY, 1.5);
381
516
  setZoom(scale);
382
- setPan({ x: (rect.width - treeW * scale) / 2, y: (rect.height - treeH * scale) / 2 });
517
+ setPan({ x: (rect.width - treeW * scale) / 2, y: 20 });
383
518
  }, [treeW, treeH]);
519
+ useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
384
520
 
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;
521
+ // Highlight connected chain on hover
522
+ var hoveredChainId = null;
523
+ if (hoveredId) {
524
+ var hn = nodes.find(function(n) { return n.id === hoveredId; });
525
+ if (hn) hoveredChainId = hn.chainId;
526
+ }
408
527
 
409
- // Hovered node for tooltip
410
- var hoveredNode = hoveredId ? positioned.find(function(n) { return (n.id || n.agentId) === hoveredId; }) : null;
528
+ var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
529
+
530
+ // ─── Toolbar ─────────────────────────────────────────
531
+ 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' } },
532
+ h('div', { style: { fontWeight: 700, fontSize: 14, color: '#fff', display: 'flex', alignItems: 'center', gap: 6 } },
533
+ I.workflow(), 'Task Pipeline',
534
+ h(HelpButton, { label: 'Task Pipeline' },
535
+ h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
536
+ h('h4', { style: _h4 }, 'Features'),
537
+ h('ul', { style: _ul },
538
+ h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
539
+ h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
540
+ h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
541
+ h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
542
+ h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
543
+ h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
544
+ ),
545
+ h('h4', { style: _h4 }, 'Interactions'),
546
+ h('ul', { style: _ul },
547
+ h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
548
+ h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
549
+ h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
550
+ h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
551
+ ),
552
+ 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.')
553
+ )
554
+ ),
555
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
556
+ h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'flowPulse 2s infinite' } }),
557
+ h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } }, 'Live')
558
+ ),
559
+ h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } },
560
+ (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
561
+ ),
562
+ h('div', { style: { flex: 1 } }),
563
+ // Filters
564
+ ['active', 'all', 'completed', 'failed'].map(function(f) {
565
+ 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));
566
+ }),
567
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
568
+ legendDot(STATUS_COLORS.in_progress, 'Active'),
569
+ legendDot(STATUS_COLORS.completed, 'Done'),
570
+ legendDot(STATUS_COLORS.failed, 'Failed'),
571
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
572
+ h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
573
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11, minWidth: 36, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
574
+ h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
575
+ h('button', { onClick: fitToView, style: toolbarBtnStyle }, 'Fit'),
576
+ h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
577
+ );
411
578
 
412
579
  if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
413
580
 
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
- ),
581
+ if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
582
+ toolbar,
583
+ h(MetricsBar, { stats: stats }),
439
584
  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.')
585
+ 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()),
586
+ h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
587
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
443
588
  )
444
589
  );
445
590
 
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
- ),
591
+ // Build expanded chain for inline flowchart
592
+ var expandedChain = null;
593
+ if (expandedTaskId) {
594
+ var et = tasks.find(function(t) { return t.id === expandedTaskId; });
595
+ if (et && et.chainId) {
596
+ expandedChain = tasks.filter(function(ct) { return ct.chainId === et.chainId; });
597
+ expandedChain.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
598
+ } else if (et) {
599
+ expandedChain = [et];
600
+ }
601
+ }
503
602
 
603
+ return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
604
+ toolbar,
605
+ // Metrics bar
606
+ h(MetricsBar, { stats: stats }),
504
607
  // Canvas
505
608
  h('div', {
506
609
  ref: containerRef,
@@ -509,153 +612,256 @@ export function TaskPipelinePage() {
509
612
  onWheel: handleWheel,
510
613
  },
511
614
  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' } },
615
+
616
+ // Chain labels (left side)
617
+ chainInfos.map(function(ci, i) {
618
+ 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' } },
619
+ ci.customer && h(CustomerBadge, { customer: ci.customer })
620
+ );
621
+ }),
622
+
623
+ // SVG edges with animated flow
624
+ h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
514
625
  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 })
626
+ h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
627
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
517
628
  ),
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 })
629
+ h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
630
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
631
+ ),
632
+ // Animated glow filter
633
+ h('filter', { id: 'tp-glow' },
634
+ h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
635
+ h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
520
636
  )
521
637
  ),
522
638
  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
- });
639
+ var fromId = e.from.id;
640
+ var toId = e.to.id;
641
+ var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
642
+ var dim = hoveredChainId && !isHl;
643
+ var dType = e.delegationType || 'delegation';
644
+ var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
645
+ var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
646
+
647
+ return h(Fragment, { key: i },
648
+ // Base path
649
+ h('path', {
650
+ d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
651
+ markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
652
+ style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
653
+ }),
654
+ // Animated flow dash overlay for active tasks
655
+ e.isActive && h('path', {
656
+ d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
657
+ strokeDasharray: '6 18',
658
+ className: 'tp-flow-active',
659
+ filter: 'url(#tp-glow)',
660
+ style: { opacity: dim ? 0.1 : 0.7 },
661
+ }),
662
+ // Delegation type label on edge
663
+ !dim && dType !== 'delegation' && h('text', {
664
+ x: (e.from.x + e.from.w + e.to.x) / 2,
665
+ y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
666
+ fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
667
+ fontSize: 8, textAnchor: 'middle', fontWeight: 600,
668
+ }, dType)
669
+ );
536
670
  })
537
671
  ),
538
672
 
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
673
+ // Task nodes
674
+ nodes.map(function(node) {
578
675
  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';
676
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
677
+ var isHovered = hoveredId === node.id;
678
+ var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
679
+ var dim = hoveredChainId && !isChainHl;
680
+ var isActive = t.status === 'in_progress';
582
681
 
682
+ var isExpanded = expandedTaskId === node.id;
583
683
  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 }); },
684
+ key: node.id,
685
+ className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
686
+ onClick: function() { toggleExpand(t); },
687
+ onDoubleClick: function() { openTaskDetail(t); },
688
+ onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
689
+ onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
589
690
  onMouseLeave: function() { setHoveredId(null); },
590
691
  style: {
591
- position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
692
+ position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
592
693
  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',
694
+ border: '1px solid ' + (isExpanded ? sc : isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
695
+ borderLeft: '3px solid ' + sc,
696
+ borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
697
+ transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
698
+ backdropFilter: 'blur(6px)',
699
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
599
700
  },
600
701
  },
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)
702
+ // Agent + title row
703
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
704
+ 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' } },
705
+ (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
706
+ ),
707
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
605
708
  ),
606
- // Status + priority + time
709
+ // Status + agent name + time
607
710
  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))
711
+ tag(sc, t.status.replace('_', ' ')),
712
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.4)' } }, t.assignedToName || t.assignedTo),
713
+ t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
714
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
611
715
  ),
612
716
  // 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 } })
717
+ isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
718
+ h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
615
719
  )
616
720
  );
617
- })
721
+ }),
722
+
723
+ // ── Expanded chain flow (rendered ON the canvas below clicked node) ──
724
+ expandedChain && expandedChain.length > 0 && (function() {
725
+ // Find the clicked node position to anchor below it
726
+ var anchor = nodes.find(function(n) { return n.id === expandedTaskId; });
727
+ if (!anchor) return null;
728
+ var flowY = anchor.y + anchor.h + 20;
729
+ var flowX = anchor.x;
730
+ var STEP_W = 120;
731
+ var STEP_H = 44;
732
+ var STEP_GAP = 48;
733
+ var ARROW_W = STEP_GAP;
734
+
735
+ // Build person-centric flow steps: createdBy → assignedTo for each chain task, then final status
736
+ var steps = [];
737
+ expandedChain.forEach(function(ct, i) {
738
+ if (i === 0 && ct.createdBy && ct.createdBy !== 'system') {
739
+ steps.push({ label: ct.createdByName || ct.createdBy, type: 'person', isHuman: ct.createdBy.indexOf('agent') === -1 && ct.createdBy !== 'system', status: null, arrow: ct.delegationType || 'assigned' });
740
+ } else if (i === 0 && ct.createdBy === 'system') {
741
+ steps.push({ label: 'System', type: 'system', isHuman: false, status: null, arrow: 'assigned' });
742
+ }
743
+ steps.push({ label: ct.assignedToName || ct.assignedTo, type: 'agent', isHuman: false, status: ct.status, taskId: ct.id, arrow: i < expandedChain.length - 1 ? (expandedChain[i + 1].delegationType || 'delegation') : null, duration: ct.actualDurationMs, progress: ct.progress });
744
+ });
745
+ // Add final status node
746
+ var lastTask = expandedChain[expandedChain.length - 1];
747
+ var isDone = lastTask.status === 'completed' || lastTask.status === 'failed' || lastTask.status === 'cancelled';
748
+ if (isDone) {
749
+ steps.push({ label: lastTask.status === 'completed' ? 'Completed!' : lastTask.status === 'failed' ? 'Failed' : 'Cancelled', type: 'terminal', isHuman: false, status: lastTask.status, arrow: null });
750
+ }
751
+
752
+ var totalW = steps.length * STEP_W + (steps.length - 1) * STEP_GAP;
753
+
754
+ return h('div', { style: { position: 'absolute', left: flowX, top: flowY, pointerEvents: 'auto' } },
755
+ // Background card
756
+ h('div', { style: { background: 'rgba(10,12,20,0.85)', border: '1px solid rgba(99,102,241,0.2)', borderRadius: 12, padding: '14px 16px 12px', backdropFilter: 'blur(8px)' } },
757
+ // Header
758
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 } },
759
+ h('span', { style: { fontSize: 10, fontWeight: 600, color: 'rgba(255,255,255,0.4)', letterSpacing: '0.06em' } }, 'TASK FLOW'),
760
+ expandedChain[0].chainId && h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.2)', fontFamily: 'var(--font-mono)' } }, '#' + expandedChain[0].chainId.slice(0, 8)),
761
+ h('div', { style: { flex: 1 } }),
762
+ h('button', { className: 'tp-node', onClick: function() { setExpandedTaskId(null); }, style: { background: 'none', border: 'none', color: 'rgba(255,255,255,0.3)', cursor: 'pointer', fontSize: 14, padding: '0 2px' } }, '\u00D7')
763
+ ),
764
+ // Flow
765
+ h('div', { style: { position: 'relative', height: STEP_H + 8, minWidth: totalW } },
766
+ // SVG arrows
767
+ h('svg', { width: totalW, height: STEP_H + 8, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none' } },
768
+ h('defs', null,
769
+ h('marker', { id: 'fc-arr', markerWidth: 6, markerHeight: 4, refX: 6, refY: 2, orient: 'auto' },
770
+ h('polygon', { points: '0 0, 6 2, 0 4', fill: 'rgba(99,102,241,0.5)' })
771
+ )
772
+ ),
773
+ steps.map(function(step, i) {
774
+ if (i === steps.length - 1 || !step.arrow) return null;
775
+ var x1 = i * (STEP_W + STEP_GAP) + STEP_W;
776
+ var x2 = (i + 1) * (STEP_W + STEP_GAP);
777
+ var y = 4 + STEP_H / 2;
778
+ var arrowColor = DELEGATION_COLORS[step.arrow] || 'rgba(99,102,241,0.5)';
779
+ var nextStep = steps[i + 1];
780
+ var isFlowActive = step.status === 'in_progress' || (nextStep && nextStep.status === 'in_progress');
781
+ return h(Fragment, { key: 'a' + i },
782
+ h('line', { x1: x1, y1: y, x2: x2, y2: y, stroke: arrowColor + '88', strokeWidth: 2, markerEnd: 'url(#fc-arr)' }),
783
+ isFlowActive && 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 } }),
784
+ step.arrow !== 'assigned' && step.arrow !== 'delegation' && h('text', { x: (x1 + x2) / 2, y: y - 6, fill: arrowColor, fontSize: 8, textAnchor: 'middle', fontWeight: 600 }, step.arrow)
785
+ );
786
+ })
787
+ ),
788
+ // Step nodes
789
+ steps.map(function(step, i) {
790
+ var x = i * (STEP_W + STEP_GAP);
791
+ var sc = step.type === 'terminal'
792
+ ? (STATUS_COLORS[step.status] || '#22c55e')
793
+ : step.type === 'person' || step.isHuman
794
+ ? '#f59e0b'
795
+ : step.status ? (STATUS_COLORS[step.status] || '#6366f1') : '#6366f1';
796
+ var isTerminal = step.type === 'terminal';
797
+ var isMe = step.taskId === expandedTaskId;
798
+
799
+ return h('div', {
800
+ key: i,
801
+ className: 'tp-node',
802
+ onClick: function(e) { e.stopPropagation(); if (step.taskId) { var ct = expandedChain.find(function(c) { return c.id === step.taskId; }); if (ct) openTaskDetail(ct); } },
803
+ style: {
804
+ position: 'absolute', left: x, top: 4, width: STEP_W, height: STEP_H,
805
+ background: isTerminal ? sc + '15' : isMe ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)',
806
+ border: '1.5px solid ' + (isTerminal ? sc + '44' : isMe ? sc : 'rgba(255,255,255,0.1)'),
807
+ borderRadius: isTerminal ? 22 : 10,
808
+ display: 'flex', alignItems: 'center', gap: 8, padding: '0 10px',
809
+ cursor: step.taskId ? 'pointer' : 'default',
810
+ },
811
+ },
812
+ // Avatar
813
+ h('div', { style: {
814
+ width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
815
+ background: isTerminal ? sc + '33' : step.isHuman || step.type === 'person' ? 'linear-gradient(135deg, #f59e0b, #f97316)' : step.type === 'system' ? 'rgba(255,255,255,0.1)' : 'linear-gradient(135deg, #6366f1, #8b5cf6)',
816
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
817
+ fontSize: isTerminal ? 12 : 10, fontWeight: 700,
818
+ color: isTerminal ? sc : '#fff',
819
+ border: '2px solid ' + (isTerminal ? sc + '44' : 'transparent'),
820
+ } },
821
+ isTerminal ? (step.status === 'completed' ? '\u2714' : step.status === 'failed' ? '\u2716' : '\u2716') : step.label.charAt(0).toUpperCase()
822
+ ),
823
+ // Info
824
+ h('div', { style: { overflow: 'hidden', flex: 1, minWidth: 0 } },
825
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: isTerminal ? sc : '#fff', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } }, step.label),
826
+ !isTerminal && h('div', { style: { fontSize: 9, color: 'rgba(255,255,255,0.35)', marginTop: 1 } },
827
+ step.type === 'person' || step.isHuman ? 'Human' : step.type === 'system' ? 'System' : 'Agent',
828
+ step.duration ? ' \u00B7 ' + formatDuration(step.duration) : '',
829
+ step.status === 'in_progress' && step.progress > 0 ? ' \u00B7 ' + step.progress + '%' : ''
830
+ )
831
+ )
832
+ );
833
+ })
834
+ )
835
+ )
836
+ );
837
+ })()
618
838
  )
619
839
  ),
620
840
 
621
841
  // Hover tooltip
622
- hoveredNode && !hoveredNode.isAgent && hoveredNode.task && h('div', { style: {
842
+ hoveredNode && hoveredNode.task && h('div', { style: {
623
843
  position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
624
844
  background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
625
845
  border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
626
- padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 200, maxWidth: 300,
846
+ padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
627
847
  }},
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)
848
+ hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
849
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.task.title),
850
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
851
+ 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 || '-')),
852
+ 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('_', ' '))),
853
+ 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))),
854
+ 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)),
855
+ 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
856
  )
639
857
  ),
640
858
 
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
- ),
651
-
652
- // Task detail modal
653
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
859
+ // Detail modal (double-click)
860
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
654
861
  );
655
862
  }
656
863
 
657
- // ─── Agent Task Pipeline (for agent-detail workforce tab) ─
658
- // Reusable mini version scoped to a single agent
864
+ // ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
659
865
  export function AgentTaskPipeline(props) {
660
866
  var agentId = props.agentId;
661
867
  var _tasks = useState([]);
@@ -672,8 +878,6 @@ export function AgentTaskPipeline(props) {
672
878
  engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
673
879
  setTasks(res?.tasks || []);
674
880
  }).catch(function() {}).finally(function() { setLoading(false); });
675
-
676
- // SSE
677
881
  var baseUrl = window.__ENGINE_BASE || '/api/engine';
678
882
  var es;
679
883
  try {
@@ -709,57 +913,46 @@ export function AgentTaskPipeline(props) {
709
913
  var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
710
914
 
711
915
  function renderTaskRow(t) {
712
- var statusColor = STATUS_COLORS[t.status] || '#6b7394';
713
- var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
916
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
714
917
  return h('div', {
715
918
  key: t.id,
716
919
  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
- },
920
+ style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
722
921
  onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
723
922
  onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
724
923
  },
725
- h('span', { style: { fontSize: 14, flexShrink: 0 } }, catIcon),
924
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: sc, flexShrink: 0 } }),
726
925
  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))
926
+ h('div', { style: { fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
927
+ h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 4, alignItems: 'center' } },
928
+ t.category,
929
+ t.delegationType && h('span', { style: { color: DELEGATION_COLORS[t.delegationType] || '#6b7394' } }, '\u2192 ' + t.delegationType),
930
+ h('span', null, '\u00B7 ' + timeAgo(t.createdAt))
931
+ )
729
932
  ),
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))
933
+ t.status === 'in_progress' && t.progress > 0 && h('span', { style: { fontSize: 10, color: sc, fontWeight: 600 } }, t.progress + '%'),
934
+ h('span', { style: { padding: '2px 6px', borderRadius: 8, fontSize: 9, fontWeight: 600, background: sc + '22', color: sc, flexShrink: 0 } }, t.status.replace('_', ' ')),
935
+ t.actualDurationMs && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
733
936
  );
734
937
  }
735
938
 
736
939
  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' } }),
940
+ active.length > 0 && h('div', { style: { marginBottom: 12 } },
941
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
942
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
741
943
  'Active (' + active.length + ')'
742
944
  ),
743
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
744
- active.map(renderTaskRow)
745
- )
945
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, active.map(renderTaskRow))
746
946
  ),
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')
947
+ completed.length > 0 && h('div', { style: { marginBottom: 12 } },
948
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 6 } }, 'Completed (' + completed.length + ')'),
949
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, completed.slice(0, 10).map(renderTaskRow)),
950
+ completed.length > 10 && h('div', { style: { padding: 6, textAlign: 'center', fontSize: 11, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
754
951
  ),
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
- )
952
+ failed.length > 0 && h('div', { style: { marginBottom: 12 } },
953
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
954
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
761
955
  ),
762
- // Detail modal
763
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
956
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
764
957
  );
765
958
  }