@agenticmail/enterprise 0.5.208 → 0.5.210

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)
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)
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
+ )
206
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
  )
@@ -236,6 +310,7 @@ function TaskDetail(props) {
236
310
 
237
311
  // ─── Main Page ───────────────────────────────────────────
238
312
  export function TaskPipelinePage() {
313
+ injectCSS();
239
314
  var app = useApp();
240
315
  var toast = app.toast;
241
316
  var _tasks = useState([]);
@@ -246,10 +321,10 @@ export function TaskPipelinePage() {
246
321
  var loading = _loading[0]; var setLoading = _loading[1];
247
322
  var _selectedTask = useState(null);
248
323
  var selectedTask = _selectedTask[0]; var setSelectedTask = _selectedTask[1];
324
+ var _selectedChain = useState(null);
325
+ var selectedChain = _selectedChain[0]; var setSelectedChain = _selectedChain[1];
249
326
  var _hoveredId = useState(null);
250
327
  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
328
  var _zoom = useState(1);
254
329
  var zoom = _zoom[0]; var setZoom = _zoom[1];
255
330
  var _pan = useState({ x: 0, y: 0 });
@@ -260,6 +335,8 @@ export function TaskPipelinePage() {
260
335
  var dragStart = _dragStart[0]; var setDragStart = _dragStart[1];
261
336
  var _filter = useState('active');
262
337
  var filter = _filter[0]; var setFilter = _filter[1];
338
+ var _mousePos = useState({ x: 0, y: 0 });
339
+ var mousePos = _mousePos[0]; var setMousePos = _mousePos[1];
263
340
  var containerRef = useRef(null);
264
341
 
265
342
  var loadData = useCallback(function() {
@@ -270,9 +347,8 @@ export function TaskPipelinePage() {
270
347
  ]).then(function(res) {
271
348
  setTasks(res[0]?.tasks || []);
272
349
  setStats(res[1] || stats);
273
- }).catch(function(err) {
274
- console.error('[TaskPipeline] load:', err);
275
- }).finally(function() { setLoading(false); });
350
+ }).catch(function(err) { console.error('[TaskPipeline]', err); })
351
+ .finally(function() { setLoading(false); });
276
352
  }, []);
277
353
 
278
354
  // SSE
@@ -299,10 +375,7 @@ export function TaskPipelinePage() {
299
375
  if (idx >= 0) { var next = prev.slice(); next[idx] = event.task; return next; }
300
376
  return [event.task].concat(prev);
301
377
  });
302
- setSelectedTask(function(prev) {
303
- if (prev && prev.id === event.task.id) return event.task;
304
- return prev;
305
- });
378
+ setSelectedTask(function(prev) { return prev && prev.id === event.task.id ? event.task : prev; });
306
379
  }
307
380
  } catch (err) {}
308
381
  };
@@ -310,7 +383,6 @@ export function TaskPipelinePage() {
310
383
  return function() { if (es) es.close(); };
311
384
  }, []);
312
385
 
313
- // Periodic stats
314
386
  useEffect(function() {
315
387
  var iv = setInterval(function() {
316
388
  engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
@@ -326,6 +398,18 @@ export function TaskPipelinePage() {
326
398
  }).catch(function(err) { toast(err.message || 'Failed', 'error'); });
327
399
  }, []);
328
400
 
401
+ function openTaskDetail(t) {
402
+ setSelectedTask(t);
403
+ // Load full chain if task has one
404
+ if (t.chainId) {
405
+ var chainTasks = tasks.filter(function(ct) { return ct.chainId === t.chainId; });
406
+ chainTasks.sort(function(a, b) { return (a.chainSeq || 0) - (b.chainSeq || 0); });
407
+ setSelectedChain(chainTasks.length > 1 ? chainTasks : null);
408
+ } else {
409
+ setSelectedChain(null);
410
+ }
411
+ }
412
+
329
413
  // Filter
330
414
  var filtered = tasks.filter(function(t) {
331
415
  if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
@@ -335,34 +419,28 @@ export function TaskPipelinePage() {
335
419
  });
336
420
 
337
421
  // Layout
338
- var layout = layoutPipeline(filtered);
339
- var positioned = layout.positioned;
422
+ var layout = layoutChains(filtered);
423
+ var nodes = layout.nodes;
340
424
  var edges = layout.edges;
341
425
  var treeW = layout.width;
342
426
  var treeH = layout.height;
427
+ var chainInfos = layout.chains;
343
428
 
344
- // Zoom
429
+ // Zoom/Pan handlers
345
430
  var handleWheel = useCallback(function(e) {
346
431
  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)); });
