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.
Files changed (113) 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.qzbx5wtj.js +4 -0
  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 +10 -9
  31. package/src/crypto.ts +4 -3
  32. package/src/db.ts +171 -36
  33. package/src/integrations/agentdojo.ts +95 -12
  34. package/src/integrations/index.ts +7 -0
  35. package/src/mcp-platform.ts +870 -142
  36. package/src/openapi.ts +96 -0
  37. package/src/providers.ts +60 -34
  38. package/src/routes/api/agent-utils.ts +59 -47
  39. package/src/routes/api/agents.ts +71 -2
  40. package/src/routes/api/integrations.ts +11 -5
  41. package/src/routes/api/mcp.ts +5 -4
  42. package/src/routes/api/meta-agent.ts +37 -1
  43. package/src/routes/api/projects.ts +3 -3
  44. package/src/routes/api/providers.ts +121 -30
  45. package/src/routes/api/skills.ts +2 -3
  46. package/src/routes/api/system.ts +98 -14
  47. package/src/routes/api/telemetry.ts +19 -1
  48. package/src/routes/share.ts +85 -0
  49. package/src/server.ts +43 -32
  50. package/src/triggers/agentdojo.ts +2 -2
  51. package/src/web/App.tsx +107 -21
  52. package/src/web/components/activity/ActivityPage.tsx +242 -389
  53. package/src/web/components/agents/AgentCard.tsx +19 -27
  54. package/src/web/components/agents/AgentPanel.tsx +358 -198
  55. package/src/web/components/agents/AgentsView.tsx +4 -4
  56. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  57. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  58. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  59. package/src/web/components/auth/LoginPage.tsx +10 -10
  60. package/src/web/components/common/Icons.tsx +8 -0
  61. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  62. package/src/web/components/common/Modal.tsx +8 -8
  63. package/src/web/components/common/Select.tsx +11 -10
  64. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  65. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  66. package/src/web/components/connections/OverviewTab.tsx +13 -13
  67. package/src/web/components/connections/TriggersTab.tsx +99 -99
  68. package/src/web/components/dashboard/Dashboard.tsx +177 -52
  69. package/src/web/components/index.ts +1 -1
  70. package/src/web/components/layout/Header.tsx +50 -34
  71. package/src/web/components/layout/Sidebar.tsx +41 -16
  72. package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
  73. package/src/web/components/mcp/McpPage.tsx +218 -209
  74. package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
  75. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  76. package/src/web/components/settings/SettingsPage.tsx +389 -221
  77. package/src/web/components/skills/SkillsPage.tsx +88 -88
  78. package/src/web/components/tasks/TasksPage.tsx +385 -68
  79. package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
  80. package/src/web/components/tests/TestsPage.tsx +50 -50
  81. package/src/web/components/threads/ThreadsPage.tsx +315 -0
  82. package/src/web/context/AuthContext.tsx +3 -3
  83. package/src/web/context/ProjectContext.tsx +8 -3
  84. package/src/web/context/TelemetryContext.tsx +24 -6
  85. package/src/web/context/ThemeContext.tsx +69 -0
  86. package/src/web/context/index.ts +3 -1
  87. package/src/web/styles.css +25 -7
  88. package/src/web/themes.ts +99 -0
  89. package/src/web/types.ts +4 -7
  90. package/dist/ActivityPage.41nbye4r.js +0 -3
  91. package/dist/ApiDocsPage.4smnt8m3.js +0 -4
  92. package/dist/App.0sbax9et.js +0 -4
  93. package/dist/App.0ws427h8.js +0 -4
  94. package/dist/App.6q6bar8b.js +0 -4
  95. package/dist/App.80301vdb.js +0 -4
  96. package/dist/App.af2wg84v.js +0 -267
  97. package/dist/App.ca1rz1ph.js +0 -4
  98. package/dist/App.ensa6z0r.js +0 -4
  99. package/dist/App.f8g7tych.js +0 -13
  100. package/dist/App.mvtqv6qc.js +0 -20
  101. package/dist/App.ncgc9cxy.js +0 -4
  102. package/dist/App.p02f4ret.js +0 -1
  103. package/dist/App.p0fb1pds.js +0 -4
  104. package/dist/App.pmaq48sj.js +0 -4
  105. package/dist/App.yv87t9m5.js +0 -4
  106. package/dist/App.zjmfm8p6.js +0 -4
  107. package/dist/ConnectionsPage.anb3rv9a.js +0 -3
  108. package/dist/McpPage.y396h6fy.js +0 -3
  109. package/dist/SettingsPage.p1hc60gk.js +0 -3
  110. package/dist/SkillsPage.yj3xdsay.js +0 -3
  111. package/dist/TasksPage.sjv0khtv.js +0 -3
  112. package/dist/TelemetryPage.2qm4w16r.js +0 -3
  113. 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
