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,6 +1,10 @@
1
1
  import React, { useState, useEffect, useMemo, useRef, useCallback } from "react";
2
2
  import { Select } from "../common/Select";
3
3
  import { useTelemetryContext, useProjects, useAuth, type TelemetryEvent } from "../../context";
4
+ import {
5
+ AreaChart, Area, BarChart, Bar,
6
+ XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
7
+ } from "recharts";
4
8
 
5
9
  interface TelemetryStats {
6
10
  total_events: number;
@@ -9,6 +13,7 @@ interface TelemetryStats {
9
13
  total_errors: number;
10
14
  total_input_tokens: number;
11
15
  total_output_tokens: number;
16
+ total_cost: number;
12
17
  }
13
18
 
14
19
  interface UsageByAgent {
@@ -18,6 +23,16 @@ interface UsageByAgent {
18
23
  llm_calls: number;
19
24
  tool_calls: number;
20
25
  errors: number;
26
+ cost: number;
27
+ }
28
+
29
+ interface DailyUsage {
30
+ date: string;
31
+ input_tokens: number;
32
+ output_tokens: number;
33
+ llm_calls: number;
34
+ tool_calls: number;
35
+ errors: number;
21
36
  }
22
37
 
23
38
  // Helper to extract stats from a single event
@@ -45,11 +60,12 @@ function extractEventStats(event: TelemetryEvent): {
45
60
 
46
61
  export function TelemetryPage() {
47
62
  const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
48
- const { currentProjectId, currentProject } = useProjects();
63
+ const { currentProjectId, currentProject, costTrackingEnabled } = useProjects();
49
64
  const { authFetch } = useAuth();
50
65
  const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
51
66
  const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
52
67
  const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
68
+ const [dailyUsage, setDailyUsage] = useState<DailyUsage[]>([]);
53
69
  const [loading, setLoading] = useState(true);
54
70
  const [filter, setFilter] = useState({
55
71
  level: "",
@@ -60,6 +76,20 @@ export function TelemetryPage() {
60
76
  const [agents, setAgents] = useState<Array<{ id: string; name: string; projectId: string | null }>>([]);
61
77
  const [expandedEvent, setExpandedEvent] = useState<string | null>(null);
62
78
 
79
+ // Sort state for usage table
80
+ type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "errors" | "cost";
81
+ const [sortKey, setSortKey] = useState<SortKey>("cost");
82
+ const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
83
+
84
+ const handleSort = (key: SortKey) => {
85
+ if (sortKey === key) {
86
+ setSortDir(d => d === "asc" ? "desc" : "asc");
87
+ } else {
88
+ setSortKey(key);
89
+ setSortDir("desc");
90
+ }
91
+ };
92
+
63
93
  // Track IDs that were in the fetched stats to avoid double-counting
64
94
  const countedEventIdsRef = useRef<Set<string>>(new Set());
65
95
 
@@ -127,6 +157,18 @@ export function TelemetryPage() {
127
157
  const usageRes = await authFetch(`/api/telemetry/usage?${usageParams}`);
128
158
  const usageData = await usageRes.json();
129
159
  setFetchedUsage(usageData.usage || []);
160
+
161
+ // Fetch daily usage for charts
162
+ const dailyParams = new URLSearchParams();
163
+ dailyParams.set("group_by", "day");
164
+ if (projectParam) dailyParams.set("project_id", projectParam);
165
+ const dailyRes = await authFetch(`/api/telemetry/usage?${dailyParams}`);
166
+ const dailyData = await dailyRes.json();
167
+ // Sort by date ascending for charts
168
+ const sorted = (dailyData.usage || []).sort((a: DailyUsage, b: DailyUsage) =>
169
+ a.date.localeCompare(b.date)
170
+ );
171
+ setDailyUsage(sorted);
130
172
  } catch (e) {
131
173
  console.error("Failed to fetch telemetry:", e);
132
174
  }
@@ -168,6 +210,7 @@ export function TelemetryPage() {
168
210
  total_errors: fetchedStats.total_errors + deltaErrors,
169
211
  total_input_tokens: fetchedStats.total_input_tokens + deltaInputTokens,
170
212
  total_output_tokens: fetchedStats.total_output_tokens + deltaOutputTokens,
213
+ total_cost: fetchedStats.total_cost || 0,
171
214
  };
172
215
  }, [fetchedStats, realtimeEvents]);
173
216
 
@@ -198,6 +241,7 @@ export function TelemetryPage() {
198
241
  errors: eventStats.errors,
199
242
  input_tokens: eventStats.input_tokens,
200
243
  output_tokens: eventStats.output_tokens,
244
+ cost: 0,
201
245
  });
202
246
  }
203
247
  }
@@ -206,6 +250,22 @@ export function TelemetryPage() {
206
250
  return Array.from(usageMap.values());
207
251
  }, [fetchedUsage, realtimeEvents]);
208
252
 
253
+ // Sorted usage for the table
254
+ const sortedUsage = useMemo(() => {
255
+ const sorted = [...usage];
256
+ sorted.sort((a, b) => {
257
+ if (sortKey === "agent") {
258
+ const aName = (agents.find(ag => ag.id === a.agent_id)?.name || a.agent_id).toLowerCase();
259
+ const bName = (agents.find(ag => ag.id === b.agent_id)?.name || b.agent_id).toLowerCase();
260
+ return sortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
261
+ }
262
+ const aVal = a[sortKey] as number;
263
+ const bVal = b[sortKey] as number;
264
+ return sortDir === "asc" ? aVal - bVal : bVal - aVal;
265
+ });
266
+ return sorted;
267
+ }, [usage, sortKey, sortDir, agents]);
268
+
209
269
  // Merge real-time events with historical, filtering and deduping
210
270
  const allEvents = React.useMemo(() => {
211
271
  // Apply filters to real-time events
@@ -289,7 +349,7 @@ export function TelemetryPage() {
289
349
  };
290
350
 
291
351
  const levelColors: Record<string, string> = {
292
- debug: "text-[#555]",
352
+ debug: "text-[var(--color-text-faint)]",
293
353
  info: "text-blue-400",
294
354
  warn: "text-yellow-400",
295
355
  error: "text-red-400",
@@ -354,61 +414,256 @@ export function TelemetryPage() {
354
414
  : `Telemetry - ${currentProject?.name || ""}`}
355
415
  </h1>
356
416
  </div>
357
- <p className="text-[#666]">
417
+ <p className="text-[var(--color-text-muted)]">
358
418
  Monitor agent activity, token usage, and errors.
359
419
  </p>
360
420
  </div>
361
421
 
362
422
  {/* Stats Cards */}
363
423
  {stats && (
364
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
424
+ <div className={`grid grid-cols-2 md:grid-cols-3 ${costTrackingEnabled ? "lg:grid-cols-7" : "lg:grid-cols-6"} gap-4 mb-6`}>
365
425
  <StatCard label="Events" value={formatNumber(stats.total_events)} />
366
426
  <StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
367
427
  <StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
368
428
  <StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
369
429
  <StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
370
430
  <StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
431
+ {costTrackingEnabled && (
432
+ <StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
433
+ )}
371
434
  </div>
372
435
  )}
373
436
 
437
+ {/* Charts */}
438
+ {(() => {
439
+ // Use daily data if we have multiple days, otherwise aggregate events by hour
440
+ const useDaily = dailyUsage.length > 1;
441
+ const chartData = useDaily ? dailyUsage : (() => {
442
+ // Aggregate all visible events by hour
443
+ const buckets = new Map<string, { date: string; llm_calls: number; tool_calls: number; errors: number; input_tokens: number; output_tokens: number }>();
444
+ for (const event of allEvents) {
445
+ const d = new Date(event.timestamp);
446
+ const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:00`;
447
+ if (!buckets.has(key)) {
448
+ buckets.set(key, { date: key, llm_calls: 0, tool_calls: 0, errors: 0, input_tokens: 0, output_tokens: 0 });
449
+ }
450
+ const b = buckets.get(key)!;
451
+ const s = extractEventStats(event);
452
+ b.llm_calls += s.llm_calls;
453
+ b.tool_calls += s.tool_calls;
454
+ b.errors += s.errors;
455
+ b.input_tokens += s.input_tokens;
456
+ b.output_tokens += s.output_tokens;
457
+ }
458
+ return Array.from(buckets.values()).sort((a, b) => a.date.localeCompare(b.date));
459
+ })();
460
+ const chartLabel = useDaily ? "Daily" : "Hourly";
461
+
462
+ if (chartData.length === 0) return null;
463
+
464
+ return (
465
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
466
+ {/* Activity Chart */}
467
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
468
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Activity</h3>
469
+ <ResponsiveContainer width="100%" height={200}>
470
+ <AreaChart data={chartData}>
471
+ <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
472
+ <XAxis
473
+ dataKey="date"
474
+ stroke="var(--color-border-light)"
475
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
476
+ tickFormatter={(v) => {
477
+ if (!useDaily && v.includes(" ")) {
478
+ return v.split(" ")[1];
479
+ }
480
+ const d = new Date(v + "T00:00:00");
481
+ return `${d.getMonth() + 1}/${d.getDate()}`;
482
+ }}
483
+ />
484
+ <YAxis stroke="var(--color-border-light)" tick={{ fill: "var(--color-text-muted)", fontSize: 11 }} allowDecimals={false} />
485
+ <Tooltip
486
+ contentStyle={{
487
+ backgroundColor: "var(--color-surface)",
488
+ border: "1px solid var(--color-border-light)",
489
+ borderRadius: "8px",
490
+ fontSize: 12,
491
+ }}
492
+ labelStyle={{ color: "var(--color-text-secondary)" }}
493
+ cursor={{ stroke: "rgba(255,255,255,0.1)" }}
494
+ labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
495
+ />
496
+ <Legend
497
+ wrapperStyle={{ fontSize: 11 }}
498
+ iconType="circle"
499
+ iconSize={8}
500
+ />
501
+ <Area
502
+ type="monotone"
503
+ dataKey="llm_calls"
504
+ name="LLM Calls"
505
+ stroke="var(--color-accent)"
506
+ fill="var(--color-accent)"
507
+ fillOpacity={0.15}
508
+ strokeWidth={1.5}
509
+ />
510
+ <Area
511
+ type="monotone"
512
+ dataKey="tool_calls"
513
+ name="Tool Calls"
514
+ stroke="var(--color-accent-hover)"
515
+ fill="var(--color-accent-hover)"
516
+ fillOpacity={0.08}
517
+ strokeWidth={1.5}
518
+ />
519
+ <Area
520
+ type="monotone"
521
+ dataKey="errors"
522
+ name="Errors"
523
+ stroke="#ef4444"
524
+ fill="#ef4444"
525
+ fillOpacity={0.1}
526
+ strokeWidth={1.5}
527
+ />
528
+ </AreaChart>
529
+ </ResponsiveContainer>
530
+ </div>
531
+
532
+ {/* Token Usage Chart */}
533
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
534
+ <h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
535
+ <ResponsiveContainer width="100%" height={200}>
536
+ <BarChart data={chartData}>
537
+ <CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
538
+ <XAxis
539
+ dataKey="date"
540
+ stroke="var(--color-border-light)"
541
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
542
+ tickFormatter={(v) => {
543
+ if (!useDaily && v.includes(" ")) {
544
+ return v.split(" ")[1];
545
+ }
546
+ const d = new Date(v + "T00:00:00");
547
+ return `${d.getMonth() + 1}/${d.getDate()}`;
548
+ }}
549
+ />
550
+ <YAxis
551
+ stroke="var(--color-border-light)"
552
+ tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
553
+ tickFormatter={(v) => {
554
+ if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
555
+ if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
556
+ return v;
557
+ }}
558
+ />
559
+ <Tooltip
560
+ contentStyle={{
561
+ backgroundColor: "var(--color-surface)",
562
+ border: "1px solid var(--color-border-light)",
563
+ borderRadius: "8px",
564
+ fontSize: 12,
565
+ }}
566
+ labelStyle={{ color: "var(--color-text-secondary)" }}
567
+ cursor={{ fill: "rgba(255,255,255,0.03)" }}
568
+ labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
569
+ formatter={(value: number) => [value.toLocaleString(), undefined]}
570
+ />
571
+ <Legend
572
+ wrapperStyle={{ fontSize: 11 }}
573
+ iconType="circle"
574
+ iconSize={8}
575
+ />
576
+ <Bar
577
+ dataKey="input_tokens"
578
+ name="Input Tokens"
579
+ fill="var(--color-accent)"
580
+ radius={[2, 2, 0, 0]}
581
+ />
582
+ <Bar
583
+ dataKey="output_tokens"
584
+ name="Output Tokens"
585
+ fill="var(--color-accent-hover)"
586
+ radius={[2, 2, 0, 0]}
587
+ />
588
+ </BarChart>
589
+ </ResponsiveContainer>
590
+ </div>
591
+ </div>
592
+ );
593
+ })()}
594
+
374
595
  {/* Usage by Agent */}
375
- {usage.length > 0 && (
596
+ {usage.length > 0 && (() => {
597
+ const maxCost = Math.max(...sortedUsage.map(u => u.cost), 0.0001);
598
+ const SortHeader = ({ label, field, align = "right" }: { label: string; field: SortKey; align?: string }) => (
599
+ <th
600
+ className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
601
+ onClick={() => handleSort(field)}
602
+ >
603
+ <span className="inline-flex items-center gap-1">
604
+ {align === "right" && sortKey === field && (
605
+ <span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
606
+ )}
607
+ {label}
608
+ {align === "left" && sortKey === field && (
609
+ <span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
610
+ )}
611
+ </span>
612
+ </th>
613
+ );
614
+
615
+ return (
376
616
  <div className="mb-6">
377
617
  <h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
378
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg overflow-hidden">
618
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg overflow-hidden">
379
619
  <table className="w-full text-sm">
380
620
  <thead>
381
- <tr className="border-b border-[#1a1a1a] text-[#666]">
382
- <th className="text-left p-3">Agent</th>
383
- <th className="text-right p-3">LLM Calls</th>
384
- <th className="text-right p-3">Tool Calls</th>
385
- <th className="text-right p-3">Input Tokens</th>
386
- <th className="text-right p-3">Output Tokens</th>
387
- <th className="text-right p-3">Errors</th>
621
+ <tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
622
+ <SortHeader label="Agent" field="agent" align="left" />
623
+ <SortHeader label="LLM Calls" field="llm_calls" />
624
+ <SortHeader label="Tool Calls" field="tool_calls" />
625
+ <SortHeader label="Input Tokens" field="input_tokens" />
626
+ <SortHeader label="Output Tokens" field="output_tokens" />
627
+ <SortHeader label="Errors" field="errors" />
628
+ {costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
388
629
  </tr>
389
630
  </thead>
390
631
  <tbody>
391
- {usage.map((u) => (
392
- <tr key={u.agent_id} className="border-b border-[#1a1a1a] last:border-0">
632
+ {sortedUsage.map((u) => (
633
+ <tr key={u.agent_id} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
393
634
  <td className="p-3 font-medium">{getAgentName(u.agent_id)}</td>
394
- <td className="p-3 text-right text-[#888]">{formatNumber(u.llm_calls)}</td>
395
- <td className="p-3 text-right text-[#888]">{formatNumber(u.tool_calls)}</td>
396
- <td className="p-3 text-right text-[#888]">{formatNumber(u.input_tokens)}</td>
397
- <td className="p-3 text-right text-[#888]">{formatNumber(u.output_tokens)}</td>
635
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
636
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
637
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
638
+ <td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
398
639
  <td className="p-3 text-right">
399
640
  {u.errors > 0 ? (
400
641
  <span className="text-red-400">{u.errors}</span>
401
642
  ) : (
402
- <span className="text-[#444]">0</span>
643
+ <span className="text-[var(--color-text-faint)]">0</span>
403
644
  )}
404
645
  </td>
646
+ {costTrackingEnabled && (
647
+ <td className="p-3 text-right">
648
+ <div className="flex items-center justify-end gap-2">
649
+ <div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
650
+ <div
651
+ className="h-full bg-orange-500 rounded-full"
652
+ style={{ width: `${(u.cost / maxCost) * 100}%` }}
653
+ />
654
+ </div>
655
+ <span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
656
+ </div>
657
+ </td>
658
+ )}
405
659
  </tr>
406
660
  ))}
407
661
  </tbody>
408
662
  </table>
409
663
  </div>
410
664
  </div>
411
- )}
665
+ );
666
+ })()}
412
667
 
413
668
  {/* Filters */}
414
669
  <div className="flex flex-wrap items-center gap-3 mb-4">
@@ -424,14 +679,14 @@ export function TelemetryPage() {
424
679
  <div className="flex flex-wrap items-center gap-1.5 flex-1">
425
680
  {allCategories.map((cat) => {
426
681
  const isHidden = hiddenCategories.has(cat);
427
- const colorClass = categoryColors[cat] || "bg-[#222] text-[#888] border-[#333]";
682
+ const colorClass = categoryColors[cat] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]";
428
683
  return (
429
684
  <button
430
685
  key={cat}
431
686
  onClick={() => toggleCategory(cat)}
432
687
  className={`px-2 py-0.5 rounded text-xs border transition-all ${
433
688
  isHidden
434
- ? "bg-[#1a1a1a] text-[#555] border-[#333] opacity-50"
689
+ ? "bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] border-[var(--color-border-light)] opacity-50"
435
690
  : colorClass
436
691
  }`}
437
692
  >
@@ -451,7 +706,7 @@ export function TelemetryPage() {
451
706
  </div>
452
707
  <button
453
708
  onClick={fetchData}
454
- className="px-3 py-2 bg-[#1a1a1a] hover:bg-[#222] border border-[#333] rounded text-sm transition"
709
+ 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"
455
710
  >
456
711
  Refresh
457
712
  </button>
@@ -459,31 +714,31 @@ export function TelemetryPage() {
459
714
  </div>
460
715
 
461
716
  {/* Events List */}
462
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg">
463
- <div className="p-3 border-b border-[#1a1a1a] flex items-center justify-between">
717
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg">
718
+ <div className="p-3 border-b border-[var(--color-border)] flex items-center justify-between">
464
719
  <h2 className="font-medium">Recent Events</h2>
465
720
  {realtimeEvents.length > 0 && (
466
- <span className="text-xs text-[#666]">
721
+ <span className="text-xs text-[var(--color-text-muted)]">
467
722
  {realtimeEvents.length} new
468
723
  </span>
469
724
  )}
470
725
  </div>
471
726
 
472
727
  {loading && allEvents.length === 0 ? (
473
- <div className="p-8 text-center text-[#666]">Loading...</div>
728
+ <div className="p-8 text-center text-[var(--color-text-muted)]">Loading...</div>
474
729
  ) : allEvents.length === 0 ? (
475
- <div className="p-8 text-center text-[#666]">
730
+ <div className="p-8 text-center text-[var(--color-text-muted)]">
476
731
  No telemetry events yet. Events will appear here in real-time once agents start sending data.
477
732
  </div>
478
733
  ) : (
479
- <div className="divide-y divide-[#1a1a1a]">
734
+ <div className="divide-y divide-[var(--color-border)]">
480
735
  {allEvents.map((event) => {
481
736
  const isNew = newEventIds.has(event.id);
482
737
 
483
738
  return (
484
739
  <div
485
740
  key={event.id}
486
- className={`p-3 hover:bg-[#0a0a0a] cursor-pointer transition-all duration-500 ${
741
+ className={`p-3 hover:bg-[var(--color-bg)] cursor-pointer transition-all duration-500 ${
487
742
  isNew ? "bg-green-500/5" : ""
488
743
  }`}
489
744
  style={{
@@ -492,17 +747,17 @@ export function TelemetryPage() {
492
747
  onClick={() => setExpandedEvent(expandedEvent === event.id ? null : event.id)}
493
748
  >
494
749
  <div className="flex items-start gap-3">
495
- <span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[#222] text-[#888] border-[#333]"}`}>
750
+ <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)]"}`}>
496
751
  {event.category}
497
752
  </span>
498
753
  <div className="flex-1 min-w-0">
499
754
  <div className="flex items-center gap-2">
500
755
  <span className="font-medium text-sm">{event.type}</span>
501
- <span className={`text-xs ${levelColors[event.level] || "text-[#666]"}`}>
756
+ <span className={`text-xs ${levelColors[event.level] || "text-[var(--color-text-muted)]"}`}>
502
757
  {event.level}
503
758
  </span>
504
759
  {event.duration_ms && (
505
- <span className="text-xs text-[#555]">{event.duration_ms}ms</span>
760
+ <span className="text-xs text-[var(--color-text-faint)]">{event.duration_ms}ms</span>
506
761
  )}
507
762
  <span
508
763
  className={`w-1.5 h-1.5 rounded-full bg-green-400 transition-opacity duration-1000 ${
@@ -510,14 +765,14 @@ export function TelemetryPage() {
510
765
  }`}
511
766
  />
512
767
  </div>
513
- <div className="text-xs text-[#555] mt-1">
768
+ <div className="text-xs text-[var(--color-text-faint)] mt-1">
514
769
  {getAgentName(event.agent_id)} · {new Date(event.timestamp).toLocaleString()}
515
770
  </div>
516
771
  {event.error && (
517
772
  <div className="text-xs text-red-400 mt-1 font-mono">{event.error}</div>
518
773
  )}
519
774
  {expandedEvent === event.id && event.data && Object.keys(event.data).length > 0 && (
520
- <pre className="text-xs text-[#666] mt-2 p-2 bg-[#0a0a0a] rounded overflow-x-auto">
775
+ <pre className="text-xs text-[var(--color-text-muted)] mt-2 p-2 bg-[var(--color-bg)] rounded overflow-x-auto">
521
776
  {JSON.stringify(event.data, null, 2)}
522
777
  </pre>
523
778
  )}
@@ -536,9 +791,9 @@ export function TelemetryPage() {
536
791
 
537
792
  function StatCard({ label, value, color }: { label: string; value: string; color?: string }) {
538
793
  return (
539
- <div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
540
- <div className="text-[#666] text-xs mb-1">{label}</div>
541
- <div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : ""}`}>
794
+ <div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
795
+ <div className="text-[var(--color-text-muted)] text-xs mb-1">{label}</div>
796
+ <div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : color === "orange" ? "text-orange-400" : ""}`}>
542
797
  {value}
543
798
  </div>
544
799
  </div>