apteva 0.4.20 → 0.4.29
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.41nbye4r.js +3 -0
- package/dist/{ApiDocsPage.kf6bbwkk.js → ApiDocsPage.4smnt8m3.js} +2 -2
- package/dist/{App.jfx3der4.js → App.0sbax9et.js} +3 -3
- package/dist/App.0ws427h8.js +4 -0
- package/dist/App.4ehxpt48.js +4 -0
- package/dist/App.6q6bar8b.js +4 -0
- package/dist/App.ca1rz1ph.js +4 -0
- package/dist/{App.7v1w3ys9.js → App.ensa6z0r.js} +3 -3
- package/dist/{App.n4jb3c22.js → App.f8g7tych.js} +3 -3
- package/dist/App.kh7d2xj3.js +267 -0
- package/dist/App.mvtqv6qc.js +20 -0
- package/dist/{App.c90t3dxg.js → App.ncgc9cxy.js} +3 -3
- package/dist/{App.039re6cf.js → App.p0fb1pds.js} +3 -3
- package/dist/App.pmaq48sj.js +4 -0
- package/dist/{App.2yy66bnp.js → App.yv87t9m5.js} +3 -3
- package/dist/App.zjmfm8p6.js +4 -0
- package/dist/ConnectionsPage.anb3rv9a.js +3 -0
- package/dist/McpPage.y396h6fy.js +3 -0
- package/dist/SettingsPage.5k6vp396.js +3 -0
- package/dist/SkillsPage.yj3xdsay.js +3 -0
- package/dist/TasksPage.sjv0khtv.js +3 -0
- package/dist/TelemetryPage.2qm4w16r.js +3 -0
- package/dist/TestsPage.zzs4qfj8.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/channels/telegram.ts +5 -0
- package/src/crypto.ts +13 -4
- package/src/db.ts +25 -2
- package/src/integrations/agentdojo.ts +1 -1
- package/src/providers.ts +46 -0
- package/src/routes/api/agent-utils.ts +64 -9
- package/src/routes/api/agents.ts +41 -13
- package/src/routes/api/integrations.ts +16 -6
- package/src/routes/api/mcp.ts +7 -0
- package/src/routes/api/triggers.ts +45 -5
- package/src/web/App.tsx +1 -0
- package/src/web/components/activity/ActivityPage.tsx +349 -214
- package/src/web/components/agents/AgentCard.tsx +37 -8
- package/src/web/components/agents/AgentPanel.tsx +268 -23
- package/src/web/components/connections/IntegrationsTab.tsx +57 -31
- package/src/web/components/connections/TriggersTab.tsx +336 -159
- package/src/web/components/dashboard/Dashboard.tsx +39 -7
- package/src/web/components/layout/Header.tsx +0 -34
- package/src/web/components/layout/Sidebar.tsx +43 -3
- package/src/web/components/mcp/McpPage.tsx +16 -5
- package/src/web/components/settings/SettingsPage.tsx +279 -30
- package/src/web/components/tasks/TasksPage.tsx +32 -6
- package/src/web/context/ProjectContext.tsx +5 -0
- package/src/web/context/TelemetryContext.tsx +14 -0
- package/src/web/types.ts +20 -2
- package/dist/ActivityPage.h769ek3a.js +0 -3
- package/dist/App.2jmkqm8c.js +0 -4
- package/dist/App.3515wsb4.js +0 -4
- package/dist/App.edwahsvz.js +0 -4
- package/dist/App.q3bpx15d.js +0 -20
- package/dist/App.r0a2nmqs.js +0 -267
- package/dist/App.s2yrcz15.js +0 -4
- package/dist/App.s5j82a5j.js +0 -4
- package/dist/App.tg1b94tx.js +0 -4
- package/dist/ConnectionsPage.a67fjgbf.js +0 -3
- package/dist/McpPage.d4p3xvtk.js +0 -3
- package/dist/SettingsPage.46sqpe39.js +0 -3
- package/dist/SkillsPage.j9hkqm99.js +0 -3
- package/dist/TasksPage.6pvkb7s7.js +0 -3
- package/dist/TelemetryPage.5zq9msb5.js +0 -3
- package/dist/TestsPage.24432yqt.js +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon } from "../common/Icons";
|
|
3
|
-
import { useAgentActivity, useProjects } from "../../context";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon, ActivityIcon } from "../common/Icons";
|
|
3
|
+
import { useAgentActivity, useProjects, useAuth } from "../../context";
|
|
4
4
|
import type { Agent, AgentFeatures } from "../../types";
|
|
5
5
|
|
|
6
6
|
interface AgentCardProps {
|
|
@@ -26,9 +26,18 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
26
26
|
const enabledFeatures = FEATURE_ICONS.filter(f => agent.features?.[f.key]);
|
|
27
27
|
const mcpServers = agent.mcpServerDetails || [];
|
|
28
28
|
const skills = agent.skillDetails || [];
|
|
29
|
-
const { isActive,
|
|
29
|
+
const { isActive, label: activityLabel } = useAgentActivity(agent.id);
|
|
30
30
|
const { projects } = useProjects();
|
|
31
|
+
const { authFetch } = useAuth();
|
|
31
32
|
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
33
|
+
const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
authFetch(`/api/subscriptions?agent_id=${agent.id}`)
|
|
37
|
+
.then(res => res.ok ? res.json() : { subscriptions: [] })
|
|
38
|
+
.then(data => setSubscriptions(data.subscriptions || []))
|
|
39
|
+
.catch(() => {});
|
|
40
|
+
}, [agent.id, authFetch]);
|
|
32
41
|
|
|
33
42
|
return (
|
|
34
43
|
<div
|
|
@@ -53,7 +62,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
53
62
|
</p>
|
|
54
63
|
)}
|
|
55
64
|
</div>
|
|
56
|
-
<StatusBadge status={agent.status} isActive={isActive && agent.status === "running"}
|
|
65
|
+
<StatusBadge status={agent.status} isActive={isActive && agent.status === "running"} activityLabel={activityLabel} />
|
|
57
66
|
</div>
|
|
58
67
|
|
|
59
68
|
{enabledFeatures.length > 0 && (
|
|
@@ -115,6 +124,26 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
115
124
|
</div>
|
|
116
125
|
)}
|
|
117
126
|
|
|
127
|
+
{/* Subscriptions (triggers listening to) */}
|
|
128
|
+
{subscriptions.length > 0 && (
|
|
129
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
130
|
+
{subscriptions.map((sub) => (
|
|
131
|
+
<span
|
|
132
|
+
key={sub.id}
|
|
133
|
+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs ${
|
|
134
|
+
sub.enabled
|
|
135
|
+
? "bg-cyan-500/10 text-cyan-400"
|
|
136
|
+
: "bg-[#222] text-[#666]"
|
|
137
|
+
}`}
|
|
138
|
+
title={`Trigger: ${sub.trigger_slug.replace(/_/g, " ")}`}
|
|
139
|
+
>
|
|
140
|
+
<ActivityIcon className="w-3 h-3" />
|
|
141
|
+
{sub.trigger_slug.replace(/_/g, " ")}
|
|
142
|
+
</span>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
|
|
118
147
|
<p className="text-sm text-[#666] line-clamp-2 mb-4 flex-1">
|
|
119
148
|
{agent.systemPrompt}
|
|
120
149
|
</p>
|
|
@@ -136,11 +165,11 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
136
165
|
);
|
|
137
166
|
}
|
|
138
167
|
|
|
139
|
-
function StatusBadge({ status, isActive,
|
|
140
|
-
if (status === "running" && isActive &&
|
|
168
|
+
function StatusBadge({ status, isActive, activityLabel }: { status: Agent["status"]; isActive?: boolean; activityLabel?: string }) {
|
|
169
|
+
if (status === "running" && isActive && activityLabel) {
|
|
141
170
|
return (
|
|
142
171
|
<span className="px-2 py-1 rounded text-xs font-medium bg-green-500/20 text-green-400 animate-pulse">
|
|
143
|
-
{
|
|
172
|
+
{activityLabel}
|
|
144
173
|
</span>
|
|
145
174
|
);
|
|
146
175
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Chat, convertApiMessages } from "@apteva/apteva-kit";
|
|
3
3
|
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
|
|
4
|
-
import { formatCron, formatRelativeTime } from "../tasks/TasksPage";
|
|
4
|
+
import { formatCron, formatRelativeTime, TrajectoryView } from "../tasks/TasksPage";
|
|
5
5
|
import { Select } from "../common/Select";
|
|
6
6
|
import { useConfirm } from "../common/Modal";
|
|
7
7
|
import { useTelemetry } from "../../context";
|
|
8
8
|
import { useAuth } from "../../context";
|
|
9
|
-
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig } from "../../types";
|
|
10
|
-
import { getMultiAgentConfig } from "../../types";
|
|
9
|
+
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig, OperatorConfig, Task } from "../../types";
|
|
10
|
+
import { getMultiAgentConfig, getOperatorConfig } from "../../types";
|
|
11
11
|
|
|
12
12
|
type Tab = "chat" | "threads" | "tasks" | "memory" | "files" | "settings";
|
|
13
13
|
|
|
@@ -314,10 +314,13 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
function TasksTab({ agent }: { agent: Agent }) {
|
|
317
|
-
const
|
|
317
|
+
const { authFetch } = useAuth();
|
|
318
|
+
const [tasks, setTasks] = useState<Task[]>([]);
|
|
318
319
|
const [loading, setLoading] = useState(true);
|
|
319
320
|
const [error, setError] = useState<string | null>(null);
|
|
320
321
|
const [filter, setFilter] = useState<string>("all");
|
|
322
|
+
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
323
|
+
const [loadingTask, setLoadingTask] = useState(false);
|
|
321
324
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
322
325
|
|
|
323
326
|
// Reset state when agent changes
|
|
@@ -325,6 +328,7 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
325
328
|
setTasks([]);
|
|
326
329
|
setError(null);
|
|
327
330
|
setLoading(true);
|
|
331
|
+
setSelectedTask(null);
|
|
328
332
|
}, [agent.id]);
|
|
329
333
|
|
|
330
334
|
const fetchTasks = async () => {
|
|
@@ -346,6 +350,24 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
346
350
|
}
|
|
347
351
|
};
|
|
348
352
|
|
|
353
|
+
const selectTask = async (task: Task) => {
|
|
354
|
+
setSelectedTask(task);
|
|
355
|
+
setLoadingTask(true);
|
|
356
|
+
try {
|
|
357
|
+
const res = await authFetch(`/api/tasks/${task.agentId || agent.id}/${task.id}`);
|
|
358
|
+
if (res.ok) {
|
|
359
|
+
const data = await res.json();
|
|
360
|
+
if (data.task) {
|
|
361
|
+
setSelectedTask({ ...data.task, agentId: task.agentId || agent.id, agentName: task.agentName || agent.name });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {
|
|
365
|
+
console.error("Failed to fetch task details:", e);
|
|
366
|
+
} finally {
|
|
367
|
+
setLoadingTask(false);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
349
371
|
// Refetch when agent changes, filter changes, or task telemetry arrives
|
|
350
372
|
useEffect(() => {
|
|
351
373
|
setLoading(true);
|
|
@@ -403,6 +425,133 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
403
425
|
{ value: "failed", label: "Failed" },
|
|
404
426
|
];
|
|
405
427
|
|
|
428
|
+
// Show task detail view when a task is selected
|
|
429
|
+
if (selectedTask) {
|
|
430
|
+
return (
|
|
431
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
432
|
+
{/* Back button */}
|
|
433
|
+
<div className="px-4 pt-3 pb-2 border-b border-[#1a1a1a] shrink-0">
|
|
434
|
+
<button
|
|
435
|
+
onClick={() => setSelectedTask(null)}
|
|
436
|
+
className="text-sm text-[#666] hover:text-[#e0e0e0] transition flex items-center gap-1"
|
|
437
|
+
>
|
|
438
|
+
<span>←</span> Back to tasks
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* Task detail content */}
|
|
443
|
+
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
444
|
+
{/* Title & Status */}
|
|
445
|
+
<div>
|
|
446
|
+
<div className="flex items-start justify-between gap-2 mb-1">
|
|
447
|
+
<h3 className="text-lg font-medium">{selectedTask.title}</h3>
|
|
448
|
+
<span className={`px-2 py-1 rounded text-xs font-medium flex-shrink-0 ${statusColors[selectedTask.status]}`}>
|
|
449
|
+
{selectedTask.status}
|
|
450
|
+
</span>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
{/* Description */}
|
|
455
|
+
{selectedTask.description && (
|
|
456
|
+
<div>
|
|
457
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-1">Description</h4>
|
|
458
|
+
<p className="text-sm text-[#888] whitespace-pre-wrap">{selectedTask.description}</p>
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
|
|
462
|
+
{/* Metadata */}
|
|
463
|
+
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
464
|
+
<div>
|
|
465
|
+
<span className="text-[#666]">Type</span>
|
|
466
|
+
<p className="capitalize">{selectedTask.type}</p>
|
|
467
|
+
</div>
|
|
468
|
+
<div>
|
|
469
|
+
<span className="text-[#666]">Priority</span>
|
|
470
|
+
<p>{selectedTask.priority}</p>
|
|
471
|
+
</div>
|
|
472
|
+
{selectedTask.recurrence && (
|
|
473
|
+
<div>
|
|
474
|
+
<span className="text-[#666]">Recurrence</span>
|
|
475
|
+
<p>{formatCron(selectedTask.recurrence)}</p>
|
|
476
|
+
<p className="text-xs text-[#444] mt-0.5 font-mono">{selectedTask.recurrence}</p>
|
|
477
|
+
</div>
|
|
478
|
+
)}
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
{/* Timestamps */}
|
|
482
|
+
<div className="space-y-2 text-sm">
|
|
483
|
+
<div className="flex justify-between">
|
|
484
|
+
<span className="text-[#666]">Created</span>
|
|
485
|
+
<span>{new Date(selectedTask.created_at).toLocaleString()}</span>
|
|
486
|
+
</div>
|
|
487
|
+
{selectedTask.execute_at && (
|
|
488
|
+
<div className="flex justify-between">
|
|
489
|
+
<span className="text-[#666]">Scheduled</span>
|
|
490
|
+
<span className="text-[#f97316]">{formatRelativeTime(selectedTask.execute_at)}</span>
|
|
491
|
+
</div>
|
|
492
|
+
)}
|
|
493
|
+
{selectedTask.executed_at && (
|
|
494
|
+
<div className="flex justify-between">
|
|
495
|
+
<span className="text-[#666]">Started</span>
|
|
496
|
+
<span>{new Date(selectedTask.executed_at).toLocaleString()}</span>
|
|
497
|
+
</div>
|
|
498
|
+
)}
|
|
499
|
+
{selectedTask.completed_at && (
|
|
500
|
+
<div className="flex justify-between">
|
|
501
|
+
<span className="text-[#666]">Completed</span>
|
|
502
|
+
<span>{new Date(selectedTask.completed_at).toLocaleString()}</span>
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
{selectedTask.next_run && (
|
|
506
|
+
<div className="flex justify-between">
|
|
507
|
+
<span className="text-[#666]">Next Run</span>
|
|
508
|
+
<span className="text-[#f97316]">{formatRelativeTime(selectedTask.next_run)}</span>
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
{/* Error */}
|
|
514
|
+
{selectedTask.status === "failed" && selectedTask.error && (
|
|
515
|
+
<div className="min-w-0">
|
|
516
|
+
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
517
|
+
<div className="bg-red-500/10 border border-red-500/20 rounded p-3 overflow-x-auto">
|
|
518
|
+
<pre className="text-sm text-red-400 whitespace-pre-wrap break-words">{selectedTask.error}</pre>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
|
|
523
|
+
{/* Result */}
|
|
524
|
+
{selectedTask.status === "completed" && selectedTask.result && (
|
|
525
|
+
<div className="min-w-0">
|
|
526
|
+
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
527
|
+
<div className="bg-green-500/10 border border-green-500/20 rounded p-3 overflow-x-auto">
|
|
528
|
+
<pre className="text-sm text-green-400 whitespace-pre-wrap break-words">
|
|
529
|
+
{typeof selectedTask.result === "string" ? selectedTask.result : JSON.stringify(selectedTask.result, null, 2)}
|
|
530
|
+
</pre>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
534
|
+
|
|
535
|
+
{/* Trajectory */}
|
|
536
|
+
{loadingTask && !selectedTask.trajectory && (
|
|
537
|
+
<div>
|
|
538
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-2">Trajectory</h4>
|
|
539
|
+
<div className="text-sm text-[#555]">Loading trajectory...</div>
|
|
540
|
+
</div>
|
|
541
|
+
)}
|
|
542
|
+
{selectedTask.trajectory && selectedTask.trajectory.length > 0 && (
|
|
543
|
+
<div>
|
|
544
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-2">
|
|
545
|
+
Trajectory ({selectedTask.trajectory.length} steps)
|
|
546
|
+
</h4>
|
|
547
|
+
<TrajectoryView trajectory={selectedTask.trajectory} />
|
|
548
|
+
</div>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
406
555
|
return (
|
|
407
556
|
<div className="flex-1 overflow-auto p-4">
|
|
408
557
|
{/* Filter tabs */}
|
|
@@ -431,7 +580,11 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
431
580
|
) : (
|
|
432
581
|
<div className="space-y-3">
|
|
433
582
|
{tasks.map(task => (
|
|
434
|
-
<div
|
|
583
|
+
<div
|
|
584
|
+
key={task.id}
|
|
585
|
+
onClick={() => selectTask(task)}
|
|
586
|
+
className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 cursor-pointer hover:border-[#333] transition"
|
|
587
|
+
>
|
|
435
588
|
<div className="flex items-start justify-between mb-2">
|
|
436
589
|
<div className="flex-1 min-w-0">
|
|
437
590
|
<h3 className="font-medium">{task.title || task.name}</h3>
|
|
@@ -970,7 +1123,17 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
970
1123
|
const [availableMcpServers, setAvailableMcpServers] = useState<McpServer[]>([]);
|
|
971
1124
|
const [availableSkills, setAvailableSkills] = useState<AvailableSkill[]>([]);
|
|
972
1125
|
const [apiKey, setApiKey] = useState<string | null>(null);
|
|
1126
|
+
const [apiKeyFull, setApiKeyFull] = useState<string | null>(null);
|
|
973
1127
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
1128
|
+
const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
|
|
1129
|
+
|
|
1130
|
+
// Fetch subscriptions for this agent
|
|
1131
|
+
useEffect(() => {
|
|
1132
|
+
authFetch(`/api/subscriptions?agent_id=${agent.id}`)
|
|
1133
|
+
.then(res => res.ok ? res.json() : { subscriptions: [] })
|
|
1134
|
+
.then(data => setSubscriptions(data.subscriptions || []))
|
|
1135
|
+
.catch(() => {});
|
|
1136
|
+
}, [agent.id, authFetch]);
|
|
974
1137
|
|
|
975
1138
|
// Fetch available MCP servers
|
|
976
1139
|
useEffect(() => {
|
|
@@ -986,22 +1149,22 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
986
1149
|
fetchMcpServers();
|
|
987
1150
|
}, [authFetch]);
|
|
988
1151
|
|
|
989
|
-
// Fetch API key
|
|
1152
|
+
// Fetch API key
|
|
990
1153
|
useEffect(() => {
|
|
991
|
-
if (!isDev) return;
|
|
992
1154
|
const fetchApiKey = async () => {
|
|
993
1155
|
try {
|
|
994
1156
|
const res = await authFetch(`/api/agents/${agent.id}/api-key`);
|
|
995
1157
|
if (res.ok) {
|
|
996
1158
|
const data = await res.json();
|
|
997
1159
|
setApiKey(data.apiKey);
|
|
1160
|
+
setApiKeyFull(data.fullKey || null);
|
|
998
1161
|
}
|
|
999
1162
|
} catch (e) {
|
|
1000
1163
|
// Ignore - not critical
|
|
1001
1164
|
}
|
|
1002
1165
|
};
|
|
1003
1166
|
fetchApiKey();
|
|
1004
|
-
}, [agent.id,
|
|
1167
|
+
}, [agent.id, authFetch]);
|
|
1005
1168
|
|
|
1006
1169
|
// Fetch available skills
|
|
1007
1170
|
useEffect(() => {
|
|
@@ -1055,19 +1218,13 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1055
1218
|
const toggleFeature = (key: keyof AgentFeatures) => {
|
|
1056
1219
|
if (key === "agents") {
|
|
1057
1220
|
// Special handling for agents feature - convert to MultiAgentConfig
|
|
1058
|
-
const current = prev => {
|
|
1059
|
-
const agentConfig = getMultiAgentConfig(prev.features, agent.projectId);
|
|
1060
|
-
return agentConfig.enabled;
|
|
1061
|
-
};
|
|
1062
1221
|
setForm(prev => {
|
|
1063
1222
|
const isEnabled = typeof prev.features.agents === "boolean"
|
|
1064
1223
|
? prev.features.agents
|
|
1065
1224
|
: (prev.features.agents as MultiAgentConfig)?.enabled ?? false;
|
|
1066
1225
|
if (isEnabled) {
|
|
1067
|
-
// Turning off - set to false
|
|
1068
1226
|
return { ...prev, features: { ...prev.features, agents: false } };
|
|
1069
1227
|
} else {
|
|
1070
|
-
// Turning on - set to config with defaults
|
|
1071
1228
|
return {
|
|
1072
1229
|
...prev,
|
|
1073
1230
|
features: {
|
|
@@ -1077,6 +1234,22 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1077
1234
|
};
|
|
1078
1235
|
}
|
|
1079
1236
|
});
|
|
1237
|
+
} else if (key === "operator") {
|
|
1238
|
+
// Special handling for operator feature - convert to OperatorConfig
|
|
1239
|
+
setForm(prev => {
|
|
1240
|
+
const opConfig = getOperatorConfig(prev.features);
|
|
1241
|
+
if (opConfig.enabled) {
|
|
1242
|
+
return { ...prev, features: { ...prev.features, operator: false } };
|
|
1243
|
+
} else {
|
|
1244
|
+
return {
|
|
1245
|
+
...prev,
|
|
1246
|
+
features: {
|
|
1247
|
+
...prev.features,
|
|
1248
|
+
operator: { enabled: true },
|
|
1249
|
+
},
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1080
1253
|
} else {
|
|
1081
1254
|
setForm(prev => ({
|
|
1082
1255
|
...prev,
|
|
@@ -1112,6 +1285,33 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1112
1285
|
return config.mode || "worker";
|
|
1113
1286
|
};
|
|
1114
1287
|
|
|
1288
|
+
// Helper to check if operator feature is enabled
|
|
1289
|
+
const isOperatorEnabled = () => {
|
|
1290
|
+
return getOperatorConfig(form.features).enabled;
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
// Get current operator config
|
|
1294
|
+
const getOperatorCfg = (): OperatorConfig => {
|
|
1295
|
+
return getOperatorConfig(form.features);
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// Get browser providers from the providers list
|
|
1299
|
+
const browserProviders = providers.filter(p => p.type === "browser" && p.hasKey);
|
|
1300
|
+
|
|
1301
|
+
// Set operator browser provider
|
|
1302
|
+
const setOperatorBrowserProvider = (browserProvider: string) => {
|
|
1303
|
+
setForm(prev => {
|
|
1304
|
+
const current = getOperatorConfig(prev.features);
|
|
1305
|
+
return {
|
|
1306
|
+
...prev,
|
|
1307
|
+
features: {
|
|
1308
|
+
...prev.features,
|
|
1309
|
+
operator: { ...current, enabled: true, browser_provider: browserProvider },
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1312
|
+
});
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1115
1315
|
const toggleMcpServer = (serverId: string) => {
|
|
1116
1316
|
setForm(prev => ({
|
|
1117
1317
|
...prev,
|
|
@@ -1191,8 +1391,10 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1191
1391
|
<FormField label="Features">
|
|
1192
1392
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
1193
1393
|
{FEATURE_CONFIG.map(({ key, label, description, icon: Icon }) => {
|
|
1194
|
-
// For agents
|
|
1195
|
-
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
1394
|
+
// For agents/operator features, check the enabled property of the config
|
|
1395
|
+
const isEnabled = key === "agents" ? isAgentsEnabled()
|
|
1396
|
+
: key === "operator" ? isOperatorEnabled()
|
|
1397
|
+
: !!form.features[key];
|
|
1196
1398
|
return (
|
|
1197
1399
|
<button
|
|
1198
1400
|
key={key}
|
|
@@ -1258,6 +1460,29 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1258
1460
|
</FormField>
|
|
1259
1461
|
)}
|
|
1260
1462
|
|
|
1463
|
+
{/* Operator Browser Provider - shown when operator is enabled */}
|
|
1464
|
+
{isOperatorEnabled() && (
|
|
1465
|
+
<FormField label="Browser Provider">
|
|
1466
|
+
{browserProviders.length > 0 ? (
|
|
1467
|
+
<Select
|
|
1468
|
+
value={getOperatorCfg().browser_provider || ""}
|
|
1469
|
+
options={[
|
|
1470
|
+
{ value: "", label: "Auto (first available)" },
|
|
1471
|
+
...browserProviders.map(p => ({
|
|
1472
|
+
value: p.id,
|
|
1473
|
+
label: p.name,
|
|
1474
|
+
})),
|
|
1475
|
+
]}
|
|
1476
|
+
onChange={(value) => setOperatorBrowserProvider(value)}
|
|
1477
|
+
/>
|
|
1478
|
+
) : (
|
|
1479
|
+
<p className="text-sm text-[#666] p-3 border border-[#222] rounded bg-[#0a0a0a]">
|
|
1480
|
+
No browser providers configured. Go to Settings → Providers to add one.
|
|
1481
|
+
</p>
|
|
1482
|
+
)}
|
|
1483
|
+
</FormField>
|
|
1484
|
+
)}
|
|
1485
|
+
|
|
1261
1486
|
{/* Agent Built-in Tools - Anthropic only */}
|
|
1262
1487
|
{form.provider === "anthropic" && (
|
|
1263
1488
|
<FormField label="Agent Built-in Tools">
|
|
@@ -1443,8 +1668,30 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1443
1668
|
</p>
|
|
1444
1669
|
)}
|
|
1445
1670
|
|
|
1671
|
+
{/* Subscriptions */}
|
|
1672
|
+
<div className="mt-8 pt-6 border-t border-[#222]">
|
|
1673
|
+
<p className="text-sm text-[#666] mb-3">Subscriptions</p>
|
|
1674
|
+
{subscriptions.length === 0 ? (
|
|
1675
|
+
<p className="text-xs text-[#555]">No subscriptions. Set up triggers in Connections to have this agent listen to external events.</p>
|
|
1676
|
+
) : (
|
|
1677
|
+
<div className="space-y-2">
|
|
1678
|
+
{subscriptions.map(sub => (
|
|
1679
|
+
<div key={sub.id} className="flex items-center gap-2 px-3 py-2 bg-[#111] rounded border border-[#1a1a1a]">
|
|
1680
|
+
<span className={`w-2 h-2 rounded-full shrink-0 ${sub.enabled ? "bg-cyan-400" : "bg-[#444]"}`} />
|
|
1681
|
+
<span className={`text-sm flex-1 ${sub.enabled ? "text-cyan-400" : "text-[#666]"}`}>
|
|
1682
|
+
{sub.trigger_slug.replace(/_/g, " ")}
|
|
1683
|
+
</span>
|
|
1684
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded ${sub.enabled ? "bg-cyan-500/10 text-cyan-400" : "bg-[#222] text-[#555]"}`}>
|
|
1685
|
+
{sub.enabled ? "active" : "disabled"}
|
|
1686
|
+
</span>
|
|
1687
|
+
</div>
|
|
1688
|
+
))}
|
|
1689
|
+
</div>
|
|
1690
|
+
)}
|
|
1691
|
+
</div>
|
|
1692
|
+
|
|
1446
1693
|
{/* Developer Info (dev mode only) */}
|
|
1447
|
-
{
|
|
1694
|
+
{apiKey && (
|
|
1448
1695
|
<div className="mt-8 pt-6 border-t border-[#222]">
|
|
1449
1696
|
<p className="text-sm text-[#666] mb-3">Developer Info</p>
|
|
1450
1697
|
<div className="space-y-2">
|
|
@@ -1466,17 +1713,15 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1466
1713
|
{showApiKey ? "Hide" : "Show"}
|
|
1467
1714
|
</button>
|
|
1468
1715
|
</div>
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
</code>
|
|
1473
|
-
)}
|
|
1716
|
+
<code className="text-xs bg-[#1a1a1a] px-2 py-1 rounded text-[#888] break-all">
|
|
1717
|
+
{showApiKey ? (apiKeyFull || apiKey) : apiKey}
|
|
1718
|
+
</code>
|
|
1474
1719
|
</div>
|
|
1475
1720
|
{agent.status === "running" && agent.port && (
|
|
1476
1721
|
<div className="flex flex-col gap-1 mt-2">
|
|
1477
1722
|
<span className="text-xs text-[#666]">Test with curl</span>
|
|
1478
1723
|
<code className="text-xs bg-[#1a1a1a] px-2 py-1.5 rounded text-[#666] break-all">
|
|
1479
|
-
curl -H "X-API-Key: {showApiKey ? apiKey : "***"}" http://localhost:{agent.port}/config
|
|
1724
|
+
curl -H "X-API-Key: {showApiKey ? (apiKeyFull || apiKey) : "***"}" http://localhost:{agent.port}/config
|
|
1480
1725
|
</code>
|
|
1481
1726
|
</div>
|
|
1482
1727
|
)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback } from "react";
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { useAuth, useProjects } from "../../context";
|
|
3
3
|
import { IntegrationsPanel } from "../mcp/IntegrationsPanel";
|
|
4
4
|
|
|
@@ -12,18 +12,35 @@ interface TriggerType {
|
|
|
12
12
|
logo: string | null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
interface ProviderInfo {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
connected: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function IntegrationsTab() {
|
|
16
22
|
const { authFetch } = useAuth();
|
|
17
23
|
const { currentProjectId } = useProjects();
|
|
18
24
|
|
|
19
25
|
const projectId = currentProjectId && currentProjectId !== "unassigned" ? currentProjectId : null;
|
|
26
|
+
const projectParam = projectId ? `?project_id=${projectId}` : "";
|
|
20
27
|
|
|
21
|
-
// Provider selection
|
|
22
|
-
const [
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// Provider selection — only show configured providers
|
|
29
|
+
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
30
|
+
const [selectedProvider, setSelectedProvider] = useState("");
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
authFetch(`/api/triggers/providers${projectParam}`)
|
|
34
|
+
.then(r => r.json())
|
|
35
|
+
.then(data => {
|
|
36
|
+
const connected = (data.providers || []).filter((p: ProviderInfo) => p.connected);
|
|
37
|
+
setProviders(connected);
|
|
38
|
+
if (connected.length > 0 && !connected.find((p: ProviderInfo) => p.id === selectedProvider)) {
|
|
39
|
+
setSelectedProvider(connected[0].id);
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {});
|
|
43
|
+
}, [authFetch]);
|
|
27
44
|
|
|
28
45
|
// Trigger type browsing
|
|
29
46
|
const [browsingToolkit, setBrowsingToolkit] = useState<string | null>(null);
|
|
@@ -53,32 +70,41 @@ export function IntegrationsTab() {
|
|
|
53
70
|
Connect external apps via OAuth or API Key. Connected apps can be used for triggers and MCP integrations.
|
|
54
71
|
</p>
|
|
55
72
|
|
|
56
|
-
{/* Provider Selector */}
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
{/* Provider Selector — only show if multiple configured */}
|
|
74
|
+
{providers.length > 1 && (
|
|
75
|
+
<div className="flex items-center gap-2 mb-4">
|
|
76
|
+
<span className="text-xs text-[#666]">Provider:</span>
|
|
77
|
+
<div className="flex gap-1 bg-[#111] border border-[#1a1a1a] rounded-lg p-0.5">
|
|
78
|
+
{providers.map(p => (
|
|
79
|
+
<button
|
|
80
|
+
key={p.id}
|
|
81
|
+
onClick={() => setSelectedProvider(p.id)}
|
|
82
|
+
className={`px-3 py-1 rounded text-xs font-medium transition ${
|
|
83
|
+
selectedProvider === p.id
|
|
84
|
+
? "bg-[#1a1a1a] text-white"
|
|
85
|
+
: "text-[#666] hover:text-[#888]"
|
|
86
|
+
}`}
|
|
87
|
+
>
|
|
88
|
+
{p.name}
|
|
89
|
+
</button>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
73
92
|
</div>
|
|
74
|
-
|
|
93
|
+
)}
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
{providers.length === 0 ? (
|
|
96
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-8 text-center">
|
|
97
|
+
<p className="text-[#666]">No integration providers configured.</p>
|
|
98
|
+
<p className="text-sm text-[#555] mt-1">Add API keys for Composio or AgentDojo in Settings.</p>
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
<IntegrationsPanel
|
|
102
|
+
providerId={selectedProvider}
|
|
103
|
+
projectId={projectId}
|
|
104
|
+
hideMcpConfig
|
|
105
|
+
onBrowseTriggers={handleBrowseTriggers}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
82
108
|
|
|
83
109
|
{/* Trigger Types Panel */}
|
|
84
110
|
{browsingToolkit && (
|