apteva 0.4.10 → 0.4.11

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/index.html CHANGED
@@ -11,6 +11,6 @@
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
14
- <script type="module" src="/App.csbvbyak.js"></script>
14
+ <script type="module" src="/App.9ph8javh.js"></script>
15
15
  </body>
16
16
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apteva",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
4
4
  "description": "Run AI agents locally. Multi-provider support for Claude, GPT, Gemini, Llama, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,7 +1,7 @@
1
1
  // Built-in MCP server that exposes the Apteva platform API as MCP tools
2
2
  // This allows the meta agent (Apteva Assistant) to control the platform
3
3
 
4
- import { AgentDB, ProjectDB, McpServerDB, TelemetryDB, generateId } from "./db";
4
+ import { AgentDB, ProjectDB, McpServerDB, SkillDB, TelemetryDB, generateId } from "./db";
5
5
  import { getProvidersWithStatus, PROVIDERS } from "./providers";
6
6
  import { startAgentProcess, setAgentStatus, toApiAgent, META_AGENT_ID, agentFetch } from "./routes/api/agent-utils";
7
7
  import { agentProcesses } from "./server";
@@ -153,10 +153,76 @@ const PLATFORM_TOOLS = [
153
153
  },
154
154
  {
155
155
  name: "list_mcp_servers",
156
- description: "List all configured MCP servers (tool integrations).",
156
+ description: "List all configured MCP servers (tool integrations). Optionally filter by project.",
157
157
  inputSchema: {
158
158
  type: "object",
159
- properties: {},
159
+ properties: {
160
+ project_id: { type: "string", description: "Filter by project ID (optional)" },
161
+ },
162
+ },
163
+ },
164
+ {
165
+ name: "get_mcp_server",
166
+ description: "Get detailed information about an MCP server by ID.",
167
+ inputSchema: {
168
+ type: "object",
169
+ properties: {
170
+ server_id: { type: "string", description: "The MCP server ID" },
171
+ },
172
+ required: ["server_id"],
173
+ },
174
+ },
175
+ {
176
+ name: "create_mcp_server",
177
+ description: "Create a new MCP server. For HTTP (remote) servers, provide url and optional headers. For npm package servers, provide a package name.",
178
+ inputSchema: {
179
+ type: "object",
180
+ properties: {
181
+ name: { type: "string", description: "Server display name" },
182
+ type: { type: "string", description: "Server type: 'http' (remote URL), 'npm' (npm package), 'pip' (Python package), 'custom' (custom command)" },
183
+ url: { type: "string", description: "For http type: the remote MCP server URL" },
184
+ headers: { type: "object", description: "For http type: auth headers (e.g. {\"Authorization\": \"Bearer ...\"})" },
185
+ package: { type: "string", description: "For npm/pip type: the package name (e.g. '@modelcontextprotocol/server-filesystem')" },
186
+ command: { type: "string", description: "For custom type: the command to run" },
187
+ args: { type: "string", description: "Command arguments (optional)" },
188
+ project_id: { type: "string", description: "Project ID to scope the server to (optional, null = global)" },
189
+ },
190
+ required: ["name", "type"],
191
+ },
192
+ },
193
+ {
194
+ name: "delete_mcp_server",
195
+ description: "Delete an MCP server. It must be stopped first.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ server_id: { type: "string", description: "The MCP server ID to delete" },
200
+ },
201
+ required: ["server_id"],
202
+ },
203
+ },
204
+ {
205
+ name: "assign_mcp_server_to_agent",
206
+ description: "Assign an MCP server to an agent so the agent can use its tools. The agent must have MCP feature enabled.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ agent_id: { type: "string", description: "The agent ID" },
211
+ server_id: { type: "string", description: "The MCP server ID to assign" },
212
+ },
213
+ required: ["agent_id", "server_id"],
214
+ },
215
+ },
216
+ {
217
+ name: "unassign_mcp_server_from_agent",
218
+ description: "Remove an MCP server from an agent.",
219
+ inputSchema: {
220
+ type: "object",
221
+ properties: {
222
+ agent_id: { type: "string", description: "The agent ID" },
223
+ server_id: { type: "string", description: "The MCP server ID to remove" },
224
+ },
225
+ required: ["agent_id", "server_id"],
160
226
  },
161
227
  },
162
228
  {
@@ -179,6 +245,75 @@ const PLATFORM_TOOLS = [
179
245
  required: ["agent_id", "message"],
180
246
  },
181
247
  },