432
+ setZoom(function(z) { return Math.min(3, Math.max(0.15, z + (e.deltaY > 0 ? -0.08 : 0.08))); });
349
433
  }, []);
350
-
351
- // Pan
352
434
  var handleMouseDown = useCallback(function(e) {
353
- if (e.button !== 0) return;
354
- if (e.target.closest('.task-node')) return;
435
+ if (e.button !== 0 || e.target.closest('.tp-node')) return;
355
436
  setDragging(true);
356
437
  setDragStart({ x: e.clientX - pan.x, y: e.clientY - pan.y });
357
438
  }, [pan]);
358
-
359
439
  var handleMouseMove = useCallback(function(e) {
360
440
  if (!dragging) return;
361
441
  setPan({ x: e.clientX - dragStart.x, y: e.clientY - dragStart.y });
362
442
  }, [dragging, dragStart]);
363
-
364
443
  var handleMouseUp = useCallback(function() { setDragging(false); }, []);
365
-
366
444
  useEffect(function() {
367
445
  if (dragging) {
368
446
  window.addEventListener('mousemove', handleMouseMove);
@@ -371,7 +449,6 @@ export function TaskPipelinePage() {
371
449
  }
372
450
  }, [dragging, handleMouseMove, handleMouseUp]);
373
451
 
374
- // Fit
375
452
  var fitToView = useCallback(function() {
376
453
  if (!containerRef.current || !treeW || !treeH) return;
377
454
  var rect = containerRef.current.getBoundingClientRect();
@@ -379,128 +456,81 @@ export function TaskPipelinePage() {
379
456
  var scaleY = (rect.height - 40) / treeH;
380
457
  var scale = Math.min(scaleX, scaleY, 1.5);
381
458
  setZoom(scale);
382
- setPan({ x: (rect.width - treeW * scale) / 2, y: (rect.height - treeH * scale) / 2 });
459
+ setPan({ x: (rect.width - treeW * scale) / 2, y: 20 });
383
460
  }, [treeW, treeH]);
461
+ useEffect(function() { if (nodes.length > 0) fitToView(); }, [nodes.length]);
384
462
 
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;
463
+ // Highlight connected chain on hover
464
+ var hoveredChainId = null;
465
+ if (hoveredId) {
466
+ var hn = nodes.find(function(n) { return n.id === hoveredId; });
467
+ if (hn) hoveredChainId = hn.chainId;
468
+ }
408
469
 
409
- // Hovered node for tooltip
410
- var hoveredNode = hoveredId ? positioned.find(function(n) { return (n.id || n.agentId) === hoveredId; }) : null;
470
+ var hoveredNode = hoveredId ? nodes.find(function(n) { return n.id === hoveredId; }) : null;
471
+
472
+ // ─── Toolbar ─────────────────────────────────────────
473
+ 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' } },
474
+ h('div', { style: { fontWeight: 700, fontSize: 14, color: '#fff', display: 'flex', alignItems: 'center', gap: 6 } },
475
+ I.workflow(), 'Task Pipeline',
476
+ h(HelpButton, { label: 'Task Pipeline' },
477
+ h('p', null, 'Visual flow of all agent tasks. Tasks flow left-to-right showing delegation chains, multi-agent handoffs, and circular review loops.'),
478
+ h('h4', { style: _h4 }, 'Features'),
479
+ h('ul', { style: _ul },
480
+ h('li', null, h('strong', null, 'Horizontal flow'), ' \u2014 Tasks move left to right through agents'),
481
+ h('li', null, h('strong', null, 'Chain tracking'), ' \u2014 When a manager delegates to a junior, the chain shows the full flow'),
482
+ h('li', null, h('strong', null, 'Circular flows'), ' \u2014 If a task returns (revision/review), the arc curves back'),
483
+ h('li', null, h('strong', null, 'Animated lines'), ' \u2014 Active tasks show flowing dashes on their connections'),
484
+ h('li', null, h('strong', null, 'Customer profiles'), ' \u2014 Support tasks show the customer\'s info'),
485
+ h('li', null, h('strong', null, 'Real-time SSE'), ' \u2014 Updates stream live, no refresh needed')
486
+ ),
487
+ h('h4', { style: _h4 }, 'Interactions'),
488
+ h('ul', { style: _ul },
489
+ h('li', null, h('strong', null, 'Hover'), ' \u2014 Highlights the entire chain'),
490
+ h('li', null, h('strong', null, 'Click'), ' \u2014 Opens detail modal with chain timeline, activity log, customer context'),
491
+ h('li', null, h('strong', null, 'Scroll'), ' \u2014 Zoom'),
492
+ h('li', null, h('strong', null, 'Drag'), ' \u2014 Pan')
493
+ ),
494
+ 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.')
495
+ )
496
+ ),
497
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } },
498
+ h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'flowPulse 2s infinite' } }),
499
+ h('span', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } }, 'Live')
500
+ ),
501
+ h('div', { style: { color: 'rgba(255,255,255,0.4)', fontSize: 11 } },
502
+ (stats.inProgress || 0) + ' active \u00B7 ' + (stats.completed || 0) + ' done \u00B7 ' + (stats.total || 0) + ' total'
503
+ ),
504
+ h('div', { style: { flex: 1 } }),
505
+ // Filters
506
+ ['active', 'all', 'completed', 'failed'].map(function(f) {
507
+ 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));
508
+ }),
509
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
510
+ legendDot(STATUS_COLORS.in_progress, 'Active'),
511
+ legendDot(STATUS_COLORS.completed, 'Done'),
512
+ legendDot(STATUS_COLORS.failed, 'Failed'),
513
+ h('div', { style: { width: 1, height: 14, background: 'rgba(255,255,255,0.12)' } }),
514
+ h('button', { onClick: function() { setZoom(function(z) { return Math.min(3, z + 0.2); }); }, style: toolbarBtnStyle }, '+'),
515
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 11, minWidth: 36, textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
516
+ h('button', { onClick: function() { setZoom(function(z) { return Math.max(0.15, z - 0.2); }); }, style: toolbarBtnStyle }, '\u2212'),
517
+ h('button', { onClick: fitToView, style: toolbarBtnStyle }, 'Fit'),
518
+ h('button', { onClick: loadData, style: toolbarBtnStyle }, 'Refresh'),
519
+ );
411
520
 
