apteva 0.4.32 → 0.4.41

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.
Files changed (89) hide show
  1. package/dist/ActivityPage.7907h64p.js +3 -0
  2. package/dist/ApiDocsPage.k3jjenpq.js +4 -0
  3. package/dist/App.01nq20st.js +4 -0
  4. package/dist/App.1maqvamf.js +4 -0
  5. package/dist/App.2yjrh32f.js +4 -0
  6. package/dist/App.3qw8nben.js +20 -0
  7. package/dist/App.7fb3e7mp.js +4 -0
  8. package/dist/App.7sy3wq8c.js +4 -0
  9. package/dist/App.apjrmctz.js +57 -0
  10. package/dist/App.av6t2yhe.js +4 -0
  11. package/dist/App.jqj5a094.js +46 -0
  12. package/dist/App.mc7xf85h.js +4 -0
  13. package/dist/App.myxqcj9x.js +4 -0
  14. package/dist/App.nm91r1mp.js +13 -0
  15. package/dist/App.qcknavjz.js +221 -0
  16. package/dist/App.vc7vfhg4.js +4 -0
  17. package/dist/App.z4s9zkw5.js +4 -0
  18. package/dist/ConnectionsPage.z1pw5xe2.js +3 -0
  19. package/dist/McpPage.8vc97z0b.js +3 -0
  20. package/dist/SettingsPage.p61bz8kd.js +3 -0
  21. package/dist/SkillsPage.r9x43g3g.js +3 -0
  22. package/dist/TasksPage.1e0zkye4.js +3 -0
  23. package/dist/TelemetryPage.p9vbe4gf.js +3 -0
  24. package/dist/TestsPage.d4xy504e.js +3 -0
  25. package/dist/ThreadsPage.m016am3x.js +3 -0
  26. package/dist/index.html +1 -1
  27. package/dist/styles.css +1 -1
  28. package/package.json +8 -7
  29. package/src/crypto.ts +4 -3
  30. package/src/db.ts +153 -28
  31. package/src/integrations/agentdojo.ts +94 -12
  32. package/src/integrations/index.ts +7 -0
  33. package/src/mcp-platform.ts +494 -121
  34. package/src/providers.ts +12 -12
  35. package/src/routes/api/agent-utils.ts +59 -46
  36. package/src/routes/api/agents.ts +52 -1
  37. package/src/routes/api/integrations.ts +11 -5
  38. package/src/routes/api/mcp.ts +5 -4
  39. package/src/routes/api/meta-agent.ts +35 -1
  40. package/src/routes/api/projects.ts +3 -3
  41. package/src/routes/api/providers.ts +121 -30
  42. package/src/routes/api/skills.ts +2 -3
  43. package/src/routes/api/system.ts +8 -13
  44. package/src/server.ts +31 -32
  45. package/src/triggers/agentdojo.ts +2 -2
  46. package/src/web/App.tsx +18 -10
  47. package/src/web/components/activity/ActivityPage.tsx +241 -388
  48. package/src/web/components/agents/AgentCard.tsx +5 -13
  49. package/src/web/components/common/Icons.tsx +8 -0
  50. package/src/web/components/common/Select.tsx +4 -3
  51. package/src/web/components/dashboard/Dashboard.tsx +155 -30
  52. package/src/web/components/index.ts +1 -1
  53. package/src/web/components/layout/Sidebar.tsx +7 -1
  54. package/src/web/components/mcp/IntegrationsPanel.tsx +126 -35
  55. package/src/web/components/mcp/McpPage.tsx +10 -1
  56. package/src/web/components/meta-agent/MetaAgent.tsx +4 -2
  57. package/src/web/components/settings/SettingsPage.tsx +133 -48
  58. package/src/web/components/tasks/TasksPage.tsx +48 -16
  59. package/src/web/components/telemetry/TelemetryPage.tsx +184 -0
  60. package/src/web/components/threads/ThreadsPage.tsx +313 -0
  61. package/src/web/context/AuthContext.tsx +3 -3
  62. package/src/web/context/ProjectContext.tsx +3 -3
  63. package/src/web/context/TelemetryContext.tsx +24 -6
  64. package/src/web/context/index.ts +1 -1
  65. package/src/web/styles.css +20 -4
  66. package/src/web/types.ts +4 -3
  67. package/dist/ActivityPage.41nbye4r.js +0 -3
  68. package/dist/ApiDocsPage.4smnt8m3.js +0 -4
  69. package/dist/App.0sbax9et.js +0 -4
  70. package/dist/App.0ws427h8.js +0 -4
  71. package/dist/App.6q6bar8b.js +0 -4
  72. package/dist/App.80301vdb.js +0 -4
  73. package/dist/App.af2wg84v.js +0 -267
  74. package/dist/App.ca1rz1ph.js +0 -4
  75. package/dist/App.ensa6z0r.js +0 -4
  76. package/dist/App.f8g7tych.js +0 -13
  77. package/dist/App.mvtqv6qc.js +0 -20
  78. package/dist/App.ncgc9cxy.js +0 -4
  79. package/dist/App.p0fb1pds.js +0 -4
  80. package/dist/App.pmaq48sj.js +0 -4
  81. package/dist/App.yv87t9m5.js +0 -4
  82. package/dist/App.zjmfm8p6.js +0 -4
  83. package/dist/ConnectionsPage.anb3rv9a.js +0 -3
  84. package/dist/McpPage.y396h6fy.js +0 -3
  85. package/dist/SettingsPage.p1hc60gk.js +0 -3
  86. package/dist/SkillsPage.yj3xdsay.js +0 -3
  87. package/dist/TasksPage.sjv0khtv.js +0 -3
  88. package/dist/TelemetryPage.2qm4w16r.js +0 -3
  89. package/dist/TestsPage.zzs4qfj8.js +0 -3
