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.
Files changed (78) hide show
  1. package/dist/ActivityPage.9a1qg4bp.js +3 -0
  2. package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
  3. package/dist/App.1nmg2h01.js +4 -0
  4. package/dist/App.5qw2dtxs.js +4 -0
  5. package/dist/App.6nc5acvk.js +4 -0
  6. package/dist/App.7vzbaz56.js +4 -0
  7. package/dist/App.8rfz30p1.js +4 -0
  8. package/dist/App.amwp54wf.js +4 -0
  9. package/dist/App.e4202qb4.js +267 -0
  10. package/dist/App.errxz2q4.js +4 -0
  11. package/dist/App.f8qsyhpr.js +4 -0
  12. package/dist/App.g8vq68n0.js +20 -0
  13. package/dist/App.kfyrnznw.js +13 -0
  14. package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
  15. package/dist/App.p93mmyqw.js +4 -0
  16. package/dist/App.qmg33p02.js +4 -0
  17. package/dist/App.sdsc0258.js +4 -0
  18. package/dist/ConnectionsPage.7zqba1r0.js +3 -0
  19. package/dist/McpPage.kf2g327t.js +3 -0
  20. package/dist/SettingsPage.472c15ep.js +3 -0
  21. package/dist/SkillsPage.xdxnh68a.js +3 -0
  22. package/dist/TasksPage.7g0b8xwc.js +3 -0
  23. package/dist/TelemetryPage.pr7rbz4r.js +3 -0
  24. package/dist/TestsPage.zhc6rqjm.js +3 -0
  25. package/dist/apteva-kit.css +1 -1
  26. package/dist/index.html +1 -1
  27. package/dist/styles.css +1 -1
  28. package/package.json +9 -4
  29. package/src/auth/middleware.ts +2 -0
  30. package/src/channels/index.ts +40 -0
  31. package/src/channels/telegram.ts +306 -0
  32. package/src/db.ts +342 -11
  33. package/src/integrations/agentdojo.ts +1 -1
  34. package/src/mcp-handler.ts +31 -24
  35. package/src/mcp-platform.ts +41 -1
  36. package/src/providers.ts +22 -9
  37. package/src/routes/api/agent-utils.ts +38 -2
  38. package/src/routes/api/agents.ts +65 -2
  39. package/src/routes/api/channels.ts +182 -0
  40. package/src/routes/api/integrations.ts +13 -5
  41. package/src/routes/api/mcp.ts +27 -9
  42. package/src/routes/api/projects.ts +19 -2
  43. package/src/routes/api/system.ts +26 -12
  44. package/src/routes/api/telemetry.ts +30 -0
  45. package/src/routes/api/triggers.ts +478 -0
  46. package/src/routes/api/webhooks.ts +171 -0
  47. package/src/routes/api.ts +7 -1
  48. package/src/routes/static.ts +12 -3
  49. package/src/server.ts +43 -6
  50. package/src/triggers/agentdojo.ts +253 -0
  51. package/src/triggers/composio.ts +264 -0
  52. package/src/triggers/index.ts +71 -0
  53. package/src/tui/AgentList.tsx +145 -0
  54. package/src/tui/App.tsx +102 -0
  55. package/src/tui/Login.tsx +104 -0
  56. package/src/tui/api.ts +72 -0
  57. package/src/tui/index.tsx +7 -0
  58. package/src/web/App.tsx +18 -11
  59. package/src/web/components/agents/AgentCard.tsx +14 -7
  60. package/src/web/components/agents/AgentPanel.tsx +94 -137
  61. package/src/web/components/common/Icons.tsx +16 -0
  62. package/src/web/components/common/index.ts +1 -0
  63. package/src/web/components/connections/ConnectionsPage.tsx +54 -0
  64. package/src/web/components/connections/IntegrationsTab.tsx +144 -0
  65. package/src/web/components/connections/OverviewTab.tsx +137 -0
  66. package/src/web/components/connections/TriggersTab.tsx +1169 -0
  67. package/src/web/components/index.ts +1 -0
  68. package/src/web/components/layout/Header.tsx +196 -4
  69. package/src/web/components/layout/Sidebar.tsx +7 -1
  70. package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
  71. package/src/web/components/settings/SettingsPage.tsx +364 -2
  72. package/src/web/components/tasks/TasksPage.tsx +2 -2
  73. package/src/web/components/tests/TestsPage.tsx +1 -2
  74. package/src/web/context/TelemetryContext.tsx +14 -1
  75. package/src/web/context/index.ts +1 -1
  76. package/src/web/hooks/useAgents.ts +15 -11
  77. package/src/web/types.ts +1 -1
  78. package/dist/App.fq4xbpcz.js +0 -228
@@ -18,3 +18,4 @@ export { McpPage } from "./mcp";
18
18
  export { SkillsPage } from "./skills/SkillsPage";
19
19
  export { TestsPage } from "./tests/TestsPage";
20
20
  export { TelemetryPage } from "./telemetry/TelemetryPage";
21
+ export { ConnectionsPage } from "./connections/ConnectionsPage";
@@ -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}