groove-dev 0.27.141 → 0.27.143
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/CLAUDE.md +7 -0
- 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 +18 -7
- package/node_modules/@groove-dev/daemon/src/introducer.js +1 -1
- package/node_modules/@groove-dev/daemon/src/journalist.js +3 -2
- package/node_modules/@groove-dev/daemon/src/keeper.js +2 -2
- package/node_modules/@groove-dev/daemon/src/memory.js +8 -5
- package/node_modules/@groove-dev/daemon/src/process.js +5 -16
- package/node_modules/@groove-dev/daemon/src/rotator.js +25 -8
- package/node_modules/@groove-dev/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/node_modules/@groove-dev/gui/dist/assets/index-CCVvAoQn.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DGIv_TRm.js +984 -0
- package/node_modules/@groove-dev/gui/dist/index.html +3 -3
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.jsx +0 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +73 -17
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +8 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/node_modules/@groove-dev/gui/src/components/agents/agent-panel.jsx +79 -5
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +2 -68
- package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +1 -2
- package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +3 -3
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +3 -71
- package/node_modules/@groove-dev/gui/src/views/models.jsx +5 -6
- 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 +18 -7
- package/packages/daemon/src/introducer.js +1 -1
- package/packages/daemon/src/journalist.js +3 -2
- package/packages/daemon/src/keeper.js +2 -2
- package/packages/daemon/src/memory.js +8 -5
- package/packages/daemon/src/process.js +5 -16
- package/packages/daemon/src/rotator.js +25 -8
- package/packages/gui/dist/assets/{codemirror-BQqYnZfL.js → codemirror-BYKpdS2W.js} +10 -10
- package/packages/gui/dist/assets/index-CCVvAoQn.css +1 -0
- package/packages/gui/dist/assets/index-DGIv_TRm.js +984 -0
- package/packages/gui/dist/index.html +3 -3
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +0 -2
- package/packages/gui/src/components/agents/agent-chat.jsx +73 -17
- package/packages/gui/src/components/agents/agent-feed.jsx +8 -2
- package/packages/gui/src/components/agents/agent-file-tree.jsx +5 -6
- package/packages/gui/src/components/agents/agent-panel.jsx +79 -5
- package/packages/gui/src/components/agents/workspace-mode.jsx +2 -53
- package/packages/gui/src/components/dashboard/context-gauges.jsx +111 -0
- package/packages/gui/src/components/dashboard/routing-chart.jsx +70 -33
- package/packages/gui/src/components/editor/code-editor.jsx +2 -68
- package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
- package/packages/gui/src/components/layout/activity-bar.jsx +1 -2
- package/packages/gui/src/components/layout/terminal-panel.jsx +0 -1
- package/packages/gui/src/stores/groove.js +3 -3
- package/packages/gui/src/views/dashboard.jsx +2 -0
- package/packages/gui/src/views/marketplace.jsx +3 -71
- package/packages/gui/src/views/models.jsx +5 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +0 -78
- package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +0 -144
- package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/node_modules/@groove-dev/gui/src/views/toys.jsx +0 -162
- package/packages/gui/dist/assets/index-A4e1gIDh.css +0 -1
- package/packages/gui/dist/assets/index-P1hsM27-.js +0 -8696
- package/packages/gui/src/components/toys/toy-card.jsx +0 -78
- package/packages/gui/src/components/toys/toy-creator.jsx +0 -144
- package/packages/gui/src/components/toys/toy-launcher.jsx +0 -187
- package/packages/gui/src/views/toys.jsx +0 -162
|
@@ -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
|
|
87
|
+
{/* Tier donut + legend */}
|
|
30
88
|
{total > 0 && (
|
|
31
|
-
<div className="
|
|
32
|
-
<
|
|
33
|
-
|
|
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">{
|
|
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
|
-
<
|
|
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
|
|
4
|
-
import { EditorState, Compartment
|
|
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;
|
|
@@ -18,6 +18,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
18
18
|
const sendCodeToAgent = useGrooveStore((s) => s.sendCodeToAgent);
|
|
19
19
|
const toggleAiPanel = useGrooveStore((s) => s.toggleAiPanel);
|
|
20
20
|
const aiPanelOpen = useGrooveStore((s) => s.editorAiPanelOpen);
|
|
21
|
+
const selectAgent = useGrooveStore((s) => s.selectAgent);
|
|
21
22
|
|
|
22
23
|
useEffect(() => {
|
|
23
24
|
function handleClick(e) {
|
|
@@ -51,6 +52,7 @@ export function SelectionMenu({ x, y, filePath, lineStart, lineEnd, selectedCode
|
|
|
51
52
|
if (!agentId) return;
|
|
52
53
|
sendCodeToAgent(agentId, action.instruction, filePath, lineStart, lineEnd, selectedCode);
|
|
53
54
|
if (!aiPanelOpen) toggleAiPanel();
|
|
55
|
+
selectAgent(agentId);
|
|
54
56
|
onClose();
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
-
import { Network, Code2, ChartSpline, Puzzle,
|
|
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]*\]
|
|
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)\]
|
|
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,
|
|
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 {
|
|
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,
|
|
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('
|
|
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 />}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
3
|
-
import { ScrollArea } from '../components/ui/scroll-area';
|
|
4
3
|
import { Badge } from '../components/ui/badge';
|
|
5
4
|
import { Button } from '../components/ui/button';
|
|
6
5
|
import { api } from '../lib/api';
|
|
@@ -603,12 +602,12 @@ export default function ModelsView() {
|
|
|
603
602
|
if (filter === 'running') list = list.filter((m) => m.status === 'running');
|
|
604
603
|
else if (filter === 'ready') list = list.filter((m) => m.status === 'ready');
|
|
605
604
|
else if (filter === 'downloaded') list = list.filter((m) => m.status === 'downloaded' || m.status === 'downloading');
|
|
606
|
-
if (searchQuery.trim()) {
|
|
605
|
+
if (searchQuery.trim() && discoveryTab !== 'search') {
|
|
607
606
|
const q = searchQuery.toLowerCase();
|
|
608
607
|
list = list.filter((m) => m.name.toLowerCase().includes(q));
|
|
609
608
|
}
|
|
610
609
|
return list;
|
|
611
|
-
}, [unifiedModels, filter, searchQuery]);
|
|
610
|
+
}, [unifiedModels, filter, searchQuery, discoveryTab]);
|
|
612
611
|
|
|
613
612
|
const filterCounts = useMemo(() => ({
|
|
614
613
|
all: unifiedModels.length,
|
|
@@ -749,7 +748,7 @@ export default function ModelsView() {
|
|
|
749
748
|
</div>
|
|
750
749
|
|
|
751
750
|
{/* ════ ZONE 2 + 3: Scrollable Content ════ */}
|
|
752
|
-
<
|
|
751
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
753
752
|
<div className="p-5 space-y-6">
|
|
754
753
|
|
|
755
754
|
{/* Empty State */}
|
|
@@ -794,7 +793,7 @@ export default function ModelsView() {
|
|
|
794
793
|
</div>
|
|
795
794
|
) : (
|
|
796
795
|
/* ── Card Grid ── */
|
|
797
|
-
<div className="grid
|
|
796
|
+
<div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))' }}>
|
|
798
797
|
{filteredModels.map((model) => (
|
|
799
798
|
<UnifiedModelCard
|
|
800
799
|
key={model.id}
|
|
@@ -965,7 +964,7 @@ export default function ModelsView() {
|
|
|
965
964
|
)}
|
|
966
965
|
</div>
|
|
967
966
|
</div>
|
|
968
|
-
</
|
|
967
|
+
</div>
|
|
969
968
|
</div>
|
|
970
969
|
);
|
|
971
970
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.143",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -460,13 +460,14 @@ export function createApi(app, daemon) {
|
|
|
460
460
|
// Discoveries (error → fix pairs)
|
|
461
461
|
app.get('/api/memory/discoveries', (req, res) => {
|
|
462
462
|
const role = req.query.role;
|
|
463
|
+
const teamId = req.query.teamId;
|
|
463
464
|
const limit = Math.min(parseInt(req.query.limit) || 100, 500);
|
|
464
|
-
res.json({ discoveries: daemon.memory.listDiscoveries({ role, limit }) });
|
|
465
|
+
res.json({ discoveries: daemon.memory.listDiscoveries({ role, teamId, limit }) });
|
|
465
466
|
});
|
|
466
467
|
|
|
467
468
|
app.post('/api/memory/discoveries', (req, res) => {
|
|
468
|
-
const { agentId, role, trigger, fix, outcome } = req.body || {};
|
|
469
|
-
const result = daemon.memory.addDiscovery({ agentId, role, trigger, fix, outcome });
|
|
469
|
+
const { agentId, role, trigger, fix, outcome, teamId } = req.body || {};
|
|
470
|
+
const result = daemon.memory.addDiscovery({ agentId, role, trigger, fix, outcome, teamId });
|
|
470
471
|
if (!result.added && result.error) {
|
|
471
472
|
return res.status(400).json(result);
|
|
472
473
|
}
|
|
@@ -3171,11 +3172,21 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3171
3172
|
|
|
3172
3173
|
function validateFilePath(relPath, projectDir) {
|
|
3173
3174
|
if (!relPath || typeof relPath !== 'string') return { error: 'path is required' };
|
|
3174
|
-
if (relPath.
|
|
3175
|
-
|
|
3175
|
+
if (relPath.includes('\0')) return { error: 'Invalid path' };
|
|
3176
|
+
|
|
3177
|
+
let fullPath;
|
|
3178
|
+
if (relPath.startsWith('/')) {
|
|
3179
|
+
if (relPath.includes('..')) return { error: 'Invalid path' };
|
|
3180
|
+
if (!relPath.startsWith(projectDir + '/') && relPath !== projectDir) {
|
|
3181
|
+
return { error: 'Path outside project' };
|
|
3182
|
+
}
|
|
3183
|
+
fullPath = relPath;
|
|
3184
|
+
} else {
|
|
3185
|
+
if (relPath.includes('..')) return { error: 'Invalid path' };
|
|
3186
|
+
fullPath = resolve(projectDir, relPath);
|
|
3187
|
+
if (!fullPath.startsWith(projectDir)) return { error: 'Path outside project' };
|
|
3176
3188
|
}
|
|
3177
|
-
|
|
3178
|
-
if (!fullPath.startsWith(projectDir)) return { error: 'Path outside project' };
|
|
3189
|
+
|
|
3179
3190
|
// Symlink resolution — ensure real path is also within project
|
|
3180
3191
|
try {
|
|
3181
3192
|
const realPath = realpathSync(fullPath);
|
|
@@ -437,7 +437,7 @@ export class Introducer {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
if (!isLightPlanner && (hasTask || isRotation)) {
|
|
440
|
-
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role, 8, 600, newAgent.scope);
|
|
440
|
+
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role, 8, 600, newAgent.scope, newAgent.teamId);
|
|
441
441
|
if (discoveries) {
|
|
442
442
|
parts.push(`### Known Fixes for ${newAgent.role} Role\n${discoveries}`);
|
|
443
443
|
}
|
|
@@ -877,7 +877,7 @@ export class Journalist {
|
|
|
877
877
|
const entries = agentLog?.entries || [];
|
|
878
878
|
|
|
879
879
|
// Layer 7 memory: discoveries (inline, not pointer — agents lose context with pointers), constraints, specializations
|
|
880
|
-
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500) || '';
|
|
880
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 1500, agent.scope, agent.teamId) || '';
|
|
881
881
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
882
882
|
const specialization = this.daemon.memory?.getSpecialization(agent.id);
|
|
883
883
|
const specLine = specialization?.avgQualityScore != null
|
|
@@ -1106,7 +1106,7 @@ export class Journalist {
|
|
|
1106
1106
|
if (!thread) return null;
|
|
1107
1107
|
|
|
1108
1108
|
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
1109
|
-
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 5, 1000) || '';
|
|
1109
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 5, 1000, agent.scope, agent.teamId) || '';
|
|
1110
1110
|
|
|
1111
1111
|
let prompt = [
|
|
1112
1112
|
`# Session Context Resume`,
|
|
@@ -1352,6 +1352,7 @@ export class Journalist {
|
|
|
1352
1352
|
this.daemon.memory.addDiscovery({
|
|
1353
1353
|
agentId: agent.id,
|
|
1354
1354
|
role: agent.role,
|
|
1355
|
+
teamId: agent.teamId || null,
|
|
1355
1356
|
trigger: trigger.slice(0, 300),
|
|
1356
1357
|
fix: fix.slice(0, 500),
|
|
1357
1358
|
outcome: 'success',
|
|
@@ -254,9 +254,9 @@ export class Keeper {
|
|
|
254
254
|
// ── Command parser ────────────────────────────────────────
|
|
255
255
|
|
|
256
256
|
static parseCommand(text) {
|
|
257
|
-
const cmdMatch = text.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]
|
|
257
|
+
const cmdMatch = text.match(/\[(save|append|update|delete|view|doc|link|read|instruct)\]/i);
|
|
258
258
|
if (!cmdMatch) return null;
|
|
259
|
-
const command =
|
|
259
|
+
const command = cmdMatch[1].toLowerCase();
|
|
260
260
|
const rest = text.slice(cmdMatch.index + cmdMatch[0].length).trim();
|
|
261
261
|
|
|
262
262
|
if (command === 'instruct') {
|