groove-dev 0.26.38 → 0.27.0
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/CHANGELOG.md +59 -0
- package/CLAUDE.md +24 -19
- package/node_modules/@groove-dev/cli/bin/groove.js +2 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/cli/src/commands/nuke.js +16 -4
- package/node_modules/@groove-dev/cli/src/commands/stop.js +17 -2
- package/node_modules/@groove-dev/daemon/integrations-registry.json +681 -75
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/adaptive.js +23 -25
- package/node_modules/@groove-dev/daemon/src/api.js +346 -22
- package/node_modules/@groove-dev/daemon/src/classifier.js +53 -6
- package/node_modules/@groove-dev/daemon/src/firstrun.js +14 -1
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +2 -2
- package/node_modules/@groove-dev/daemon/src/index.js +28 -4
- package/node_modules/@groove-dev/daemon/src/integrations.js +215 -14
- package/node_modules/@groove-dev/daemon/src/introducer.js +84 -11
- package/node_modules/@groove-dev/daemon/src/journalist.js +43 -1
- package/node_modules/@groove-dev/daemon/src/lockmanager.js +60 -0
- package/node_modules/@groove-dev/daemon/src/mcp-manager.js +270 -0
- package/node_modules/@groove-dev/daemon/src/memory.js +370 -0
- package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +141 -9
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/daemon/src/rotator.js +334 -31
- package/node_modules/@groove-dev/daemon/src/router.js +43 -0
- package/node_modules/@groove-dev/daemon/src/tokentracker.js +70 -18
- package/node_modules/@groove-dev/daemon/src/validate.js +5 -13
- package/node_modules/@groove-dev/daemon/templates/groove-slides.cjs +306 -0
- package/node_modules/@groove-dev/daemon/test/classifier.test.js +3 -5
- package/node_modules/@groove-dev/daemon/test/lockmanager.test.js +64 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +252 -0
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +108 -0
- package/node_modules/@groove-dev/daemon/test/router.test.js +64 -0
- package/node_modules/@groove-dev/daemon/test/slides-engine.test.js +230 -0
- package/node_modules/@groove-dev/daemon/test/tokentracker.test.js +78 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-eCrVowF0.js +652 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -4
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +26 -17
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +22 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +53 -21
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +132 -90
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +212 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +495 -174
- package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +12 -2
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +24 -19
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +391 -61
- package/node_modules/@groove-dev/gui/src/components/marketplace/marketplace-card.jsx +29 -7
- package/node_modules/@groove-dev/gui/src/lib/format.js +0 -6
- package/node_modules/@groove-dev/gui/src/lib/hooks/use-dashboard.js +23 -5
- package/node_modules/@groove-dev/gui/src/stores/groove.js +59 -9
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +84 -10
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +24 -21
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +153 -85
- package/package.json +2 -8
- package/packages/cli/bin/groove.js +2 -0
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/nuke.js +16 -4
- package/packages/cli/src/commands/stop.js +17 -2
- package/packages/daemon/integrations-registry.json +681 -75
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/adaptive.js +23 -25
- package/packages/daemon/src/api.js +346 -22
- package/packages/daemon/src/classifier.js +53 -6
- package/packages/daemon/src/firstrun.js +14 -1
- package/packages/daemon/src/gateways/manager.js +2 -2
- package/packages/daemon/src/index.js +28 -4
- package/packages/daemon/src/integrations.js +215 -14
- package/packages/daemon/src/introducer.js +84 -11
- package/packages/daemon/src/journalist.js +43 -1
- package/packages/daemon/src/lockmanager.js +60 -0
- package/packages/daemon/src/mcp-manager.js +270 -0
- package/packages/daemon/src/memory.js +370 -0
- package/packages/daemon/src/pm.js +1 -1
- package/packages/daemon/src/process.js +141 -9
- package/packages/daemon/src/registry.js +1 -1
- package/packages/daemon/src/rotator.js +334 -31
- package/packages/daemon/src/router.js +43 -0
- package/packages/daemon/src/tokentracker.js +70 -18
- package/packages/daemon/src/validate.js +5 -13
- package/packages/daemon/templates/groove-slides.cjs +306 -0
- package/packages/gui/dist/assets/index-DjORRpF0.css +1 -0
- package/packages/gui/dist/assets/index-eCrVowF0.js +652 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -4
- package/packages/gui/src/components/agents/agent-chat.jsx +26 -17
- package/packages/gui/src/components/agents/agent-config.jsx +22 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +53 -21
- package/packages/gui/src/components/agents/agent-node.jsx +132 -90
- package/packages/gui/src/components/agents/spawn-wizard.jsx +212 -1
- package/packages/gui/src/components/dashboard/cache-ring.jsx +6 -2
- package/packages/gui/src/components/dashboard/intel-panel.jsx +495 -174
- package/packages/gui/src/components/dashboard/kpi-card.jsx +12 -2
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +3 -3
- package/packages/gui/src/components/layout/app-shell.jsx +24 -19
- package/packages/gui/src/components/layout/command-palette.jsx +2 -2
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +391 -61
- package/packages/gui/src/components/marketplace/marketplace-card.jsx +29 -7
- package/packages/gui/src/lib/format.js +0 -6
- package/packages/gui/src/lib/hooks/use-dashboard.js +23 -5
- package/packages/gui/src/stores/groove.js +59 -9
- package/packages/gui/src/views/agents.jsx +84 -10
- package/packages/gui/src/views/dashboard.jsx +24 -21
- package/packages/gui/src/views/marketplace.jsx +153 -85
- package/node_modules/@groove-dev/gui/dist/assets/index-CEFKgLGB.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-CaKBNWcK.js +0 -638
- package/node_modules/@groove-dev/gui/dist/groove-logo-short.png +0 -0
- package/node_modules/@groove-dev/gui/dist/groove-logo.png +0 -0
- package/node_modules/@groove-dev/gui/public/groove-logo-short.png +0 -0
- package/node_modules/@groove-dev/gui/public/groove-logo.png +0 -0
- package/node_modules/@groove-dev/gui/src/components/ui/dropdown-menu.jsx +0 -60
- package/node_modules/@groove-dev/gui/src/lib/hooks/use-media-query.js +0 -18
- package/node_modules/@radix-ui/react-dropdown-menu/LICENSE +0 -21
- package/node_modules/@radix-ui/react-dropdown-menu/README.md +0 -3
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.mts +0 -97
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.ts +0 -97
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js +0 -337
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js.map +0 -7
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs +0 -305
- package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs.map +0 -7
- package/node_modules/@radix-ui/react-dropdown-menu/package.json +0 -75
- package/node_modules/@radix-ui/react-popover/LICENSE +0 -21
- package/node_modules/@radix-ui/react-popover/README.md +0 -3
- package/node_modules/@radix-ui/react-popover/dist/index.d.mts +0 -85
- package/node_modules/@radix-ui/react-popover/dist/index.d.ts +0 -85
- package/node_modules/@radix-ui/react-popover/dist/index.js +0 -352
- package/node_modules/@radix-ui/react-popover/dist/index.js.map +0 -7
- package/node_modules/@radix-ui/react-popover/dist/index.mjs +0 -320
- package/node_modules/@radix-ui/react-popover/dist/index.mjs.map +0 -7
- package/node_modules/@radix-ui/react-popover/package.json +0 -82
- package/node_modules/@radix-ui/react-separator/LICENSE +0 -21
- package/node_modules/@radix-ui/react-separator/README.md +0 -3
- package/node_modules/@radix-ui/react-separator/dist/index.d.mts +0 -21
- package/node_modules/@radix-ui/react-separator/dist/index.d.ts +0 -21
- package/node_modules/@radix-ui/react-separator/dist/index.js +0 -65
- package/node_modules/@radix-ui/react-separator/dist/index.js.map +0 -7
- package/node_modules/@radix-ui/react-separator/dist/index.mjs +0 -32
- package/node_modules/@radix-ui/react-separator/dist/index.mjs.map +0 -7
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/LICENSE +0 -21
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/README.md +0 -3
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.mts +0 -52
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.ts +0 -52
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js +0 -80
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js.map +0 -7
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs +0 -47
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs.map +0 -7
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/package.json +0 -69
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/LICENSE +0 -21
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/README.md +0 -3
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.mts +0 -22
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.ts +0 -22
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js +0 -152
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js.map +0 -7
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs +0 -119
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs.map +0 -7
- package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/package.json +0 -64
- package/node_modules/@radix-ui/react-separator/package.json +0 -69
- package/packages/gui/dist/assets/index-CEFKgLGB.css +0 -1
- package/packages/gui/dist/assets/index-CaKBNWcK.js +0 -638
- package/packages/gui/dist/groove-logo-short.png +0 -0
- package/packages/gui/dist/groove-logo.png +0 -0
- package/packages/gui/public/groove-logo-short.png +0 -0
- package/packages/gui/public/groove-logo.png +0 -0
- package/packages/gui/src/components/ui/dropdown-menu.jsx +0 -60
- package/packages/gui/src/lib/hooks/use-media-query.js +0 -18
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
|
|
4
|
-
import { fmtNum, fmtPct, timeAgo } from '../../lib/format';
|
|
4
|
+
import { fmtNum, fmtPct, fmtDollar, timeAgo } from '../../lib/format';
|
|
5
5
|
import { cn } from '../../lib/cn';
|
|
6
6
|
import { HEX } from '../../lib/theme-hex';
|
|
7
7
|
import { roleColor } from '../../lib/status';
|
|
8
|
-
import {
|
|
8
|
+
import { Activity, Brain, Radio, AlertTriangle, CheckCircle, RotateCw, HelpCircle, BookOpen } from 'lucide-react';
|
|
9
|
+
import { Tooltip } from '../ui/tooltip';
|
|
9
10
|
|
|
10
11
|
/* ── Tiny SVG sparkline for inline use ──────────────────────── */
|
|
11
12
|
function TinySparkline({ data, color = HEX.accent, width = 60, height = 16 }) {
|
|
@@ -28,8 +29,46 @@ function TinySparkline({ data, color = HEX.accent, width = 60, height = 16 }) {
|
|
|
28
29
|
);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
/* ──
|
|
32
|
-
function
|
|
32
|
+
/* ── Info tip (? icon with tooltip) ────────────────────────── */
|
|
33
|
+
function InfoTip({ text, side = 'bottom' }) {
|
|
34
|
+
return (
|
|
35
|
+
<Tooltip content={<span className="max-w-[220px] block leading-relaxed">{text}</span>} side={side}>
|
|
36
|
+
<HelpCircle size={10} className="text-text-4 hover:text-text-2 cursor-help flex-shrink-0 transition-colors inline-block ml-1" />
|
|
37
|
+
</Tooltip>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Quality score color ───────────────────────────────────── */
|
|
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;
|
|
52
|
+
return (
|
|
53
|
+
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
54
|
+
{label}: <span style={{ color: danger ? '#e06c75' : HEX.text1 }}>{value}</span>
|
|
55
|
+
</span>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── Quality bar ───────────────────────────────────────────── */
|
|
60
|
+
function QualityBar({ score }) {
|
|
61
|
+
const pct = Math.max(0, Math.min(100, score || 0));
|
|
62
|
+
const color = qualityColor(score);
|
|
63
|
+
return (
|
|
64
|
+
<div className="h-1 rounded-full overflow-hidden flex-1" style={{ background: 'rgba(51,175,188,0.08)' }}>
|
|
65
|
+
<div className="h-full rounded-full transition-all duration-700" style={{ width: `${pct}%`, background: color }} />
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* ── Progress bar ──────────────────────────────────────────── */
|
|
71
|
+
function ProgressBar({ label, value, total, color }) {
|
|
33
72
|
const pct = total > 0 ? (value / total) * 100 : 0;
|
|
34
73
|
return (
|
|
35
74
|
<div className="space-y-1">
|
|
@@ -37,7 +76,7 @@ function SavingsBar({ label, value, total, color }) {
|
|
|
37
76
|
<span className="text-xs font-mono text-text-2">{label}</span>
|
|
38
77
|
<div className="flex items-center gap-2">
|
|
39
78
|
<span className="text-xs font-mono font-semibold tabular-nums" style={{ color }}>
|
|
40
|
-
{Math.round(pct)}%
|
|
79
|
+
{pct >= 1 ? Math.round(pct) : pct > 0 ? pct.toFixed(1) : 0}%
|
|
41
80
|
</span>
|
|
42
81
|
<span className="text-2xs font-mono text-text-3 tabular-nums w-10 text-right">{fmtNum(value)}</span>
|
|
43
82
|
</div>
|
|
@@ -52,99 +91,268 @@ function SavingsBar({ label, value, total, color }) {
|
|
|
52
91
|
);
|
|
53
92
|
}
|
|
54
93
|
|
|
55
|
-
/* ──
|
|
56
|
-
function
|
|
57
|
-
const savings = tokens?.savings || {};
|
|
58
|
-
const totalSaved = savings.total || 0;
|
|
59
|
-
const totalUsed = tokens?.totalTokens || 0;
|
|
60
|
-
const hypothetical = totalUsed + totalSaved;
|
|
61
|
-
const efficiencyPct = hypothetical > 0 ? Math.round((totalSaved / hypothetical) * 100) : 0;
|
|
62
|
-
|
|
94
|
+
/* ── Health Tab ─────────────────────────────────────────────── */
|
|
95
|
+
function HealthTab({ tokens, rotation, agentBreakdown }) {
|
|
63
96
|
const recentHistory = (rotation?.history || []).slice(-10).reverse();
|
|
97
|
+
const liveScores = rotation?.liveScores || {};
|
|
98
|
+
|
|
99
|
+
const runningAgents = (agentBreakdown || []).filter((a) => a.status === 'running');
|
|
100
|
+
const allAgents = agentBreakdown || [];
|
|
101
|
+
const agentsWithQuality = allAgents.filter((a) => a.quality?.score != null);
|
|
102
|
+
const avgQuality = agentsWithQuality.length > 0
|
|
103
|
+
? Math.round(agentsWithQuality.reduce((s, a) => s + a.quality.score, 0) / agentsWithQuality.length)
|
|
104
|
+
: null;
|
|
105
|
+
|
|
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;
|
|
64
111
|
|
|
65
112
|
return (
|
|
66
113
|
<div className="p-3 space-y-4">
|
|
67
114
|
{/* Hero stats */}
|
|
68
|
-
<div className="grid grid-cols-
|
|
115
|
+
<div className="grid grid-cols-4 gap-2">
|
|
69
116
|
<div className="bg-surface-0 rounded p-2.5">
|
|
70
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">
|
|
117
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
118
|
+
Quality
|
|
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-2xl font-mono font-bold tabular-nums leading-none" style={{ color: qualityColor(avgQuality) }}>
|
|
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>
|
|
71
130
|
<div className="text-2xl font-mono font-bold text-text-0 tabular-nums leading-none">
|
|
72
131
|
{rotation?.totalRotations || 0}
|
|
73
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
|
+
)}
|
|
74
138
|
</div>
|
|
75
139
|
<div className="bg-surface-0 rounded p-2.5">
|
|
76
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">
|
|
77
|
-
|
|
78
|
-
|
|
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-2xl font-mono font-bold tabular-nums leading-none" style={{ color: HEX.accent }}>
|
|
145
|
+
{fmtPct((tokens?.cacheHitRate || 0) * 100)}
|
|
79
146
|
</div>
|
|
80
147
|
</div>
|
|
81
148
|
<div className="bg-surface-0 rounded p-2.5">
|
|
82
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1">
|
|
83
|
-
|
|
84
|
-
|
|
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-2xl font-mono font-bold tabular-nums leading-none" style={{ color: completionRate >= 90 ? '#4ae168' : '#e5c07b' }}>
|
|
154
|
+
{completionRate}%
|
|
85
155
|
</div>
|
|
86
156
|
</div>
|
|
87
157
|
</div>
|
|
88
158
|
|
|
89
|
-
{/*
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{/* Internal Overhead (GROOVE's own cost to coordinate) */}
|
|
208
|
+
{tokens?.internalOverhead?.tokens > 0 && (
|
|
209
|
+
<div>
|
|
210
|
+
<div className="flex items-center justify-between mb-2">
|
|
211
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider flex items-center">
|
|
212
|
+
GROOVE Overhead
|
|
213
|
+
<InfoTip text="Tokens consumed by GROOVE's own coordination: the Journalist (synthesis), PM (approval gates), Planner, Task Negotiator, Gateway, and user Q&A. Previously invisible — now tracked for honest ROI." />
|
|
214
|
+
</span>
|
|
215
|
+
<div className="flex items-center gap-2">
|
|
216
|
+
<span className="text-2xs font-mono text-text-2 tabular-nums">
|
|
217
|
+
{fmtNum(tokens.internalOverhead.tokens)} tokens
|
|
218
|
+
</span>
|
|
219
|
+
<span className="text-2xs font-mono text-text-3 tabular-nums">
|
|
220
|
+
{fmtDollar(tokens.internalOverhead.costUsd || 0)}
|
|
221
|
+
</span>
|
|
222
|
+
{tokens.totalTokens > 0 && (
|
|
223
|
+
<span className="text-2xs font-mono font-semibold tabular-nums" style={{ color: HEX.purple }}>
|
|
224
|
+
{Math.round((tokens.internalOverhead.tokens / tokens.totalTokens) * 100)}%
|
|
225
|
+
</span>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
<div className="grid grid-cols-3 gap-1.5">
|
|
230
|
+
{Object.entries(tokens.internalOverhead.components || {})
|
|
231
|
+
.sort((a, b) => (b[1].tokens || 0) - (a[1].tokens || 0))
|
|
232
|
+
.slice(0, 6)
|
|
233
|
+
.map(([id, comp]) => {
|
|
234
|
+
const label = id.replace(/^__|__$/g, '').replace(/_/g, ' ');
|
|
235
|
+
return (
|
|
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>
|
|
245
|
+
</div>
|
|
246
|
+
)}
|
|
247
|
+
|
|
248
|
+
{/* Coordination savings */}
|
|
249
|
+
{(tokens?.savings?.total || 0) > 0 && (
|
|
250
|
+
<div>
|
|
251
|
+
<div className="flex items-center justify-between mb-2">
|
|
252
|
+
<span className="text-2xs font-mono text-text-3 uppercase tracking-wider flex items-center">
|
|
253
|
+
Coordination Savings
|
|
254
|
+
<InfoTip text="Tokens saved vs. uncoordinated agents. Rotation savings are estimated from context degradation (pre/post velocity measurement underway). Conflict prevention and cold-start skip use fixed-overhead models. Compare against GROOVE Overhead above for honest ROI." />
|
|
255
|
+
</span>
|
|
256
|
+
<span className="text-2xs font-mono text-text-2 tabular-nums">{fmtNum(tokens.savings.total)} tokens</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="space-y-2">
|
|
259
|
+
<div className="space-y-1">
|
|
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
|
+
)}
|
|
95
274
|
|
|
96
275
|
{/* Rotation timeline */}
|
|
97
276
|
{recentHistory.length > 0 ? (
|
|
98
277
|
<div>
|
|
99
278
|
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2.5">Recent Rotations</div>
|
|
100
279
|
<div className="space-y-0">
|
|
101
|
-
{recentHistory.map((r, i) =>
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
280
|
+
{recentHistory.map((r, i) => {
|
|
281
|
+
const isQuality = r.reason === 'quality_degradation';
|
|
282
|
+
const isNatural = r.reason === 'natural_compaction';
|
|
283
|
+
const isTokenLimit = r.reason === 'token_limit_exceeded';
|
|
284
|
+
const isVelocity = r.reason === 'runaway_velocity';
|
|
285
|
+
const dotColor = isTokenLimit ? '#e06c75'
|
|
286
|
+
: isVelocity ? '#ff8c42'
|
|
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" />
|
|
115
294
|
<div
|
|
116
|
-
className="w-
|
|
117
|
-
style={{ background: 'rgba(51,175,188,0.15)', minHeight: '12px' }}
|
|
118
|
-
/>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* Content card */}
|
|
123
|
-
<div className={cn('flex-1 bg-surface-0 rounded px-2 py-1.5', i < recentHistory.length - 1 && 'mb-2')}>
|
|
124
|
-
<div className="flex items-center gap-2">
|
|
125
|
-
<span className="text-xs font-mono text-text-1 font-medium capitalize truncate flex-1">
|
|
126
|
-
{r.agentName || r.role}
|
|
127
|
-
</span>
|
|
128
|
-
<span
|
|
129
|
-
className="text-2xs font-mono font-semibold tabular-nums flex-shrink-0"
|
|
295
|
+
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
130
296
|
style={{
|
|
131
|
-
|
|
297
|
+
background: i === 0 ? dotColor : `${dotColor}25`,
|
|
298
|
+
border: `1px solid ${dotColor}80`,
|
|
299
|
+
boxShadow: i === 0 ? `0 0 6px ${dotColor}60` : 'none',
|
|
132
300
|
}}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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>
|
|
137
345
|
</div>
|
|
138
346
|
</div>
|
|
139
|
-
|
|
140
|
-
)
|
|
347
|
+
);
|
|
348
|
+
})}
|
|
141
349
|
</div>
|
|
142
350
|
</div>
|
|
143
351
|
) : (
|
|
144
352
|
<div className="bg-surface-0 rounded p-3 text-center space-y-1.5">
|
|
145
|
-
<div className="text-xs font-mono text-text-2 font-semibold">
|
|
353
|
+
<div className="text-xs font-mono text-text-2 font-semibold">Monitoring for degradation</div>
|
|
146
354
|
<div className="text-2xs font-mono text-text-3 leading-relaxed">
|
|
147
|
-
|
|
355
|
+
Auto-rotation triggers when session quality drops below 40 (errors, repetitions, file churn) or context exceeds the adaptive threshold.
|
|
148
356
|
</div>
|
|
149
357
|
</div>
|
|
150
358
|
)}
|
|
@@ -191,21 +399,14 @@ function AdaptiveTab({ adaptive }) {
|
|
|
191
399
|
borderLeft: p.converged ? '2px solid #33afbc' : '2px solid rgba(229,192,123,0.35)',
|
|
192
400
|
}}
|
|
193
401
|
>
|
|
194
|
-
{/* Card header */}
|
|
195
402
|
<div className="flex items-center gap-2 px-3 pt-2.5 pb-1.5">
|
|
196
|
-
<span
|
|
197
|
-
className="text-xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm"
|
|
198
|
-
style={{ background: rc.bg, color: rc.text }}
|
|
199
|
-
>
|
|
403
|
+
<span className="text-xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm" style={{ background: rc.bg, color: rc.text }}>
|
|
200
404
|
{displayRole}
|
|
201
405
|
</span>
|
|
202
406
|
{role && (
|
|
203
|
-
<span className="text-2xs font-mono text-text-4 bg-surface-4 px-1.5 py-px rounded-sm">
|
|
204
|
-
{provider}
|
|
205
|
-
</span>
|
|
407
|
+
<span className="text-2xs font-mono text-text-4 bg-surface-4 px-1.5 py-px rounded-sm">{provider}</span>
|
|
206
408
|
)}
|
|
207
409
|
<div className="flex-1" />
|
|
208
|
-
{/* Convergence pill */}
|
|
209
410
|
<span
|
|
210
411
|
className="flex items-center gap-1 text-2xs font-mono font-bold px-2 py-px rounded-full"
|
|
211
412
|
style={{
|
|
@@ -214,23 +415,18 @@ function AdaptiveTab({ adaptive }) {
|
|
|
214
415
|
}}
|
|
215
416
|
>
|
|
216
417
|
{!p.converged && (
|
|
217
|
-
<span
|
|
218
|
-
|
|
219
|
-
style={{ background: '#e5c07b', animation: 'node-pulse-bar 1.5s ease-in-out infinite' }}
|
|
220
|
-
/>
|
|
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' }} />
|
|
221
420
|
)}
|
|
222
421
|
{p.converged ? 'Converged' : 'Learning'}
|
|
223
422
|
</span>
|
|
224
423
|
</div>
|
|
225
424
|
|
|
226
|
-
{/* Threshold hero + adjustments */}
|
|
227
425
|
<div className="flex items-end gap-5 px-3 pb-2">
|
|
228
426
|
<div>
|
|
229
427
|
<div className="text-2xs font-mono text-text-4 uppercase tracking-wider mb-0.5">Threshold</div>
|
|
230
|
-
<div
|
|
231
|
-
|
|
232
|
-
style={{ color: p.converged ? '#33afbc' : '#e5c07b' }}
|
|
233
|
-
>
|
|
428
|
+
<div className="text-3xl font-mono font-bold tabular-nums leading-none"
|
|
429
|
+
style={{ color: p.converged ? '#33afbc' : '#e5c07b' }}>
|
|
234
430
|
{fmtPct(p.threshold * 100)}
|
|
235
431
|
</div>
|
|
236
432
|
</div>
|
|
@@ -240,57 +436,33 @@ function AdaptiveTab({ adaptive }) {
|
|
|
240
436
|
</div>
|
|
241
437
|
</div>
|
|
242
438
|
|
|
243
|
-
{/* Sparklines */}
|
|
244
439
|
{(hasHistory || hasScores) && (
|
|
245
440
|
<div className="px-3 pb-2 space-y-2 overflow-hidden">
|
|
246
441
|
{hasHistory && (
|
|
247
442
|
<div>
|
|
248
443
|
<div className="text-2xs font-mono text-text-4 mb-0.5">Threshold history</div>
|
|
249
|
-
<TinySparkline
|
|
250
|
-
data={p.thresholdHistory.map((h) => h.v)}
|
|
251
|
-
color={p.converged ? HEX.accent : HEX.warning}
|
|
252
|
-
width={240}
|
|
253
|
-
height={32}
|
|
254
|
-
/>
|
|
444
|
+
<TinySparkline data={p.thresholdHistory.map((h) => h.v)} color={p.converged ? HEX.accent : HEX.warning} width={240} height={32} />
|
|
255
445
|
</div>
|
|
256
446
|
)}
|
|
257
447
|
{hasScores && (
|
|
258
448
|
<div>
|
|
259
449
|
<div className="text-2xs font-mono text-text-4 mb-0.5">Quality score</div>
|
|
260
|
-
<TinySparkline
|
|
261
|
-
data={p.recentScores}
|
|
262
|
-
color={HEX.warning}
|
|
263
|
-
width={240}
|
|
264
|
-
height={24}
|
|
265
|
-
/>
|
|
450
|
+
<TinySparkline data={p.recentScores} color={HEX.warning} width={240} height={24} />
|
|
266
451
|
</div>
|
|
267
452
|
)}
|
|
268
453
|
</div>
|
|
269
454
|
)}
|
|
270
455
|
|
|
271
|
-
{/* Signal pills */}
|
|
272
456
|
{signals && (
|
|
273
457
|
<div className="flex flex-wrap gap-1.5 px-3 pb-2.5">
|
|
274
|
-
{signals.errorCount
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
</span>
|
|
278
|
-
)}
|
|
458
|
+
<SignalPill label="Errors" value={signals.errorCount} danger />
|
|
459
|
+
<SignalPill label="Reps" value={signals.repetitions} danger />
|
|
460
|
+
<SignalPill label="Churn" value={signals.fileChurn} danger />
|
|
279
461
|
{signals.toolSuccessRate != null && (
|
|
280
462
|
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
281
463
|
Tools: <span className="text-text-1">{Math.round(signals.toolSuccessRate * 100)}%</span>
|
|
282
464
|
</span>
|
|
283
465
|
)}
|
|
284
|
-
{signals.fileChurn != null && (
|
|
285
|
-
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
286
|
-
Churn: <span className="text-text-1">{signals.fileChurn}</span>
|
|
287
|
-
</span>
|
|
288
|
-
)}
|
|
289
|
-
{signals.repetitions != null && (
|
|
290
|
-
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3">
|
|
291
|
-
Reps: <span className="text-warning">{signals.repetitions}</span>
|
|
292
|
-
</span>
|
|
293
|
-
)}
|
|
294
466
|
</div>
|
|
295
467
|
)}
|
|
296
468
|
</div>
|
|
@@ -311,87 +483,231 @@ function JournalistTab({ journalist }) {
|
|
|
311
483
|
}
|
|
312
484
|
|
|
313
485
|
return (
|
|
314
|
-
<div>
|
|
315
|
-
<div className="
|
|
316
|
-
|
|
317
|
-
|
|
486
|
+
<div className="p-3 space-y-3">
|
|
487
|
+
<div className="flex items-center gap-3">
|
|
488
|
+
<div>
|
|
489
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Cycles</div>
|
|
490
|
+
<div className="text-lg font-mono font-semibold text-text-0 tabular-nums leading-none">{journalist.cycleCount || 0}</div>
|
|
491
|
+
</div>
|
|
492
|
+
{journalist.lastCycleAt && (
|
|
318
493
|
<div>
|
|
319
|
-
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">
|
|
320
|
-
<div className="text-
|
|
321
|
-
{journalist.cycleCount || 0}
|
|
322
|
-
</div>
|
|
494
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-0.5">Last</div>
|
|
495
|
+
<div className="text-xs font-mono text-text-2">{timeAgo(journalist.lastCycleAt)}</div>
|
|
323
496
|
</div>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
497
|
+
)}
|
|
498
|
+
{journalist.synthesizing && (
|
|
499
|
+
<span className="text-2xs font-mono font-bold text-accent uppercase tracking-wider animate-pulse">Synthesizing</span>
|
|
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>
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* ── Memory Tab (Layer 7) ───────────────────────────────────── */
|
|
543
|
+
function MemoryTab({ memory }) {
|
|
544
|
+
const constraints = memory?.constraints || [];
|
|
545
|
+
const discoveries = memory?.discoveries || [];
|
|
546
|
+
const roles = memory?.roles || [];
|
|
547
|
+
const perAgent = memory?.specializations?.perAgent || {};
|
|
548
|
+
const perRole = memory?.specializations?.perProjectRole || {};
|
|
549
|
+
const agentCount = Object.keys(perAgent).length;
|
|
550
|
+
const roleCount = Object.keys(perRole).length;
|
|
551
|
+
|
|
552
|
+
const totalItems = constraints.length + discoveries.length + roles.length;
|
|
553
|
+
if (totalItems === 0 && agentCount === 0) {
|
|
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
|
+
}
|
|
562
|
+
|
|
563
|
+
return (
|
|
564
|
+
<div className="p-3 space-y-4">
|
|
565
|
+
{/* Hero stats */}
|
|
566
|
+
<div className="grid grid-cols-4 gap-2">
|
|
567
|
+
<div className="bg-surface-0 rounded p-2.5">
|
|
568
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-1 flex items-center">
|
|
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>
|
|
334
604
|
)}
|
|
335
605
|
</div>
|
|
606
|
+
</div>
|
|
336
607
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
608
|
+
{/* Constraints list */}
|
|
609
|
+
{constraints.length > 0 && (
|
|
610
|
+
<div>
|
|
611
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2">Project Constraints</div>
|
|
612
|
+
<div className="space-y-1">
|
|
613
|
+
{constraints.slice(0, 10).map((c) => (
|
|
614
|
+
<div key={c.hash} className="flex items-start gap-2 bg-surface-0 rounded px-2 py-1.5">
|
|
615
|
+
<span className="text-2xs font-mono px-1.5 py-px rounded-sm bg-surface-4 text-text-3 uppercase tracking-wider flex-shrink-0 mt-0.5">
|
|
616
|
+
{c.category}
|
|
617
|
+
</span>
|
|
618
|
+
<span className="text-xs font-mono text-text-2 leading-relaxed flex-1">{c.text}</span>
|
|
619
|
+
</div>
|
|
620
|
+
))}
|
|
621
|
+
{constraints.length > 10 && (
|
|
622
|
+
<div className="text-2xs font-mono text-text-4 px-2">+{constraints.length - 10} more</div>
|
|
623
|
+
)}
|
|
344
624
|
</div>
|
|
345
|
-
|
|
625
|
+
</div>
|
|
626
|
+
)}
|
|
346
627
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
628
|
+
{/* Recent discoveries */}
|
|
629
|
+
{discoveries.length > 0 && (
|
|
630
|
+
<div>
|
|
631
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2">Recent Discoveries</div>
|
|
632
|
+
<div className="space-y-1.5">
|
|
633
|
+
{discoveries.slice(0, 8).map((d, i) => {
|
|
634
|
+
const rc = roleColor(d.role);
|
|
635
|
+
return (
|
|
636
|
+
<div key={i} className="bg-surface-0 rounded px-2 py-1.5 space-y-1">
|
|
637
|
+
<div className="flex items-center gap-2">
|
|
638
|
+
<span className="text-2xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm" style={{ background: rc.bg, color: rc.text }}>
|
|
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
|
+
})}
|
|
354
652
|
</div>
|
|
355
|
-
|
|
653
|
+
</div>
|
|
654
|
+
)}
|
|
356
655
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
656
|
+
{/* Handoff chain roles */}
|
|
657
|
+
{roles.length > 0 && (
|
|
658
|
+
<div>
|
|
659
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2">Active Role Chains</div>
|
|
660
|
+
<div className="flex flex-wrap gap-1.5">
|
|
661
|
+
{roles.map((role) => {
|
|
662
|
+
const rc = roleColor(role);
|
|
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}
|
|
666
|
+
</span>
|
|
667
|
+
);
|
|
668
|
+
})}
|
|
364
669
|
</div>
|
|
365
|
-
|
|
670
|
+
</div>
|
|
671
|
+
)}
|
|
366
672
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
<span className="text-
|
|
673
|
+
{/* Per-role specialization summary */}
|
|
674
|
+
{roleCount > 0 && (
|
|
675
|
+
<div>
|
|
676
|
+
<div className="text-2xs font-mono text-text-3 uppercase tracking-wider mb-2">Role Quality Profiles</div>
|
|
677
|
+
<div className="space-y-1">
|
|
678
|
+
{Object.entries(perRole).map(([role, data]) => {
|
|
679
|
+
const rc = roleColor(role);
|
|
680
|
+
return (
|
|
681
|
+
<div key={role} className="flex items-center gap-2 bg-surface-0 rounded px-2 py-1.5">
|
|
682
|
+
<span className="text-2xs font-mono font-semibold capitalize px-1.5 py-px rounded-sm" style={{ background: rc.bg, color: rc.text }}>
|
|
683
|
+
{role}
|
|
684
|
+
</span>
|
|
685
|
+
<span className="text-xs font-mono text-text-3 flex-1">{data.sessionCount} sessions</span>
|
|
686
|
+
<span className="text-xs font-mono font-semibold tabular-nums" style={{ color: qualityColor(data.avgQualityScore) }}>
|
|
687
|
+
Q:{data.avgQualityScore}
|
|
688
|
+
</span>
|
|
377
689
|
</div>
|
|
378
|
-
)
|
|
379
|
-
|
|
690
|
+
);
|
|
691
|
+
})}
|
|
380
692
|
</div>
|
|
381
|
-
|
|
382
|
-
|
|
693
|
+
</div>
|
|
694
|
+
)}
|
|
383
695
|
</div>
|
|
384
696
|
);
|
|
385
697
|
}
|
|
386
698
|
|
|
387
699
|
/* ── Intel Panel (main export) ──────────────────────────────── */
|
|
388
|
-
const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journalist }) {
|
|
700
|
+
const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journalist, agentBreakdown, memory }) {
|
|
389
701
|
return (
|
|
390
|
-
<Tabs defaultValue="
|
|
702
|
+
<Tabs defaultValue="health" className="flex flex-col h-full">
|
|
391
703
|
<TabsList className="flex-shrink-0 px-1">
|
|
392
|
-
<TabsTrigger value="
|
|
393
|
-
<
|
|
394
|
-
|
|
704
|
+
<TabsTrigger value="health" className="text-xs px-2.5 py-1.5 inline-flex items-center gap-1.5">
|
|
705
|
+
<Activity size={11} />
|
|
706
|
+
Health
|
|
707
|
+
</TabsTrigger>
|
|
708
|
+
<TabsTrigger value="memory" className="text-xs px-2.5 py-1.5 inline-flex items-center gap-1.5">
|
|
709
|
+
<BookOpen size={11} />
|
|
710
|
+
Memory
|
|
395
711
|
</TabsTrigger>
|
|
396
712
|
<TabsTrigger value="adaptive" className="text-xs px-2.5 py-1.5 inline-flex items-center gap-1.5">
|
|
397
713
|
<Brain size={11} />
|
|
@@ -403,9 +719,14 @@ const IntelPanel = memo(function IntelPanel({ tokens, rotation, adaptive, journa
|
|
|
403
719
|
</TabsTrigger>
|
|
404
720
|
</TabsList>
|
|
405
721
|
|
|
406
|
-
<TabsContent value="
|
|
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">
|
|
407
728
|
<div className="absolute inset-0 overflow-y-auto">
|
|
408
|
-
<
|
|
729
|
+
<MemoryTab memory={memory} />
|
|
409
730
|
</div>
|
|
410
731
|
</TabsContent>
|
|
411
732
|
<TabsContent value="adaptive" className="flex-1 min-h-0 relative">
|