apteva 0.4.41 → 0.4.48

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 (103) hide show
  1. package/dist/ActivityPage.sw9p594m.js +3 -0
  2. package/dist/ApiDocsPage.90e03bz7.js +4 -0
  3. package/dist/App.0ws87fpx.js +53 -0
  4. package/dist/App.3vnrera5.js +4 -0
  5. package/dist/App.94x6mh7f.js +20 -0
  6. package/dist/{App.7fb3e7mp.js → App.9sryp183.js} +1 -1
  7. package/dist/App.d9tny4t0.js +221 -0
  8. package/dist/App.jhb45d7r.js +8 -0
  9. package/dist/App.p7jjw1zf.js +4 -0
  10. package/dist/App.pfbdzrhh.js +4 -0
  11. package/dist/App.stgng5bx.js +13 -0
  12. package/dist/App.tm3k7h4b.js +4 -0
  13. package/dist/App.vkg121c6.js +4 -0
  14. package/dist/App.wghtdzsk.js +1 -0
  15. package/dist/App.xf7wsckg.js +4 -0
  16. package/dist/App.xva0tfzh.js +4 -0
  17. package/dist/App.ysxy7akk.js +61 -0
  18. package/dist/App.yzkh4gq2.js +4 -0
  19. package/dist/ConnectionsPage.q5f9fd37.js +3 -0
  20. package/dist/McpPage.f3ccrezb.js +3 -0
  21. package/dist/SettingsPage.3sqx6wm4.js +3 -0
  22. package/dist/SkillsPage.whxnez67.js +3 -0
  23. package/dist/TasksPage.zp4jfevw.js +3 -0
  24. package/dist/TelemetryPage.a9fmxq87.js +3 -0
  25. package/dist/TestsPage.18krj0d1.js +3 -0
  26. package/dist/ThreadsPage.nnphgy98.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 +11 -10
  31. package/src/db.ts +61 -13
  32. package/src/integrations/agentdojo.ts +1 -0
  33. package/src/mcp-platform.ts +418 -63
  34. package/src/openapi.ts +96 -0
  35. package/src/providers.ts +55 -24
  36. package/src/routes/api/agent-utils.ts +25 -4
  37. package/src/routes/api/agents.ts +19 -1
  38. package/src/routes/api/meta-agent.ts +2 -0
  39. package/src/routes/api/system.ts +90 -1
  40. package/src/routes/api/telemetry.ts +38 -2
  41. package/src/routes/share.ts +85 -0
  42. package/src/server.ts +64 -1
  43. package/src/web/App.tsx +89 -11
  44. package/src/web/components/activity/ActivityPage.tsx +14 -14
  45. package/src/web/components/agents/AgentCard.tsx +19 -17
  46. package/src/web/components/agents/AgentPanel.tsx +541 -220
  47. package/src/web/components/agents/AgentsView.tsx +4 -4
  48. package/src/web/components/agents/CreateAgentModal.tsx +24 -82
  49. package/src/web/components/api/ApiDocsPage.tsx +66 -66
  50. package/src/web/components/auth/CreateAccountStep.tsx +16 -16
  51. package/src/web/components/auth/LoginPage.tsx +10 -10
  52. package/src/web/components/common/LoadingSpinner.tsx +2 -2
  53. package/src/web/components/common/Modal.tsx +9 -9
  54. package/src/web/components/common/Select.tsx +9 -9
  55. package/src/web/components/connections/ConnectionsPage.tsx +4 -4
  56. package/src/web/components/connections/IntegrationsTab.tsx +18 -18
  57. package/src/web/components/connections/OverviewTab.tsx +13 -13
  58. package/src/web/components/connections/TriggersTab.tsx +99 -99
  59. package/src/web/components/dashboard/Dashboard.tsx +32 -32
  60. package/src/web/components/layout/Header.tsx +50 -34
  61. package/src/web/components/layout/Sidebar.tsx +35 -15
  62. package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
  63. package/src/web/components/mcp/McpPage.tsx +208 -208
  64. package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
  65. package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
  66. package/src/web/components/settings/SettingsPage.tsx +291 -175
  67. package/src/web/components/skills/SkillsPage.tsx +88 -88
  68. package/src/web/components/tasks/TasksPage.tsx +539 -78
  69. package/src/web/components/telemetry/TelemetryPage.tsx +405 -65
  70. package/src/web/components/tests/TestsPage.tsx +50 -50
  71. package/src/web/components/threads/ThreadsPage.tsx +23 -21
  72. package/src/web/context/ProjectContext.tsx +6 -1
  73. package/src/web/context/ThemeContext.tsx +90 -0
  74. package/src/web/context/index.ts +2 -0
  75. package/src/web/index.html +1 -6
  76. package/src/web/styles.css +52 -3
  77. package/src/web/themes.ts +162 -0
  78. package/src/web/types.ts +0 -4
  79. package/dist/ActivityPage.7907h64p.js +0 -3
  80. package/dist/ApiDocsPage.k3jjenpq.js +0 -4
  81. package/dist/App.01nq20st.js +0 -4
  82. package/dist/App.1maqvamf.js +0 -4
  83. package/dist/App.2yjrh32f.js +0 -4
  84. package/dist/App.3qw8nben.js +0 -20
  85. package/dist/App.7sy3wq8c.js +0 -4
  86. package/dist/App.apjrmctz.js +0 -57
  87. package/dist/App.av6t2yhe.js +0 -4
  88. package/dist/App.jqj5a094.js +0 -46
  89. package/dist/App.mc7xf85h.js +0 -4
  90. package/dist/App.myxqcj9x.js +0 -4
  91. package/dist/App.nm91r1mp.js +0 -13
  92. package/dist/App.p02f4ret.js +0 -1
  93. package/dist/App.qcknavjz.js +0 -221
  94. package/dist/App.vc7vfhg4.js +0 -4
  95. package/dist/App.z4s9zkw5.js +0 -4
  96. package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
  97. package/dist/McpPage.8vc97z0b.js +0 -3
  98. package/dist/SettingsPage.p61bz8kd.js +0 -3
  99. package/dist/SkillsPage.r9x43g3g.js +0 -3
  100. package/dist/TasksPage.1e0zkye4.js +0 -3
  101. package/dist/TelemetryPage.p9vbe4gf.js +0 -3
  102. package/dist/TestsPage.d4xy504e.js +0 -3
  103. package/dist/ThreadsPage.m016am3x.js +0 -3
