groove-dev 0.27.141 → 0.27.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +18 -7
  4. package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
  5. package/node_modules/@groove-dev/daemon/src/journalist.js +3 -2
  6. package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
  7. package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
  8. package/node_modules/@groove-dev/daemon/src/process.js +5 -16
  9. package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
  10. package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
  11. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +984 -0
  12. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +1 -0
  13. package/node_modules/@groove-dev/gui/dist/index.html +3 -3
  14. package/node_modules/@groove-dev/gui/package.json +1 -1
  15. package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
  16. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +6 -7
  17. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
  18. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +5 -6
  19. package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
  20. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -53
  21. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
  22. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
  23. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
  24. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
  25. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +0 -1
  26. package/node_modules/@groove-dev/gui/src/stores/groove.js +3 -3
  27. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
  28. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
  29. package/node_modules/@groove-dev/gui/src/views/models.jsx +3 -3
  30. package/package.json +1 -1
  31. package/packages/cli/package.json +1 -1
  32. package/packages/daemon/package.json +1 -1
  33. package/packages/daemon/src/api.js +18 -7
  34. package/packages/daemon/src/introducer.js +1 -1
  35. package/packages/daemon/src/journalist.js +3 -2
  36. package/packages/daemon/src/keeper.js +2 -2
  37. package/packages/daemon/src/memory.js +8 -5
  38. package/packages/daemon/src/process.js +5 -16
  39. package/packages/daemon/src/rotator.js +25 -8
  40. package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
  41. package/packages/gui/dist/assets/index-Bjd91ufV.js +984 -0
  42. package/packages/gui/dist/assets/index-BqdwIFn4.css +1 -0
  43. package/packages/gui/dist/index.html +3 -3
  44. package/packages/gui/package.json +1 -1
  45. package/packages/gui/src/app.jsx +0 -2
  46. package/packages/gui/src/components/agents/agent-chat.jsx +6 -7
  47. package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
  48. package/packages/gui/src/components/agents/agent-file-tree.jsx +5 -6
  49. package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
  50. package/packages/gui/src/components/agents/workspace-mode.jsx +2 -53
  51. package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
  52. package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
  53. package/packages/gui/src/components/editor/code-editor.jsx +2 -68
  54. package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
  55. package/packages/gui/src/components/layout/terminal-panel.jsx +0 -1
  56. package/packages/gui/src/stores/groove.js +3 -3
  57. package/packages/gui/src/views/dashboard.jsx +2 -0
  58. package/packages/gui/src/views/marketplace.jsx +3 -71
  59. package/packages/gui/src/views/models.jsx +3 -3
  60. package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +0 -1
  61. package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +0 -8696
  62. package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
  63. package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
  64. package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
  65. package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
  66. package/packages/gui/dist/assets/index-A4e1gIDh.css +0 -1
  67. package/packages/gui/dist/assets/index-P1hsM27-.js +0 -8696
  68. package/packages/gui/src/components/toys/toy-card.jsx +0 -78
  69. package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
  70. package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
  71. package/packages/gui/src/views/toys.jsx +0 -162
@@ -6,7 +6,6 @@ import { AgentFileTree } from './agent-file-tree';
6
6
  import { DiffViewer } from './diff-viewer';
7
7
  import { CodeReview } from './code-review';
8
8
  import { CodeEditor } from '../editor/code-editor';
9
- import { AiPanel } from '../editor/ai-panel';
10
9
  import { SelectionMenu } from '../editor/selection-menu';
11
10
  import { InlinePrompt } from '../editor/inline-prompt';
12
11
  import { QuickSearch } from '../editor/quick-search';
@@ -15,7 +14,7 @@ import { roleColor } from '../../lib/status';
15
14
  import { MediaViewer, isMediaFile } from '../editor/media-viewer';
16
15
  import {
17
16
  X, Code2, FileCode, GitCompareArrows,
18
- ClipboardCheck, Users, PanelLeftOpen, Sparkles, Search,
17
+ ClipboardCheck, Users, PanelLeftOpen, Search,
19
18
  } from 'lucide-react';
20
19
 
21
20
  const TREE_DEFAULT = 220;
@@ -61,7 +60,7 @@ function AgentRail({ agents, activeId, onSelect }) {
61
60
  );
62
61
  }
63
62
 
