apteva 0.4.32 → 0.4.44

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 (113) hide show
  1. package/dist/ActivityPage.c48n83h2.js +3 -0
  2. package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
  3. package/dist/App.09yb8t0b.js +1 -0
  4. package/dist/App.152mbs1r.js +4 -0
  5. package/dist/App.3a67nx9w.js +4 -0
  6. package/dist/App.9epx6785.js +4 -0
  7. package/dist/App.d8955awp.js +4 -0
  8. package/dist/App.drwb57jq.js +4 -0
  9. package/dist/App.gssbmajb.js +4 -0
  10. package/dist/App.qw70pc29.js +53 -0
  11. package/dist/App.qzbx5wtj.js +4 -0
  12. package/dist/App.r5serxkt.js +8 -0
  13. package/dist/App.tpmp9020.js +20 -0
  14. package/dist/App.v2wb4d7d.js +61 -0
  15. package/dist/App.vxmaaj0m.js +13 -0
  16. package/dist/App.w4p2tda9.js +4 -0
  17. package/dist/App.wv2ng55q.js +221 -0
  18. package/dist/App.yncnrn0f.js +4 -0
  19. package/dist/ConnectionsPage.k6cspyqq.js +3 -0
  20. package/dist/McpPage.cdxm48xj.js +3 -0
  21. package/dist/SettingsPage.evpv7c2y.js +3 -0
  22. package/dist/SkillsPage.pvzp6c1a.js +3 -0
  23. package/dist/TasksPage.6jnvbpsy.js +3 -0
  24. package/dist/TelemetryPage.t7vk24zc.js +3 -0
  25. package/dist/TestsPage.5x6658aa.js +3 -0
  26. package/dist/ThreadsPage.3fvhtevh.js +3 -0
  27. package/dist/apteva-kit.css +1 -1
  28. package/dist/index.html +1 -1
  29. package/dist/styles.css +1 -1
  30. package/package.json +10 -9
  31. package/src/crypto.ts +4 -3
  32. package/src/db.ts +171 -36
  33. package/src/integrations/agentdojo.ts +95 -12
  34. package/src/integrations/index.ts +7 -0
  35. package/src/mcp-platform.ts +870 -142
  36. package/src/openapi.ts +96 -0
  37. package/src/providers.ts +60 -34
  38. package/src/routes/api/agent-utils.ts +59 -47
  39. package/src/routes/api/agents.ts +71 -2
  40. package/src/routes/api/integrations.ts +11 -5
  41. package/src/routes/api/mcp.ts +5 -4
  42. package/src/routes/api/meta-agent.ts +37 -1
  43. package/src/routes/api/projects.ts +3 -3
  44. package/src/routes/api/providers.ts +121 -30
  45. package/src/routes/api/skills.ts +2 -3
  46. package/src/routes/api/system.ts +98 -14
  47. package/src/routes/api/telemetry.ts +19 -1
  48. package/src/routes/share.ts +85 -0
  49. package/src/server.ts +43 -32
  50. package/src/triggers/agentdojo.ts +2 -2
  51. package/src/web/App.tsx +107 -21
  52. package/src/web/components/activity/ActivityPage.tsx +242 -389
  53. package/src/web/components/agents/AgentCard.tsx +19 -27
  54. package/src/web/components/agents/AgentPanel.tsx +358 -198
  55. package/src/web/components/agents/AgentsView.tsx +4 -4
  56. package/src/web/components/agents/CreateAgentModal.tsx +21 -79
  57. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  58. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  59. package/src/web/components/auth/LoginPage.tsx +10 -10
  60. package/src/web/components/common/Icons.tsx +8 -0
  61. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  62. package/src/web/components/common/Modal.tsx +8 -8
  63. package/src/web/components/common/Select.tsx +11 -10
  64. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  65. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  66. package/src/web/components/connections/OverviewTab.tsx +13 -13
  67. package/src/web/components/connections/TriggersTab.tsx +99 -99
  68. package/src/web/components/dashboard/Dashboard.tsx +177 -52
  69. package/src/web/components/index.ts +1 -1
  70. package/src/web/components/layout/Header.tsx +50 -34
  71. package/src/web/components/layout/Sidebar.tsx +41 -16
  72. package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
  73. package/src/web/components/mcp/McpPage.tsx +218 -209
  74. package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
  75. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  76. package/src/web/components/settings/SettingsPage.tsx +389 -221
  77. package/src/web/components/skills/SkillsPage.tsx +88 -88
  78. package/src/web/components/tasks/TasksPage.tsx +385 -68
  79. package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
  80. package/src/web/components/tests/TestsPage.tsx +50 -50
  81. package/src/web/components/threads/ThreadsPage.tsx +315 -0
  82. package/src/web/context/AuthContext.tsx +3 -3
  83. package/src/web/context/ProjectContext.tsx +8 -3
  84. package/src/web/context/TelemetryContext.tsx +24 -6
  85. package/src/web/context/ThemeContext.tsx +69 -0
  86. package/src/web/context/index.ts +3 -1
  87. package/src/web/styles.css +25 -7
  88. package/src/web/themes.ts +99 -0
  89. package/src/web/types.ts +4 -7
  90. package/dist/ActivityPage.41nbye4r.js +0 -3
  91. package/dist/ApiDocsPage.4smnt8m3.js +0 -4
  92. package/dist/App.0sbax9et.js +0 -4
  93. package/dist/App.0ws427h8.js +0 -4
  94. package/dist/App.6q6bar8b.js +0 -4
  95. package/dist/App.80301vdb.js +0 -4
  96. package/dist/App.af2wg84v.js +0 -267
  97. package/dist/App.ca1rz1ph.js +0 -4
  98. package/dist/App.ensa6z0r.js +0 -4
  99. package/dist/App.f8g7tych.js +0 -13
  100. package/dist/App.mvtqv6qc.js +0 -20
  101. package/dist/App.ncgc9cxy.js +0 -4
  102. package/dist/App.p02f4ret.js +0 -1
  103. package/dist/App.p0fb1pds.js +0 -4
  104. package/dist/App.pmaq48sj.js +0 -4
  105. package/dist/App.yv87t9m5.js +0 -4
  106. package/dist/App.zjmfm8p6.js +0 -4
  107. package/dist/ConnectionsPage.anb3rv9a.js +0 -3
  108. package/dist/McpPage.y396h6fy.js +0 -3
  109. package/dist/SettingsPage.p1hc60gk.js +0 -3
  110. package/dist/SkillsPage.yj3xdsay.js +0 -3
  111. package/dist/TasksPage.sjv0khtv.js +0 -3
  112. package/dist/TelemetryPage.2qm4w16r.js +0 -3
  113. package/dist/TestsPage.zzs4qfj8.js +0 -3