248
+ // Skills management
249
+ {
250
+ name: "list_skills",
251
+ description: "List all installed skills. Skills are reusable instruction sets that give agents specialized capabilities.",
252
+ inputSchema: {
253
+ type: "object",
254
+ properties: {
255
+ enabled_only: { type: "boolean", description: "Only return enabled skills (optional, default false)" },
256
+ },
257
+ },
258
+ },
259
+ {
260
+ name: "get_skill",
261
+ description: "Get detailed information about a skill by ID, including its full instructions content.",
262
+ inputSchema: {
263
+ type: "object",
264
+ properties: {
265
+ skill_id: { type: "string", description: "The skill ID" },
266
+ },
267
+ required: ["skill_id"],
268
+ },
269
+ },
270
+ {
271
+ name: "toggle_skill",
272
+ description: "Enable or disable a skill.",
273
+ inputSchema: {
274
+ type: "object",
275
+ properties: {
276
+ skill_id: { type: "string", description: "The skill ID" },
277
+ enabled: { type: "boolean", description: "Whether to enable (true) or disable (false) the skill" },
278
+ },
279
+ required: ["skill_id", "enabled"],
280
+ },
281
+ },
282
+ {
283
+ name: "assign_skill_to_agent",
284
+ description: "Assign a skill to an agent so it can use those instructions.",
285
+ inputSchema: {
286
+ type: "object",
287
+ properties: {
288
+ agent_id: { type: "string", description: "The agent ID" },
289
+ skill_id: { type: "string", description: "The skill ID to assign" },
290
+ },
291
+ required: ["agent_id", "skill_id"],
292
+ },
293
+ },
294
+ {
295
+ name: "unassign_skill_from_agent",
296
+ description: "Remove a skill from an agent.",
297
+ inputSchema: {
298
+ type: "object",
299
+ properties: {
300
+ agent_id: { type: "string", description: "The agent ID" },
301
+ skill_id: { type: "string", description: "The skill ID to remove" },
302
+ },
303
+ required: ["agent_id", "skill_id"],
304
+ },
305
+ },
306
+ {
307
+ name: "delete_skill",
308
+ description: "Delete a skill. It will be unassigned from all agents.",
309
+ inputSchema: {
310
+ type: "object",
311
+ properties: {
312
+ skill_id: { type: "string", description: "The skill ID to delete" },
313
+ },
314
+ required: ["skill_id"],
315
+ },
316
+ },
182
317
  ];
183
318
 
184
319
  // Tool execution handlers
@@ -351,24 +486,114 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
351
486
  }
352
487
 
353
488
  case "list_mcp_servers": {
354
- const servers = McpServerDB.findAll();
489
+ const servers = args.project_id
490
+ ? McpServerDB.findByProject(args.project_id)
491
+ : McpServerDB.findAll();
355
492
  const result = servers.map(s => ({
356
493
  id: s.id,
357
494
  name: s.name,
358
495
  type: s.type,
359
496
  status: s.status,
360
497
  url: s.url,
498
+ package: s.package,
499
+ projectId: s.project_id,
361
500
  }));
362
501
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
363
502
  }
364
503
 
