apteva 0.4.31 → 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
@@ -1,4 +1,14 @@
1
+ import { existsSync } from "fs";
1
2
  import { json, debug } from "./helpers";
3
+
4
+ // Detect if running inside a Docker container
5
+ async function isRunningInDocker(): Promise<boolean> {
6
+ try {
7
+ return existsSync("/.dockerenv") || existsSync("/run/.containerenv");
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
2
12
  import { startAgentProcess, setAgentStatus } from "./agent-utils";
3
13
  import { AgentDB, UserDB } from "../../db";
4
14
  import { ProviderKeys, Onboarding, getProvidersWithStatus, PROVIDERS, type ProviderId } from "../../providers";
@@ -55,6 +65,7 @@ export async function handleProviderRoutes(
55
65
  // GET /api/providers/ollama/status - Check if Ollama is running
56
66
  if (path === "/api/providers/ollama/status" && method === "GET") {
57
67
  const ollamaUrl = ProviderKeys.getDecrypted("ollama") || "http://localhost:11434";
68
+ const isDocker = await isRunningInDocker();
58
69
 
59
70
  try {
60
71
  const response = await fetch(`${ollamaUrl}/api/tags`, {
@@ -68,11 +79,96 @@ export async function handleProviderRoutes(
68
79
  connected: true,
69
80
  url: ollamaUrl,
70
81
  modelCount: data.models?.length || 0,
82
+ isDocker,
71
83
  });
72
84
  }
73
- return json({ connected: false, url: ollamaUrl, error: "Ollama not responding" });
85
+ return json({ connected: false, url: ollamaUrl, error: "Ollama not responding", isDocker });
74
86
  } catch {
75
- return json({ connected: false, url: ollamaUrl, error: "Ollama not reachable" });
87
+ return json({ connected: false, url: ollamaUrl, error: "Ollama not reachable", isDocker });
88
+ }
89
+ }
90
+
91
+ // POST /api/providers/ollama/install - Install Ollama automatically
92
+ if (path === "/api/providers/ollama/install" && method === "POST") {
93
+ // Don't allow install inside Docker containers
94
+ if (await isRunningInDocker()) {
95
+ return json({ success: false, error: "Cannot install Ollama inside a Docker container. Configure an external Ollama URL instead." }, 400);
96
+ }
97
+
98
+ // Only supported on Linux/macOS
99
+ const platform = process.platform;
100
+ if (platform !== "linux" && platform !== "darwin") {
101
+ return json({ success: false, error: "Auto-install is only supported on Linux and macOS. Please download from https://ollama.com/download" }, 400);
102
+ }
103
+
104
+ // Check if already installed
105
+ try {
106
+ const which = Bun.spawnSync(["which", "ollama"]);
107
+ if (which.exitCode === 0) {
108
+ // Already installed, just make sure it's running
109
+ Bun.spawn(["ollama", "serve"], { stdout: "ignore", stderr: "ignore" });
110
+ // Wait a moment for it to start
111
+ await new Promise(r => setTimeout(r, 2000));
112
+ return json({ success: true, message: "Ollama is already installed", alreadyInstalled: true });
113
+ }
114
+ } catch { /* not installed */ }
115
+
116
+ // Install Ollama using official install script
117
+ try {
118
+ const proc = Bun.spawnSync(["bash", "-c", "curl -fsSL https://ollama.com/install.sh | sh"], {
119
+ timeout: 120_000, // 2 minute timeout
120
+ stderr: "pipe",
121
+ stdout: "pipe",
122
+ });
123
+
124
+ if (proc.exitCode !== 0) {
125
+ const stderr = proc.stderr.toString().trim();
126
+ return json({ success: false, error: `Installation failed: ${stderr || "Unknown error"}` }, 500);
127
+ }
128
+
129
+ // Start Ollama serve in background
130
+ Bun.spawn(["ollama", "serve"], { stdout: "ignore", stderr: "ignore" });
131
+
132
+ // Wait for it to come up
133
+ await new Promise(r => setTimeout(r, 3000));
134
+
135
+ // Verify it's running
136
+ try {
137
+ const check = await fetch("http://localhost:11434/api/tags", { signal: AbortSignal.timeout(3000) });
138
+ if (check.ok) {
139
+ return json({ success: true, message: "Ollama installed and running" });
140
+ }
141
+ } catch { /* may still be starting */ }
142
+
143
+ return json({ success: true, message: "Ollama installed. It may take a moment to start." });
144
+ } catch (err: any) {
145
+ return json({ success: false, error: `Installation failed: ${err.message}` }, 500);
146
+ }
147
+ }
148
+
149
+ // POST /api/providers/ollama/pull - Pull a model
150
+ if (path === "/api/providers/ollama/pull" && method === "POST") {
151
+ const body = await req.json() as { model?: string };
152
+ const model = body.model;
153
+ if (!model) {
154
+ return json({ error: "Model name required" }, 400);
155
+ }
156
+
157
+ try {
158
+ const ollamaUrl = ProviderKeys.getDecrypted("ollama") || "http://localhost:11434";
159
+ const response = await fetch(`${ollamaUrl}/api/pull`, {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify({ name: model }),
163
+ });
164
+
165
+ if (!response.ok) {
166
+ return json({ success: false, error: "Failed to start model pull" }, 500);
167
+ }
168
+
169
+ return json({ success: true, message: `Pulling ${model}...` });
170
+ } catch (err: any) {
171
+ return json({ success: false, error: `Failed to pull model: ${err.message}` }, 500);
76
172
  }
77
173
  }
78
174
 
@@ -216,38 +312,33 @@ export async function handleProviderRoutes(
216
312
  a => a.status === "running" && a.provider === providerId
217
313
  );
218
314
 
219
- const restartResults: Array<{ id: string; name: string; success: boolean; error?: string }> = [];
315
+ // Stop all agents first
220
316
  for (const agent of runningAgents) {
221
- try {
222
- // Stop the agent
223
- const agentProc = agentProcesses.get(agent.id);
224
- if (agentProc) {
225
- agentProc.proc.kill();
226
- agentProcesses.delete(agent.id);
227
- }
228
- setAgentStatus(agent.id, "stopped", "provider_restart");
229
-
230
- // Wait a moment for port to be released
231
- await new Promise(r => setTimeout(r, 500));
232
-
233
- // Restart the agent with new key
234
- const startResult = await startAgentProcess(agent, { silent: true });
235
- restartResults.push({
236
- id: agent.id,
237
- name: agent.name,
238
- success: startResult.success,
239
- error: startResult.error,
240
- });
241
- } catch (e) {
242
- restartResults.push({
243
- id: agent.id,
244
- name: agent.name,
245
- success: false,
246
- error: String(e),
247
- });
317
+ const agentProc = agentProcesses.get(agent.id);
318
+ if (agentProc) {
319
+ agentProc.proc.kill();
320
+ agentProcesses.delete(agent.id);
248
321
  }
322
+ setAgentStatus(agent.id, "stopped", "provider_restart");
249
323
  }
250
324
 
325
+ // Wait once for ports to be released
326
+ if (runningAgents.length > 0) {
327
+ await new Promise(r => setTimeout(r, 500));
328
+ }
329
+
330
+ // Restart all agents in parallel
331
+ const restartResults = await Promise.all(
332
+ runningAgents.map(async (agent) => {
333
+ try {
334
+ const startResult = await startAgentProcess(agent, { silent: true });
335
+ return { id: agent.id, name: agent.name, success: startResult.success, error: startResult.error };
336
+ } catch (e) {
337
+ return { id: agent.id, name: agent.name, success: false, error: String(e) };
338
+ }
339
+ })
340
+ );
341
+
251
342
  return json({
252
343
  success: true,
253
344
  message: "API key saved successfully",
@@ -499,13 +499,12 @@ export async function handleSkillRoutes(
499
499
  const agentsWithSkill = AgentDB.findBySkill(skillMatch[1]);
500
500
  const runningAgents = agentsWithSkill.filter(a => a.status === "running" && a.port);
501
501
 
502
- for (const agent of runningAgents) {
502
+ await Promise.allSettled(runningAgents.map(async (agent) => {
503
503
  try {
504
504
  const providerKey = ProviderKeys.getDecrypted(agent.provider);
505
505
  if (providerKey) {
506
506
  const config = buildAgentConfig(agent, providerKey);
507
507
  await pushConfigToAgent(agent.id, agent.port!, config);
508
- // Push skills via /skills endpoint
509
508
  if (config.skills?.definitions?.length > 0) {
510
509
  await pushSkillsToAgent(agent.id, agent.port!, config.skills.definitions);
511
510
  }
@@ -514,7 +513,7 @@ export async function handleSkillRoutes(
514
513
  } catch (err) {
515
514
  console.error(`Failed to push skill update to agent ${agent.name}:`, err);
516
515
  }
517
- }
516
+ }));
518
517
 
519
518
  return json({ skill: updated, agents_updated: runningAgents.length });
520
519
  } catch (err) {
@@ -99,21 +99,16 @@ export async function handleSystemRoutes(
99
99
  return json({ success: false, error: result.error }, 500);
100
100
  }
101
101
 
102
- // Restart agents that were running
103
- const restartResults: { id: string; name: string; success: boolean; error?: string }[] = [];
104
- for (const agentId of agentsToRestart) {
105
- const agent = AgentDB.findById(agentId);
106
- if (agent) {
102
+ // Restart agents that were running - in parallel
103
+ const restartResults = await Promise.all(
104
+ agentsToRestart.map(async (agentId) => {
105
+ const agent = AgentDB.findById(agentId);
106
+ if (!agent) return null;
107
107
  console.log(`Restarting agent ${agent.name} after update...`);
108
108
  const startResult = await startAgentProcess(agent);
109
- restartResults.push({
110
- id: agent.id,
111
- name: agent.name,
112
- success: startResult.success,
113
- error: startResult.error,
114
- });
115
- }
116
- }
109
+ return { id: agent.id, name: agent.name, success: startResult.success, error: startResult.error };
110
+ })
111
+ ).then(r => r.filter(Boolean));
117
112
 
118
113
  return json({
119
114
  success: true,
package/src/server.ts CHANGED
@@ -5,7 +5,7 @@ import { serveStatic } from "./routes/static";
5
5
  import { join } from "path";
6
6
  import { homedir } from "os";
7
7
  import { mkdirSync, existsSync } from "fs";
8
- import { initDatabase, AgentDB, ProviderKeysDB, McpServerDB, ChannelDB, type McpServer, type Agent } from "./db";
8
+ import { initDatabase, AgentDB, ProviderKeysDB, McpServerDB, ChannelDB, TelemetryDB, type McpServer, type Agent } from "./db";
9
9
  import { authMiddleware, type AuthContext } from "./auth/middleware";
10
10
  import { startMcpProcess } from "./mcp-client";
11
11
  import {
@@ -103,6 +103,12 @@ if (await envFile.exists()) {
103
103
  // Initialize database (silently)
104
104
  initDatabase(DATA_DIR);
105
105
 
106
+ // Clean up old telemetry events (keep last 30 days)
107
+ try {
108
+ const deleted = TelemetryDB.deleteOlderThan(30);
109
+ if (deleted > 0) console.log(`[db] Cleaned up ${deleted} telemetry events older than 30 days`);
110
+ } catch { /* ignore */ }
111
+
106
112
  // Initialize version tracking
107
113
  initVersionTracking(DATA_DIR);
108
114
 
@@ -127,37 +133,31 @@ async function cleanupOrphanedProcesses(): Promise<void> {
127
133
 
128
134
  if (assignedPorts.length === 0) return;
129
135
 
130
- let cleaned = 0;
131
- for (const port of assignedPorts) {
136
+ // Check all ports in parallel
137
+ const results = await Promise.allSettled(assignedPorts.map(async (port) => {
132
138
  try {
133
139
  const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(200) });
134
- if (res.ok) {
135
- // Orphaned process on this port - shut it down gracefully first
136
- try {
137
- await fetch(`http://localhost:${port}/shutdown`, { method: "POST", signal: AbortSignal.timeout(500) });
138
- await new Promise(r => setTimeout(r, 500)); // Wait for graceful shutdown
139
- } catch {
140
- // Graceful shutdown failed
141
- }
142
-
143
- // Check if still running and force kill if needed
144
- try {
145
- const check = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(200) });
146
- if (check.ok) {
147
- // Still running - force kill via lsof
148
- const { execSync } = await import("child_process");
149
- execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, { stdio: "ignore" });
150
- }
151
- } catch {
152
- // Not responding anymore - good
140
+ if (!res.ok) return false;
141
+ // Orphaned process - shut it down gracefully
142
+ try {
143
+ await fetch(`http://localhost:${port}/shutdown`, { method: "POST", signal: AbortSignal.timeout(500) });
144
+ await new Promise(r => setTimeout(r, 500));
145
+ } catch {}
146
+ // Force kill if still running
147
+ try {
148
+ const check = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(200) });
149
+ if (check.ok) {
150
+ const { execSync } = await import("child_process");
151
+ execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, { stdio: "ignore" });
153
152
  }
154
- cleaned++;
155
- }
153
+ } catch {}
154
+ return true;
156
155
  } catch {
157
- // Port not in use - good
156
+ return false;
158
157
  }
159
- }
158
+ }));
160
159
 
160
+ const cleaned = results.filter(r => r.status === "fulfilled" && r.value).length;
161
161
  if (cleaned > 0) {
162
162
  console.log(` [cleanup] Stopped ${cleaned} orphaned agent process(es)`);
163
163
  }
@@ -490,14 +490,13 @@ if (hasRestarts) {
490
490
  }
491
491
  }
492
492
 
493
- // Then restart agents
493
+ // Then restart agents - in parallel
494
494
  if (agentsToRestart.length > 0) {
495
495
  console.log(` ${c.darkGray}Agents${c.reset} ${c.gray}Restarting ${agentsToRestart.length} agent(s)...${c.reset}`);
496
496
 
497
- for (const agent of agentsToRestart) {
497
+ await Promise.allSettled(agentsToRestart.map(async (agent) => {
498
498
  try {
499
499
  const result = await startAgentProcess(agent, { silent: true });
500
-
501
500
  if (result.success) {
502
501
  console.log(` ${c.gray} ✓ ${agent.name} on :${result.port}${c.reset}`);
503
502
  } else {
@@ -506,7 +505,7 @@ if (hasRestarts) {
506
505
  } catch (err) {
507
506
  console.log(` ${c.gray} ✗ ${agent.name}: ${err}${c.reset}`);
508
507
  }
509
- }
508
+ }));
510
509
  }
511
510
 
512
511
  // Restart channels (after agents, since channels depend on running agents)
@@ -514,7 +513,7 @@ if (hasRestarts) {
514
513
  const { startChannel } = await import("./channels");
515
514
  console.log(` ${c.darkGray}Channels${c.reset} ${c.gray}Restarting ${channelsToRestart.length} channel(s)...${c.reset}`);
516
515
 
517
- for (const channel of channelsToRestart) {
516
+ await Promise.allSettled(channelsToRestart.map(async (channel) => {
518
517
  try {
519
518
  const result = await startChannel(channel.id);
520
519
  if (result.success) {
@@ -525,7 +524,7 @@ if (hasRestarts) {
525
524
  } catch (err) {
526
525
  console.log(` ${c.gray} ✗ ${channel.name}: ${err}${c.reset}`);
527
526
  }
528
- }
527
+ }));
529
528
  }
530
529
  })();
531
530
  }
@@ -16,13 +16,13 @@ export const AgentDojoTriggerProvider: TriggerProvider = {
16
16
  name: "AgentDojo",
17
17
 
18
18
  async listTriggerTypes(apiKey: string, toolkitSlugs?: string[]): Promise<TriggerType[]> {
19
- const params = new URLSearchParams({ is_active: "true", limit: "200" });
19
+ const params = new URLSearchParams({ is_active: "true", limit: "500" });
20
20
  if (toolkitSlugs?.length) {
21
21
  // Filter by toolkit name(s) — API supports one at a time, so fetch each
22
22
  const allItems: any[] = [];
23
23
  for (const slug of toolkitSlugs) {
24
24
  const res = await fetch(
25
- `${AGENTDOJO_API_BASE}/triggers?${new URLSearchParams({ toolkit_name: slug, is_active: "true", limit: "200" })}`,
25
+ `${AGENTDOJO_API_BASE}/triggers?${new URLSearchParams({ toolkit_name: slug, is_active: "true", limit: "500" })}`,
26
26
  { headers: headers(apiKey) },
27
27
  );
28
28
  if (res.ok) {
package/src/web/App.tsx CHANGED
@@ -28,13 +28,14 @@ import { MetaAgentProvider, MetaAgentPanel } from "./components/meta-agent/MetaA
28
28
 
29
29
  // Lazy-loaded page components (only loaded when navigated to)
30
30
  const SettingsPage = lazy(() => import("./components/settings/SettingsPage").then(m => ({ default: m.SettingsPage })));
31
- const ActivityPage = lazy(() => import("./components/activity/ActivityPage").then(m => ({ default: m.ActivityPage })));
32
31
  const TasksPage = lazy(() => import("./components/tasks/TasksPage").then(m => ({ default: m.TasksPage })));
33
32
  const McpPage = lazy(() => import("./components/mcp/McpPage").then(m => ({ default: m.McpPage })));
34
33
  const SkillsPage = lazy(() => import("./components/skills/SkillsPage").then(m => ({ default: m.SkillsPage })));
35
34
  const TestsPage = lazy(() => import("./components/tests/TestsPage").then(m => ({ default: m.TestsPage })));
35
+ const ThreadsPage = lazy(() => import("./components/threads/ThreadsPage").then(m => ({ default: m.ThreadsPage })));
36
36
  const TelemetryPage = lazy(() => import("./components/telemetry/TelemetryPage").then(m => ({ default: m.TelemetryPage })));
37
37
  const ConnectionsPage = lazy(() => import("./components/connections/ConnectionsPage").then(m => ({ default: m.ConnectionsPage })));
38
+ const ActivityPage = lazy(() => import("./components/activity/ActivityPage").then(m => ({ default: m.ActivityPage })));
38
39
  const ApiDocsPage = lazy(() => import("./components/api/ApiDocsPage").then(m => ({ default: m.ApiDocsPage })));
39
40
 
40
41
  function AppContent() {
@@ -111,7 +112,7 @@ function AppContent() {
111
112
  };
112
113
 
113
114
  fetchTaskCount();
114
- }, [shouldFetchData, accessToken, currentProjectId, agents, statusChangeCounter, taskChangeCounter]);
115
+ }, [shouldFetchData, accessToken, currentProjectId, statusChangeCounter, taskChangeCounter]);
115
116
 
116
117
  // Form state
117
118
  const [newAgent, setNewAgent] = useState<NewAgentForm>({
@@ -260,14 +261,6 @@ function AppContent() {
260
261
  <Suspense fallback={<LoadingSpinner />}>
261
262
  {route === "settings" && <SettingsPage />}
262
263
 
263
- {route === "activity" && (
264
- <ActivityPage
265
- agents={agents}
266
- loading={loading}
267
- onNavigate={setRoute}
268
- />
269
- )}
270
-
271
264
  {route === "agents" && (
272
265
  <AgentsView
273
266
  agents={agents}
@@ -295,6 +288,21 @@ function AppContent() {
295
288
  />
296
289
  )}
297
290
 
291
+ {route === "threads" && (
292
+ <ThreadsPage
293
+ agents={agents}
294
+ onNavigate={setRoute}
295
+ />
296
+ )}
297
+
298
+ {route === "activity" && (
299
+ <ActivityPage
300
+ agents={agents}
301
+ loading={loading}
302
+ onNavigate={handleNavigate}
303
+ />
304
+ )}
305
+
298
306
  {route === "tasks" && <TasksPage />}
299
307
 
300
308
  {route === "connections" && <ConnectionsPage />}