@agenticmail/enterprise 0.5.200 → 0.5.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-heartbeat-EGMBRD3R.js +510 -0
- package/dist/chunk-5C3SCMY5.js +4457 -0
- package/dist/chunk-6OZYUTPL.js +1224 -0
- package/dist/chunk-ZR4Z42HT.js +3679 -0
- package/dist/cli-agent-PLMDHMRR.js +1602 -0
- package/dist/cli-serve-PLBAWN7N.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/app.js +3 -0
- package/dist/dashboard/components/icons.js +1 -0
- package/dist/dashboard/pages/task-pipeline.js +455 -0
- package/dist/index.js +3 -3
- package/dist/routes-KHABOHOV.js +13273 -0
- package/dist/runtime-6WFHCG3N.js +45 -0
- package/dist/server-BENJQHTB.js +15 -0
- package/dist/setup-7RQIFV5Y.js +20 -0
- package/package.json +1 -1
- package/src/dashboard/app.js +3 -0
- package/src/dashboard/components/icons.js +1 -0
- package/src/dashboard/pages/task-pipeline.js +455 -0
- package/src/engine/model-fallback.ts +141 -0
- package/src/engine/routes.ts +5 -0
- package/src/engine/task-queue-after-spawn.ts +66 -0
- package/src/engine/task-queue-before-spawn.ts +109 -0
- package/src/engine/task-queue-routes.ts +133 -0
- package/src/engine/task-queue.ts +369 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentRuntime,
|
|
3
|
+
EmailChannel,
|
|
4
|
+
FollowUpScheduler,
|
|
5
|
+
SessionManager,
|
|
6
|
+
SubAgentManager,
|
|
7
|
+
ToolRegistry,
|
|
8
|
+
callLLM,
|
|
9
|
+
createAgentRuntime,
|
|
10
|
+
createNoopHooks,
|
|
11
|
+
createRuntimeHooks,
|
|
12
|
+
estimateMessageTokens,
|
|
13
|
+
estimateTokens,
|
|
14
|
+
executeTool,
|
|
15
|
+
runAgentLoop,
|
|
16
|
+
toolsToDefinitions
|
|
17
|
+
} from "./chunk-5C3SCMY5.js";
|
|
18
|
+
import {
|
|
19
|
+
PROVIDER_REGISTRY,
|
|
20
|
+
listAllProviders,
|
|
21
|
+
resolveApiKeyForProvider,
|
|
22
|
+
resolveProvider
|
|
23
|
+
} from "./chunk-UF3ZJMJO.js";
|
|
24
|
+
import "./chunk-KFQGP6VL.js";
|
|
25
|
+
export {
|
|
26
|
+
AgentRuntime,
|
|
27
|
+
EmailChannel,
|
|
28
|
+
FollowUpScheduler,
|
|
29
|
+
PROVIDER_REGISTRY,
|
|
30
|
+
SessionManager,
|
|
31
|
+
SubAgentManager,
|
|
32
|
+
ToolRegistry,
|
|
33
|
+
callLLM,
|
|
34
|
+
createAgentRuntime,
|
|
35
|
+
createNoopHooks,
|
|
36
|
+
createRuntimeHooks,
|
|
37
|
+
estimateMessageTokens,
|
|
38
|
+
estimateTokens,
|
|
39
|
+
executeTool,
|
|
40
|
+
listAllProviders,
|
|
41
|
+
resolveApiKeyForProvider,
|
|
42
|
+
resolveProvider,
|
|
43
|
+
runAgentLoop,
|
|
44
|
+
toolsToDefinitions
|
|
45
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createServer
|
|
3
|
+
} from "./chunk-ZR4Z42HT.js";
|
|
4
|
+
import "./chunk-OF4MUWWS.js";
|
|
5
|
+
import "./chunk-UF3ZJMJO.js";
|
|
6
|
+
import "./chunk-3OC6RH7W.js";
|
|
7
|
+
import "./chunk-2DDKGTD6.js";
|
|
8
|
+
import "./chunk-YVK6F5OD.js";
|
|
9
|
+
import "./chunk-MKRNEM5A.js";
|
|
10
|
+
import "./chunk-DRXMYYKN.js";
|
|
11
|
+
import "./chunk-6WSX7QXF.js";
|
|
12
|
+
import "./chunk-KFQGP6VL.js";
|
|
13
|
+
export {
|
|
14
|
+
createServer
|
|
15
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
promptCompanyInfo,
|
|
3
|
+
promptDatabase,
|
|
4
|
+
promptDeployment,
|
|
5
|
+
promptDomain,
|
|
6
|
+
promptRegistration,
|
|
7
|
+
provision,
|
|
8
|
+
runSetupWizard
|
|
9
|
+
} from "./chunk-6OZYUTPL.js";
|
|
10
|
+
import "./chunk-VQQ4SYYQ.js";
|
|
11
|
+
import "./chunk-KFQGP6VL.js";
|
|
12
|
+
export {
|
|
13
|
+
promptCompanyInfo,
|
|
14
|
+
promptDatabase,
|
|
15
|
+
promptDeployment,
|
|
16
|
+
promptDomain,
|
|
17
|
+
promptRegistration,
|
|
18
|
+
provision,
|
|
19
|
+
runSetupWizard
|
|
20
|
+
};
|
package/package.json
CHANGED
package/src/dashboard/app.js
CHANGED
|
@@ -25,6 +25,7 @@ import { KnowledgeContributionsPage } from './pages/knowledge-contributions.js';
|
|
|
25
25
|
import { SkillConnectionsPage } from './pages/skill-connections.js';
|
|
26
26
|
import { VaultPage } from './pages/vault.js';
|
|
27
27
|
import { OrgChartPage } from './pages/org-chart.js';
|
|
28
|
+
import { TaskPipelinePage } from './pages/task-pipeline.js';
|
|
28
29
|
|
|
29
30
|
// ─── Toast System ────────────────────────────────────────
|
|
30
31
|
let toastId = 0;
|
|
@@ -161,6 +162,7 @@ function App() {
|
|
|
161
162
|
]},
|
|
162
163
|
{ section: 'Management', items: [
|
|
163
164
|
{ id: 'org-chart', icon: I.orgChart, label: 'Org Chart' },
|
|
165
|
+
{ id: 'task-pipeline', icon: I.workflow, label: 'Task Pipeline' },
|
|
164
166
|
{ id: 'workforce', icon: I.clock, label: 'Workforce' },
|
|
165
167
|
{ id: 'messages', icon: I.messages, label: 'Messages' },
|
|
166
168
|
{ id: 'guardrails', icon: I.guardrails, label: 'Guardrails' },
|
|
@@ -199,6 +201,7 @@ function App() {
|
|
|
199
201
|
'skill-connections': SkillConnectionsPage,
|
|
200
202
|
vault: VaultPage,
|
|
201
203
|
'org-chart': OrgChartPage,
|
|
204
|
+
'task-pipeline': TaskPipelinePage,
|
|
202
205
|
};
|
|
203
206
|
|
|
204
207
|
const navigateToAgent = (agentId) => { _setSelectedAgentId(agentId); history.pushState(null, '', '/dashboard/agents/' + agentId); };
|
|
@@ -43,6 +43,7 @@ export const I = {
|
|
|
43
43
|
link: () => h('svg', S, h('path', { d: 'M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71' }), h('path', { d: 'M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71' })),
|
|
44
44
|
folder: () => h('svg', S, h('path', { d: 'M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z' })),
|
|
45
45
|
globe: () => h('svg', S, h('circle', { cx: 12, cy: 12, r: 10 }), h('line', { x1: 2, y1: 12, x2: 22, y2: 12 }), h('path', { d: 'M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z' })),
|
|
46
|
+
workflow: () => h('svg', S, h('rect', { x: 3, y: 3, width: 6, height: 6, rx: 1 }), h('rect', { x: 15, y: 3, width: 6, height: 6, rx: 1 }), h('rect', { x: 9, y: 15, width: 6, height: 6, rx: 1 }), h('line', { x1: 9, y1: 6, x2: 15, y2: 6 }), h('line', { x1: 12, y1: 9, x2: 12, y2: 15 }), h('line', { x1: 6, y1: 9, x2: 6, y2: 18 }), h('line', { x1: 6, y1: 18, x2: 9, y2: 18 }), h('line', { x1: 18, y1: 9, x2: 18, y2: 18 }), h('line', { x1: 18, y1: 18, x2: 15, y2: 18 })),
|
|
46
47
|
orgChart: () => h('svg', S, h('rect', { x: 8, y: 2, width: 8, height: 5, rx: 1 }), h('rect', { x: 1, y: 17, width: 8, height: 5, rx: 1 }), h('rect', { x: 15, y: 17, width: 8, height: 5, rx: 1 }), h('line', { x1: 12, y1: 7, x2: 12, y2: 12 }), h('line', { x1: 5, y1: 12, x2: 19, y2: 12 }), h('line', { x1: 5, y1: 12, x2: 5, y2: 17 }), h('line', { x1: 19, y1: 12, x2: 19, y2: 17 })),
|
|
47
48
|
terminal: () => h('svg', S, h('polyline', { points: '4 17 10 11 4 5' }), h('line', { x1: 12, y1: 19, x2: 20, y2: 19 })),
|
|
48
49
|
chart: () => h('svg', S, h('line', { x1: 18, y1: 20, x2: 18, y2: 10 }), h('line', { x1: 12, y1: 20, x2: 12, y2: 4 }), h('line', { x1: 6, y1: 20, x2: 6, y2: 14 })),
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
|
|
2
|
+
import { I } from '../components/icons.js';
|
|
3
|
+
import { HelpButton } from '../components/help-button.js';
|
|
4
|
+
|
|
5
|
+
// ─── Colors ──────────────────────────────────────────────
|
|
6
|
+
const STATUS_COLORS = {
|
|
7
|
+
created: '#6366f1',
|
|
8
|
+
assigned: '#f59e0b',
|
|
9
|
+
in_progress: '#06b6d4',
|
|
10
|
+
completed: '#22c55e',
|
|
11
|
+
failed: '#ef4444',
|
|
12
|
+
cancelled: '#6b7394',
|
|
13
|
+
};
|
|
14
|
+
const PRIORITY_COLORS = { urgent: '#ef4444', high: '#f59e0b', normal: '#6366f1', low: '#6b7394' };
|
|
15
|
+
const 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' };
|
|
16
|
+
const BG = '#0a0c14';
|
|
17
|
+
const ACCENT = '#6366f1';
|
|
18
|
+
|
|
19
|
+
// ─── Styles ──────────────────────────────────────────────
|
|
20
|
+
const _h4 = { marginTop: 16, marginBottom: 8, fontSize: 14 };
|
|
21
|
+
const _ul = { paddingLeft: 20, margin: '4px 0 8px' };
|
|
22
|
+
const _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
|
|
23
|
+
|
|
24
|
+
// ─── Pipeline Columns (Kanban) ───────────────────────────
|
|
25
|
+
const COLUMNS = [
|
|
26
|
+
{ key: 'created', label: 'Queued', color: STATUS_COLORS.created },
|
|
27
|
+
{ key: 'assigned', label: 'Assigned', color: STATUS_COLORS.assigned },
|
|
28
|
+
{ key: 'in_progress', label: 'In Progress', color: STATUS_COLORS.in_progress },
|
|
29
|
+
{ key: 'completed', label: 'Completed', color: STATUS_COLORS.completed },
|
|
30
|
+
{ key: 'failed', label: 'Failed', color: STATUS_COLORS.failed },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function formatDuration(ms) {
|
|
34
|
+
if (!ms) return '-';
|
|
35
|
+
var s = Math.floor(ms / 1000);
|
|
36
|
+
if (s < 60) return s + 's';
|
|
37
|
+
var m = Math.floor(s / 60);
|
|
38
|
+
if (m < 60) return m + 'm ' + (s % 60) + 's';
|
|
39
|
+
var hr = Math.floor(m / 60);
|
|
40
|
+
return hr + 'h ' + (m % 60) + 'm';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function timeAgo(ts) {
|
|
44
|
+
if (!ts) return '-';
|
|
45
|
+
var diff = Date.now() - new Date(ts).getTime();
|
|
46
|
+
if (diff < 5000) return 'just now';
|
|
47
|
+
if (diff < 60000) return Math.floor(diff / 1000) + 's ago';
|
|
48
|
+
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
|
|
49
|
+
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
|
|
50
|
+
return Math.floor(diff / 86400000) + 'd ago';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Task Card Component ─────────────────────────────────
|
|
54
|
+
function TaskCard({ task, onSelect }) {
|
|
55
|
+
var priColor = PRIORITY_COLORS[task.priority] || PRIORITY_COLORS.normal;
|
|
56
|
+
var catIcon = CATEGORY_ICONS[task.category] || CATEGORY_ICONS.custom;
|
|
57
|
+
|
|
58
|
+
return h('div', {
|
|
59
|
+
onClick: function() { onSelect(task); },
|
|
60
|
+
style: {
|
|
61
|
+
background: 'var(--bg-secondary, #111827)',
|
|
62
|
+
border: '1px solid var(--border, #1e293b)',
|
|
63
|
+
borderLeft: '3px solid ' + priColor,
|
|
64
|
+
borderRadius: 'var(--radius, 8px)',
|
|
65
|
+
padding: 12,
|
|
66
|
+
marginBottom: 8,
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
transition: 'transform 0.1s, box-shadow 0.1s',
|
|
69
|
+
},
|
|
70
|
+
onMouseEnter: function(e) { e.currentTarget.style.transform = 'translateY(-1px)'; e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)'; },
|
|
71
|
+
onMouseLeave: function(e) { e.currentTarget.style.transform = ''; e.currentTarget.style.boxShadow = ''; },
|
|
72
|
+
},
|
|
73
|
+
// Header: icon + title
|
|
74
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 } },
|
|
75
|
+
h('span', { style: { fontSize: 14 } }, catIcon),
|
|
76
|
+
h('span', { style: { fontSize: 13, fontWeight: 600, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, task.title)
|
|
77
|
+
),
|
|
78
|
+
// Agent + time
|
|
79
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 11, color: 'var(--text-muted)' } },
|
|
80
|
+
h('span', null, task.assignedToName || task.assignedTo || '-'),
|
|
81
|
+
h('span', null, timeAgo(task.createdAt))
|
|
82
|
+
),
|
|
83
|
+
// Progress bar (if in progress)
|
|
84
|
+
task.status === 'in_progress' && task.progress > 0 && h('div', { style: { marginTop: 6, height: 3, background: 'var(--border)', borderRadius: 2, overflow: 'hidden' } },
|
|
85
|
+
h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 2, transition: 'width 0.3s' } })
|
|
86
|
+
),
|
|
87
|
+
// Tags
|
|
88
|
+
task.tags && task.tags.length > 0 && h('div', { style: { display: 'flex', gap: 4, marginTop: 6, flexWrap: 'wrap' } },
|
|
89
|
+
task.tags.map(function(tag) {
|
|
90
|
+
return h('span', { key: tag, style: { fontSize: 10, padding: '1px 6px', borderRadius: 10, background: 'var(--accent-soft, rgba(99,102,241,0.15))', color: 'var(--accent, #6366f1)' } }, tag);
|
|
91
|
+
})
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Task Detail Modal ───────────────────────────────────
|
|
97
|
+
function TaskDetail({ task, onClose, onCancel }) {
|
|
98
|
+
if (!task) return null;
|
|
99
|
+
var statusColor = STATUS_COLORS[task.status] || '#6b7394';
|
|
100
|
+
|
|
101
|
+
return h('div', { className: 'modal-overlay', onClick: onClose },
|
|
102
|
+
h('div', { className: 'modal', onClick: function(e) { e.stopPropagation(); }, style: { width: 560, maxHeight: '80vh', overflow: 'auto' } },
|
|
103
|
+
h('div', { className: 'modal-header' },
|
|
104
|
+
h('h2', null, task.title),
|
|
105
|
+
h('button', { className: 'btn btn-ghost btn-icon', onClick: onClose }, '\u00D7')
|
|
106
|
+
),
|
|
107
|
+
h('div', { className: 'modal-body', style: { padding: 20 } },
|
|
108
|
+
// Status badge
|
|
109
|
+
h('div', { style: { display: 'flex', gap: 8, marginBottom: 16, alignItems: 'center' } },
|
|
110
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, fontWeight: 600, background: statusColor + '22', color: statusColor, border: '1px solid ' + statusColor + '44' } },
|
|
111
|
+
task.status.replace('_', ' ').toUpperCase()
|
|
112
|
+
),
|
|
113
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: (PRIORITY_COLORS[task.priority] || '#6366f1') + '22', color: PRIORITY_COLORS[task.priority] || '#6366f1' } },
|
|
114
|
+
task.priority.toUpperCase()
|
|
115
|
+
),
|
|
116
|
+
h('span', { style: { padding: '3px 10px', borderRadius: 12, fontSize: 12, background: 'var(--bg-tertiary)', color: 'var(--text-muted)' } },
|
|
117
|
+
(CATEGORY_ICONS[task.category] || '') + ' ' + task.category
|
|
118
|
+
)
|
|
119
|
+
),
|
|
120
|
+
|
|
121
|
+
// Description
|
|
122
|
+
task.description && h('div', { style: { marginBottom: 16, fontSize: 13, lineHeight: 1.6, color: 'var(--text-secondary)' } }, task.description),
|
|
123
|
+
|
|
124
|
+
// Progress
|
|
125
|
+
task.status === 'in_progress' && h('div', { style: { marginBottom: 16 } },
|
|
126
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4 } },
|
|
127
|
+
h('span', null, 'Progress'),
|
|
128
|
+
h('span', null, task.progress + '%')
|
|
129
|
+
),
|
|
130
|
+
h('div', { style: { height: 6, background: 'var(--border)', borderRadius: 3, overflow: 'hidden' } },
|
|
131
|
+
h('div', { style: { height: '100%', width: task.progress + '%', background: STATUS_COLORS.in_progress, borderRadius: 3 } })
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
|
|
135
|
+
// Metadata grid
|
|
136
|
+
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 24px', fontSize: 13, marginBottom: 16 } },
|
|
137
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Assigned To'), h('div', null, task.assignedToName || task.assignedTo || '-')),
|
|
138
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Created By'), h('div', null, task.createdByName || task.createdBy || '-')),
|
|
139
|
+
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() : '-')),
|
|
140
|
+
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() : '-')),
|
|
141
|
+
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() : '-')),
|
|
142
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Duration'), h('div', null, formatDuration(task.actualDurationMs))),
|
|
143
|
+
h('div', null, h('div', { style: { color: 'var(--text-muted)', fontSize: 11, marginBottom: 2 } }, 'Model'), h('div', null, task.modelUsed || task.model || '-')),
|
|
144
|
+
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)))
|
|
145
|
+
),
|
|
146
|
+
|
|
147
|
+
// Error
|
|
148
|
+
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' } },
|
|
149
|
+
h('strong', null, 'Error: '), task.error
|
|
150
|
+
),
|
|
151
|
+
|
|
152
|
+
// Result
|
|
153
|
+
task.result && h('div', { style: { marginBottom: 16 } },
|
|
154
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 4 } }, 'Result'),
|
|
155
|
+
h('pre', { style: { fontSize: 12, background: 'var(--bg-tertiary)', padding: 12, borderRadius: 'var(--radius)', overflow: 'auto', maxHeight: 200 } },
|
|
156
|
+
JSON.stringify(task.result, null, 2)
|
|
157
|
+
)
|
|
158
|
+
),
|
|
159
|
+
|
|
160
|
+
// Actions
|
|
161
|
+
(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 } },
|
|
162
|
+
h('button', { className: 'btn btn-danger btn-sm', onClick: function() { onCancel(task.id); } }, 'Cancel Task')
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ─── Stats Bar ───────────────────────────────────────────
|
|
170
|
+
function StatsBar({ stats }) {
|
|
171
|
+
var items = [
|
|
172
|
+
{ label: 'Queued', value: stats.created, color: STATUS_COLORS.created },
|
|
173
|
+
{ label: 'Assigned', value: stats.assigned, color: STATUS_COLORS.assigned },
|
|
174
|
+
{ label: 'In Progress', value: stats.inProgress, color: STATUS_COLORS.in_progress },
|
|
175
|
+
{ label: 'Completed', value: stats.completed, color: STATUS_COLORS.completed },
|
|
176
|
+
{ label: 'Failed', value: stats.failed, color: STATUS_COLORS.failed },
|
|
177
|
+
];
|
|
178
|
+
return h('div', { style: { display: 'flex', gap: 16, marginBottom: 20, flexWrap: 'wrap' } },
|
|
179
|
+
items.map(function(it) {
|
|
180
|
+
return h('div', { key: it.label, style: { background: 'var(--bg-secondary)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', padding: '12px 20px', minWidth: 120, textAlign: 'center' } },
|
|
181
|
+
h('div', { style: { fontSize: 28, fontWeight: 700, color: it.color, lineHeight: 1 } }, it.value),
|
|
182
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, it.label)
|
|
183
|
+
);
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── Main Page Component ─────────────────────────────────
|
|
189
|
+
export function TaskPipelinePage() {
|
|
190
|
+
var { toast } = useApp();
|
|
191
|
+
var [tasks, setTasks] = useState([]);
|
|
192
|
+
var [stats, setStats] = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0 });
|
|
193
|
+
var [selectedTask, setSelectedTask] = useState(null);
|
|
194
|
+
var [view, setView] = useState('kanban'); // 'kanban' | 'list' | 'timeline'
|
|
195
|
+
var [filter, setFilter] = useState('all'); // 'all' | 'active' | 'completed' | 'failed'
|
|
196
|
+
var [loading, setLoading] = useState(true);
|
|
197
|
+
var eventSourceRef = useRef(null);
|
|
198
|
+
|
|
199
|
+
// Fetch initial data
|
|
200
|
+
var loadData = useCallback(function() {
|
|
201
|
+
setLoading(true);
|
|
202
|
+
Promise.all([
|
|
203
|
+
engineCall('/task-pipeline?limit=200'),
|
|
204
|
+
engineCall('/task-pipeline/stats'),
|
|
205
|
+
]).then(function(results) {
|
|
206
|
+
setTasks(results[0]?.tasks || []);
|
|
207
|
+
setStats(results[1] || stats);
|
|
208
|
+
}).catch(function(err) {
|
|
209
|
+
console.error('[TaskPipeline] load error:', err);
|
|
210
|
+
}).finally(function() { setLoading(false); });
|
|
211
|
+
}, []);
|
|
212
|
+
|
|
213
|
+
// SSE for real-time updates
|
|
214
|
+
useEffect(function() {
|
|
215
|
+
loadData();
|
|
216
|
+
|
|
217
|
+
// Connect to SSE stream
|
|
218
|
+
var baseUrl = window.__ENGINE_BASE || '/engine';
|
|
219
|
+
var es = new EventSource(baseUrl + '/task-pipeline/stream');
|
|
220
|
+
eventSourceRef.current = es;
|
|
221
|
+
|
|
222
|
+
es.onmessage = function(e) {
|
|
223
|
+
try {
|
|
224
|
+
var event = JSON.parse(e.data);
|
|
225
|
+
if (event.type === 'init') {
|
|
226
|
+
// Initial state from SSE
|
|
227
|
+
if (event.tasks) setTasks(function(prev) {
|
|
228
|
+
var map = new Map();
|
|
229
|
+
prev.forEach(function(t) { map.set(t.id, t); });
|
|
230
|
+
event.tasks.forEach(function(t) { map.set(t.id, t); });
|
|
231
|
+
return Array.from(map.values()).sort(function(a, b) { return new Date(b.createdAt) - new Date(a.createdAt); });
|
|
232
|
+
});
|
|
233
|
+
if (event.stats) setStats(event.stats);
|
|
234
|
+
} else if (event.task) {
|
|
235
|
+
// Real-time task event
|
|
236
|
+
setTasks(function(prev) {
|
|
237
|
+
var idx = prev.findIndex(function(t) { return t.id === event.task.id; });
|
|
238
|
+
if (idx >= 0) {
|
|
239
|
+
var next = prev.slice();
|
|
240
|
+
next[idx] = event.task;
|
|
241
|
+
return next;
|
|
242
|
+
}
|
|
243
|
+
return [event.task].concat(prev);
|
|
244
|
+
});
|
|
245
|
+
// Update stats locally
|
|
246
|
+
setStats(function(prev) {
|
|
247
|
+
var s = Object.assign({}, prev);
|
|
248
|
+
if (event.type === 'task_created') { s.created++; s.total++; }
|
|
249
|
+
else if (event.type === 'task_completed') { s.completed++; s.inProgress = Math.max(0, s.inProgress - 1); }
|
|
250
|
+
else if (event.type === 'task_failed') { s.failed++; s.inProgress = Math.max(0, s.inProgress - 1); }
|
|
251
|
+
return s;
|
|
252
|
+
});
|
|
253
|
+
// Update selected task if viewing it
|
|
254
|
+
setSelectedTask(function(prev) {
|
|
255
|
+
if (prev && prev.id === event.task.id) return event.task;
|
|
256
|
+
return prev;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
} catch (err) { /* ignore parse errors */ }
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
es.onerror = function() {
|
|
263
|
+
// Reconnect handled by browser
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return function() {
|
|
267
|
+
if (es) es.close();
|
|
268
|
+
};
|
|
269
|
+
}, []);
|
|
270
|
+
|
|
271
|
+
// Periodic stats refresh
|
|
272
|
+
useEffect(function() {
|
|
273
|
+
var interval = setInterval(function() {
|
|
274
|
+
engineCall('/task-pipeline/stats').then(function(s) { if (s) setStats(s); }).catch(function() {});
|
|
275
|
+
}, 30000);
|
|
276
|
+
return function() { clearInterval(interval); };
|
|
277
|
+
}, []);
|
|
278
|
+
|
|
279
|
+
var cancelTask = useCallback(function(taskId) {
|
|
280
|
+
engineCall('/task-pipeline/' + taskId + '/cancel', { method: 'POST' })
|
|
281
|
+
.then(function(res) {
|
|
282
|
+
toast('Task cancelled', 'success');
|
|
283
|
+
setSelectedTask(null);
|
|
284
|
+
loadData();
|
|
285
|
+
})
|
|
286
|
+
.catch(function(err) { toast(err.message || 'Failed to cancel', 'error'); });
|
|
287
|
+
}, []);
|
|
288
|
+
|
|
289
|
+
// Filter tasks
|
|
290
|
+
var filteredTasks = tasks.filter(function(t) {
|
|
291
|
+
if (filter === 'active') return t.status === 'created' || t.status === 'assigned' || t.status === 'in_progress';
|
|
292
|
+
if (filter === 'completed') return t.status === 'completed';
|
|
293
|
+
if (filter === 'failed') return t.status === 'failed' || t.status === 'cancelled';
|
|
294
|
+
return true;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Group by status for kanban
|
|
298
|
+
var columns = {};
|
|
299
|
+
COLUMNS.forEach(function(col) { columns[col.key] = []; });
|
|
300
|
+
filteredTasks.forEach(function(t) {
|
|
301
|
+
if (columns[t.status]) columns[t.status].push(t);
|
|
302
|
+
else if (t.status === 'cancelled' && columns.failed) columns.failed.push(t);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
if (loading) {
|
|
306
|
+
return h('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)' } }, 'Loading task pipeline...');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return h(Fragment, null,
|
|
310
|
+
// ─── Header ────────────────────────────────────────
|
|
311
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } },
|
|
312
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 12 } },
|
|
313
|
+
h('h1', { style: { fontSize: 22, fontWeight: 700 } }, I.workflow(), ' Task Pipeline'),
|
|
314
|
+
h(HelpButton, { title: 'Task Pipeline' },
|
|
315
|
+
h('p', null, 'The centralized task pipeline shows every task assigned to agents across your organization in real-time.'),
|
|
316
|
+
h('h4', { style: _h4 }, 'How It Works'),
|
|
317
|
+
h('ul', { style: _ul },
|
|
318
|
+
h('li', null, 'Tasks are automatically recorded when agents are spawned for work'),
|
|
319
|
+
h('li', null, 'The pipeline tracks task status from creation through completion'),
|
|
320
|
+
h('li', null, 'Real-time updates via SSE — no need to refresh'),
|
|
321
|
+
h('li', null, 'Smart metadata extraction categorizes tasks automatically')
|
|
322
|
+
),
|
|
323
|
+
h('h4', { style: _h4 }, 'Task Lifecycle'),
|
|
324
|
+
h('ul', { style: _ul },
|
|
325
|
+
h('li', null, h('strong', null, 'Queued'), ' — Task created, waiting to be assigned'),
|
|
326
|
+
h('li', null, h('strong', null, 'Assigned'), ' — Agent selected, about to start'),
|
|
327
|
+
h('li', null, h('strong', null, 'In Progress'), ' — Agent actively working'),
|
|
328
|
+
h('li', null, h('strong', null, 'Completed'), ' — Task finished successfully'),
|
|
329
|
+
h('li', null, h('strong', null, 'Failed'), ' — Task encountered an error')
|
|
330
|
+
),
|
|
331
|
+
h('div', { style: _tip }, 'Tip: Click any task card to see full details including model used, tokens consumed, duration, and results.')
|
|
332
|
+
)
|
|
333
|
+
),
|
|
334
|
+
h('div', { style: { display: 'flex', gap: 8 } },
|
|
335
|
+
// View toggle
|
|
336
|
+
h('div', { style: { display: 'flex', border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
337
|
+
['kanban', 'list'].map(function(v) {
|
|
338
|
+
return h('button', {
|
|
339
|
+
key: v,
|
|
340
|
+
className: 'btn btn-sm ' + (view === v ? 'btn-primary' : 'btn-ghost'),
|
|
341
|
+
onClick: function() { setView(v); },
|
|
342
|
+
style: { borderRadius: 0, textTransform: 'capitalize', fontSize: 12 }
|
|
343
|
+
}, v);
|
|
344
|
+
})
|
|
345
|
+
),
|
|
346
|
+
// Filter
|
|
347
|
+
h('select', {
|
|
348
|
+
className: 'form-control',
|
|
349
|
+
value: filter,
|
|
350
|
+
onChange: function(e) { setFilter(e.target.value); },
|
|
351
|
+
style: { fontSize: 12, padding: '4px 8px', width: 'auto' }
|
|
352
|
+
},
|
|
353
|
+
h('option', { value: 'all' }, 'All Tasks'),
|
|
354
|
+
h('option', { value: 'active' }, 'Active Only'),
|
|
355
|
+
h('option', { value: 'completed' }, 'Completed'),
|
|
356
|
+
h('option', { value: 'failed' }, 'Failed / Cancelled')
|
|
357
|
+
),
|
|
358
|
+
h('button', { className: 'btn btn-secondary btn-sm', onClick: loadData }, I.refresh(), ' Refresh')
|
|
359
|
+
)
|
|
360
|
+
),
|
|
361
|
+
|
|
362
|
+
// ─── Stats Bar ─────────────────────────────────────
|
|
363
|
+
h(StatsBar, { stats: stats }),
|
|
364
|
+
|
|
365
|
+
// ─── Live indicator ────────────────────────────────
|
|
366
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 16, fontSize: 12, color: 'var(--text-muted)' } },
|
|
367
|
+
h('div', { style: { width: 8, height: 8, borderRadius: '50%', background: '#22c55e', animation: 'pulse 2s infinite' } }),
|
|
368
|
+
'Live — updates in real-time',
|
|
369
|
+
h('span', { style: { marginLeft: 'auto' } }, filteredTasks.length + ' tasks')
|
|
370
|
+
),
|
|
371
|
+
|
|
372
|
+
// ─── Kanban View ───────────────────────────────────
|
|
373
|
+
view === 'kanban' && h('div', {
|
|
374
|
+
style: {
|
|
375
|
+
display: 'grid',
|
|
376
|
+
gridTemplateColumns: 'repeat(' + COLUMNS.length + ', 1fr)',
|
|
377
|
+
gap: 12,
|
|
378
|
+
minHeight: 400,
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
COLUMNS.map(function(col) {
|
|
382
|
+
var colTasks = columns[col.key] || [];
|
|
383
|
+
return h('div', { key: col.key, style: { background: 'var(--bg-primary)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' } },
|
|
384
|
+
// Column header
|
|
385
|
+
h('div', { style: { padding: '10px 12px', borderBottom: '2px solid ' + col.color, display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
386
|
+
h('span', { style: { fontSize: 13, fontWeight: 600 } }, col.label),
|
|
387
|
+
h('span', { style: { fontSize: 11, padding: '2px 8px', borderRadius: 10, background: col.color + '22', color: col.color, fontWeight: 600 } }, colTasks.length)
|
|
388
|
+
),
|
|
389
|
+
// Cards
|
|
390
|
+
h('div', { style: { padding: 8, maxHeight: 500, overflowY: 'auto' } },
|
|
391
|
+
colTasks.length === 0
|
|
392
|
+
? h('div', { style: { padding: 20, textAlign: 'center', color: 'var(--text-muted)', fontSize: 12 } }, 'No tasks')
|
|
393
|
+
: colTasks.map(function(t) {
|
|
394
|
+
return h(TaskCard, { key: t.id, task: t, onSelect: setSelectedTask });
|
|
395
|
+
})
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
})
|
|
399
|
+
),
|
|
400
|
+
|
|
401
|
+
// ─── List View ─────────────────────────────────────
|
|
402
|
+
view === 'list' && h('div', { className: 'card' },
|
|
403
|
+
h('div', { style: { overflowX: 'auto' } },
|
|
404
|
+
h('table', { className: 'table' },
|
|
405
|
+
h('thead', null,
|
|
406
|
+
h('tr', null,
|
|
407
|
+
h('th', null, 'Task'),
|
|
408
|
+
h('th', null, 'Agent'),
|
|
409
|
+
h('th', null, 'Status'),
|
|
410
|
+
h('th', null, 'Priority'),
|
|
411
|
+
h('th', null, 'Category'),
|
|
412
|
+
h('th', null, 'Created'),
|
|
413
|
+
h('th', null, 'Duration'),
|
|
414
|
+
h('th', null, 'Model')
|
|
415
|
+
)
|
|
416
|
+
),
|
|
417
|
+
h('tbody', null,
|
|
418
|
+
filteredTasks.length === 0
|
|
419
|
+
? h('tr', null, h('td', { colSpan: 8, style: { textAlign: 'center', color: 'var(--text-muted)', padding: 40 } }, 'No tasks found'))
|
|
420
|
+
: filteredTasks.map(function(t) {
|
|
421
|
+
var statusColor = STATUS_COLORS[t.status] || '#6b7394';
|
|
422
|
+
return h('tr', {
|
|
423
|
+
key: t.id,
|
|
424
|
+
onClick: function() { setSelectedTask(t); },
|
|
425
|
+
style: { cursor: 'pointer' }
|
|
426
|
+
},
|
|
427
|
+
h('td', { style: { maxWidth: 250, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 500 } }, t.title),
|
|
428
|
+
h('td', { style: { fontSize: 13 } }, t.assignedToName || '-'),
|
|
429
|
+
h('td', null,
|
|
430
|
+
h('span', { style: { padding: '2px 8px', borderRadius: 10, fontSize: 11, fontWeight: 600, background: statusColor + '22', color: statusColor } },
|
|
431
|
+
t.status.replace('_', ' ')
|
|
432
|
+
)
|
|
433
|
+
),
|
|
434
|
+
h('td', null,
|
|
435
|
+
h('span', { style: { fontSize: 11, color: PRIORITY_COLORS[t.priority] || '#6366f1' } }, t.priority)
|
|
436
|
+
),
|
|
437
|
+
h('td', { style: { fontSize: 12 } }, (CATEGORY_ICONS[t.category] || '') + ' ' + t.category),
|
|
438
|
+
h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, timeAgo(t.createdAt)),
|
|
439
|
+
h('td', { style: { fontSize: 12 } }, formatDuration(t.actualDurationMs)),
|
|
440
|
+
h('td', { style: { fontSize: 12, fontFamily: 'var(--font-mono)' } }, t.modelUsed || t.model || '-')
|
|
441
|
+
);
|
|
442
|
+
})
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
),
|
|
447
|
+
|
|
448
|
+
// ─── Task Detail Modal ─────────────────────────────
|
|
449
|
+
selectedTask && h(TaskDetail, {
|
|
450
|
+
task: selectedTask,
|
|
451
|
+
onClose: function() { setSelectedTask(null); },
|
|
452
|
+
onCancel: cancelTask
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
}
|