apteva 0.4.17 → 0.4.19
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.9a1qg4bp.js +3 -0
- package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
- package/dist/App.1nmg2h01.js +4 -0
- package/dist/App.5qw2dtxs.js +4 -0
- package/dist/App.6nc5acvk.js +4 -0
- package/dist/App.7vzbaz56.js +4 -0
- package/dist/App.8rfz30p1.js +4 -0
- package/dist/App.amwp54wf.js +4 -0
- package/dist/App.e4202qb4.js +267 -0
- package/dist/App.errxz2q4.js +4 -0
- package/dist/App.f8qsyhpr.js +4 -0
- package/dist/App.g8vq68n0.js +20 -0
- package/dist/App.kfyrnznw.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/App.p93mmyqw.js +4 -0
- package/dist/App.qmg33p02.js +4 -0
- package/dist/App.sdsc0258.js +4 -0
- package/dist/ConnectionsPage.7zqba1r0.js +3 -0
- package/dist/McpPage.kf2g327t.js +3 -0
- package/dist/SettingsPage.472c15ep.js +3 -0
- package/dist/SkillsPage.xdxnh68a.js +3 -0
- package/dist/TasksPage.7g0b8xwc.js +3 -0
- package/dist/TelemetryPage.pr7rbz4r.js +3 -0
- package/dist/TestsPage.zhc6rqjm.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -4
- package/src/auth/middleware.ts +2 -0
- package/src/channels/index.ts +40 -0
- package/src/channels/telegram.ts +306 -0
- package/src/db.ts +342 -11
- package/src/integrations/agentdojo.ts +1 -1
- package/src/mcp-handler.ts +31 -24
- package/src/mcp-platform.ts +41 -1
- package/src/providers.ts +22 -9
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/channels.ts +182 -0
- package/src/routes/api/integrations.ts +13 -5
- package/src/routes/api/mcp.ts +27 -9
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/telemetry.ts +30 -0
- package/src/routes/api/triggers.ts +478 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +7 -1
- package/src/routes/static.ts +12 -3
- package/src/server.ts +43 -6
- package/src/triggers/agentdojo.ts +253 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/tui/AgentList.tsx +145 -0
- package/src/tui/App.tsx +102 -0
- package/src/tui/Login.tsx +104 -0
- package/src/tui/api.ts +72 -0
- package/src/tui/index.tsx +7 -0
- package/src/web/App.tsx +18 -11
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +94 -137
- package/src/web/components/common/Icons.tsx +16 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/connections/ConnectionsPage.tsx +54 -0
- package/src/web/components/connections/IntegrationsTab.tsx +144 -0
- package/src/web/components/connections/OverviewTab.tsx +137 -0
- package/src/web/components/connections/TriggersTab.tsx +1169 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +196 -4
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
- package/src/web/components/settings/SettingsPage.tsx +364 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.fq4xbpcz.js +0 -228
|
@@ -1,18 +1,128 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { useTelemetryContext, useAuth, useProjects } from "../../context";
|
|
3
|
-
import { MenuIcon, ChevronDownIcon } from "../common/Icons";
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
+
import { useTelemetryContext, useAuth, useAuthHeaders, useProjects, useNotificationChange } from "../../context";
|
|
3
|
+
import { MenuIcon, ChevronDownIcon, BellIcon } from "../common/Icons";
|
|
4
4
|
import { MetaAgentButton } from "../meta-agent/MetaAgent";
|
|
5
5
|
|
|
6
|
+
interface Notification {
|
|
7
|
+
id: string;
|
|
8
|
+
agent_id: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
category: string;
|
|
11
|
+
type: string;
|
|
12
|
+
level: string;
|
|
13
|
+
error: string | null;
|
|
14
|
+
data: Record<string, unknown> | null;
|
|
15
|
+
seen?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
interface HeaderProps {
|
|
7
19
|
onMenuClick?: () => void;
|
|
20
|
+
agents?: Array<{ id: string; name: string; projectId: string | null }>;
|
|
8
21
|
}
|
|
9
22
|
|
|
10
|
-
export function Header({ onMenuClick }: HeaderProps) {
|
|
23
|
+
export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
11
24
|
const { connected } = useTelemetryContext();
|
|
12
25
|
const { user, logout } = useAuth();
|
|
26
|
+
const authHeaders = useAuthHeaders();
|
|
13
27
|
const { projects, currentProjectId, currentProject, setCurrentProjectId, unassignedCount, projectsEnabled } = useProjects();
|
|
14
28
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
15
29
|
const [showProjectMenu, setShowProjectMenu] = useState(false);
|
|
30
|
+
const [showNotifications, setShowNotifications] = useState(false);
|
|
31
|
+
const [unseenCount, setUnseenCount] = useState(0);
|
|
32
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
33
|
+
const notificationChange = useNotificationChange();
|
|
34
|
+
const { events } = useTelemetryContext();
|
|
35
|
+
const { accessToken } = useAuth();
|
|
36
|
+
const fetchedOnce = useRef(false);
|
|
37
|
+
|
|
38
|
+
const agentNames = React.useMemo(() => {
|
|
39
|
+
const map: Record<string, string> = {};
|
|
40
|
+
for (const a of agents) map[a.id] = a.name;
|
|
41
|
+
return map;
|
|
42
|
+
}, [agents]);
|
|
43
|
+
|
|
44
|
+
// Set of agent IDs matching the current project filter
|
|
45
|
+
const projectAgentIds = React.useMemo(() => {
|
|
46
|
+
if (!projectsEnabled || currentProjectId === null) return null; // null = show all
|
|
47
|
+
if (currentProjectId === "unassigned") return new Set(agents.filter(a => !a.projectId).map(a => a.id));
|
|
48
|
+
return new Set(agents.filter(a => a.projectId === currentProjectId).map(a => a.id));
|
|
49
|
+
}, [agents, currentProjectId, projectsEnabled]);
|
|
50
|
+
|
|
51
|
+
// Fetch initial unseen count once
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (fetchedOnce.current || !accessToken) return;
|
|
54
|
+
fetchedOnce.current = true;
|
|
55
|
+
fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
56
|
+
.then(r => r.json())
|
|
57
|
+
.then(d => setUnseenCount(d.count || 0))
|
|
58
|
+
.catch(() => {});
|
|
59
|
+
}, [accessToken]);
|
|
60
|
+
|
|
61
|
+
// Bump count live from SSE (only if event matches current project)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (notificationChange === 0) return;
|
|
64
|
+
const latest = events.find(e =>
|
|
65
|
+
e.level === "error" || e.category === "ERROR" || (e.category === "system" && e.type === "agent_stopped")
|
|
66
|
+
);
|
|
67
|
+
if (latest && (!projectAgentIds || projectAgentIds.has(latest.agent_id))) {
|
|
68
|
+
setUnseenCount(c => c + 1);
|
|
69
|
+
}
|
|
70
|
+
}, [notificationChange]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
|
+
|
|
72
|
+
// Re-count when project filter changes (from cached notifications or reset)
|
|
73
|
+
const prevProjectRef = useRef(currentProjectId);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (prevProjectRef.current === currentProjectId) return;
|
|
76
|
+
prevProjectRef.current = currentProjectId;
|
|
77
|
+
// Refetch count with project filter
|
|
78
|
+
if (!accessToken) return;
|
|
79
|
+
fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
80
|
+
.then(r => r.json())
|
|
81
|
+
.then(d => {
|
|
82
|
+
// API returns total unseen — client filters by project
|
|
83
|
+
if (!projectAgentIds) {
|
|
84
|
+
setUnseenCount(d.count || 0);
|
|
85
|
+
} else {
|
|
86
|
+
// We need to fetch actual notifications to filter by project
|
|
87
|
+
fetch("/api/notifications?limit=200", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
88
|
+
.then(r => r.json())
|
|
89
|
+
.then(nd => {
|
|
90
|
+
const unseen = (nd.notifications || []).filter(
|
|
91
|
+
(n: Notification) => !n.seen && projectAgentIds.has(n.agent_id)
|
|
92
|
+
);
|
|
93
|
+
setUnseenCount(unseen.length);
|
|
94
|
+
})
|
|
95
|
+
.catch(() => {});
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.catch(() => {});
|
|
99
|
+
}, [currentProjectId, accessToken, projectAgentIds]);
|
|
100
|
+
|
|
101
|
+
const openNotifications = useCallback(async () => {
|
|
102
|
+
setShowNotifications(prev => !prev);
|
|
103
|
+
if (!showNotifications) {
|
|
104
|
+
try {
|
|
105
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
106
|
+
if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
107
|
+
const res = await fetch("/api/notifications?limit=50", { headers });
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
let items: Notification[] = data.notifications || [];
|
|
110
|
+
if (projectAgentIds) items = items.filter(n => projectAgentIds.has(n.agent_id));
|
|
111
|
+
setNotifications(items);
|
|
112
|
+
// Mark all as seen
|
|
113
|
+
if (unseenCount > 0) {
|
|
114
|
+
await fetch("/api/notifications/mark-seen", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers,
|
|
117
|
+
body: JSON.stringify({ all: true }),
|
|
118
|
+
});
|
|
119
|
+
setUnseenCount(0);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Ignore
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [showNotifications, unseenCount, accessToken, projectAgentIds]);
|
|
16
126
|
|
|
17
127
|
const handleLogout = async () => {
|
|
18
128
|
await logout();
|
|
@@ -123,6 +233,77 @@ export function Header({ onMenuClick }: HeaderProps) {
|
|
|
123
233
|
{connected ? "Live" : "Offline"}
|
|
124
234
|
</span>
|
|
125
235
|
</div>
|
|
236
|
+
{/* Notification Bell */}
|
|
237
|
+
<div className="relative">
|
|
238
|
+
<button
|
|
239
|
+
onClick={openNotifications}
|
|
240
|
+
className="relative p-2 text-[#666] hover:text-[#e0e0e0] transition rounded hover:bg-[#1a1a1a]"
|
|
241
|
+
>
|
|
242
|
+
<BellIcon className="w-5 h-5" />
|
|
243
|
+
{unseenCount > 0 && (
|
|
244
|
+
<span
|
|
245
|
+
className="absolute flex items-center justify-center bg-red-500 text-white font-bold rounded-full pointer-events-none"
|
|
246
|
+
style={{
|
|
247
|
+
top: 2,
|
|
248
|
+
right: 2,
|
|
249
|
+
fontSize: 9,
|
|
250
|
+
lineHeight: 1,
|
|
251
|
+
minWidth: 16,
|
|
252
|
+
height: 16,
|
|
253
|
+
padding: "0 4px",
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{unseenCount > 99 ? "99+" : unseenCount}
|
|
257
|
+
</span>
|
|
258
|
+
)}
|
|
259
|
+
</button>
|
|
260
|
+
{showNotifications && (
|
|
261
|
+
<>
|
|
262
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
|
|
263
|
+
<div className="absolute right-0 top-full mt-1 w-80 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50 max-h-96 overflow-y-auto">
|
|
264
|
+
<div className="px-4 py-3 border-b border-[#222] flex items-center justify-between">
|
|
265
|
+
<span className="text-sm font-medium">Notifications</span>
|
|
266
|
+
{notifications.length > 0 && (
|
|
267
|
+
<span className="text-xs text-[#666]">{notifications.length} recent</span>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
{notifications.length === 0 ? (
|
|
271
|
+
<div className="px-4 py-8 text-center text-sm text-[#666]">
|
|
272
|
+
No notifications
|
|
273
|
+
</div>
|
|
274
|
+
) : (
|
|
275
|
+
<div className="py-1">
|
|
276
|
+
{notifications.map(n => (
|
|
277
|
+
<div key={n.id} className={`px-4 py-3 hover:bg-[#1a1a1a] transition border-b border-[#1a1a1a] ${!n.seen ? "bg-[#0f0f0f]" : ""}`}>
|
|
278
|
+
<div className="flex items-center gap-2 mb-1">
|
|
279
|
+
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
280
|
+
!n.seen
|
|
281
|
+
? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "bg-[#f97316]")
|
|
282
|
+
: "bg-[#333]"
|
|
283
|
+
}`} />
|
|
284
|
+
<span className={`text-xs font-medium truncate ${!n.seen ? "text-[#ccc]" : "text-[#666]"}`}>
|
|
285
|
+
{n.category === "system" && n.type === "agent_stopped" ? "Agent Stopped" :
|
|
286
|
+
n.category === "ERROR" ? "Error" :
|
|
287
|
+
`${n.category} / ${n.type}`}
|
|
288
|
+
</span>
|
|
289
|
+
<span className="text-[10px] text-[#555] ml-auto flex-shrink-0">
|
|
290
|
+
{formatNotifTime(n.timestamp)}
|
|
291
|
+
</span>
|
|
292
|
+
</div>
|
|
293
|
+
<div className={`text-xs truncate ${!n.seen ? "text-[#aaa]" : "text-[#666]"}`}>
|
|
294
|
+
{n.error || (n.data as any)?.message || (n.data as any)?.error || `${n.type} event`}
|
|
295
|
+
</div>
|
|
296
|
+
<div className="text-[10px] text-[#555] mt-1">
|
|
297
|
+
{agentNames[n.agent_id] || n.agent_id.slice(0, 8)}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
126
307
|
<MetaAgentButton />
|
|
127
308
|
{user && (
|
|
128
309
|
<div className="relative">
|
|
@@ -156,3 +337,14 @@ export function Header({ onMenuClick }: HeaderProps) {
|
|
|
156
337
|
</header>
|
|
157
338
|
);
|
|
158
339
|
}
|
|
340
|
+
|
|
341
|
+
function formatNotifTime(timestamp: string): string {
|
|
342
|
+
const diff = Date.now() - new Date(timestamp).getTime();
|
|
343
|
+
const mins = Math.floor(diff / 60000);
|
|
344
|
+
if (mins < 1) return "just now";
|
|
345
|
+
if (mins < 60) return `${mins}m ago`;
|
|
346
|
+
const hours = Math.floor(mins / 60);
|
|
347
|
+
if (hours < 24) return `${hours}h ago`;
|
|
348
|
+
const days = Math.floor(hours / 24);
|
|
349
|
+
return `${days}d ago`;
|
|
350
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { DashboardIcon, ActivityIcon, AgentsIcon, TasksIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
|
|
2
|
+
import { DashboardIcon, ActivityIcon, AgentsIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
|
|
3
3
|
import type { Route } from "../../types";
|
|
4
4
|
|
|
5
5
|
interface SidebarProps {
|
|
@@ -88,6 +88,12 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
|
|
|
88
88
|
active={route === "skills"}
|
|
89
89
|
onClick={() => handleNavigate("skills")}
|
|
90
90
|
/>
|
|
91
|
+
<NavButton
|
|
92
|
+
icon={<ConnectionsIcon />}
|
|
93
|
+
label="Connections"
|
|
94
|
+
active={route === "connections"}
|
|
95
|
+
onClick={() => handleNavigate("connections")}
|
|
96
|
+
/>
|
|
91
97
|
<NavButton
|
|
92
98
|
icon={<TestsIcon />}
|
|
93
99
|
label="Tests"
|
|
@@ -46,10 +46,14 @@ export function IntegrationsPanel({
|
|
|
46
46
|
providerId = "composio",
|
|
47
47
|
projectId,
|
|
48
48
|
onConnectionComplete,
|
|
49
|
+
onBrowseTriggers,
|
|
50
|
+
hideMcpConfig,
|
|
49
51
|
}: {
|
|
50
52
|
providerId?: string;
|
|
51
53
|
projectId?: string | null;
|
|
52
54
|
onConnectionComplete?: () => void;
|
|
55
|
+
onBrowseTriggers?: (toolkitSlug: string) => void;
|
|
56
|
+
hideMcpConfig?: boolean;
|
|
53
57
|
}) {
|
|
54
58
|
const { authFetch } = useAuth();
|
|
55
59
|
const [apps, setApps] = useState<IntegrationApp[]>([]);
|
|
@@ -314,9 +318,10 @@ export function IntegrationsPanel({
|
|
|
314
318
|
);
|
|
315
319
|
};
|
|
316
320
|
|
|
317
|
-
// Get connection for app
|
|
321
|
+
// Get connection for app (prefer active account)
|
|
318
322
|
const getConnection = (appSlug: string) => {
|
|
319
|
-
return connectedAccounts.find((a) => a.appId === appSlug)
|
|
323
|
+
return connectedAccounts.find((a) => a.appId === appSlug && a.status === "active")
|
|
324
|
+
|| connectedAccounts.find((a) => a.appId === appSlug);
|
|
320
325
|
};
|
|
321
326
|
|
|
322
327
|
// Filter apps
|
|
@@ -590,7 +595,8 @@ export function IntegrationsPanel({
|
|
|
590
595
|
const conn = getConnection(app.slug);
|
|
591
596
|
if (conn) handleDisconnect(conn);
|
|
592
597
|
}}
|
|
593
|
-
onCreateMcpConfig={() => openMcpConfigModal(app)}
|
|
598
|
+
onCreateMcpConfig={hideMcpConfig ? undefined : () => openMcpConfigModal(app)}
|
|
599
|
+
onBrowseTriggers={onBrowseTriggers ? () => onBrowseTriggers(app.slug) : undefined}
|
|
594
600
|
connecting={connecting === app.slug}
|
|
595
601
|
/>
|
|
596
602
|
))}
|
|
@@ -636,6 +642,7 @@ function AppCard({
|
|
|
636
642
|
onConnect,
|
|
637
643
|
onDisconnect,
|
|
638
644
|
onCreateMcpConfig,
|
|
645
|
+
onBrowseTriggers,
|
|
639
646
|
connecting,
|
|
640
647
|
}: {
|
|
641
648
|
app: IntegrationApp;
|
|
@@ -643,6 +650,7 @@ function AppCard({
|
|
|
643
650
|
onConnect: () => void;
|
|
644
651
|
onDisconnect?: () => void;
|
|
645
652
|
onCreateMcpConfig?: () => void;
|
|
653
|
+
onBrowseTriggers?: () => void;
|
|
646
654
|
connecting: boolean;
|
|
647
655
|
}) {
|
|
648
656
|
const isConnected = connection?.status === "active";
|
|
@@ -723,6 +731,14 @@ function AppCard({
|
|
|
723
731
|
Create MCP Config
|
|
724
732
|
</button>
|
|
725
733
|
)}
|
|
734
|
+
{onBrowseTriggers && (
|
|
735
|
+
<button
|
|
736
|
+
onClick={onBrowseTriggers}
|
|
737
|
+
className="flex-1 text-xs bg-[#1a1a2a] hover:bg-[#1a1a3a] border border-blue-500/30 hover:border-blue-500/50 text-blue-400 px-3 py-1.5 rounded transition"
|
|
738
|
+
>
|
|
739
|
+
Browse Triggers
|
|
740
|
+
</button>
|
|
741
|
+
)}
|
|
726
742
|
{onDisconnect && (
|
|
727
743
|
<button
|
|
728
744
|
onClick={onDisconnect}
|