- return [...tasks].sort((a, b) => {
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
- <h1 className="text-xl md:text-2xl font-semibold mb-1">Tasks</h1>
140
- <p className="text-sm text-[#666]">
141
- View tasks from all running agents
142
- </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>
143
170
  </div>
144
- <div className="flex gap-2 overflow-x-auto scrollbar-hide pb-1">
145
- {filterOptions.map(opt => (
146
- <button
147
- key={opt.value}
148
- onClick={() => setFilter(opt.value)}
149
- className={`px-3 py-1.5 rounded text-sm transition whitespace-nowrap ${
150
- filter === opt.value
151
- ? "bg-[#f97316] text-black"
152
- : "bg-[#1a1a1a] hover:bg-[#222]"
153
- }`}
154
- >
155
- {opt.label}
156
- </button>
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-[#666]">Loading tasks...</div>
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-[#333]" />
166
- <p className="text-[#666]">No tasks found</p>
167
- <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">
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-[#111] border rounded-lg p-4 cursor-pointer transition ${
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-[#f97316]"
180
- : "border-[#1a1a1a] hover:border-[#333]"
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-[#666]">{task.agentName}</p>
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-[#888] mb-2 line-clamp-2">
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-[#555]">
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-[#f97316]">{formatRelativeTime(task.next_run)}</span>
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-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
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-[#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}
250
344
  {/* Header */}
251
- <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)]">
252
346
  <h2 className="font-medium truncate pr-2">Task Details</h2>
253
- <button onClick={onClose} className="text-[#666] hover:text-[#e0e0e0] transition">
254
- <CloseIcon />
255
- </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>
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-[#f97316] hover:underline"
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-[#666] uppercase tracking-wider mb-1">Description</h4>
280
- <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>
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-[#666]">Type</span>
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-[#666]">Priority</span>
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-[#666]">Source</span>
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-[#666]">Recurrence</span>
416
+ <span className="text-[var(--color-text-muted)]">Recurrence</span>
301
417
  <p>{formatCron(task.recurrence)}</p>
302
- <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>
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-[#666]">Created</span>
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-[#666]">Scheduled</span>
316
- <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>
317
433
  </div>
318
434
  )}
319
435
  {task.executed_at && (
320
436
  <div className="flex justify-between">
321
- <span className="text-[#666]">Started</span>
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-[#666]">Completed</span>
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-[#666]">Next Run</span>
334
- <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>
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-[#666] uppercase tracking-wider mb-2">Trajectory</h4>
365
- <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>
366
482
  </div>
367
483
  )}
368
484
  {task.trajectory && task.trajectory.length > 0 && (
369
485
  <div>
370
- <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">
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-[#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' : ''}`}>
413
529
  {content}
414
530
  </p>
415
531
  {isLong && (
416
532
  <button
417
533
  onClick={() => toggleStep(step.id)}
418
- 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"
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-[#888]">{block.name}</span>
558
+ <span className="text-xs text-[var(--color-text-secondary)]">{block.name}</span>
443
559
  </div>
444
- <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' : ''}`}>
445
561
  {inputStr}
446
562
  </pre>
447
563
  {isLong && (
448
564
  <button
449
565
  onClick={() => toggleStep(blockId)}
450
- 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"
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-[#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' : ''}`}>
477
593
  {block.content}
478
594
  </pre>
479
595
  {isLong && (
480
596
  <button
481
597
  onClick={() => toggleStep(blockId)}
482
- 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"
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-[#1a1a1a] rounded overflow-hidden p-3`}
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-[#555]">· {step.model}</span>
627
+ <span className="text-xs text-[var(--color-text-faint)]">· {step.model}</span>
512
628
  )}
513
- <span className="text-xs text-[#555]">
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"];