apteva 0.2.6 → 0.2.8

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 (41) hide show
  1. package/dist/App.hzbfeg94.js +217 -0
  2. package/dist/index.html +3 -1
  3. package/dist/styles.css +1 -1
  4. package/package.json +1 -1
  5. package/src/auth/index.ts +386 -0
  6. package/src/auth/middleware.ts +183 -0
  7. package/src/binary.ts +19 -1
  8. package/src/db.ts +570 -32
  9. package/src/routes/api.ts +913 -38
  10. package/src/routes/auth.ts +242 -0
  11. package/src/server.ts +60 -8
  12. package/src/web/App.tsx +61 -19
  13. package/src/web/components/agents/AgentCard.tsx +30 -41
  14. package/src/web/components/agents/AgentPanel.tsx +751 -11
  15. package/src/web/components/agents/AgentsView.tsx +81 -9
  16. package/src/web/components/agents/CreateAgentModal.tsx +28 -1
  17. package/src/web/components/auth/CreateAccountStep.tsx +176 -0
  18. package/src/web/components/auth/LoginPage.tsx +91 -0
  19. package/src/web/components/auth/index.ts +2 -0
  20. package/src/web/components/common/Icons.tsx +48 -0
  21. package/src/web/components/common/Modal.tsx +1 -1
  22. package/src/web/components/dashboard/Dashboard.tsx +91 -31
  23. package/src/web/components/index.ts +3 -0
  24. package/src/web/components/layout/Header.tsx +145 -15
  25. package/src/web/components/layout/Sidebar.tsx +81 -43
  26. package/src/web/components/mcp/McpPage.tsx +261 -32
  27. package/src/web/components/onboarding/OnboardingWizard.tsx +64 -8
  28. package/src/web/components/settings/SettingsPage.tsx +404 -18
  29. package/src/web/components/tasks/TasksPage.tsx +21 -19
  30. package/src/web/components/telemetry/TelemetryPage.tsx +271 -81
  31. package/src/web/context/AuthContext.tsx +230 -0
  32. package/src/web/context/ProjectContext.tsx +182 -0
  33. package/src/web/context/TelemetryContext.tsx +98 -76
  34. package/src/web/context/index.ts +5 -0
  35. package/src/web/hooks/useAgents.ts +18 -6
  36. package/src/web/hooks/useOnboarding.ts +20 -4
  37. package/src/web/hooks/useProviders.ts +15 -5
  38. package/src/web/icon.png +0 -0
  39. package/src/web/styles.css +12 -0
  40. package/src/web/types.ts +6 -0
  41. package/dist/App.0mzj9cz9.js +0 -213
