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,6 +1,6 @@
1
- import React, { useState, useEffect } from "react";
1
+ import React from "react";
2
2
  import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon, ActivityIcon } from "../common/Icons";
3
- import { useAgentActivity, useProjects, useAuth } from "../../context";
3
+ import { useAgentActivity, useProjects } from "../../context";
4
4
  import type { Agent, AgentFeatures } from "../../types";
5
5
 
6
6
  interface AgentCardProps {
@@ -22,22 +22,14 @@ const FEATURE_ICONS: { key: keyof AgentFeatures; icon: React.ComponentType<{ cla
22
22
  { key: "agents", icon: MultiAgentIcon, label: "Multi-Agent" },
23
23
  ];
24
24
 
25
- export function AgentCard({ agent, selected, onSelect, onToggle, showProject }: AgentCardProps) {
25
+ export const AgentCard = React.memo(function AgentCard({ agent, selected, onSelect, onToggle, showProject }: AgentCardProps) {
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
29
  const { isActive, label: activityLabel } = useAgentActivity(agent.id);
30
30
  const { projects } = useProjects();
31
- const { authFetch } = useAuth();
32
31
  const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
33
- const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
34
-
35
- useEffect(() => {
36
- authFetch(`/api/subscriptions?agent_id=${agent.id}`)
37
- .then(res => res.ok ? res.json() : { subscriptions: [] })
38
- .then(data => setSubscriptions(data.subscriptions || []))
39
- .catch(() => {});
40
- }, [agent.id, authFetch]);
32
+ const subscriptions = agent.subscriptions || [];
41
33
 
42
34
  return (
43
35
  <div
@@ -163,7 +155,7 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
163
155
  </button>
164
156
  </div>
165
157
  );
166
- }
158
+ });
167
159
 
168
160
  function StatusBadge({ status, isActive, activityLabel }: { status: Agent["status"]; isActive?: boolean; activityLabel?: string }) {
169
161
  if (status === "running" && isActive && activityLabel) {
@@ -233,6 +233,14 @@ export function TaskOnceIcon({ className = "w-4 h-4" }: IconProps) {
233
233
  );
234
234
  }
235
235
 
236
+ export function ThreadsIcon({ className = "w-5 h-5" }: IconProps) {
237
+ return (
238
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
239
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
240
+ </svg>
241
+ );
242
+ }
243
+
236
244
  export function BellIcon({ className = "w-5 h-5" }: IconProps) {
237
245
  return (
238
246
  <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -11,9 +11,10 @@ interface SelectProps {
11
11
  options: SelectOption[];
12
12
  onChange: (value: string) => void;
13
13
  placeholder?: string;
14
+ compact?: boolean;
14
15
  }
15
16
 
16
- export function Select({ value, options, onChange, placeholder = "Select..." }: SelectProps) {
17
+ export function Select({ value, options, onChange, placeholder = "Select...", compact }: SelectProps) {
17
18
  const [isOpen, setIsOpen] = useState(false);
18
19
  const ref = useRef<HTMLDivElement>(null);
19
20
 
@@ -34,7 +35,7 @@ export function Select({ value, options, onChange, placeholder = "Select..." }:
34
35
  <button
35
36
  type="button"
36
37
  onClick={() => setIsOpen(!isOpen)}
37
- className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-left flex items-center justify-between focus:outline-none focus:border-[#f97316] text-[#e0e0e0] hover:border-[#333] transition"
38
+ className={`w-full bg-[#0a0a0a] border border-[#222] rounded ${compact ? "px-2.5 py-1.5 text-sm" : "px-3 py-2"} text-left flex items-center justify-between focus:outline-none focus:border-[#f97316] text-[#e0e0e0] hover:border-[#333] transition`}
38
39
  >
39
40
  <span className={selectedOption ? "text-[#e0e0e0]" : "text-[#666]"}>
40
41
  {selectedOption ? (
@@ -59,7 +60,7 @@ export function Select({ value, options, onChange, placeholder = "Select..." }:
59
60
  onChange(option.value);
60
61
  setIsOpen(false);
61
62
  }}
62
- className={`w-full px-3 py-2 text-left flex items-center justify-between hover:bg-[#1a1a1a] transition ${
63
+ className={`w-full ${compact ? "px-2.5 py-1.5 text-sm" : "px-3 py-2"} text-left flex items-center justify-between hover:bg-[#1a1a1a] transition ${
63
64
  option.value === value ? "bg-[#1a1a1a] text-[#f97316]" : "text-[#e0e0e0]"
64
65
  }`}
65
66
  >
@@ -1,7 +1,9 @@
1
- import React, { useState, useEffect, useCallback, useMemo } from "react";
1
+ import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
2
2
  import { useAgentActivity, useAuth, useProjects, useTelemetryContext } from "../../context";
3
+ import { useTelemetry } from "../../context/TelemetryContext";
3
4
  import type { TelemetryEvent } from "../../context";
4
5
  import type { Agent, Provider, Route, DashboardStats, Task } from "../../types";
6
+ import { CloseIcon } from "../common/Icons";
5
7
 
6
8
  interface DashboardProps {
7
9
  agents: Agent[];
@@ -23,9 +25,12 @@ export function Dashboard({
23
25
  const { authFetch } = useAuth();
24
26
  const { currentProjectId } = useProjects();
25
27
  const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
28
+ const { events: taskTelemetryEvents } = useTelemetry({ category: "TASK" });
29
+ const lastProcessedTaskEventRef = useRef<string | null>(null);
26
30
  const [stats, setStats] = useState<DashboardStats | null>(null);
27
31
  const [recentTasks, setRecentTasks] = useState<Task[]>([]);
28
32
  const [historicalActivities, setHistoricalActivities] = useState<TelemetryEvent[]>([]);
33
+ const [quickMessageAgent, setQuickMessageAgent] = useState<Agent | null>(null);
29
34
 
30
35
  // Filter agents by current project
31
36
  const filteredAgents = useMemo(() => {
@@ -75,6 +80,17 @@ export function Dashboard({
75
80
  fetchDashboardData();
76
81
  }, [fetchDashboardData, statusChangeCounter]);
77
82
 
83
+ // Real-time task updates from telemetry
84
+ useEffect(() => {
85
+ if (!taskTelemetryEvents.length) return;
86
+ const latestEvent = taskTelemetryEvents[0];
87
+ if (!latestEvent || latestEvent.id === lastProcessedTaskEventRef.current) return;
88
+ if (latestEvent.type === "task_created" || latestEvent.type === "task_updated" || latestEvent.type === "task_deleted") {
89
+ lastProcessedTaskEventRef.current = latestEvent.id;
90
+ fetchDashboardData();
91
+ }
92
+ }, [taskTelemetryEvents, fetchDashboardData]);
93
+
78
94
  // Filter tasks by project agents and sort by next execution (soonest first)
79
95
  const filteredTasks = useMemo(() => {
80
96
  let list = currentProjectId
@@ -98,11 +114,11 @@ export function Dashboard({
98
114
 
99
115
  // Merge real-time + historical thread_activity events, deduplicate
100
116
  const activities = useMemo(() => {
101
- const realtimeActivities = realtimeEvents.filter(e => e.type === "thread_activity");
117
+ const realtimeActivities = realtimeEvents.filter(e => e.type === "thread_activity" && !e.data?.parent_id);
102
118
  const seen = new Set(realtimeActivities.map(e => e.id));
103
119
  const merged = [...realtimeActivities];
104
120
  for (const evt of historicalActivities) {
105
- if (!seen.has(evt.id)) {
121
+ if (!seen.has(evt.id) && !evt.data?.parent_id) {
106
122
  merged.push(evt);
107
123
  seen.add(evt.id);
108
124
  }
@@ -114,7 +130,7 @@ export function Dashboard({
114
130
  }
115
131
  // Sort newest first
116
132
  filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
117
- return filtered.slice(0, 8);
133
+ return filtered.slice(0, 12);
118
134
  }, [realtimeEvents, historicalActivities, currentProjectId, projectAgentIds]);
119
135
 
120
136
  // Build agent name lookup
@@ -150,7 +166,13 @@ export function Dashboard({
150
166
  ) : (
151
167
  <div className="divide-y divide-[#1a1a1a]">
152
168
  {filteredAgents.slice(0, 5).map((agent) => (
153
- <AgentListItem key={agent.id} agent={agent} onSelect={() => onSelectAgent(agent)} showProject={!currentProjectId} />
169
+ <AgentListItem
170
+ key={agent.id}
171
+ agent={agent}
172
+ onSelect={() => onSelectAgent(agent)}
173
+ onMessage={agent.status === "running" ? () => setQuickMessageAgent(agent) : undefined}
174
+ showProject={!currentProjectId}
175
+ />
154
176
  ))}
155
177
  </div>
156
178
  )}
@@ -221,6 +243,14 @@ export function Dashboard({
221
243
  )}
222
244
  </DashboardCard>
223
245
  </div>
246
+
247
+ {/* Quick Message Modal */}
248
+ {quickMessageAgent && (
249
+ <QuickMessageModal
250
+ agent={quickMessageAgent}
251
+ onClose={() => setQuickMessageAgent(null)}
252
+ />
253
+ )}
224
254
  </div>
225
255
  );
226
256
  }
@@ -266,40 +296,57 @@ function DashboardCard({ title, actionLabel, onAction, children }: DashboardCard
266
296
  );
267
297
  }
268
298
 
269
- function AgentListItem({ agent, onSelect, showProject }: { agent: Agent; onSelect: () => void; showProject?: boolean }) {
270
- const { isActive } = useAgentActivity(agent.id);
299
+ function AgentListItem({ agent, onSelect, onMessage, showProject }: { agent: Agent; onSelect: () => void; onMessage?: () => void; showProject?: boolean }) {
300
+ const { isActive, label } = useAgentActivity(agent.id);
271
301
  const { projects } = useProjects();
272
302
  const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
273
303
 
274
304
  return (
275
305
  <div
276
306
  onClick={onSelect}
277
- className="px-4 py-3 hover:bg-[#1a1a1a] cursor-pointer flex items-center justify-between"
307
+ className="px-4 py-3 hover:bg-[#1a1a1a] cursor-pointer flex items-center justify-between group"
278
308
  >
279
- <div className="flex-1 min-w-0">
280
- <p className="font-medium">{agent.name}</p>
281
- <div className="flex items-center gap-2 text-sm text-[#666]">
282
- <span>{agent.provider}</span>
283
- {showProject && project && (
284
- <>
285
- <span className="text-[#444]">·</span>
286
- <span className="flex items-center gap-1">
287
- <span className="w-2 h-2 rounded-full" style={{ backgroundColor: project.color }} />
288
- {project.name}
289
- </span>
290
- </>
291
- )}
309
+ <div className="flex items-center gap-3 flex-1 min-w-0">
310
+ <span
311
+ className={`w-2 h-2 rounded-full flex-shrink-0 ${
312
+ agent.status === "running"
313
+ ? isActive
314
+ ? "bg-green-400 animate-pulse"
315
+ : "bg-[#3b82f6]"
316
+ : "bg-[#444]"
317
+ }`}
318
+ />
319
+ <div className="flex-1 min-w-0">
320
+ <p className="font-medium truncate">{agent.name}</p>
321
+ <div className="flex items-center gap-2 text-sm text-[#666]">
322
+ {isActive && label ? (
323
+ <span className="text-green-400 truncate">{label}</span>
324
+ ) : (
325
+ <span>{agent.provider} · {agent.status === "running" ? "idle" : "stopped"}</span>
326
+ )}
327
+ {showProject && project && (
328
+ <>
329
+ <span className="text-[#444]">·</span>
330
+ <span className="flex items-center gap-1">
331
+ <span className="w-2 h-2 rounded-full" style={{ backgroundColor: project.color }} />
332
+ {project.name}
333
+ </span>
334
+ </>
335
+ )}
336
+ </div>
292
337
  </div>
293
338
  </div>
294
- <span
295
- className={`w-2 h-2 rounded-full flex-shrink-0 ${
296
- agent.status === "running"
297
- ? isActive
298
- ? "bg-green-400 animate-pulse"
299
- : "bg-[#3b82f6]"
300
- : "bg-[#444]"
301
- }`}
302
- />
339
+ {onMessage && (
340
+ <button
341
+ onClick={(e) => { e.stopPropagation(); onMessage(); }}
342
+ className="opacity-0 group-hover:opacity-100 transition px-2 py-1 text-xs text-[#f97316] hover:bg-[#f97316]/10 rounded"
343
+ title="Send message"
344
+ >
345
+ <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
346
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
347
+ </svg>
348
+ </button>
349
+ )}
303
350
  </div>
304
351
  );
305
352
  }
@@ -345,6 +392,84 @@ function TaskStatusBadge({ status }: { status: Task["status"] }) {
345
392
  );
346
393
  }
347
394
 
395
+ // --- Quick Message Modal ---
396
+
397
+ function QuickMessageModal({ agent, onClose }: { agent: Agent; onClose: () => void }) {
398
+ const { authFetch } = useAuth();
399
+ const [message, setMessage] = useState("");
400
+ const [sending, setSending] = useState(false);
401
+ const [sent, setSent] = useState(false);
402
+ const inputRef = useRef<HTMLInputElement>(null);
403
+
404
+ useEffect(() => {
405
+ inputRef.current?.focus();
406
+ }, []);
407
+
408
+ const handleSend = async () => {
409
+ if (!message.trim() || sending) return;
410
+ setSending(true);
411
+ try {
412
+ const res = await authFetch(`/api/agents/${agent.id}/chat`, {
413
+ method: "POST",
414
+ headers: { "Content-Type": "application/json" },
415
+ body: JSON.stringify({ message: message.trim(), agent_id: agent.id }),
416
+ });
417
+ if (res.ok) {
418
+ setSent(true);
419
+ setTimeout(onClose, 1200);
420
+ }
421
+ } catch {
422
+ // ignore
423
+ } finally {
424
+ setSending(false);
425
+ }
426
+ };
427
+
428
+ return (
429
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
430
+ <div className="absolute inset-0 bg-black/60" onClick={onClose} />
431
+ <div className="relative bg-[#111] border border-[#222] rounded-xl shadow-2xl w-full max-w-md mx-4 p-5">
432
+ <div className="flex items-center justify-between mb-4">
433
+ <div className="flex items-center gap-3">
434
+ <span className="w-2.5 h-2.5 rounded-full bg-green-400 animate-pulse" />
435
+ <h3 className="font-medium">{agent.name}</h3>
436
+ </div>
437
+ <button onClick={onClose} className="text-[#666] hover:text-[#e0e0e0] transition">
438
+ <CloseIcon />
439
+ </button>
440
+ </div>
441
+
442
+ {sent ? (
443
+ <div className="py-6 text-center">
444
+ <p className="text-green-400 font-medium">Message sent</p>
445
+ <p className="text-sm text-[#555] mt-1">The agent will process your message</p>
446
+ </div>
447
+ ) : (
448
+ <div className="flex gap-2">
449
+ <input
450
+ ref={inputRef}
451
+ type="text"
452
+ value={message}
453
+ onChange={e => setMessage(e.target.value)}
454
+ onKeyDown={e => e.key === "Enter" && handleSend()}
455
+ placeholder={`Message ${agent.name}...`}
456
+ disabled={sending}
457
+ className="flex-1 bg-[#0a0a0a] border border-[#222] rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-[#f97316] placeholder-[#444] disabled:opacity-50"
458
+ />
459
+ <button
460
+ onClick={handleSend}
461
+ disabled={sending || !message.trim()}
462
+ className="px-4 py-2.5 bg-[#f97316] text-black rounded-lg text-sm font-medium hover:bg-[#fb923c] transition disabled:opacity-30"
463
+ >
464
+ {sending ? "..." : "Send"}
465
+ </button>
466
+ </div>
467
+ )}
468
+ </div>
469
+ </div>
470
+ );
471
+ }
472
+
348
473
  // --- Task sorting helper ---
349
474
 
350
475
  function statusPriority(task: Task): number {
@@ -12,7 +12,7 @@ export { OnboardingWizard } from "./onboarding";
12
12
  export { SettingsPage } from "./settings";
13
13
  export { AgentCard, CreateAgentModal, AgentPanel, AgentsView } from "./agents";
14
14
  export { Dashboard } from "./dashboard";
15
- export { ActivityPage } from "./activity";
15
+ export { ThreadsPage } from "./threads/ThreadsPage";
16
16
  export { TasksPage } from "./tasks";
17
17
  export { McpPage } from "./mcp";
18
18
  export { SkillsPage } from "./skills/SkillsPage";
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from "react";
2
- import { DashboardIcon, ActivityIcon, AgentsIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
2
+ import { DashboardIcon, ThreadsIcon, AgentsIcon, ActivityIcon, TasksIcon, ConnectionsIcon, McpIcon, SkillsIcon, TestsIcon, TelemetryIcon, ApiIcon, SettingsIcon, CloseIcon } from "../common/Icons";
3
3
  import { useAuth } from "../../context";
4
4
  import type { Route } from "../../types";
5
5
 
@@ -72,6 +72,12 @@ export function Sidebar({ route, agentCount, taskCount, onNavigate, isOpen, onCl
72
72
  onClick={() => handleNavigate("agents")}
73
73
  badge={agentCount > 0 ? String(agentCount) : undefined}
74
74
  />
75
+ <NavButton
76
+ icon={<ThreadsIcon />}
77
+ label="Threads"
78
+ active={route === "threads"}
79
+ onClick={() => handleNavigate("threads")}
80
+ />
75
81
  <NavButton
76
82
  icon={<ActivityIcon />}
77
83
  label="Activity"