apteva 0.2.11 → 0.3.7
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.mvbdnw89.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/index.ts +11 -3
- package/src/auth/middleware.ts +1 -0
- package/src/crypto.ts +52 -7
- package/src/db.ts +437 -14
- package/src/integrations/skillsmp.ts +318 -0
- package/src/providers.ts +21 -0
- package/src/routes/api.ts +836 -16
- package/src/server.ts +58 -7
- package/src/web/App.tsx +24 -8
- package/src/web/components/agents/AgentCard.tsx +36 -11
- package/src/web/components/agents/AgentPanel.tsx +333 -24
- package/src/web/components/agents/AgentsView.tsx +1 -1
- package/src/web/components/agents/CreateAgentModal.tsx +169 -23
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +4 -2
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/McpPage.tsx +602 -19
- package/src/web/components/meta-agent/MetaAgent.tsx +222 -0
- package/src/web/components/settings/SettingsPage.tsx +212 -150
- package/src/web/components/skills/SkillsPage.tsx +871 -0
- package/src/web/context/AuthContext.tsx +5 -0
- package/src/web/context/ProjectContext.tsx +26 -4
- package/src/web/types.ts +48 -3
- package/dist/App.44ge5b89.js +0 -218
package/src/server.ts
CHANGED
|
@@ -127,13 +127,26 @@ async function cleanupOrphanedProcesses(): Promise<void> {
|
|
|
127
127
|
try {
|
|
128
128
|
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(200) });
|
|
129
129
|
if (res.ok) {
|
|
130
|
-
// Orphaned process on this port - shut it down
|
|
130
|
+
// Orphaned process on this port - shut it down gracefully first
|
|
131
131
|
try {
|
|
132
132
|
await fetch(`http://localhost:${port}/shutdown`, { method: "POST", signal: AbortSignal.timeout(500) });
|
|
133
|
-
|
|
133
|
+
await new Promise(r => setTimeout(r, 500)); // Wait for graceful shutdown
|
|
134
134
|
} catch {
|
|
135
|
-
//
|
|
135
|
+
// Graceful shutdown failed
|
|
136
136
|
}
|
|
137
|
+
|
|
138
|
+
// Check if still running and force kill if needed
|
|
139
|
+
try {
|
|
140
|
+
const check = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(200) });
|
|
141
|
+
if (check.ok) {
|
|
142
|
+
// Still running - force kill via lsof
|
|
143
|
+
const { execSync } = await import("child_process");
|
|
144
|
+
execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, { stdio: "ignore" });
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// Not responding anymore - good
|
|
148
|
+
}
|
|
149
|
+
cleaned++;
|
|
137
150
|
}
|
|
138
151
|
} catch {
|
|
139
152
|
// Port not in use - good
|
|
@@ -154,6 +167,44 @@ export const agentProcesses: Map<string, { proc: Subprocess; port: number }> = n
|
|
|
154
167
|
// Track agents currently being started (to prevent race conditions)
|
|
155
168
|
export const agentsStarting: Set<string> = new Set();
|
|
156
169
|
|
|
170
|
+
// Graceful shutdown handler - stop all agents when server exits
|
|
171
|
+
async function shutdownAllAgents() {
|
|
172
|
+
if (agentProcesses.size === 0) return;
|
|
173
|
+
|
|
174
|
+
console.log(`\n Stopping ${agentProcesses.size} running agent(s)...`);
|
|
175
|
+
|
|
176
|
+
for (const [agentId, { proc, port }] of agentProcesses) {
|
|
177
|
+
try {
|
|
178
|
+
// Try graceful shutdown
|
|
179
|
+
await fetch(`http://localhost:${port}/shutdown`, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
signal: AbortSignal.timeout(1000),
|
|
182
|
+
}).catch(() => {});
|
|
183
|
+
|
|
184
|
+
proc.kill();
|
|
185
|
+
AgentDB.setStatus(agentId, "stopped");
|
|
186
|
+
} catch {
|
|
187
|
+
// Ignore errors during shutdown
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
agentProcesses.clear();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Handle process termination signals
|
|
194
|
+
let shuttingDown = false;
|
|
195
|
+
process.on("SIGINT", async () => {
|
|
196
|
+
if (shuttingDown) return;
|
|
197
|
+
shuttingDown = true;
|
|
198
|
+
await shutdownAllAgents();
|
|
199
|
+
process.exit(0);
|
|
200
|
+
});
|
|
201
|
+
process.on("SIGTERM", async () => {
|
|
202
|
+
if (shuttingDown) return;
|
|
203
|
+
shuttingDown = true;
|
|
204
|
+
await shutdownAllAgents();
|
|
205
|
+
process.exit(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
157
208
|
// Binary path - can be overridden via environment variable, or found from npm/downloaded
|
|
158
209
|
export function getBinaryPathForAgent(): string {
|
|
159
210
|
// Environment override takes priority
|
|
@@ -175,8 +226,8 @@ export const BINARY_PATH = getBinaryPathForAgent();
|
|
|
175
226
|
// Export binary status function for API
|
|
176
227
|
export { getBinaryStatus, BIN_DIR };
|
|
177
228
|
|
|
178
|
-
// Base port for
|
|
179
|
-
export let
|
|
229
|
+
// Base port for MCP server proxies (separate range from agents which use 4100-4199)
|
|
230
|
+
export let nextMcpPort = 4200;
|
|
180
231
|
|
|
181
232
|
// Check if a port is available by trying to connect to it
|
|
182
233
|
async function isPortAvailable(port: number): Promise<boolean> {
|
|
@@ -201,11 +252,11 @@ async function isPortAvailable(port: number): Promise<boolean> {
|
|
|
201
252
|
}
|
|
202
253
|
}
|
|
203
254
|
|
|
204
|
-
// Get next available port (checking that nothing is using it)
|
|
255
|
+
// Get next available port for MCP servers (checking that nothing is using it)
|
|
205
256
|
export async function getNextPort(): Promise<number> {
|
|
206
257
|
const maxAttempts = 100; // Prevent infinite loop
|
|
207
258
|
for (let i = 0; i < maxAttempts; i++) {
|
|
208
|
-
const port =
|
|
259
|
+
const port = nextMcpPort++;
|
|
209
260
|
const available = await isPortAvailable(port);
|
|
210
261
|
if (available) {
|
|
211
262
|
return port;
|
package/src/web/App.tsx
CHANGED
|
@@ -25,10 +25,12 @@ import {
|
|
|
25
25
|
Dashboard,
|
|
26
26
|
TasksPage,
|
|
27
27
|
McpPage,
|
|
28
|
+
SkillsPage,
|
|
28
29
|
TelemetryPage,
|
|
29
30
|
LoginPage,
|
|
30
31
|
} from "./components";
|
|
31
32
|
import { ApiDocsPage } from "./components/api/ApiDocsPage";
|
|
33
|
+
import { MetaAgentProvider, MetaAgentPanel } from "./components/meta-agent/MetaAgent";
|
|
32
34
|
|
|
33
35
|
function AppContent() {
|
|
34
36
|
// Auth state
|
|
@@ -63,6 +65,9 @@ function AppContent() {
|
|
|
63
65
|
fetchProviders,
|
|
64
66
|
} = useProviders(shouldFetchData);
|
|
65
67
|
|
|
68
|
+
// Filter to only LLM providers for agent creation
|
|
69
|
+
const llmProviders = configuredProviders.filter(p => p.type === "llm");
|
|
70
|
+
|
|
66
71
|
// UI state
|
|
67
72
|
const [showCreate, setShowCreate] = useState(false);
|
|
68
73
|
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
|
|
@@ -99,12 +104,14 @@ function AppContent() {
|
|
|
99
104
|
provider: "",
|
|
100
105
|
systemPrompt: "You are a helpful assistant.",
|
|
101
106
|
features: { ...DEFAULT_FEATURES },
|
|
107
|
+
mcpServers: [],
|
|
108
|
+
skills: [],
|
|
102
109
|
});
|
|
103
110
|
|
|
104
111
|
// Set default provider when providers are loaded
|
|
105
112
|
useEffect(() => {
|
|
106
|
-
if (
|
|
107
|
-
const defaultProvider =
|
|
113
|
+
if (llmProviders.length > 0 && !newAgent.provider) {
|
|
114
|
+
const defaultProvider = llmProviders[0];
|
|
108
115
|
const defaultModel = defaultProvider.models.find(m => m.recommended)?.value || defaultProvider.models[0]?.value || "";
|
|
109
116
|
setNewAgent(prev => ({
|
|
110
117
|
...prev,
|
|
@@ -112,7 +119,7 @@ function AppContent() {
|
|
|
112
119
|
model: defaultModel,
|
|
113
120
|
}));
|
|
114
121
|
}
|
|
115
|
-
}, [
|
|
122
|
+
}, [llmProviders, newAgent.provider]);
|
|
116
123
|
|
|
117
124
|
// Update selected agent when agents list changes
|
|
118
125
|
useEffect(() => {
|
|
@@ -140,7 +147,7 @@ function AppContent() {
|
|
|
140
147
|
if (!newAgent.name) return;
|
|
141
148
|
await createAgent(newAgent);
|
|
142
149
|
await refreshProjects(); // Refresh project agent counts
|
|
143
|
-
const defaultProvider =
|
|
150
|
+
const defaultProvider = llmProviders[0];
|
|
144
151
|
const defaultModel = defaultProvider?.models.find(m => m.recommended)?.value || defaultProvider?.models[0]?.value || "";
|
|
145
152
|
setNewAgent({
|
|
146
153
|
name: "",
|
|
@@ -148,6 +155,8 @@ function AppContent() {
|
|
|
148
155
|
provider: defaultProvider?.id || "",
|
|
149
156
|
systemPrompt: "You are a helpful assistant.",
|
|
150
157
|
features: { ...DEFAULT_FEATURES },
|
|
158
|
+
mcpServers: [],
|
|
159
|
+
skills: [],
|
|
151
160
|
});
|
|
152
161
|
setShowCreate(false);
|
|
153
162
|
};
|
|
@@ -246,7 +255,7 @@ function AppContent() {
|
|
|
246
255
|
onDeleteAgent={handleDeleteAgent}
|
|
247
256
|
onUpdateAgent={updateAgent}
|
|
248
257
|
onNewAgent={() => setShowCreate(true)}
|
|
249
|
-
canCreateAgent={
|
|
258
|
+
canCreateAgent={llmProviders.length > 0}
|
|
250
259
|
/>
|
|
251
260
|
)}
|
|
252
261
|
|
|
@@ -265,6 +274,8 @@ function AppContent() {
|
|
|
265
274
|
|
|
266
275
|
{route === "mcp" && <McpPage />}
|
|
267
276
|
|
|
277
|
+
{route === "skills" && <SkillsPage />}
|
|
278
|
+
|
|
268
279
|
{route === "telemetry" && <TelemetryPage />}
|
|
269
280
|
|
|
270
281
|
{route === "api" && <ApiDocsPage />}
|
|
@@ -286,6 +297,9 @@ function AppContent() {
|
|
|
286
297
|
}}
|
|
287
298
|
/>
|
|
288
299
|
)}
|
|
300
|
+
|
|
301
|
+
{/* Meta Agent - side drawer */}
|
|
302
|
+
<MetaAgentPanel />
|
|
289
303
|
</div>
|
|
290
304
|
);
|
|
291
305
|
}
|
|
@@ -295,9 +309,11 @@ function App() {
|
|
|
295
309
|
return (
|
|
296
310
|
<AuthProvider>
|
|
297
311
|
<ProjectProvider>
|
|
298
|
-
<
|
|
299
|
-
<
|
|
300
|
-
|
|
312
|
+
<MetaAgentProvider>
|
|
313
|
+
<TelemetryProvider>
|
|
314
|
+
<AppContent />
|
|
315
|
+
</TelemetryProvider>
|
|
316
|
+
</MetaAgentProvider>
|
|
301
317
|
</ProjectProvider>
|
|
302
318
|
</AuthProvider>
|
|
303
319
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon } from "../common/Icons";
|
|
2
|
+
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon } from "../common/Icons";
|
|
3
3
|
import { useAgentActivity, useProjects } from "../../context";
|
|
4
4
|
import type { Agent, AgentFeatures } from "../../types";
|
|
5
5
|
|
|
@@ -25,6 +25,7 @@ const FEATURE_ICONS: { key: keyof AgentFeatures; icon: React.ComponentType<{ cla
|
|
|
25
25
|
export function AgentCard({ agent, selected, onSelect, onToggle, showProject }: AgentCardProps) {
|
|
26
26
|
const enabledFeatures = FEATURE_ICONS.filter(f => agent.features?.[f.key]);
|
|
27
27
|
const mcpServers = agent.mcpServerDetails || [];
|
|
28
|
+
const skills = agent.skillDetails || [];
|
|
28
29
|
const { isActive, type } = useAgentActivity(agent.id);
|
|
29
30
|
const { projects } = useProjects();
|
|
30
31
|
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
@@ -32,7 +33,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
32
33
|
return (
|
|
33
34
|
<div
|
|
34
35
|
onClick={onSelect}
|
|
35
|
-
className={`bg-[#111] rounded p-5 border transition cursor-pointer ${
|
|
36
|
+
className={`bg-[#111] rounded p-5 border transition cursor-pointer flex flex-col h-full ${
|
|
36
37
|
selected
|
|
37
38
|
? 'border-[#f97316]'
|
|
38
39
|
: 'border-[#1a1a1a] hover:border-[#333]'
|
|
@@ -73,30 +74,54 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
73
74
|
{/* MCP Servers */}
|
|
74
75
|
{mcpServers.length > 0 && (
|
|
75
76
|
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
76
|
-
{mcpServers.map((server) =>
|
|
77
|
+
{mcpServers.map((server) => {
|
|
78
|
+
// HTTP/remote servers are always available
|
|
79
|
+
const isAvailable = (server.type === "http" && server.url) || server.status === "running";
|
|
80
|
+
return (
|
|
81
|
+
<span
|
|
82
|
+
key={server.id}
|
|
83
|
+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs ${
|
|
84
|
+
isAvailable
|
|
85
|
+
? "bg-green-500/10 text-green-400"
|
|
86
|
+
: "bg-[#222] text-[#666]"
|
|
87
|
+
}`}
|
|
88
|
+
title={`MCP: ${server.name} (${isAvailable ? "available" : server.status})`}
|
|
89
|
+
>
|
|
90
|
+
<McpIcon className="w-3 h-3" />
|
|
91
|
+
{server.name}
|
|
92
|
+
</span>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
|
|
98
|
+
{/* Skills */}
|
|
99
|
+
{skills.length > 0 && (
|
|
100
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
101
|
+
{skills.map((skill) => (
|
|
77
102
|
<span
|
|
78
|
-
key={
|
|
103
|
+
key={skill.id}
|
|
79
104
|
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs ${
|
|
80
|
-
|
|
81
|
-
? "bg-
|
|
105
|
+
skill.enabled
|
|
106
|
+
? "bg-purple-500/10 text-purple-400"
|
|
82
107
|
: "bg-[#222] text-[#666]"
|
|
83
108
|
}`}
|
|
84
|
-
title={`
|
|
109
|
+
title={`Skill: ${skill.name} v${skill.version}`}
|
|
85
110
|
>
|
|
86
|
-
<
|
|
87
|
-
{
|
|
111
|
+
<SkillsIcon className="w-3 h-3" />
|
|
112
|
+
{skill.name}
|
|
88
113
|
</span>
|
|
89
114
|
))}
|
|
90
115
|
</div>
|
|
91
116
|
)}
|
|
92
117
|
|
|
93
|
-
<p className="text-sm text-[#666] line-clamp-2 mb-4">
|
|
118
|
+
<p className="text-sm text-[#666] line-clamp-2 mb-4 flex-1">
|
|
94
119
|
{agent.systemPrompt}
|
|
95
120
|
</p>
|
|
96
121
|
|
|
97
122
|
<button
|
|
98
123
|
onClick={onToggle}
|
|
99
|
-
className={`w-full px-3 py-1.5 rounded text-sm font-medium transition ${
|
|
124
|
+
className={`w-full px-3 py-1.5 rounded text-sm font-medium transition mt-auto ${
|
|
100
125
|
agent.status === "running"
|
|
101
126
|
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
102
127
|
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|