package/src/providers.ts CHANGED
@@ -12,7 +12,8 @@ export const PROVIDERS = {
12
12
  docsUrl: "https://console.anthropic.com/settings/keys",
13
13
  testEndpoint: "https://api.anthropic.com/v1/messages",
14
14
  models: [
15
- { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5", recommended: true },
15
+ { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", recommended: true },
16
+ { value: "claude-sonnet-4-5", label: "Claude Sonnet 4.5" },
16
17
  { value: "claude-haiku-4-5", label: "Claude Haiku 4.5 (Fast)" },
17
18
  ],
18
19
  },
@@ -152,22 +153,21 @@ export const PROVIDERS = {
152
153
  browserengine: {
153
154
  id: "browserengine",
154
155
  name: "BrowserEngine",
155
- displayName: "BrowserEngine (Self-hosted)",
156
+ displayName: "BrowserEngine",
156
157
  type: "browser" as const,
157
- envVar: "BROWSER_ENGINE_URL",
158
- docsUrl: "",
159
- description: "Your own virtual browser service",
160
- isLocal: true,
158
+ envVar: "BROWSERENGINE_API_KEY",
159
+ docsUrl: "https://browserengine.co",
160
+ description: "Cloud browser automation with stealth browsing and proxies",
161
161
  models: [],
162
162
  },
163
- chrome: {
164
- id: "chrome",
165
- name: "Chrome",
166
- displayName: "Chrome DevTools",
163
+ cdp: {
164
+ id: "cdp",
165
+ name: "CDP",
166
+ displayName: "Direct CDP",
167
167
  type: "browser" as const,
168
- envVar: "CHROME_DEBUG_URL",
168
+ envVar: "CDP_URL",
169
169
  docsUrl: "",
170
- description: "Direct CDP connection to Chrome",
170
+ description: "Connect directly to any browser via Chrome DevTools Protocol",
171
171
  isLocal: true,
172
172
  models: [],
173
173
  },
@@ -3,7 +3,7 @@ import { join } from "path";
3
3
  import { homedir } from "os";
4
4
  import { mkdirSync, existsSync, rmSync } from "fs";
5
5
  import { agentProcesses, agentsStarting, getBinaryPathForAgent, getBinaryStatus, BIN_DIR, telemetryBroadcaster, isShuttingDown, type TelemetryEvent } from "../../server";
6
- import { AgentDB, McpServerDB, SkillDB, TelemetryDB, generateId, getMultiAgentConfig, getOperatorConfig, type Agent, type Project } from "../../db";
6
+ import { AgentDB, McpServerDB, SkillDB, SubscriptionDB, TelemetryDB, generateId, getMultiAgentConfig, getOperatorConfig, type Agent, type Project } from "../../db";
7
7
  import { ProviderKeys, PROVIDERS, type ProviderId } from "../../providers";
8
8
  import { binaryExists } from "../../binary";
9
9
 
@@ -93,32 +93,29 @@ function buildOperatorConfig(features: Agent["features"], projectId: string | nu
93
93
  if (!opConfig.enabled) {
94
94
  return {
95
95
  enabled: false,
96
- virtual_browser: "http://localhost:8098",
97
96
  display_width: 1024,
98
97
  display_height: 768,
99
98
  max_actions_per_turn: 5,
100
99
  };
101
100
  }
102
101
 
103
- const browserProvider = opConfig.browser_provider || "";
102
+ const browserProvider = opConfig.browser_provider || "browserengine";
104
103
  const displayWidth = opConfig.display_width || 1024;
105
104
  const displayHeight = opConfig.display_height || 768;
106
105
  const maxActions = opConfig.max_actions_per_turn || 5;
107
106
 
108
- // Map browser provider IDs to agent binary config
109
107
  const operatorResult: Record<string, unknown> = {
110
108
  enabled: true,
109
+ browser_provider: browserProvider,
111
110
  display_width: displayWidth,
112
111
  display_height: displayHeight,
113
112
  max_actions_per_turn: maxActions,
114
113
  };
115
114
 
115
+ // Only include the active provider's config
116
116
  if (browserProvider === "browserbase") {
117
117
  const raw = ProviderKeys.getDecryptedForProject("browserbase", projectId);
118
- operatorResult.browser_provider = "browserbase";
119
- operatorResult.virtual_browser = "http://localhost:8098"; // fallback
120
118
  if (raw) {
121
- // Parse JSON object {api_key, project_id} or plain string (backwards compat)
122
119
  try {
123
120
  const parsed = JSON.parse(raw);
124
121
  operatorResult.browserbase = { api_key: parsed.api_key || raw, project_id: parsed.project_id || "" };
@@ -128,26 +125,21 @@ function buildOperatorConfig(features: Agent["features"], projectId: string | nu
128
125
  }
129
126
  } else if (browserProvider === "steel") {
130
127
  const apiKey = ProviderKeys.getDecryptedForProject("steel", projectId);
131
- operatorResult.browser_provider = "steel";
132
- operatorResult.virtual_browser = "http://localhost:8098"; // fallback
133
128
  if (apiKey) {
134
129
  operatorResult.steel = { api_key: apiKey, base_url: "https://api.steel.dev" };
135
130
  }
136
- } else if (browserProvider === "chrome") {
137
- const debugUrl = ProviderKeys.getDecryptedForProject("chrome", projectId);
138
- operatorResult.browser_provider = "chrome";
139
- operatorResult.virtual_browser = "http://localhost:8098"; // fallback
140
- if (debugUrl) {
141
- operatorResult.chrome = { debug_url: debugUrl };
131
+ } else if (browserProvider === "cdp") {
132
+ // CDP uses a URL, not an API key — stored as the provider key value
133
+ const cdpUrl = ProviderKeys.getDecryptedForProject("cdp", projectId);
134
+ if (cdpUrl) {
135
+ operatorResult.cdp = { url: cdpUrl };
142
136
  }
143
- } else if (browserProvider === "browserengine") {
144
- const url = ProviderKeys.getDecryptedForProject("browserengine", projectId);
145
- operatorResult.browser_provider = "self";
146
- operatorResult.virtual_browser = url || "http://localhost:8098";
147
137
  } else {
148
- // Default: auto-select first configured browser provider, or fall back to self
149
- operatorResult.browser_provider = "self";
150
- operatorResult.virtual_browser = ProviderKeys.getDecryptedForProject("browserengine", projectId) || "http://localhost:8098";
138
+ // Default: browserengine
139
+ const apiKey = ProviderKeys.getDecryptedForProject("browserengine", projectId);
140
+ if (apiKey) {
141
+ operatorResult.browserengine = { api_key: apiKey, base_url: "https://api.browserengine.co" };
142
+ }
151
143
  }
152
144
 
153
145
  return operatorResult;
@@ -173,8 +165,12 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
173
165
  enabled: boolean;
174
166
  }> = [];
175
167
 
168
+ // Batch load skills and MCP servers (2 queries instead of N+M)
169
+ const skillMap = SkillDB.findByIds(agent.skills || []);
170
+ const mcpMap = McpServerDB.findByIds(agent.mcp_servers || []);
171
+
176
172
  for (const skillId of agent.skills || []) {
177
- const skill = SkillDB.findById(skillId);
173
+ const skill = skillMap.get(skillId);
178
174
  if (!skill || !skill.enabled) continue;
179
175
 
180
176
  skillDefinitions.push({
@@ -190,7 +186,7 @@ export function buildAgentConfig(agent: Agent, providerKey: string) {
190
186
  }
191
187
 
192
188
  for (const id of agent.mcp_servers || []) {
193
- const server = McpServerDB.findById(id);
189
+ const server = mcpMap.get(id);
194
190
  if (!server) continue;
195
191
 
196
192
  if (server.type === "local" && server.status === "running") {
@@ -387,9 +383,8 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
387
383
  }
388
384
 
389
385
  try {
390
- // Push each skill - try PUT first (update), then POST (create) if not found
391
- for (const skill of skills) {
392
- // First try PUT to update existing skill
386
+ // Push all skills in parallel - try PUT first (update), then POST (create) if not found
387
+ await Promise.allSettled(skills.map(async (skill) => {
393
388
  let res = await agentFetch(agentId, port, "/skills", {
394
389
  method: "PUT",
395
390
  headers: { "Content-Type": "application/json" },
@@ -397,7 +392,6 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
397
392
  signal: AbortSignal.timeout(5000),
398
393
  });
399
394
 
400
- // If skill doesn't exist (404), create it with POST
401
395
  if (res.status === 404) {
402
396
  res = await agentFetch(agentId, port, "/skills", {
403
397
  method: "POST",
@@ -411,7 +405,7 @@ export async function pushSkillsToAgent(agentId: string, port: number, skills: A
411
405
  const data = await res.json().catch(() => ({}));
412
406
  console.error(`[pushSkillsToAgent] Failed to push skill ${skill.name}:`, data.error || res.status);
413
407
  }
414
- }
408
+ }));
415
409
 
416
410
  // Enable skills globally via POST /skills/status
417
411
  const statusRes = await agentFetch(agentId, port, "/skills/status", {
@@ -611,6 +605,8 @@ export async function startAgentProcess(
611
605
  console.log(` Pushing configuration...`);
612
606
  }
613
607
  const config = buildAgentConfig(agent, providerKey);
608
+ console.log(`[DEBUG] operator config being pushed:`, JSON.stringify(config.operator, null, 2));
609
+ console.log(`[DEBUG] builtin_tools:`, JSON.stringify(config.operator?.builtin_tools));
614
610
  const configResult = await pushConfigToAgent(agent.id, port, config);
615
611
  if (!configResult.success) {
616
612
  if (!silent) {
@@ -650,24 +646,27 @@ export async function startAgentProcess(
650
646
  }
651
647
 
652
648
  // Transform DB agent to API response format (camelCase for frontend compatibility)
649
+ // Uses batch queries + light MCP loading (no decryption) for performance
653
650
  export function toApiAgent(agent: Agent) {
654
- // Look up MCP server details
651
+ // Batch load MCP servers (light = no decryption) and skills in 2 queries
652
+ const mcpMap = McpServerDB.findByIdsLight(agent.mcp_servers || []);
653
+ const skillMap = SkillDB.findByIds(agent.skills || []);
654
+
655
655
  const mcpServerDetails = (agent.mcp_servers || [])
656
- .map(id => McpServerDB.findById(id))
657
- .filter((s): s is NonNullable<typeof s> => s !== null)
656
+ .map(id => mcpMap.get(id))
657
+ .filter((s): s is NonNullable<typeof s> => !!s)
658
658
  .map(s => ({
659
659
  id: s.id,
660
660
  name: s.name,
661
661
  type: s.type,
662
662
  status: s.status,
663
663
  port: s.port,
664
- url: s.url, // Include URL for HTTP servers
664
+ url: s.url,
665
665
  }));
666
666
 
667
- // Look up skill details
668
667
  const skillDetails = (agent.skills || [])
669
- .map(id => SkillDB.findById(id))
670
- .filter((s): s is NonNullable<typeof s> => s !== null)
668
+ .map(id => skillMap.get(id))
669
+ .filter((s): s is NonNullable<typeof s> => !!s)
671
670
  .map(s => ({
672
671
  id: s.id,
673
672
  name: s.name,
@@ -676,6 +675,11 @@ export function toApiAgent(agent: Agent) {
676
675
  enabled: s.enabled,
677
676
  }));
678
677
 
678
+ // Look up subscriptions
679
+ const subscriptions = SubscriptionDB.findByAgentId(agent.id).map(s => ({
680
+ id: s.id, trigger_slug: s.trigger_slug, enabled: s.enabled,
681
+ }));
682
+
679
683
  return {
680
684
  id: agent.id,
681
685
  name: agent.name,
@@ -685,29 +689,33 @@ export function toApiAgent(agent: Agent) {
685
689
  status: agent.status,
686
690
  port: agent.port,
687
691
  features: agent.features,
688
- mcpServers: agent.mcp_servers, // Keep IDs for backwards compatibility
689
- mcpServerDetails, // Include full details
690
- skills: agent.skills, // Skill IDs
691
- skillDetails, // Include full details
692
+ mcpServers: agent.mcp_servers,
693
+ mcpServerDetails,
694
+ skills: agent.skills,
695
+ skillDetails,
696
+ subscriptions,
692
697
  projectId: agent.project_id,
693
698
  createdAt: agent.created_at,
694
699
  updatedAt: agent.updated_at,
695
700
  };
696
701
  }
697
702
 
698
- // Batch transform: fetch all MCP servers + skills in 2 queries instead of N per agent
703
+ // Batch transform: fetch all MCP servers + skills + subscriptions in 3 queries instead of N per agent
699
704
  export function toApiAgentsBatch(agents: Agent[]) {
700
705
  // Collect all unique IDs
701
706
  const allMcpIds = new Set<string>();
702
707
  const allSkillIds = new Set<string>();
708
+ const allAgentIds: string[] = [];
703
709
  for (const agent of agents) {
710
+ allAgentIds.push(agent.id);
704
711
  for (const id of agent.mcp_servers || []) allMcpIds.add(id);
705
712
  for (const id of agent.skills || []) allSkillIds.add(id);
706
713
  }
707
714
 
708
- // Batch load in 2 queries
709
- const mcpMap = McpServerDB.findByIds([...allMcpIds]);
715
+ // Batch load in 3 queries (Light = no decryption for MCP servers)
716
+ const mcpMap = McpServerDB.findByIdsLight([...allMcpIds]);
710
717
  const skillMap = SkillDB.findByIds([...allSkillIds]);
718
+ const subsMap = SubscriptionDB.findByAgentIds(allAgentIds);
711
719
 
712
720
  return agents.map(agent => {
713
721
  const mcpServerDetails = (agent.mcp_servers || [])
@@ -720,11 +728,15 @@ export function toApiAgentsBatch(agents: Agent[]) {
720
728
  .filter((s): s is NonNullable<typeof s> => !!s)
721
729
  .map(s => ({ id: s.id, name: s.name, description: s.description, version: s.version, enabled: s.enabled }));
722
730
 
731
+ const subscriptions = (subsMap.get(agent.id) || []).map(s => ({
732
+ id: s.id, trigger_slug: s.trigger_slug, enabled: s.enabled,
733
+ }));
734
+
723
735
  return {
724
736
  id: agent.id, name: agent.name, model: agent.model, provider: agent.provider,
725
737
  systemPrompt: agent.system_prompt, status: agent.status, port: agent.port,
726
738
  features: agent.features, mcpServers: agent.mcp_servers, mcpServerDetails,
727
- skills: agent.skills, skillDetails, projectId: agent.project_id,
739
+ skills: agent.skills, skillDetails, subscriptions, projectId: agent.project_id,
728
740
  createdAt: agent.created_at, updatedAt: agent.updated_at,
729
741
  };
730
742
  });
@@ -742,11 +754,12 @@ export function toApiProject(project: Project) {
742
754
  };
743
755
  }
744
756
 
745
- // Helper to fetch from a running agent (with authentication)
746
- export async function fetchFromAgent(agentId: string, port: number, endpoint: string): Promise<any> {
757
+ // Helper to fetch from a running agent (with authentication + timeout)
758
+ export async function fetchFromAgent(agentId: string, port: number, endpoint: string, timeoutMs = 3000): Promise<any> {
747
759
  try {
748
760
  const response = await agentFetch(agentId, port, endpoint, {
749
761
  headers: { "Accept": "application/json" },
762
+ signal: AbortSignal.timeout(timeoutMs),
750
763
  });
751
764
  if (response.ok) {
752
765
  return await response.json();
@@ -62,7 +62,7 @@ export async function handleAgentRoutes(
62
62
  const agent = AgentDB.create({
63
63
  id: generateId(),
64
64
  name,
65
- model: model || "claude-sonnet-4-5",
65
+ model: model || "claude-sonnet-4-6",
66
66
  provider: provider || "anthropic",
67
67
  system_prompt: systemPrompt || "You are a helpful assistant.",
68
68
  features: features || DEFAULT_FEATURES,
@@ -358,6 +358,7 @@ export async function handleAgentRoutes(
358
358
  try {
359
359
  const body = await req.json();
360
360
 
361
+
361
362
  // Proxy to the agent's /chat endpoint with authentication
362
363
  const response = await agentFetch(agent.id, agent.port, "/chat", {
363
364
  method: "POST",
@@ -451,6 +452,56 @@ export async function handleAgentRoutes(
451
452
 
452
453
  // ==================== THREAD & MESSAGE PROXY ====================
453
454
 
455
+ // GET /api/threads - Consolidated threads from all running agents
456
+ if (path === "/api/threads" && method === "GET") {
457
+ const url = new URL(req.url);
458
+ const projectId = url.searchParams.get("project_id");
459
+
460
+ let agents;
461
+ if (projectId === "unassigned") {
462
+ agents = AgentDB.findByProject(null);
463
+ } else if (projectId) {
464
+ agents = AgentDB.findByProject(projectId);
465
+ } else {
466
+ agents = AgentDB.findAll();
467
+ }
468
+
469
+ // Only query running agents (excluding meta agent)
470
+ const runningAgents = agents.filter(a => a.id !== META_AGENT_ID && a.status === "running" && a.port);
471
+
472
+ const results = await Promise.allSettled(
473
+ runningAgents.map(async (agent) => {
474
+ const response = await agentFetch(agent.id, agent.port!, "/threads", {
475
+ method: "GET",
476
+ headers: { "Accept": "application/json" },
477
+ signal: AbortSignal.timeout(3000),
478
+ });
479
+ if (!response.ok) return [];
480
+ const data = await response.json() as any;
481
+ const threads = Array.isArray(data) ? data : (data.threads ?? []);
482
+ return threads.map((t: any) => ({
483
+ ...t,
484
+ agent_id: agent.id,
485
+ agent_name: agent.name,
486
+ }));
487
+ })
488
+ );
489
+
490
+ const allThreads = results
491
+ .filter((r): r is PromiseFulfilledResult<any[]> => r.status === "fulfilled")
492
+ .flatMap(r => r.value)
493
+ .filter((t: any) => !t.parent_id);
494
+
495
+ // Sort by most recent first
496
+ allThreads.sort((a, b) => {
497
+ const ta = a.updated_at || a.created_at || "";
498
+ const tb = b.updated_at || b.created_at || "";
499
+ return tb.localeCompare(ta);
500
+ });
501
+
502
+ return json({ threads: allThreads });
503
+ }
504
+
454
505
  // GET/POST /api/agents/:id/threads
455
506
  const threadsListMatch = path.match(/^\/api\/agents\/([^/]+)\/threads$/);
456
507
  if (threadsListMatch && method === "GET") {
@@ -111,8 +111,11 @@ export async function handleIntegrationRoutes(
111
111
 
112
112
  try {
113
113
  const body = await req.json();
114
- const { appSlug, redirectUrl, credentials, project_id } = body;
115
- const apiKey = ProviderKeys.getDecryptedForProject(providerId, project_id || null);
114
+ const { appSlug, redirectUrl, credentials } = body;
115
+ // Read project_id from query param (frontend sends it there), fall back to body
116
+ const url = new URL(req.url);
117
+ const projectId = url.searchParams.get("project_id") || body.project_id || null;
118
+ const apiKey = ProviderKeys.getDecryptedForProject(providerId, projectId);
116
119
  if (!apiKey) {
117
120
  return json({ error: `${provider.name} API key not configured` }, 401);
118
121
  }
@@ -127,7 +130,8 @@ export async function handleIntegrationRoutes(
127
130
  }
128
131
 
129
132
  // Default redirect URL back to our integrations page
130
- const callbackUrl = redirectUrl || `http://localhost:${process.env.PORT || 4280}/mcp?tab=hosted&connected=${appSlug}`;
133
+ const origin = req.headers.get("origin") || process.env.INSTANCE_URL || `http://localhost:${process.env.PORT || 4280}`;
134
+ const callbackUrl = redirectUrl || `${origin}/mcp?tab=hosted&connected=${appSlug}`;
131
135
 
132
136
  const result = await provider.initiateConnection(apiKey, user.id, appSlug, callbackUrl, credentials);
133
137
  return json(result);
@@ -329,7 +333,8 @@ export async function handleIntegrationRoutes(
329
333
  : mcpUrl;
330
334
 
331
335
  // Check if already exists (match by config ID in URL)
332
- const existing = McpServerDB.findAll().find(
336
+ // Use Light variant to skip expensive decryption of env/headers
337
+ const existing = McpServerDB.findAllLight().find(
333
338
  s => s.source === "composio" && s.url?.includes(configId)
334
339
  );
335
340
  if (existing) {
@@ -527,8 +532,9 @@ export async function handleIntegrationRoutes(
527
532
  console.log(`[agentdojo:add] fetched server: slug=${server.slug} name=${server.name} url=${server.url?.substring(0, 80)}`);
528
533
 
529
534
  // Check if already exists — match by slug in URL, scoped to same project
535
+ // Use Light variant to skip expensive decryption of env/headers
530
536
  const effectiveProjectId = projectId && projectId !== "unassigned" ? projectId : null;
531
- const allServers = McpServerDB.findAll();
537
+ const allServers = McpServerDB.findAllLight();
532
538
  const agentdojoServers = allServers.filter(s => s.source === "agentdojo");
533
539
  console.log(`[agentdojo:add] total servers=${allServers.length} agentdojo servers=${agentdojoServers.length}`);
534
540
  const existing = agentdojoServers.find(
@@ -28,16 +28,17 @@ export async function handleMcpRoutes(
28
28
  let queryMode: string;
29
29
  if (forAgent !== null) {
30
30
  // Get servers available for an agent (global + agent's project)
31
- servers = McpServerDB.findForAgent(forAgent || null);
31
+ // Use Light variant: skips expensive decryption of env/headers
32
+ servers = McpServerDB.findForAgentLight(forAgent || null);
32
33
  queryMode = `forAgent=${forAgent}`;
33
34
  } else if (projectFilter === "global") {
34
- servers = McpServerDB.findGlobal();
35
+ servers = McpServerDB.findGlobalLight();
35
36
  queryMode = "global";
36
37
  } else if (projectFilter && projectFilter !== "all") {
37
- servers = McpServerDB.findByProject(projectFilter);
38
+ servers = McpServerDB.findByProjectLight(projectFilter);
38
39
  queryMode = `project=${projectFilter}`;
39
40
  } else {
40
- servers = McpServerDB.findAll();
41
+ servers = McpServerDB.findAllLight();
41
42
  queryMode = "all";
42
43
  }
43
44
  const agentdojoCount = servers.filter(s => s.source === "agentdojo").length;
@@ -56,6 +56,9 @@ WHAT YOU CAN DO:
56
56
  - **Skills**: List, enable/disable, and assign skills to agents
57
57
  - **Providers**: Check which LLM providers have API keys configured
58
58
  - **Communication**: Send messages to running agents
59
+ - **Integrations**: Connect third-party apps (GitHub, Slack, etc.) via API key, create MCP servers from them, and add them locally
60
+ - **Tests**: Create and run automated tests for agent workflows
61
+ - **Triggers**: Subscribe agents to external events (webhooks)
59
62
 
60
63
  WORKFLOW FOR CREATING AGENTS:
61
64
  1. Use list_providers to check which providers have API keys
@@ -63,6 +66,14 @@ WORKFLOW FOR CREATING AGENTS:
63
66
  3. Optionally assign MCP servers (for tools) and skills (for behavior)
64
67
  4. Use start_agent to run it
65
68
 
69
+ WORKFLOW FOR ADDING INTEGRATIONS:
70
+ 1. Use list_integration_providers to see available providers (agentdojo, composio)
71
+ 2. Use list_integration_apps to browse available apps/toolkits
72
+ 3. Use connect_integration_app to authenticate with an API key (for OAuth, direct user to Browse Toolkits UI)
73
+ 4. Use create_integration_config to create an MCP server from the connected app
74
+ 5. Use add_integration_config_locally to add it as a local MCP server
75
+ 6. Use assign_mcp_server_to_agent to give an agent access
76
+
66
77
  AGENT FEATURES (enable when creating/updating):
67
78
  - **memory**: Persistent memory across conversations (needs OpenAI key for embeddings)
68
79
  - **tasks**: Scheduling and task tracking
@@ -70,6 +81,9 @@ AGENT FEATURES (enable when creating/updating):
70
81
  - **mcp**: Required if assigning MCP servers — gives the agent tool-use capability
71
82
  - **files**: File read/write in agent workspace
72
83
 
84
+ CRITICAL — PROJECT CONTEXT:
85
+ Your chat context tells you the current project (name and id). **You MUST pass this project_id to EVERY tool call that accepts a project_id parameter.** API keys, integrations, MCP servers, and agents are scoped per project — calls without project_id will fail to find them. Extract the id from your context and always include it.
86
+
73
87
  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
88
  Be concise. Use markdown formatting.`,
75
89
  features: {
@@ -81,6 +95,10 @@ Be concise. Use markdown formatting.`,
81
95
  realtime: false,
82
96
  files: false,
83
97
  agents: false,
98
+ builtinTools: {
99
+ webSearch: providerId === "anthropic",
100
+ webFetch: providerId === "anthropic",
101
+ },
84
102
  },
85
103
  mcp_servers: [],
86
104
  skills: [],
@@ -118,8 +136,24 @@ Be concise. Use markdown formatting.`,
118
136
  return json({ agent: toApiAgent(metaAgent), message: "Already running" });
119
137
  }
120
138
 
139
+ // Ensure builtinTools are enabled for anthropic provider
140
+ if (metaAgent.provider === "anthropic" && !metaAgent.features?.builtinTools?.webSearch) {
141
+ const updatedFeatures = {
142
+ ...metaAgent.features,
143
+ builtinTools: { webSearch: true, webFetch: true },
144
+ };
145
+ AgentDB.update(META_AGENT_ID, { features: updatedFeatures });
146
+ console.log(`[meta-agent] Enabled builtinTools for anthropic provider`);
147
+ }
148
+ // Always re-read to get latest features
149
+ const freshAgent = AgentDB.findById(META_AGENT_ID);
150
+ if (!freshAgent) {
151
+ return json({ error: "Meta agent not found after update" }, 500);
152
+ }
153
+ console.log(`[meta-agent] Starting with builtinTools:`, JSON.stringify(freshAgent.features?.builtinTools));
154
+
121
155
  // Start the agent using existing startAgentProcess function
122
- const result = await startAgentProcess(metaAgent, { silent: true });
156
+ const result = await startAgentProcess(freshAgent, { silent: true });
123
157
  if (!result.success) {
124
158
  return json({ error: result.error || "Failed to start meta agent" }, 500);
125
159
  }
@@ -88,9 +88,9 @@ export async function handleProjectRoutes(
88
88
  return json({ error: "Project not found" }, 404);
89
89
  }
90
90
 
91
- // Stop any running agents in this project first
91
+ // Stop any running agents in this project first - in parallel
92
92
  const projectAgents = AgentDB.findByProject(projectMatch[1]);
93
- for (const agent of projectAgents) {
93
+ await Promise.allSettled(projectAgents.map(async (agent) => {
94
94
  if (agent.status === "running") {
95
95
  const entry = agentProcesses.get(agent.id);
96
96
  if (entry) {
@@ -102,7 +102,7 @@ export async function handleProjectRoutes(
102
102
  }
103
103
  setAgentStatus(agent.id, "stopped", "project_deleted");
104
104
  }
105
- }
105
+ }));
106
106
 
107
107
  ProjectDB.delete(projectMatch[1]);
108
108
  return json({ success: true });