@@ -0,0 +1,230 @@
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, useRef, type ReactNode } from "react";
2
+
3
+ interface User {
4
+ id: string;
5
+ username: string;
6
+ role: "admin" | "user";
7
+ }
8
+
9
+ interface AuthStatus {
10
+ hasUsers: boolean;
11
+ authenticated: boolean;
12
+ user?: User;
13
+ }
14
+
15
+ interface AuthContextValue {
16
+ user: User | null;
17
+ isAuthenticated: boolean;
18
+ isLoading: boolean;
19
+ hasUsers: boolean | null;
20
+ accessToken: string | null;
21
+ login: (username: string, password: string) => Promise<{ success: boolean; error?: string }>;
22
+ logout: () => Promise<void>;
23
+ refreshToken: () => Promise<boolean>;
24
+ checkAuth: () => Promise<void>;
25
+ authFetch: (url: string, options?: RequestInit) => Promise<Response>;
26
+ }
27
+
28
+ const AuthContext = createContext<AuthContextValue | null>(null);
29
+
30
+ export function useAuth(): AuthContextValue {
31
+ const context = useContext(AuthContext);
32
+ if (!context) {
33
+ throw new Error("useAuth must be used within an AuthProvider");
34
+ }
35
+ return context;
36
+ }
37
+
38
+ interface AuthProviderProps {
39
+ children: ReactNode;
40
+ }
41
+
42
+ export function AuthProvider({ children }: AuthProviderProps) {
43
+ const [user, setUser] = useState<User | null>(null);
44
+ const [accessToken, setAccessToken] = useState<string | null>(null);
45
+ const [isLoading, setIsLoading] = useState(true);
46
+ const [hasUsers, setHasUsers] = useState<boolean | null>(null);
47
+
48
+ // Refs to track state without causing re-renders
49
+ const tokenRef = useRef<string | null>(null);
50
+ const refreshingRef = useRef(false);
51
+ const initializedRef = useRef(false);
52
+
53
+ // Helper to set token in both state and ref
54
+ const updateToken = useCallback((token: string | null) => {
55
+ tokenRef.current = token;
56
+ setAccessToken(token);
57
+ }, []);
58
+
59
+ // Internal refresh function - prevents concurrent refreshes
60
+ const refreshTokenInternal = useCallback(async (): Promise<boolean> => {
61
+ // Prevent concurrent refresh calls
62
+ if (refreshingRef.current) {
63
+ return false;
64
+ }
65
+ refreshingRef.current = true;
66
+
67
+ try {
68
+ const res = await fetch("/api/auth/refresh", {
69
+ method: "POST",
70
+ credentials: "include",
71
+ });
72
+
73
+ if (!res.ok) {
74
+ return false;
75
+ }
76
+
77
+ const data = await res.json();
78
+ updateToken(data.accessToken);
79
+
80
+ // Get user info with new token
81
+ const meRes = await fetch("/api/auth/me", {
82
+ headers: { Authorization: `Bearer ${data.accessToken}` },
83
+ });
84
+
85
+ if (meRes.ok) {
86
+ const meData = await meRes.json();
87
+ setUser(meData.user);
88
+ return true;
89
+ }
90
+
91
+ return false;
92
+ } catch (e) {
93
+ console.error("Token refresh failed:", e);
94
+ return false;
95
+ } finally {
96
+ refreshingRef.current = false;
97
+ }
98
+ }, [updateToken]);
99
+
100
+ // Check auth status
101
+ const checkAuth = useCallback(async () => {
102
+ try {
103
+ const token = tokenRef.current;
104
+ const res = await fetch("/api/auth/check", {
105
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
106
+ });
107
+ const data: AuthStatus = await res.json();
108
+
109
+ setHasUsers(data.hasUsers);
110
+
111
+ if (data.authenticated && data.user) {
112
+ setUser(data.user as User);
113
+ } else {
114
+ setUser(null);
115
+ // Try to refresh if we have users (meaning there might be a cookie)
116
+ if (data.hasUsers) {
117
+ const refreshed = await refreshTokenInternal();
118
+ if (!refreshed) {
119
+ updateToken(null);
120
+ }
121
+ }
122
+ }
123
+ } catch (e) {
124
+ console.error("Auth check failed:", e);
125
+ setUser(null);
126
+ updateToken(null);
127
+ } finally {
128
+ setIsLoading(false);
129
+ }
130
+ }, [refreshTokenInternal, updateToken]);
131
+
132
+ // Login
133
+ const login = useCallback(async (username: string, password: string): Promise<{ success: boolean; error?: string }> => {
134
+ try {
135
+ const res = await fetch("/api/auth/login", {
136
+ method: "POST",
137
+ headers: { "Content-Type": "application/json" },
138
+ credentials: "include",
139
+ body: JSON.stringify({ username, password }),
140
+ });
141
+
142
+ const data = await res.json();
143
+
144
+ if (!res.ok) {
145
+ return { success: false, error: data.error || "Login failed" };
146
+ }
147
+
148
+ updateToken(data.accessToken);
149
+ setUser(data.user);
150
+ setHasUsers(true);
151
+
152
+ return { success: true };
153
+ } catch (e) {
154
+ console.error("Login failed:", e);
155
+ return { success: false, error: "Login failed" };
156
+ }
157
+ }, [updateToken]);
158
+
159
+ // Logout
160
+ const logout = useCallback(async () => {
161
+ try {
162
+ const token = tokenRef.current;
163
+ await fetch("/api/auth/logout", {
164
+ method: "POST",
165
+ credentials: "include",
166
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
167
+ });
168
+ } catch (e) {
169
+ console.error("Logout failed:", e);
170
+ } finally {
171
+ setUser(null);
172
+ updateToken(null);
173
+ }
174
+ }, [updateToken]);
175
+
176
+ // Authenticated fetch wrapper - uses ref for latest token
177
+ const authFetch = useCallback(async (url: string, options: RequestInit = {}): Promise<Response> => {
178
+ const headers = new Headers(options.headers);
179
+ const token = tokenRef.current;
180
+ if (token) {
181
+ headers.set("Authorization", `Bearer ${token}`);
182
+ }
183
+ return fetch(url, { ...options, headers });
184
+ }, []);
185
+
186
+ // Public refresh function
187
+ const refreshToken = useCallback(async (): Promise<boolean> => {
188
+ return refreshTokenInternal();
189
+ }, [refreshTokenInternal]);
190
+
191
+ // Check auth on mount - only once
192
+ useEffect(() => {
193
+ if (initializedRef.current) return;
194
+ initializedRef.current = true;
195
+ checkAuth();
196
+ }, [checkAuth]);
197
+
198
+ // Set up token refresh interval
199
+ useEffect(() => {
200
+ if (!accessToken) return;
201
+
202
+ // Refresh token 1 minute before expiry (tokens last 15 min)
203
+ const refreshInterval = setInterval(() => {
204
+ refreshTokenInternal();
205
+ }, 14 * 60 * 1000); // 14 minutes
206
+
207
+ return () => clearInterval(refreshInterval);
208
+ }, [accessToken, refreshTokenInternal]);
209
+
210
+ const value: AuthContextValue = {
211
+ user,
212
+ isAuthenticated: !!user,
213
+ isLoading,
214
+ hasUsers,
215
+ accessToken,
216
+ login,
217
+ logout,
218
+ refreshToken,
219
+ checkAuth,
220
+ authFetch,
221
+ };
222
+
223
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
224
+ }
225
+
226
+ // Hook to get auth headers for API calls
227
+ export function useAuthHeaders(): Record<string, string> {
228
+ const { accessToken } = useAuth();
229
+ return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
230
+ }
@@ -0,0 +1,182 @@
1
+ import React, { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
2
+ import { useAuth } from "./AuthContext";
3
+
4
+ export interface Project {
5
+ id: string;
6
+ name: string;
7
+ description: string | null;
8
+ color: string;
9
+ agentCount: number;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ }
13
+
14
+ interface ProjectContextValue {
15
+ projects: Project[];
16
+ currentProjectId: string | null; // null = "All Projects", "unassigned" = unassigned agents
17
+ currentProject: Project | null;
18
+ isLoading: boolean;
19
+ error: string | null;
20
+ unassignedCount: number;
21
+ setCurrentProjectId: (id: string | null) => void;
22
+ createProject: (data: { name: string; description?: string; color?: string }) => Promise<Project | null>;
23
+ updateProject: (id: string, data: { name?: string; description?: string; color?: string }) => Promise<Project | null>;
24
+ deleteProject: (id: string) => Promise<boolean>;
25
+ refreshProjects: () => Promise<void>;
26
+ }
27
+
28
+ const ProjectContext = createContext<ProjectContextValue | null>(null);
29
+
30
+ export function useProjects(): ProjectContextValue {
31
+ const context = useContext(ProjectContext);
32
+ if (!context) {
33
+ throw new Error("useProjects must be used within a ProjectProvider");
34
+ }
35
+ return context;
36
+ }
37
+
38
+ interface ProjectProviderProps {
39
+ children: ReactNode;
40
+ }
41
+
42
+ const STORAGE_KEY = "apteva_current_project";
43
+
44
+ export function ProjectProvider({ children }: ProjectProviderProps) {
45
+ const { authFetch, isAuthenticated, isLoading: authLoading } = useAuth();
46
+ const [projects, setProjects] = useState<Project[]>([]);
47
+ const [currentProjectId, setCurrentProjectIdState] = useState<string | null>(() => {
48
+ // Load from localStorage
49
+ if (typeof window !== "undefined") {
50
+ return localStorage.getItem(STORAGE_KEY);
51
+ }
52
+ return null;
53
+ });
54
+ const [isLoading, setIsLoading] = useState(true);
55
+ const [error, setError] = useState<string | null>(null);
56
+ const [unassignedCount, setUnassignedCount] = useState(0);
57
+
58
+ const setCurrentProjectId = useCallback((id: string | null) => {
59
+ setCurrentProjectIdState(id);
60
+ if (typeof window !== "undefined") {
61
+ if (id === null) {
62
+ localStorage.removeItem(STORAGE_KEY);
63
+ } else {
64
+ localStorage.setItem(STORAGE_KEY, id);
65
+ }
66
+ }
67
+ }, []);
68
+
69
+ const currentProject = projects.find(p => p.id === currentProjectId) || null;
70
+
71
+ const refreshProjects = useCallback(async () => {
72
+ if (!isAuthenticated && !authLoading) {
73
+ setProjects([]);
74
+ setIsLoading(false);
75
+ return;
76
+ }
77
+
78
+ try {
79
+ setError(null);
80
+ const res = await authFetch("/api/projects");
81
+ if (!res.ok) {
82
+ throw new Error("Failed to fetch projects");
83
+ }
84
+ const data = await res.json();
85
+ setProjects(data.projects || []);
86
+ setUnassignedCount(data.unassignedCount || 0);
87
+
88
+ // If current project no longer exists, reset to all
89
+ if (currentProjectId && currentProjectId !== "unassigned" && !data.projects.find((p: Project) => p.id === currentProjectId)) {
90
+ setCurrentProjectId(null);
91
+ }
92
+ } catch (e) {
93
+ console.error("Failed to fetch projects:", e);
94
+ setError("Failed to load projects");
95
+ } finally {
96
+ setIsLoading(false);
97
+ }
98
+ }, [authFetch, isAuthenticated, authLoading, currentProjectId, setCurrentProjectId]);
99
+
100
+ const createProject = useCallback(async (data: { name: string; description?: string; color?: string }): Promise<Project | null> => {
101
+ try {
102
+ const res = await authFetch("/api/projects", {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify(data),
106
+ });
107
+ if (!res.ok) {
108
+ const err = await res.json();
109
+ throw new Error(err.error || "Failed to create project");
110
+ }
111
+ const result = await res.json();
112
+ await refreshProjects();
113
+ return result.project;
114
+ } catch (e) {
115
+ console.error("Failed to create project:", e);
116
+ return null;
117
+ }
118
+ }, [authFetch, refreshProjects]);
119
+
120
+ const updateProject = useCallback(async (id: string, data: { name?: string; description?: string; color?: string }): Promise<Project | null> => {
121
+ try {
122
+ const res = await authFetch(`/api/projects/${id}`, {
123
+ method: "PUT",
124
+ headers: { "Content-Type": "application/json" },
125
+ body: JSON.stringify(data),
126
+ });
127
+ if (!res.ok) {
128
+ const err = await res.json();
129
+ throw new Error(err.error || "Failed to update project");
130
+ }
131
+ const result = await res.json();
132
+ await refreshProjects();
133
+ return result.project;
134
+ } catch (e) {
135
+ console.error("Failed to update project:", e);
136
+ return null;
137
+ }
138
+ }, [authFetch, refreshProjects]);
139
+
140
+ const deleteProject = useCallback(async (id: string): Promise<boolean> => {
141
+ try {
142
+ const res = await authFetch(`/api/projects/${id}`, {
143
+ method: "DELETE",
144
+ });
145
+ if (!res.ok) {
146
+ const err = await res.json();
147
+ throw new Error(err.error || "Failed to delete project");
148
+ }
149
+ if (currentProjectId === id) {
150
+ setCurrentProjectId(null);
151
+ }
152
+ await refreshProjects();
153
+ return true;
154
+ } catch (e) {
155
+ console.error("Failed to delete project:", e);
156
+ return false;
157
+ }
158
+ }, [authFetch, currentProjectId, setCurrentProjectId, refreshProjects]);
159
+
160
+ // Fetch projects when authenticated
161
+ useEffect(() => {
162
+ if (!authLoading) {
163
+ refreshProjects();
164
+ }
165
+ }, [authLoading, refreshProjects]);
166
+
167
+ const value: ProjectContextValue = {
168
+ projects,
169
+ currentProjectId,
170
+ currentProject,
171
+ isLoading,
172
+ error,
173
+ unassignedCount,
174
+ setCurrentProjectId,
175
+ createProject,
176
+ updateProject,
177
+ deleteProject,
178
+ refreshProjects,
179
+ };
180
+
181
+ return <ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>;
182
+ }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useState, useCallback, useRef } from "react";
1
+ import React, { createContext, useContext, useEffect, useState, useCallback, useRef, useMemo } from "react";
2
2
 
