apteva 0.2.7 → 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 (40) 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 +561 -32
  9. package/src/routes/api.ts +901 -35
  10. package/src/routes/auth.ts +242 -0
  11. package/src/server.ts +46 -5
  12. package/src/web/App.tsx +61 -19
  13. package/src/web/components/agents/AgentCard.tsx +24 -22
  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 +70 -22
  23. package/src/web/components/index.ts +3 -0
  24. package/src/web/components/layout/Header.tsx +135 -18
  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 +320 -21
  29. package/src/web/components/tasks/TasksPage.tsx +21 -19
  30. package/src/web/components/telemetry/TelemetryPage.tsx +163 -61
  31. package/src/web/context/AuthContext.tsx +230 -0
  32. package/src/web/context/ProjectContext.tsx +182 -0
  33. package/src/web/context/index.ts +5 -0
  34. package/src/web/hooks/useAgents.ts +18 -6
  35. package/src/web/hooks/useOnboarding.ts +20 -4
  36. package/src/web/hooks/useProviders.ts +15 -5
  37. package/src/web/icon.png +0 -0
  38. package/src/web/styles.css +12 -0
  39. package/src/web/types.ts +6 -0
  40. package/dist/App.3kb50qa3.js +0 -213
@@ -0,0 +1,242 @@
1
+ import { UserDB } from "../db";
2
+ import {
3
+ login,
4
+ refreshSession,
5
+ invalidateSession,
6
+ getAuthStatus,
7
+ verifyAccessToken,
8
+ hashPassword,
9
+ validatePassword,
10
+ REFRESH_TOKEN_EXPIRY,
11
+ } from "../auth";
12
+ import {
13
+ getTokenFromRequest,
14
+ getRefreshTokenFromCookie,
15
+ createRefreshTokenCookie,
16
+ clearRefreshTokenCookie,
17
+ } from "../auth/middleware";
18
+
19
+ function json(data: unknown, status = 200, headers: Record<string, string> = {}): Response {
20
+ return new Response(JSON.stringify(data), {
21
+ status,
22
+ headers: { "Content-Type": "application/json", ...headers },
23
+ });
24
+ }
25
+
26
+ export async function handleAuthRequest(req: Request, path: string): Promise<Response> {
27
+ const method = req.method;
28
+
29
+ // GET /api/auth/check - Check authentication status
30
+ if (path === "/api/auth/check" && method === "GET") {
31
+ const token = getTokenFromRequest(req);
32
+ const status = getAuthStatus(token || undefined);
33
+ return json(status);
34
+ }
35
+
36
+ // POST /api/auth/login - Login with username and password
37
+ if (path === "/api/auth/login" && method === "POST") {
38
+ try {
39
+ const body = await req.json();
40
+ const { username, password } = body;
41
+
42
+ if (!username || !password) {
43
+ return json({ error: "Username and password are required" }, 400);
44
+ }
45
+
46
+ const result = await login(username, password);
47
+
48
+ if (!result.success) {
49
+ return json({ error: result.error }, 401);
50
+ }
51
+
52
+ // Set refresh token as httpOnly cookie
53
+ const cookieHeader = createRefreshTokenCookie(result.tokens!.refreshToken, REFRESH_TOKEN_EXPIRY);
54
+
55
+ return json(
56
+ {
57
+ user: result.user,
58
+ accessToken: result.tokens!.accessToken,
59
+ expiresIn: result.tokens!.expiresIn,
60
+ },
61
+ 200,
62
+ { "Set-Cookie": cookieHeader }
63
+ );
64
+ } catch (e) {
65
+ return json({ error: "Invalid request body" }, 400);
66
+ }
67
+ }
68
+
69
+ // POST /api/auth/logout - Logout (invalidate refresh token)
70
+ if (path === "/api/auth/logout" && method === "POST") {
71
+ const refreshToken = getRefreshTokenFromCookie(req);
72
+
73
+ if (refreshToken) {
74
+ invalidateSession(refreshToken);
75
+ }
76
+
77
+ // Clear the cookie
78
+ return json(
79
+ { success: true },
80
+ 200,
81
+ { "Set-Cookie": clearRefreshTokenCookie() }
82
+ );
83
+ }
84
+
85
+ // POST /api/auth/refresh - Refresh access token
86
+ if (path === "/api/auth/refresh" && method === "POST") {
87
+ // No users = no valid sessions possible
88
+ if (!UserDB.hasUsers()) {
89
+ return json({ error: "No users exist" }, 401, { "Set-Cookie": clearRefreshTokenCookie() });
90
+ }
91
+
92
+ const refreshToken = getRefreshTokenFromCookie(req);
93
+
94
+ if (!refreshToken) {
95
+ return json({ error: "No refresh token" }, 401);
96
+ }
97
+
98
+ const result = await refreshSession(refreshToken);
99
+
100
+ if (!result) {
101
+ return json(
102
+ { error: "Invalid or expired refresh token" },
103
+ 401,
104
+ { "Set-Cookie": clearRefreshTokenCookie() }
105
+ );
106
+ }
107
+
108
+ // Set new refresh token cookie
109
+ const cookieHeader = createRefreshTokenCookie(result.refreshToken, REFRESH_TOKEN_EXPIRY);
110
+
111
+ return json(
112
+ {
113
+ accessToken: result.accessToken,
114
+ expiresIn: result.expiresIn,
115
+ },
116
+ 200,
117
+ { "Set-Cookie": cookieHeader }
118
+ );
119
+ }
120
+
121
+ // GET /api/auth/me - Get current user
122
+ if (path === "/api/auth/me" && method === "GET") {
123
+ const token = getTokenFromRequest(req);
124
+
125
+ if (!token) {
126
+ return json({ error: "Unauthorized" }, 401);
127
+ }
128
+
129
+ const payload = verifyAccessToken(token);
130
+ if (!payload) {
131
+ return json({ error: "Invalid or expired token" }, 401);
132
+ }
133
+
134
+ const user = UserDB.findById(payload.userId);
135
+ if (!user) {
136
+ return json({ error: "User not found" }, 404);
137
+ }
138
+
139
+ return json({
140
+ user: {
141
+ id: user.id,
142
+ username: user.username,
143
+ email: user.email,
144
+ role: user.role,
145
+ createdAt: user.created_at,
146
+ lastLoginAt: user.last_login_at,
147
+ },
148
+ });
149
+ }
150
+
151
+ // PUT /api/auth/me - Update current user profile
152
+ if (path === "/api/auth/me" && method === "PUT") {
153
+ const token = getTokenFromRequest(req);
154
+
155
+ if (!token) {
156
+ return json({ error: "Unauthorized" }, 401);
157
+ }
158
+
159
+ const payload = verifyAccessToken(token);
160
+ if (!payload) {
161
+ return json({ error: "Invalid or expired token" }, 401);
162
+ }
163
+
164
+ const user = UserDB.findById(payload.userId);
165
+ if (!user) {
166
+ return json({ error: "User not found" }, 404);
167
+ }
168
+
169
+ try {
170
+ const body = await req.json();
171
+ const updates: Parameters<typeof UserDB.update>[1] = {};
172
+
173
+ if (body.email !== undefined) updates.email = body.email;
174
+
175
+ const updated = UserDB.update(user.id, updates);
176
+
177
+ return json({
178
+ user: updated ? {
179
+ id: updated.id,
180
+ username: updated.username,
181
+ email: updated.email,
182
+ role: updated.role,
183
+ createdAt: updated.created_at,
184
+ lastLoginAt: updated.last_login_at,
185
+ } : null,
186
+ });
187
+ } catch (e) {
188
+ return json({ error: "Invalid request body" }, 400);
189
+ }
190
+ }
191
+
192
+ // PUT /api/auth/password - Change password
193
+ if (path === "/api/auth/password" && method === "PUT") {
194
+ const token = getTokenFromRequest(req);
195
+
196
+ if (!token) {
197
+ return json({ error: "Unauthorized" }, 401);
198
+ }
199
+
200
+ const payload = verifyAccessToken(token);
201
+ if (!payload) {
202
+ return json({ error: "Invalid or expired token" }, 401);
203
+ }
204
+
205
+ const user = UserDB.findById(payload.userId);
206
+ if (!user) {
207
+ return json({ error: "User not found" }, 404);
208
+ }
209
+
210
+ try {
211
+ const body = await req.json();
212
+ const { currentPassword, newPassword } = body;
213
+
214
+ if (!currentPassword || !newPassword) {
215
+ return json({ error: "Current and new password are required" }, 400);
216
+ }
217
+
218
+ // Verify current password
219
+ const { verifyPassword } = await import("../auth");
220
+ const isValid = await verifyPassword(currentPassword, user.password_hash);
221
+ if (!isValid) {
222
+ return json({ error: "Current password is incorrect" }, 401);
223
+ }
224
+
225
+ // Validate new password
226
+ const validation = validatePassword(newPassword);
227
+ if (!validation.valid) {
228
+ return json({ error: validation.errors.join(". ") }, 400);
229
+ }
230
+
231
+ // Update password
232
+ const newHash = await hashPassword(newPassword);
233
+ UserDB.update(user.id, { password_hash: newHash });
234
+
235
+ return json({ success: true, message: "Password updated successfully" });
236
+ } catch (e) {
237
+ return json({ error: "Invalid request body" }, 400);
238
+ }
239
+ }
240
+
241
+ return json({ error: "Not found" }, 404);
242
+ }
package/src/server.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { type Server, type Subprocess } from "bun";
2
2
  import { handleApiRequest } from "./routes/api";
