apteva 0.4.4 → 0.4.5
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.y11xqt9m.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/db.ts +93 -19
- package/src/integrations/agentdojo.ts +350 -0
- package/src/openapi.ts +195 -0
- package/src/providers.ts +78 -7
- package/src/routes/api/agent-utils.ts +638 -0
- package/src/routes/api/agents.ts +743 -0
- package/src/routes/api/helpers.ts +12 -0
- package/src/routes/api/integrations.ts +608 -0
- package/src/routes/api/mcp.ts +377 -0
- package/src/routes/api/meta-agent.ts +145 -0
- package/src/routes/api/projects.ts +95 -0
- package/src/routes/api/providers.ts +269 -0
- package/src/routes/api/skills.ts +538 -0
- package/src/routes/api/system.ts +215 -0
- package/src/routes/api/telemetry.ts +142 -0
- package/src/routes/api/users.ts +148 -0
- package/src/routes/api.ts +32 -3477
- package/src/server.ts +1 -1
- package/src/web/components/api/ApiDocsPage.tsx +259 -0
- package/src/web/components/mcp/IntegrationsPanel.tsx +15 -8
- package/src/web/components/mcp/McpPage.tsx +458 -174
- package/src/web/components/settings/SettingsPage.tsx +275 -36
- package/src/web/components/skills/SkillsPage.tsx +330 -1
- package/src/web/components/tasks/TasksPage.tsx +187 -58
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/hooks/useAgents.ts +9 -0
- package/src/web/types.ts +22 -4
- package/dist/App.mbp9atpm.js +0 -227
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { spawn } from "bun";
|
|
2
|
+
import { json } from "./helpers";
|
|
3
|
+
import { McpServerDB, generateId, type McpServer } from "../../db";
|
|
4
|
+
import { getNextPort } from "../../server";
|
|
5
|
+
import {
|
|
6
|
+
startMcpProcess,
|
|
7
|
+
stopMcpProcess,
|
|
8
|
+
initializeMcpServer,
|
|
9
|
+
listMcpTools,
|
|
10
|
+
callMcpTool,
|
|
11
|
+
getMcpProcess,
|
|
12
|
+
getHttpMcpClient,
|
|
13
|
+
} from "../../mcp-client";
|
|
14
|
+
|
|
15
|
+
export async function handleMcpRoutes(
|
|
16
|
+
req: Request,
|
|
17
|
+
path: string,
|
|
18
|
+
method: string,
|
|
19
|
+
): Promise<Response | null> {
|
|
20
|
+
// GET /api/mcp/servers - List MCP servers (optionally filtered by project)
|
|
21
|
+
if (path === "/api/mcp/servers" && method === "GET") {
|
|
22
|
+
const url = new URL(req.url);
|
|
23
|
+
const projectFilter = url.searchParams.get("project"); // "all", "global", or project ID
|
|
24
|
+
const forAgent = url.searchParams.get("forAgent"); // agent's project ID (shows global + project)
|
|
25
|
+
|
|
26
|
+
let servers;
|
|
27
|
+
if (forAgent !== null) {
|
|
28
|
+
// Get servers available for an agent (global + agent's project)
|
|
29
|
+
servers = McpServerDB.findForAgent(forAgent || null);
|
|
30
|
+
} else if (projectFilter === "global") {
|
|
31
|
+
servers = McpServerDB.findGlobal();
|
|
32
|
+
} else if (projectFilter && projectFilter !== "all") {
|
|
33
|
+
servers = McpServerDB.findByProject(projectFilter);
|
|
34
|
+
} else {
|
|
35
|
+
servers = McpServerDB.findAll();
|
|
36
|
+
}
|
|
37
|
+
return json({ servers });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// GET /api/mcp/registry - Search MCP registry for available servers
|
|
41
|
+
if (path === "/api/mcp/registry" && method === "GET") {
|
|
42
|
+
const url = new URL(req.url);
|
|
43
|
+
const search = url.searchParams.get("search") || "";
|
|
44
|
+
const limit = url.searchParams.get("limit") || "20";
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const registryUrl = `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(search)}&limit=${limit}`;
|
|
48
|
+
const res = await fetch(registryUrl);
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
return json({ error: "Failed to fetch registry" }, 500);
|
|
51
|
+
}
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
|
|
54
|
+
// Transform to simpler format - dedupe by name
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
const servers = (data.servers || [])
|
|
57
|
+
.map((item: any) => {
|
|
58
|
+
const s = item.server;
|
|
59
|
+
const pkg = s.packages?.find((p: any) => p.registryType === "npm");
|
|
60
|
+
const remote = s.remotes?.[0];
|
|
61
|
+
|
|
62
|
+
// Extract a short display name from the full name
|
|
63
|
+
const fullName = s.name || "";
|
|
64
|
+
const shortName = fullName.split("/").pop()?.replace(/-mcp$/, "").replace(/^mcp-/, "") || fullName;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id: fullName,
|
|
68
|
+
name: shortName,
|
|
69
|
+
fullName: fullName,
|
|
70
|
+
description: s.description,
|
|
71
|
+
version: s.version,
|
|
72
|
+
repository: s.repository?.url,
|
|
73
|
+
npmPackage: pkg?.identifier || null,
|
|
74
|
+
remoteUrl: remote?.url || null,
|
|
75
|
+
transport: pkg?.transport?.type || (remote ? "http" : "stdio"),
|
|
76
|
+
};
|
|
77
|
+
})
|
|
78
|
+
.filter((s: any) => {
|
|
79
|
+
// Dedupe by fullName
|
|
80
|
+
if (seen.has(s.fullName)) return false;
|
|
81
|
+
seen.add(s.fullName);
|
|
82
|
+
// Only show servers with npm package or remote URL
|
|
83
|
+
return s.npmPackage || s.remoteUrl;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return json({ servers });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
return json({ error: "Failed to search registry" }, 500);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// POST /api/mcp/servers - Create/install a new MCP server
|
|
93
|
+
if (path === "/api/mcp/servers" && method === "POST") {
|
|
94
|
+
try {
|
|
95
|
+
const body = await req.json();
|
|
96
|
+
const { name, type, package: pkg, pip_module, command, args, env, url, headers, source, project_id } = body;
|
|
97
|
+
|
|
98
|
+
if (!name) {
|
|
99
|
+
return json({ error: "Name is required" }, 400);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const server = McpServerDB.create({
|
|
103
|
+
id: generateId(),
|
|
104
|
+
name,
|
|
105
|
+
type: type || "npm",
|
|
106
|
+
package: pkg || null,
|
|
107
|
+
pip_module: pip_module || null,
|
|
108
|
+
command: command || null,
|
|
109
|
+
args: args || null,
|
|
110
|
+
env: env || {},
|
|
111
|
+
url: url || null,
|
|
112
|
+
headers: headers || {},
|
|
113
|
+
source: source || null,
|
|
114
|
+
project_id: project_id || null,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return json({ server }, 201);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
console.error("Create MCP server error:", e);
|
|
120
|
+
return json({ error: "Invalid request body" }, 400);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// GET /api/mcp/servers/:id - Get a specific MCP server
|
|
125
|
+
const mcpServerMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)$/);
|
|
126
|
+
if (mcpServerMatch && method === "GET") {
|
|
127
|
+
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
128
|
+
if (!server) {
|
|
129
|
+
return json({ error: "MCP server not found" }, 404);
|
|
130
|
+
}
|
|
131
|
+
return json({ server });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// PUT /api/mcp/servers/:id - Update an MCP server
|
|
135
|
+
if (mcpServerMatch && method === "PUT") {
|
|
136
|
+
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
137
|
+
if (!server) {
|
|
138
|
+
return json({ error: "MCP server not found" }, 404);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const body = await req.json();
|
|
143
|
+
const updates: Partial<McpServer> = {};
|
|
144
|
+
|
|
145
|
+
if (body.name !== undefined) updates.name = body.name;
|
|
146
|
+
if (body.type !== undefined) updates.type = body.type;
|
|
147
|
+
if (body.package !== undefined) updates.package = body.package;
|
|
148
|
+
if (body.pip_module !== undefined) updates.pip_module = body.pip_module;
|
|
149
|
+
if (body.command !== undefined) updates.command = body.command;
|
|
150
|
+
if (body.args !== undefined) updates.args = body.args;
|
|
151
|
+
if (body.env !== undefined) updates.env = body.env;
|
|
152
|
+
if (body.url !== undefined) updates.url = body.url;
|
|
153
|
+
if (body.headers !== undefined) updates.headers = body.headers;
|
|
154
|
+
if (body.project_id !== undefined) updates.project_id = body.project_id;
|
|
155
|
+
|
|
156
|
+
const updated = McpServerDB.update(mcpServerMatch[1], updates);
|
|
157
|
+
return json({ server: updated });
|
|
158
|
+
} catch (e) {
|
|
159
|
+
return json({ error: "Invalid request body" }, 400);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// DELETE /api/mcp/servers/:id - Delete an MCP server
|
|
164
|
+
if (mcpServerMatch && method === "DELETE") {
|
|
165
|
+
const server = McpServerDB.findById(mcpServerMatch[1]);
|
|
166
|
+
if (!server) {
|
|
167
|
+
return json({ error: "MCP server not found" }, 404);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Stop if running
|
|
171
|
+
if (server.status === "running") {
|
|
172
|
+
// TODO: Stop the server process
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
McpServerDB.delete(mcpServerMatch[1]);
|
|
176
|
+
return json({ success: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// POST /api/mcp/servers/:id/start - Start an MCP server
|
|
180
|
+
const mcpStartMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/start$/);
|
|
181
|
+
if (mcpStartMatch && method === "POST") {
|
|
182
|
+
const server = McpServerDB.findById(mcpStartMatch[1]);
|
|
183
|
+
if (!server) {
|
|
184
|
+
return json({ error: "MCP server not found" }, 404);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (server.status === "running") {
|
|
188
|
+
return json({ error: "MCP server already running" }, 400);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Determine command to run
|
|
192
|
+
// Helper to substitute $ENV_VAR references with actual values
|
|
193
|
+
const substituteEnvVars = (str: string, env: Record<string, string>): string => {
|
|
194
|
+
return str.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, varName) => {
|
|
195
|
+
return env[varName] || '';
|
|
196
|
+
});
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
let cmd: string[];
|
|
200
|
+
const serverEnv = server.env || {};
|
|
201
|
+
|
|
202
|
+
if (server.command) {
|
|
203
|
+
// Custom command - substitute env vars in args
|
|
204
|
+
cmd = server.command.split(" ");
|
|
205
|
+
if (server.args) {
|
|
206
|
+
const substitutedArgs = substituteEnvVars(server.args, serverEnv);
|
|
207
|
+
cmd.push(...substitutedArgs.split(" "));
|
|
208
|
+
}
|
|
209
|
+
} else if (server.type === "pip" && server.package) {
|
|
210
|
+
// Python pip package - install first, then run module
|
|
211
|
+
const pipPackage = server.package;
|
|
212
|
+
const pipModule = server.pip_module || server.package.split("[")[0]; // Default: package name without extras
|
|
213
|
+
|
|
214
|
+
console.log(`Installing pip package: ${pipPackage}...`);
|
|
215
|
+
const installResult = spawn({
|
|
216
|
+
cmd: ["pip", "install", "--quiet", "--break-system-packages", pipPackage],
|
|
217
|
+
env: { ...process.env as Record<string, string>, ...serverEnv },
|
|
218
|
+
stdout: "pipe",
|
|
219
|
+
stderr: "pipe",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Wait for installation to complete
|
|
223
|
+
const exitCode = await installResult.exited;
|
|
224
|
+
if (exitCode !== 0) {
|
|
225
|
+
const stderr = await new Response(installResult.stderr).text();
|
|
226
|
+
return json({ error: `Failed to install pip package: ${stderr || "unknown error"}` }, 500);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Now run the module
|
|
230
|
+
cmd = ["python", "-m", pipModule];
|
|
231
|
+
if (server.args) {
|
|
232
|
+
const substitutedArgs = substituteEnvVars(server.args, serverEnv);
|
|
233
|
+
cmd.push(...substitutedArgs.split(" "));
|
|
234
|
+
}
|
|
235
|
+
} else if (server.package) {
|
|
236
|
+
// npm package - use npx
|
|
237
|
+
cmd = ["npx", "-y", server.package];
|
|
238
|
+
if (server.args) {
|
|
239
|
+
const substitutedArgs = substituteEnvVars(server.args, serverEnv);
|
|
240
|
+
cmd.push(...substitutedArgs.split(" "));
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
return json({ error: "No command or package specified" }, 400);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Get a port for the HTTP proxy
|
|
247
|
+
const port = await getNextPort();
|
|
248
|
+
|
|
249
|
+
console.log(`Starting MCP server ${server.name}...`);
|
|
250
|
+
console.log(` Command: ${cmd.join(" ")}`);
|
|
251
|
+
console.log(` HTTP proxy: http://localhost:${port}/mcp`);
|
|
252
|
+
|
|
253
|
+
// Start the MCP process with stdio pipes + HTTP proxy
|
|
254
|
+
const result = await startMcpProcess(server.id, cmd, server.env || {}, port);
|
|
255
|
+
|
|
256
|
+
if (!result.success) {
|
|
257
|
+
console.error(`Failed to start MCP server: ${result.error}`);
|
|
258
|
+
return json({ error: `Failed to start: ${result.error}` }, 500);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Update status with the HTTP proxy port
|
|
262
|
+
const updated = McpServerDB.setStatus(server.id, "running", port);
|
|
263
|
+
|
|
264
|
+
return json({
|
|
265
|
+
server: updated,
|
|
266
|
+
message: "MCP server started",
|
|
267
|
+
proxyUrl: `http://localhost:${port}/mcp`,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// POST /api/mcp/servers/:id/stop - Stop an MCP server
|
|
272
|
+
const mcpStopMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/stop$/);
|
|
273
|
+
if (mcpStopMatch && method === "POST") {
|
|
274
|
+
const server = McpServerDB.findById(mcpStopMatch[1]);
|
|
275
|
+
if (!server) {
|
|
276
|
+
return json({ error: "MCP server not found" }, 404);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Stop the MCP process
|
|
280
|
+
stopMcpProcess(server.id);
|
|
281
|
+
|
|
282
|
+
const updated = McpServerDB.setStatus(server.id, "stopped");
|
|
283
|
+
return json({ server: updated, message: "MCP server stopped" });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// GET /api/mcp/servers/:id/tools - List tools from an MCP server
|
|
287
|
+
const mcpToolsMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/tools$/);
|
|
288
|
+
if (mcpToolsMatch && method === "GET") {
|
|
289
|
+
const server = McpServerDB.findById(mcpToolsMatch[1]);
|
|
290
|
+
if (!server) {
|
|
291
|
+
return json({ error: "MCP server not found" }, 404);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// HTTP servers use remote HTTP transport
|
|
295
|
+
if (server.type === "http" && server.url) {
|
|
296
|
+
try {
|
|
297
|
+
const httpClient = getHttpMcpClient(server.url, server.headers || {});
|
|
298
|
+
const serverInfo = await httpClient.initialize();
|
|
299
|
+
const tools = await httpClient.listTools();
|
|
300
|
+
|
|
301
|
+
return json({
|
|
302
|
+
serverInfo,
|
|
303
|
+
tools,
|
|
304
|
+
});
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.error(`Failed to list HTTP MCP tools: ${err}`);
|
|
307
|
+
return json({ error: `Failed to communicate with MCP server: ${err}` }, 500);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Stdio servers require a running process
|
|
312
|
+
const mcpProcess = getMcpProcess(server.id);
|
|
313
|
+
if (!mcpProcess) {
|
|
314
|
+
return json({ error: "MCP server is not running" }, 400);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const serverInfo = await initializeMcpServer(server.id);
|
|
319
|
+
const tools = await listMcpTools(server.id);
|
|
320
|
+
|
|
321
|
+
return json({
|
|
322
|
+
serverInfo,
|
|
323
|
+
tools,
|
|
324
|
+
});
|
|
325
|
+
} catch (err) {
|
|
326
|
+
console.error(`Failed to list MCP tools: ${err}`);
|
|
327
|
+
return json({ error: `Failed to communicate with MCP server: ${err}` }, 500);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// POST /api/mcp/servers/:id/tools/:toolName/call - Call a tool on an MCP server
|
|
332
|
+
const mcpToolCallMatch = path.match(/^\/api\/mcp\/servers\/([^/]+)\/tools\/([^/]+)\/call$/);
|
|
333
|
+
if (mcpToolCallMatch && method === "POST") {
|
|
334
|
+
const server = McpServerDB.findById(mcpToolCallMatch[1]);
|
|
335
|
+
if (!server) {
|
|
336
|
+
return json({ error: "MCP server not found" }, 404);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const toolName = decodeURIComponent(mcpToolCallMatch[2]);
|
|
340
|
+
|
|
341
|
+
// HTTP servers use remote HTTP transport
|
|
342
|
+
if (server.type === "http" && server.url) {
|
|
343
|
+
try {
|
|
344
|
+
const body = await req.json();
|
|
345
|
+
const args = body.arguments || {};
|
|
346
|
+
|
|
347
|
+
const httpClient = getHttpMcpClient(server.url, server.headers || {});
|
|
348
|
+
const result = await httpClient.callTool(toolName, args);
|
|
349
|
+
|
|
350
|
+
return json({ result });
|
|
351
|
+
} catch (err) {
|
|
352
|
+
console.error(`Failed to call HTTP MCP tool: ${err}`);
|
|
353
|
+
return json({ error: `Failed to call tool: ${err}` }, 500);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Stdio servers require a running process
|
|
358
|
+
const mcpProcess = getMcpProcess(server.id);
|
|
359
|
+
if (!mcpProcess) {
|
|
360
|
+
return json({ error: "MCP server is not running" }, 400);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const body = await req.json();
|
|
365
|
+
const args = body.arguments || {};
|
|
366
|
+
|
|
367
|
+
const result = await callMcpTool(server.id, toolName, args);
|
|
368
|
+
|
|
369
|
+
return json({ result });
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error(`Failed to call MCP tool: ${err}`);
|
|
372
|
+
return json({ error: `Failed to call tool: ${err}` }, 500);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { json } from "./helpers";
|
|
2
|
+
import { META_AGENT_ENABLED, META_AGENT_ID, toApiAgent, startAgentProcess, setAgentStatus } from "./agent-utils";
|
|
3
|
+
import { AgentDB } from "../../db";
|
|
4
|
+
import { ProviderKeys, Onboarding, PROVIDERS } from "../../providers";
|
|
5
|
+
import { agentProcesses } from "../../server";
|
|
6
|
+
|
|
7
|
+
export async function handleMetaAgentRoutes(
|
|
8
|
+
req: Request,
|
|
9
|
+
path: string,
|
|
10
|
+
method: string,
|
|
11
|
+
): Promise<Response | null> {
|
|
12
|
+
// GET /api/meta-agent/status - Get meta agent status and config
|
|
13
|
+
if (path === "/api/meta-agent/status" && method === "GET") {
|
|
14
|
+
if (!META_AGENT_ENABLED) {
|
|
15
|
+
return json({ enabled: false });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check if onboarding is complete
|
|
19
|
+
if (!Onboarding.isComplete()) {
|
|
20
|
+
return json({ enabled: true, available: false, reason: "onboarding_incomplete" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Get first configured provider
|
|
24
|
+
const configuredProviders = ProviderKeys.getConfiguredProviders();
|
|
25
|
+
if (configuredProviders.length === 0) {
|
|
26
|
+
return json({ enabled: true, available: false, reason: "no_provider" });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const providerId = configuredProviders[0] as keyof typeof PROVIDERS;
|
|
30
|
+
const provider = PROVIDERS[providerId];
|
|
31
|
+
if (!provider) {
|
|
32
|
+
return json({ enabled: true, available: false, reason: "invalid_provider" });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if meta agent exists, create if not
|
|
36
|
+
let metaAgent = AgentDB.findById(META_AGENT_ID);
|
|
37
|
+
if (!metaAgent) {
|
|
38
|
+
// Find a recommended model or use first one
|
|
39
|
+
const defaultModel = provider.models.find((m: any) => m.recommended)?.value || provider.models[0]?.value;
|
|
40
|
+
if (!defaultModel) {
|
|
41
|
+
return json({ enabled: true, available: false, reason: "no_model" });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Create the meta agent
|
|
45
|
+
metaAgent = AgentDB.create({
|
|
46
|
+
id: META_AGENT_ID,
|
|
47
|
+
name: "Apteva Assistant",
|
|
48
|
+
model: defaultModel,
|
|
49
|
+
provider: providerId,
|
|
50
|
+
system_prompt: `You are the Apteva Assistant, a helpful guide for users of the Apteva agent management platform.
|
|
51
|
+
|
|
52
|
+
You can help users with:
|
|
53
|
+
- Creating and configuring AI agents
|
|
54
|
+
- Setting up MCP servers for tool integrations
|
|
55
|
+
- Managing projects and organizing agents
|
|
56
|
+
- Explaining features like Memory, Tasks, Vision, Operator, Files, and Multi-Agent
|
|
57
|
+
- Troubleshooting common issues
|
|
58
|
+
|
|
59
|
+
Be concise, friendly, and helpful. When users ask about creating something, guide them step by step.
|
|
60
|
+
Keep responses short and actionable. Use markdown formatting when helpful.`,
|
|
61
|
+
features: {
|
|
62
|
+
memory: false,
|
|
63
|
+
tasks: false,
|
|
64
|
+
vision: false,
|
|
65
|
+
operator: false,
|
|
66
|
+
mcp: false,
|
|
67
|
+
realtime: false,
|
|
68
|
+
files: false,
|
|
69
|
+
agents: false,
|
|
70
|
+
},
|
|
71
|
+
mcp_servers: [],
|
|
72
|
+
skills: [],
|
|
73
|
+
project_id: null, // Meta agent belongs to no project
|
|
74
|
+
} as any);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Return status
|
|
78
|
+
return json({
|
|
79
|
+
enabled: true,
|
|
80
|
+
available: true,
|
|
81
|
+
agent: {
|
|
82
|
+
id: metaAgent.id,
|
|
83
|
+
name: metaAgent.name,
|
|
84
|
+
status: metaAgent.status,
|
|
85
|
+
port: metaAgent.port,
|
|
86
|
+
provider: metaAgent.provider,
|
|
87
|
+
model: metaAgent.model,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// POST /api/meta-agent/start - Start the meta agent
|
|
93
|
+
if (path === "/api/meta-agent/start" && method === "POST") {
|
|
94
|
+
if (!META_AGENT_ENABLED) {
|
|
95
|
+
return json({ error: "Meta agent is not enabled" }, 400);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const metaAgent = AgentDB.findById(META_AGENT_ID);
|
|
99
|
+
if (!metaAgent) {
|
|
100
|
+
return json({ error: "Meta agent not found" }, 404);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (metaAgent.status === "running") {
|
|
104
|
+
return json({ agent: toApiAgent(metaAgent), message: "Already running" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Start the agent using existing startAgentProcess function
|
|
108
|
+
const result = await startAgentProcess(metaAgent, { silent: true });
|
|
109
|
+
if (!result.success) {
|
|
110
|
+
return json({ error: result.error || "Failed to start meta agent" }, 500);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const updated = AgentDB.findById(META_AGENT_ID);
|
|
114
|
+
return json({ agent: updated ? toApiAgent(updated) : null });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// POST /api/meta-agent/stop - Stop the meta agent
|
|
118
|
+
if (path === "/api/meta-agent/stop" && method === "POST") {
|
|
119
|
+
if (!META_AGENT_ENABLED) {
|
|
120
|
+
return json({ error: "Meta agent is not enabled" }, 400);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const metaAgent = AgentDB.findById(META_AGENT_ID);
|
|
124
|
+
if (!metaAgent) {
|
|
125
|
+
return json({ error: "Meta agent not found" }, 404);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (metaAgent.status === "stopped") {
|
|
129
|
+
return json({ agent: toApiAgent(metaAgent), message: "Already stopped" });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Stop the agent
|
|
133
|
+
const proc = agentProcesses.get(META_AGENT_ID);
|
|
134
|
+
if (proc) {
|
|
135
|
+
proc.proc.kill(); // BUG FIX: was proc.kill() which would fail
|
|
136
|
+
agentProcesses.delete(META_AGENT_ID);
|
|
137
|
+
}
|
|
138
|
+
setAgentStatus(META_AGENT_ID, "stopped", "user_stopped");
|
|
139
|
+
|
|
140
|
+
const updated = AgentDB.findById(META_AGENT_ID);
|
|
141
|
+
return json({ agent: updated ? toApiAgent(updated) : null });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { json } from "./helpers";
|
|
2
|
+
import { AgentDB, ProjectDB, type Project } from "../../db";
|
|
3
|
+
import { toApiAgent, toApiProject } from "./agent-utils";
|
|
4
|
+
|
|
5
|
+
export async function handleProjectRoutes(
|
|
6
|
+
req: Request,
|
|
7
|
+
path: string,
|
|
8
|
+
method: string,
|
|
9
|
+
authContext?: unknown,
|
|
10
|
+
): Promise<Response | null> {
|
|
11
|
+
// GET /api/projects - List all projects
|
|
12
|
+
if (path === "/api/projects" && method === "GET") {
|
|
13
|
+
const projects = ProjectDB.findAll();
|
|
14
|
+
const agentCounts = ProjectDB.getAgentCounts();
|
|
15
|
+
return json({
|
|
16
|
+
projects: projects.map(p => ({
|
|
17
|
+
...toApiProject(p),
|
|
18
|
+
agentCount: agentCounts.get(p.id) || 0,
|
|
19
|
+
})),
|
|
20
|
+
unassignedCount: agentCounts.get(null) || 0,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// POST /api/projects - Create a new project
|
|
25
|
+
if (path === "/api/projects" && method === "POST") {
|
|
26
|
+
try {
|
|
27
|
+
const body = await req.json();
|
|
28
|
+
const { name, description, color } = body;
|
|
29
|
+
|
|
30
|
+
if (!name) {
|
|
31
|
+
return json({ error: "Name is required" }, 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const project = ProjectDB.create({
|
|
35
|
+
name,
|
|
36
|
+
description: description || null,
|
|
37
|
+
color: color || "#6366f1",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return json({ project: toApiProject(project) }, 201);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.error("Create project error:", e);
|
|
43
|
+
return json({ error: "Invalid request body" }, 400);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// GET /api/projects/:id - Get a specific project
|
|
48
|
+
const projectMatch = path.match(/^\/api\/projects\/([^/]+)$/);
|
|
49
|
+
if (projectMatch && method === "GET") {
|
|
50
|
+
const project = ProjectDB.findById(projectMatch[1]);
|
|
51
|
+
if (!project) {
|
|
52
|
+
return json({ error: "Project not found" }, 404);
|
|
53
|
+
}
|
|
54
|
+
const agents = AgentDB.findByProject(project.id);
|
|
55
|
+
return json({
|
|
56
|
+
project: toApiProject(project),
|
|
57
|
+
agents: agents.map(toApiAgent),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// PUT /api/projects/:id - Update a project
|
|
62
|
+
if (projectMatch && method === "PUT") {
|
|
63
|
+
const project = ProjectDB.findById(projectMatch[1]);
|
|
64
|
+
if (!project) {
|
|
65
|
+
return json({ error: "Project not found" }, 404);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const body = await req.json();
|
|
70
|
+
const updates: Partial<Project> = {};
|
|
71
|
+
|
|
72
|
+
if (body.name !== undefined) updates.name = body.name;
|
|
73
|
+
if (body.description !== undefined) updates.description = body.description;
|
|
74
|
+
if (body.color !== undefined) updates.color = body.color;
|
|
75
|
+
|
|
76
|
+
const updated = ProjectDB.update(projectMatch[1], updates);
|
|
77
|
+
return json({ project: updated ? toApiProject(updated) : null });
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return json({ error: "Invalid request body" }, 400);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// DELETE /api/projects/:id - Delete a project
|
|
84
|
+
if (projectMatch && method === "DELETE") {
|
|
85
|
+
const project = ProjectDB.findById(projectMatch[1]);
|
|
86
|
+
if (!project) {
|
|
87
|
+
return json({ error: "Project not found" }, 404);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ProjectDB.delete(projectMatch[1]);
|
|
91
|
+
return json({ success: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|