3
3
  export interface TelemetryEvent {
4
4
  id: string;
@@ -18,6 +18,7 @@ interface TelemetryContextValue {
18
18
  connected: boolean;
19
19
  events: TelemetryEvent[];
20
20
  lastActivityByAgent: Record<string, { timestamp: string; category: string; type: string }>;
21
+ activeAgents: Record<string, { type: string; expiresAt: number }>;
21
22
  clearEvents: () => void;
22
23
  }
23
24
 
@@ -29,70 +30,111 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
29
30
  const [connected, setConnected] = useState(false);
30
31
  const [events, setEvents] = useState<TelemetryEvent[]>([]);
31
32
  const [lastActivityByAgent, setLastActivityByAgent] = useState<Record<string, { timestamp: string; category: string; type: string }>>({});
33
+ const [activeAgents, setActiveAgents] = useState<Record<string, { type: string; expiresAt: number }>>({});
32
34
  const eventSourceRef = useRef<EventSource | null>(null);
33
35
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
34
36
 
37
+ // Clean up expired active states
38
+ useEffect(() => {
39
+ const interval = setInterval(() => {
40
+ const now = Date.now();
41
+ setActiveAgents(prev => {
42
+ const updated: Record<string, { type: string; expiresAt: number }> = {};
43
+ for (const [agentId, state] of Object.entries(prev)) {
44
+ if (state.expiresAt > now) {
45
+ updated[agentId] = state;
46
+ }
47
+ }
48
+ return updated;
49
+ });
50
+ }, 500);
51
+ return () => clearInterval(interval);
52
+ }, []);
53
+
35
54
  const connect = useCallback(() => {
36
55
  if (eventSourceRef.current) {
37
56
  eventSourceRef.current.close();
57
+ eventSourceRef.current = null;
38
58
  }
39
59
 
40
- const es = new EventSource("/api/telemetry/stream");
41
- eventSourceRef.current = es;
42
-
43
- es.onopen = () => {
44
- setConnected(true);
45
- };
46
-
47
- es.onmessage = (event) => {
48
- try {
49
- const data = JSON.parse(event.data);
50
-
51
- // Handle connection message
52
- if (data.connected) {
53
- setConnected(true);
54
- return;
55
- }
56
-
57
- // Handle array of events
58
- if (Array.isArray(data)) {
59
- setEvents(prev => {
60
- const combined = [...data, ...prev];
61
- return combined.slice(0, MAX_EVENTS);
62
- });
63
-
64
- // Update last activity per agent
65
- setLastActivityByAgent(prev => {
66
- const updated = { ...prev };
67
- for (const evt of data) {
68
- const existing = updated[evt.agent_id];
69
- if (!existing || new Date(evt.timestamp) > new Date(existing.timestamp)) {
70
- updated[evt.agent_id] = {
71
- timestamp: evt.timestamp,
72
- category: evt.category,
73
- type: evt.type,
74
- };
60
+ try {
61
+ const es = new EventSource("/api/telemetry/stream");
62
+ eventSourceRef.current = es;
63
+
64
+ es.onopen = () => {
65
+ setConnected(true);
66
+ };
67
+
68
+ es.onmessage = (event) => {
69
+ // Ignore keepalive pings (comments starting with :)
70
+ if (!event.data || event.data.trim() === "") return;
71
+
72
+ try {
73
+ const data = JSON.parse(event.data);
74
+
75
+ // Handle connection message
76
+ if (data.connected) {
77
+ setConnected(true);
78
+ return;
79
+ }
80
+
81
+ // Handle array of events
82
+ if (Array.isArray(data)) {
83
+ setEvents(prev => {
84
+ const combined = [...data, ...prev];
85
+ return combined.slice(0, MAX_EVENTS);
86
+ });
87
+
88
+ // Update last activity per agent
89
+ setLastActivityByAgent(prev => {
90
+ const updated = { ...prev };
91
+ for (const evt of data) {
92
+ const existing = updated[evt.agent_id];
93
+ if (!existing || new Date(evt.timestamp) > new Date(existing.timestamp)) {
94
+ updated[evt.agent_id] = {
95
+ timestamp: evt.timestamp,
96
+ category: evt.category,
97
+ type: evt.type,
98
+ };
99
+ }
75
100
  }
76
- }
77
- return updated;
78
- });
101
+ return updated;
102
+ });
103
+
104
+ // Set agents as active for 3 seconds (tracked in context, not component)
105
+ setActiveAgents(prev => {
106
+ const updated = { ...prev };
107
+ const expiresAt = Date.now() + 3000;
108
+ for (const evt of data) {
109
+ updated[evt.agent_id] = { type: evt.type, expiresAt };
110
+ }
111
+ return updated;
112
+ });
113
+ }
114
+ } catch {
115
+ // Ignore parse errors (likely keepalive or empty message)
79
116
  }
80
- } catch (e) {
81
- console.error("Failed to parse telemetry event:", e);
82
- }
83
- };
117
+ };
84
118
 
85
- es.onerror = () => {
86
- setConnected(false);
87
- es.close();
88
- eventSourceRef.current = null;
119
+ es.onerror = () => {
120
+ setConnected(false);
121
+ es.close();
122
+ eventSourceRef.current = null;
89
123
 
90
- // Reconnect after 3 seconds
124
+ // Reconnect after 2 seconds
125
+ if (reconnectTimeoutRef.current) {
126
+ clearTimeout(reconnectTimeoutRef.current);
127
+ }
128
+ reconnectTimeoutRef.current = setTimeout(connect, 2000);
129
+ };
130
+ } catch {
131
+ // Failed to create EventSource, retry
132
+ setConnected(false);
91
133
  if (reconnectTimeoutRef.current) {
92
134
  clearTimeout(reconnectTimeoutRef.current);
93
135
  }
94
- reconnectTimeoutRef.current = setTimeout(connect, 3000);
95
- };
136
+ reconnectTimeoutRef.current = setTimeout(connect, 2000);
137
+ }
96
138
  }, []);
97
139
 
98
140
  useEffect(() => {
@@ -113,7 +155,7 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
113
155
  }, []);
114
156
 
115
157
  return (
116
- <TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, clearEvents }}>
158
+ <TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, activeAgents, clearEvents }}>
117
159
  {children}
