apteva 0.2.3 → 0.2.6

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 (38) hide show
  1. package/dist/App.0mzj9cz9.js +213 -0
  2. package/dist/index.html +1 -1
  3. package/dist/styles.css +1 -1
  4. package/package.json +6 -6
  5. package/src/binary.ts +271 -1
  6. package/src/crypto.ts +53 -0
  7. package/src/db.ts +492 -3
  8. package/src/mcp-client.ts +599 -0
  9. package/src/providers.ts +31 -0
  10. package/src/routes/api.ts +832 -64
  11. package/src/server.ts +169 -5
  12. package/src/web/App.tsx +44 -2
  13. package/src/web/components/agents/AgentCard.tsx +53 -9
  14. package/src/web/components/agents/AgentPanel.tsx +381 -0
  15. package/src/web/components/agents/AgentsView.tsx +27 -10
  16. package/src/web/components/agents/CreateAgentModal.tsx +7 -7
  17. package/src/web/components/agents/index.ts +1 -1
  18. package/src/web/components/common/Icons.tsx +8 -0
  19. package/src/web/components/common/Modal.tsx +2 -2
  20. package/src/web/components/common/Select.tsx +1 -1
  21. package/src/web/components/common/index.ts +1 -0
  22. package/src/web/components/dashboard/Dashboard.tsx +74 -25
  23. package/src/web/components/index.ts +5 -2
  24. package/src/web/components/layout/Sidebar.tsx +22 -2
  25. package/src/web/components/mcp/McpPage.tsx +1144 -0
  26. package/src/web/components/mcp/index.ts +1 -0
  27. package/src/web/components/onboarding/OnboardingWizard.tsx +5 -1
  28. package/src/web/components/settings/SettingsPage.tsx +312 -82
  29. package/src/web/components/tasks/TasksPage.tsx +129 -0
  30. package/src/web/components/tasks/index.ts +1 -0
  31. package/src/web/components/telemetry/TelemetryPage.tsx +359 -0
  32. package/src/web/context/TelemetryContext.tsx +202 -0
  33. package/src/web/context/index.ts +2 -0
  34. package/src/web/hooks/useAgents.ts +23 -0
  35. package/src/web/styles.css +18 -0
  36. package/src/web/types.ts +75 -1
  37. package/dist/App.wfhmfhx7.js +0 -213
  38. package/src/web/components/agents/ChatPanel.tsx +0 -63