412
521
  if (loading) return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
413
522
 
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
- ),
523
+ if (nodes.length === 0) return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: BG, borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
524
+ toolbar,
439
525
  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.')
526
+ h('div', { style: { width: 48, height: 48, borderRadius: 12, background: 'rgba(99,102,241,0.1)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 16, color: '#6366f1' } }, I.workflow()),
527
+ h('div', { style: { fontSize: 16, fontWeight: 600, marginBottom: 6, color: '#fff' } }, 'No Tasks in Pipeline'),
528
+ h('div', { style: { color: 'rgba(255,255,255,0.5)', fontSize: 13 } }, 'Tasks will appear here as agents are assigned work.')
443
529
  )
444
530
  );
445
531
 
446
532
  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
- ),
503
-
533
+ toolbar,
504
534
  // Canvas
505
535
  h('div', {
506
536
  ref: containerRef,
@@ -509,109 +539,108 @@ export function TaskPipelinePage() {
509
539
  onWheel: handleWheel,
510
540
  },
511
541
  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' } },
542
+
543
+ // Chain labels (left side)
544
+ chainInfos.map(function(ci, i) {
545
+ 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' } },
546
+ ci.customer && h(CustomerBadge, { customer: ci.customer })
547
+ );
548
+ }),
549
+
550
+ // SVG edges with animated flow
551
+ h('svg', { width: treeW + 100, height: treeH + 100, style: { position: 'absolute', top: 0, left: 0, pointerEvents: 'none', overflow: 'visible' } },
514
552
  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 })
