groove-dev 0.27.142 → 0.27.144
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.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
- package/node_modules/@groove-dev/daemon/src/index.js +3 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
- package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
- package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
- package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
- package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
- package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
- package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
- package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
- package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
- package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
- package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
- package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
- package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
- package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
- package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
- package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
- package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
- package/node_modules/@groove-dev/gui/src/app.css +35 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
- package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
- package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
- package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
- package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
- package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
- package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
- package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
- package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +1086 -6532
- package/packages/daemon/src/gateways/manager.js +35 -1
- package/packages/daemon/src/index.js +3 -0
- package/packages/daemon/src/journalist.js +23 -13
- package/packages/daemon/src/mlx-server.js +365 -0
- package/packages/daemon/src/model-lab.js +308 -12
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/local.js +36 -8
- package/packages/daemon/src/registry.js +21 -5
- package/packages/daemon/src/routes/agents.js +889 -0
- package/packages/daemon/src/routes/coordination.js +318 -0
- package/packages/daemon/src/routes/files.js +751 -0
- package/packages/daemon/src/routes/integrations.js +485 -0
- package/packages/daemon/src/routes/network.js +1784 -0
- package/packages/daemon/src/routes/providers.js +755 -0
- package/packages/daemon/src/routes/schedules.js +110 -0
- package/packages/daemon/src/routes/teams.js +650 -0
- package/packages/daemon/src/scheduler.js +456 -24
- package/packages/daemon/src/teams.js +1 -1
- package/packages/daemon/src/validate.js +38 -1
- package/packages/daemon/templates/mlx-setup.json +12 -0
- package/packages/daemon/templates/tgi-setup.json +1 -1
- package/packages/daemon/templates/vllm-setup.json +1 -1
- package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
- package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
- package/packages/gui/src/app.css +35 -0
- package/packages/gui/src/components/agents/agent-config.jsx +1 -128
- package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
- package/packages/gui/src/components/agents/agent-node.jsx +8 -13
- package/packages/gui/src/components/agents/code-review.jsx +159 -122
- package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
- package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
- package/packages/gui/src/components/automations/automation-card.jsx +274 -0
- package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
- package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
- package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
- package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
- package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
- package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
- package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
- package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
- package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
- package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
- package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
- package/packages/gui/src/components/network/network-health.jsx +2 -2
- package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
- package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
- package/packages/gui/src/components/ui/sheet.jsx +5 -2
- package/packages/gui/src/lib/cron.js +64 -0
- package/packages/gui/src/lib/status.js +24 -24
- package/packages/gui/src/lib/theme-hex.js +1 -0
- package/packages/gui/src/stores/groove.js +34 -3144
- package/packages/gui/src/stores/helpers.js +10 -0
- package/packages/gui/src/stores/slices/agents-slice.js +452 -0
- package/packages/gui/src/stores/slices/automations-slice.js +96 -0
- package/packages/gui/src/stores/slices/chat-slice.js +227 -0
- package/packages/gui/src/stores/slices/editor-slice.js +285 -0
- package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
- package/packages/gui/src/stores/slices/network-slice.js +361 -0
- package/packages/gui/src/stores/slices/preview-slice.js +109 -0
- package/packages/gui/src/stores/slices/providers-slice.js +897 -0
- package/packages/gui/src/stores/slices/teams-slice.js +413 -0
- package/packages/gui/src/stores/slices/ui-slice.js +98 -0
- package/packages/gui/src/views/agents.jsx +5 -5
- package/packages/gui/src/views/dashboard.jsx +12 -13
- package/packages/gui/src/views/marketplace.jsx +191 -3
- package/packages/gui/src/views/model-lab.jsx +17 -6
- package/packages/gui/src/views/models.jsx +410 -509
- package/packages/gui/src/views/network.jsx +3 -3
- package/packages/gui/src/views/settings.jsx +81 -94
- package/packages/gui/src/views/teams.jsx +40 -483
- package/SECURITY_SWEEP.md +0 -228
- package/TRAINING_DATA_v4.md +0 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
- package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
- package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
- package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
- package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
- package/packages/gui/src/views/preview.jsx +0 -6
- package/packages/gui/src/views/subscription-panel.jsx +0 -327
- package/test.py +0 -571
|
@@ -1,379 +1,147 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { memo } from 'react';
|
|
3
|
-
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
|
|
4
3
|
import { fmtNum, fmtPct, fmtDollar, timeAgo } from '../../lib/format';
|
|
5
|
-
import {
|
|
6
|
-
import { HEX } from '../../lib/theme-hex';
|
|
4
|
+
import { HEX, hexAlpha } from '../../lib/theme-hex';
|
|
7
5
|
import { roleColor } from '../../lib/status';
|
|
8
|
-
import {
|
|
6
|
+
import { HelpCircle } from 'lucide-react';
|
|
9
7
|
import { Tooltip } from '../ui/tooltip';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
function TinySparkline({ data, color = HEX.accent, width = 60, height = 16 }) {
|
|
13
|
-
if (!data || data.length < 2) return <div style={{ width, height }} />;
|
|
14
|
-
const vals = Array.isArray(data[0]) ? data : data.map((d) => (typeof d === 'number' ? d : d.v));
|
|
15
|
-
const min = Math.min(...vals);
|
|
16
|
-
const max = Math.max(...vals);
|
|
17
|
-
const range = max - min || 1;
|
|
18
|
-
|
|
19
|
-
const points = vals.map((v, i) => {
|
|
20
|
-
const x = (i / (vals.length - 1)) * width;
|
|
21
|
-
const y = height - ((v - min) / range) * (height - 2) - 1;
|
|
22
|
-
return `${x},${y}`;
|
|
23
|
-
}).join(' ');
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<svg width={width} height={height} className="flex-shrink-0">
|
|
27
|
-
<polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeLinejoin="round" strokeOpacity="0.7" />
|
|
28
|
-
</svg>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/* ── Info tip (? icon with tooltip) ────────────────────────── */
|
|
33
|
-
function InfoTip({ text, side = 'bottom' }) {
|
|
9
|
+
function Tip({ text }) {
|
|
34
10
|
return (
|
|
35
|
-
<Tooltip content={<span className="max-w-[220px] block leading-relaxed">{text}</span>} side=
|
|
36
|
-
<HelpCircle size={
|
|
11
|
+
<Tooltip content={<span className="max-w-[220px] block leading-relaxed">{text}</span>} side="bottom">
|
|
12
|
+
<HelpCircle size={9} className="text-text-4 hover:text-text-2 cursor-help flex-shrink-0 transition-colors ml-0.5" />
|
|
37
13
|
</Tooltip>
|
|
38
14
|
);
|
|
39
15
|
}
|
|
40
16
|
|
|
41
|
-
|
|
42
|
-
function qualityColor(score) {
|
|
43
|
-
if (score == null) return HEX.text3;
|
|
44
|
-
if (score >= 70) return '#4ae168';
|
|
45
|
-
if (score >= 40) return '#e5c07b';
|
|
46
|
-
return '#e06c75';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* ── Signal pill ───────────────────────────────────────────── */
|
|
50
|
-
function SignalPill({ label, value, danger }) {
|
|
51
|
-
if (value == null || value === 0) return null;
|
|
17
|
+
function Label({ children, tip }) {
|
|
52
18
|
return (
|
|
53
|
-
<
|
|
54
|
-
{
|
|
55
|
-
</
|
|
19
|
+
<div className="text-2xs font-mono text-text-4 uppercase tracking-wider flex items-center gap-0.5">
|
|
20
|
+
{children}{tip && <Tip text={tip} />}
|
|
21
|
+
</div>
|
|
56
22
|
);
|
|
57
23
|
}
|
|
58
24
|
|
|
59
|
-
|
|
60
|
-
function QualityBar({ score }) {
|
|
61
|
-
const pct = Math.max(0, Math.min(100, score || 0));
|
|
62
|
-
const color = qualityColor(score);
|
|
25
|
+
function Stat({ label, value, tip }) {
|
|
63
26
|
return (
|
|
64
|
-
<div
|
|
65
|
-
<
|
|
27
|
+
<div>
|
|
28
|
+
<Label tip={tip}>{label}</Label>
|
|
29
|
+
<div className="text-sm font-mono font-semibold text-text-1 tabular-nums leading-snug">{value}</div>
|
|
66
30
|
</div>
|
|
67
31
|
);
|
|
68
32
|
}
|
|
69
33
|
|
|
70
|
-
|
|
71
|
-
function ProgressBar({ label, value, total, color }) {
|
|
72
|
-
const pct = total > 0 ? (value / total) * 100 : 0;
|
|
34
|
+
function Section({ title, children, tip }) {
|
|
73
35
|
return (
|
|
74
|
-
<div className="
|
|
75
|
-
<div className="flex items-center
|
|
76
|
-
|
|
77
|
-
<div className="flex items-center gap-2">
|
|
78
|
-
<span className="text-xs font-mono font-semibold tabular-nums" style={{ color }}>
|
|
79
|
-
{pct >= 1 ? Math.round(pct) : pct > 0 ? pct.toFixed(1) : 0}%
|
|
80
|
-
</span>
|
|
81
|
-
<span className="text-2xs font-mono text-text-3 tabular-nums w-10 text-right">{fmtNum(value)}</span>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
<div className="h-0.5 rounded-sm overflow-hidden" style={{ background: 'rgba(51,175,188,0.08)' }}>
|
|
85
|
-
<div
|
|
86
|
-
className="h-full rounded-sm transition-all duration-500"
|
|
87
|
-
style={{ width: `${Math.min(pct, 100)}%`, background: color }}
|
|
88
|
-
/>
|
|
36
|
+
<div className="px-3 py-2.5">
|
|
37
|
+
<div className="text-2xs font-mono text-text-4 uppercase tracking-wider mb-2 flex items-center gap-0.5">
|
|
38
|
+
{title}{tip && <Tip text={tip} />}
|
|
89
39
|
</div>
|
|
40
|
+
{children}
|
|
90
41
|
</div>
|
|
91
42
|
);
|
|
92
43
|
}
|
|
93
44
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const liveScores = rotation?.liveScores || {};
|
|
45
|
+
function Divider() {
|
|
46
|
+
return <div className="h-px bg-border mx-3" />;
|
|
47
|
+
}
|
|
98
48
|
|
|
99
|
-
|
|
49
|
+
/* ── Metrics row ───────────────────────────────────────────── */
|
|
50
|
+
function MetricsRow({ tokens, rotation, agentBreakdown }) {
|
|
100
51
|
const allAgents = agentBreakdown || [];
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
? Math.round(
|
|
52
|
+
const withQuality = allAgents.filter((a) => a.quality?.score != null);
|
|
53
|
+
const avgQ = withQuality.length > 0
|
|
54
|
+
? Math.round(withQuality.reduce((s, a) => s + a.quality.score, 0) / withQuality.length)
|
|
104
55
|
: null;
|
|
105
56
|
|
|
106
|
-
const completions = allAgents.filter((a) => a.status === 'completed' || a.status === 'stopped').length;
|
|
107
|
-
const crashes = allAgents.filter((a) => a.status === 'crashed').length;
|
|
108
|
-
const completionRate = (completions + crashes) > 0
|
|
109
|
-
? Math.round((completions / (completions + crashes)) * 100)
|
|
110
|
-
: 100;
|
|
111
|
-
|
|
112
57
|
return (
|
|
113
|
-
<div className="
|
|
114
|
-
{
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
<InfoTip text="Average session quality score (0-100). Based on error rate, tool failures, repetitions, and file churn. Below 40 triggers auto-rotation to prevent wasted tokens." />
|
|
120
|
-
</div>
|
|
121
|
-
<div className="text-base font-mono font-bold text-text-1 tabular-nums leading-none">
|
|
122
|
-
{avgQuality ?? '—'}
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
126
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
127
|
-
Rotations
|
|
128
|
-
<InfoTip text="Context rotations: quality-based (q), context threshold (c), and natural compactions (n) from provider-managed context resets. Each rotation preserves progress via a journalist handoff brief." />
|
|
129
|
-
</div>
|
|
130
|
-
<div className="text-base font-mono font-bold text-text-1 tabular-nums leading-none">
|
|
131
|
-
{rotation?.totalRotations || 0}
|
|
132
|
-
</div>
|
|
133
|
-
{(rotation?.qualityRotations > 0 || rotation?.contextRotations > 0 || rotation?.naturalCompactions > 0) && (
|
|
134
|
-
<div className="text-2xs font-mono text-text-4 mt-0.5">
|
|
135
|
-
{rotation.qualityRotations || 0}q / {rotation.contextRotations || 0}c / {rotation.naturalCompactions || 0}n
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
</div>
|
|
139
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
140
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
141
|
-
Cache
|
|
142
|
-
<InfoTip text="Prompt cache hit rate. Cache reads are ~90% cheaper than regular input tokens. Managed by your AI provider — GROOVE tracks it, doesn't control it." />
|
|
143
|
-
</div>
|
|
144
|
-
<div className="text-base font-mono font-bold text-text-1 tabular-nums leading-none">
|
|
145
|
-
{fmtPct((tokens?.cacheHitRate || 0) * 100)}
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
149
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
150
|
-
Success
|
|
151
|
-
<InfoTip text="Agent completion rate. Completed agents vs. crashed agents. High success rate means agents are finishing tasks without errors." />
|
|
152
|
-
</div>
|
|
153
|
-
<div className="text-base font-mono font-bold text-text-1 tabular-nums leading-none">
|
|
154
|
-
{completionRate}%
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
</div>
|
|
158
|
-
|
|
159
|
-
{/* Running agents with quality signals */}
|
|
160
|
-
{runningAgents.length > 0 && (
|
|
161
|
-
<div>
|
|
162
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2">Live Agent Quality</div>
|
|
163
|
-
<div className="space-y-2">
|
|
164
|
-
{runningAgents.map((agent) => {
|
|
165
|
-
const q = agent.quality || {};
|
|
166
|
-
const live = liveScores[agent.id];
|
|
167
|
-
const score = live?.score ?? q.score;
|
|
168
|
-
const rc = roleColor(agent.role);
|
|
169
|
-
return (
|
|
170
|
-
<div key={agent.id} className="bg-surface-0 rounded p-2.5">
|
|
171
|
-
<div className="flex items-center gap-2 mb-1.5">
|
|
172
|
-
<span className="text-2xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm" style={{ background: rc.bg, color: rc.text }}>
|
|
173
|
-
{agent.role}
|
|
174
|
-
</span>
|
|
175
|
-
<span className="text-xs font-mono text-text-1 truncate flex-1">{agent.name}</span>
|
|
176
|
-
<span className="text-sm font-mono font-bold tabular-nums" style={{ color: qualityColor(score) }}>
|
|
177
|
-
{score ?? '—'}
|
|
178
|
-
</span>
|
|
179
|
-
</div>
|
|
180
|
-
<QualityBar score={score} />
|
|
181
|
-
<div className="flex flex-wrap gap-1.5 mt-1.5">
|
|
182
|
-
<SignalPill label="Errors" value={q.errorCount} danger />
|
|
183
|
-
<SignalPill label="Reps" value={q.repetitions} danger />
|
|
184
|
-
<SignalPill label="Churn" value={q.fileChurn} danger />
|
|
185
|
-
<SignalPill label="Tools" value={q.toolCalls} />
|
|
186
|
-
<SignalPill label="Files" value={q.filesWritten} />
|
|
187
|
-
{q.toolCalls > 0 && (
|
|
188
|
-
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
189
|
-
Success: <span style={{ color: q.toolSuccessRate >= 0.8 ? '#4ae168' : '#e5c07b' }}>
|
|
190
|
-
{Math.round(q.toolSuccessRate * 100)}%
|
|
191
|
-
</span>
|
|
192
|
-
</span>
|
|
193
|
-
)}
|
|
194
|
-
{q.eventCount > 0 && (
|
|
195
|
-
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
196
|
-
Events: <span className="text-text-2">{q.eventCount}</span>
|
|
197
|
-
</span>
|
|
198
|
-
)}
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
})}
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
58
|
+
<div className="px-3 py-3 flex items-start gap-5">
|
|
59
|
+
<Stat label="Quality" value={avgQ ?? '—'} tip="Average session quality (0-100). Below 40 triggers auto-rotation." />
|
|
60
|
+
<Stat label="Cache" value={fmtPct((tokens?.cacheHitRate || 0) * 100)} tip="Prompt cache hit rate. Higher = faster + cheaper." />
|
|
61
|
+
<Stat label="Rotations" value={rotation?.totalRotations || 0} tip="Total context rotations this session." />
|
|
62
|
+
{(tokens?.totalCostUsd || 0) > 0 && (
|
|
63
|
+
<Stat label="Cost" value={fmtDollar(tokens.totalCostUsd)} tip="Total cost reported by providers." />
|
|
205
64
|
)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
206
68
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
69
|
+
/* ── Live agents ───────────────────────────────────────────── */
|
|
70
|
+
function LiveAgents({ agentBreakdown, rotation }) {
|
|
71
|
+
const liveScores = rotation?.liveScores || {};
|
|
72
|
+
const running = (agentBreakdown || []).filter((a) => a.status === 'running');
|
|
73
|
+
if (running.length === 0) return null;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<>
|
|
77
|
+
<Divider />
|
|
78
|
+
<Section title="Live agents">
|
|
79
|
+
<div className="space-y-0">
|
|
80
|
+
{running.map((agent) => {
|
|
81
|
+
const q = agent.quality || {};
|
|
82
|
+
const score = liveScores[agent.id]?.score ?? q.score;
|
|
83
|
+
const rc = roleColor(agent.role);
|
|
84
|
+
const issues = [
|
|
85
|
+
q.errorCount > 0 && `${q.errorCount} err`,
|
|
86
|
+
q.repetitions > 0 && `${q.repetitions} rep`,
|
|
87
|
+
q.fileChurn > 0 && `${q.fileChurn} churn`,
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div key={agent.id} className="flex items-center gap-2 py-1 text-xs font-mono">
|
|
92
|
+
<span className="text-2xs font-semibold capitalize px-1 py-px rounded-sm flex-shrink-0" style={{ background: rc.bg, color: rc.text }}>
|
|
93
|
+
{agent.role}
|
|
225
94
|
</span>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<div key={id} className="bg-surface-0 rounded px-2 py-1.5">
|
|
237
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider truncate">{label}</div>
|
|
238
|
-
<div className="text-xs font-mono text-text-1 tabular-nums font-semibold">
|
|
239
|
-
{fmtNum(comp.tokens || 0)}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
);
|
|
243
|
-
})}
|
|
244
|
-
</div>
|
|
95
|
+
<span className="text-text-2 truncate flex-1">{agent.name}</span>
|
|
96
|
+
{issues.length > 0 && (
|
|
97
|
+
<span className="text-2xs text-text-4 flex-shrink-0">{issues.join(' · ')}</span>
|
|
98
|
+
)}
|
|
99
|
+
<span className="text-text-1 font-semibold tabular-nums flex-shrink-0 w-6 text-right">
|
|
100
|
+
{score ?? '—'}
|
|
101
|
+
</span>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
245
105
|
</div>
|
|
246
|
-
|
|
106
|
+
</Section>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
247
110
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<ProgressBar label="Cold-start skip" value={tokens.savings.fromColdStartSkip || 0} total={tokens.savings.total || 1} color={HEX.info} />
|
|
261
|
-
<div className="text-2xs font-mono text-text-4 pl-2">estimated · {(tokens?.savings?.fromColdStartSkip || 0) > 0 ? 'fixed overhead per skip' : ''}</div>
|
|
262
|
-
</div>
|
|
263
|
-
<div className="space-y-1">
|
|
264
|
-
<ProgressBar label="Rotation" value={tokens.savings.fromRotation || 0} total={tokens.savings.total || 1} color={HEX.accent} />
|
|
265
|
-
<div className="text-2xs font-mono text-text-4 pl-2">estimated · velocity measurement accumulating</div>
|
|
266
|
-
</div>
|
|
267
|
-
<div className="space-y-1">
|
|
268
|
-
<ProgressBar label="Conflict prevention" value={tokens.savings.fromConflictPrevention || 0} total={tokens.savings.total || 1} color="#4ec9d4" />
|
|
269
|
-
<div className="text-2xs font-mono text-text-4 pl-2">estimated · fixed overhead per conflict</div>
|
|
270
|
-
</div>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
)}
|
|
111
|
+
/* ── Recent rotations ──────────────────────────────────────── */
|
|
112
|
+
function RecentRotations({ rotation }) {
|
|
113
|
+
const history = (rotation?.history || []).slice(-5).reverse();
|
|
114
|
+
if (history.length === 0) return null;
|
|
115
|
+
|
|
116
|
+
function reason(r) {
|
|
117
|
+
if (r.reason === 'quality_degradation') return `Q:${r.qualityScore}`;
|
|
118
|
+
if (r.reason === 'token_limit_exceeded') return 'tokens';
|
|
119
|
+
if (r.reason === 'runaway_velocity') return 'velocity';
|
|
120
|
+
if (r.reason === 'natural_compaction') return 'compacted';
|
|
121
|
+
return fmtPct((r.contextUsage || 0) * 100);
|
|
122
|
+
}
|
|
274
123
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
: isQuality ? '#e5c07b'
|
|
288
|
-
: isNatural ? '#c678dd'
|
|
289
|
-
: '#33afbc';
|
|
290
|
-
return (
|
|
291
|
-
<div key={i} className="flex items-start gap-2.5">
|
|
292
|
-
<div className="flex flex-col items-center flex-shrink-0">
|
|
293
|
-
<div className="h-1.5" />
|
|
294
|
-
<div
|
|
295
|
-
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
296
|
-
style={{
|
|
297
|
-
background: i === 0 ? dotColor : `${dotColor}25`,
|
|
298
|
-
border: `1px solid ${dotColor}80`,
|
|
299
|
-
boxShadow: i === 0 ? `0 0 6px ${dotColor}60` : 'none',
|
|
300
|
-
}}
|
|
301
|
-
/>
|
|
302
|
-
{i < recentHistory.length - 1 && (
|
|
303
|
-
<div className="w-px flex-1 mt-1" style={{ background: 'rgba(51,175,188,0.15)', minHeight: '12px' }} />
|
|
304
|
-
)}
|
|
305
|
-
</div>
|
|
306
|
-
<div className={cn('flex-1 bg-surface-0 rounded px-2 py-1.5', i < recentHistory.length - 1 && 'mb-2')}>
|
|
307
|
-
<div className="flex items-center gap-2">
|
|
308
|
-
<span className="text-xs font-mono text-text-1 font-medium capitalize truncate flex-1">
|
|
309
|
-
{r.agentName || r.role}
|
|
310
|
-
</span>
|
|
311
|
-
{isTokenLimit ? (
|
|
312
|
-
<span className="text-2xs font-mono font-semibold tabular-nums flex-shrink-0" style={{ color: '#e06c75' }}
|
|
313
|
-
title={`Auto-rotated: agent burned ${r.instanceTokens?.toLocaleString()} tokens in one session`}>
|
|
314
|
-
T:{fmtPct(((r.instanceTokens || 0) / 1_000_000) * 100).replace('%', 'M')}
|
|
315
|
-
</span>
|
|
316
|
-
) : isVelocity ? (
|
|
317
|
-
<span className="flex items-center gap-1 flex-shrink-0">
|
|
318
|
-
<span className="text-2xs font-mono font-semibold tabular-nums" style={{ color: '#ff8c42' }}
|
|
319
|
-
title={`Auto-rotated: runaway velocity (${r.velocity?.toLocaleString()} tokens in recent window)`}>
|
|
320
|
-
V:{fmtPct(((r.velocity || 0) / 1_000_000) * 100).replace('%', 'M')}
|
|
321
|
-
</span>
|
|
322
|
-
{r.velocityDelta != null && r.velocityDelta > 0 && (
|
|
323
|
-
<span className="text-2xs font-mono tabular-nums" style={{ color: '#4ae168' }}
|
|
324
|
-
title={`Post-rotation velocity dropped by ${r.velocityDelta.toLocaleString()} tokens — rotation worked`}>
|
|
325
|
-
↓{fmtPct((r.velocityDelta / 1_000_000) * 100).replace('%', 'M')}
|
|
326
|
-
</span>
|
|
327
|
-
)}
|
|
328
|
-
</span>
|
|
329
|
-
) : isQuality ? (
|
|
330
|
-
<span className="text-2xs font-mono font-semibold tabular-nums flex-shrink-0" style={{ color: '#e5c07b' }}>
|
|
331
|
-
Q:{r.qualityScore}
|
|
332
|
-
</span>
|
|
333
|
-
) : isNatural ? (
|
|
334
|
-
<span className="text-2xs font-mono font-semibold tabular-nums flex-shrink-0" style={{ color: '#c678dd' }}>
|
|
335
|
-
{fmtPct((r.contextUsage || 0) * 100)} → {fmtPct((r.contextAfter || 0) * 100)}
|
|
336
|
-
</span>
|
|
337
|
-
) : (
|
|
338
|
-
<span className="text-2xs font-mono font-semibold tabular-nums flex-shrink-0"
|
|
339
|
-
style={{ color: (r.contextUsage || 0) > 0.8 ? '#e06c75' : '#33afbc' }}>
|
|
340
|
-
{fmtPct((r.contextUsage || 0) * 100)}
|
|
341
|
-
</span>
|
|
342
|
-
)}
|
|
343
|
-
<span className="text-2xs font-mono text-text-4 flex-shrink-0">{timeAgo(r.timestamp)}</span>
|
|
344
|
-
</div>
|
|
345
|
-
</div>
|
|
346
|
-
</div>
|
|
347
|
-
);
|
|
348
|
-
})}
|
|
349
|
-
</div>
|
|
350
|
-
</div>
|
|
351
|
-
) : (
|
|
352
|
-
<div className="bg-surface-0 rounded p-3 text-center space-y-1.5">
|
|
353
|
-
<div className="text-xs font-mono text-text-2 font-semibold">Monitoring for degradation</div>
|
|
354
|
-
<div className="text-2xs font-mono text-text-3 leading-relaxed">
|
|
355
|
-
Auto-rotation triggers when session quality drops below 40 (errors, repetitions, file churn) or context exceeds the adaptive threshold.
|
|
356
|
-
</div>
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<Divider />
|
|
127
|
+
<Section title="Recent rotations">
|
|
128
|
+
<div className="space-y-0">
|
|
129
|
+
{history.map((r, i) => (
|
|
130
|
+
<div key={i} className="flex items-center gap-2 py-0.5 text-xs font-mono">
|
|
131
|
+
<span className="text-text-2 truncate flex-1">{r.agentName || r.role}</span>
|
|
132
|
+
<span className="text-text-3 flex-shrink-0">{reason(r)}</span>
|
|
133
|
+
<span className="text-text-4 flex-shrink-0 w-10 text-right">{timeAgo(r.timestamp)}</span>
|
|
134
|
+
</div>
|
|
135
|
+
))}
|
|
357
136
|
</div>
|
|
358
|
-
|
|
359
|
-
|
|
137
|
+
</Section>
|
|
138
|
+
</>
|
|
360
139
|
);
|
|
361
140
|
}
|
|
362
141
|
|
|
363
|
-
/* ── Adaptive
|
|
364
|
-
function
|
|
365
|
-
if (!adaptive?.length)
|
|
366
|
-
return (
|
|
367
|
-
<div className="p-3">
|
|
368
|
-
<div className="bg-surface-0 rounded p-4 text-center space-y-2">
|
|
369
|
-
<div className="text-xs font-mono text-text-2 font-semibold">No adaptive profiles yet</div>
|
|
370
|
-
<div className="text-2xs font-mono text-text-3 leading-relaxed">
|
|
371
|
-
Adaptive thresholds learn when each agent role benefits from rotation. GROOVE tracks quality scores and adjusts rotation triggers automatically — converging to the optimal threshold per role and provider.
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
</div>
|
|
375
|
-
);
|
|
376
|
-
}
|
|
142
|
+
/* ── Adaptive profiles ─────────────────────────────────────── */
|
|
143
|
+
function AdaptiveProfiles({ adaptive }) {
|
|
144
|
+
if (!adaptive?.length) return null;
|
|
377
145
|
|
|
378
146
|
function parseKey(key) {
|
|
379
147
|
const parts = key.split(':');
|
|
@@ -381,365 +149,179 @@ function AdaptiveTab({ adaptive }) {
|
|
|
381
149
|
}
|
|
382
150
|
|
|
383
151
|
return (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
>
|
|
402
|
-
<div className="flex items-center gap-2 px-3 pt-2.5 pb-1.5">
|
|
403
|
-
<span className="text-xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm" style={{ background: rc.bg, color: rc.text }}>
|
|
404
|
-
{displayRole}
|
|
405
|
-
</span>
|
|
406
|
-
{role && (
|
|
407
|
-
<span className="text-2xs font-mono text-text-4 bg-surface-4 px-1.5 py-px rounded-sm">{provider}</span>
|
|
408
|
-
)}
|
|
409
|
-
<div className="flex-1" />
|
|
410
|
-
<span
|
|
411
|
-
className="flex items-center gap-1 text-2xs font-mono font-bold px-2 py-px rounded-full"
|
|
412
|
-
style={{
|
|
413
|
-
background: p.converged ? 'rgba(74,225,104,0.12)' : 'rgba(229,192,123,0.12)',
|
|
414
|
-
color: p.converged ? '#4ae168' : '#e5c07b',
|
|
415
|
-
}}
|
|
416
|
-
>
|
|
417
|
-
{!p.converged && (
|
|
418
|
-
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
|
419
|
-
style={{ background: '#e5c07b', animation: 'node-pulse-bar 1.5s ease-in-out infinite' }} />
|
|
420
|
-
)}
|
|
421
|
-
{p.converged ? 'Converged' : 'Learning'}
|
|
422
|
-
</span>
|
|
423
|
-
</div>
|
|
424
|
-
|
|
425
|
-
<div className="flex items-end gap-5 px-3 pb-2">
|
|
426
|
-
<div>
|
|
427
|
-
<div className="text-2xs font-mono text-text-4 uppercase tracking-wider mb-0.5">Threshold</div>
|
|
428
|
-
<div className="text-3xl font-mono font-bold tabular-nums leading-none"
|
|
429
|
-
style={{ color: p.converged ? '#33afbc' : '#e5c07b' }}>
|
|
152
|
+
<>
|
|
153
|
+
<Divider />
|
|
154
|
+
<Section title="Adaptive thresholds" tip="Per-role rotation thresholds. GROOVE learns when each role benefits from rotation and adjusts automatically.">
|
|
155
|
+
<div className="space-y-0">
|
|
156
|
+
{adaptive.map((p) => {
|
|
157
|
+
const { provider, role } = parseKey(p.key);
|
|
158
|
+
const displayRole = role || provider;
|
|
159
|
+
const rc = roleColor(displayRole);
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div key={p.key} className="flex items-center gap-2 py-1 text-xs font-mono">
|
|
163
|
+
<span className="text-2xs font-semibold capitalize px-1 py-px rounded-sm flex-shrink-0" style={{ background: rc.bg, color: rc.text }}>
|
|
164
|
+
{displayRole}
|
|
165
|
+
</span>
|
|
166
|
+
{role && <span className="text-2xs text-text-4 flex-shrink-0">{provider}</span>}
|
|
167
|
+
<div className="flex-1" />
|
|
168
|
+
<span className="text-text-1 font-semibold tabular-nums flex-shrink-0">
|
|
430
169
|
{fmtPct(p.threshold * 100)}
|
|
431
|
-
</
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
{(hasHistory || hasScores) && (
|
|
440
|
-
<div className="px-3 pb-2 space-y-2 overflow-hidden">
|
|
441
|
-
{hasHistory && (
|
|
442
|
-
<div>
|
|
443
|
-
<div className="text-2xs font-mono text-text-4 mb-0.5">Threshold history</div>
|
|
444
|
-
<TinySparkline data={p.thresholdHistory.map((h) => h.v)} color={p.converged ? HEX.accent : HEX.warning} width={240} height={32} />
|
|
445
|
-
</div>
|
|
446
|
-
)}
|
|
447
|
-
{hasScores && (
|
|
448
|
-
<div>
|
|
449
|
-
<div className="text-2xs font-mono text-text-4 mb-0.5">Quality score</div>
|
|
450
|
-
<TinySparkline data={p.recentScores} color={HEX.warning} width={240} height={24} />
|
|
451
|
-
</div>
|
|
452
|
-
)}
|
|
453
|
-
</div>
|
|
454
|
-
)}
|
|
455
|
-
|
|
456
|
-
{signals && (
|
|
457
|
-
<div className="flex flex-wrap gap-1.5 px-3 pb-2.5">
|
|
458
|
-
<SignalPill label="Errors" value={signals.errorCount} danger />
|
|
459
|
-
<SignalPill label="Reps" value={signals.repetitions} danger />
|
|
460
|
-
<SignalPill label="Churn" value={signals.fileChurn} danger />
|
|
461
|
-
{signals.toolSuccessRate != null && (
|
|
462
|
-
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
463
|
-
Tools: <span className="text-text-1">{Math.round(signals.toolSuccessRate * 100)}%</span>
|
|
170
|
+
</span>
|
|
171
|
+
{p.converged ? (
|
|
172
|
+
<span className="text-2xs font-semibold flex-shrink-0" style={{ color: HEX.accent }}>Converged</span>
|
|
173
|
+
) : (
|
|
174
|
+
<span className="text-2xs text-text-3 flex-shrink-0 flex items-center gap-1">
|
|
175
|
+
<span className="w-1 h-1 rounded-full [animation:node-pulse-bar_1.5s_ease-in-out_infinite]" style={{ background: HEX.text3 }} />
|
|
176
|
+
Learning
|
|
464
177
|
</span>
|
|
465
178
|
)}
|
|
466
179
|
</div>
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
180
|
+
);
|
|
181
|
+
})}
|
|
182
|
+
</div>
|
|
183
|
+
</Section>
|
|
184
|
+
</>
|
|
472
185
|
);
|
|
473
186
|
}
|
|
474
187
|
|
|
475
|
-
/* ── Journalist
|
|
476
|
-
function
|
|
477
|
-
if (!journalist)
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
Journalist inactive
|
|
481
|
-
</div>
|
|
482
|
-
);
|
|
483
|
-
}
|
|
188
|
+
/* ── Journalist ────────────────────────────────────────────── */
|
|
189
|
+
function JournalistSection({ journalist }) {
|
|
190
|
+
if (!journalist) return null;
|
|
191
|
+
const hasCycles = (journalist.cycleCount || 0) > 0;
|
|
192
|
+
if (!hasCycles && !journalist.synthesizing) return null;
|
|
484
193
|
|
|
485
194
|
return (
|
|
486
|
-
|
|
487
|
-
<
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
<
|
|
195
|
+
<>
|
|
196
|
+
<Divider />
|
|
197
|
+
<Section title="Journalist">
|
|
198
|
+
<div className="flex items-center gap-3 text-xs font-mono">
|
|
199
|
+
<span className="text-text-2">{journalist.cycleCount || 0} cycles</span>
|
|
200
|
+
{journalist.lastCycleAt && (
|
|
201
|
+
<span className="text-text-4">{timeAgo(journalist.lastCycleAt)}</span>
|
|
202
|
+
)}
|
|
203
|
+
{journalist.synthesizing && (
|
|
204
|
+
<span className="font-semibold text-accent animate-pulse">Synthesizing</span>
|
|
205
|
+
)}
|
|
491
206
|
</div>
|
|
492
|
-
{journalist.
|
|
493
|
-
<div>
|
|
494
|
-
|
|
495
|
-
<div className="text-xs font-mono text-text-2">{timeAgo(journalist.lastCycleAt)}</div>
|
|
207
|
+
{journalist.lastSummary && (
|
|
208
|
+
<div className="text-xs text-text-3 leading-relaxed mt-1.5 line-clamp-3">
|
|
209
|
+
{journalist.lastSummary}
|
|
496
210
|
</div>
|
|
497
211
|
)}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
)}
|
|
501
|
-
</div>
|
|
502
|
-
|
|
503
|
-
{journalist.lastSummary && (
|
|
504
|
-
<div>
|
|
505
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">Summary</div>
|
|
506
|
-
<div className="text-xs font-sans text-text-2 leading-relaxed">{journalist.lastSummary}</div>
|
|
507
|
-
</div>
|
|
508
|
-
)}
|
|
509
|
-
|
|
510
|
-
{journalist.projectMap && (
|
|
511
|
-
<div>
|
|
512
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">Project Map</div>
|
|
513
|
-
<div className="text-xs font-mono text-text-2 leading-relaxed whitespace-pre-wrap">{journalist.projectMap}</div>
|
|
514
|
-
</div>
|
|
515
|
-
)}
|
|
516
|
-
|
|
517
|
-
{journalist.decisions && (
|
|
518
|
-
<div>
|
|
519
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">Decisions</div>
|
|
520
|
-
<div className="text-xs font-mono text-text-2 leading-relaxed whitespace-pre-wrap">{journalist.decisions}</div>
|
|
521
|
-
</div>
|
|
522
|
-
)}
|
|
523
|
-
|
|
524
|
-
{journalist.recentHistory?.length > 0 && (
|
|
525
|
-
<div>
|
|
526
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1.5">History</div>
|
|
527
|
-
<div className="space-y-1">
|
|
528
|
-
{journalist.recentHistory.slice().reverse().map((h, i) => (
|
|
529
|
-
<div key={i} className="flex items-center gap-2 text-xs font-mono px-2 py-1 bg-surface-0 rounded">
|
|
530
|
-
<span className="text-text-3">#{h.cycle}</span>
|
|
531
|
-
<span className="text-text-2 flex-1 truncate">{h.agentCount} agents</span>
|
|
532
|
-
<span className="text-text-4">{timeAgo(h.timestamp)}</span>
|
|
533
|
-
</div>
|
|
534
|
-
))}
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
)}
|
|
538
|
-
</div>
|
|
212
|
+
</Section>
|
|
213
|
+
</>
|
|
539
214
|
);
|
|
540
215
|
}
|
|
541
216
|
|
|
542
|
-
/* ── Memory
|
|
543
|
-
function
|
|
217
|
+
/* ── Memory ────────────────────────────────────────────────── */
|
|
218
|
+
function MemorySection({ memory }) {
|
|
544
219
|
const constraints = memory?.constraints || [];
|
|
545
220
|
const discoveries = memory?.discoveries || [];
|
|
546
221
|
const roles = memory?.roles || [];
|
|
547
|
-
const perAgent = memory?.specializations?.perAgent || {};
|
|
548
222
|
const perRole = memory?.specializations?.perProjectRole || {};
|
|
549
|
-
const agentCount = Object.keys(perAgent).length;
|
|
550
|
-
const roleCount = Object.keys(perRole).length;
|
|
223
|
+
const agentCount = Object.keys(memory?.specializations?.perAgent || {}).length;
|
|
551
224
|
|
|
552
|
-
const
|
|
553
|
-
if (
|
|
554
|
-
return (
|
|
555
|
-
<div className="p-6 text-center text-xs font-mono text-text-3">
|
|
556
|
-
<BookOpen size={24} className="mx-auto mb-2 text-text-4" />
|
|
557
|
-
<div>No memory accumulated yet</div>
|
|
558
|
-
<div className="text-2xs text-text-4 mt-1">Constraints, handoff chains, and discoveries populate as agents work</div>
|
|
559
|
-
</div>
|
|
560
|
-
);
|
|
561
|
-
}
|
|
225
|
+
const total = constraints.length + discoveries.length + roles.length + agentCount;
|
|
226
|
+
if (total === 0) return null;
|
|
562
227
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
Constraints
|
|
570
|
-
<InfoTip text="Project rules discovered by agents or set by the user. Every new agent reads these on spawn to avoid rediscovering them." />
|
|
571
|
-
</div>
|
|
572
|
-
<div className="text-2xl font-mono font-bold tabular-nums leading-none" style={{ color: HEX.accent }}>
|
|
573
|
-
{constraints.length}
|
|
574
|
-
</div>
|
|
575
|
-
</div>
|
|
576
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
577
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
578
|
-
Discoveries
|
|
579
|
-
<InfoTip text="Error→fix pairs successful agents have recorded. Injected into future agent context so they don't rediscover known solutions." />
|
|
580
|
-
</div>
|
|
581
|
-
<div className="text-2xl font-mono font-bold tabular-nums leading-none" style={{ color: HEX.success }}>
|
|
582
|
-
{discoveries.length}
|
|
583
|
-
</div>
|
|
584
|
-
</div>
|
|
585
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
586
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
587
|
-
Handoff Chains
|
|
588
|
-
<InfoTip text="Cumulative rotation briefs per role. Agent #50 knows what agent #1 struggled with. Each role keeps its last 10 rotation briefs." />
|
|
589
|
-
</div>
|
|
590
|
-
<div className="text-2xl font-mono font-bold tabular-nums leading-none" style={{ color: HEX.purple }}>
|
|
591
|
-
{roles.length}
|
|
592
|
-
</div>
|
|
593
|
-
</div>
|
|
594
|
-
<div className="bg-surface-0 rounded p-2.5">
|
|
595
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
596
|
-
Specializations
|
|
597
|
-
<InfoTip text="Per-agent quality profiles: session counts, average quality, file touches, preferred thresholds." />
|
|
598
|
-
</div>
|
|
599
|
-
<div className="text-2xl font-mono font-bold tabular-nums leading-none" style={{ color: HEX.info }}>
|
|
600
|
-
{agentCount}
|
|
601
|
-
</div>
|
|
602
|
-
{roleCount > 0 && (
|
|
603
|
-
<div className="text-2xs font-mono text-text-4 mt-0.5">across {roleCount} roles</div>
|
|
604
|
-
)}
|
|
605
|
-
</div>
|
|
606
|
-
</div>
|
|
228
|
+
const counts = [
|
|
229
|
+
constraints.length > 0 && `${constraints.length} constraints`,
|
|
230
|
+
discoveries.length > 0 && `${discoveries.length} discoveries`,
|
|
231
|
+
roles.length > 0 && `${roles.length} role chains`,
|
|
232
|
+
agentCount > 0 && `${agentCount} specializations`,
|
|
233
|
+
].filter(Boolean);
|
|
607
234
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
235
|
+
return (
|
|
236
|
+
<>
|
|
237
|
+
<Divider />
|
|
238
|
+
<Section title="Memory" tip="Persistent knowledge across agent rotations. Constraints, error→fix discoveries, and handoff chains.">
|
|
239
|
+
<div className="text-xs font-mono text-text-3 mb-1.5">{counts.join(' · ')}</div>
|
|
240
|
+
|
|
241
|
+
{constraints.length > 0 && (
|
|
242
|
+
<div className="mt-2 space-y-0">
|
|
243
|
+
<div className="text-2xs font-mono text-text-4 mb-1">Constraints</div>
|
|
244
|
+
{constraints.slice(0, 3).map((c) => (
|
|
245
|
+
<div key={c.hash} className="text-xs font-mono text-text-2 py-0.5 truncate">
|
|
246
|
+
{c.text}
|
|
619
247
|
</div>
|
|
620
248
|
))}
|
|
621
|
-
{constraints.length >
|
|
622
|
-
<div className="text-2xs font-mono text-text-4
|
|
249
|
+
{constraints.length > 3 && (
|
|
250
|
+
<div className="text-2xs font-mono text-text-4">+{constraints.length - 3} more</div>
|
|
623
251
|
)}
|
|
624
252
|
</div>
|
|
625
|
-
|
|
626
|
-
)}
|
|
253
|
+
)}
|
|
627
254
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
{d.role}
|
|
640
|
-
</span>
|
|
641
|
-
<span className="text-2xs font-mono text-text-4 flex-1">{timeAgo(d.ts)}</span>
|
|
642
|
-
</div>
|
|
643
|
-
<div className="text-xs font-mono text-text-2 leading-relaxed">
|
|
644
|
-
<span className="text-text-4">When:</span> <span className="text-text-1">{d.trigger}</span>
|
|
645
|
-
</div>
|
|
646
|
-
<div className="text-xs font-mono text-text-2 leading-relaxed">
|
|
647
|
-
<span className="text-text-4">Fix:</span> <span style={{ color: HEX.success }}>{d.fix}</span>
|
|
648
|
-
</div>
|
|
649
|
-
</div>
|
|
650
|
-
);
|
|
651
|
-
})}
|
|
255
|
+
{discoveries.length > 0 && (
|
|
256
|
+
<div className="mt-2 space-y-0">
|
|
257
|
+
<div className="text-2xs font-mono text-text-4 mb-1">Discoveries</div>
|
|
258
|
+
{discoveries.slice(0, 3).map((d, i) => (
|
|
259
|
+
<div key={i} className="text-xs font-mono text-text-2 py-0.5 truncate">
|
|
260
|
+
<span className="text-text-4">{d.trigger}</span> → {d.fix}
|
|
261
|
+
</div>
|
|
262
|
+
))}
|
|
263
|
+
{discoveries.length > 3 && (
|
|
264
|
+
<div className="text-2xs font-mono text-text-4">+{discoveries.length - 3} more</div>
|
|
265
|
+
)}
|
|
652
266
|
</div>
|
|
653
|
-
|
|
654
|
-
)}
|
|
267
|
+
)}
|
|
655
268
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
return (
|
|
664
|
-
<span key={role} className="text-2xs font-mono font-semibold capitalize px-2 py-1 rounded" style={{ background: rc.bg, color: rc.text }}>
|
|
665
|
-
{role}
|
|
269
|
+
{Object.keys(perRole).length > 0 && (
|
|
270
|
+
<div className="mt-2">
|
|
271
|
+
<div className="text-2xs font-mono text-text-4 mb-1">Role quality</div>
|
|
272
|
+
<div className="flex flex-wrap gap-x-3 gap-y-0.5">
|
|
273
|
+
{Object.entries(perRole).map(([role, data]) => (
|
|
274
|
+
<span key={role} className="text-xs font-mono text-text-3">
|
|
275
|
+
<span className="text-text-2 capitalize">{role}</span> Q:{data.avgQualityScore} <span className="text-text-4">({data.sessionCount}s)</span>
|
|
666
276
|
</span>
|
|
667
|
-
)
|
|
668
|
-
|
|
277
|
+
))}
|
|
278
|
+
</div>
|
|
669
279
|
</div>
|
|
670
|
-
|
|
671
|
-
|
|
280
|
+
)}
|
|
281
|
+
</Section>
|
|
282
|
+
</>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
672
285
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
})}
|
|
692
|
-
</div>
|
|
693
|
-
</div>
|
|
694
|
-
)}
|
|
695
|
-
</div>
|
|
286
|
+
/* ── Overhead (collapsed) ──────────────────────────────────── */
|
|
287
|
+
function OverheadSection({ tokens }) {
|
|
288
|
+
if (!tokens?.internalOverhead?.tokens || !tokens?.savings?.total) return null;
|
|
289
|
+
const oh = tokens.internalOverhead;
|
|
290
|
+
const sv = tokens.savings;
|
|
291
|
+
const pct = tokens.totalTokens > 0 ? Math.round((oh.tokens / tokens.totalTokens) * 100) : 0;
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<>
|
|
295
|
+
<Divider />
|
|
296
|
+
<div className="px-3 py-2.5 flex items-center gap-3 text-xs font-mono">
|
|
297
|
+
<span className="text-text-4">Overhead</span>
|
|
298
|
+
<span className="text-text-3 tabular-nums">{fmtNum(oh.tokens)} ({pct}%)</span>
|
|
299
|
+
<div className="flex-1" />
|
|
300
|
+
<span className="text-text-4">Saved</span>
|
|
301
|
+
<span className="text-text-3 tabular-nums">{fmtNum(sv.total)}</span>
|
|
302
|
+
</div>
|
|
303
|
+
</>
|
|
696
304
|
);
|
|
697
305
|
}
|
|
698
306
|
|
|
699
307
|
/* ── Intel Panel (main export) ──────────────────────────────── */
|
|
700
308
|
const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journalist, agentBreakdown, memory }) {
|
|
701
309
|
return (
|
|
702
|
-
<
|
|
703
|
-
<
|
|
704
|
-
<
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
<
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
<
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
<Radio size={11} />
|
|
718
|
-
Journalist
|
|
719
|
-
</TabsTrigger>
|
|
720
|
-
</TabsList>
|
|
721
|
-
|
|
722
|
-
<TabsContent value="health" className="flex-1 min-h-0 relative">
|
|
723
|
-
<div className="absolute inset-0 overflow-y-auto">
|
|
724
|
-
<HealthTab tokens={tokens} rotation={rotation} agentBreakdown={agentBreakdown} />
|
|
725
|
-
</div>
|
|
726
|
-
</TabsContent>
|
|
727
|
-
<TabsContent value="memory" className="flex-1 min-h-0 relative">
|
|
728
|
-
<div className="absolute inset-0 overflow-y-auto">
|
|
729
|
-
<MemoryTab memory={memory} />
|
|
730
|
-
</div>
|
|
731
|
-
</TabsContent>
|
|
732
|
-
<TabsContent value="adaptive" className="flex-1 min-h-0 relative">
|
|
733
|
-
<div className="absolute inset-0 overflow-y-auto">
|
|
734
|
-
<AdaptiveTab adaptive={adaptive} />
|
|
735
|
-
</div>
|
|
736
|
-
</TabsContent>
|
|
737
|
-
<TabsContent value="journalist" className="flex-1 min-h-0 relative">
|
|
738
|
-
<div className="absolute inset-0 overflow-y-auto">
|
|
739
|
-
<JournalistTab journalist={journalist} />
|
|
740
|
-
</div>
|
|
741
|
-
</TabsContent>
|
|
742
|
-
</Tabs>
|
|
310
|
+
<div className="flex flex-col h-full">
|
|
311
|
+
<div className="flex-shrink-0 px-3 pt-2.5 pb-1.5">
|
|
312
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Intel</span>
|
|
313
|
+
</div>
|
|
314
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
315
|
+
<MetricsRow tokens={tokens} rotation={rotation} agentBreakdown={agentBreakdown} />
|
|
316
|
+
<LiveAgents agentBreakdown={agentBreakdown} rotation={rotation} />
|
|
317
|
+
<RecentRotations rotation={rotation} />
|
|
318
|
+
<AdaptiveProfiles adaptive={adaptive} />
|
|
319
|
+
<JournalistSection journalist={journalist} />
|
|
320
|
+
<MemorySection memory={memory} />
|
|
321
|
+
<OverheadSection tokens={tokens} />
|
|
322
|
+
<div className="h-3" />
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
743
325
|
);
|
|
744
326
|
});
|
|
745
327
|
|