apteva 0.4.26 → 0.4.30

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 (54) hide show
  1. package/dist/ActivityPage.41nbye4r.js +3 -0
  2. package/dist/{ApiDocsPage.3q5x9hhg.js → ApiDocsPage.4smnt8m3.js} +1 -1
  3. package/dist/{App.9bzz8dqh.js → App.0sbax9et.js} +1 -1
  4. package/dist/{App.a7h91mxr.js → App.0ws427h8.js} +1 -1
  5. package/dist/App.6q6bar8b.js +4 -0
  6. package/dist/App.80301vdb.js +4 -0
  7. package/dist/App.af2wg84v.js +267 -0
  8. package/dist/{App.wnap3h7r.js → App.ca1rz1ph.js} +1 -1
  9. package/dist/{App.e54ynjf2.js → App.ensa6z0r.js} +1 -1
  10. package/dist/{App.sb2fg71h.js → App.f8g7tych.js} +1 -1
  11. package/dist/App.mvtqv6qc.js +20 -0
  12. package/dist/{App.2prdcxgq.js → App.ncgc9cxy.js} +1 -1
  13. package/dist/{App.r2c5nw36.js → App.p0fb1pds.js} +1 -1
  14. package/dist/{App.6ftxk387.js → App.pmaq48sj.js} +1 -1
  15. package/dist/{App.40azyqz6.js → App.yv87t9m5.js} +1 -1
  16. package/dist/App.zjmfm8p6.js +4 -0
  17. package/dist/ConnectionsPage.anb3rv9a.js +3 -0
  18. package/dist/McpPage.y396h6fy.js +3 -0
  19. package/dist/SettingsPage.p1hc60gk.js +3 -0
  20. package/dist/SkillsPage.yj3xdsay.js +3 -0
  21. package/dist/{TasksPage.65dcf4vw.js → TasksPage.sjv0khtv.js} +1 -1
  22. package/dist/TelemetryPage.2qm4w16r.js +3 -0
  23. package/dist/TestsPage.zzs4qfj8.js +3 -0
  24. package/dist/index.html +1 -1
  25. package/dist/styles.css +1 -1
  26. package/package.json +1 -1
  27. package/src/channels/telegram.ts +5 -0
  28. package/src/crypto.ts +25 -4
  29. package/src/db.ts +27 -2
  30. package/src/providers.ts +53 -1
  31. package/src/routes/api/agent-utils.ts +71 -9
  32. package/src/routes/api/agents.ts +41 -13
  33. package/src/routes/api/integrations.ts +16 -6
  34. package/src/routes/api/mcp.ts +7 -0
  35. package/src/web/components/activity/ActivityPage.tsx +3 -3
  36. package/src/web/components/agents/AgentCard.tsx +5 -5
  37. package/src/web/components/agents/AgentPanel.tsx +81 -20
  38. package/src/web/components/mcp/McpPage.tsx +16 -5
  39. package/src/web/components/settings/SettingsPage.tsx +331 -40
  40. package/src/web/context/ProjectContext.tsx +5 -0
  41. package/src/web/context/TelemetryContext.tsx +14 -0
  42. package/src/web/types.ts +20 -2
  43. package/dist/ActivityPage.cycn14ck.js +0 -3
  44. package/dist/App.0wwyytz2.js +0 -4
  45. package/dist/App.fq11mvc7.js +0 -4
  46. package/dist/App.h6k4j1w9.js +0 -4
  47. package/dist/App.jq5tmjws.js +0 -267
  48. package/dist/App.k377qek6.js +0 -20
  49. package/dist/ConnectionsPage.6fyhqfhz.js +0 -3
  50. package/dist/McpPage.hk2qt1qt.js +0 -3
  51. package/dist/SettingsPage.gwpx9v7v.js +0 -3
  52. package/dist/SkillsPage.j5zech2z.js +0 -3
  53. package/dist/TelemetryPage.07xrbd7k.js +0 -3
  54. package/dist/TestsPage.q6zfephf.js +0 -3