553
+ h('marker', { id: 'tp-arr', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
554
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_COLOR })
555
+ ),
556
+ h('marker', { id: 'tp-arr-hl', markerWidth: 7, markerHeight: 5, refX: 7, refY: 2.5, orient: 'auto' },
557
+ h('polygon', { points: '0 0, 7 2.5, 0 5', fill: EDGE_HL })
517
558
  ),
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 })
559
+ // Animated glow filter
560
+ h('filter', { id: 'tp-glow' },
561
+ h('feGaussianBlur', { stdDeviation: 2, result: 'blur' }),
562
+ h('feMerge', null, h('feMergeNode', { in: 'blur' }), h('feMergeNode', { in: 'SourceGraphic' }))
520
563
  )
521
564
  ),
522
565
  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
- });
566
+ var fromId = e.from.id;
567
+ var toId = e.to.id;
568
+ var isHl = hoveredChainId && e.from.chainId === hoveredChainId;
569
+ var dim = hoveredChainId && !isHl;
570
+ var dType = e.delegationType || 'delegation';
571
+ var edgeColor = isHl ? EDGE_HL : dim ? 'rgba(255,255,255,0.06)' : (DELEGATION_COLORS[dType] || EDGE_COLOR) + '88';
572
+ var d = e.isCircular ? circularPath(e.from, e.to) : horizontalPath(e.from, e.to);
573
+
574
+ return h(Fragment, { key: i },
575
+ // Base path
576
+ h('path', {
577
+ d: d, stroke: edgeColor, strokeWidth: isHl ? 2.5 : 1.5, fill: 'none',
578
+ markerEnd: isHl ? 'url(#tp-arr-hl)' : 'url(#tp-arr)',
579
+ style: { transition: 'stroke 0.2s, opacity 0.2s', opacity: dim ? 0.2 : 1 },
580
+ }),
581
+ // Animated flow dash overlay for active tasks
582
+ e.isActive && h('path', {
583
+ d: d, stroke: STATUS_COLORS.in_progress, strokeWidth: 2, fill: 'none',
584
+ strokeDasharray: '6 18',
585
+ className: 'tp-flow-active',
586
+ filter: 'url(#tp-glow)',
587
+ style: { opacity: dim ? 0.1 : 0.7 },
588
+ }),
589
+ // Delegation type label on edge
590
+ !dim && dType !== 'delegation' && h('text', {
591
+ x: (e.from.x + e.from.w + e.to.x) / 2,
592
+ y: (e.from.y + e.to.y) / 2 + (e.from.h / 2) - (e.isCircular ? 20 : 6),
593
+ fill: (DELEGATION_COLORS[dType] || 'rgba(255,255,255,0.3)') + (dim ? '33' : ''),
594
+ fontSize: 8, textAnchor: 'middle', fontWeight: 600,
595
+ }, dType)
596
+ );
536
597
  })
537
598
  ),
538
599
 
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
600
+ // Task nodes
601
+ nodes.map(function(node) {
578
602
  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';
603
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
604
+ var isHovered = hoveredId === node.id;
605
+ var isChainHl = hoveredChainId && node.chainId === hoveredChainId;
606
+ var dim = hoveredChainId && !isChainHl;
607
+ var isActive = t.status === 'in_progress';
582
608
 
583
609
  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 }); },
610
+ key: node.id,
611
+ className: 'tp-node' + (isActive ? ' tp-node-active' : ''),
612
+ onClick: function() { openTaskDetail(t); },
613
+ onMouseEnter: function(ev) { setHoveredId(node.id); setMousePos({ x: ev.clientX, y: ev.clientY }); },
614
+ onMouseMove: function(ev) { setMousePos({ x: ev.clientX, y: ev.clientY }); },
589
615
  onMouseLeave: function() { setHoveredId(null); },