@@ -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
@@ -133,7 +149,7 @@ export function Dashboard({
133
149
  <StatCard label="Agents" value={filteredAgents.length} subValue={`${filteredRunningCount} running`} />
134
150
  <StatCard label="Tasks" value={taskStats.total} subValue={`${taskStats.pending} pending`} />
135
151
  <StatCard label="Completed" value={taskStats.completed} color="text-green-400" />
136
- <StatCard label="Providers" value={configuredProviders.length} color="text-[#f97316]" />
152
+ <StatCard label="Providers" value={configuredProviders.length} color="text-[var(--color-accent)]" />
137
153
  </div>
138
154
 
139
155
  <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
@@ -144,13 +160,19 @@ export function Dashboard({
144
160
  onAction={() => onNavigate("agents")}
145
161
  >
146
162
  {loading ? (
147
- <div className="p-4 text-center text-[#666]">Loading...</div>
163
+ <div className="p-4 text-center text-[var(--color-text-muted)]">Loading...</div>
148
164
  ) : filteredAgents.length === 0 ? (
149
- <div className="p-4 text-center text-[#666]">No agents yet</div>
165
+ <div className="p-4 text-center text-[var(--color-text-muted)]">No agents yet</div>
150
166
  ) : (
151
- <div className="divide-y divide-[#1a1a1a]">
167
+ <div className="divide-y divide-[var(--color-border)]">
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
  )}
@@ -163,12 +185,12 @@ export function Dashboard({
163
185
  onAction={() => onNavigate("telemetry")}
164
186
  >
165
187
  {activities.length === 0 ? (
166
- <div className="p-4 text-center text-[#666]">
188
+ <div className="p-4 text-center text-[var(--color-text-muted)]">
167
189
  <p>No activity yet</p>
168
- <p className="text-sm text-[#444] mt-1">Agent activity will appear here in real-time</p>
190
+ <p className="text-sm text-[var(--color-text-faint)] mt-1">Agent activity will appear here in real-time</p>
169
191
  </div>
170
192
  ) : (
171
- <div className="divide-y divide-[#1a1a1a]">
193
+ <div className="divide-y divide-[var(--color-border)]">
172
194
  {activities.map((evt) => (
173
195
  <ActivityItem
174
196
  key={evt.id}
@@ -188,12 +210,12 @@ export function Dashboard({
188
210
  onAction={() => onNavigate("tasks")}
189
211
  >
190
212
  {filteredTasks.length === 0 ? (
191
- <div className="p-4 text-center text-[#666]">
213
+ <div className="p-4 text-center text-[var(--color-text-muted)]">
192
214
  <p>No tasks yet</p>
193
- <p className="text-sm text-[#444] mt-1">Tasks will appear when agents create them</p>
215
+ <p className="text-sm text-[var(--color-text-faint)] mt-1">Tasks will appear when agents create them</p>
194
216
  </div>
195
217
  ) : (
196
- <div className="divide-y divide-[#1a1a1a]">
218
+ <div className="divide-y divide-[var(--color-border)]">
197
219
  {filteredTasks.slice(0, 5).map((task) => (
198
220
  <div
199
221
  key={`${task.agentId}-${task.id}`}
@@ -201,16 +223,16 @@ export function Dashboard({
201
223
  >
202
224
  <div className="flex-1 min-w-0">
203
225
  <p className="font-medium truncate">{task.title}</p>
204
- <p className="text-sm text-[#666]">
226
+ <p className="text-sm text-[var(--color-text-muted)]">
205
227
  {task.agentName}
206
228
  {task.recurrence && (
207
- <span className="ml-1 text-[#555]">· {formatCronShort(task.recurrence)}</span>
229
+ <span className="ml-1 text-[var(--color-text-faint)]">· {formatCronShort(task.recurrence)}</span>
208
230
  )}
209
231
  {task.next_run && (
210
- <span className="ml-1 text-[#f97316]">· {formatRelativeShort(task.next_run)}</span>
232
+ <span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.next_run)}</span>
211
233
  )}
212
234
  {!task.next_run && task.execute_at && (
213
- <span className="ml-1 text-[#f97316]">· {formatRelativeShort(task.execute_at)}</span>
235
+ <span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.execute_at)}</span>
214
236
  )}
215
237
  </p>
216
238
  </div>
@@ -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
  }
@@ -234,10 +264,10 @@ interface StatCardProps {
234
264
 
235
265
  function StatCard({ label, value, subValue, color }: StatCardProps) {
236
266
  return (
237
- <div className="bg-[#111] rounded p-4 border border-[#1a1a1a]">
238
- <p className="text-sm text-[#666] mb-1">{label}</p>
267
+ <div className="bg-[var(--color-surface)] rounded p-4 border border-[var(--color-border)]">
268
+ <p className="text-sm text-[var(--color-text-muted)] mb-1">{label}</p>
239
269
  <p className={`text-2xl font-semibold ${color || ''}`}>{value}</p>
240
- {subValue && <p className="text-xs text-[#555] mt-1">{subValue}</p>}
270
+ {subValue && <p className="text-xs text-[var(--color-text-faint)] mt-1">{subValue}</p>}
241
271
  </div>
242
272
  );
243
273
  }
@@ -251,8 +281,8 @@ interface DashboardCardProps {
251
281
 
252
282
  function DashboardCard({ title, actionLabel, onAction, children }: DashboardCardProps) {
253
283
  return (
254
- <div className="bg-[#111] rounded border border-[#1a1a1a] overflow-hidden">
255
- <div className="px-4 py-3 border-b border-[#1a1a1a] flex items-center justify-between">
284
+ <div className="bg-[var(--color-surface)] rounded border border-[var(--color-border)] overflow-hidden">
285
+ <div className="px-4 py-3 border-b border-[var(--color-border)] flex items-center justify-between">
256
286
  <h3 className="font-semibold">{title}</h3>
257
287
  <button
258
288
  onClick={onAction}
@@ -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-[var(--color-surface-raised)] 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-[var(--color-scrollbar)]"
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-[var(--color-text-muted)]">
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-[var(--color-text-faint)]">·</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-[var(--color-accent)] hover:bg-[var(--color-accent-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
  }
@@ -320,9 +367,9 @@ function ActivityItem({ activity, agentName, timestamp }: { activity: string; ag
320
367
  return (
321
368
  <div className="px-4 py-3">
322
369
  <p className="text-sm truncate">{activity}</p>
323
- <div className="flex items-center gap-2 text-xs text-[#555] mt-1">
324
- <span className="text-[#666]">{agentName}</span>
325
- <span className="text-[#444]">&middot;</span>
370
+ <div className="flex items-center gap-2 text-xs text-[var(--color-text-faint)] mt-1">
371
+ <span className="text-[var(--color-text-muted)]">{agentName}</span>
372
+ <span className="text-[var(--color-text-faint)]">&middot;</span>
326
373
  <span>{timeAgo(timestamp)}</span>
327
374
  </div>
328
375
  </div>
@@ -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-[var(--color-surface)] border border-[var(--color-border-light)] 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-[var(--color-text-muted)] hover:text-[var(--color-text)] 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-[var(--color-text-faint)] 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-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-[var(--color-accent)] placeholder-[#444] disabled:opacity-50"
458
+ />
459
+ <button
460
+ onClick={handleSend}
461
+ disabled={sending || !message.trim()}
462
+ className="px-4 py-2.5 bg-[var(--color-accent)] text-black rounded-lg text-sm font-medium hover:bg-[var(--color-accent-hover)] 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";
@@ -134,24 +134,25 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
134
134
  };
135
135
 
136
136
  const getProjectColor = () => {
137
- if (currentProjectId === null) return "#666";
138
- if (currentProjectId === "unassigned") return "#888";
137
+ if (currentProjectId === null) return "var(--color-text-muted)";
138
+ if (currentProjectId === "unassigned") return "var(--color-text-secondary)";
139
139
  return currentProject?.color || "#6366f1";
140
140
  };
141
141
 
142
142
  return (
143
- <header className="border-b border-[#1a1a1a] px-4 md:px-6 py-4 flex-shrink-0">
143
+ <header className="px-4 md:px-6 py-4 flex-shrink-0" style={{ borderBottom: "1px solid var(--color-border)" }}>
144
144
  <div className="flex items-center justify-between">
145
145
  <div className="flex items-center gap-3">
146
146
  {/* Hamburger menu button - mobile only */}
147
147
  <button
148
148
  onClick={onMenuClick}
149
- className="p-2 -ml-2 text-[#666] hover:text-[#e0e0e0] transition md:hidden"
149
+ className="p-2 -ml-2 transition md:hidden"
150
+ style={{ color: "var(--color-text-muted)" }}
150
151
  >
151
152
  <MenuIcon />
152
153
  </button>
153
154
  <div className="flex items-center gap-2">
154
- <span className="text-[#f97316]">&gt;_</span>
155
+ <span style={{ color: "var(--color-accent)" }}>&gt;_</span>
155
156
  <span className="text-xl tracking-wider">apteva</span>
156
157
  </div>
157
158
 
@@ -160,7 +161,8 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
160
161
  <div className="relative ml-2 md:ml-4">
161
162
  <button
162
163
  onClick={() => setShowProjectMenu(!showProjectMenu)}
163
- className="flex items-center gap-2 px-3 py-1.5 rounded border border-[#222] bg-[#111] hover:bg-[#1a1a1a] transition text-sm"
164
+ className="flex items-center gap-2 px-3 py-1.5 rounded transition text-sm"
165
+ style={{ border: "1px solid var(--color-border-light)", backgroundColor: "var(--color-surface)" }}
164
166
  >
165
167
  <span
166
168
  className="w-2.5 h-2.5 rounded-full"
@@ -172,43 +174,49 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
172
174
  <ChevronDownIcon />
173
175
  </button>
174
176
  {showProjectMenu && (
175
- <div className="absolute left-0 top-full mt-1 w-56 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50">
177
+ <div className="absolute left-0 top-full mt-1 w-56 rounded-lg shadow-xl z-50" style={{ backgroundColor: "var(--color-surface)", border: "1px solid var(--color-border-light)" }}>
176
178
  <div className="py-1 max-h-64 overflow-y-auto">
177
179
  <button
178
180
  onClick={() => handleProjectSelect(null)}
179
- className={`w-full px-4 py-2 text-left text-sm flex items-center gap-2 hover:bg-[#1a1a1a] transition ${
180
- currentProjectId === null ? "bg-[#1a1a1a] text-[#f97316]" : ""
181
- }`}
181
+ className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
182
+ style={{
183
+ backgroundColor: currentProjectId === null ? "var(--color-surface-raised)" : "transparent",
184
+ color: currentProjectId === null ? "var(--color-accent)" : "var(--color-text)",
185
+ }}
182
186
  >
183
- <span className="w-2.5 h-2.5 rounded-full bg-[#666]" />
187
+ <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-muted)" }} />
184
188
  All Projects
185
189
  </button>
186
190
  {projects.map(project => (
187
191
  <button
188
192
  key={project.id}
189
193
  onClick={() => handleProjectSelect(project.id)}
190
- className={`w-full px-4 py-2 text-left text-sm flex items-center gap-2 hover:bg-[#1a1a1a] transition ${
191
- currentProjectId === project.id ? "bg-[#1a1a1a] text-[#f97316]" : ""
192
- }`}
194
+ className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
195
+ style={{
196
+ backgroundColor: currentProjectId === project.id ? "var(--color-surface-raised)" : "transparent",
197
+ color: currentProjectId === project.id ? "var(--color-accent)" : "var(--color-text)",
198
+ }}
193
199
  >
194
200
  <span
195
201
  className="w-2.5 h-2.5 rounded-full flex-shrink-0"
196
202
  style={{ backgroundColor: project.color }}
197
203
  />
198
204
  <span className="truncate">{project.name}</span>
199
- <span className="ml-auto text-xs text-[#666]">{project.agentCount}</span>
205
+ <span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{project.agentCount}</span>
200
206
  </button>
201
207
  ))}
202
208
  {unassignedCount > 0 && (
203
209
  <button
204
210
  onClick={() => handleProjectSelect("unassigned")}
205
- className={`w-full px-4 py-2 text-left text-sm flex items-center gap-2 hover:bg-[#1a1a1a] transition ${
206
- currentProjectId === "unassigned" ? "bg-[#1a1a1a] text-[#f97316]" : ""
207
- }`}
211
+ className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
212
+ style={{
213
+ backgroundColor: currentProjectId === "unassigned" ? "var(--color-surface-raised)" : "transparent",
214
+ color: currentProjectId === "unassigned" ? "var(--color-accent)" : "var(--color-text)",
215
+ }}
208
216
  >
209
- <span className="w-2.5 h-2.5 rounded-full bg-[#888]" />
217
+ <span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-secondary)" }} />
210
218
  <span className="truncate">Unassigned</span>
211
- <span className="ml-auto text-xs text-[#666]">{unassignedCount}</span>
219
+ <span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{unassignedCount}</span>
212
220
  </button>
213
221
  )}
214
222
  </div>
@@ -222,7 +230,7 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
222
230
  <span
223
231
  className={`w-2 h-2 rounded-full ${connected ? "bg-green-400" : "bg-red-400"}`}
224
232
  />
225
- <span className="text-xs text-[#666] hidden sm:inline">
233
+ <span className="text-xs hidden sm:inline" style={{ color: "var(--color-text-muted)" }}>
226
234
  {connected ? "Live" : "Offline"}
227
235
  </span>
228
236
  </div>
@@ -230,7 +238,8 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
230
238
  <div className="relative">
231
239
  <button
232
240
  onClick={openNotifications}
233
- className="relative p-2 text-[#666] hover:text-[#e0e0e0] transition rounded hover:bg-[#1a1a1a]"
241
+ className="relative p-2 transition rounded"
242
+ style={{ color: "var(--color-text-muted)" }}
234
243
  >
235
244
  <BellIcon className="w-5 h-5" />
236
245
  {unseenCount > 0 && (
@@ -253,40 +262,47 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
253
262
  {showNotifications && (
254
263
  <>
255
264
  <div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
256
- <div className="absolute right-0 top-full mt-1 w-80 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50 max-h-96 overflow-y-auto">
257
- <div className="px-4 py-3 border-b border-[#222] flex items-center justify-between">
265
+ <div className="absolute right-0 top-full mt-1 w-80 rounded-lg shadow-xl z-50 max-h-96 overflow-y-auto" style={{ backgroundColor: "var(--color-surface)", border: "1px solid var(--color-border-light)" }}>
266
+ <div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom: "1px solid var(--color-border-light)" }}>
258
267
  <span className="text-sm font-medium">Notifications</span>
259
268
  {notifications.length > 0 && (
260
- <span className="text-xs text-[#666]">{notifications.length} recent</span>
269
+ <span className="text-xs" style={{ color: "var(--color-text-muted)" }}>{notifications.length} recent</span>
261
270
  )}
262
271
  </div>
263
272
  {notifications.length === 0 ? (
264
- <div className="px-4 py-8 text-center text-sm text-[#666]">
273
+ <div className="px-4 py-8 text-center text-sm" style={{ color: "var(--color-text-muted)" }}>
265
274
  No notifications
266
275
  </div>
267
276
  ) : (
268
277
  <div className="py-1">
269
278
  {notifications.map(n => (
270
- <div key={n.id} className={`px-4 py-3 hover:bg-[#1a1a1a] transition border-b border-[#1a1a1a] ${!n.seen ? "bg-[#0f0f0f]" : ""}`}>
279
+ <div key={n.id} className="px-4 py-3 transition" style={{
280
+ borderBottom: "1px solid var(--color-border)",
281
+ backgroundColor: !n.seen ? "var(--color-bg-secondary)" : "transparent",
282
+ }}>
271
283
  <div className="flex items-center gap-2 mb-1">
272
284
  <span className={`w-2 h-2 rounded-full flex-shrink-0 ${
273
285
  !n.seen
274
- ? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "bg-[#f97316]")
275
- : "bg-[#333]"
276
- }`} />
277
- <span className={`text-xs font-medium truncate ${!n.seen ? "text-[#ccc]" : "text-[#666]"}`}>
286
+ ? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "")
287
+ : ""
288
+ }`} style={{
289
+ backgroundColor: !n.seen
290
+ ? (n.level === "error" || n.category === "ERROR" ? undefined : "var(--color-accent)")
291
+ : "var(--color-surface-raised)",
292
+ }} />
293
+ <span className="text-xs font-medium truncate" style={{ color: !n.seen ? "var(--color-text)" : "var(--color-text-muted)" }}>
278
294
  {n.category === "system" && n.type === "agent_stopped" ? "Agent Stopped" :
279
295
  n.category === "ERROR" ? "Error" :
280
296
  `${n.category} / ${n.type}`}
281
297
  </span>
282
- <span className="text-[10px] text-[#555] ml-auto flex-shrink-0">
298
+ <span className="text-[10px] ml-auto flex-shrink-0" style={{ color: "var(--color-text-faint)" }}>
283
299
  {formatNotifTime(n.timestamp)}
284
300
  </span>
285
301
  </div>
286
- <div className={`text-xs truncate ${!n.seen ? "text-[#aaa]" : "text-[#666]"}`}>
302
+ <div className="text-xs truncate" style={{ color: !n.seen ? "var(--color-text-secondary)" : "var(--color-text-muted)" }}>
287
303
  {n.error || (n.data as any)?.message || (n.data as any)?.error || `${n.type} event`}
288
304
  </div>
289
- <div className="text-[10px] text-[#555] mt-1">
305
+ <div className="text-[10px] mt-1" style={{ color: "var(--color-text-faint)" }}>
290
306
  {agentNames[n.agent_id] || n.agent_id.slice(0, 8)}
291
307
  </div>
292
308
  </div>