@@ -110,20 +110,47 @@ export async function handleAgentRoutes(
110
110
 
111
111
  const updated = AgentDB.update(agentMatch[1], updates);
112
112
 
113
- // If agent is running, push the new config and skills
113
+ // If agent is running, handle config update
114
114
  if (updated && updated.status === "running" && updated.port) {
115
- const providerKey = ProviderKeys.getDecrypted(updated.provider);
116
- if (providerKey) {
117
- const config = buildAgentConfig(updated, providerKey);
118
- const configResult = await pushConfigToAgent(updated.id, updated.port, config);
119
- if (!configResult.success) {
120
- console.error(`Failed to push config to running agent: ${configResult.error}`);
115
+ const providerChanged = body.provider !== undefined && body.provider !== agent.provider;
116
+
117
+ if (providerChanged) {
118
+ // Provider changed must restart to get new API key in env
119
+ console.log(`Provider changed for ${updated.name} (${agent.provider} -> ${updated.provider}), restarting...`);
120
+ const agentProc = agentProcesses.get(updated.id);
121
+ if (agentProc) {
122
+ // Graceful shutdown
123
+ try {
124
+ await fetch(`http://localhost:${updated.port}/shutdown`, {
125
+ method: "POST",
126
+ signal: AbortSignal.timeout(2000),
127
+ });
128
+ await new Promise(r => setTimeout(r, 500));
129
+ } catch {}
130
+ try { agentProc.proc.kill(); } catch {}
131
+ agentProcesses.delete(updated.id);
121
132
  }
122
- // Push skills via /skills endpoint
123
- if (config.skills?.definitions?.length > 0) {
124
- const skillsResult = await pushSkillsToAgent(updated.id, updated.port, config.skills.definitions);
125
- if (!skillsResult.success) {
126
- console.error(`Failed to push skills to running agent: ${skillsResult.error}`);
133
+ setAgentStatus(updated.id, "stopped", "provider_changed");
134
+ // Start with new provider
135
+ const startResult = await startAgentProcess(updated, { silent: true });
136
+ if (!startResult.success) {
137
+ console.error(`Failed to restart agent after provider change: ${startResult.error}`);
138
+ }
139
+ } else {
140
+ // Same provider — just push updated config
141
+ const providerKey = ProviderKeys.getDecrypted(updated.provider);
142
+ if (providerKey) {
143
+ const config = buildAgentConfig(updated, providerKey);
144
+ const configResult = await pushConfigToAgent(updated.id, updated.port, config);
145
+ if (!configResult.success) {
146
+ console.error(`Failed to push config to running agent: ${configResult.error}`);
147
+ }
148
+ // Push skills via /skills endpoint
149
+ if (config.skills?.definitions?.length > 0) {
150
+ const skillsResult = await pushSkillsToAgent(updated.id, updated.port, config.skills.definitions);
151
+ if (!skillsResult.success) {
152
+ console.error(`Failed to push skills to running agent: ${skillsResult.error}`);
153
+ }
127
154
  }
128
155
  }
129
156
  }
@@ -215,10 +242,11 @@ export async function handleAgentRoutes(
215
242
  return json({ error: "No API key found for this agent" }, 404);
216
243
  }
217
244
 
218
- // Return masked key (show only first 8 chars)
245
+ // Return masked key + full key (full key only shown on demand by frontend)
219
246
  const masked = apiKey.substring(0, 8) + "..." + apiKey.substring(apiKey.length - 4);
220
247
  return json({
221
248
  apiKey: masked,
249
+ fullKey: apiKey,
222
250
  hasKey: true,
223
251
  });
224
252
  }
@@ -518,20 +518,29 @@ export async function handleIntegrationRoutes(
518
518
  }
519
519
 
520
520
  try {
521
+ console.log(`[agentdojo:add] configId=${configId} projectId=${projectId}`);
521
522
  const server = await getAgentDojoServer(apiKey, configId);
522
523
  if (!server) {
524
+ console.log(`[agentdojo:add] server not found from AgentDojo API for configId=${configId}`);
523
525
  return json({ error: "Config not found" }, 404);
524
526
  }
525
-
526
- // Check if already exists
527
- const existing = McpServerDB.findAll().find(
528
- s => s.source === "agentdojo" && (s.url?.includes(server.slug) || s.url?.includes(configId))
527
+ console.log(`[agentdojo:add] fetched server: slug=${server.slug} name=${server.name} url=${server.url?.substring(0, 80)}`);
528
+
529
+ // Check if already exists — match by slug in URL, scoped to same project
530
+ const effectiveProjectId = projectId && projectId !== "unassigned" ? projectId : null;
531
+ const allServers = McpServerDB.findAll();
532
+ const agentdojoServers = allServers.filter(s => s.source === "agentdojo");
533
+ console.log(`[agentdojo:add] total servers=${allServers.length} agentdojo servers=${agentdojoServers.length}`);
534
+ const existing = agentdojoServers.find(
535
+ s => s.project_id === effectiveProjectId && s.url?.endsWith(`/${server.slug}`)
529
536
  );
530
537
  if (existing) {
538
+ console.log(`[agentdojo:add] ALREADY EXISTS: id=${existing.id} project_id=${existing.project_id} slug=${server.slug}`);
531
539
  return json({ server: existing, message: "Server already exists" });
532
540
  }
533
541
 
534
542
  // Create the MCP server entry
543
+ console.log(`[agentdojo:add] creating new server with effectiveProjectId=${effectiveProjectId}`);
535
544
  const mcpServer = McpServerDB.create({
536
545
  id: generateId(),
537
546
  name: server.name,
@@ -544,12 +553,13 @@ export async function handleIntegrationRoutes(
544
553
  url: server.url,
545
554
  headers: { "X-API-Key": apiKey },
546
555
  source: "agentdojo",
547
- project_id: projectId && projectId !== "unassigned" ? projectId : null,
556
+ project_id: effectiveProjectId,
548
557
  });
558
+ console.log(`[agentdojo:add] created server: id=${mcpServer.id} project_id=${mcpServer.project_id}`);
549
559
 
550
560
  return json({ server: mcpServer, message: "Server added successfully" });
551
561
  } catch (e) {
552
- console.error("Failed to add AgentDojo config:", e);
562
+ console.error("[agentdojo:add] Failed to add AgentDojo config:", e);
553
563
  return json({ error: "Failed to add AgentDojo config" }, 500);
554
564
  }
555
565
  }
@@ -25,16 +25,23 @@ export async function handleMcpRoutes(
25
25
  const forAgent = url.searchParams.get("forAgent"); // agent's project ID (shows global + project)
26
26
 
27
27
  let servers;
28
+ let queryMode: string;
28
29
  if (forAgent !== null) {
29
30
  // Get servers available for an agent (global + agent's project)
30
31
  servers = McpServerDB.findForAgent(forAgent || null);
32
+ queryMode = `forAgent=${forAgent}`;
31
33
  } else if (projectFilter === "global") {
32
34
  servers = McpServerDB.findGlobal();
35
+ queryMode = "global";
33
36
  } else if (projectFilter && projectFilter !== "all") {
34
37
  servers = McpServerDB.findByProject(projectFilter);
38
+ queryMode = `project=${projectFilter}`;
35
39
  } else {
36
40
  servers = McpServerDB.findAll();
41
+ queryMode = "all";
37
42
  }
43
+ const agentdojoCount = servers.filter(s => s.source === "agentdojo").length;
44
+ console.log(`[mcp:GET] mode=${queryMode} total=${servers.length} agentdojo=${agentdojoCount}`);
38
45
  return json({ servers });
39
46
  }
40
47
 
@@ -216,7 +216,7 @@ function AgentRow({ agent, selected, onSelect }: {
216
216
  selected: boolean;
217
217
  onSelect: () => void;
218
218
  }) {
219
- const { isActive, type } = useAgentActivity(agent.id);
219
+ const { isActive, label } = useAgentActivity(agent.id);
220
220
  const isRunning = agent.status === "running";
221
221
 
222
222
  return (
@@ -244,8 +244,8 @@ function AgentRow({ agent, selected, onSelect }: {
244
244
  </span>
245
245
  <span className="text-[10px] text-[#555] shrink-0">{agent.provider}</span>
246
246
  </div>
247
- {isActive && type ? (
248
- <p className="text-[11px] text-green-400 truncate">{type}</p>
247
+ {isActive && label ? (
248
+ <p className="text-[11px] text-green-400 truncate">{label}</p>
249
249
  ) : (
250
250
  <p className={`text-[11px] ${isRunning ? "text-[#555]" : "text-[#444]"}`}>
251
251
  {isRunning ? "idle" : "stopped"}
@@ -26,7 +26,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
26
26
  const enabledFeatures = FEATURE_ICONS.filter(f => agent.features?.[f.key]);
27
27
  const mcpServers = agent.mcpServerDetails || [];
28
28
  const skills = agent.skillDetails || [];
29
- const { isActive, type } = useAgentActivity(agent.id);
29
+ const { isActive, label: activityLabel } = useAgentActivity(agent.id);
30
30
  const { projects } = useProjects();
31
31
  const { authFetch } = useAuth();
32
32
  const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
@@ -62,7 +62,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
62
62
  </p>
63
63
  )}
64
64
  </div>
65
- <StatusBadge status={agent.status} isActive={isActive && agent.status === "running"} activityType={type} />
65
+ <StatusBadge status={agent.status} isActive={isActive && agent.status === "running"} activityLabel={activityLabel} />
66
66
  </div>
67
67
 
68
68
  {enabledFeatures.length > 0 && (
@@ -165,11 +165,11 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
165
165
  );
166
166
  }
167
167
 
168
- function StatusBadge({ status, isActive, activityType }: { status: Agent["status"]; isActive?: boolean; activityType?: string }) {
169
- if (status === "running" && isActive && activityType) {
168
+ function StatusBadge({ status, isActive, activityLabel }: { status: Agent["status"]; isActive?: boolean; activityLabel?: string }) {
169
+ if (status === "running" && isActive && activityLabel) {
170
170
  return (
171
171
  <span className="px-2 py-1 rounded text-xs font-medium bg-green-500/20 text-green-400 animate-pulse">
172
- {activityType}
172
+ {activityLabel}
173
173
  </span>
174
174
  );
175
175
  }
@@ -6,8 +6,8 @@ import { Select } from "../common/Select";
6
6
  import { useConfirm } from "../common/Modal";
7
7
  import { useTelemetry } from "../../context";
8
8
  import { useAuth } from "../../context";
9
- import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig, Task } from "../../types";
10
- import { getMultiAgentConfig } from "../../types";
9
+ import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig, OperatorConfig, Task } from "../../types";
10
+ import { getMultiAgentConfig, getOperatorConfig } from "../../types";
11
11
 
12
12
  type Tab = "chat" | "threads" | "tasks" | "memory" | "files" | "settings";
13
13
 
@@ -1123,6 +1123,7 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1123
1123
  const [availableMcpServers, setAvailableMcpServers] = useState<McpServer[]>([]);
1124
1124
  const [availableSkills, setAvailableSkills] = useState<AvailableSkill[]>([]);
1125
1125
  const [apiKey, setApiKey] = useState<string | null>(null);
1126
+ const [apiKeyFull, setApiKeyFull] = useState<string | null>(null);
1126
1127
  const [showApiKey, setShowApiKey] = useState(false);
1127
1128
  const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
1128
1129
 
@@ -1148,22 +1149,22 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1148
1149
  fetchMcpServers();
1149
1150
  }, [authFetch]);
1150
1151
 
1151
- // Fetch API key (dev mode only)
1152
+ // Fetch API key
1152
1153
  useEffect(() => {
1153
- if (!isDev) return;
1154
1154
  const fetchApiKey = async () => {
1155
1155
  try {
1156
1156
  const res = await authFetch(`/api/agents/${agent.id}/api-key`);
1157
1157
  if (res.ok) {
1158
1158
  const data = await res.json();
1159
1159
  setApiKey(data.apiKey);
1160
+ setApiKeyFull(data.fullKey || null);
1160
1161
  }
1161
1162
  } catch (e) {
1162
1163
  // Ignore - not critical
1163
1164
  }
1164
1165
  };
1165
1166
  fetchApiKey();
1166
- }, [agent.id, isDev, authFetch]);
1167
+ }, [agent.id, authFetch]);
1167
1168
 
1168
1169
  // Fetch available skills
1169
1170
  useEffect(() => {
@@ -1217,19 +1218,13 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1217
1218
  const toggleFeature = (key: keyof AgentFeatures) => {
1218
1219
  if (key === "agents") {
1219
1220
  // Special handling for agents feature - convert to MultiAgentConfig
1220
- const current = prev => {
1221
- const agentConfig = getMultiAgentConfig(prev.features, agent.projectId);
1222
- return agentConfig.enabled;
1223
- };
1224
1221
  setForm(prev => {
1225
1222
  const isEnabled = typeof prev.features.agents === "boolean"
1226
1223
  ? prev.features.agents
1227
1224
  : (prev.features.agents as MultiAgentConfig)?.enabled ?? false;
1228
1225
  if (isEnabled) {
1229
- // Turning off - set to false
1230
1226
  return { ...prev, features: { ...prev.features, agents: false } };
1231
1227
  } else {
1232
- // Turning on - set to config with defaults
1233
1228
  return {
1234
1229
  ...prev,
1235
1230
  features: {
@@ -1239,6 +1234,22 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1239
1234
  };
1240
1235
  }
1241
1236
  });
1237
+ } else if (key === "operator") {
1238
+ // Special handling for operator feature - convert to OperatorConfig
1239
+ setForm(prev => {
1240
+ const opConfig = getOperatorConfig(prev.features);
1241
+ if (opConfig.enabled) {
1242
+ return { ...prev, features: { ...prev.features, operator: false } };
1243
+ } else {
1244
+ return {
1245
+ ...prev,
1246
+ features: {
1247
+ ...prev.features,
1248
+ operator: { enabled: true },
1249
+ },
1250
+ };
1251
+ }
1252
+ });
1242
1253
  } else {
1243
1254
  setForm(prev => ({
1244
1255
  ...prev,
@@ -1274,6 +1285,33 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1274
1285
  return config.mode || "worker";
1275
1286
  };
1276
1287
 
1288
+ // Helper to check if operator feature is enabled
1289
+ const isOperatorEnabled = () => {
1290
+ return getOperatorConfig(form.features).enabled;
1291
+ };
1292
+
1293
+ // Get current operator config
1294
+ const getOperatorCfg = (): OperatorConfig => {
1295
+ return getOperatorConfig(form.features);
1296
+ };
1297
+
1298
+ // Get browser providers from the providers list
1299
+ const browserProviders = providers.filter(p => p.type === "browser" && p.hasKey);
1300
+
1301
+ // Set operator browser provider
1302
+ const setOperatorBrowserProvider = (browserProvider: string) => {
1303
+ setForm(prev => {
1304
+ const current = getOperatorConfig(prev.features);
1305
+ return {
1306
+ ...prev,
1307
+ features: {
1308
+ ...prev.features,
1309
+ operator: { ...current, enabled: true, browser_provider: browserProvider },
1310
+ },
1311
+ };
1312
+ });
1313
+ };
1314
+
1277
1315
  const toggleMcpServer = (serverId: string) => {
1278
1316
  setForm(prev => ({
1279
1317
  ...prev,
@@ -1353,8 +1391,10 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1353
1391
  <FormField label="Features">
1354
1392
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
1355
1393
  {FEATURE_CONFIG.map(({ key, label, description, icon: Icon }) => {
1356
- // For agents feature, check the enabled property of the config
1357
- const isEnabled = key === "agents" ? isAgentsEnabled() : !!form.features[key];
1394
+ // For agents/operator features, check the enabled property of the config
1395
+ const isEnabled = key === "agents" ? isAgentsEnabled()
1396
+ : key === "operator" ? isOperatorEnabled()
1397
+ : !!form.features[key];
1358
1398
  return (
1359
1399
  <button
1360
1400
  key={key}
@@ -1420,6 +1460,29 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1420
1460
  </FormField>
1421
1461
  )}
1422
1462
 
1463
+ {/* Operator Browser Provider - shown when operator is enabled */}
1464
+ {isOperatorEnabled() && (
1465
+ <FormField label="Browser Provider">
1466
+ {browserProviders.length > 0 ? (
1467
+ <Select
1468
+ value={getOperatorCfg().browser_provider || ""}
1469
+ options={[
1470
+ { value: "", label: "Auto (first available)" },
1471
+ ...browserProviders.map(p => ({
1472
+ value: p.id,
1473
+ label: p.name,
1474
+ })),
1475
+ ]}
1476
+ onChange={(value) => setOperatorBrowserProvider(value)}
1477
+ />
1478
+ ) : (
1479
+ <p className="text-sm text-[#666] p-3 border border-[#222] rounded bg-[#0a0a0a]">
1480
+ No browser providers configured. Go to Settings &rarr; Providers to add one.
1481
+ </p>
1482
+ )}
1483
+ </FormField>
1484
+ )}
1485
+
1423
1486
  {/* Agent Built-in Tools - Anthropic only */}
1424
1487
  {form.provider === "anthropic" && (
1425
1488
  <FormField label="Agent Built-in Tools">
@@ -1628,7 +1691,7 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1628
1691
  </div>
1629
1692
 
1630
1693
  {/* Developer Info (dev mode only) */}
1631
- {isDev && apiKey && (
1694
+ {apiKey && (
1632
1695
  <div className="mt-8 pt-6 border-t border-[#222]">
1633
1696
  <p className="text-sm text-[#666] mb-3">Developer Info</p>
1634
1697
  <div className="space-y-2">
@@ -1650,17 +1713,15 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
1650
1713
  {showApiKey ? "Hide" : "Show"}
1651
1714
  </button>
1652
1715
  </div>
1653
- {showApiKey && (
1654
- <code className="text-xs bg-[#1a1a1a] px-2 py-1 rounded text-[#888] break-all">
1655
- {apiKey}
1656
- </code>
1657
- )}
1716
+ <code className="text-xs bg-[#1a1a1a] px-2 py-1 rounded text-[#888] break-all">
1717
+ {showApiKey ? (apiKeyFull || apiKey) : apiKey}
1718
+ </code>
1658
1719
  </div>
1659
1720
  {agent.status === "running" && agent.port && (
1660
1721
  <div className="flex flex-col gap-1 mt-2">
1661
1722
  <span className="text-xs text-[#666]">Test with curl</span>
1662
1723
  <code className="text-xs bg-[#1a1a1a] px-2 py-1.5 rounded text-[#666] break-all">
1663
- curl -H "X-API-Key: {showApiKey ? apiKey : "***"}" http://localhost:{agent.port}/config
1724
+ curl -H "X-API-Key: {showApiKey ? (apiKeyFull || apiKey) : "***"}" http://localhost:{agent.port}/config
1664
1725
  </code>
1665
1726
  </div>
1666
1727
  )}
@@ -1366,6 +1366,7 @@ function AgentDojoContent({
1366
1366
  const serversUrl = projectId && projectId !== "unassigned"
1367
1367
  ? `/api/mcp/servers?project=${encodeURIComponent(projectId)}`
1368
1368
  : "/api/mcp/servers";
1369
+ console.log(`[AgentDojo:fetchConfigs] projectId=${projectId} serversUrl=${serversUrl}`);
1369
1370
  const [configsRes, serversRes] = await Promise.all([
1370
1371
  authFetch(`/api/integrations/agentdojo/configs${projectParam}`),
1371
1372
  authFetch(serversUrl),
@@ -1373,18 +1374,24 @@ function AgentDojoContent({
1373
1374
  const configsData = await configsRes.json();
1374
1375
  const serversData = await serversRes.json();
1375
1376
 
1377
+ console.log(`[AgentDojo:fetchConfigs] configs=${(configsData.configs || []).length} servers=${(serversData.servers || []).length}`);
1376
1378
  setConfigs(configsData.configs || []);
1377
1379
 
1378
1380
  // Track which configs are already added as local servers
1381
+ const agentdojoServers = (serversData.servers || []).filter((s: any) => s.source === "agentdojo");
1382
+ console.log(`[AgentDojo:fetchConfigs] agentdojo servers found: ${agentdojoServers.length}`);
1383
+ for (const s of agentdojoServers) {
1384
+ const match = s.url?.match(/\/mcp\/([^/?]+)/);
1385
+ console.log(`[AgentDojo:fetchConfigs] server: id=${s.id} name=${s.name} project_id=${s.project_id} url=${s.url?.substring(0, 80)} extracted=${match ? match[1] : s.name}`);
1386
+ }
1379
1387
  const agentdojoServerIds = new Set(
1380
- (serversData.servers || [])
1381
- .filter((s: any) => s.source === "agentdojo")
1382
- .map((s: any) => {
1388
+ agentdojoServers.map((s: any) => {
1383
1389
  // Extract config ID from URL or match by name
1384
1390
  const match = s.url?.match(/\/mcp\/([^/?]+)/);
1385
1391
  return match ? match[1] : s.name;
1386
1392
  })
1387
1393
  );
1394
+ console.log(`[AgentDojo:fetchConfigs] addedServers set:`, [...agentdojoServerIds]);
1388
1395
  setAddedServers(agentdojoServerIds);
1389
1396
  } catch (e) {
1390
1397
  console.error("Failed to fetch AgentDojo configs:", e);
@@ -1396,15 +1403,19 @@ function AgentDojoContent({
1396
1403
  setAddingConfig(configId);
1397
1404
  try {
1398
1405
  const projectParam = projectId && projectId !== "unassigned" ? `?project_id=${projectId}` : "";
1406
+ console.log(`[AgentDojo:addConfig] configId=${configId} projectParam=${projectParam}`);
1399
1407
  const res = await authFetch(`/api/integrations/agentdojo/configs/${configId}/add${projectParam}`, {
1400
1408
  method: "POST",
1401
1409
  });
1410
+ const data = await res.json();
1411
+ console.log(`[AgentDojo:addConfig] response status=${res.status} ok=${res.ok} message=${data.message} server.id=${data.server?.id} server.project_id=${data.server?.project_id}`);
1402
1412
  if (res.ok) {
1403
1413
  const config = configs.find(c => c.id === configId);
1404
- setAddedServers(prev => new Set([...prev, config?.slug || configId]));
1414
+ const addKey = config?.slug || configId;
1415
+ console.log(`[AgentDojo:addConfig] marking as added: key=${addKey} config.slug=${config?.slug} config.id=${config?.id} config.name=${config?.name}`);
1416
+ setAddedServers(prev => new Set([...prev, addKey]));
1405
1417
  onServerAdded?.();
1406
1418
  } else {
1407
- const data = await res.json();
1408
1419
  await alert(data.error || "Failed to add config", { title: "Error", variant: "error" });
1409
1420
  }
1410
1421
  } catch (e) {