590
616
  style: {
591
- position: 'absolute', left: node.x, top: node.y, width: NODE_W, height: NODE_H,
617
+ position: 'absolute', left: node.x, top: node.y, width: node.w, height: node.h,
592
618
  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',
619
+ border: '1px solid ' + (isHovered || isChainHl ? sc + '66' : 'rgba(255,255,255,0.1)'),
620
+ borderLeft: '3px solid ' + sc,
621
+ borderRadius: 10, padding: '6px 10px', cursor: 'pointer',
622
+ transition: 'all 0.15s', opacity: dim ? 0.15 : 1,
623
+ backdropFilter: 'blur(6px)',
624
+ display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: 3, userSelect: 'none',
599
625
  },
600
626
  },
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)
627
+ // Agent + title row
628
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 5 } },
629
+ 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' } },
630
+ (t.assignedToName || t.assignedTo || '?').charAt(0).toUpperCase()
631
+ ),
632
+ h('span', { style: { fontSize: 11, fontWeight: 600, color: '#fff', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title)
605
633
  ),
606
- // Status + priority + time
634
+ // Status + agent name + time
607
635
  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))
636
+ tag(sc, t.status.replace('_', ' ')),
637
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.4)' } }, t.assignedToName || t.assignedTo),
638
+ t.delegationType && tag(DELEGATION_COLORS[t.delegationType] || '#6b7394', t.delegationType),
639
+ h('span', { style: { fontSize: 9, color: 'rgba(255,255,255,0.3)', marginLeft: 'auto' } }, timeAgo(t.createdAt))
611
640
  ),
612
641
  // 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 } })
642
+ isActive && t.progress > 0 && h('div', { style: { height: 2, background: 'rgba(255,255,255,0.08)', borderRadius: 1, overflow: 'hidden', marginTop: 1 } },
643
+ h('div', { style: { height: '100%', width: t.progress + '%', background: sc, borderRadius: 1, transition: 'width 0.3s' } })
615
644
  )
616
645
  );
617
646
  })
@@ -619,43 +648,29 @@ export function TaskPipelinePage() {
619
648
  ),
620
649
 
621
650
  // Hover tooltip
622
- hoveredNode && !hoveredNode.isAgent && hoveredNode.task && h('div', { style: {
651
+ hoveredNode && hoveredNode.task && h('div', { style: {
623
652
  position: 'fixed', left: mousePos.x + 16, top: mousePos.y - 10,
624
653
  background: 'rgba(15,17,23,0.95)', backdropFilter: 'blur(12px)',
625
654
  border: '1px solid rgba(255,255,255,0.15)', borderRadius: 10,
626
- padding: '12px 16px', pointerEvents: 'none', zIndex: 1000, minWidth: 200, maxWidth: 300,
655
+ padding: '10px 14px', pointerEvents: 'none', zIndex: 1000, minWidth: 180, maxWidth: 280,
627
656
  }},
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)
657
+ hoveredNode.task.customerContext && h(CustomerBadge, { customer: hoveredNode.task.customerContext }),
658
+ h('div', { style: { fontSize: 12, fontWeight: 600, color: '#fff', marginBottom: 6 } }, hoveredNode.task.title),
659
+ h('div', { style: { display: 'flex', flexDirection: 'column', gap: 3, fontSize: 11 } },
660
+ 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 || '-')),
661
+ 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('_', ' '))),
662
+ 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))),
663
+ 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)),
664
+ 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
665
  )
639
666
  ),
640
667
 
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 })
668
+ // Detail modal
669
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: selectedChain, onClose: function() { setSelectedTask(null); setSelectedChain(null); }, onCancel: cancelTask })
654
670
  );
655
671
  }
656
672
 