@@ -0,0 +1 @@
1
+ export { McpPage } from "./McpPage";
@@ -19,7 +19,11 @@ export function OnboardingWizard({ onComplete }: OnboardingWizardProps) {
19
19
  useEffect(() => {
20
20
  fetch("/api/providers")
21
21
  .then(res => res.json())
22
- .then(data => setProviders(data.providers || []));
22
+ .then(data => {
23
+ // Only show LLM providers in onboarding, not integrations
24
+ const llmProviders = (data.providers || []).filter((p: Provider) => p.type === "llm");
25
+ setProviders(llmProviders);
26
+ });
23
27
  }, []);
24
28
 
25
29
  const configuredProviders = providers.filter(p => p.hasKey);
@@ -126,43 +126,87 @@ function ProvidersSettings() {
126
126
  fetchProviders();
127
127
  };
128
128
 
129
- const configuredCount = providers.filter(p => p.hasKey).length;
129
+ const llmProviders = providers.filter(p => p.type === "llm");
130
+ const integrations = providers.filter(p => p.type === "integration");
131
+ const llmConfiguredCount = llmProviders.filter(p => p.hasKey).length;
132
+ const intConfiguredCount = integrations.filter(p => p.hasKey).length;
130
133
 
131
134
  return (
132
- <div className="max-w-4xl">
133
- <div className="mb-6">
134
- <h1 className="text-2xl font-semibold mb-1">AI Providers</h1>
135
- <p className="text-[#666]">
136
- Manage your API keys for AI providers. {configuredCount} of {providers.length} configured.
137
- </p>
135
+ <div className="max-w-4xl space-y-10">
136
+ {/* AI Providers Section */}
137
+ <div>
138
+ <div className="mb-6">
139
+ <h1 className="text-2xl font-semibold mb-1">AI Providers</h1>
140
+ <p className="text-[#666]">
141
+ Manage your API keys for AI providers. {llmConfiguredCount} of {llmProviders.length} configured.
142
+ </p>
143
+ </div>
144
+
145
+ <div className="grid gap-4 md:grid-cols-2">
146
+ {llmProviders.map(provider => (
147
+ <ProviderKeyCard
148
+ key={provider.id}
149
+ provider={provider}
150
+ isEditing={selectedProvider === provider.id}
151
+ apiKey={apiKey}
152
+ saving={saving}
153
+ testing={testing}
154
+ error={selectedProvider === provider.id ? error : null}
155
+ success={selectedProvider === provider.id ? success : null}
156
+ onStartEdit={() => {
157
+ setSelectedProvider(provider.id);
158
+ setError(null);
159
+ setSuccess(null);
160
+ }}
161
+ onCancelEdit={() => {
162
+ setSelectedProvider(null);
163
+ setApiKey("");
164
+ setError(null);
165
+ }}
166
+ onApiKeyChange={setApiKey}
167
+ onSave={saveKey}
168
+ onDelete={() => deleteKey(provider.id)}
169
+ />
170
+ ))}
171
+ </div>
138
172
  </div>
139
173
 
140
- <div className="grid gap-4 md:grid-cols-2">
141
- {providers.map(provider => (
142
- <ProviderKeyCard
143
- key={provider.id}
144
- provider={provider}
145
- isEditing={selectedProvider === provider.id}
146
- apiKey={apiKey}
147
- saving={saving}
148
- testing={testing}
149
- error={selectedProvider === provider.id ? error : null}
150
- success={selectedProvider === provider.id ? success : null}
151
- onStartEdit={() => {
152
- setSelectedProvider(provider.id);
153
- setError(null);
154
- setSuccess(null);
155
- }}
156
- onCancelEdit={() => {
157
- setSelectedProvider(null);
158
- setApiKey("");
159
- setError(null);
160
- }}
161
- onApiKeyChange={setApiKey}
162
- onSave={saveKey}
163
- onDelete={() => deleteKey(provider.id)}
164
- />
165
- ))}
174
+ {/* MCP Integrations Section */}
175
+ <div>
176
+ <div className="mb-6">
177
+ <h2 className="text-xl font-semibold mb-1">MCP Integrations</h2>
178
+ <p className="text-[#666]">
179
+ Connect to MCP gateways for tool integrations. {intConfiguredCount} of {integrations.length} configured.
180
+ </p>
181
+ </div>
182
+
183
+ <div className="grid gap-4 md:grid-cols-2">
184
+ {integrations.map(provider => (
185
+ <IntegrationKeyCard
186
+ key={provider.id}
187
+ provider={provider}
188
+ isEditing={selectedProvider === provider.id}
189
+ apiKey={apiKey}
190
+ saving={saving}
191
+ testing={testing}
192
+ error={selectedProvider === provider.id ? error : null}
193
+ success={selectedProvider === provider.id ? success : null}
194
+ onStartEdit={() => {
195
+ setSelectedProvider(provider.id);
196
+ setError(null);
197
+ setSuccess(null);
198
+ }}
199
+ onCancelEdit={() => {
200
+ setSelectedProvider(null);
201
+ setApiKey("");
202
+ setError(null);
203
+ }}
204
+ onApiKeyChange={setApiKey}
205
+ onSave={saveKey}
206
+ onDelete={() => deleteKey(provider.id)}
207
+ />
208
+ ))}
209
+ </div>
166
210
  </div>
167
211
  </div>
168
212
  );
@@ -184,17 +228,24 @@ interface ProviderKeyCardProps {
184
228
  }
185
229
 
186
230
  interface VersionInfo {
187
- current: string;
188
- latest: string;
231
+ installed: string | null;
232
+ latest: string | null;
189
233
  updateAvailable: boolean;
190
- updateCommand: string;
234
+ lastChecked: string | null;
235
+ }
236
+
237
+ interface AllVersionInfo {
238
+ apteva: VersionInfo;
239
+ agent: VersionInfo;
191
240
  }
192
241
 
193
242
  function UpdatesSettings() {
194
- const [version, setVersion] = useState<VersionInfo | null>(null);
243
+ const [versions, setVersions] = useState<AllVersionInfo | null>(null);
195
244
  const [checking, setChecking] = useState(false);
245
+ const [updatingAgent, setUpdatingAgent] = useState(false);
196
246
  const [error, setError] = useState<string | null>(null);
197
- const [copied, setCopied] = useState(false);
247
+ const [updateSuccess, setUpdateSuccess] = useState<string | null>(null);
248
+ const [copied, setCopied] = useState<string | null>(null);
198
249
 
199
250
  const checkForUpdates = async () => {
200
251
  setChecking(true);
@@ -203,92 +254,165 @@ function UpdatesSettings() {
203
254
  const res = await fetch("/api/version");
204
255
  if (!res.ok) throw new Error("Failed to check for updates");
205
256
  const data = await res.json();
206
- setVersion(data);
257
+ setVersions(data);
207
258
  } catch (e) {
208
259
  setError("Failed to check for updates");
209
260
  }
210
261
  setChecking(false);
211
262
  };
212
263
 
264
+ const updateAgent = async () => {
265
+ setUpdatingAgent(true);
266
+ setError(null);
267
+ setUpdateSuccess(null);
268
+ try {
269
+ const res = await fetch("/api/version/update", { method: "POST" });
270
+ const data = await res.json();
271
+ if (!data.success) {
272
+ setError(data.error || "Update failed");
273
+ } else {
274
+ setUpdateSuccess(`Agent updated to v${data.version}. New agents will use this version.`);
275
+ await checkForUpdates();
276
+ }
277
+ } catch (e) {
278
+ setError("Failed to update agent");
279
+ }
280
+ setUpdatingAgent(false);
281
+ };
282
+
213
283
  useEffect(() => {
214
284
  checkForUpdates();
215
285
  }, []);
216
286
 
217
- const copyCommand = () => {
218
- if (version?.updateCommand) {
219
- navigator.clipboard.writeText(version.updateCommand);
220
- setCopied(true);
221
- setTimeout(() => setCopied(false), 2000);
222
- }
287
+ const copyCommand = (cmd: string, id: string) => {
288
+ navigator.clipboard.writeText(cmd);
289
+ setCopied(id);
290
+ setTimeout(() => setCopied(null), 2000);
223
291
  };
224
292
 
293
+ const hasAnyUpdate = versions?.apteva.updateAvailable || versions?.agent.updateAvailable;
294
+
225
295
  return (
226
296
  <div className="max-w-2xl">
227
297
  <div className="mb-6">
228
298
  <h1 className="text-2xl font-semibold mb-1">Updates</h1>
229
299
  <p className="text-[#666]">
230
- Check for new versions of apteva.
300
+ Check for new versions of apteva and the agent binary.
231
301
  </p>
232
302
  </div>
233
303
 
234
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6">
235
- {checking && !version ? (
236
- <div className="text-[#666]">Checking for updates...</div>
237
- ) : error ? (
238
- <div className="text-red-400">{error}</div>
239
- ) : version ? (
240
- <div className="space-y-4">
241
- <div className="flex items-center justify-between">
304
+ {checking && !versions ? (
305
+ <div className="text-[#666]">Checking for updates...</div>
306
+ ) : error && !versions ? (
307
+ <div className="text-red-400">{error}</div>
308
+ ) : versions ? (
309
+ <div className="space-y-6">
310
+ {updateSuccess && (
311
+ <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4 text-green-400">
312
+ {updateSuccess}
313
+ </div>
314
+ )}
315
+
316
+ {error && (
317
+ <div className="bg-red-500/10 border border-red-500/30 rounded-lg p-4 text-red-400">
318
+ {error}
319
+ </div>
320
+ )}
321
+
322
+ {/* Apteva App Version */}
323
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-5">
324
+ <div className="flex items-center justify-between mb-4">
242
325
  <div>
243
- <div className="text-sm text-[#666]">Current version</div>
244
- <div className="text-xl font-mono">v{version.current}</div>
326
+ <h3 className="font-medium text-lg">apteva</h3>
327
+ <p className="text-sm text-[#666]">The app you're running</p>
245
328
  </div>
246
329
  <div className="text-right">
247
- <div className="text-sm text-[#666]">Latest version</div>
248
- <div className="text-xl font-mono">v{version.latest}</div>
330
+ <div className="text-xl font-mono">v{versions.apteva.installed || "?"}</div>
331
+ {versions.apteva.updateAvailable && (
332
+ <div className="text-sm text-[#f97316]">→ v{versions.apteva.latest}</div>
333
+ )}
249
334
  </div>
250
335
  </div>
251
336
 
252
- {version.updateAvailable ? (
337
+ {versions.apteva.updateAvailable ? (
253
338
  <div className="bg-[#f97316]/10 border border-[#f97316]/30 rounded-lg p-4">
254
- <div className="flex items-center gap-2 text-[#f97316] font-medium mb-2">
255
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
256
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
257
- </svg>
258
- Update available!
259
- </div>
260
339
  <p className="text-sm text-[#888] mb-3">
261
- A new version of apteva is available. Run this command to update:
340
+ Update by running:
262
341
  </p>
263
342
  <div className="flex items-center gap-2">
264
- <code className="flex-1 bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm">
265
- {version.updateCommand}
343
+ <code className="flex-1 bg-[#0a0a0a] px-3 py-2 rounded font-mono text-sm text-[#888]">
344
+ npx apteva@latest
266
345
  </code>
267
346
  <button
268
- onClick={copyCommand}
347
+ onClick={() => copyCommand("npx apteva@latest", "apteva")}
269
348
  className="px-3 py-2 bg-[#1a1a1a] hover:bg-[#222] rounded text-sm"
270
349
  >
271
- {copied ? "Copied!" : "Copy"}
350
+ {copied === "apteva" ? "Copied!" : "Copy"}
272
351
  </button>
273
352
  </div>
274
353
  </div>
275
354
  ) : (
276
- <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4 flex items-center gap-2 text-green-400">
277
- <CheckIcon className="w-5 h-5" />
278
- You're running the latest version!
355
+ <div className="flex items-center gap-2 text-green-400 text-sm">
356
+ <CheckIcon className="w-4 h-4" />
357
+ Up to date
279
358
  </div>
280
359
  )}
360
+ </div>
361
+
362
+ {/* Agent Binary Version */}
363
+ <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-5">
364
+ <div className="flex items-center justify-between mb-4">
365
+ <div>
366
+ <h3 className="font-medium text-lg">Agent Binary</h3>
367
+ <p className="text-sm text-[#666]">The Go binary that runs agents</p>
368
+ </div>
369
+ <div className="text-right">
370
+ <div className="text-xl font-mono">v{versions.agent.installed || "?"}</div>
371
+ {versions.agent.updateAvailable && (
372
+ <div className="text-sm text-[#f97316]">→ v{versions.agent.latest}</div>
373
+ )}
374
+ </div>
375
+ </div>
281
376
 
282
- <button
283
- onClick={checkForUpdates}
284
- disabled={checking}
285
- className="text-sm text-[#666] hover:text-[#888] disabled:opacity-50"
286
- >
287
- {checking ? "Checking..." : "Check again"}
288
- </button>
377
+ {versions.agent.updateAvailable ? (
378
+ <div className="bg-[#f97316]/10 border border-[#f97316]/30 rounded-lg p-4">
379
+ <p className="text-sm text-[#888] mb-3">
380
+ A new version is available. Stop all agents before updating.
381
+ </p>
382
+ <div className="flex items-center gap-2">
383
+ <button
384
+ onClick={updateAgent}
385
+ disabled={updatingAgent}
386
+ className="px-4 py-2 bg-[#f97316] text-black rounded font-medium text-sm disabled:opacity-50"
387
+ >
388
+ {updatingAgent ? "Updating..." : "Update Agent"}
389
+ </button>
390
+ </div>
391
+ </div>
392
+ ) : (
393
+ <div className="flex items-center gap-2 text-green-400 text-sm">
394
+ <CheckIcon className="w-4 h-4" />
395
+ Up to date
396
+ </div>
397
+ )}
289
398
  </div>
290
- ) : null}
291
- </div>
399
+
400
+ {!hasAnyUpdate && !updateSuccess && (
401
+ <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4 flex items-center gap-2 text-green-400">
402
+ <CheckIcon className="w-5 h-5" />
403
+ Everything is up to date!
404
+ </div>
405
+ )}
406
+
407
+ <button
408
+ onClick={checkForUpdates}
409
+ disabled={checking}
410
+ className="text-sm text-[#666] hover:text-[#888] disabled:opacity-50"
411
+ >
412
+ {checking ? "Checking..." : "Check for updates"}
413
+ </button>
414
+ </div>
415
+ ) : null}
292
416
  </div>
293
417
  );
294
418
  }
@@ -398,3 +522,109 @@ function ProviderKeyCard({
398
522
  </div>
399
523
  );
400
524
  }
525
+
526
+ function IntegrationKeyCard({
527
+ provider,
528
+ isEditing,
529
+ apiKey,
530
+ saving,
531
+ testing,
532
+ error,
533
+ success,
534
+ onStartEdit,
535
+ onCancelEdit,
536
+ onApiKeyChange,
537
+ onSave,
538
+ onDelete,
539
+ }: ProviderKeyCardProps) {
540
+ return (
541
+ <div className={`bg-[#111] border rounded-lg p-4 ${
542
+ provider.hasKey ? 'border-[#f97316]/20' : 'border-[#1a1a1a]'
543
+ }`}>
544
+ <div className="flex items-center justify-between mb-2">
545
+ <div>
546
+ <h3 className="font-medium">{provider.name}</h3>
547
+ <p className="text-sm text-[#666]">{provider.description || "MCP integration"}</p>
548
+ </div>
549
+ {provider.hasKey ? (
550
+ <span className="text-[#f97316] text-xs flex items-center gap-1 bg-[#f97316]/10 px-2 py-1 rounded">
551
+ <CheckIcon className="w-3 h-3" />
552
+ {provider.keyHint}
553
+ </span>
554
+ ) : (
555
+ <span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded">
556
+ Not configured
557
+ </span>
558
+ )}
559
+ </div>
560
+
561
+ {provider.hasKey ? (
562
+ <div className="flex items-center justify-between mt-3 pt-3 border-t border-[#1a1a1a]">
563
+ <a
564
+ href={provider.docsUrl}
565
+ target="_blank"
566
+ rel="noopener noreferrer"
567
+ className="text-sm text-[#3b82f6] hover:underline"
568
+ >
569
+ View docs
570
+ </a>
571
+ <button
572
+ onClick={onDelete}
573
+ className="text-red-400 hover:text-red-300 text-sm"
574
+ >
575
+ Remove key
576
+ </button>
577
+ </div>
578
+ ) : (
579
+ <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
580
+ {isEditing ? (
581
+ <div className="space-y-3">
582
+ <input
583
+ type="password"
584
+ value={apiKey}
585
+ onChange={e => onApiKeyChange(e.target.value)}
586
+ placeholder="Enter API key..."
587
+ autoFocus
588
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
589
+ />
590
+ {error && <p className="text-red-400 text-sm">{error}</p>}
591
+ {success && <p className="text-green-400 text-sm">{success}</p>}
592
+ <div className="flex gap-2">
593
+ <button
594
+ onClick={onCancelEdit}
595
+ className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
596
+ >
597
+ Cancel
598
+ </button>
599
+ <button
600
+ onClick={onSave}
601
+ disabled={!apiKey || saving}
602
+ className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
603
+ >
604
+ {testing ? "Validating..." : saving ? "Saving..." : "Save"}
605
+ </button>
606
+ </div>
607
+ </div>
608
+ ) : (
609
+ <div className="flex items-center justify-between">
610
+ <a
611
+ href={provider.docsUrl}
612
+ target="_blank"
613
+ rel="noopener noreferrer"
614
+ className="text-sm text-[#3b82f6] hover:underline"
615
+ >
616
+ Get API key
617
+ </a>
618
+ <button
619
+ onClick={onStartEdit}
620
+ className="text-sm text-[#f97316] hover:text-[#fb923c]"
621
+ >
622
+ + Add key
623
+ </button>
624
+ </div>
625
+ )}
626
+ </div>
627
+ )}
628
+ </div>
629
+ );
630
+ }
@@ -0,0 +1,129 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { TasksIcon } from "../common/Icons";
3
+ import type { Task } from "../../types";
4
+
5
+ interface TasksPageProps {
6
+ onSelectAgent?: (agentId: string) => void;
7
+ }
8
+
9
+ export function TasksPage({ onSelectAgent }: TasksPageProps) {
10
+ const [tasks, setTasks] = useState<Task[]>([]);
11
+ const [loading, setLoading] = useState(true);
12
+ const [filter, setFilter] = useState<string>("all");
13
+
14
+ useEffect(() => {
15
+ fetchTasks();
16
+ // Refresh every 10 seconds
17
+ const interval = setInterval(fetchTasks, 10000);
18
+ return () => clearInterval(interval);
19
+ }, [filter]);
20
+
21
+ const fetchTasks = async () => {
22
+ try {
23
+ const res = await fetch(`/api/tasks?status=${filter}`);
24
+ const data = await res.json();
25
+ setTasks(data.tasks || []);
26
+ } catch (e) {
27
+ console.error("Failed to fetch tasks:", e);
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+
33
+ const statusColors: Record<string, string> = {
34
+ pending: "bg-yellow-500/20 text-yellow-400",
35
+ running: "bg-blue-500/20 text-blue-400",
36
+ completed: "bg-green-500/20 text-green-400",
37
+ failed: "bg-red-500/20 text-red-400",
38
+ cancelled: "bg-gray-500/20 text-gray-400",
39
+ };
40
+
41
+ const filterOptions = [
42
+ { value: "all", label: "All" },
43
+ { value: "pending", label: "Pending" },
44
+ { value: "running", label: "Running" },
45
+ { value: "completed", label: "Completed" },
46
+ { value: "failed", label: "Failed" },
47
+ ];
48
+
49
+ return (
50
+ <div className="flex-1 p-6 overflow-auto">
51
+ <div className="max-w-4xl mx-auto">
52
+ <div className="flex items-center justify-between mb-6">
53
+ <div>
54
+ <h1 className="text-2xl font-semibold mb-1">Tasks</h1>
55
+ <p className="text-[#666]">
56
+ View tasks from all running agents
57
+ </p>
58
+ </div>
59
+ <div className="flex gap-2">
60
+ {filterOptions.map(opt => (
61
+ <button
62
+ key={opt.value}
63
+ onClick={() => setFilter(opt.value)}
64
+ className={`px-3 py-1.5 rounded text-sm transition ${
65
+ filter === opt.value
66
+ ? "bg-[#f97316] text-black"
67
+ : "bg-[#1a1a1a] hover:bg-[#222]"
68
+ }`}
69
+ >
70
+ {opt.label}
71
+ </button>
72
+ ))}
73
+ </div>
74
+ </div>
75
+
76
+ {loading ? (
77
+ <div className="text-center py-12 text-[#666]">Loading tasks...</div>
78
+ ) : tasks.length === 0 ? (
79
+ <div className="text-center py-12">
80
+ <TasksIcon className="w-12 h-12 mx-auto mb-4 text-[#333]" />
81
+ <p className="text-[#666]">No tasks found</p>
82
+ <p className="text-sm text-[#444] mt-1">
83
+ Tasks will appear here when agents create them
84
+ </p>
85
+ </div>
86
+ ) : (
87
+ <div className="space-y-3">
88
+ {tasks.map(task => (
89
+ <div
90
+ key={`${task.agentId}-${task.id}`}
91
+ className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 hover:border-[#333] transition"
92
+ >
93
+ <div className="flex items-start justify-between mb-2">
94
+ <div className="flex-1">
95
+ <h3 className="font-medium">{task.title}</h3>
96
+ <p className="text-sm text-[#666]">
97
+ {task.agentName}
98
+ {task.execute_at && (
99
+ <span className="ml-2">
100
+ · Scheduled: {new Date(task.execute_at).toLocaleString()}
101
+ </span>
102
+ )}
103
+ </p>
104
+ </div>
105
+ <span className={`px-2 py-1 rounded text-xs font-medium ${statusColors[task.status] || statusColors.pending}`}>
106
+ {task.status}
107
+ </span>
108
+ </div>
109
+
110
+ {task.description && (
111
+ <p className="text-sm text-[#888] mb-2 line-clamp-2">
112
+ {task.description}
113
+ </p>
114
+ )}
115
+
116
+ <div className="flex items-center gap-4 text-xs text-[#555]">
117
+ <span>Type: {task.type}</span>
118
+ <span>Priority: {task.priority}</span>
119
+ {task.recurrence && <span>Recurrence: {task.recurrence}</span>}
120
+ <span>Created: {new Date(task.created_at).toLocaleString()}</span>
121
+ </div>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ )}
126
+ </div>
127
+ </div>
128
+ );
129
+ }
@@ -0,0 +1 @@
1
+ export { TasksPage } from "./TasksPage";