apteva 0.4.32 → 0.4.44
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/ActivityPage.c48n83h2.js +3 -0
- package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
- package/dist/App.09yb8t0b.js +1 -0
- package/dist/App.152mbs1r.js +4 -0
- package/dist/App.3a67nx9w.js +4 -0
- package/dist/App.9epx6785.js +4 -0
- package/dist/App.d8955awp.js +4 -0
- package/dist/App.drwb57jq.js +4 -0
- package/dist/App.gssbmajb.js +4 -0
- package/dist/App.qw70pc29.js +53 -0
- package/dist/App.qzbx5wtj.js +4 -0
- package/dist/App.r5serxkt.js +8 -0
- package/dist/App.tpmp9020.js +20 -0
- package/dist/App.v2wb4d7d.js +61 -0
- package/dist/App.vxmaaj0m.js +13 -0
- package/dist/App.w4p2tda9.js +4 -0
- package/dist/App.wv2ng55q.js +221 -0
- package/dist/App.yncnrn0f.js +4 -0
- package/dist/ConnectionsPage.k6cspyqq.js +3 -0
- package/dist/McpPage.cdxm48xj.js +3 -0
- package/dist/SettingsPage.evpv7c2y.js +3 -0
- package/dist/SkillsPage.pvzp6c1a.js +3 -0
- package/dist/TasksPage.6jnvbpsy.js +3 -0
- package/dist/TelemetryPage.t7vk24zc.js +3 -0
- package/dist/TestsPage.5x6658aa.js +3 -0
- package/dist/ThreadsPage.3fvhtevh.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +10 -9
- package/src/crypto.ts +4 -3
- package/src/db.ts +171 -36
- package/src/integrations/agentdojo.ts +95 -12
- package/src/integrations/index.ts +7 -0
- package/src/mcp-platform.ts +870 -142
- package/src/openapi.ts +96 -0
- package/src/providers.ts +60 -34
- package/src/routes/api/agent-utils.ts +59 -47
- package/src/routes/api/agents.ts +71 -2
- package/src/routes/api/integrations.ts +11 -5
- package/src/routes/api/mcp.ts +5 -4
- package/src/routes/api/meta-agent.ts +37 -1
- package/src/routes/api/projects.ts +3 -3
- package/src/routes/api/providers.ts +121 -30
- package/src/routes/api/skills.ts +2 -3
- package/src/routes/api/system.ts +98 -14
- package/src/routes/api/telemetry.ts +19 -1
- package/src/routes/share.ts +85 -0
- package/src/server.ts +43 -32
- package/src/triggers/agentdojo.ts +2 -2
- package/src/web/App.tsx +107 -21
- package/src/web/components/activity/ActivityPage.tsx +242 -389
- package/src/web/components/agents/AgentCard.tsx +19 -27
- package/src/web/components/agents/AgentPanel.tsx +358 -198
- package/src/web/components/agents/AgentsView.tsx +4 -4
- package/src/web/components/agents/CreateAgentModal.tsx +21 -79
- package/src/web/components/api/ApiDocsPage.tsx +66 -66
- package/src/web/components/auth/CreateAccountStep.tsx +16 -16
- package/src/web/components/auth/LoginPage.tsx +10 -10
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +8 -8
- package/src/web/components/common/Select.tsx +11 -10
- package/src/web/components/connections/ConnectionsPage.tsx +4 -4
- package/src/web/components/connections/IntegrationsTab.tsx +18 -18
- package/src/web/components/connections/OverviewTab.tsx +13 -13
- package/src/web/components/connections/TriggersTab.tsx +99 -99
- package/src/web/components/dashboard/Dashboard.tsx +177 -52
- package/src/web/components/index.ts +1 -1
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +41 -16
- package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
- package/src/web/components/mcp/McpPage.tsx +218 -209
- package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +389 -221
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +385 -68
- package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +315 -0
- package/src/web/context/AuthContext.tsx +3 -3
- package/src/web/context/ProjectContext.tsx +8 -3
- package/src/web/context/TelemetryContext.tsx +24 -6
- package/src/web/context/ThemeContext.tsx +69 -0
- package/src/web/context/index.ts +3 -1
- package/src/web/styles.css +25 -7
- package/src/web/themes.ts +99 -0
- package/src/web/types.ts +4 -7
- package/dist/ActivityPage.41nbye4r.js +0 -3
- package/dist/ApiDocsPage.4smnt8m3.js +0 -4
- package/dist/App.0sbax9et.js +0 -4
- package/dist/App.0ws427h8.js +0 -4
- package/dist/App.6q6bar8b.js +0 -4
- package/dist/App.80301vdb.js +0 -4
- package/dist/App.af2wg84v.js +0 -267
- package/dist/App.ca1rz1ph.js +0 -4
- package/dist/App.ensa6z0r.js +0 -4
- package/dist/App.f8g7tych.js +0 -13
- package/dist/App.mvtqv6qc.js +0 -20
- package/dist/App.ncgc9cxy.js +0 -4
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.p0fb1pds.js +0 -4
- package/dist/App.pmaq48sj.js +0 -4
- package/dist/App.yv87t9m5.js +0 -4
- package/dist/App.zjmfm8p6.js +0 -4
- package/dist/ConnectionsPage.anb3rv9a.js +0 -3
- package/dist/McpPage.y396h6fy.js +0 -3
- package/dist/SettingsPage.p1hc60gk.js +0 -3
- package/dist/SkillsPage.yj3xdsay.js +0 -3
- package/dist/TasksPage.sjv0khtv.js +0 -3
- package/dist/TelemetryPage.2qm4w16r.js +0 -3
- package/dist/TestsPage.zzs4qfj8.js +0 -3
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
2
2
|
import { TasksIcon, CloseIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
|
|
3
|
+
import { Select } from "../common/Select";
|
|
4
|
+
import { useConfirm } from "../common/Modal";
|
|
3
5
|
import { useAuth, useProjects } from "../../context";
|
|
4
6
|
import { useTelemetry } from "../../context/TelemetryContext";
|
|
5
|
-
import type { Task, TaskTrajectoryStep, ToolUseBlock, ToolResultBlock } from "../../types";
|
|
7
|
+
import type { Task, TaskTrajectoryStep, ToolUseBlock, ToolResultBlock, Agent } from "../../types";
|
|
6
8
|
|
|
7
9
|
interface TasksPageProps {
|
|
8
10
|
onSelectAgent?: (agentId: string) => void;
|
|
@@ -14,8 +16,10 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
14
16
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
15
17
|
const [loading, setLoading] = useState(true);
|
|
16
18
|
const [filter, setFilter] = useState<string>("all");
|
|
19
|
+
const [agentFilter, setAgentFilter] = useState<string>("all");
|
|
17
20
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
18
21
|
const [loadingTask, setLoadingTask] = useState(false);
|
|
22
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
19
23
|
const lastProcessedEventRef = useRef<string | null>(null);
|
|
20
24
|
|
|
21
25
|
// Subscribe to task telemetry events for real-time updates
|
|
@@ -87,9 +91,23 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
87
91
|
}
|
|
88
92
|
}, [authFetch]);
|
|
89
93
|
|
|
94
|
+
// Extract unique agents from tasks for the agent filter
|
|
95
|
+
const uniqueAgents = useMemo(() => {
|
|
96
|
+
const map = new Map<string, string>();
|
|
97
|
+
for (const t of tasks) {
|
|
98
|
+
const id = t.agentId || (t as any).agent_id;
|
|
99
|
+
const name = t.agentName || (t as any).agent_name;
|
|
100
|
+
if (id && name && !map.has(id)) {
|
|
101
|
+
map.set(id, name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return Array.from(map.entries()).sort((a, b) => a[1].localeCompare(b[1]));
|
|
105
|
+
}, [tasks]);
|
|
106
|
+
|
|
90
107
|
// Sort tasks: running first, then pending by next execution (soonest first), then completed/failed by date
|
|
91
108
|
const sortedTasks = useMemo(() => {
|
|
92
|
-
|
|
109
|
+
const filtered = agentFilter === "all" ? tasks : tasks.filter(t => (t.agentId || (t as any).agent_id) === agentFilter);
|
|
110
|
+
return [...filtered].sort((a, b) => {
|
|
93
111
|
// Running tasks first
|
|
94
112
|
if (a.status === "running" && b.status !== "running") return -1;
|
|
95
113
|
if (b.status === "running" && a.status !== "running") return 1;
|
|
@@ -111,7 +129,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
111
129
|
const bDate = b.completed_at || b.executed_at || b.created_at;
|
|
112
130
|
return new Date(bDate).getTime() - new Date(aDate).getTime();
|
|
113
131
|
});
|
|
114
|
-
}, [tasks]);
|
|
132
|
+
}, [tasks, agentFilter]);
|
|
115
133
|
|
|
116
134
|
const statusColors: Record<string, string> = {
|
|
117
135
|
pending: "bg-yellow-500/20 text-yellow-400",
|
|
@@ -135,36 +153,61 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
135
153
|
<div className={`flex-1 p-4 md:p-6 overflow-auto ${selectedTask ? 'hidden md:block md:w-1/2 lg:w-2/3' : ''}`}>
|
|
136
154
|
<div className="max-w-4xl">
|
|
137
155
|
<div className="mb-6">
|
|
138
|
-
<div className="mb-4">
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
156
|
+
<div className="mb-4 flex items-start justify-between">
|
|
157
|
+
<div>
|
|
158
|
+
<h1 className="text-xl md:text-2xl font-semibold mb-1">Tasks</h1>
|
|
159
|
+
<p className="text-sm text-[var(--color-text-muted)]">
|
|
160
|
+
View tasks from all running agents
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
<button
|
|
164
|
+
onClick={() => setShowCreateModal(true)}
|
|
165
|
+
className="px-3 py-1.5 rounded text-sm bg-[var(--color-accent)] text-black hover:opacity-90 transition flex items-center gap-1.5 flex-shrink-0"
|
|
166
|
+
>
|
|
167
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
|
|
168
|
+
Create Task
|
|
169
|
+
</button>
|
|
143
170
|
</div>
|
|
144
|
-
<div className="flex
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
<div className="flex items-center gap-3 flex-wrap pb-1">
|
|
172
|
+
<div className="flex gap-2 overflow-x-auto scrollbar-hide">
|
|
173
|
+
{filterOptions.map(opt => (
|
|
174
|
+
<button
|
|
175
|
+
key={opt.value}
|
|
176
|
+
onClick={() => setFilter(opt.value)}
|
|
177
|
+
className={`px-3 py-1.5 rounded text-sm transition whitespace-nowrap ${
|
|
178
|
+
filter === opt.value
|
|
179
|
+
? "bg-[var(--color-accent)] text-black"
|
|
180
|
+
: "bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)]"
|
|
181
|
+
}`}
|
|
182
|
+
>
|
|
183
|
+
{opt.label}
|
|
184
|
+
</button>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
{uniqueAgents.length > 0 && (
|
|
188
|
+
<div className="w-48">
|
|
189
|
+
<Select
|
|
190
|
+
value={agentFilter}
|
|
191
|
+
onChange={setAgentFilter}
|
|
192
|
+
placeholder="All agents"
|
|
193
|
+
compact
|
|
194
|
+
options={[
|
|
195
|
+
{ value: "all", label: "All agents" },
|
|
196
|
+
...uniqueAgents.map(([id, name]) => ({ value: id, label: name })),
|
|
197
|
+
]}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
158
201
|
</div>
|
|
159
202
|
</div>
|
|
160
203
|
|
|
161
204
|
{loading ? (
|
|
162
|
-
<div className="text-center py-12 text-[
|
|
205
|
+
<div className="text-center py-12 text-[var(--color-text-muted)]">Loading tasks...</div>
|
|
163
206
|
) : sortedTasks.length === 0 ? (
|
|
164
207
|
<div className="text-center py-12">
|
|
165
|
-
<TasksIcon className="w-12 h-12 mx-auto mb-4 text-[
|
|
166
|
-
<p className="text-[
|
|
167
|
-
<p className="text-sm text-[
|
|
208
|
+
<TasksIcon className="w-12 h-12 mx-auto mb-4 text-[var(--color-border-light)]" />
|
|
209
|
+
<p className="text-[var(--color-text-muted)]">No tasks found</p>
|
|
210
|
+
<p className="text-sm text-[var(--color-text-faint)] mt-1">
|
|
168
211
|
Tasks will appear here when agents create them
|
|
169
212
|
</p>
|
|
170
213
|
</div>
|
|
@@ -174,16 +217,16 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
174
217
|
<div
|
|
175
218
|
key={`${task.agentId}-${task.id}`}
|
|
176
219
|
onClick={() => selectTask(task)}
|
|
177
|
-
className={`bg-[
|
|
220
|
+
className={`bg-[var(--color-surface)] border rounded-lg p-4 cursor-pointer transition ${
|
|
178
221
|
selectedTask?.id === task.id && selectedTask?.agentId === task.agentId
|
|
179
|
-
? "border-[
|
|
180
|
-
: "border-[
|
|
222
|
+
? "border-[var(--color-accent)]"
|
|
223
|
+
: "border-[var(--color-border)] hover:border-[var(--color-border-light)]"
|
|
181
224
|
}`}
|
|
182
225
|
>
|
|
183
226
|
<div className="flex items-start justify-between mb-2">
|
|
184
227
|
<div className="flex-1">
|
|
185
228
|
<h3 className="font-medium">{task.title}</h3>
|
|
186
|
-
<p className="text-sm text-[
|
|
229
|
+
<p className="text-sm text-[var(--color-text-muted)]">{task.agentName}</p>
|
|
187
230
|
</div>
|
|
188
231
|
<span className={`px-2 py-1 rounded text-xs font-medium ${statusColors[task.status] || statusColors.pending}`}>
|
|
189
232
|
{task.status}
|
|
@@ -191,12 +234,12 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
191
234
|
</div>
|
|
192
235
|
|
|
193
236
|
{task.description && (
|
|
194
|
-
<p className="text-sm text-[
|
|
237
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-2 line-clamp-2">
|
|
195
238
|
{task.description}
|
|
196
239
|
</p>
|
|
197
240
|
)}
|
|
198
241
|
|
|
199
|
-
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[
|
|
242
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[var(--color-text-faint)]">
|
|
200
243
|
<span className="flex items-center gap-1">
|
|
201
244
|
{task.type === "recurring"
|
|
202
245
|
? <RecurringIcon className="w-3.5 h-3.5" />
|
|
@@ -208,10 +251,10 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
208
251
|
</span>
|
|
209
252
|
<span>Priority: {task.priority}</span>
|
|
210
253
|
{task.next_run && (
|
|
211
|
-
<span className="text-[
|
|
254
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.next_run)}</span>
|
|
212
255
|
)}
|
|
213
256
|
{!task.next_run && task.execute_at && (
|
|
214
|
-
<span className="text-[
|
|
257
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.execute_at)}</span>
|
|
215
258
|
)}
|
|
216
259
|
<span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
|
|
217
260
|
</div>
|
|
@@ -230,6 +273,18 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
230
273
|
onClose={() => setSelectedTask(null)}
|
|
231
274
|
onSelectAgent={onSelectAgent}
|
|
232
275
|
loading={loadingTask}
|
|
276
|
+
authFetch={authFetch}
|
|
277
|
+
onRefresh={() => { fetchTasks(); setSelectedTask(null); }}
|
|
278
|
+
/>
|
|
279
|
+
)}
|
|
280
|
+
|
|
281
|
+
{/* Create Task Modal */}
|
|
282
|
+
{showCreateModal && (
|
|
283
|
+
<CreateTaskModal
|
|
284
|
+
authFetch={authFetch}
|
|
285
|
+
currentProjectId={currentProjectId}
|
|
286
|
+
onClose={() => setShowCreateModal(false)}
|
|
287
|
+
onCreated={() => { fetchTasks(); setShowCreateModal(false); }}
|
|
233
288
|
/>
|
|
234
289
|
)}
|
|
235
290
|
</div>
|
|
@@ -242,17 +297,78 @@ export interface TaskDetailPanelProps {
|
|
|
242
297
|
onClose: () => void;
|
|
243
298
|
onSelectAgent?: (agentId: string) => void;
|
|
244
299
|
loading?: boolean;
|
|
300
|
+
authFetch?: (url: string, options?: RequestInit) => Promise<Response>;
|
|
301
|
+
onRefresh?: () => void;
|
|
245
302
|
}
|
|
246
303
|
|
|
247
|
-
export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading }: TaskDetailPanelProps) {
|
|
304
|
+
export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, loading, authFetch, onRefresh }: TaskDetailPanelProps) {
|
|
305
|
+
const [executing, setExecuting] = useState(false);
|
|
306
|
+
const [deleting, setDeleting] = useState(false);
|
|
307
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
308
|
+
|
|
309
|
+
const handleExecute = async () => {
|
|
310
|
+
if (!authFetch || executing) return;
|
|
311
|
+
setExecuting(true);
|
|
312
|
+
try {
|
|
313
|
+
await authFetch(`/api/tasks/${task.agentId}/${task.id}/execute`, { method: "POST" });
|
|
314
|
+
onRefresh?.();
|
|
315
|
+
} catch (e) {
|
|
316
|
+
console.error("Failed to execute task:", e);
|
|
317
|
+
} finally {
|
|
318
|
+
setExecuting(false);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const handleDelete = async () => {
|
|
323
|
+
if (!authFetch || deleting) return;
|
|
324
|
+
const ok = await confirm(`Are you sure you want to delete "${task.title}"?`, {
|
|
325
|
+
title: "Delete Task",
|
|
326
|
+
confirmText: "Delete",
|
|
327
|
+
confirmVariant: "danger",
|
|
328
|
+
});
|
|
329
|
+
if (!ok) return;
|
|
330
|
+
setDeleting(true);
|
|
331
|
+
try {
|
|
332
|
+
await authFetch(`/api/tasks/${task.agentId}/${task.id}`, { method: "DELETE" });
|
|
333
|
+
onRefresh?.();
|
|
334
|
+
} catch (e) {
|
|
335
|
+
console.error("Failed to delete task:", e);
|
|
336
|
+
} finally {
|
|
337
|
+
setDeleting(false);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
248
341
|
return (
|
|
249
|
-
<div className="w-full md:w-1/2 lg:w-1/3 border-l border-[
|
|
342
|
+
<div className="w-full md:w-1/2 lg:w-1/3 border-l border-[var(--color-border)] bg-[var(--color-bg)] flex flex-col overflow-hidden">
|
|
343
|
+
{ConfirmDialog}
|
|
250
344
|
{/* Header */}
|
|
251
|
-
<div className="flex items-center justify-between p-4 border-b border-[
|
|
345
|
+
<div className="flex items-center justify-between p-4 border-b border-[var(--color-border)]">
|
|
252
346
|
<h2 className="font-medium truncate pr-2">Task Details</h2>
|
|
253
|
-
<
|
|
254
|
-
|
|
255
|
-
|
|
347
|
+
<div className="flex items-center gap-2">
|
|
348
|
+
{authFetch && (task.status === "pending" || task.status === "completed") && (
|
|
349
|
+
<button
|
|
350
|
+
onClick={handleExecute}
|
|
351
|
+
disabled={executing}
|
|
352
|
+
title="Execute now"
|
|
353
|
+
className="text-[var(--color-accent)] hover:opacity-80 transition disabled:opacity-50"
|
|
354
|
+
>
|
|
355
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
356
|
+
</button>
|
|
357
|
+
)}
|
|
358
|
+
{authFetch && (
|
|
359
|
+
<button
|
|
360
|
+
onClick={handleDelete}
|
|
361
|
+
disabled={deleting}
|
|
362
|
+
title="Delete task"
|
|
363
|
+
className="text-red-400 hover:text-red-300 transition disabled:opacity-50"
|
|
364
|
+
>
|
|
365
|
+
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
|
366
|
+
</button>
|
|
367
|
+
)}
|
|
368
|
+
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|
|
369
|
+
<CloseIcon />
|
|
370
|
+
</button>
|
|
371
|
+
</div>
|
|
256
372
|
</div>
|
|
257
373
|
|
|
258
374
|
{/* Content */}
|
|
@@ -267,7 +383,7 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
267
383
|
</div>
|
|
268
384
|
<button
|
|
269
385
|
onClick={() => onSelectAgent?.(task.agentId)}
|
|
270
|
-
className="text-sm text-[
|
|
386
|
+
className="text-sm text-[var(--color-accent)] hover:underline"
|
|
271
387
|
>
|
|
272
388
|
{task.agentName}
|
|
273
389
|
</button>
|
|
@@ -276,30 +392,30 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
276
392
|
{/* Description */}
|
|
277
393
|
{task.description && (
|
|
278
394
|
<div>
|
|
279
|
-
<h4 className="text-xs text-[
|
|
280
|
-
<p className="text-sm text-[
|
|
395
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1">Description</h4>
|
|
396
|
+
<p className="text-sm text-[var(--color-text-secondary)] whitespace-pre-wrap">{task.description}</p>
|
|
281
397
|
</div>
|
|
282
398
|
)}
|
|
283
399
|
|
|
284
400
|
{/* Metadata */}
|
|
285
401
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
286
402
|
<div>
|
|
287
|
-
<span className="text-[
|
|
403
|
+
<span className="text-[var(--color-text-muted)]">Type</span>
|
|
288
404
|
<p className="capitalize">{task.type}</p>
|
|
289
405
|
</div>
|
|
290
406
|
<div>
|
|
291
|
-
<span className="text-[
|
|
407
|
+
<span className="text-[var(--color-text-muted)]">Priority</span>
|
|
292
408
|
<p>{task.priority}</p>
|
|
293
409
|
</div>
|
|
294
410
|
<div>
|
|
295
|
-
<span className="text-[
|
|
411
|
+
<span className="text-[var(--color-text-muted)]">Source</span>
|
|
296
412
|
<p className="capitalize">{task.source}</p>
|
|
297
413
|
</div>
|
|
298
414
|
{task.recurrence && (
|
|
299
415
|
<div>
|
|
300
|
-
<span className="text-[
|
|
416
|
+
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
301
417
|
<p>{formatCron(task.recurrence)}</p>
|
|
302
|
-
<p className="text-xs text-[
|
|
418
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{task.recurrence}</p>
|
|
303
419
|
</div>
|
|
304
420
|
)}
|
|
305
421
|
</div>
|
|
@@ -307,31 +423,31 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
307
423
|
{/* Timestamps */}
|
|
308
424
|
<div className="space-y-2 text-sm">
|
|
309
425
|
<div className="flex justify-between">
|
|
310
|
-
<span className="text-[
|
|
426
|
+
<span className="text-[var(--color-text-muted)]">Created</span>
|
|
311
427
|
<span>{new Date(task.created_at).toLocaleString()}</span>
|
|
312
428
|
</div>
|
|
313
429
|
{task.execute_at && (
|
|
314
430
|
<div className="flex justify-between">
|
|
315
|
-
<span className="text-[
|
|
316
|
-
<span className="text-[
|
|
431
|
+
<span className="text-[var(--color-text-muted)]">Scheduled</span>
|
|
432
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.execute_at)}</span>
|
|
317
433
|
</div>
|
|
318
434
|
)}
|
|
319
435
|
{task.executed_at && (
|
|
320
436
|
<div className="flex justify-between">
|
|
321
|
-
<span className="text-[
|
|
437
|
+
<span className="text-[var(--color-text-muted)]">Started</span>
|
|
322
438
|
<span>{new Date(task.executed_at).toLocaleString()}</span>
|
|
323
439
|
</div>
|
|
324
440
|
)}
|
|
325
441
|
{task.completed_at && (
|
|
326
442
|
<div className="flex justify-between">
|
|
327
|
-
<span className="text-[
|
|
443
|
+
<span className="text-[var(--color-text-muted)]">Completed</span>
|
|
328
444
|
<span>{new Date(task.completed_at).toLocaleString()}</span>
|
|
329
445
|
</div>
|
|
330
446
|
)}
|
|
331
447
|
{task.next_run && (
|
|
332
448
|
<div className="flex justify-between">
|
|
333
|
-
<span className="text-[
|
|
334
|
-
<span className="text-[
|
|
449
|
+
<span className="text-[var(--color-text-muted)]">Next Run</span>
|
|
450
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.next_run)}</span>
|
|
335
451
|
</div>
|
|
336
452
|
)}
|
|
337
453
|
</div>
|
|
@@ -361,13 +477,13 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
361
477
|
{/* Trajectory */}
|
|
362
478
|
{loading && !task.trajectory && (
|
|
363
479
|
<div>
|
|
364
|
-
<h4 className="text-xs text-[
|
|
365
|
-
<div className="text-sm text-[
|
|
480
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-2">Trajectory</h4>
|
|
481
|
+
<div className="text-sm text-[var(--color-text-faint)]">Loading trajectory...</div>
|
|
366
482
|
</div>
|
|
367
483
|
)}
|
|
368
484
|
{task.trajectory && task.trajectory.length > 0 && (
|
|
369
485
|
<div>
|
|
370
|
-
<h4 className="text-xs text-[
|
|
486
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-2">
|
|
371
487
|
Trajectory ({task.trajectory.length} steps)
|
|
372
488
|
</h4>
|
|
373
489
|
<TrajectoryView trajectory={task.trajectory} />
|
|
@@ -409,13 +525,13 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
409
525
|
|
|
410
526
|
return (
|
|
411
527
|
<div>
|
|
412
|
-
<p className={`text-sm text-[
|
|
528
|
+
<p className={`text-sm text-[var(--color-text)] whitespace-pre-wrap break-words ${!isExpanded && isLong ? 'line-clamp-4' : ''}`}>
|
|
413
529
|
{content}
|
|
414
530
|
</p>
|
|
415
531
|
{isLong && (
|
|
416
532
|
<button
|
|
417
533
|
onClick={() => toggleStep(step.id)}
|
|
418
|
-
className="text-xs text-[
|
|
534
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
419
535
|
>
|
|
420
536
|
{isExpanded ? "Show less" : "Show more..."}
|
|
421
537
|
</button>
|
|
@@ -439,15 +555,15 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
439
555
|
<div className="flex items-center gap-2 mb-1">
|
|
440
556
|
<span className="text-orange-400">🔧</span>
|
|
441
557
|
<span className="text-xs font-medium text-orange-400">Tool Call</span>
|
|
442
|
-
<span className="text-xs text-[
|
|
558
|
+
<span className="text-xs text-[var(--color-text-secondary)]">{block.name}</span>
|
|
443
559
|
</div>
|
|
444
|
-
<pre className={`text-xs text-[
|
|
560
|
+
<pre className={`text-xs text-[var(--color-text-secondary)] overflow-x-auto ${!isExpanded && isLong ? 'line-clamp-3' : ''}`}>
|
|
445
561
|
{inputStr}
|
|
446
562
|
</pre>
|
|
447
563
|
{isLong && (
|
|
448
564
|
<button
|
|
449
565
|
onClick={() => toggleStep(blockId)}
|
|
450
|
-
className="text-xs text-[
|
|
566
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
451
567
|
>
|
|
452
568
|
{isExpanded ? "Show less" : "Show more..."}
|
|
453
569
|
</button>
|
|
@@ -473,13 +589,13 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
473
589
|
Tool Result
|
|
474
590
|
</span>
|
|
475
591
|
</div>
|
|
476
|
-
<pre className={`text-xs text-[
|
|
592
|
+
<pre className={`text-xs text-[var(--color-text-secondary)] overflow-x-auto whitespace-pre-wrap break-words ${!isExpanded && isLong ? 'line-clamp-3' : ''}`}>
|
|
477
593
|
{block.content}
|
|
478
594
|
</pre>
|
|
479
595
|
{isLong && (
|
|
480
596
|
<button
|
|
481
597
|
onClick={() => toggleStep(blockId)}
|
|
482
|
-
className="text-xs text-[
|
|
598
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
483
599
|
>
|
|
484
600
|
{isExpanded ? "Show less" : "Show more..."}
|
|
485
601
|
</button>
|
|
@@ -502,15 +618,15 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
502
618
|
return (
|
|
503
619
|
<div
|
|
504
620
|
key={step.id}
|
|
505
|
-
className={`${style.bg} border border-[
|
|
621
|
+
className={`${style.bg} border border-[var(--color-border)] rounded overflow-hidden p-3`}
|
|
506
622
|
>
|
|
507
623
|
<div className="flex items-center gap-2 mb-2">
|
|
508
624
|
<span>{style.icon}</span>
|
|
509
625
|
<span className={`text-xs font-medium ${style.text}`}>{style.label}</span>
|
|
510
626
|
{step.model && (
|
|
511
|
-
<span className="text-xs text-[
|
|
627
|
+
<span className="text-xs text-[var(--color-text-faint)]">· {step.model}</span>
|
|
512
628
|
)}
|
|
513
|
-
<span className="text-xs text-[
|
|
629
|
+
<span className="text-xs text-[var(--color-text-faint)]">
|
|
514
630
|
· {new Date(step.created_at).toLocaleTimeString()}
|
|
515
631
|
</span>
|
|
516
632
|
</div>
|
|
@@ -522,6 +638,207 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
522
638
|
);
|
|
523
639
|
}
|
|
524
640
|
|
|
641
|
+
// --- Create Task Modal ---
|
|
642
|
+
|
|
643
|
+
interface CreateTaskModalProps {
|
|
644
|
+
authFetch: (url: string, options?: RequestInit) => Promise<Response>;
|
|
645
|
+
currentProjectId: string | null;
|
|
646
|
+
onClose: () => void;
|
|
647
|
+
onCreated: () => void;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function CreateTaskModal({ authFetch, currentProjectId, onClose, onCreated }: CreateTaskModalProps) {
|
|
651
|
+
const [agents, setAgents] = useState<{ id: string; name: string }[]>([]);
|
|
652
|
+
const [agentId, setAgentId] = useState("");
|
|
653
|
+
const [title, setTitle] = useState("");
|
|
654
|
+
const [description, setDescription] = useState("");
|
|
655
|
+
const [type, setType] = useState<"once" | "recurring">("once");
|
|
656
|
+
const [priority, setPriority] = useState(5);
|
|
657
|
+
const [executeAt, setExecuteAt] = useState("");
|
|
658
|
+
const [recurrence, setRecurrence] = useState("");
|
|
659
|
+
const [creating, setCreating] = useState(false);
|
|
660
|
+
const [error, setError] = useState("");
|
|
661
|
+
|
|
662
|
+
useEffect(() => {
|
|
663
|
+
authFetch("/api/agents").then(r => r.json()).then(data => {
|
|
664
|
+
const running = (data.agents || []).filter((a: Agent) => a.status === "running" && a.features?.tasks);
|
|
665
|
+
setAgents(running.map((a: Agent) => ({ id: a.id, name: a.name })));
|
|
666
|
+
if (running.length === 1) setAgentId(running[0].id);
|
|
667
|
+
}).catch(() => {});
|
|
668
|
+
}, [authFetch]);
|
|
669
|
+
|
|
670
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
671
|
+
e.preventDefault();
|
|
672
|
+
if (!agentId || !title.trim()) return;
|
|
673
|
+
setCreating(true);
|
|
674
|
+
setError("");
|
|
675
|
+
|
|
676
|
+
const body: Record<string, unknown> = {
|
|
677
|
+
title: title.trim(),
|
|
678
|
+
description: description.trim() || undefined,
|
|
679
|
+
type,
|
|
680
|
+
priority,
|
|
681
|
+
};
|
|
682
|
+
if (type === "once" && executeAt) {
|
|
683
|
+
body.execute_at = new Date(executeAt).toISOString();
|
|
684
|
+
}
|
|
685
|
+
if (type === "recurring" && recurrence.trim()) {
|
|
686
|
+
body.recurrence = recurrence.trim();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
const res = await authFetch(`/api/tasks/${agentId}`, {
|
|
691
|
+
method: "POST",
|
|
692
|
+
headers: { "Content-Type": "application/json" },
|
|
693
|
+
body: JSON.stringify(body),
|
|
694
|
+
});
|
|
695
|
+
if (!res.ok) {
|
|
696
|
+
const data = await res.json().catch(() => ({}));
|
|
697
|
+
setError(data.error || `HTTP ${res.status}`);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
onCreated();
|
|
701
|
+
} catch (err) {
|
|
702
|
+
setError(String(err));
|
|
703
|
+
} finally {
|
|
704
|
+
setCreating(false);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
return (
|
|
709
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
|
|
710
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg w-full max-w-md" onClick={e => e.stopPropagation()}>
|
|
711
|
+
<div className="flex items-center justify-between p-4 border-b border-[var(--color-border)]">
|
|
712
|
+
<h2 className="font-medium">Create Task</h2>
|
|
713
|
+
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|
|
714
|
+
<CloseIcon />
|
|
715
|
+
</button>
|
|
716
|
+
</div>
|
|
717
|
+
<form onSubmit={handleSubmit} className="p-4 space-y-4">
|
|
718
|
+
{/* Agent */}
|
|
719
|
+
<div>
|
|
720
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Agent</label>
|
|
721
|
+
{agents.length === 0 ? (
|
|
722
|
+
<p className="text-sm text-[var(--color-text-faint)]">No running agents with tasks enabled</p>
|
|
723
|
+
) : (
|
|
724
|
+
<select
|
|
725
|
+
value={agentId}
|
|
726
|
+
onChange={e => setAgentId(e.target.value)}
|
|
727
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm"
|
|
728
|
+
required
|
|
729
|
+
>
|
|
730
|
+
<option value="">Select agent...</option>
|
|
731
|
+
{agents.map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
|
|
732
|
+
</select>
|
|
733
|
+
)}
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
{/* Title */}
|
|
737
|
+
<div>
|
|
738
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Title</label>
|
|
739
|
+
<input
|
|
740
|
+
type="text"
|
|
741
|
+
value={title}
|
|
742
|
+
onChange={e => setTitle(e.target.value)}
|
|
743
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm"
|
|
744
|
+
placeholder="e.g. Check email for new orders"
|
|
745
|
+
required
|
|
746
|
+
/>
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
{/* Description */}
|
|
750
|
+
<div>
|
|
751
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Description</label>
|
|
752
|
+
<textarea
|
|
753
|
+
value={description}
|
|
754
|
+
onChange={e => setDescription(e.target.value)}
|
|
755
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm resize-none"
|
|
756
|
+
rows={2}
|
|
757
|
+
placeholder="Optional instructions for the agent..."
|
|
758
|
+
/>
|
|
759
|
+
</div>
|
|
760
|
+
|
|
761
|
+
{/* Type & Priority */}
|
|
762
|
+
<div className="grid grid-cols-2 gap-3">
|
|
763
|
+
<div>
|
|
764
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Type</label>
|
|
765
|
+
<select
|
|
766
|
+
value={type}
|
|
767
|
+
onChange={e => setType(e.target.value as "once" | "recurring")}
|
|
768
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm"
|
|
769
|
+
>
|
|
770
|
+
<option value="once">One-time</option>
|
|
771
|
+
<option value="recurring">Recurring</option>
|
|
772
|
+
</select>
|
|
773
|
+
</div>
|
|
774
|
+
<div>
|
|
775
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Priority</label>
|
|
776
|
+
<input
|
|
777
|
+
type="number"
|
|
778
|
+
min={1}
|
|
779
|
+
max={10}
|
|
780
|
+
value={priority}
|
|
781
|
+
onChange={e => setPriority(Number(e.target.value))}
|
|
782
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm"
|
|
783
|
+
/>
|
|
784
|
+
</div>
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
{/* Schedule */}
|
|
788
|
+
{type === "once" && (
|
|
789
|
+
<div>
|
|
790
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Schedule (optional)</label>
|
|
791
|
+
<input
|
|
792
|
+
type="datetime-local"
|
|
793
|
+
value={executeAt}
|
|
794
|
+
onChange={e => setExecuteAt(e.target.value)}
|
|
795
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm"
|
|
796
|
+
/>
|
|
797
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">Leave empty to execute immediately</p>
|
|
798
|
+
</div>
|
|
799
|
+
)}
|
|
800
|
+
|
|
801
|
+
{type === "recurring" && (
|
|
802
|
+
<div>
|
|
803
|
+
<label className="block text-sm text-[var(--color-text-muted)] mb-1">Cron Schedule</label>
|
|
804
|
+
<input
|
|
805
|
+
type="text"
|
|
806
|
+
value={recurrence}
|
|
807
|
+
onChange={e => setRecurrence(e.target.value)}
|
|
808
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border)] rounded px-3 py-2 text-sm font-mono"
|
|
809
|
+
placeholder="*/30 * * * *"
|
|
810
|
+
required
|
|
811
|
+
/>
|
|
812
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">e.g. */30 * * * * = every 30 min, 0 9 * * 1-5 = weekdays at 9am</p>
|
|
813
|
+
</div>
|
|
814
|
+
)}
|
|
815
|
+
|
|
816
|
+
{error && (
|
|
817
|
+
<p className="text-sm text-red-400">{error}</p>
|
|
818
|
+
)}
|
|
819
|
+
|
|
820
|
+
<div className="flex justify-end gap-2 pt-2">
|
|
821
|
+
<button
|
|
822
|
+
type="button"
|
|
823
|
+
onClick={onClose}
|
|
824
|
+
className="px-4 py-2 rounded text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-border)] transition"
|
|
825
|
+
>
|
|
826
|
+
Cancel
|
|
827
|
+
</button>
|
|
828
|
+
<button
|
|
829
|
+
type="submit"
|
|
830
|
+
disabled={creating || !agentId || !title.trim() || agents.length === 0}
|
|
831
|
+
className="px-4 py-2 rounded text-sm bg-[var(--color-accent)] text-black hover:opacity-90 transition disabled:opacity-50"
|
|
832
|
+
>
|
|
833
|
+
{creating ? "Creating..." : "Create Task"}
|
|
834
|
+
</button>
|
|
835
|
+
</div>
|
|
836
|
+
</form>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
|
|
525
842
|
// --- Schedule formatting helpers ---
|
|
526
843
|
|
|
527
844
|
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|