657
- // ─── Agent Task Pipeline (for agent-detail workforce tab) ─
658
- // Reusable mini version scoped to a single agent
673
+ // ─── Agent Task Pipeline (reusable mini for agent-detail workforce tab) ─
659
674
  export function AgentTaskPipeline(props) {
660
675
  var agentId = props.agentId;
661
676
  var _tasks = useState([]);
@@ -672,8 +687,6 @@ export function AgentTaskPipeline(props) {
672
687
  engineCall('/task-pipeline/agent/' + agentId + '?completed=true').then(function(res) {
673
688
  setTasks(res?.tasks || []);
674
689
  }).catch(function() {}).finally(function() { setLoading(false); });
675
-
676
- // SSE
677
690
  var baseUrl = window.__ENGINE_BASE || '/api/engine';
678
691
  var es;
679
692
  try {
@@ -709,57 +722,46 @@ export function AgentTaskPipeline(props) {
709
722
  var failed = tasks.filter(function(t) { return t.status === 'failed' || t.status === 'cancelled'; });
710
723
 
711
724
  function renderTaskRow(t) {
712
- var statusColor = STATUS_COLORS[t.status] || '#6b7394';
713
- var catIcon = CATEGORY_ICONS[t.category] || '\uD83D\uDCCB';
725
+ var sc = STATUS_COLORS[t.status] || '#6b7394';
714
726
  return h('div', {
715
727
  key: t.id,
716
728
  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
- },
729
+ style: { display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', borderBottom: '1px solid var(--border)', cursor: 'pointer', transition: 'background 0.15s' },
722
730
  onMouseEnter: function(e) { e.currentTarget.style.background = 'var(--bg-secondary)'; },
723
731
  onMouseLeave: function(e) { e.currentTarget.style.background = ''; },
724
732
  },
725
- h('span', { style: { fontSize: 14, flexShrink: 0 } }, catIcon),
733
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: sc, flexShrink: 0 } }),
726
734
  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))
735
+ h('div', { style: { fontSize: 12, fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, t.title),
736
+ h('div', { style: { fontSize: 10, color: 'var(--text-muted)', marginTop: 2, display: 'flex', gap: 4, alignItems: 'center' } },
737
+ t.category,
738
+ t.delegationType && h('span', { style: { color: DELEGATION_COLORS[t.delegationType] || '#6b7394' } }, '\u2192 ' + t.delegationType),
739
+ h('span', null, '\u00B7 ' + timeAgo(t.createdAt))
740
+ )
729
741
  ),
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))
742
+ t.status === 'in_progress' && t.progress > 0 && h('span', { style: { fontSize: 10, color: sc, fontWeight: 600 } }, t.progress + '%'),
743
+ h('span', { style: { padding: '2px 6px', borderRadius: 8, fontSize: 9, fontWeight: 600, background: sc + '22', color: sc, flexShrink: 0 } }, t.status.replace('_', ' ')),
744
+ t.actualDurationMs && h('span', { style: { fontSize: 10, color: 'var(--text-muted)', flexShrink: 0 } }, formatDuration(t.actualDurationMs))
733
745
  );
734
746
  }
735
747
 
736
748
  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' } }),
749
+ active.length > 0 && h('div', { style: { marginBottom: 12 } },
750
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
751
+ h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
741
752
  'Active (' + active.length + ')'
742
753
  ),
743
- h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
744
- active.map(renderTaskRow)
745
- )
754
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, active.map(renderTaskRow))
746
755
  ),
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')
756
+ completed.length > 0 && h('div', { style: { marginBottom: 12 } },
757
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.completed, marginBottom: 6 } }, 'Completed (' + completed.length + ')'),
758
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, completed.slice(0, 10).map(renderTaskRow)),
759
+ completed.length > 10 && h('div', { style: { padding: 6, textAlign: 'center', fontSize: 11, color: 'var(--text-muted)' } }, '+ ' + (completed.length - 10) + ' more')
754
760
  ),
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
- )
761
+ failed.length > 0 && h('div', { style: { marginBottom: 12 } },
762
+ h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.failed, marginBottom: 6 } }, 'Failed (' + failed.length + ')'),
763
+ h('div', { style: { border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } }, failed.slice(0, 5).map(renderTaskRow))
761
764
  ),
762
- // Detail modal
763
- selectedTask && h(TaskDetail, { task: selectedTask, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
765
+ selectedTask && h(TaskDetail, { task: selectedTask, chain: null, onClose: function() { setSelectedTask(null); }, onCancel: cancelTask })
764
766
  );
765
767
  }