@@ -13,15 +13,36 @@ interface TelemetryStats {
13
13
  total_errors: number;
14
14
  total_input_tokens: number;
15
15
  total_output_tokens: number;
16
+ total_cache_creation_tokens: number;
17
+ total_cache_read_tokens: number;
18
+ total_reasoning_tokens: number;
19
+ total_cost: number;
16
20
  }
17
21
 
18
22
  interface UsageByAgent {
19
23
  agent_id: string;
20
24
  input_tokens: number;
21
25
  output_tokens: number;
26
+ cache_creation_tokens: number;
27
+ cache_read_tokens: number;
28
+ reasoning_tokens: number;
22
29
  llm_calls: number;
23
30
  tool_calls: number;
24
31
  errors: number;
32
+ cost: number;
33
+ }
34
+
35
+ interface UsageByProject {
36
+ project_id: string | null;
37
+ input_tokens: number;
38
+ output_tokens: number;
39
+ cache_creation_tokens: number;
40
+ cache_read_tokens: number;
41
+ reasoning_tokens: number;
42
+ llm_calls: number;
43
+ tool_calls: number;
44
+ errors: number;
45
+ cost: number;
25
46
  }
26
47
 
27
48
  interface DailyUsage {
@@ -40,12 +61,18 @@ function extractEventStats(event: TelemetryEvent): {
40
61
  errors: number;
41
62
  input_tokens: number;
42
63
  output_tokens: number;
64
+ cache_creation_tokens: number;
65
+ cache_read_tokens: number;
66
+ reasoning_tokens: number;
43
67
  } {
44
68
  const isLlm = event.category === "LLM";
45
69
  const isTool = event.category === "TOOL";
46
70
  const isError = event.level === "error";
47
71
  const inputTokens = (event.data?.input_tokens as number) || 0;
48
72
  const outputTokens = (event.data?.output_tokens as number) || 0;
73
+ const cacheCreationTokens = (event.data?.cache_creation_tokens as number) || 0;
74
+ const cacheReadTokens = (event.data?.cache_read_tokens as number) || 0;
75
+ const reasoningTokens = (event.data?.reasoning_tokens as number) || 0;
49
76
 
50
77
  return {
51
78
  llm_calls: isLlm ? 1 : 0,
@@ -53,17 +80,21 @@ function extractEventStats(event: TelemetryEvent): {
53
80
  errors: isError ? 1 : 0,
54
81
  input_tokens: inputTokens,
55
82
  output_tokens: outputTokens,
83
+ cache_creation_tokens: cacheCreationTokens,
84
+ cache_read_tokens: cacheReadTokens,
85
+ reasoning_tokens: reasoningTokens,
56
86
  };
57
87
  }
58
88
 
59
89
  export function TelemetryPage() {
60
90
  const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
61
- const { currentProjectId, currentProject } = useProjects();
91
+ const { currentProjectId, currentProject, costTrackingEnabled, projectsEnabled, projects } = useProjects();
62
92
  const { authFetch } = useAuth();
63
93
  const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
64
94
  const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
65
95
  const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
66
96
  const [dailyUsage, setDailyUsage] = useState<DailyUsage[]>([]);
97
+ const [projectUsage, setProjectUsage] = useState<UsageByProject[]>([]);
67
98
  const [loading, setLoading] = useState(true);
68
99
  const [filter, setFilter] = useState({
69
100
  level: "",
@@ -74,6 +105,37 @@ export function TelemetryPage() {
74
105
  const [agents, setAgents] = useState<Array<{ id: string; name: string; projectId: string | null }>>([]);
75
106
  const [expandedEvent, setExpandedEvent] = useState<string | null>(null);
76
107
 
108
+ // Time range filter
109
+ const [timeRange, setTimeRange] = useState<string>("all");
110
+
111
+ // Sort state for usage table
112
+ type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "reasoning_tokens" | "errors" | "cost";
113
+ const [sortKey, setSortKey] = useState<SortKey>("cost");
114
+ const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
115
+
116
+ const handleSort = (key: SortKey) => {
117
+ if (sortKey === key) {
118
+ setSortDir(d => d === "asc" ? "desc" : "asc");
119
+ } else {
120
+ setSortKey(key);
121
+ setSortDir("desc");
122
+ }
123
+ };
124
+
125
+ // Sort state for project usage table
126
+ type ProjectSortKey = "project" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "reasoning_tokens" | "errors" | "cost";
127
+ const [projectSortKey, setProjectSortKey] = useState<ProjectSortKey>("cost");
128
+ const [projectSortDir, setProjectSortDir] = useState<"asc" | "desc">("desc");
129
+
130
+ const handleProjectSort = (key: ProjectSortKey) => {
131
+ if (projectSortKey === key) {
132
+ setProjectSortDir(d => d === "asc" ? "desc" : "asc");
133
+ } else {
134
+ setProjectSortKey(key);
135
+ setProjectSortDir("desc");
136
+ }
137
+ };
138
+
77
139
  // Track IDs that were in the fetched stats to avoid double-counting
78
140
  const countedEventIdsRef = useRef<Set<string>>(new Set());
79
141
 
@@ -105,16 +167,35 @@ export function TelemetryPage() {
105
167
  // Get agent IDs for the current project
106
168
  const projectAgentIds = useMemo(() => new Set(filteredAgents.map(a => a.id)), [filteredAgents]);
107
169
 
170
+ // Compute `since` ISO string from selected time range
171
+ const getSince = useCallback((): string | undefined => {
172
+ if (timeRange === "all") return undefined;
173
+ const now = new Date();
174
+ const ms: Record<string, number> = {
175
+ "1h": 3600000,
176
+ "6h": 6 * 3600000,
177
+ "24h": 24 * 3600000,
178
+ "7d": 7 * 24 * 3600000,
179
+ "30d": 30 * 24 * 3600000,
180
+ };
181
+ if (ms[timeRange]) {
182
+ return new Date(now.getTime() - ms[timeRange]).toISOString();
183
+ }
184
+ return undefined;
185
+ }, [timeRange]);
186
+
108
187
  // Fetch stats and historical data (less frequently now since we have real-time)
109
188
  const fetchData = async () => {
110
189
  setLoading(true);
111
190
  try {
112
191
  // Build project filter param
113
192
  const projectParam = currentProjectId === "unassigned" ? "null" : currentProjectId || "";
193
+ const since = getSince();
114
194
 
115
195
  // Fetch stats
116
196
  const statsParams = new URLSearchParams();
117
197
  if (projectParam) statsParams.set("project_id", projectParam);
198
+ if (since) statsParams.set("since", since);
118
199
  const statsRes = await authFetch(`/api/telemetry/stats${statsParams.toString() ? `?${statsParams}` : ""}`);
119
200
  const statsData = await statsRes.json();
120
201
  setFetchedStats(statsData.stats);
@@ -124,6 +205,7 @@ export function TelemetryPage() {
124
205
  if (filter.level) params.set("level", filter.level);
125
206
  if (filter.agent_id) params.set("agent_id", filter.agent_id);
126
207
  if (projectParam) params.set("project_id", projectParam);
208
+ if (since) params.set("since", since);
127
209
  params.set("limit", "100"); // Fetch more since we filter client-side
128
210
 
129
211
  const eventsRes = await authFetch(`/api/telemetry/events?${params}`);
@@ -138,6 +220,7 @@ export function TelemetryPage() {
138
220
  const usageParams = new URLSearchParams();
139
221
  usageParams.set("group_by", "agent");
140
222
  if (projectParam) usageParams.set("project_id", projectParam);
223
+ if (since) usageParams.set("since", since);
141
224
  const usageRes = await authFetch(`/api/telemetry/usage?${usageParams}`);
142
225
  const usageData = await usageRes.json();
143
226
  setFetchedUsage(usageData.usage || []);
@@ -146,6 +229,7 @@ export function TelemetryPage() {
146
229
  const dailyParams = new URLSearchParams();
147
230
  dailyParams.set("group_by", "day");
148
231
  if (projectParam) dailyParams.set("project_id", projectParam);
232
+ if (since) dailyParams.set("since", since);
149
233
  const dailyRes = await authFetch(`/api/telemetry/usage?${dailyParams}`);
150
234
  const dailyData = await dailyRes.json();
151
235
  // Sort by date ascending for charts
@@ -153,6 +237,18 @@ export function TelemetryPage() {
153
237
  a.date.localeCompare(b.date)
154
238
  );
155
239
  setDailyUsage(sorted);
240
+
241
+ // Fetch usage by project (only when viewing all projects and feature is enabled)
242
+ if (projectsEnabled && currentProjectId === null) {
243
+ const projParams = new URLSearchParams();
244
+ projParams.set("group_by", "project");
245
+ if (since) projParams.set("since", since);
246
+ const projRes = await authFetch(`/api/telemetry/usage?${projParams}`);
247
+ const projData = await projRes.json();
248
+ setProjectUsage(projData.usage || []);
249
+ } else {
250
+ setProjectUsage([]);
251
+ }
156
252
  } catch (e) {
157
253
  console.error("Failed to fetch telemetry:", e);
158
254
  }
@@ -161,7 +257,7 @@ export function TelemetryPage() {
161
257
 
162
258
  useEffect(() => {
163
259
  fetchData();
164
- }, [filter, currentProjectId, authFetch, statusChangeCounter]);
260
+ }, [filter, timeRange, currentProjectId, authFetch, statusChangeCounter]);
165
261
 
166
262
  // Compute real-time stats from new events (not already counted in fetched stats)
167
263
  const stats = useMemo(() => {
@@ -174,6 +270,9 @@ export function TelemetryPage() {
174
270
  let deltaErrors = 0;
175
271
  let deltaInputTokens = 0;
176
272
  let deltaOutputTokens = 0;
273
+ let deltaCacheCreationTokens = 0;
274
+ let deltaCacheReadTokens = 0;
275
+ let deltaReasoningTokens = 0;
177
276
 
178
277
  for (const event of realtimeEvents) {
179
278
  if (!countedEventIdsRef.current.has(event.id)) {
@@ -184,6 +283,9 @@ export function TelemetryPage() {
184
283
  deltaErrors += eventStats.errors;
185
284
  deltaInputTokens += eventStats.input_tokens;
186
285
  deltaOutputTokens += eventStats.output_tokens;
286
+ deltaCacheCreationTokens += eventStats.cache_creation_tokens;
287
+ deltaCacheReadTokens += eventStats.cache_read_tokens;
288
+ deltaReasoningTokens += eventStats.reasoning_tokens;
187
289
  }
188
290
  }
189
291
 
@@ -194,6 +296,10 @@ export function TelemetryPage() {
194
296
  total_errors: fetchedStats.total_errors + deltaErrors,
195
297
  total_input_tokens: fetchedStats.total_input_tokens + deltaInputTokens,
196
298
  total_output_tokens: fetchedStats.total_output_tokens + deltaOutputTokens,
299
+ total_cache_creation_tokens: (fetchedStats.total_cache_creation_tokens || 0) + deltaCacheCreationTokens,
300
+ total_cache_read_tokens: (fetchedStats.total_cache_read_tokens || 0) + deltaCacheReadTokens,
301
+ total_reasoning_tokens: (fetchedStats.total_reasoning_tokens || 0) + deltaReasoningTokens,
302
+ total_cost: fetchedStats.total_cost || 0,
197
303
  };
198
304
  }, [fetchedStats, realtimeEvents]);
199
305
 
@@ -216,6 +322,9 @@ export function TelemetryPage() {
216
322
  existing.errors += eventStats.errors;
217
323
  existing.input_tokens += eventStats.input_tokens;
218
324
  existing.output_tokens += eventStats.output_tokens;
325
+ existing.cache_creation_tokens += eventStats.cache_creation_tokens;
326
+ existing.cache_read_tokens += eventStats.cache_read_tokens;
327
+ existing.reasoning_tokens += eventStats.reasoning_tokens;
219
328
  } else {
220
329
  usageMap.set(event.agent_id, {
221
330
  agent_id: event.agent_id,
@@ -224,6 +333,10 @@ export function TelemetryPage() {
224
333
  errors: eventStats.errors,
225
334
  input_tokens: eventStats.input_tokens,
226
335
  output_tokens: eventStats.output_tokens,
336
+ cache_creation_tokens: eventStats.cache_creation_tokens,
337
+ cache_read_tokens: eventStats.cache_read_tokens,
338
+ reasoning_tokens: eventStats.reasoning_tokens,
339
+ cost: 0,
227
340
  });
228
341
  }
229
342
  }
@@ -232,6 +345,49 @@ export function TelemetryPage() {
232
345
  return Array.from(usageMap.values());
233
346
  }, [fetchedUsage, realtimeEvents]);
234
347
 
348
+ // Sorted usage for the table
349
+ const sortedUsage = useMemo(() => {
350
+ const sorted = [...usage];
351
+ sorted.sort((a, b) => {
352
+ if (sortKey === "agent") {
353
+ const aName = (agents.find(ag => ag.id === a.agent_id)?.name || a.agent_id).toLowerCase();
354
+ const bName = (agents.find(ag => ag.id === b.agent_id)?.name || b.agent_id).toLowerCase();
355
+ return sortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
356
+ }
357
+ const aVal = a[sortKey] as number;
358
+ const bVal = b[sortKey] as number;
359
+ return sortDir === "asc" ? aVal - bVal : bVal - aVal;
360
+ });
361
+ return sorted;
362
+ }, [usage, sortKey, sortDir, agents]);
363
+
364
+ // Sorted project usage for the table
365
+ const sortedProjectUsage = useMemo(() => {
366
+ const sorted = [...projectUsage];
367
+ sorted.sort((a, b) => {
368
+ if (projectSortKey === "project") {
369
+ const aName = (a.project_id ? projects.find(p => p.id === a.project_id)?.name || a.project_id : "Unassigned").toLowerCase();
370
+ const bName = (b.project_id ? projects.find(p => p.id === b.project_id)?.name || b.project_id : "Unassigned").toLowerCase();
371
+ return projectSortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
372
+ }
373
+ const aVal = a[projectSortKey] as number;
374
+ const bVal = b[projectSortKey] as number;
375
+ return projectSortDir === "asc" ? aVal - bVal : bVal - aVal;
376
+ });
377
+ return sorted;
378
+ }, [projectUsage, projectSortKey, projectSortDir, projects]);
379
+
380
+ const getProjectName = (projectId: string | null) => {
381
+ if (!projectId) return "Unassigned";
382
+ const project = projects.find(p => p.id === projectId);
383
+ return project?.name || projectId;
384
+ };
385
+
386
+ const getProjectColor = (projectId: string | null) => {
387
+ if (!projectId) return undefined;
388
+ return projects.find(p => p.id === projectId)?.color;
389
+ };
390
+
235
391
  // Merge real-time events with historical, filtering and deduping
236
392
  const allEvents = React.useMemo(() => {
237
393
  // Apply filters to real-time events
@@ -315,7 +471,7 @@ export function TelemetryPage() {
315
471
  };
316
472
 
317
473
  const levelColors: Record<string, string> = {
318
- debug: "text-[#555]",
474
+ debug: "text-[var(--color-text-faint)]",
319
475
  info: "text-blue-400",
320
476
  warn: "text-yellow-400",
321
477
  error: "text-red-400",
@@ -380,20 +536,56 @@ export function TelemetryPage() {
380
536
  : `Telemetry - ${currentProject?.name || ""}`}
381
537
  </h1>
382
538
  </div>
383
- <p className="text-[#666]">
539
+ <p className="text-[var(--color-text-muted)]">
384
540
  Monitor agent activity, token usage, and errors.
385
541
  </p>
386
542
  </div>
387
543
 
544
+ {/* Time Range Selector */}
545
+ <div className="flex gap-2 mb-6">
546
+ {[
547
+ { value: "1h", label: "Last hour" },
548
+ { value: "6h", label: "Last 6h" },
549
+ { value: "24h", label: "Last 24h" },
550
+ { value: "7d", label: "Last 7 days" },
551
+ { value: "30d", label: "Last 30 days" },
552
+ { value: "all", label: "All time" },
553
+ ].map(opt => (
554
+ <button
555
+ key={opt.value}
556
+ onClick={() => setTimeRange(opt.value)}
557
+ className={`px-3 py-1.5 rounded text-sm transition ${
558
+ timeRange === opt.value
559
+ ? "bg-[var(--color-accent)] text-black"
560
+ : "bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)]"
561
+ }`}
562
+ >
563
+ {opt.label}
564
+ </button>
565
+ ))}
566
+ </div>
567
+
388
568
  {/* Stats Cards */}
389
569
  {stats && (
390
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
570
+ <div className="flex flex-wrap gap-4 mb-6">
391
571
  <StatCard label="Events" value={formatNumber(stats.total_events)} />
392
572
  <StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
393
573
  <StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
394
574
  <StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
395
575
  <StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
396
576
  <StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
577
+ {(stats.total_cache_creation_tokens > 0 || stats.total_cache_read_tokens > 0) && (
578
+ <>
579
+ <StatCard label="Cache Write" value={formatNumber(stats.total_cache_creation_tokens)} />
580
+ <StatCard label="Cache Read" value={formatNumber(stats.total_cache_read_tokens)} />
581
+ </>
582
+ )}
583
+ {stats.total_reasoning_tokens > 0 && (
584
+ <StatCard label="Reasoning" value={formatNumber(stats.total_reasoning_tokens)} />
585
+ )}
586
+ {costTrackingEnabled && (
587
+ <StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
588
+ )}
397
589
  </div>
398
590
  )}
399
591
 
@@ -427,15 +619,15 @@ export function TelemetryPage() {
427
619
  return (
428
620
  <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
429
621
  {/* Activity Chart */}
430
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
431
- <h3 className="text-sm font-medium text-[#888] mb-4">{chartLabel} Activity</h3>
622
+ <div className="bg-[var(--color-surface)] card p-4">
623
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Activity</h3>
432
624
  <ResponsiveContainer width="100%" height={200}>
433
625
  <AreaChart data={chartData}>
434
- <CartesianGrid strokeDasharray="3 3" stroke="#1a1a1a" />
626
+ <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
435
627
  <XAxis
436
628
  dataKey="date"
437
- stroke="#444"
438
- tick={{ fill: "#666", fontSize: 11 }}
629
+ stroke="var(--color-border-light)"
630
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
439
631
  tickFormatter={(v) => {
440
632
  if (!useDaily && v.includes(" ")) {
441
633
  return v.split(" ")[1];
@@ -444,15 +636,15 @@ export function TelemetryPage() {
444
636
  return `${d.getMonth() + 1}/${d.getDate()}`;
445
637
  }}
446
638
  />
447
- <YAxis stroke="#444" tick={{ fill: "#666", fontSize: 11 }} allowDecimals={false} />
639
+ <YAxis stroke="var(--color-border-light)" tick={{ fill: "var(--color-text-muted)", fontSize: 11 }} allowDecimals={false} />
448
640
  <Tooltip
449
641
  contentStyle={{
450
- backgroundColor: "#111",
451
- border: "1px solid #333",
642
+ backgroundColor: "var(--color-surface)",
643
+ border: "1px solid var(--color-border-light)",
452
644
  borderRadius: "8px",
453
645
  fontSize: 12,
454
646
  }}
455
- labelStyle={{ color: "#888" }}
647
+ labelStyle={{ color: "var(--color-text-secondary)" }}
456
648
  cursor={{ stroke: "rgba(255,255,255,0.1)" }}
457
649
  labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
458
650
  />
@@ -465,19 +657,21 @@ export function TelemetryPage() {
465
657
  type="monotone"
466
658
  dataKey="llm_calls"
467
659
  name="LLM Calls"
468
- stroke="#f97316"
469
- fill="#f97316"
660
+ stroke="var(--color-accent)"
661
+ fill="var(--color-accent)"
470
662
  fillOpacity={0.15}
471
663
  strokeWidth={1.5}
664
+
472
665
  />
473
666
  <Area
474
667
  type="monotone"
475
668
  dataKey="tool_calls"
476
669
  name="Tool Calls"
477
- stroke="#fb923c"
478
- fill="#fb923c"
670
+ stroke="var(--color-accent-hover)"
671
+ fill="var(--color-accent-hover)"
479
672
  fillOpacity={0.08}
480
673
  strokeWidth={1.5}
674
+
481
675
  />
482
676
  <Area
483
677
  type="monotone"
@@ -487,21 +681,22 @@ export function TelemetryPage() {
487
681
  fill="#ef4444"
488
682
  fillOpacity={0.1}
489
683
  strokeWidth={1.5}
684
+
490
685
  />
491
686
  </AreaChart>
492
687
  </ResponsiveContainer>
493
688
  </div>
494
689
 
495
690
  {/* Token Usage Chart */}
496
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
497
- <h3 className="text-sm font-medium text-[#888] mb-4">{chartLabel} Token Usage</h3>
691
+ <div className="bg-[var(--color-surface)] card p-4">
692
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
498
693
  <ResponsiveContainer width="100%" height={200}>
499
694
  <BarChart data={chartData}>
500
- <CartesianGrid strokeDasharray="3 3" stroke="#1a1a1a" />
695
+ <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
501
696
  <XAxis
502
697
  dataKey="date"
503
- stroke="#444"
504
- tick={{ fill: "#666", fontSize: 11 }}
698
+ stroke="var(--color-border-light)"
699
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
505
700
  tickFormatter={(v) => {
506
701
  if (!useDaily && v.includes(" ")) {
507
702
  return v.split(" ")[1];
@@ -511,8 +706,8 @@ export function TelemetryPage() {
511
706
  }}
512
707
  />
513
708
  <YAxis
514
- stroke="#444"
515
- tick={{ fill: "#666", fontSize: 11 }}
709
+ stroke="var(--color-border-light)"
710
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
516
711
  tickFormatter={(v) => {
517
712
  if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
518
713
  if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
@@ -521,12 +716,12 @@ export function TelemetryPage() {
521
716
  />
522
717
  <Tooltip
523
718
  contentStyle={{
524
- backgroundColor: "#111",
525
- border: "1px solid #333",
719
+ backgroundColor: "var(--color-surface)",
720
+ border: "1px solid var(--color-border-light)",
526
721
  borderRadius: "8px",
527
722
  fontSize: 12,
528
723
  }}
529
- labelStyle={{ color: "#888" }}
724
+ labelStyle={{ color: "var(--color-text-secondary)" }}
530
725
  cursor={{ fill: "rgba(255,255,255,0.03)" }}
531
726
  labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
532
727
  formatter={(value: number) => [value.toLocaleString(), undefined]}
@@ -539,14 +734,16 @@ export function TelemetryPage() {
539
734
  <Bar
540
735
  dataKey="input_tokens"
541
736
  name="Input Tokens"
542
- fill="#f97316"
737
+ fill="var(--color-accent)"
543
738
  radius={[2, 2, 0, 0]}
739
+
544
740
  />
545
741
  <Bar
546
742
  dataKey="output_tokens"
547
743
  name="Output Tokens"
548
- fill="#ea580c"
744
+ fill="var(--color-accent-hover)"
549
745
  radius={[2, 2, 0, 0]}
746
+
550
747
  />
551
748
  </BarChart>
552
749
  </ResponsiveContainer>
@@ -556,43 +753,186 @@ export function TelemetryPage() {
556
753
  })()}
557
754
 
558
755
  {/* Usage by Agent */}
559
- {usage.length > 0 && (
756
+ {usage.length > 0 && (() => {
757
+ const maxCost = Math.max(...sortedUsage.map(u => u.cost), 0.0001);
758
+ const hasCacheTokens = sortedUsage.some(u => (u.cache_creation_tokens || 0) > 0 || (u.cache_read_tokens || 0) > 0);
759
+ const hasReasoningTokens = sortedUsage.some(u => (u.reasoning_tokens || 0) > 0);
760
+ const SortHeader = ({ label, field, align = "right" }: { label: string; field: SortKey; align?: string }) => (
761
+ <th
762
+ className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
763
+ onClick={() => handleSort(field)}
764
+ >
765
+ <span className="inline-flex items-center gap-1">
766
+ {align === "right" && sortKey === field && (
767
+ <span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
768
+ )}
769
+ {label}
770
+ {align === "left" && sortKey === field && (
771
+ <span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
772
+ )}
773
+ </span>
774
+ </th>
775
+ );
776
+
777
+ return (
560
778
  <div className="mb-6">
561
779
  <h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
562
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg overflow-hidden">
780
+ <div className="bg-[var(--color-surface)] card overflow-hidden">
563
781
  <table className="w-full text-sm">
564
782
  <thead>
565
- <tr className="border-b border-[#1a1a1a] text-[#666]">
566
- <th className="text-left p-3">Agent</th>
567
- <th className="text-right p-3">LLM Calls</th>
568
- <th className="text-right p-3">Tool Calls</th>
569
- <th className="text-right p-3">Input Tokens</th>
570
- <th className="text-right p-3">Output Tokens</th>
571
- <th className="text-right p-3">Errors</th>
783
+ <tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
784
+ <SortHeader label="Agent" field="agent" align="left" />
785
+ <SortHeader label="LLM Calls" field="llm_calls" />
786
+ <SortHeader label="Tool Calls" field="tool_calls" />
787
+ <SortHeader label="Input Tokens" field="input_tokens" />
788
+ <SortHeader label="Output Tokens" field="output_tokens" />
789
+ {hasCacheTokens && <SortHeader label="Cache Write" field="cache_creation_tokens" />}
790
+ {hasCacheTokens && <SortHeader label="Cache Read" field="cache_read_tokens" />}
791
+ {hasReasoningTokens && <SortHeader label="Reasoning" field="reasoning_tokens" />}
792
+ <SortHeader label="Errors" field="errors" />
793
+ {costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
572
794
  </tr>
573
795
  </thead>
574
796
  <tbody>
575
- {usage.map((u) => (
576
- <tr key={u.agent_id} className="border-b border-[#1a1a1a] last:border-0">
797
+ {sortedUsage.map((u) => (
798
+ <tr key={u.agent_id} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
577
799
  <td className="p-3 font-medium">{getAgentName(u.agent_id)}</td>
578
- <td className="p-3 text-right text-[#888]">{formatNumber(u.llm_calls)}</td>
579
- <td className="p-3 text-right text-[#888]">{formatNumber(u.tool_calls)}</td>
580
- <td className="p-3 text-right text-[#888]">{formatNumber(u.input_tokens)}</td>
581
- <td className="p-3 text-right text-[#888]">{formatNumber(u.output_tokens)}</td>
800
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
801
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
802
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
803
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
804
+ {hasCacheTokens && (
805
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
806
+ )}
807
+ {hasCacheTokens && (
808
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
809
+ )}
810
+ {hasReasoningTokens && (
811
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
812
+ )}
582
813
  <td className="p-3 text-right">
583
814
  {u.errors > 0 ? (
584
815
  <span className="text-red-400">{u.errors}</span>
585
816
  ) : (
586
- <span className="text-[#444]">0</span>
817
+ <span className="text-[var(--color-text-faint)]">0</span>
587
818
  )}
588
819
  </td>
820
+ {costTrackingEnabled && (
821
+ <td className="p-3 text-right">
822
+ <div className="flex items-center justify-end gap-2">
823
+ <div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
824
+ <div
825
+ className="h-full bg-orange-500 rounded-full"
826
+ style={{ width: `${(u.cost / maxCost) * 100}%` }}
827
+ />
828
+ </div>
829
+ <span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
830
+ </div>
831
+ </td>
832
+ )}
589
833
  </tr>
590
834
  ))}
591
835
  </tbody>
592
836
  </table>
593
837
  </div>
594
838
  </div>
595
- )}
839
+ );
840
+ })()}
841
+
842
+ {/* Usage by Project */}
843
+ {projectsEnabled && currentProjectId === null && sortedProjectUsage.length > 0 && (() => {
844
+ const maxCost = Math.max(...sortedProjectUsage.map(u => u.cost), 0.0001);
845
+ const hasProjCacheTokens = sortedProjectUsage.some(u => (u.cache_creation_tokens || 0) > 0 || (u.cache_read_tokens || 0) > 0);
846
+ const hasProjReasoningTokens = sortedProjectUsage.some(u => (u.reasoning_tokens || 0) > 0);
847
+ const PSortHeader = ({ label, field, align = "right" }: { label: string; field: ProjectSortKey; align?: string }) => (
848
+ <th
849
+ className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
850
+ onClick={() => handleProjectSort(field)}
851
+ >
852
+ <span className="inline-flex items-center gap-1">
853
+ {align === "right" && projectSortKey === field && (
854
+ <span className="text-orange-400">{projectSortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
855
+ )}
856
+ {label}
857
+ {align === "left" && projectSortKey === field && (
858
+ <span className="text-orange-400">{projectSortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
859
+ )}
860
+ </span>
861
+ </th>
862
+ );
863
+
864
+ return (
865
+ <div className="mb-6">
866
+ <h2 className="text-lg font-medium mb-3">Usage by Project</h2>
867
+ <div className="bg-[var(--color-surface)] card overflow-hidden">
868
+ <table className="w-full text-sm">
869
+ <thead>
870
+ <tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
871
+ <PSortHeader label="Project" field="project" align="left" />
872
+ <PSortHeader label="LLM Calls" field="llm_calls" />
873
+ <PSortHeader label="Tool Calls" field="tool_calls" />
874
+ <PSortHeader label="Input Tokens" field="input_tokens" />
875
+ <PSortHeader label="Output Tokens" field="output_tokens" />
876
+ {hasProjCacheTokens && <PSortHeader label="Cache Write" field="cache_creation_tokens" />}
877
+ {hasProjCacheTokens && <PSortHeader label="Cache Read" field="cache_read_tokens" />}
878
+ {hasProjReasoningTokens && <PSortHeader label="Reasoning" field="reasoning_tokens" />}
879
+ <PSortHeader label="Errors" field="errors" />
880
+ {costTrackingEnabled && <PSortHeader label="Est. Cost" field="cost" />}
881
+ </tr>
882
+ </thead>
883
+ <tbody>
884
+ {sortedProjectUsage.map((u) => {
885
+ const color = getProjectColor(u.project_id);
886
+ return (
887
+ <tr key={u.project_id || "_unassigned"} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
888
+ <td className="p-3 font-medium">
889
+ <span className="inline-flex items-center gap-2">
890
+ {color && <span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: color }} />}
891
+ {getProjectName(u.project_id)}
892
+ </span>
893
+ </td>
894
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
895
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
896
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
897
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
898
+ {hasProjCacheTokens && (
899
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
900
+ )}
901
+ {hasProjCacheTokens && (
902
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
903
+ )}
904
+ {hasProjReasoningTokens && (
905
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
906
+ )}
907
+ <td className="p-3 text-right">
908
+ {u.errors > 0 ? (
909
+ <span className="text-red-400">{u.errors}</span>
910
+ ) : (
911
+ <span className="text-[var(--color-text-faint)]">0</span>
912
+ )}
913
+ </td>
914
+ {costTrackingEnabled && (
915
+ <td className="p-3 text-right">
916
+ <div className="flex items-center justify-end gap-2">
917
+ <div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
918
+ <div
919
+ className="h-full bg-orange-500 rounded-full"
920
+ style={{ width: `${(u.cost / maxCost) * 100}%` }}
921
+ />
922
+ </div>
923
+ <span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
924
+ </div>
925
+ </td>
926
+ )}
927
+ </tr>
928
+ );
929
+ })}
930
+ </tbody>
931
+ </table>
932
+ </div>
933
+ </div>
934
+ );
935
+ })()}
596
936
 
597
937
  {/* Filters */}
598
938
  <div className="flex flex-wrap items-center gap-3 mb-4">
@@ -608,14 +948,14 @@ export function TelemetryPage() {
608
948
  <div className="flex flex-wrap items-center gap-1.5 flex-1">
609
949
  {allCategories.map((cat) => {
610
950
  const isHidden = hiddenCategories.has(cat);
611
- const colorClass = categoryColors[cat] || "bg-[#222] text-[#888] border-[#333]";
951
+ const colorClass = categoryColors[cat] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]";
612
952
  return (
613
953
  <button
614
954
  key={cat}
615
955
  onClick={() => toggleCategory(cat)}
616
956
  className={`px-2 py-0.5 rounded text-xs border transition-all ${
617
957
  isHidden
618
- ? "bg-[#1a1a1a] text-[#555] border-[#333] opacity-50"
958
+ ? "bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] border-[var(--color-border-light)] opacity-50"
619
959
  : colorClass
620
960
  }`}
621
961
  >
@@ -635,7 +975,7 @@ export function TelemetryPage() {
635
975
  </div>
636
976
  <button
637
977
  onClick={fetchData}
638
- className="px-3 py-2 bg-[#1a1a1a] hover:bg-[#222] border border-[#333] rounded text-sm transition"
978
+ className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] rounded text-sm transition"
639
979
  >
640
980
  Refresh
641
981
  </button>
@@ -643,31 +983,31 @@ export function TelemetryPage() {
643
983
  </div>
644
984
 
645
985
  {/* Events List */}
646
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg">
647
- <div className="p-3 border-b border-[#1a1a1a] flex items-center justify-between">
986
+ <div className="bg-[var(--color-surface)] card">
987
+ <div className="p-3 border-b border-[var(--color-border)] flex items-center justify-between">
648
988
  <h2 className="font-medium">Recent Events</h2>
649
989
  {realtimeEvents.length > 0 && (
650
- <span className="text-xs text-[#666]">
990
+ <span className="text-xs text-[var(--color-text-muted)]">
651
991
  {realtimeEvents.length} new
652
992
  </span>
653
993
  )}
654
994
  </div>
655
995
 
656
996
  {loading && allEvents.length === 0 ? (
657
- <div className="p-8 text-center text-[#666]">Loading...</div>
997
+ <div className="p-8 text-center text-[var(--color-text-muted)]">Loading...</div>
658
998
  ) : allEvents.length === 0 ? (
659
- <div className="p-8 text-center text-[#666]">
999
+ <div className="p-8 text-center text-[var(--color-text-muted)]">
660
1000
  No telemetry events yet. Events will appear here in real-time once agents start sending data.
661
1001
  </div>
662
1002
  ) : (
663
- <div className="divide-y divide-[#1a1a1a]">
1003
+ <div className="divide-y divide-[var(--color-border)]">
664
1004
  {allEvents.map((event) => {
665
1005
  const isNew = newEventIds.has(event.id);
666
1006
 
667
1007
  return (
668
1008
  <div
669
1009
  key={event.id}
670
- className={`p-3 hover:bg-[#0a0a0a] cursor-pointer transition-all duration-500 ${
1010
+ className={`p-3 hover:bg-[var(--color-bg)] cursor-pointer transition-all duration-500 ${
671
1011
  isNew ? "bg-green-500/5" : ""
672
1012
  }`}
673
1013
  style={{
@@ -676,17 +1016,17 @@ export function TelemetryPage() {
676
1016
  onClick={() => setExpandedEvent(expandedEvent === event.id ? null : event.id)}
677
1017
  >
678
1018
  <div className="flex items-start gap-3">
679
- <span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[#222] text-[#888] border-[#333]"}`}>
1019
+ <span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]"}`}>
680
1020
  {event.category}
681
1021
  </span>
682
1022
  <div className="flex-1 min-w-0">
683
1023
  <div className="flex items-center gap-2">
684
1024
  <span className="font-medium text-sm">{event.type}</span>
685
- <span className={`text-xs ${levelColors[event.level] || "text-[#666]"}`}>
1025
+ <span className={`text-xs ${levelColors[event.level] || "text-[var(--color-text-muted)]"}`}>
686
1026
  {event.level}
687
1027
  </span>
688
1028
  {event.duration_ms && (
689
- <span className="text-xs text-[#555]">{event.duration_ms}ms</span>
1029
+ <span className="text-xs text-[var(--color-text-faint)]">{event.duration_ms}ms</span>
690
1030
  )}
691
1031
  <span
692
1032
  className={`w-1.5 h-1.5 rounded-full bg-green-400 transition-opacity duration-1000 ${
@@ -694,14 +1034,14 @@ export function TelemetryPage() {
694
1034
  }`}
695
1035
  />
696
1036
  </div>
697
- <div className="text-xs text-[#555] mt-1">
1037
+ <div className="text-xs text-[var(--color-text-faint)] mt-1">
698
1038
  {getAgentName(event.agent_id)} · {new Date(event.timestamp).toLocaleString()}
699
1039
  </div>
700
1040
  {event.error && (
701
1041
  <div className="text-xs text-red-400 mt-1 font-mono">{event.error}</div>
702
1042
  )}
703
1043
  {expandedEvent === event.id && event.data && Object.keys(event.data).length > 0 && (
704
- <pre className="text-xs text-[#666] mt-2 p-2 bg-[#0a0a0a] rounded overflow-x-auto">
1044
+ <pre className="text-xs text-[var(--color-text-muted)] mt-2 p-2 bg-[var(--color-bg)] rounded overflow-x-auto">
705
1045
  {JSON.stringify(event.data, null, 2)}
706
1046
  </pre>
707
1047
  )}
@@ -720,9 +1060,9 @@ export function TelemetryPage() {
720
1060
 
721
1061
  function StatCard({ label, value, color }: { label: string; value: string; color?: string }) {
722
1062
  return (
723
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
724
- <div className="text-[#666] text-xs mb-1">{label}</div>
725
- <div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : ""}`}>
1063
+ <div className="bg-[var(--color-surface)] card p-4 flex-1 min-w-[120px]">
1064
+ <div className="text-[var(--color-text-muted)] text-xs mb-1">{label}</div>
1065
+ <div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : color === "orange" ? "text-orange-400" : ""}`}>
726
1066
  {value}
727
1067
  </div>
728
1068
  </div>