118
160
  </TelemetryContext.Provider>
119
161
  );
@@ -170,33 +212,13 @@ export function useTelemetry(filter?: {
170
212
  };
171
213
  }
172
214
 
173
- // Hook for agent activity indicator
215
+ // Hook for agent activity indicator - uses context-level tracking
174
216
  export function useAgentActivity(agentId: string) {
175
- const { lastActivityByAgent } = useTelemetryContext();
176
- const [isActive, setIsActive] = useState(false);
177
- const lastActivity = lastActivityByAgent[agentId];
178
-
179
- useEffect(() => {
180
- if (!lastActivity) {
181
- setIsActive(false);
182
- return;
183
- }
184
-
185
- // Set active when we get new activity
186
- setIsActive(true);
187
-
188
- // Clear active state after 3 seconds of no activity
189
- const timeout = setTimeout(() => {
190
- setIsActive(false);
191
- }, 3000);
192
-
193
- return () => clearTimeout(timeout);
194
- }, [lastActivity?.timestamp]);
217
+ const { activeAgents } = useTelemetryContext();
218
+ const activity = activeAgents[agentId];
195
219
 
196
220
  return {
197
- isActive,
198
- lastActivity,
199
- category: lastActivity?.category,
200
- type: lastActivity?.type,
221
+ isActive: !!activity,
222
+ type: activity?.type,
201
223
  };
202
224
  }
@@ -1,2 +1,7 @@
1
1
  export { TelemetryProvider, useTelemetryContext, useTelemetry, useAgentActivity } from "./TelemetryContext";
2
2
  export type { TelemetryEvent } from "./TelemetryContext";
3
+
4
+ export { AuthProvider, useAuth, useAuthHeaders } from "./AuthContext";
5
+
6
+ export { ProjectProvider, useProjects } from "./ProjectContext";
7
+ export type { Project } from "./ProjectContext";