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.
Files changed (102) hide show
  1. package/dist/ActivityPage.c48n83h2.js +3 -0
  2. package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
  3. package/dist/App.09yb8t0b.js +1 -0
  4. package/dist/App.152mbs1r.js +4 -0
  5. package/dist/App.3a67nx9w.js +4 -0
  6. package/dist/App.9epx6785.js +4 -0
  7. package/dist/App.d8955awp.js +4 -0
  8. package/dist/App.drwb57jq.js +4 -0
  9. package/dist/App.gssbmajb.js +4 -0
  10. package/dist/App.qw70pc29.js +53 -0
  11. package/dist/{App.7fb3e7mp.js → App.qzbx5wtj.js} +1 -1
  12. package/dist/App.r5serxkt.js +8 -0
  13. package/dist/App.tpmp9020.js +20 -0
  14. package/dist/App.v2wb4d7d.js +61 -0
  15. package/dist/App.vxmaaj0m.js +13 -0
  16. package/dist/App.w4p2tda9.js +4 -0
  17. package/dist/App.wv2ng55q.js +221 -0
  18. package/dist/App.yncnrn0f.js +4 -0
  19. package/dist/ConnectionsPage.k6cspyqq.js +3 -0
  20. package/dist/McpPage.cdxm48xj.js +3 -0
  21. package/dist/SettingsPage.evpv7c2y.js +3 -0
  22. package/dist/SkillsPage.pvzp6c1a.js +3 -0
  23. package/dist/TasksPage.6jnvbpsy.js +3 -0
  24. package/dist/TelemetryPage.t7vk24zc.js +3 -0
  25. package/dist/TestsPage.5x6658aa.js +3 -0
  26. package/dist/ThreadsPage.3fvhtevh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +8 -8
  31. package/src/db.ts +19 -9
  32. package/src/integrations/agentdojo.ts +1 -0
  33. package/src/mcp-platform.ts +418 -63
  34. package/src/openapi.ts +96 -0
  35. package/src/providers.ts +50 -24
  36. package/src/routes/api/agent-utils.ts +0 -1
  37. package/src/routes/api/agents.ts +19 -1
  38. package/src/routes/api/meta-agent.ts +2 -0
  39. package/src/routes/api/system.ts +90 -1
  40. package/src/routes/api/telemetry.ts +19 -1
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +12 -0
  43. package/src/web/App.tsx +89 -11
  44. package/src/web/components/activity/ActivityPage.tsx +14 -14
  45. package/src/web/components/agents/AgentCard.tsx +14 -14
  46. package/src/web/components/agents/AgentPanel.tsx +358 -198
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  49. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  50. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  51. package/src/web/components/auth/LoginPage.tsx +10 -10
  52. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  53. package/src/web/components/common/Modal.tsx +8 -8
  54. package/src/web/components/common/Select.tsx +9 -9
  55. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  56. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  57. package/src/web/components/connections/OverviewTab.tsx +13 -13
  58. package/src/web/components/connections/TriggersTab.tsx +99 -99
  59. package/src/web/components/dashboard/Dashboard.tsx +32 -32
  60. package/src/web/components/layout/Header.tsx +50 -34
  61. package/src/web/components/layout/Sidebar.tsx +34 -15
  62. package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
  63. package/src/web/components/mcp/McpPage.tsx +208 -208
  64. package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
  65. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  66. package/src/web/components/settings/SettingsPage.tsx +258 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +339 -54
  69. package/src/web/components/telemetry/TelemetryPage.tsx +135 -64
  70. package/src/web/components/tests/TestsPage.tsx +50 -50
  71. package/src/web/components/threads/ThreadsPage.tsx +23 -21
  72. package/src/web/context/ProjectContext.tsx +6 -1
  73. package/src/web/context/ThemeContext.tsx +69 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/styles.css +5 -3
  76. package/src/web/themes.ts +99 -0
  77. package/src/web/types.ts +0 -4
  78. package/dist/ActivityPage.7907h64p.js +0 -3
  79. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  80. package/dist/App.01nq20st.js +0 -4
  81. package/dist/App.1maqvamf.js +0 -4
  82. package/dist/App.2yjrh32f.js +0 -4
  83. package/dist/App.3qw8nben.js +0 -20
  84. package/dist/App.7sy3wq8c.js +0 -4
  85. package/dist/App.apjrmctz.js +0 -57
  86. package/dist/App.av6t2yhe.js +0 -4
  87. package/dist/App.jqj5a094.js +0 -46
  88. package/dist/App.mc7xf85h.js +0 -4
  89. package/dist/App.myxqcj9x.js +0 -4
  90. package/dist/App.nm91r1mp.js +0 -13
  91. package/dist/App.p02f4ret.js +0 -1
  92. package/dist/App.qcknavjz.js +0 -221
  93. package/dist/App.vc7vfhg4.js +0 -4
  94. package/dist/App.z4s9zkw5.js +0 -4
  95. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  96. package/dist/McpPage.8vc97z0b.js +0 -3
  97. package/dist/SettingsPage.p61bz8kd.js +0 -3
  98. package/dist/SkillsPage.r9x43g3g.js +0 -3
  99. package/dist/TasksPage.1e0zkye4.js +0 -3
  100. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  101. package/dist/TestsPage.d4xy504e.js +0 -3
  102. 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
