apteva 0.4.41 → 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.7fb3e7mp.js → App.qzbx5wtj.js} +1 -1
- 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 +8 -8
- package/src/db.ts +19 -9
- package/src/integrations/agentdojo.ts +1 -0
- package/src/mcp-platform.ts +418 -63
- package/src/openapi.ts +96 -0
- package/src/providers.ts +50 -24
- package/src/routes/api/agent-utils.ts +0 -1
- package/src/routes/api/agents.ts +19 -1
- package/src/routes/api/meta-agent.ts +2 -0
- package/src/routes/api/system.ts +90 -1
- package/src/routes/api/telemetry.ts +19 -1
- package/src/routes/share.ts +85 -0
- package/src/server.ts +12 -0
- package/src/web/App.tsx +89 -11
- package/src/web/components/activity/ActivityPage.tsx +14 -14
- package/src/web/components/agents/AgentCard.tsx +14 -14
- 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/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +8 -8
- package/src/web/components/common/Select.tsx +9 -9
- 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 +32 -32
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +34 -15
- package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
- package/src/web/components/mcp/McpPage.tsx +208 -208
- package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +258 -175
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +339 -54
- package/src/web/components/telemetry/TelemetryPage.tsx +135 -64
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +23 -21
- package/src/web/context/ProjectContext.tsx +6 -1
- package/src/web/context/ThemeContext.tsx +69 -0
- package/src/web/context/index.ts +2 -0
- package/src/web/styles.css +5 -3
- package/src/web/themes.ts +99 -0
- package/src/web/types.ts +0 -4
- package/dist/ActivityPage.7907h64p.js +0 -3
- package/dist/ApiDocsPage.k3jjenpq.js +0 -4
- package/dist/App.01nq20st.js +0 -4
- package/dist/App.1maqvamf.js +0 -4
- package/dist/App.2yjrh32f.js +0 -4
- package/dist/App.3qw8nben.js +0 -20
- package/dist/App.7sy3wq8c.js +0 -4
- package/dist/App.apjrmctz.js +0 -57
- package/dist/App.av6t2yhe.js +0 -4
- package/dist/App.jqj5a094.js +0 -46
- package/dist/App.mc7xf85h.js +0 -4
- package/dist/App.myxqcj9x.js +0 -4
- package/dist/App.nm91r1mp.js +0 -13
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.qcknavjz.js +0 -221
- package/dist/App.vc7vfhg4.js +0 -4
- package/dist/App.z4s9zkw5.js +0 -4
- package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
- package/dist/McpPage.8vc97z0b.js +0 -3
- package/dist/SettingsPage.p61bz8kd.js +0 -3
- package/dist/SkillsPage.r9x43g3g.js +0 -3
- package/dist/TasksPage.1e0zkye4.js +0 -3
- package/dist/TelemetryPage.p9vbe4gf.js +0 -3
- package/dist/TestsPage.d4xy504e.js +0 -3
- package/dist/ThreadsPage.m016am3x.js +0 -3
|
@@ -1,9 +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
3
|
import { Select } from "../common/Select";
|
|
4
|
+
import { useConfirm } from "../common/Modal";
|
|
4
5
|
import { useAuth, useProjects } from "../../context";
|
|
5
6
|
import { useTelemetry } from "../../context/TelemetryContext";
|
|
6
|
-
import type { Task, TaskTrajectoryStep, ToolUseBlock, ToolResultBlock } from "../../types";
|
|
7
|
+
import type { Task, TaskTrajectoryStep, ToolUseBlock, ToolResultBlock, Agent } from "../../types";
|
|
7
8
|
|
|
8
9
|
interface TasksPageProps {
|
|
9
10
|
onSelectAgent?: (agentId: string) => void;
|
|
@@ -18,6 +19,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
18
19
|
const [agentFilter, setAgentFilter] = useState<string>("all");
|
|
19
20
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
20
21
|
const [loadingTask, setLoadingTask] = useState(false);
|
|
22
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
21
23
|
const lastProcessedEventRef = useRef<string | null>(null);
|
|
22
24
|
|
|
23
25
|
// Subscribe to task telemetry events for real-time updates
|
|
@@ -151,11 +153,20 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
151
153
|
<div className={`flex-1 p-4 md:p-6 overflow-auto ${selectedTask ? 'hidden md:block md:w-1/2 lg:w-2/3' : ''}`}>
|
|
152
154
|
<div className="max-w-4xl">
|
|
153
155
|
<div className="mb-6">
|
|
154
|
-
<div className="mb-4">
|
|
155
|
-
<
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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>
|
|
159
170
|
</div>
|
|
160
171
|
<div className="flex items-center gap-3 flex-wrap pb-1">
|
|
161
172
|
<div className="flex gap-2 overflow-x-auto scrollbar-hide">
|
|
@@ -165,8 +176,8 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
165
176
|
onClick={() => setFilter(opt.value)}
|
|
166
177
|
className={`px-3 py-1.5 rounded text-sm transition whitespace-nowrap ${
|
|
167
178
|
filter === opt.value
|
|
168
|
-
? "bg-[
|
|
169
|
-
: "bg-[
|
|
179
|
+
? "bg-[var(--color-accent)] text-black"
|
|
180
|
+
: "bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)]"
|
|
170
181
|
}`}
|
|
171
182
|
>
|
|
172
183
|
{opt.label}
|
|
@@ -191,12 +202,12 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
191
202
|
</div>
|
|
192
203
|
|
|
193
204
|
{loading ? (
|
|
194
|
-
<div className="text-center py-12 text-[
|
|
205
|
+
<div className="text-center py-12 text-[var(--color-text-muted)]">Loading tasks...</div>
|
|
195
206
|
) : sortedTasks.length === 0 ? (
|
|
196
207
|
<div className="text-center py-12">
|
|
197
|
-
<TasksIcon className="w-12 h-12 mx-auto mb-4 text-[
|
|
198
|
-
<p className="text-[
|
|
199
|
-
<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">
|
|
200
211
|
Tasks will appear here when agents create them
|
|
201
212
|
</p>
|
|
202
213
|
</div>
|
|
@@ -206,16 +217,16 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
206
217
|
<div
|
|
207
218
|
key={`${task.agentId}-${task.id}`}
|
|
208
219
|
onClick={() => selectTask(task)}
|
|
209
|
-
className={`bg-[
|
|
220
|
+
className={`bg-[var(--color-surface)] border rounded-lg p-4 cursor-pointer transition ${
|
|
210
221
|
selectedTask?.id === task.id && selectedTask?.agentId === task.agentId
|
|
211
|
-
? "border-[
|
|
212
|
-
: "border-[
|
|
222
|
+
? "border-[var(--color-accent)]"
|
|
223
|
+
: "border-[var(--color-border)] hover:border-[var(--color-border-light)]"
|
|
213
224
|
}`}
|
|
214
225
|
>
|
|
215
226
|
<div className="flex items-start justify-between mb-2">
|
|
216
227
|
<div className="flex-1">
|
|
217
228
|
<h3 className="font-medium">{task.title}</h3>
|
|
218
|
-
<p className="text-sm text-[
|
|
229
|
+
<p className="text-sm text-[var(--color-text-muted)]">{task.agentName}</p>
|
|
219
230
|
</div>
|
|
220
231
|
<span className={`px-2 py-1 rounded text-xs font-medium ${statusColors[task.status] || statusColors.pending}`}>
|
|
221
232
|
{task.status}
|
|
@@ -223,12 +234,12 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
223
234
|
</div>
|
|
224
235
|
|
|
225
236
|
{task.description && (
|
|
226
|
-
<p className="text-sm text-[
|
|
237
|
+
<p className="text-sm text-[var(--color-text-secondary)] mb-2 line-clamp-2">
|
|
227
238
|
{task.description}
|
|
228
239
|
</p>
|
|
229
240
|
)}
|
|
230
241
|
|
|
231
|
-
<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)]">
|
|
232
243
|
<span className="flex items-center gap-1">
|
|
233
244
|
{task.type === "recurring"
|
|
234
245
|
? <RecurringIcon className="w-3.5 h-3.5" />
|
|
@@ -240,10 +251,10 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
240
251
|
</span>
|
|
241
252
|
<span>Priority: {task.priority}</span>
|
|
242
253
|
{task.next_run && (
|
|
243
|
-
<span className="text-[
|
|
254
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.next_run)}</span>
|
|
244
255
|
)}
|
|
245
256
|
{!task.next_run && task.execute_at && (
|
|
246
|
-
<span className="text-[
|
|
257
|
+
<span className="text-[var(--color-accent)]">{formatRelativeTime(task.execute_at)}</span>
|
|
247
258
|
)}
|
|
248
259
|
<span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
|
|
249
260
|
</div>
|
|
@@ -262,6 +273,18 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
262
273
|
onClose={() => setSelectedTask(null)}
|
|
263
274
|
onSelectAgent={onSelectAgent}
|
|
264
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); }}
|
|
265
288
|
/>
|
|
266
289
|
)}
|
|
267
290
|
</div>
|
|
@@ -274,17 +297,78 @@ export interface TaskDetailPanelProps {
|
|
|
274
297
|
onClose: () => void;
|
|
275
298
|
onSelectAgent?: (agentId: string) => void;
|
|
276
299
|
loading?: boolean;
|
|
300
|
+
authFetch?: (url: string, options?: RequestInit) => Promise<Response>;
|
|
301
|
+
onRefresh?: () => void;
|
|
277
302
|
}
|
|
278
303
|
|
|
279
|
-
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
|
+
|
|
280
341
|
return (
|
|
281
|
-
<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}
|
|
282
344
|
{/* Header */}
|
|
283
|
-
<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)]">
|
|
284
346
|
<h2 className="font-medium truncate pr-2">Task Details</h2>
|
|
285
|
-
<
|
|
286
|
-
|
|
287
|
-
|
|
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>
|
|
288
372
|
</div>
|
|
289
373
|
|
|
290
374
|
{/* Content */}
|
|
@@ -299,7 +383,7 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
299
383
|
</div>
|
|
300
384
|
<button
|
|
301
385
|
onClick={() => onSelectAgent?.(task.agentId)}
|
|
302
|
-
className="text-sm text-[
|
|
386
|
+
className="text-sm text-[var(--color-accent)] hover:underline"
|
|
303
387
|
>
|
|
304
388
|
{task.agentName}
|
|
305
389
|
</button>
|
|
@@ -308,30 +392,30 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
308
392
|
{/* Description */}
|
|
309
393
|
{task.description && (
|
|
310
394
|
<div>
|
|
311
|
-
<h4 className="text-xs text-[
|
|
312
|
-
<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>
|
|
313
397
|
</div>
|
|
314
398
|
)}
|
|
315
399
|
|
|
316
400
|
{/* Metadata */}
|
|
317
401
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
318
402
|
<div>
|
|
319
|
-
<span className="text-[
|
|
403
|
+
<span className="text-[var(--color-text-muted)]">Type</span>
|
|
320
404
|
<p className="capitalize">{task.type}</p>
|
|
321
405
|
</div>
|
|
322
406
|
<div>
|
|
323
|
-
<span className="text-[
|
|
407
|
+
<span className="text-[var(--color-text-muted)]">Priority</span>
|
|
324
408
|
<p>{task.priority}</p>
|
|
325
409
|
</div>
|
|
326
410
|
<div>
|
|
327
|
-
<span className="text-[
|
|
411
|
+
<span className="text-[var(--color-text-muted)]">Source</span>
|
|
328
412
|
<p className="capitalize">{task.source}</p>
|
|
329
413
|
</div>
|
|
330
414
|
{task.recurrence && (
|
|
331
415
|
<div>
|
|
332
|
-
<span className="text-[
|
|
416
|
+
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
333
417
|
<p>{formatCron(task.recurrence)}</p>
|
|
334
|
-
<p className="text-xs text-[
|
|
418
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{task.recurrence}</p>
|
|
335
419
|
</div>
|
|
336
420
|
)}
|
|
337
421
|
</div>
|
|
@@ -339,31 +423,31 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
339
423
|
{/* Timestamps */}
|
|
340
424
|
<div className="space-y-2 text-sm">
|
|
341
425
|
<div className="flex justify-between">
|
|
342
|
-
<span className="text-[
|
|
426
|
+
<span className="text-[var(--color-text-muted)]">Created</span>
|
|
343
427
|
<span>{new Date(task.created_at).toLocaleString()}</span>
|
|
344
428
|
</div>
|
|
345
429
|
{task.execute_at && (
|
|
346
430
|
<div className="flex justify-between">
|
|
347
|
-
<span className="text-[
|
|
348
|
-
<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>
|
|
349
433
|
</div>
|
|
350
434
|
)}
|
|
351
435
|
{task.executed_at && (
|
|
352
436
|
<div className="flex justify-between">
|
|
353
|
-
<span className="text-[
|
|
437
|
+
<span className="text-[var(--color-text-muted)]">Started</span>
|
|
354
438
|
<span>{new Date(task.executed_at).toLocaleString()}</span>
|
|
355
439
|
</div>
|
|
356
440
|
)}
|
|
357
441
|
{task.completed_at && (
|
|
358
442
|
<div className="flex justify-between">
|
|
359
|
-
<span className="text-[
|
|
443
|
+
<span className="text-[var(--color-text-muted)]">Completed</span>
|
|
360
444
|
<span>{new Date(task.completed_at).toLocaleString()}</span>
|
|
361
445
|
</div>
|
|
362
446
|
)}
|
|
363
447
|
{task.next_run && (
|
|
364
448
|
<div className="flex justify-between">
|
|
365
|
-
<span className="text-[
|
|
366
|
-
<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>
|
|
367
451
|
</div>
|
|
368
452
|
)}
|
|
369
453
|
</div>
|
|
@@ -393,13 +477,13 @@ export function TaskDetailPanel({ task, statusColors, onClose, onSelectAgent, lo
|
|
|
393
477
|
{/* Trajectory */}
|
|
394
478
|
{loading && !task.trajectory && (
|
|
395
479
|
<div>
|
|
396
|
-
<h4 className="text-xs text-[
|
|
397
|
-
<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>
|
|
398
482
|
</div>
|
|
399
483
|
)}
|
|
400
484
|
{task.trajectory && task.trajectory.length > 0 && (
|
|
401
485
|
<div>
|
|
402
|
-
<h4 className="text-xs text-[
|
|
486
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-2">
|
|
403
487
|
Trajectory ({task.trajectory.length} steps)
|
|
404
488
|
</h4>
|
|
405
489
|
<TrajectoryView trajectory={task.trajectory} />
|
|
@@ -441,13 +525,13 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
441
525
|
|
|
442
526
|
return (
|
|
443
527
|
<div>
|
|
444
|
-
<p className={`text-sm text-[
|
|
528
|
+
<p className={`text-sm text-[var(--color-text)] whitespace-pre-wrap break-words ${!isExpanded && isLong ? 'line-clamp-4' : ''}`}>
|
|
445
529
|
{content}
|
|
446
530
|
</p>
|
|
447
531
|
{isLong && (
|
|
448
532
|
<button
|
|
449
533
|
onClick={() => toggleStep(step.id)}
|
|
450
|
-
className="text-xs text-[
|
|
534
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
451
535
|
>
|
|
452
536
|
{isExpanded ? "Show less" : "Show more..."}
|
|
453
537
|
</button>
|
|
@@ -471,15 +555,15 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
471
555
|
<div className="flex items-center gap-2 mb-1">
|
|
472
556
|
<span className="text-orange-400">🔧</span>
|
|
473
557
|
<span className="text-xs font-medium text-orange-400">Tool Call</span>
|
|
474
|
-
<span className="text-xs text-[
|
|
558
|
+
<span className="text-xs text-[var(--color-text-secondary)]">{block.name}</span>
|
|
475
559
|
</div>
|
|
476
|
-
<pre className={`text-xs text-[
|
|
560
|
+
<pre className={`text-xs text-[var(--color-text-secondary)] overflow-x-auto ${!isExpanded && isLong ? 'line-clamp-3' : ''}`}>
|
|
477
561
|
{inputStr}
|
|
478
562
|
</pre>
|
|
479
563
|
{isLong && (
|
|
480
564
|
<button
|
|
481
565
|
onClick={() => toggleStep(blockId)}
|
|
482
|
-
className="text-xs text-[
|
|
566
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
483
567
|
>
|
|
484
568
|
{isExpanded ? "Show less" : "Show more..."}
|
|
485
569
|
</button>
|
|
@@ -505,13 +589,13 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
505
589
|
Tool Result
|
|
506
590
|
</span>
|
|
507
591
|
</div>
|
|
508
|
-
<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' : ''}`}>
|
|
509
593
|
{block.content}
|
|
510
594
|
</pre>
|
|
511
595
|
{isLong && (
|
|
512
596
|
<button
|
|
513
597
|
onClick={() => toggleStep(blockId)}
|
|
514
|
-
className="text-xs text-[
|
|
598
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] mt-1"
|
|
515
599
|
>
|
|
516
600
|
{isExpanded ? "Show less" : "Show more..."}
|
|
517
601
|
</button>
|
|
@@ -534,15 +618,15 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
534
618
|
return (
|
|
535
619
|
<div
|
|
536
620
|
key={step.id}
|
|
537
|
-
className={`${style.bg} border border-[
|
|
621
|
+
className={`${style.bg} border border-[var(--color-border)] rounded overflow-hidden p-3`}
|
|
538
622
|
>
|
|
539
623
|
<div className="flex items-center gap-2 mb-2">
|
|
540
624
|
<span>{style.icon}</span>
|
|
541
625
|
<span className={`text-xs font-medium ${style.text}`}>{style.label}</span>
|
|
542
626
|
{step.model && (
|
|
543
|
-
<span className="text-xs text-[
|
|
627
|
+
<span className="text-xs text-[var(--color-text-faint)]">· {step.model}</span>
|
|
544
628
|
)}
|
|
545
|
-
<span className="text-xs text-[
|
|
629
|
+
<span className="text-xs text-[var(--color-text-faint)]">
|
|
546
630
|
· {new Date(step.created_at).toLocaleTimeString()}
|
|
547
631
|
</span>
|
|
548
632
|
</div>
|
|
@@ -554,6 +638,207 @@ export function TrajectoryView({ trajectory }: { trajectory: TaskTrajectoryStep[
|
|
|
554
638
|
);
|
|
555
639
|
}
|
|
556
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
|
+
|
|
557
842
|
// --- Schedule formatting helpers ---
|
|
558
843
|
|
|
559
844
|
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|