apteva 0.4.12 → 0.4.15

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.
@@ -243,8 +243,8 @@ export async function handleMcpRoutes(
243
243
  return json({ error: "No command or package specified" }, 400);
244
244
  }
245
245
 
246
- // Get a port for the HTTP proxy
247
- const port = await getNextPort();
246
+ // Use permanently assigned port from DB, fallback to dynamic
247
+ const port = server.port || await getNextPort();
248
248
 
249
249
  console.log(`Starting MCP server ${server.name}...`);
250
250
  console.log(` Command: ${cmd.join(" ")}`);
@@ -126,8 +126,17 @@ export async function handleSystemRoutes(
126
126
  if (path === "/api/tasks" && method === "GET") {
127
127
  const url = new URL(req.url);
128
128
  const status = url.searchParams.get("status") || "all";
129
+ const projectId = url.searchParams.get("project_id");
130
+
131
+ let runningAgents = AgentDB.findAll().filter(a => a.status === "running" && a.port);
132
+
133
+ // Filter agents by project if requested
134
+ if (projectId === "unassigned") {
135
+ runningAgents = runningAgents.filter(a => !a.project_id);
136
+ } else if (projectId) {
137
+ runningAgents = runningAgents.filter(a => a.project_id === projectId);
138
+ }
129
139
 
130
- const runningAgents = AgentDB.findAll().filter(a => a.status === "running" && a.port);
131
140
  const allTasks: any[] = [];
132
141
 
133
142
  for (const agent of runningAgents) {
@@ -0,0 +1,148 @@
1
+ import { json } from "./helpers";
2
+ import { TestCaseDB, TestRunDB } from "../../db-tests";
3
+ import { AgentDB } from "../../db";
4
+ import { runTest, runAll } from "../../test-runner";
5
+
6
+ export async function handleTestRoutes(
7
+ req: Request,
8
+ path: string,
9
+ method: string,
10
+ ): Promise<Response | null> {
11
+ // GET /api/tests - List test cases
12
+ if (path === "/api/tests" && method === "GET") {
13
+ const url = new URL(req.url);
14
+ const projectId = url.searchParams.get("project_id") || undefined;
15
+ const tests = TestCaseDB.findAll(projectId);
16
+
17
+ // Enrich with agent name and latest run
18
+ const enriched = tests.map(tc => {
19
+ const agent = tc.agent_id ? AgentDB.findById(tc.agent_id) : null;
20
+ const lastRun = TestRunDB.getLatestByTestCase(tc.id);
21
+ return {
22
+ ...tc,
23
+ agent_name: agent?.name || null,
24
+ agent_status: agent?.status || null,
25
+ last_run: lastRun ? {
26
+ id: lastRun.id,
27
+ status: lastRun.status,
28
+ score: lastRun.score,
29
+ duration_ms: lastRun.duration_ms,
30
+ judge_reasoning: lastRun.judge_reasoning,
31
+ generated_message: lastRun.generated_message,
32
+ selected_agent_id: lastRun.selected_agent_id,
33
+ selected_agent_name: lastRun.selected_agent_name,
34
+ planner_reasoning: lastRun.planner_reasoning,
35
+ created_at: lastRun.created_at,
36
+ } : null,
37
+ };
38
+ });
39
+
40
+ return json(enriched);
41
+ }
42
+
43
+ // POST /api/tests - Create test case
44
+ if (path === "/api/tests" && method === "POST") {
45
+ const body = await req.json() as any;
46
+
47
+ // Behavior-driven: only name + behavior required
48
+ // Legacy: name + agent_id + input_message + eval_criteria required
49
+ if (!body.name) {
50
+ return json({ error: "Missing required field: name" }, 400);
51
+ }
52
+
53
+ if (!body.behavior && (!body.agent_id || !body.input_message)) {
54
+ return json({ error: "Either 'behavior' or both 'agent_id' and 'input_message' are required" }, 400);
55
+ }
56
+
57
+ // Validate agent if explicitly specified
58
+ if (body.agent_id) {
59
+ const agent = AgentDB.findById(body.agent_id);
60
+ if (!agent) {
61
+ return json({ error: "Agent not found" }, 404);
62
+ }
63
+ }
64
+
65
+ const testCase = TestCaseDB.create({
66
+ name: body.name,
67
+ description: body.description,
68
+ behavior: body.behavior,
69
+ agent_id: body.agent_id || null,
70
+ input_message: body.input_message || null,
71
+ eval_criteria: body.eval_criteria,
72
+ project_id: body.project_id,
73
+ });
74
+
75
+ return json(testCase, 201);
76
+ }
77
+
78
+ // PUT /api/tests/:id - Update test case
79
+ const updateMatch = path.match(/^\/api\/tests\/([^/]+)$/);
80
+ if (updateMatch && method === "PUT") {
81
+ const body = await req.json() as any;
82
+ const updated = TestCaseDB.update(updateMatch[1], body);
83
+ if (!updated) {
84
+ return json({ error: "Test case not found" }, 404);
85
+ }
86
+ return json(updated);
87
+ }
88
+
89
+ // DELETE /api/tests/:id - Delete test case
90
+ if (updateMatch && method === "DELETE") {
91
+ const deleted = TestCaseDB.delete(updateMatch[1]);
92
+ if (!deleted) {
93
+ return json({ error: "Test case not found" }, 404);
94
+ }
95
+ return json({ success: true });
96
+ }
97
+
98
+ // POST /api/tests/:id/run - Run single test
99
+ const runSingleMatch = path.match(/^\/api\/tests\/([^/]+)\/run$/);
100
+ if (runSingleMatch && method === "POST") {
101
+ const testCase = TestCaseDB.findById(runSingleMatch[1]);
102
+ if (!testCase) {
103
+ return json({ error: "Test case not found" }, 404);
104
+ }
105
+
106
+ const result = await runTest(testCase);
107
+ return json(result);
108
+ }
109
+
110
+ // POST /api/tests/run - Run all (or filtered) tests
111
+ if (path === "/api/tests/run" && method === "POST") {
112
+ const body = await req.json().catch(() => ({})) as any;
113
+ const testCaseIds = body.test_case_ids as string[] | undefined;
114
+
115
+ const results = await runAll(testCaseIds);
116
+ const passed = results.filter(r => r.status === "passed").length;
117
+ const failed = results.filter(r => r.status === "failed").length;
118
+ const errors = results.filter(r => r.status === "error").length;
119
+
120
+ return json({
121
+ summary: { total: results.length, passed, failed, errors },
122
+ results,
123
+ });
124
+ }
125
+
126
+ // GET /api/tests/:id/runs - Get run history for a test
127
+ const runsMatch = path.match(/^\/api\/tests\/([^/]+)\/runs$/);
128
+ if (runsMatch && method === "GET") {
129
+ const testCase = TestCaseDB.findById(runsMatch[1]);
130
+ if (!testCase) {
131
+ return json({ error: "Test case not found" }, 404);
132
+ }
133
+ const runs = TestRunDB.findByTestCase(runsMatch[1]);
134
+ return json(runs);
135
+ }
136
+
137
+ // GET /api/tests/runs/:runId - Get single run details
138
+ const runDetailMatch = path.match(/^\/api\/tests\/runs\/([^/]+)$/);
139
+ if (runDetailMatch && method === "GET") {
140
+ const run = TestRunDB.findById(runDetailMatch[1]);
141
+ if (!run) {
142
+ return json({ error: "Test run not found" }, 404);
143
+ }
144
+ return json(run);
145
+ }
146
+
147
+ return null;
148
+ }
package/src/routes/api.ts CHANGED
@@ -10,6 +10,8 @@ import { handleSkillRoutes } from "./api/skills";
10
10
  import { handleIntegrationRoutes } from "./api/integrations";
11
11
  import { handleMetaAgentRoutes } from "./api/meta-agent";
12
12
  import { handleTelemetryRoutes } from "./api/telemetry";
13
+ import { handleTestRoutes } from "./api/tests";
14
+ import { handleApiKeyRoutes } from "./api/api-keys";
13
15
  import { handlePlatformMcpRequest } from "../mcp-platform";
14
16
 
15
17
  // Re-export for backward compatibility (server.ts dynamic import)
@@ -29,6 +31,7 @@ export async function handleApiRequest(
29
31
 
30
32
  return (
31
33
  (await handleSystemRoutes(req, path, method, authContext)) ??
34
+ (await handleApiKeyRoutes(req, path, method, authContext)) ?? // Must be before provider routes to handle /api/keys/personal
32
35
  (await handleProviderRoutes(req, path, method, authContext)) ??
33
36
  (await handleUserRoutes(req, path, method, authContext)) ??
34
37
  (await handleProjectRoutes(req, path, method, authContext)) ??
@@ -38,6 +41,7 @@ export async function handleApiRequest(
38
41
  (await handleIntegrationRoutes(req, path, method)) ??
39
42
  (await handleMetaAgentRoutes(req, path, method)) ??
40
43
  (await handleTelemetryRoutes(req, path, method)) ??
44
+ (await handleTestRoutes(req, path, method)) ??
41
45
  json({ error: "Not found" }, 404)
42
46
  );
43
47
  }
package/src/server.ts CHANGED
@@ -456,7 +456,8 @@ if (hasRestarts) {
456
456
  continue;
457
457
  }
458
458
 
459
- const port = await getNextPort();
459
+ // Use permanently assigned port from DB, fallback to dynamic
460
+ const port = server.port || await getNextPort();
460
461
  const result = await startMcpProcess(server.id, cmd, serverEnv, port);
461
462
 
462
463
  if (result.success) {