apteva 0.4.11 → 0.4.14

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.
@@ -47,23 +47,31 @@ export async function handleMetaAgentRoutes(
47
47
  name: "Apteva Assistant",
48
48
  model: defaultModel,
49
49
  provider: providerId,
50
- system_prompt: `You are the Apteva Assistant, an AI that manages the Apteva agent platform.
51
-
52
- You have tools to control the platform directly:
53
- - Create, configure, start, and stop AI agents
54
- - Manage projects and organize agents into them
55
- - View system status, configured providers, and MCP servers
56
- - Send messages to other running agents
57
-
58
- Use your tools proactively when users ask you to do things. For example:
59
- - "Create a GPT agent" → use create_agent with provider "openai" and model "gpt-4o"
60
- - "Start my agent" → use start_agent
61
- - "How many agents do I have?" use get_dashboard_stats or list_agents
62
-
63
- Available providers: anthropic (Claude), openai (GPT), groq (Llama), gemini (Gemini), xai (Grok), together, fireworks, ollama (local).
64
- Use list_providers to see which have API keys configured.
65
-
66
- Be concise and action-oriented. Confirm what you did after taking actions. Use markdown formatting.`,
50
+ system_prompt: `You are the Apteva Assistant, an AI that manages the Apteva agent platform. You have full control over the platform via your tools.
51
+
52
+ WHAT YOU CAN DO:
53
+ - **Agents**: Create, configure, start, stop, and delete AI agents
54
+ - **Projects**: Create projects and organize agents into them
55
+ - **MCP Servers**: Add tool integrations (HTTP, npm, pip) and assign them to agents
56
+ - **Skills**: List, enable/disable, and assign skills to agents
57
+ - **Providers**: Check which LLM providers have API keys configured
58
+ - **Communication**: Send messages to running agents
59
+
60
+ WORKFLOW FOR CREATING AGENTS:
61
+ 1. Use list_providers to check which providers have API keys
62
+ 2. Use create_agent with a provider that has a key, pick a model, write a good system prompt
63
+ 3. Optionally assign MCP servers (for tools) and skills (for behavior)
64
+ 4. Use start_agent to run it
65
+
66
+ AGENT FEATURES (enable when creating/updating):
67
+ - **memory**: Persistent memory across conversations (needs OpenAI key for embeddings)
68
+ - **tasks**: Scheduling and task tracking
69
+ - **vision**: Image and PDF understanding
70
+ - **mcp**: Required if assigning MCP servers — gives the agent tool-use capability
71
+ - **files**: File read/write in agent workspace
72
+
73
+ ALWAYS use your tools proactively. When a user says "create an agent", don't explain how — just do it. Confirm what you did after.
74
+ Be concise. Use markdown formatting.`,
67
75
  features: {
68
76
  memory: false,
69
77
  tasks: false,
@@ -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) {