3
+ import { handleAuthRequest } from "./routes/auth";
3
4
  import { serveStatic } from "./routes/static";
4
5
  import { join } from "path";
5
6
  import { homedir } from "os";
6
7
  import { mkdirSync, existsSync } from "fs";
7
8
  import { initDatabase, AgentDB, ProviderKeysDB, McpServerDB, type McpServer, type Agent } from "./db";
9
+ import { authMiddleware, type AuthContext } from "./auth/middleware";
8
10
  import { startMcpProcess } from "./mcp-client";
9
11
  import {
10
12
  ensureBinary,
@@ -215,11 +217,23 @@ const server = Bun.serve({
215
217
  const url = new URL(req.url);
216
218
  const path = url.pathname;
217
219
 
218
- // CORS headers
220
+ // Dev mode route logging
221
+ if (process.env.NODE_ENV !== "production" && path.startsWith("/api/")) {
222
+ const params = url.search ? url.search : "";
223
+ console.log(`[${req.method}] ${path}${params}`);
224
+ }
225
+
226
+ // CORS headers - configurable origins
227
+ const origin = req.headers.get("Origin") || "";
228
+ const allowedOrigins = process.env.CORS_ORIGINS?.split(",") || [];
229
+ const isLocalhost = origin.includes("localhost") || origin.includes("127.0.0.1");
230
+ const allowOrigin = allowedOrigins.includes(origin) || isLocalhost || allowedOrigins.length === 0 ? origin || "*" : "";
231
+
219
232
  const corsHeaders = {
220
- "Access-Control-Allow-Origin": "*",
233
+ "Access-Control-Allow-Origin": allowOrigin,
221
234
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
222
- "Access-Control-Allow-Headers": "Content-Type",
235
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
236
+ "Access-Control-Allow-Credentials": "true",
223
237
  };
224
238
 
225
239
  // Handle preflight
@@ -229,8 +243,35 @@ const server = Bun.serve({
229
243
 
230
244
  // API routes
231
245
  if (path.startsWith("/api/")) {
232
- const response = await handleApiRequest(req, path);
233
- // Add CORS headers to response
246
+ // Auth routes handled separately (before middleware)
247
+ if (path.startsWith("/api/auth/")) {
248
+ const response = await handleAuthRequest(req, path);
249
+ Object.entries(corsHeaders).forEach(([key, value]) => {
250
+ response.headers.set(key, value);
251
+ });
252
+ return response;
253
+ }
254
+
255
+ // Health check endpoint (no auth required for Docker health checks)
256
+ if (path === "/api/health") {
257
+ const response = await handleApiRequest(req, path);
258
+ Object.entries(corsHeaders).forEach(([key, value]) => {
259
+ response.headers.set(key, value);
260
+ });
261
+ return response;
262
+ }
263
+
264
+ // Apply auth middleware
265
+ const { response: authResponse, context } = await authMiddleware(req, path);
266
+ if (authResponse) {
267
+ Object.entries(corsHeaders).forEach(([key, value]) => {
268
+ authResponse.headers.set(key, value);
269
+ });
270
+ return authResponse;
271
+ }
272
+
273
+ // Pass auth context to API handler
274
+ const response = await handleApiRequest(req, path, context);
234
275
  Object.entries(corsHeaders).forEach(([key, value]) => {
235
276
  response.headers.set(key, value);
236
277
  });
package/src/web/App.tsx CHANGED
@@ -7,7 +7,7 @@ import type { Agent, Provider, Route, NewAgentForm } from "./types";
7
7
  import { DEFAULT_FEATURES } from "./types";
8
8
 
9
9
  // Context
10
- import { TelemetryProvider } from "./context";
10
+ import { TelemetryProvider, AuthProvider, ProjectProvider, useAuth, useProjects } from "./context";
11
11
 
12
12
  // Hooks
13
13
  import { useAgents, useProviders, useOnboarding } from "./hooks";
@@ -26,13 +26,25 @@ import {
26
26
  TasksPage,
27
27
  McpPage,
28
28
  TelemetryPage,
29
+ LoginPage,
29
30
  } from "./components";
30
31
 
31
- function App() {
32
+ function AppContent() {
33
+ // Auth state
34
+ const { isAuthenticated, isLoading: authLoading, hasUsers, accessToken, checkAuth } = useAuth();
35
+ const { refreshProjects } = useProjects();
36
+
32
37
  // Onboarding state
33
38
  const { isComplete: onboardingComplete, setIsComplete: setOnboardingComplete } = useOnboarding();
34
39
 
35
- // Data hooks
40
+ // Helper to get auth headers
41
+ const getAuthHeaders = (): Record<string, string> => {
42
+ return accessToken ? { Authorization: `Bearer ${accessToken}` } : {};
43
+ };
44
+
45
+ // Data hooks - only fetch when authenticated and onboarding complete
46
+ const shouldFetchData = isAuthenticated && onboardingComplete === true;
47
+
36
48
  const {
37
49
  agents,
38
50
  loading,
@@ -42,13 +54,13 @@ function App() {
42
54
  updateAgent,
43
55
  deleteAgent,
44
56
  toggleAgent,
45
- } = useAgents(onboardingComplete === true);
57
+ } = useAgents(shouldFetchData);
46
58
 
47
59
  const {
48
60
  providers,
49
61
  configuredProviders,
50
62
  fetchProviders,
51
- } = useProviders(onboardingComplete === true);
63
+ } = useProviders(shouldFetchData);
52
64
 
53
65
  // UI state
54
66
  const [showCreate, setShowCreate] = useState(false);
@@ -56,14 +68,15 @@ function App() {
56
68
  const [route, setRoute] = useState<Route>("dashboard");
57
69
  const [startError, setStartError] = useState<string | null>(null);
58
70
  const [taskCount, setTaskCount] = useState(0);
71
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
59
72
 
60
73
  // Fetch task count periodically
61
74
  useEffect(() => {
62
- if (onboardingComplete !== true) return;
75
+ if (!shouldFetchData) return;
63
76
 
64
77
  const fetchTaskCount = async () => {
65
78
  try {
66
- const res = await fetch("/api/tasks?status=pending");
79
+ const res = await fetch("/api/tasks?status=pending", { headers: getAuthHeaders() });
67
80
  if (res.ok) {
68
81
  const data = await res.json();
69
82
  setTaskCount(data.count || 0);
@@ -76,7 +89,7 @@ function App() {
76
89
  fetchTaskCount();
77
90
  const interval = setInterval(fetchTaskCount, 15000);
78
91
  return () => clearInterval(interval);
79
- }, [onboardingComplete]);
92
+ }, [shouldFetchData, accessToken]);
80
93
 
81
94
  // Form state
82
95
  const [newAgent, setNewAgent] = useState<NewAgentForm>({
@@ -125,6 +138,7 @@ function App() {
125
138
  const handleCreateAgent = async () => {
126
139
  if (!newAgent.name) return;
127
140
  await createAgent(newAgent);
141
+ await refreshProjects(); // Refresh project agent counts
128
142
  const defaultProvider = configuredProviders[0];
129
143
  const defaultModel = defaultProvider?.models.find(m => m.recommended)?.value || defaultProvider?.models[0]?.value || "";
130
144
  setNewAgent({
@@ -152,6 +166,7 @@ function App() {
152
166
  setSelectedAgent(null);
153
167
  }
154
168
  await deleteAgent(id);
169
+ await refreshProjects(); // Refresh project agent counts
155
170
  };
156
171
 
157
172
  const handleSelectAgent = (agent: Agent) => {
@@ -168,24 +183,38 @@ function App() {
168
183
  const handleOnboardingComplete = () => {
169
184
  setOnboardingComplete(true);
170
185
  fetchProviders();
186
+ // Refresh auth to pick up new state
187
+ checkAuth();
171
188
  };
172
189
 
190
+ // Show loading while checking auth
191
+ if (authLoading || hasUsers === null) {
192
+ return <LoadingSpinner fullScreen />;
193
+ }
194
+
195
+ // No users exist - show onboarding with account creation
196
+ if (!hasUsers) {
197
+ return <OnboardingWizard onComplete={handleOnboardingComplete} needsAccount={true} />;
198
+ }
199
+
200
+ // Users exist but not authenticated - show login
201
+ if (!isAuthenticated) {
202
+ return <LoginPage />;
203
+ }
204
+
173
205
  // Show loading while checking onboarding
174
206
  if (onboardingComplete === null) {
175
207
  return <LoadingSpinner fullScreen />;
176
208
  }
177
209
 
178
- // Show onboarding if not complete
210
+ // Show onboarding if not complete (but already has account)
179
211
  if (!onboardingComplete) {
180
- return <OnboardingWizard onComplete={handleOnboardingComplete} />;
212
+ return <OnboardingWizard onComplete={handleOnboardingComplete} needsAccount={false} />;
181
213
  }
182
214
 
183
215
  return (
184
216
  <div className="h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex flex-col overflow-hidden">
185
- <Header
186
- onNewAgent={() => setShowCreate(true)}
187
- canCreateAgent={configuredProviders.length > 0}
188
- />
217
+ <Header onMenuClick={() => setMobileMenuOpen(true)} />
189
218
 
190
219
  {startError && (
191
220
  <ErrorBanner message={startError} onDismiss={() => setStartError(null)} />
@@ -197,6 +226,8 @@ function App() {
197
226
  agentCount={agents.length}
198
227
  taskCount={taskCount}
199
228
  onNavigate={handleNavigate}
229
+ isOpen={mobileMenuOpen}
230
+ onClose={() => setMobileMenuOpen(false)}
200
231
  />
201
232
 
202
233
  <main className="flex-1 overflow-hidden flex">
@@ -213,6 +244,8 @@ function App() {
213
244
  onToggleAgent={handleToggleAgent}
214
245
  onDeleteAgent={handleDeleteAgent}
215
246
  onUpdateAgent={updateAgent}
247
+ onNewAgent={() => setShowCreate(true)}
248
+ canCreateAgent={configuredProviders.length > 0}
216
249
  />
217
250
  )}
218
251
 
@@ -254,10 +287,19 @@ function App() {
254
287
  );
255
288
  }
256
289
 
290
+ // Wrapper component that provides all contexts
291
+ function App() {
292
+ return (
293
+ <AuthProvider>
294
+ <ProjectProvider>
295
+ <TelemetryProvider>
296
+ <AppContent />
297
+ </TelemetryProvider>
298
+ </ProjectProvider>
299
+ </AuthProvider>
300
+ );
301
+ }
302
+
257
303
  // Mount the app
258
304
  const root = createRoot(document.getElementById("root")!);
259
- root.render(
260
- <TelemetryProvider>
261
- <App />
262
- </TelemetryProvider>
263
- );
305
+ root.render(<App />);
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
- import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon } from "../common/Icons";
3
- import { useAgentActivity } from "../../context";
2
+ import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon } from "../common/Icons";
3
+ import { useAgentActivity, useProjects } from "../../context";
4
4
  import type { Agent, AgentFeatures } from "../../types";
5
5
 
6
6
  interface AgentCardProps {
@@ -8,22 +8,26 @@ interface AgentCardProps {
8
8
  selected: boolean;
9
9
  onSelect: () => void;
10
10
  onToggle: (e?: React.MouseEvent) => void;
11
- onDelete: (e?: React.MouseEvent) => void;
11
+ showProject?: boolean;
12
12
  }
13
13
 
14
14
  const FEATURE_ICONS: { key: keyof AgentFeatures; icon: React.ComponentType<{ className?: string }>; label: string }[] = [
15
15
  { key: "memory", icon: MemoryIcon, label: "Memory" },
16
16
  { key: "tasks", icon: TasksIcon, label: "Tasks" },
17
+ { key: "files", icon: FilesIcon, label: "Files" },
17
18
  { key: "vision", icon: VisionIcon, label: "Vision" },
18
19
  { key: "operator", icon: OperatorIcon, label: "Operator" },
19
20
  { key: "mcp", icon: McpIcon, label: "MCP" },
20
21
  { key: "realtime", icon: RealtimeIcon, label: "Realtime" },
22
+ { key: "agents", icon: MultiAgentIcon, label: "Multi-Agent" },
21
23
  ];
22
24
 
23
- export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: AgentCardProps) {
25
+ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }: AgentCardProps) {
24
26
  const enabledFeatures = FEATURE_ICONS.filter(f => agent.features?.[f.key]);
25
27
  const mcpServers = agent.mcpServerDetails || [];
26
28
  const { isActive, type } = useAgentActivity(agent.id);
29
+ const { projects } = useProjects();
30
+ const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
27
31
 
28
32
  return (
29
33
  <div
@@ -41,6 +45,12 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
41
45
  {agent.provider} / {agent.model}
42
46
  {agent.port && <span className="text-[#444]"> · :{agent.port}</span>}
43
47
  </p>
48
+ {showProject && project && (
49
+ <p className="text-sm text-[#666] flex items-center gap-1.5 mt-1">
50
+ <span className="w-2 h-2 rounded-full" style={{ backgroundColor: project.color }} />
51
+ {project.name}
52
+ </p>
53
+ )}
44
54
  </div>
45
55
  <StatusBadge status={agent.status} isActive={isActive && agent.status === "running"} activityType={type} />
46
56
  </div>
@@ -84,24 +94,16 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
84
94
  {agent.systemPrompt}
85
95
  </p>
86
96
 
87
- <div className="flex gap-2">
88
- <button
89
- onClick={onToggle}
90
- className={`flex-1 px-3 py-1.5 rounded text-sm font-medium transition ${
91
- agent.status === "running"
92
- ? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
93
- : "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
94
- }`}
95
- >
96
- {agent.status === "running" ? "Stop" : "Start"}
97
- </button>
98
- <button
99
- onClick={onDelete}
100
- className="px-3 py-1.5 rounded text-sm font-medium bg-red-500/20 text-red-400 hover:bg-red-500/30 transition"
101
- >
102
- Delete
103
- </button>
104
- </div>
97
+ <button
98
+ onClick={onToggle}
99
+ className={`w-full px-3 py-1.5 rounded text-sm font-medium transition ${
100
+ agent.status === "running"
101
+ ? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
102
+ : "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
103
+ }`}
104
+ >
105
+ {agent.status === "running" ? "Stop" : "Start"}
106
+ </button>
105
107
  </div>
106
108
  );
107
109
  }