- <h1 className="text-xl md:text-2xl font-semibold mb-1">Tasks</h1>
156
- <p className="text-sm text-[#666]">
157
- View tasks from all running agents
158
- </p>
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-[#f97316] text-black"
169
- : "bg-[#1a1a1a] hover:bg-[#222]"
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-[#666]">Loading tasks...</div>
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-[#333]" />
198
- <p className="text-[#666]">No tasks found</p>
199
- <p className="text-sm text-[#444] mt-1">
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-[#111] border rounded-lg p-4 cursor-pointer transition ${
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-[#f97316]"
212
- : "border-[#1a1a1a] hover:border-[#333]"
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-[#666]">{task.agentName}</p>
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-[#888] mb-2 line-clamp-2">
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-[#555]">
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-[#f97316]">{formatRelativeTime(task.next_run)}</span>
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-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
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-[#1a1a1a] bg-[#0a0a0a] flex flex-col overflow-hidden">
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-[#1a1a1a]">
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
- <button onClick={onClose} className="text-[#666] hover:text-[#e0e0e0] transition">
286
- <CloseIcon />
287
- </button>
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-[#f97316] hover:underline"
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-[#666] uppercase tracking-wider mb-1">Description</h4>
312
- <p className="text-sm text-[#888] whitespace-pre-wrap">{task.description}</p>
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-[#666]">Type</span>
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-[#666]">Priority</span>
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-[#666]">Source</span>
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-[#666]">Recurrence</span>
416
+ <span className="text-[var(--color-text-muted)]">Recurrence</span>
333
417
  <p>{formatCron(task.recurrence)}</p>
334
- <p className="text-xs text-[#444] mt-0.5 font-mono">{task.recurrence}</p>
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-[#666]">Created</span>
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-[#666]">Scheduled</span>
348
- <span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
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-[#666]">Started</span>
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-[#666]">Completed</span>
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-[#666]">Next Run</span>
366
- <span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
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-[#666] uppercase tracking-wider mb-2">Trajectory</h4>
397
- <div className="text-sm text-[#555]">Loading trajectory...</div>
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-[#666] uppercase tracking-wider mb-2">
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-[#ccc] whitespace-pre-wrap break-words ${!isExpanded && isLong ? 'line-clamp-4' : ''}`}>
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-[#666] hover:text-[#888] mt-1"
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-[#888]">{block.name}</span>
558
+ <span className="text-xs text-[var(--color-text-secondary)]">{block.name}</span>
475
559
  </div>
476
- <pre className={`text-xs text-[#888] overflow-x-auto ${!isExpanded && isLong ? 'line-clamp-3' : ''}`}>
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-[#666] hover:text-[#888] mt-1"
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-[#888] overflow-x-auto whitespace-pre-wrap break-words ${!isExpanded && isLong ? 'line-clamp-3' : ''}`}>
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-[#666] hover:text-[#888] mt-1"
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-[#1a1a1a] rounded overflow-hidden p-3`}
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-[#555]">· {step.model}</span>
627
+ <span className="text-xs text-[var(--color-text-faint)]">· {step.model}</span>
544
628
  )}
545
- <span className="text-xs text-[#555]">
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"];