64
- function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggleDiff, workspaceSnapshots, onBackToTeam, onToggleReview, reviewMode, onToggleAi, aiOpen, onCmdP }) {
63
+ function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggleDiff, workspaceSnapshots, onBackToTeam, onToggleReview, reviewMode, onCmdP }) {
65
64
  const hasSnapshot = activeFile && workspaceSnapshots[activeFile];
66
65
 
67
66
  return (
@@ -142,19 +141,6 @@ function TabBar({ tabs, activeFile, files, onSelect, onClose, diffMode, onToggle
142
141
  <ClipboardCheck size={12} />
143
142
  </button>
144
143
  </Tooltip>
145
- <Tooltip content="AI Panel" side="bottom">
146
- <button
147
- onClick={onToggleAi}
148
- className={cn(
149
- 'flex items-center gap-1 px-2 py-1 text-xs font-sans rounded cursor-pointer transition-colors',
150
- aiOpen
151
- ? 'bg-accent/15 text-accent'
152
- : 'text-text-3 hover:text-text-1 hover:bg-surface-3',
153
- )}
154
- >
155
- <Sparkles size={12} />
156
- </button>
157
- </Tooltip>
158
144
  <Tooltip content="Back to Team View" side="bottom">
159
145
  <button
160
146
  onClick={onBackToTeam}
@@ -177,10 +163,6 @@ export function WorkspaceMode() {
177
163
  const toggleReviewMode = useGrooveStore((s) => s.toggleReviewMode);
178
164
  const workspaceSnapshots = useGrooveStore((s) => s.workspaceSnapshots);
179
165
  const setWorkspaceMode = useGrooveStore((s) => s.setWorkspaceMode);
180
- const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
181
- const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
182
- const aiPanelWidth = useGrooveStore((s) => s.editorAiPanelWidth);
183
- const setAiPanelWidth = useGrooveStore((s) => s.setEditorAiPanelWidth);
184
166
  const setQuickSearchOpen = useGrooveStore((s) => s.setEditorQuickSearchOpen);
185
167
 
186
168
  const editorFiles = useGrooveStore((s) => s.editorFiles);
@@ -205,9 +187,6 @@ export function WorkspaceMode() {
205
187
  const treeDragging = useRef(false);
206
188
  const startX = useRef(0);
207
189
  const startW = useRef(0);
208
- const aiDragging = useRef(false);
209
- const aiStartX = useRef(0);
210
- const aiStartW = useRef(0);
211
190
 
212
191
  useEffect(() => {
213
192
  setDiffMode(false);
@@ -238,24 +217,6 @@ export function WorkspaceMode() {
238
217
  document.addEventListener('mouseup', onUp);
239
218
  }, [treeWidth]);
240
219
 
241
- const onAiPanelMouseDown = useCallback((e) => {
242
- e.preventDefault();
243
- aiDragging.current = true;
244
- aiStartX.current = e.clientX;
245
- aiStartW.current = aiPanelWidth;
246
- function onMove(e) {
247
- if (!aiDragging.current) return;
248
- const delta = aiStartX.current - e.clientX;
249
- setAiPanelWidth(Math.min(Math.max(aiStartW.current + delta, 280), 600));
250
- }
251
- function onUp() {
252
- aiDragging.current = false;
253
- document.removeEventListener('mousemove', onMove);
254
- document.removeEventListener('mouseup', onUp);
255
- }
256
- document.addEventListener('mousemove', onMove);
257
- document.addEventListener('mouseup', onUp);
258
- }, [aiPanelWidth, setAiPanelWidth]);
259
220
 
260
221
  const handleEditorMouseUp = useCallback(() => {
261
222
  const view = editorViewRef.current;
@@ -357,8 +318,6 @@ export function WorkspaceMode() {
357
318
  onBackToTeam={() => setWorkspaceMode(false)}
358
319
  onToggleReview={toggleReviewMode}
359
320
  reviewMode={workspaceReviewMode}
360
- onToggleAi={toggleAiPanel}
361
- aiOpen={aiPanelOpen}
362
321
  onCmdP={() => setQuickSearchOpen(true)}
363
322
  />
364
323
 
@@ -423,16 +382,6 @@ export function WorkspaceMode() {
423
382
  )}
424
383
  </div>
425
384
 
426
- {/* AI Panel */}
427
- {aiPanelOpen && !workspaceReviewMode && (
428
- <div className="relative flex-shrink-0" style={{ width: aiPanelWidth }}>
429
- <div
430
- className="absolute top-0 left-0 bottom-0 w-1 cursor-col-resize hover:bg-accent/30 transition-colors z-10"
431
- onMouseDown={onAiPanelMouseDown}
432
- />
433
- <AiPanel />
434
- </div>
435
- )}
436
385
  </div>
437
386
 
438
387
  {/* Quick Search modal */}
@@ -0,0 +1,111 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { memo } from 'react';
3
+ import { HEX } from '../../lib/theme-hex';
4
+
5
+ const SIZE = 36;
6
+ const STROKE = 3;
7
+ const RADIUS = (SIZE - STROKE) / 2;
8
+ const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
9
+ const START_ANGLE = -90;
10
+
11
+ function gaugeColor(pct) {
12
+ if (pct > 80) return HEX.danger;
13
+ if (pct > 60) return HEX.warning;
14
+ return HEX.success;
15
+ }
16
+
17
+ function MiniGauge({ name, pct, threshold }) {
18
+ const color = gaugeColor(pct);
19
+ const dashLen = (pct / 100) * CIRCUMFERENCE;
20
+
21
+ return (
22
+ <div className="flex flex-col items-center gap-0.5" title={`${name}: ${pct}% context used`}>
23
+ <svg width={SIZE} height={SIZE} viewBox={`0 0 ${SIZE} ${SIZE}`}>
24
+ <circle
25
+ cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
26
+ fill="none" strokeWidth={STROKE}
27
+ className="stroke-surface-4"
28
+ />
29
+ <circle
30
+ cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
31
+ fill="none" strokeWidth={STROKE}
32
+ strokeLinecap="round"
33
+ style={{
34
+ stroke: color,
35
+ strokeDasharray: `${dashLen} ${CIRCUMFERENCE - dashLen}`,
36
+ strokeDashoffset: 0,
37
+ transition: 'stroke-dasharray 0.5s ease',
38
+ }}
39
+ transform={`rotate(${START_ANGLE} ${SIZE / 2} ${SIZE / 2})`}
40
+ />
41
+ {threshold && (
42
+ <circle
43
+ cx={SIZE / 2} cy={SIZE / 2} r={RADIUS}
44
+ fill="none" strokeWidth={1}
45
+ strokeLinecap="butt"
46
+ style={{
47
+ stroke: HEX.purple,
48
+ strokeDasharray: `1 ${CIRCUMFERENCE - 1}`,
49
+ strokeDashoffset: -(threshold / 100) * CIRCUMFERENCE,
50
+ }}
51
+ transform={`rotate(${START_ANGLE} ${SIZE / 2} ${SIZE / 2})`}
52
+ />
53
+ )}
54
+ <text
55
+ x={SIZE / 2} y={SIZE / 2 + 1}
56
+ textAnchor="middle" dominantBaseline="central"
57
+ className="fill-text-1 font-mono font-semibold"
58
+ style={{ fontSize: 9 }}
59
+ >
60
+ {pct}
61
+ </text>
62
+ </svg>
63
+ <span className="text-2xs font-mono text-text-3 truncate max-w-[40px] leading-none">{name}</span>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ function FleetSummary({ zones }) {
69
+ return (
70
+ <div className="flex items-center gap-2 text-2xs font-mono">
71
+ <span className="text-success">{zones.healthy}</span>
72
+ <span className="text-text-4">/</span>
73
+ <span className="text-warning">{zones.warning}</span>
74
+ <span className="text-text-4">/</span>
75
+ <span className="text-danger">{zones.critical}</span>
76
+ </div>
77
+ );
78
+ }
79
+
80
+ const ContextGauges = memo(function ContextGauges({ agentBreakdown }) {
81
+ const alive = (agentBreakdown || []).filter(
82
+ (a) => a.status === 'running' || a.status === 'starting',
83
+ );
84
+ if (alive.length === 0) return null;
85
+
86
+ const zones = { healthy: 0, warning: 0, critical: 0 };
87
+ for (const a of alive) {
88
+ const pct = Math.round((a.contextUsage || 0) * 100);
89
+ if (pct > 80) zones.critical++;
90
+ else if (pct > 60) zones.warning++;
91
+ else zones.healthy++;
92
+ }
93
+
94
+ return (
95
+ <div className="px-3 py-2 flex-shrink-0 border-b border-border">
96
+ <div className="flex items-center justify-between mb-1.5">
97
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Context Health</span>
98
+ <FleetSummary zones={zones} />
99
+ </div>
100
+ <div className="flex items-start gap-2 overflow-x-auto">
101
+ {alive.map((a) => {
102
+ const pct = Math.round((a.contextUsage || 0) * 100);
103
+ const threshold = a.rotationThreshold ? Math.round(a.rotationThreshold * 100) : null;
104
+ return <MiniGauge key={a.id} name={a.name} pct={pct} threshold={threshold} />;
105
+ })}
106
+ </div>
107
+ </div>
108
+ );
109
+ });
110
+
111
+ export { ContextGauges };
@@ -6,6 +6,65 @@ import { fmtNum, fmtPct } from '../../lib/format';
6
6
  const TIER_COLORS = { heavy: HEX.danger, medium: HEX.warning, light: HEX.success };
7
7
  const TIER_LABELS = { heavy: 'Heavy', medium: 'Medium', light: 'Light' };
8
8
 
9
+ const DONUT_SIZE = 80;
10
+ const DONUT_STROKE = 6;
11
+ const DONUT_RADIUS = (DONUT_SIZE - DONUT_STROKE) / 2;
12
+ const DONUT_CIRCUMFERENCE = 2 * Math.PI * DONUT_RADIUS;
13
+
14
+ function TierDonut({ byTier, total, tiers }) {
15
+ let offset = 0;
16
+ const segments = [];
17
+ for (const tier of tiers) {
18
+ const count = byTier[tier] || 0;
19
+ if (count === 0) continue;
20
+ const pct = count / total;
21
+ const dashLen = pct * DONUT_CIRCUMFERENCE;
22
+ segments.push({ tier, dashLen, offset });
23
+ offset += dashLen;
24
+ }
25
+
26
+ const heavyPct = total > 0 ? Math.round(((byTier.heavy || 0) / total) * 100) : 0;
27
+
28
+ return (
29
+ <svg width={DONUT_SIZE} height={DONUT_SIZE} viewBox={`0 0 ${DONUT_SIZE} ${DONUT_SIZE}`} className="flex-shrink-0">
30
+ <circle
31
+ cx={DONUT_SIZE / 2} cy={DONUT_SIZE / 2} r={DONUT_RADIUS}
32
+ fill="none" strokeWidth={DONUT_STROKE}
33
+ className="stroke-surface-4"
34
+ />
35
+ {segments.map((seg) => (
36
+ <circle
37
+ key={seg.tier}
38
+ cx={DONUT_SIZE / 2} cy={DONUT_SIZE / 2} r={DONUT_RADIUS}
39
+ fill="none" strokeWidth={DONUT_STROKE}
40
+ strokeLinecap="butt"
41
+ style={{
42
+ stroke: TIER_COLORS[seg.tier],
43
+ strokeDasharray: `${seg.dashLen} ${DONUT_CIRCUMFERENCE - seg.dashLen}`,
44
+ strokeDashoffset: -seg.offset,
45
+ }}
46
+ transform={`rotate(-90 ${DONUT_SIZE / 2} ${DONUT_SIZE / 2})`}
47
+ />
48
+ ))}
49
+ <text
50
+ x={DONUT_SIZE / 2} y={DONUT_SIZE / 2 - 2}
51
+ textAnchor="middle" dominantBaseline="central"
52
+ className="fill-text-0 text-sm font-mono font-semibold"
53
+ >
54
+ {fmtNum(total)}
55
+ </text>
56
+ <text
57
+ x={DONUT_SIZE / 2} y={DONUT_SIZE / 2 + 11}
58
+ textAnchor="middle" dominantBaseline="central"
59
+ className="fill-text-3 font-mono"
60
+ style={{ fontSize: 7 }}
61
+ >
62
+ decisions
63
+ </text>
64
+ </svg>
65
+ );
66
+ }
67
+
9
68
  const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
10
69
  if (!routing) return null;
11
70
 
@@ -13,7 +72,6 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
13
72
  const tiers = ['heavy', 'medium', 'light'];
14
73
  const total = tiers.reduce((s, t) => s + (byTier[t] || 0), 0);
15
74
 
16
- // Build model usage from agent breakdown
17
75
  const modelUsage = {};
18
76
  for (const a of (agentBreakdown || [])) {
19
77
  const model = a.model || 'default';
@@ -26,43 +84,27 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
26
84
 
27
85
  return (
28
86
  <div className="flex flex-col h-full px-3 py-3 overflow-y-auto">
29
- {/* Tier distribution bar */}
87
+ {/* Tier donut + legend */}
30
88
  {total > 0 && (
31
- <div className="space-y-1.5 mb-3">
32
- <div className="flex items-center gap-2">
33
- <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Tier Distribution</span>
34
- <span className="text-2xs font-mono text-text-4 ml-auto tabular-nums">{fmtNum(total)} decisions</span>
35
- </div>
36
- {/* Stacked horizontal bar */}
37
- <div className="h-0.5 bg-surface-2 rounded-sm overflow-hidden flex">
89
+ <div className="flex items-center gap-3 mb-3">
90
+ <TierDonut byTier={byTier} total={total} tiers={tiers} />
91
+ <div className="flex flex-col gap-1.5 flex-1 min-w-0">
38
92
  {tiers.map((tier) => {
39
93
  const count = byTier[tier] || 0;
40
94
  if (count === 0) return null;
41
95
  const pct = (count / total) * 100;
42
- return (
43
- <div
44
- key={tier}
45
- className="h-full transition-all duration-500"
46
- style={{ width: `${pct}%`, background: TIER_COLORS[tier] }}
47
- title={`${TIER_LABELS[tier]}: ${count} (${Math.round(pct)}%)`}
48
- />
49
- );
50
- })}
51
- </div>
52
- {/* Tier legend */}
53
- <div className="flex items-center gap-3">
54
- {tiers.map((tier) => {
55
- const count = byTier[tier] || 0;
56
- if (count === 0) return null;
57
- const pct = total > 0 ? (count / total) * 100 : 0;
58
96
  return (
59
97
  <div key={tier} className="flex items-center gap-1.5">
60
98
  <span className="w-1.5 h-1.5 rounded-full flex-shrink-0" style={{ background: TIER_COLORS[tier] }} />
61
- <span className="text-2xs font-mono text-text-2">{TIER_LABELS[tier]}</span>
62
- <span className="text-2xs font-mono text-text-4 tabular-nums">{fmtPct(pct)}</span>
99
+ <span className="text-2xs font-mono text-text-2 flex-1">{TIER_LABELS[tier]}</span>
100
+ <span className="text-2xs font-mono text-text-4 tabular-nums">{count}</span>
101
+ <span className="text-2xs font-mono text-text-4 tabular-nums w-8 text-right">{fmtPct(pct)}</span>
63
102
  </div>
64
103
  );
65
104
  })}
105
+ {autoRoutedCount > 0 && (
106
+ <div className="text-2xs font-mono text-text-4 mt-0.5">{autoRoutedCount} auto-routed</div>
107
+ )}
66
108
  </div>
67
109
  </div>
68
110
  )}
@@ -70,12 +112,7 @@ const RoutingChart = memo(function RoutingChart({ routing, agentBreakdown }) {
70
112
  {/* Model usage breakdown */}
71
113
  {modelEntries.length > 0 && (
72
114
  <div className="space-y-1.5 flex-1">
73
- <div className="flex items-center gap-2">
74
- <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Models in Use</span>
75
- {autoRoutedCount > 0 && (
76
- <span className="text-2xs font-mono text-text-4 ml-auto">{autoRoutedCount} auto</span>
77
- )}
78
- </div>
115
+ <span className="text-2xs font-mono text-text-3 uppercase tracking-wider">Models in Use</span>
79
116
  <div className="space-y-1.5">
80
117
  {modelEntries.map(([model, usage]) => {
81
118
  const barPct = maxModelTokens > 0 ? (usage.tokens / maxModelTokens) * 100 : 0;
@@ -1,7 +1,7 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { useRef, useEffect, useCallback } from 'react';
3
- import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter, Decoration, ViewPlugin, gutter, GutterMarker } from '@codemirror/view';
4
- import { EditorState, Compartment, StateField, StateEffect, RangeSet } from '@codemirror/state';
3
+ import { EditorView, keymap, lineNumbers, highlightActiveLine, highlightActiveLineGutter } from '@codemirror/view';
4
+ import { EditorState, Compartment } from '@codemirror/state';
5
5
  import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
6
6
  import { bracketMatching } from '@codemirror/language';
7
7
  import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
@@ -26,7 +26,6 @@ import {
26
26
  vscodeLight, xcodeLight,
27
27
  } from '@uiw/codemirror-themes-all';
28
28
  import { useGrooveStore } from '../../stores/groove';
29
- import { api } from '../../lib/api';
30
29
 
31
30
  const LANGS = {
32
31
  javascript: () => javascript({ jsx: true, typescript: false }),
@@ -79,52 +78,6 @@ export const EDITOR_THEMES = {
79
78
  basicLight: { label: 'Basic Light', ext: basicLight },
80
79
  };
81
80
 
82
- // ── Git gutter decorations ───────────────────────────────────
83
- const setGitLines = StateEffect.define();
84
-
85
- const gitLinesField = StateField.define({
86
- create() { return { added: new Set(), modified: new Set(), deleted: new Set() }; },
87
- update(value, tr) {
88
- for (const e of tr.effects) {
89
- if (e.is(setGitLines)) return e.value;
90
- }
91
- return value;
92
- },
93
- });
94
-
95
- class GitGutterMarker extends GutterMarker {
96
- constructor(type) { super(); this.type = type; }
97
- toDOM() {
98
- const el = document.createElement('div');
99
- el.style.width = '3px';
100
- el.style.height = '100%';
101
- el.style.borderRadius = '1px';
102
- if (this.type === 'added') el.style.background = 'var(--color-success)';
103
- else if (this.type === 'modified') el.style.background = 'var(--color-warning)';
104
- else el.style.background = 'var(--color-danger)';
105
- return el;
106
- }
107
- }
108
-
109
- const addedMarker = new GitGutterMarker('added');
110
- const modifiedMarker = new GitGutterMarker('modified');
111
- const deletedMarker = new GitGutterMarker('deleted');
112
-
113
- const gitGutter = gutter({
114
- class: 'cm-git-gutter',
115
- markers(view) {
116
- const lines = view.state.field(gitLinesField);
117
- const markers = [];
118
- for (let i = 1; i <= view.state.doc.lines; i++) {
119
- const lineStart = view.state.doc.line(i).from;
120
- if (lines.added.has(i)) markers.push(addedMarker.range(lineStart));
121
- else if (lines.modified.has(i)) markers.push(modifiedMarker.range(lineStart));
122
- else if (lines.deleted.has(i)) markers.push(deletedMarker.range(lineStart));
123
- }
124
- return RangeSet.of(markers);
125
- },
126
- });
127
-
128
81
  const editorChrome = EditorView.theme({
129
82
  '&': { fontFamily: 'var(--font-mono)', fontSize: '12px', height: '100%', lineHeight: '1.6' },
130
83
  '.cm-scroller': { overflow: 'auto', padding: '4px 0' },
@@ -139,8 +92,6 @@ const editorChrome = EditorView.theme({
139
92
  '.cm-search .cm-button, .cm-button': { borderRadius: '4px', padding: '2px 8px', fontSize: '10px', fontFamily: 'var(--font-sans)', cursor: 'pointer', backgroundImage: 'none' },
140
93
  '.cm-search br': { display: 'none' },
141
94
  '.cm-panel.cm-search [name=close]': { cursor: 'pointer', padding: '0 4px' },
142
- '.cm-git-gutter': { width: '4px', marginRight: '2px' },
143
- '.cm-git-gutter .cm-gutterElement': { padding: '0', minWidth: '3px' },
144
95
  });
145
96
 
146
97
  function getThemeExt(key) {
@@ -186,8 +137,6 @@ export function CodeEditor({ content, language, onChange, onSave, onCursorChange
186
137
  const state = EditorState.create({
187
138
  doc: content || '',
188
139
  extensions: [
189
- gitLinesField,
190
- gitGutter,
191
140
  lineNumbers(),
192
141
  highlightActiveLine(),
193
142
  highlightActiveLineGutter(),
@@ -220,21 +169,6 @@ export function CodeEditor({ content, language, onChange, onSave, onCursorChange
220
169
  return () => { view.destroy(); viewRef.current = null; if (externalViewRef) externalViewRef.current = null; };
221
170
  }, []);
222
171
 
223
- // Fetch and apply git line status when filePath changes
224
- useEffect(() => {
225
- const view = viewRef.current;
226
- if (!view || !filePath) return;
227
- api.get(`/files/git-line-status?path=${encodeURIComponent(filePath)}`).then((data) => {
228
- if (!data?.lines) return;
229
- const lines = {
230
- added: new Set(data.lines.added || []),
231
- modified: new Set(data.lines.modified || []),
232
- deleted: new Set(data.lines.deleted || []),
233
- };
234
- view.dispatch({ effects: setGitLines.of(lines) });
235
- }).catch(() => {});
236
- }, [filePath, content]);
237
-
238
172
  useEffect(() => {
239
173
  const view = viewRef.current;
240
174
  if (!view) return;
@@ -1,5 +1,5 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { Network, Code2, ChartSpline, Puzzle, Gamepad2, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, Eye, BookOpen } from 'lucide-react';
2
+ import { Network, Code2, ChartSpline, Puzzle, Users, Box, FlaskConical, Newspaper, Settings, Globe, MessageCircle, Eye, BookOpen } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
4
  import { Tooltip } from '../ui/tooltip';
5
5
  import { useGrooveStore } from '../../stores/groove';
@@ -13,7 +13,6 @@ const BASE_NAV_ITEMS = [
13
13
  { id: 'memory', icon: BookOpen, label: 'Memory' },
14
14
  { id: 'teams', icon: Users, label: 'Teams' },
15
15
  { id: 'marketplace', icon: Puzzle, label: 'Marketplace' },
16
- { id: 'toys', icon: Gamepad2, label: 'Toys' },
17
16
  { id: 'models', icon: Box, label: 'Models' },
18
17
  { id: 'model-lab', icon: FlaskConical, label: 'Model Lab' },
19
18
  ];
@@ -228,7 +228,6 @@ export function TerminalPanel({
228
228
  <Tooltip content={activeAgent ? `Send to ${agent?.name || 'agent'}` : 'Send to agent'} side="top">
229
229
  <button
230
230
  onClick={handleSendClick}
231
- disabled={sending}
232
231
  className={cn(
233
232
  'flex items-center gap-1.5 px-2 py-1 rounded text-xs font-sans cursor-pointer transition-colors mr-1',
234
233
  'bg-accent/15 text-accent hover:bg-accent/25 disabled:opacity-50',
@@ -1322,7 +1322,7 @@ export const useGrooveStore = create((set, get) => ({
1322
1322
  },
1323
1323
 
1324
1324
  async _handleKeeperCommand(agentId, message, command) {
1325
- const rest = message.replace(/\[\w+[-\w]*\]|\b(?:save|append|update|delete|view|doc|link|read|instruct)\b/i, '').trim();
1325
+ const rest = message.replace(/\[\w+[-\w]*\]/i, '').trim();
1326
1326
  const tags = (rest.match(/#[\w/.-]+/g) || []).map(t => t.replace(/^#/, ''));
1327
1327
 
1328
1328
  const addSystemMsg = (text) => {
@@ -2684,9 +2684,9 @@ export const useGrooveStore = create((set, get) => ({
2684
2684
 
2685
2685
  async instructAgent(id, message) {
2686
2686
  // ── Keeper command interception ─────────────────────────
2687
- const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]|\b(save|append|update|delete|view|doc|link|read)\b(?=\s+#[\w/.-])/i);
2687
+ const keeperCmd = message.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
2688
2688
  if (keeperCmd) {
2689
- const handled = await get()._handleKeeperCommand(id, message, (keeperCmd[1] || keeperCmd[2]).toLowerCase());
2689
+ const handled = await get()._handleKeeperCommand(id, message, keeperCmd[1].toLowerCase());
2690
2690
  if (handled === true) return { status: 'keeper_handled' };
2691
2691
  if (handled?.passthrough) {
2692
2692
  message = handled.passthrough;
@@ -4,6 +4,7 @@ import { useGrooveStore } from '../stores/groove';
4
4
  import { DashboardHeader } from '../components/dashboard/header-bar';
5
5
  import { KpiStrip } from '../components/dashboard/kpi-card';
6
6
  import { FleetPanel } from '../components/dashboard/fleet-panel';
7
+ import { ContextGauges } from '../components/dashboard/context-gauges';
7
8
  import { TokenChart } from '../components/dashboard/token-chart';
8
9
  import { CacheRing } from '../components/dashboard/cache-ring';
9
10
  import { RoutingChart } from '../components/dashboard/routing-chart';
@@ -142,6 +143,7 @@ export default function DashboardView() {
142
143
  <div className="px-3 pt-2.5 pb-1 flex-shrink-0">
143
144
  <span className="text-2xs font-mono text-text-3 uppercase tracking-widest">Agent Fleet</span>
144
145
  </div>
146
+ <ContextGauges agentBreakdown={agentBreakdown} />
145
147
  <FleetPanel agentBreakdown={agentBreakdown} rotating={rotating} teams={teams} />
146
148
  </div>
147
149
 
@@ -4,15 +4,13 @@ import { ScrollArea } from '../components/ui/scroll-area';
4
4
  import { Badge } from '../components/ui/badge';
5
5
  import { Button } from '../components/ui/button';
6
6
  import { Skeleton } from '../components/ui/skeleton';
7
- import { SkillCard, SkillCardSkeleton } from '../components/marketplace/skill-card';
7
+ import { SkillCardSkeleton } from '../components/marketplace/skill-card';
8
8
  import { MarketplaceCard } from '../components/marketplace/marketplace-card';
9
9
  import { SearchBar } from '../components/marketplace/search-bar';
10
- import { CategoryBar } from '../components/marketplace/category-bar';
11
10
  import { MarketplaceBadge } from '../components/marketplace/marketplace-badge';
12
11
  import { StarRating } from '../components/marketplace/star-rating';
13
12
  import { PriceBadge } from '../components/marketplace/price-badge';
14
13
  import { VerifiedShield } from '../components/marketplace/verified-shield';
15
- import { markFavorites } from '../components/marketplace/favorites';
16
14
  import { api } from '../lib/api';
17
15
  import { useToast } from '../lib/hooks/use-toast';
18
16
  import { fmtNum, timeAgo } from '../lib/format';
@@ -22,7 +20,7 @@ import { RepoImport } from '../components/marketplace/repo-import';
22
20
  import { RepoCard } from '../components/marketplace/repo-card';
23
21
  import { RepoNukeDialog } from '../components/marketplace/repo-nuke-dialog';
24
22
  import {
25
- ChevronLeft, ChevronDown, Sparkles, Plug, LogIn,
23
+ ChevronLeft, Plug, LogIn,
26
24
  Upload, Package, Download, ShoppingBag, RefreshCw, Trash2,
27
25
  GitBranch,
28
26
  } from 'lucide-react';
@@ -214,70 +212,6 @@ function SkillDetail({ skill, onBack }) {
214
212
  }
215
213
 
216
214
  // ── Skills Browse ────────────────────────────────────────
217
- function SkillsBrowse() {
218
- const [skills, setSkills] = useState([]);
219
- const [loading, setLoading] = useState(true);
220
- const [search, setSearch] = useState('');
221
- const [category, setCategory] = useState('');
222
- const [sort, setSort] = useState('popular');
223
- const [selectedSkill, setSelectedSkill] = useState(null);
224
-
225
- useEffect(() => {
226
- setLoading(true);
227
- const params = new URLSearchParams();
228
- if (search) params.set('search', search);
229
- if (category) params.set('category', category);
230
- if (sort) params.set('sort', sort);
231
- api.get(`/skills/registry?${params}`)
232
- .then((d) => setSkills(markFavorites(d.skills || d.items || (Array.isArray(d) ? d : []))))
233
- .catch(() => setSkills([]))
234
- .finally(() => setLoading(false));
235
- }, [search, category, sort]);
236
-
237
- if (selectedSkill) {
238
- return <SkillDetail skill={selectedSkill} onBack={() => setSelectedSkill(null)} />;
239
- }
240
-
241
- return (
242
- <ScrollArea className="h-full">
243
- <div className="px-5 py-4">
244
- <div className="flex items-center gap-3">
245
- <div className="w-72">
246
- <SearchBar value={search} onChange={setSearch} />
247
- </div>
248
- <CategoryBar selected={category} onSelect={setCategory} />
249
- <div className="flex-1" />
250
- <div className="relative flex-shrink-0">
251
- <select
252
- value={sort}
253
- onChange={(e) => setSort(e.target.value)}
254
- className="appearance-none font-sans cursor-pointer pr-7 py-2 pl-3 text-xs bg-surface-0 border border-border-subtle rounded text-text-1 focus:outline-none"
255
- >
256
- <option value="popular">Popular</option>
257
- <option value="rating">Top Rated</option>
258
- <option value="newest">Newest</option>
259
- <option value="name">A-Z</option>
260
- </select>
261
- <ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none" />
262
- </div>
263
- <span className="text-2xs text-text-4 font-mono flex-shrink-0">{skills.length}</span>
264
- </div>
265
-
266
- <div className="mt-4 grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))' }}>
267
- {loading
268
- ? Array.from({ length: 8 }).map((_, i) => <SkillCardSkeleton key={i} />)
269
- : skills.map((s) => <SkillCard key={s.id} skill={s} onClick={setSelectedSkill} />)
270
- }
271
- </div>
272
-
273
- {!loading && skills.length === 0 && (
274
- <div className="text-center py-16 text-text-4 font-sans text-sm">No skills found.</div>
275
- )}
276
- </div>
277
- </ScrollArea>
278
- );
279
- }
280
-
281
215
  // ── Integrations Browse ──────────────────────────────────
282
216
  const GOOGLE_IDS = new Set(['gmail', 'google-calendar', 'google-drive', 'google-docs', 'google-sheets', 'google-slides']);
283
217
 
@@ -666,10 +600,9 @@ function GitHubBrowse() {
666
600
 
667
601
  // ── Main ─────────────────────────────────────────────────
668
602
  export default function MarketplaceView() {
669
- const [tab, setTab] = useState('skills');
603
+ const [tab, setTab] = useState('integrations');
670
604
 
671
605
  const tabs = [
672
- { id: 'skills', label: 'Skills', icon: Sparkles },
673
606
  { id: 'integrations', label: 'Integrations', icon: Plug },
674
607
  { id: 'github', label: 'GitHub', icon: GitBranch },
675
608
  { id: 'library', label: 'My Library', icon: Package },
@@ -701,7 +634,6 @@ export default function MarketplaceView() {
701
634
 
702
635
  {/* Content */}
703
636
  <div className="flex-1 min-h-0">
704
- {tab === 'skills' && <SkillsBrowse />}
705
637
  {tab === 'integrations' && <IntegrationsBrowse />}
706
638
  {tab === 'github' && <GitHubBrowse />}
707
639
  {tab === 'library' && <MyLibrary />}