504
+ case "get_mcp_server": {
505
+ const server = McpServerDB.findById(args.server_id);
506
+ if (!server) {
507
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
508
+ }
509
+ return { content: [{ type: "text", text: JSON.stringify({
510
+ id: server.id,
511
+ name: server.name,
512
+ type: server.type,
513
+ status: server.status,
514
+ url: server.url,
515
+ package: server.package,
516
+ command: server.command,
517
+ args: server.args,
518
+ port: server.port,
519
+ source: server.source,
520
+ projectId: server.project_id,
521
+ }, null, 2) }] };
522
+ }
523
+
524
+ case "create_mcp_server": {
525
+ const id = generateId();
526
+ const server = McpServerDB.create({
527
+ id,
528
+ name: args.name,
529
+ type: args.type || "http",
530
+ package: args.package || null,
531
+ pip_module: null,
532
+ command: args.command || null,
533
+ args: args.args || null,
534
+ env: {},
535
+ url: args.url || null,
536
+ headers: args.headers || {},
537
+ source: null,
538
+ project_id: args.project_id || null,
539
+ });
540
+ return { content: [{ type: "text", text: `MCP server created: ${JSON.stringify({ id: server.id, name: server.name, type: server.type }, null, 2)}` }] };
541
+ }
542
+
543
+ case "delete_mcp_server": {
544
+ const server = McpServerDB.findById(args.server_id);
545
+ if (!server) {
546
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
547
+ }
548
+ if (server.status === "running") {
549
+ return { content: [{ type: "text", text: "Cannot delete a running MCP server. Stop it first." }], isError: true };
550
+ }
551
+ McpServerDB.delete(args.server_id);
552
+ return { content: [{ type: "text", text: `MCP server deleted: ${server.name} (${server.id})` }] };
553
+ }
554
+
555
+ case "assign_mcp_server_to_agent": {
556
+ const agent = AgentDB.findById(args.agent_id);
557
+ if (!agent) {
558
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
559
+ }
560
+ const server = McpServerDB.findById(args.server_id);
561
+ if (!server) {
562
+ return { content: [{ type: "text", text: `MCP server not found: ${args.server_id}` }], isError: true };
563
+ }
564
+ const mcpServers = agent.mcp_servers || [];
565
+ if (mcpServers.includes(args.server_id)) {
566
+ return { content: [{ type: "text", text: `Server ${server.name} is already assigned to ${agent.name}` }] };
567
+ }
568
+ AgentDB.update(args.agent_id, { mcp_servers: [...mcpServers, args.server_id] });
569
+ // Enable MCP feature if not already
570
+ if (!agent.features.mcp) {
571
+ AgentDB.update(args.agent_id, { features: { ...agent.features, mcp: true } });
572
+ }
573
+ return { content: [{ type: "text", text: `Assigned MCP server "${server.name}" to agent "${agent.name}". Restart the agent for changes to take effect.` }] };
574
+ }
575
+
576
+ case "unassign_mcp_server_from_agent": {
577
+ const agent = AgentDB.findById(args.agent_id);
578
+ if (!agent) {
579
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
580
+ }
581
+ const mcpServers = agent.mcp_servers || [];
582
+ if (!mcpServers.includes(args.server_id)) {
583
+ return { content: [{ type: "text", text: `Server is not assigned to this agent` }] };
584
+ }
585
+ AgentDB.update(args.agent_id, { mcp_servers: mcpServers.filter((id: string) => id !== args.server_id) });
586
+ return { content: [{ type: "text", text: `Removed MCP server from agent "${agent.name}". Restart the agent for changes to take effect.` }] };
587
+ }
588
+
365
589
  case "get_dashboard_stats": {
366
590
  const agentCount = AgentDB.count();
367
591
  const runningCount = AgentDB.countRunning();
368
592
  const projectCount = ProjectDB.count();
369
593
  const providers = getProvidersWithStatus().filter(p => p.type === "llm");
370
594
  const configuredProviders = providers.filter(p => p.hasKey).length;
371
- const mcpServers = McpServerDB.findAll().length;
595
+ const mcpServerCount = McpServerDB.findAll().length;
596
+ const skillCount = SkillDB.count();
372
597
 
373
598
  return {
374
599
  content: [{
@@ -377,7 +602,8 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
377
602
  agents: { total: agentCount - 1, running: runningCount }, // -1 for meta agent
378
603
  projects: projectCount,
379
604
  providers: { total: providers.length, configured: configuredProviders },
380
- mcpServers,
605
+ mcpServers: mcpServerCount,
606
+ skills: skillCount,
381
607
  }, null, 2),
382
608
  }],
383
609
  };
@@ -413,6 +639,92 @@ async function executeTool(name: string, args: Record<string, any>): Promise<{ c
413
639
  }
414
640
  }
415
641
 
