apteva 0.2.3 → 0.2.6
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/App.0mzj9cz9.js +213 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -6
- package/src/binary.ts +271 -1
- package/src/crypto.ts +53 -0
- package/src/db.ts +492 -3
- package/src/mcp-client.ts +599 -0
- package/src/providers.ts +31 -0
- package/src/routes/api.ts +832 -64
- package/src/server.ts +169 -5
- package/src/web/App.tsx +44 -2
- package/src/web/components/agents/AgentCard.tsx +53 -9
- package/src/web/components/agents/AgentPanel.tsx +381 -0
- package/src/web/components/agents/AgentsView.tsx +27 -10
- package/src/web/components/agents/CreateAgentModal.tsx +7 -7
- package/src/web/components/agents/index.ts +1 -1
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/Modal.tsx +2 -2
- package/src/web/components/common/Select.tsx +1 -1
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/dashboard/Dashboard.tsx +74 -25
- package/src/web/components/index.ts +5 -2
- package/src/web/components/layout/Sidebar.tsx +22 -2
- package/src/web/components/mcp/McpPage.tsx +1144 -0
- package/src/web/components/mcp/index.ts +1 -0
- package/src/web/components/onboarding/OnboardingWizard.tsx +5 -1
- package/src/web/components/settings/SettingsPage.tsx +312 -82
- package/src/web/components/tasks/TasksPage.tsx +129 -0
- package/src/web/components/tasks/index.ts +1 -0
- package/src/web/components/telemetry/TelemetryPage.tsx +359 -0
- package/src/web/context/TelemetryContext.tsx +202 -0
- package/src/web/context/index.ts +2 -0
- package/src/web/hooks/useAgents.ts +23 -0
- package/src/web/styles.css +18 -0
- package/src/web/types.ts +75 -1
- package/dist/App.wfhmfhx7.js +0 -213
- package/src/web/components/agents/ChatPanel.tsx +0 -63
package/src/server.ts
CHANGED
|
@@ -4,8 +4,66 @@ import { serveStatic } from "./routes/static";
|
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { mkdirSync, existsSync } from "fs";
|
|
7
|
-
import { initDatabase, AgentDB, ProviderKeysDB } from "./db";
|
|
8
|
-
import {
|
|
7
|
+
import { initDatabase, AgentDB, ProviderKeysDB, McpServerDB, type McpServer, type Agent } from "./db";
|
|
8
|
+
import { startMcpProcess } from "./mcp-client";
|
|
9
|
+
import {
|
|
10
|
+
ensureBinary,
|
|
11
|
+
getBinaryPath,
|
|
12
|
+
getBinaryStatus,
|
|
13
|
+
getActualBinaryPath,
|
|
14
|
+
initVersionTracking,
|
|
15
|
+
checkForUpdates,
|
|
16
|
+
getInstalledVersion,
|
|
17
|
+
getAptevaVersion,
|
|
18
|
+
downloadLatestBinary,
|
|
19
|
+
} from "./binary";
|
|
20
|
+
|
|
21
|
+
// ============ SSE Telemetry Broadcast ============
|
|
22
|
+
export interface TelemetryEvent {
|
|
23
|
+
id: string;
|
|
24
|
+
agent_id: string;
|
|
25
|
+
timestamp: string;
|
|
26
|
+
category: string;
|
|
27
|
+
type: string;
|
|
28
|
+
level: string;
|
|
29
|
+
trace_id?: string;
|
|
30
|
+
thread_id?: string;
|
|
31
|
+
data?: Record<string, unknown>;
|
|
32
|
+
duration_ms?: number;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class TelemetryBroadcaster {
|
|
37
|
+
private clients: Set<ReadableStreamDefaultController<string>> = new Set();
|
|
38
|
+
|
|
39
|
+
addClient(controller: ReadableStreamDefaultController<string>) {
|
|
40
|
+
this.clients.add(controller);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
removeClient(controller: ReadableStreamDefaultController<string>) {
|
|
44
|
+
this.clients.delete(controller);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
broadcast(events: TelemetryEvent[]) {
|
|
48
|
+
if (this.clients.size === 0) return;
|
|
49
|
+
|
|
50
|
+
const data = `data: ${JSON.stringify(events)}\n\n`;
|
|
51
|
+
for (const controller of this.clients) {
|
|
52
|
+
try {
|
|
53
|
+
controller.enqueue(data);
|
|
54
|
+
} catch {
|
|
55
|
+
// Client disconnected, remove it
|
|
56
|
+
this.clients.delete(controller);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get clientCount() {
|
|
62
|
+
return this.clients.size;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const telemetryBroadcaster = new TelemetryBroadcaster();
|
|
9
67
|
|
|
10
68
|
const PORT = parseInt(process.env.PORT || "4280");
|
|
11
69
|
|
|
@@ -33,8 +91,16 @@ if (await envFile.exists()) {
|
|
|
33
91
|
// Initialize database (silently)
|
|
34
92
|
initDatabase(DATA_DIR);
|
|
35
93
|
|
|
36
|
-
//
|
|
94
|
+
// Initialize version tracking
|
|
95
|
+
initVersionTracking(DATA_DIR);
|
|
96
|
+
|
|
97
|
+
// Get agents and MCP servers that were running before restart (for auto-restart)
|
|
98
|
+
const agentsToRestart = AgentDB.findRunning();
|
|
99
|
+
const mcpServersToRestart = McpServerDB.findRunning();
|
|
100
|
+
|
|
101
|
+
// Reset all agents and MCP servers to stopped on startup (processes don't survive restart)
|
|
37
102
|
AgentDB.resetAllStatus();
|
|
103
|
+
McpServerDB.resetAllStatus();
|
|
38
104
|
|
|
39
105
|
// In-memory store for running agent processes only
|
|
40
106
|
export const agentProcesses: Map<string, Subprocess> = new Map();
|
|
@@ -86,8 +152,9 @@ function link(url: string, text?: string): string {
|
|
|
86
152
|
|
|
87
153
|
|
|
88
154
|
// Startup banner
|
|
155
|
+
const aptevaVersion = getAptevaVersion();
|
|
89
156
|
console.log(`
|
|
90
|
-
${c.orange}${c.bold}>_ apteva${c.reset}
|
|
157
|
+
${c.orange}${c.bold}>_ apteva${c.reset} ${c.gray}v${aptevaVersion}${c.reset}
|
|
91
158
|
${c.gray}Run AI agents locally${c.reset}
|
|
92
159
|
`);
|
|
93
160
|
|
|
@@ -97,9 +164,28 @@ const binaryResult = await ensureBinary(BIN_DIR);
|
|
|
97
164
|
// ensureBinary prints its own status when downloading/failing
|
|
98
165
|
// We only need to print "ready" if binary already existed
|
|
99
166
|
if (binaryResult.success && !binaryResult.downloaded) {
|
|
100
|
-
|
|
167
|
+
const installedVersion = getInstalledVersion();
|
|
168
|
+
console.log(`${c.gray}v${installedVersion || "unknown"} ready${c.reset}`);
|
|
101
169
|
}
|
|
102
170
|
|
|
171
|
+
// Check for updates in background (don't block startup)
|
|
172
|
+
checkForUpdates().then(versions => {
|
|
173
|
+
const updates: string[] = [];
|
|
174
|
+
if (versions.apteva.updateAvailable) {
|
|
175
|
+
updates.push(`apteva: v${versions.apteva.installed} → v${versions.apteva.latest}`);
|
|
176
|
+
}
|
|
177
|
+
if (versions.agent.updateAvailable) {
|
|
178
|
+
updates.push(`agent: v${versions.agent.installed || "?"} → v${versions.agent.latest}`);
|
|
179
|
+
}
|
|
180
|
+
if (updates.length > 0) {
|
|
181
|
+
console.log(`\n ${c.orange}Updates available:${c.reset}`);
|
|
182
|
+
updates.forEach(u => console.log(` ${c.gray}• ${u}${c.reset}`));
|
|
183
|
+
console.log(` ${c.gray}Update from Settings or run: npx apteva@latest${c.reset}\n`);
|
|
184
|
+
}
|
|
185
|
+
}).catch(() => {
|
|
186
|
+
// Silently ignore version check failures
|
|
187
|
+
});
|
|
188
|
+
|
|
103
189
|
// Check database
|
|
104
190
|
process.stdout.write(` ${c.darkGray}Agents${c.reset} `);
|
|
105
191
|
console.log(`${c.gray}${AgentDB.count()} loaded${c.reset}`);
|
|
@@ -151,4 +237,82 @@ console.log(`
|
|
|
151
237
|
${c.darkGray}Click link or Cmd/Ctrl+C to copy${c.reset}
|
|
152
238
|
`);
|
|
153
239
|
|
|
240
|
+
// Auto-restart agents and MCP servers that were running before restart
|
|
241
|
+
const hasRestarts = agentsToRestart.length > 0 || mcpServersToRestart.length > 0;
|
|
242
|
+
|
|
243
|
+
if (hasRestarts) {
|
|
244
|
+
// Restart in background to not block startup
|
|
245
|
+
(async () => {
|
|
246
|
+
// Import startAgentProcess dynamically to avoid circular dependency
|
|
247
|
+
const { startAgentProcess } = await import("./routes/api");
|
|
248
|
+
|
|
249
|
+
// Restart MCP servers first (agents may depend on them)
|
|
250
|
+
if (mcpServersToRestart.length > 0) {
|
|
251
|
+
console.log(` ${c.darkGray}MCP${c.reset} ${c.gray}Restarting ${mcpServersToRestart.length} server(s)...${c.reset}`);
|
|
252
|
+
|
|
253
|
+
for (const server of mcpServersToRestart) {
|
|
254
|
+
try {
|
|
255
|
+
// Helper to substitute $ENV_VAR references with actual values
|
|
256
|
+
const substituteEnvVars = (str: string, env: Record<string, string>): string => {
|
|
257
|
+
return str.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, varName) => {
|
|
258
|
+
return env[varName] || '';
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
let cmd: string[];
|
|
263
|
+
const serverEnv = server.env || {};
|
|
264
|
+
|
|
265
|
+
if (server.command) {
|
|
266
|
+
cmd = server.command.split(" ");
|
|
267
|
+
if (server.args) {
|
|
268
|
+
const substitutedArgs = substituteEnvVars(server.args, serverEnv);
|
|
269
|
+
cmd.push(...substitutedArgs.split(" "));
|
|
270
|
+
}
|
|
271
|
+
} else if (server.package) {
|
|
272
|
+
cmd = ["npx", "-y", server.package];
|
|
273
|
+
if (server.args) {
|
|
274
|
+
const substitutedArgs = substituteEnvVars(server.args, serverEnv);
|
|
275
|
+
cmd.push(...substitutedArgs.split(" "));
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
console.log(` ${c.gray} ✗ ${server.name}: no command or package${c.reset}`);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const port = getNextPort();
|
|
283
|
+
const result = await startMcpProcess(server.id, cmd, serverEnv, port);
|
|
284
|
+
|
|
285
|
+
if (result.success) {
|
|
286
|
+
McpServerDB.setStatus(server.id, "running", port);
|
|
287
|
+
console.log(` ${c.gray} ✓ ${server.name} on :${port}${c.reset}`);
|
|
288
|
+
} else {
|
|
289
|
+
console.log(` ${c.gray} ✗ ${server.name}: ${result.error}${c.reset}`);
|
|
290
|
+
}
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.log(` ${c.gray} ✗ ${server.name}: ${err}${c.reset}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Then restart agents
|
|
298
|
+
if (agentsToRestart.length > 0) {
|
|
299
|
+
console.log(` ${c.darkGray}Agents${c.reset} ${c.gray}Restarting ${agentsToRestart.length} agent(s)...${c.reset}`);
|
|
300
|
+
|
|
301
|
+
for (const agent of agentsToRestart) {
|
|
302
|
+
try {
|
|
303
|
+
const result = await startAgentProcess(agent, { silent: true });
|
|
304
|
+
|
|
305
|
+
if (result.success) {
|
|
306
|
+
console.log(` ${c.gray} ✓ ${agent.name} on :${result.port}${c.reset}`);
|
|
307
|
+
} else {
|
|
308
|
+
console.log(` ${c.gray} ✗ ${agent.name}: ${result.error}${c.reset}`);
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.log(` ${c.gray} ✗ ${agent.name}: ${err}${c.reset}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
})();
|
|
316
|
+
}
|
|
317
|
+
|
|
154
318
|
// Note: Don't use "export default server" - it causes Bun to print "Started server" message
|
package/src/web/App.tsx
CHANGED
|
@@ -6,6 +6,9 @@ import "@apteva/apteva-kit/styles.css";
|
|
|
6
6
|
import type { Agent, Provider, Route, NewAgentForm } from "./types";
|
|
7
7
|
import { DEFAULT_FEATURES } from "./types";
|
|
8
8
|
|
|
9
|
+
// Context
|
|
10
|
+
import { TelemetryProvider } from "./context";
|
|
11
|
+
|
|
9
12
|
// Hooks
|
|
10
13
|
import { useAgents, useProviders, useOnboarding } from "./hooks";
|
|
11
14
|
|
|
@@ -20,6 +23,9 @@ import {
|
|
|
20
23
|
CreateAgentModal,
|
|
21
24
|
AgentsView,
|
|
22
25
|
Dashboard,
|
|
26
|
+
TasksPage,
|
|
27
|
+
McpPage,
|
|
28
|
+
TelemetryPage,
|
|
23
29
|
} from "./components";
|
|
24
30
|
|
|
25
31
|
function App() {
|
|
@@ -33,6 +39,7 @@ function App() {
|
|
|
33
39
|
runningCount,
|
|
34
40
|
fetchAgents,
|
|
35
41
|
createAgent,
|
|
42
|
+
updateAgent,
|
|
36
43
|
deleteAgent,
|
|
37
44
|
toggleAgent,
|
|
38
45
|
} = useAgents(onboardingComplete === true);
|
|
@@ -48,6 +55,28 @@ function App() {
|
|
|
48
55
|
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
|
|
49
56
|
const [route, setRoute] = useState<Route>("dashboard");
|
|
50
57
|
const [startError, setStartError] = useState<string | null>(null);
|
|
58
|
+
const [taskCount, setTaskCount] = useState(0);
|
|
59
|
+
|
|
60
|
+
// Fetch task count periodically
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (onboardingComplete !== true) return;
|
|
63
|
+
|
|
64
|
+
const fetchTaskCount = async () => {
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch("/api/tasks?status=pending");
|
|
67
|
+
if (res.ok) {
|
|
68
|
+
const data = await res.json();
|
|
69
|
+
setTaskCount(data.count || 0);
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Ignore errors
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
fetchTaskCount();
|
|
77
|
+
const interval = setInterval(fetchTaskCount, 15000);
|
|
78
|
+
return () => clearInterval(interval);
|
|
79
|
+
}, [onboardingComplete]);
|
|
51
80
|
|
|
52
81
|
// Form state
|
|
53
82
|
const [newAgent, setNewAgent] = useState<NewAgentForm>({
|
|
@@ -152,7 +181,7 @@ function App() {
|
|
|
152
181
|
}
|
|
153
182
|
|
|
154
183
|
return (
|
|
155
|
-
<div className="
|
|
184
|
+
<div className="h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex flex-col overflow-hidden">
|
|
156
185
|
<Header
|
|
157
186
|
onNewAgent={() => setShowCreate(true)}
|
|
158
187
|
canCreateAgent={configuredProviders.length > 0}
|
|
@@ -166,6 +195,7 @@ function App() {
|
|
|
166
195
|
<Sidebar
|
|
167
196
|
route={route}
|
|
168
197
|
agentCount={agents.length}
|
|
198
|
+
taskCount={taskCount}
|
|
169
199
|
onNavigate={handleNavigate}
|
|
170
200
|
/>
|
|
171
201
|
|
|
@@ -177,10 +207,12 @@ function App() {
|
|
|
177
207
|
agents={agents}
|
|
178
208
|
loading={loading}
|
|
179
209
|
selectedAgent={selectedAgent}
|
|
210
|
+
providers={providers}
|
|
180
211
|
onSelectAgent={handleSelectAgent}
|
|
181
212
|
onCloseAgent={() => setSelectedAgent(null)}
|
|
182
213
|
onToggleAgent={handleToggleAgent}
|
|
183
214
|
onDeleteAgent={handleDeleteAgent}
|
|
215
|
+
onUpdateAgent={updateAgent}
|
|
184
216
|
/>
|
|
185
217
|
)}
|
|
186
218
|
|
|
@@ -194,6 +226,12 @@ function App() {
|
|
|
194
226
|
onSelectAgent={handleSelectAgent}
|
|
195
227
|
/>
|
|
196
228
|
)}
|
|
229
|
+
|
|
230
|
+
{route === "tasks" && <TasksPage />}
|
|
231
|
+
|
|
232
|
+
{route === "mcp" && <McpPage />}
|
|
233
|
+
|
|
234
|
+
{route === "telemetry" && <TelemetryPage />}
|
|
197
235
|
</main>
|
|
198
236
|
</div>
|
|
199
237
|
|
|
@@ -218,4 +256,8 @@ function App() {
|
|
|
218
256
|
|
|
219
257
|
// Mount the app
|
|
220
258
|
const root = createRoot(document.getElementById("root")!);
|
|
221
|
-
root.render(
|
|
259
|
+
root.render(
|
|
260
|
+
<TelemetryProvider>
|
|
261
|
+
<App />
|
|
262
|
+
</TelemetryProvider>
|
|
263
|
+
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon } from "../common/Icons";
|
|
3
|
+
import { useAgentActivity } from "../../context";
|
|
3
4
|
import type { Agent, AgentFeatures } from "../../types";
|
|
4
5
|
|
|
5
6
|
interface AgentCardProps {
|
|
@@ -21,6 +22,8 @@ const FEATURE_ICONS: { key: keyof AgentFeatures; icon: React.ComponentType<{ cla
|
|
|
21
22
|
|
|
22
23
|
export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: AgentCardProps) {
|
|
23
24
|
const enabledFeatures = FEATURE_ICONS.filter(f => agent.features?.[f.key]);
|
|
25
|
+
const mcpServers = agent.mcpServerDetails || [];
|
|
26
|
+
const { isActive, category, type } = useAgentActivity(agent.id);
|
|
24
27
|
|
|
25
28
|
return (
|
|
26
29
|
<div
|
|
@@ -32,14 +35,27 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
|
|
|
32
35
|
}`}
|
|
33
36
|
>
|
|
34
37
|
<div className="flex items-start justify-between mb-3">
|
|
35
|
-
<div>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
<div className="flex items-center gap-2">
|
|
39
|
+
{/* Activity indicator */}
|
|
40
|
+
{agent.status === "running" && (
|
|
41
|
+
<span
|
|
42
|
+
className={`w-2 h-2 rounded-full transition-all ${
|
|
43
|
+
isActive
|
|
44
|
+
? "bg-green-400 animate-pulse"
|
|
45
|
+
: "bg-[#333]"
|
|
46
|
+
}`}
|
|
47
|
+
title={isActive ? `${category}: ${type}` : "Idle"}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
<div>
|
|
51
|
+
<h3 className="font-semibold text-lg">{agent.name}</h3>
|
|
52
|
+
<p className="text-sm text-[#666]">
|
|
53
|
+
{agent.provider} / {agent.model}
|
|
54
|
+
{agent.port && <span className="text-[#444]"> · :{agent.port}</span>}
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
41
57
|
</div>
|
|
42
|
-
<StatusBadge status={agent.status} />
|
|
58
|
+
<StatusBadge status={agent.status} isActive={isActive && agent.status === "running"} activityType={type} />
|
|
43
59
|
</div>
|
|
44
60
|
|
|
45
61
|
{enabledFeatures.length > 0 && (
|
|
@@ -47,7 +63,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
|
|
|
47
63
|
{enabledFeatures.map(({ key, icon: Icon, label }) => (
|
|
48
64
|
<span
|
|
49
65
|
key={key}
|
|
50
|
-
className="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-[#
|
|
66
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-[#f97316]/10 text-[#f97316]/70 text-xs"
|
|
51
67
|
title={label}
|
|
52
68
|
>
|
|
53
69
|
<Icon className="w-3 h-3" />
|
|
@@ -57,6 +73,26 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
|
|
|
57
73
|
</div>
|
|
58
74
|
)}
|
|
59
75
|
|
|
76
|
+
{/* MCP Servers */}
|
|
77
|
+
{mcpServers.length > 0 && (
|
|
78
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
79
|
+
{mcpServers.map((server) => (
|
|
80
|
+
<span
|
|
81
|
+
key={server.id}
|
|
82
|
+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs ${
|
|
83
|
+
server.status === "running"
|
|
84
|
+
? "bg-green-500/10 text-green-400"
|
|
85
|
+
: "bg-[#222] text-[#666]"
|
|
86
|
+
}`}
|
|
87
|
+
title={`MCP: ${server.name} (${server.status})`}
|
|
88
|
+
>
|
|
89
|
+
<McpIcon className="w-3 h-3" />
|
|
90
|
+
{server.name}
|
|
91
|
+
</span>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
60
96
|
<p className="text-sm text-[#666] line-clamp-2 mb-4">
|
|
61
97
|
{agent.systemPrompt}
|
|
62
98
|
</p>
|
|
@@ -83,7 +119,15 @@ export function AgentCard({ agent, selected, onSelect, onToggle, onDelete }: Age
|
|
|
83
119
|
);
|
|
84
120
|
}
|
|
85
121
|
|
|
86
|
-
function StatusBadge({ status }: { status: Agent["status"] }) {
|
|
122
|
+
function StatusBadge({ status, isActive, activityType }: { status: Agent["status"]; isActive?: boolean; activityType?: string }) {
|
|
123
|
+
if (status === "running" && isActive && activityType) {
|
|
124
|
+
return (
|
|
125
|
+
<span className="px-2 py-1 rounded text-xs font-medium bg-green-500/20 text-green-400 animate-pulse">
|
|
126
|
+
{activityType}
|
|
127
|
+
</span>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
87
131
|
return (
|
|
88
132
|
<span
|
|
89
133
|
className={`px-2 py-1 rounded text-xs font-medium ${
|