642
+ case "list_skills": {
643
+ const skills = args.enabled_only ? SkillDB.findEnabled() : SkillDB.findAll();
644
+ const result = skills.map(s => ({
645
+ id: s.id,
646
+ name: s.name,
647
+ description: s.description,
648
+ version: s.version,
649
+ enabled: s.enabled,
650
+ source: s.source,
651
+ projectId: s.project_id,
652
+ }));
653
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
654
+ }
655
+
656
+ case "get_skill": {
657
+ const skill = SkillDB.findById(args.skill_id);
658
+ if (!skill) {
659
+ return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
660
+ }
661
+ return { content: [{ type: "text", text: JSON.stringify({
662
+ id: skill.id,
663
+ name: skill.name,
664
+ description: skill.description,
665
+ content: skill.content.slice(0, 500) + (skill.content.length > 500 ? "..." : ""),
666
+ version: skill.version,
667
+ enabled: skill.enabled,
668
+ source: skill.source,
669
+ allowedTools: skill.allowed_tools,
670
+ projectId: skill.project_id,
671
+ }, null, 2) }] };
672
+ }
673
+
674
+ case "toggle_skill": {
675
+ const skill = SkillDB.findById(args.skill_id);
676
+ if (!skill) {
677
+ return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
678
+ }
679
+ SkillDB.setEnabled(args.skill_id, args.enabled);
680
+ return { content: [{ type: "text", text: `Skill "${skill.name}" ${args.enabled ? "enabled" : "disabled"}` }] };
681
+ }
682
+
683
+ case "assign_skill_to_agent": {
684
+ const agent = AgentDB.findById(args.agent_id);
685
+ if (!agent) {
686
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
687
+ }
688
+ const skill = SkillDB.findById(args.skill_id);
689
+ if (!skill) {
690
+ return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
691
+ }
692
+ const skills = agent.skills || [];
693
+ if (skills.includes(args.skill_id)) {
694
+ return { content: [{ type: "text", text: `Skill "${skill.name}" is already assigned to "${agent.name}"` }] };
695
+ }
696
+ AgentDB.update(args.agent_id, { skills: [...skills, args.skill_id] });
697
+ return { content: [{ type: "text", text: `Assigned skill "${skill.name}" to agent "${agent.name}". Restart the agent for changes to take effect.` }] };
698
+ }
699
+
700
+ case "unassign_skill_from_agent": {
701
+ const agent = AgentDB.findById(args.agent_id);
702
+ if (!agent) {
703
+ return { content: [{ type: "text", text: `Agent not found: ${args.agent_id}` }], isError: true };
704
+ }
705
+ const skills = agent.skills || [];
706
+ if (!skills.includes(args.skill_id)) {
707
+ return { content: [{ type: "text", text: `Skill is not assigned to this agent` }] };
708
+ }
709
+ AgentDB.update(args.agent_id, { skills: skills.filter((id: string) => id !== args.skill_id) });
710
+ return { content: [{ type: "text", text: `Removed skill from agent "${agent.name}". Restart the agent for changes to take effect.` }] };
711
+ }
712
+
713
+ case "delete_skill": {
714
+ const skill = SkillDB.findById(args.skill_id);
715
+ if (!skill) {
716
+ return { content: [{ type: "text", text: `Skill not found: ${args.skill_id}` }], isError: true };
717
+ }
718
+ // Unassign from all agents first
719
+ const agentsWithSkill = AgentDB.findBySkill(args.skill_id);
720
+ for (const agent of agentsWithSkill) {
721
+ const updated = (agent.skills || []).filter((id: string) => id !== args.skill_id);
722
+ AgentDB.update(agent.id, { skills: updated });
723
+ }
724
+ SkillDB.delete(args.skill_id);
725
+ return { content: [{ type: "text", text: `Skill "${skill.name}" deleted${agentsWithSkill.length > 0 ? ` (unassigned from ${agentsWithSkill.length} agent(s))` : ""}` }] };
726
+ }
727
+
416
728
  default:
417
729
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
418
730
  }
@@ -1,6 +1,6 @@
1
- import React, { useState, useEffect, createContext, useContext, type ReactNode } from "react";
1
+ import React, { useState, useEffect, useMemo, createContext, useContext, type ReactNode } from "react";
2
2
  import { Chat } from "@apteva/apteva-kit";
3
- import { useAuth } from "../../context";
3
+ import { useAuth, useProjects } from "../../context";
4
4
 
5
5
  interface MetaAgentStatus {
6
6
  enabled: boolean;
@@ -129,10 +129,24 @@ export function MetaAgentButton() {
129
129
  // Chat panel component - renders as a right-side drawer
130
130
  export function MetaAgentPanel() {
131
131
  const ctx = useMetaAgent();
132
+ const { currentProjectId, currentProject } = useProjects();
132
133
  if (!ctx?.isAvailable || !ctx.isOpen) return null;
133
134
 
134
135
  const { agent, isRunning, error, isStarting, startAgent, close } = ctx;
135
136
 
137
+ // Build context string for the meta agent
138
+ const chatContext = useMemo(() => {
139
+ const parts: string[] = [];
140
+ if (currentProject) {
141
+ parts.push(`Current project: "${currentProject.name}" (id: ${currentProject.id})`);
142
+ } else if (currentProjectId === "unassigned") {
143
+ parts.push("Viewing: unassigned agents (no project)");
144
+ } else {
145
+ parts.push("Viewing: All Projects");
146
+ }
147
+ return parts.join("\n");
148
+ }, [currentProjectId, currentProject]);
149
+
136
150
  return (
137
151
  <>
138
152
  {/* Backdrop */}
@@ -166,6 +180,7 @@ export function MetaAgentPanel() {
166
180
  placeholder="Ask me anything about Apteva..."
167
181
  variant="terminal"
168
182
  showHeader={false}
183
+ context={chatContext}
169
184
  />
170
185
  ) : (
171
186
  <div className="flex-1 flex flex-col items-center justify-center p-6 text-center">