groove-dev 0.19.8 → 0.19.9
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 +9 -0
- package/node_modules/@groove-dev/cli/bin/groove.js +1 -1
- package/node_modules/@groove-dev/gui/.groove/audit.log +14 -0
- package/node_modules/@groove-dev/gui/.groove/codebase-index.json +1 -1
- package/node_modules/@groove-dev/gui/.groove/config.json +1 -1
- package/node_modules/@groove-dev/gui/.groove/daemon.pid +1 -1
- package/node_modules/@groove-dev/gui/.groove/timeline.json +184 -0
- package/node_modules/@groove-dev/gui/AGENTS_REGISTRY.md +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-CdbNHOqF.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-CF0k082p.js → index-Db0ZssmH.js} +111 -111
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +167 -103
- package/package.json +1 -1
- package/packages/cli/bin/groove.js +1 -1
- package/packages/gui/dist/assets/index-CdbNHOqF.css +1 -0
- package/packages/gui/dist/assets/{index-CF0k082p.js → index-Db0ZssmH.js} +111 -111
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/views/settings.jsx +167 -103
- package/node_modules/@groove-dev/gui/dist/assets/index-DqtVdTZe.css +0 -1
- package/packages/gui/dist/assets/index-DqtVdTZe.css +0 -1
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
7
7
|
<title>Groove GUI</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Db0ZssmH.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
|
|
13
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CdbNHOqF.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="root"></div>
|
|
@@ -7,13 +7,15 @@ import { ScrollArea } from '../components/ui/scroll-area';
|
|
|
7
7
|
import { Skeleton } from '../components/ui/skeleton';
|
|
8
8
|
import { StatusDot } from '../components/ui/status-dot';
|
|
9
9
|
import { OllamaSetup } from '../components/agents/ollama-setup';
|
|
10
|
+
import { FolderBrowser } from '../components/agents/folder-browser';
|
|
10
11
|
import { api } from '../lib/api';
|
|
11
12
|
import { cn } from '../lib/cn';
|
|
12
13
|
import { fmtUptime } from '../lib/format';
|
|
13
14
|
import {
|
|
14
15
|
Key, Eye, EyeOff, Check, Cpu, ChevronDown,
|
|
15
|
-
FolderOpen, RotateCw, Users, Gauge, Zap,
|
|
16
|
-
LogIn, LogOut, User, ShieldCheck,
|
|
16
|
+
FolderOpen, FolderSearch, RotateCw, Users, Gauge, Zap,
|
|
17
|
+
LogIn, LogOut, User, ShieldCheck, Settings,
|
|
18
|
+
Newspaper, Layers,
|
|
17
19
|
} from 'lucide-react';
|
|
18
20
|
|
|
19
21
|
/* ── Toggle ────────────────────────────────────────────────── */
|
|
@@ -35,7 +37,7 @@ function Toggle({ value, onChange }) {
|
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
/* ── Provider Card
|
|
40
|
+
/* ── Provider Card ─────────────────────────────────────────── */
|
|
39
41
|
|
|
40
42
|
function ProviderCard({ provider, onKeyChange }) {
|
|
41
43
|
const [settingKey, setSettingKey] = useState(false);
|
|
@@ -44,8 +46,10 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
44
46
|
const [ollamaOpen, setOllamaOpen] = useState(false);
|
|
45
47
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
46
48
|
|
|
47
|
-
const available = provider.installed || provider.hasKey;
|
|
48
49
|
const isLocal = provider.authType === 'local';
|
|
50
|
+
const isSubscription = provider.authType === 'subscription';
|
|
51
|
+
// "Ready" means: local + installed, subscription + installed, api-key + hasKey
|
|
52
|
+
const isReady = isLocal ? provider.installed : isSubscription ? provider.installed : provider.hasKey;
|
|
49
53
|
|
|
50
54
|
async function handleSetKey() {
|
|
51
55
|
if (!keyInput.trim()) return;
|
|
@@ -70,15 +74,15 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
73
|
-
// Ollama
|
|
77
|
+
// Ollama card
|
|
74
78
|
if (isLocal) {
|
|
75
79
|
return (
|
|
76
80
|
<div className="flex flex-col rounded-lg border border-border-subtle bg-surface-1 overflow-hidden min-w-[220px]">
|
|
77
81
|
<div className="flex items-center gap-2.5 px-4 py-3 border-b border-border-subtle">
|
|
78
|
-
<StatusDot status={
|
|
82
|
+
<StatusDot status={isReady ? 'running' : 'crashed'} size="sm" />
|
|
79
83
|
<span className="text-[13px] font-semibold text-text-0 font-sans">{provider.name}</span>
|
|
80
84
|
<div className="flex-1" />
|
|
81
|
-
{
|
|
85
|
+
{isReady ? (
|
|
82
86
|
<Badge variant="success" className="text-2xs gap-1"><Check size={8} /> Ready</Badge>
|
|
83
87
|
) : (
|
|
84
88
|
<Badge variant="default" className="text-2xs">Not installed</Badge>
|
|
@@ -86,20 +90,20 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
86
90
|
</div>
|
|
87
91
|
<div className="flex-1">
|
|
88
92
|
{ollamaOpen ? (
|
|
89
|
-
<OllamaSetup isInstalled={
|
|
93
|
+
<OllamaSetup isInstalled={isReady} onModelChange={onKeyChange} />
|
|
90
94
|
) : (
|
|
91
|
-
<div className="px-4 py-3
|
|
92
|
-
<div className="text-xs text-text-3 font-sans">
|
|
93
|
-
{
|
|
95
|
+
<div className="px-4 py-3 flex flex-col h-full">
|
|
96
|
+
<div className="text-xs text-text-3 font-sans flex-1">
|
|
97
|
+
{isReady ? `${provider.models?.length || 0} models available` : 'Local AI models — free, private, no API key'}
|
|
94
98
|
</div>
|
|
95
99
|
<Button
|
|
96
|
-
variant={
|
|
100
|
+
variant={isReady ? 'secondary' : 'primary'}
|
|
97
101
|
size="sm"
|
|
98
102
|
onClick={() => setOllamaOpen(true)}
|
|
99
|
-
className="w-full h-
|
|
103
|
+
className="w-full h-7 text-2xs gap-1.5 mt-3"
|
|
100
104
|
>
|
|
101
105
|
<Cpu size={11} />
|
|
102
|
-
{
|
|
106
|
+
{isReady ? 'Manage Models' : 'Set Up Ollama'}
|
|
103
107
|
</Button>
|
|
104
108
|
</div>
|
|
105
109
|
)}
|
|
@@ -108,25 +112,26 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
108
112
|
);
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
// Standard provider card (Claude, Codex, Gemini)
|
|
111
116
|
return (
|
|
112
117
|
<div className="flex flex-col rounded-lg border border-border-subtle bg-surface-1 overflow-hidden min-w-[220px]">
|
|
113
118
|
{/* Header */}
|
|
114
119
|
<div className="flex items-center gap-2.5 px-4 py-3 border-b border-border-subtle">
|
|
115
|
-
<StatusDot status={
|
|
120
|
+
<StatusDot status={isReady ? 'running' : 'crashed'} size="sm" />
|
|
116
121
|
<span className="text-[13px] font-semibold text-text-0 font-sans">{provider.name}</span>
|
|
117
122
|
<div className="flex-1" />
|
|
118
|
-
{
|
|
123
|
+
{isReady ? (
|
|
119
124
|
<Badge variant="success" className="text-2xs gap-1"><Check size={8} /> Ready</Badge>
|
|
120
125
|
) : (
|
|
121
|
-
<Badge variant="default" className="text-2xs">No key</Badge>
|
|
126
|
+
<Badge variant="default" className="text-2xs">{isSubscription ? 'Not installed' : 'No key'}</Badge>
|
|
122
127
|
)}
|
|
123
128
|
</div>
|
|
124
129
|
|
|
125
130
|
{/* Body */}
|
|
126
|
-
<div className="flex-1 px-4 py-3
|
|
131
|
+
<div className="flex-1 flex flex-col px-4 py-3">
|
|
127
132
|
{/* Models */}
|
|
128
133
|
{provider.models?.length > 0 && (
|
|
129
|
-
<div className="flex flex-wrap gap-1">
|
|
134
|
+
<div className="flex flex-wrap gap-1 mb-2">
|
|
130
135
|
{provider.models.map((m) => (
|
|
131
136
|
<span key={m.id} className="px-1.5 py-0.5 rounded bg-surface-4 text-2xs font-mono text-text-3">
|
|
132
137
|
{m.name || m.id}
|
|
@@ -135,9 +140,27 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
135
140
|
</div>
|
|
136
141
|
)}
|
|
137
142
|
|
|
143
|
+
{/* Subscription info for Claude */}
|
|
144
|
+
{isSubscription && isReady && !provider.hasKey && (
|
|
145
|
+
<div className="flex items-center gap-1.5 h-7 px-2 bg-accent/8 border border-accent/20 rounded text-2xs font-sans text-accent mb-2">
|
|
146
|
+
<Check size={10} /> Subscription active
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Connected state */}
|
|
151
|
+
{provider.hasKey && !settingKey && (
|
|
152
|
+
<div className="flex items-center gap-1.5 mb-2">
|
|
153
|
+
<div className="flex-1 flex items-center gap-1.5 h-7 px-2 bg-success/8 border border-success/20 rounded text-2xs font-sans text-success">
|
|
154
|
+
<Check size={10} /> API Connected
|
|
155
|
+
</div>
|
|
156
|
+
<button onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }} className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans">Edit</button>
|
|
157
|
+
<button onClick={handleDeleteKey} className="text-2xs text-text-4 hover:text-danger cursor-pointer font-sans">Remove</button>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
|
|
138
161
|
{/* Key input form */}
|
|
139
|
-
{settingKey
|
|
140
|
-
<div className="space-y-1.5">
|
|
162
|
+
{settingKey && (
|
|
163
|
+
<div className="space-y-1.5 mb-2">
|
|
141
164
|
<div className="flex gap-1.5">
|
|
142
165
|
<div className="flex-1 relative">
|
|
143
166
|
<input
|
|
@@ -159,32 +182,21 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
159
182
|
<Button variant="ghost" size="sm" onClick={() => { setSettingKey(false); setKeyInput(''); }} className="h-7 text-2xs px-2">Cancel</Button>
|
|
160
183
|
</div>
|
|
161
184
|
</div>
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<button
|
|
178
|
-
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
179
|
-
className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans flex items-center gap-1"
|
|
180
|
-
>
|
|
181
|
-
<Key size={9} /> Add API key for headless mode
|
|
182
|
-
</button>
|
|
183
|
-
</div>
|
|
184
|
-
) : (
|
|
185
|
-
/* No key, needs one */
|
|
186
|
-
<Button variant="primary" size="sm" onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }} className="w-full h-7 text-2xs gap-1">
|
|
187
|
-
<Key size={10} /> Add API Key
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
{/* Spacer to push button to bottom */}
|
|
188
|
+
<div className="flex-1" />
|
|
189
|
+
|
|
190
|
+
{/* Bottom action — always at card bottom */}
|
|
191
|
+
{!settingKey && !provider.hasKey && (
|
|
192
|
+
<Button
|
|
193
|
+
variant={isSubscription ? 'secondary' : 'primary'}
|
|
194
|
+
size="sm"
|
|
195
|
+
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
196
|
+
className="w-full h-7 text-2xs gap-1 mt-2"
|
|
197
|
+
>
|
|
198
|
+
<Key size={10} />
|
|
199
|
+
{isSubscription ? 'Add API key for headless mode' : 'Add API Key'}
|
|
188
200
|
</Button>
|
|
189
201
|
)}
|
|
190
202
|
</div>
|
|
@@ -201,9 +213,7 @@ function ConfigCard({ icon: Icon, label, description, children }) {
|
|
|
201
213
|
<div className="w-6 h-6 rounded bg-accent/8 flex items-center justify-center flex-shrink-0">
|
|
202
214
|
<Icon size={12} className="text-accent" />
|
|
203
215
|
</div>
|
|
204
|
-
<div className="
|
|
205
|
-
<div className="text-[13px] font-medium text-text-0 font-sans leading-tight">{label}</div>
|
|
206
|
-
</div>
|
|
216
|
+
<div className="text-[13px] font-medium text-text-0 font-sans leading-tight">{label}</div>
|
|
207
217
|
</div>
|
|
208
218
|
<div className="text-2xs text-text-4 font-sans leading-relaxed">{description}</div>
|
|
209
219
|
<div className="mt-auto pt-1">{children}</div>
|
|
@@ -218,6 +228,7 @@ export default function SettingsView() {
|
|
|
218
228
|
const [config, setConfig] = useState(null);
|
|
219
229
|
const [daemonInfo, setDaemonInfo] = useState(null);
|
|
220
230
|
const [loading, setLoading] = useState(true);
|
|
231
|
+
const [folderBrowserOpen, setFolderBrowserOpen] = useState(false);
|
|
221
232
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
222
233
|
const marketplaceUser = useGrooveStore((s) => s.marketplaceUser);
|
|
223
234
|
const marketplaceAuthenticated = useGrooveStore((s) => s.marketplaceAuthenticated);
|
|
@@ -246,26 +257,33 @@ export default function SettingsView() {
|
|
|
246
257
|
if (loading) {
|
|
247
258
|
return (
|
|
248
259
|
<div className="flex flex-col h-full">
|
|
249
|
-
<div className="h-
|
|
260
|
+
<div className="h-12 bg-surface-1 border-b border-border" />
|
|
250
261
|
<div className="flex-1 p-4 space-y-4">
|
|
251
|
-
<div className="
|
|
262
|
+
<div className="grid grid-cols-4 gap-3">{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-40 rounded-lg" />)}</div>
|
|
252
263
|
<div className="grid grid-cols-3 gap-3">{[...Array(6)].map((_, i) => <Skeleton key={i} className="h-28 rounded-lg" />)}</div>
|
|
253
264
|
</div>
|
|
254
265
|
</div>
|
|
255
266
|
);
|
|
256
267
|
}
|
|
257
268
|
|
|
258
|
-
const
|
|
269
|
+
const connectedCount = providers.filter((p) => {
|
|
270
|
+
if (p.authType === 'local') return p.installed;
|
|
271
|
+
if (p.authType === 'subscription') return p.installed;
|
|
272
|
+
return p.hasKey;
|
|
273
|
+
}).length;
|
|
274
|
+
|
|
275
|
+
// Rotation threshold display: 0 = auto, otherwise show as percentage
|
|
276
|
+
const rotationValue = config?.rotationThreshold || 0;
|
|
277
|
+
const rotationDisplay = rotationValue === 0 ? 'auto' : `${Math.round(rotationValue * 100)}%`;
|
|
259
278
|
|
|
260
279
|
return (
|
|
261
280
|
<div className="flex flex-col h-full">
|
|
262
281
|
|
|
263
|
-
{/* ═══════
|
|
282
|
+
{/* ═══════ HEADER BAR ═══════ */}
|
|
264
283
|
<div className="flex items-center gap-4 px-4 py-2.5 bg-surface-1 border-b border-border flex-shrink-0">
|
|
265
284
|
<h2 className="text-sm font-semibold text-text-0 font-sans">Settings</h2>
|
|
266
285
|
<div className="flex-1" />
|
|
267
286
|
|
|
268
|
-
{/* Daemon info */}
|
|
269
287
|
<div className="flex items-center gap-4 text-2xs text-text-3 font-sans">
|
|
270
288
|
{daemonInfo?.version && <span>v{daemonInfo.version}</span>}
|
|
271
289
|
{daemonInfo?.port && <span>:{daemonInfo.port}</span>}
|
|
@@ -274,7 +292,6 @@ export default function SettingsView() {
|
|
|
274
292
|
|
|
275
293
|
<div className="w-px h-4 bg-border-subtle" />
|
|
276
294
|
|
|
277
|
-
{/* Account */}
|
|
278
295
|
{marketplaceAuthenticated ? (
|
|
279
296
|
<div className="flex items-center gap-2.5">
|
|
280
297
|
{marketplaceUser?.avatar ? (
|
|
@@ -302,12 +319,12 @@ export default function SettingsView() {
|
|
|
302
319
|
<ScrollArea className="flex-1">
|
|
303
320
|
<div className="p-4 space-y-4">
|
|
304
321
|
|
|
305
|
-
{/* ═══════ PROVIDERS
|
|
322
|
+
{/* ═══════ PROVIDERS ═══════ */}
|
|
306
323
|
<div>
|
|
307
324
|
<div className="flex items-center gap-2 mb-2.5 px-0.5">
|
|
308
325
|
<span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Providers</span>
|
|
309
326
|
<div className="flex-1 h-px bg-border-subtle" />
|
|
310
|
-
<span className="text-2xs text-text-4 font-sans">{
|
|
327
|
+
<span className="text-2xs text-text-4 font-sans">{connectedCount}/{providers.length} connected</span>
|
|
311
328
|
</div>
|
|
312
329
|
<div className="grid grid-cols-4 gap-3">
|
|
313
330
|
{providers.map((p) => (
|
|
@@ -316,7 +333,7 @@ export default function SettingsView() {
|
|
|
316
333
|
</div>
|
|
317
334
|
</div>
|
|
318
335
|
|
|
319
|
-
{/* ═══════ CONFIGURATION
|
|
336
|
+
{/* ═══════ CONFIGURATION ═══════ */}
|
|
320
337
|
{config && (
|
|
321
338
|
<div>
|
|
322
339
|
<div className="flex items-center gap-2 mb-2.5 px-0.5">
|
|
@@ -325,78 +342,125 @@ export default function SettingsView() {
|
|
|
325
342
|
<span className="text-2xs text-text-4 font-sans">Auto-saves</span>
|
|
326
343
|
</div>
|
|
327
344
|
<div className="grid grid-cols-3 gap-3">
|
|
345
|
+
|
|
328
346
|
<ConfigCard icon={Cpu} label="Default Provider" description="Provider used when spawning new agents.">
|
|
329
347
|
<select
|
|
330
348
|
value={config.defaultProvider || 'claude-code'}
|
|
331
349
|
onChange={(e) => updateConfig('defaultProvider', e.target.value)}
|
|
332
350
|
className="w-full h-8 px-2.5 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono focus:outline-none focus:ring-1 focus:ring-accent cursor-pointer"
|
|
333
351
|
>
|
|
334
|
-
{
|
|
352
|
+
{providers.filter((p) => p.installed || p.hasKey).map((p) => (
|
|
335
353
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
336
354
|
))}
|
|
337
355
|
</select>
|
|
338
356
|
</ConfigCard>
|
|
339
357
|
|
|
340
358
|
<ConfigCard icon={FolderOpen} label="Working Directory" description="Default root directory for new agents.">
|
|
341
|
-
<
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
<span className="text-xs font-mono text-text-2">{config.autoRotation !== false ? 'On' : 'Off'}</span>
|
|
349
|
-
<Toggle value={config.autoRotation !== false} onChange={(v) => updateConfig('autoRotation', v)} />
|
|
359
|
+
<div className="flex items-center gap-1.5">
|
|
360
|
+
<code className="flex-1 h-8 px-2 flex items-center bg-surface-0 border border-border-subtle rounded-md text-2xs font-mono text-text-2 truncate min-w-0">
|
|
361
|
+
{config.defaultWorkingDir || 'Project root'}
|
|
362
|
+
</code>
|
|
363
|
+
<Button variant="secondary" size="sm" onClick={() => setFolderBrowserOpen(true)} className="h-8 px-2 flex-shrink-0">
|
|
364
|
+
<FolderSearch size={12} />
|
|
365
|
+
</Button>
|
|
350
366
|
</div>
|
|
351
367
|
</ConfigCard>
|
|
352
368
|
|
|
353
|
-
<ConfigCard icon={Gauge} label="Rotation Threshold" description="
|
|
354
|
-
<
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
369
|
+
<ConfigCard icon={Gauge} label="Rotation Threshold" description="Context usage that triggers auto-rotation.">
|
|
370
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
371
|
+
{['auto', '50%', '65%', '75%', '85%'].map((opt) => {
|
|
372
|
+
const val = opt === 'auto' ? 0 : parseInt(opt, 10) / 100;
|
|
373
|
+
const isActive = rotationValue === val;
|
|
374
|
+
return (
|
|
375
|
+
<button
|
|
376
|
+
key={opt}
|
|
377
|
+
onClick={() => updateConfig('rotationThreshold', val)}
|
|
378
|
+
className={cn(
|
|
379
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
380
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
381
|
+
)}
|
|
382
|
+
>
|
|
383
|
+
{opt === 'auto' ? 'Auto' : opt}
|
|
384
|
+
</button>
|
|
385
|
+
);
|
|
386
|
+
})}
|
|
387
|
+
</div>
|
|
361
388
|
</ConfigCard>
|
|
362
389
|
|
|
363
|
-
<ConfigCard icon={ShieldCheck} label="QC Threshold" description="
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
390
|
+
<ConfigCard icon={ShieldCheck} label="QC Threshold" description="Running agents count that triggers auto-QC.">
|
|
391
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
392
|
+
{[2, 3, 4, 6, 8].map((n) => {
|
|
393
|
+
const isActive = (config.qcThreshold || 2) === n;
|
|
394
|
+
return (
|
|
395
|
+
<button
|
|
396
|
+
key={n}
|
|
397
|
+
onClick={() => updateConfig('qcThreshold', n)}
|
|
398
|
+
className={cn(
|
|
399
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
400
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
401
|
+
)}
|
|
402
|
+
>
|
|
403
|
+
{n}
|
|
404
|
+
</button>
|
|
405
|
+
);
|
|
406
|
+
})}
|
|
407
|
+
</div>
|
|
371
408
|
</ConfigCard>
|
|
372
409
|
|
|
373
|
-
<ConfigCard icon={Users} label="Max Agents" description="
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
410
|
+
<ConfigCard icon={Users} label="Max Agents" description="Concurrent agent limit. 0 = unlimited.">
|
|
411
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
412
|
+
{[0, 4, 8, 12, 20].map((n) => {
|
|
413
|
+
const isActive = (config.maxAgents || 0) === n;
|
|
414
|
+
return (
|
|
415
|
+
<button
|
|
416
|
+
key={n}
|
|
417
|
+
onClick={() => updateConfig('maxAgents', n)}
|
|
418
|
+
className={cn(
|
|
419
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
420
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
421
|
+
)}
|
|
422
|
+
>
|
|
423
|
+
{n === 0 ? '\u221E' : n}
|
|
424
|
+
</button>
|
|
425
|
+
);
|
|
426
|
+
})}
|
|
427
|
+
</div>
|
|
381
428
|
</ConfigCard>
|
|
382
429
|
|
|
383
|
-
<ConfigCard icon={Newspaper} label="Journalist Interval" description="Seconds between
|
|
384
|
-
<div className="flex
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
<ConfigCard icon={Newspaper} label="Journalist Interval" description="Seconds between synthesis cycles.">
|
|
431
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
432
|
+
{[60, 120, 300, 600].map((n) => {
|
|
433
|
+
const isActive = (config.journalistInterval || 120) === n;
|
|
434
|
+
const label = n < 60 ? `${n}s` : `${n / 60}m`;
|
|
435
|
+
return (
|
|
436
|
+
<button
|
|
437
|
+
key={n}
|
|
438
|
+
onClick={() => updateConfig('journalistInterval', n)}
|
|
439
|
+
className={cn(
|
|
440
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
441
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
442
|
+
)}
|
|
443
|
+
>
|
|
444
|
+
{label}
|
|
445
|
+
</button>
|
|
446
|
+
);
|
|
447
|
+
})}
|
|
393
448
|
</div>
|
|
394
449
|
</ConfigCard>
|
|
450
|
+
|
|
395
451
|
</div>
|
|
396
452
|
</div>
|
|
397
453
|
)}
|
|
398
454
|
</div>
|
|
399
455
|
</ScrollArea>
|
|
456
|
+
|
|
457
|
+
{/* Folder Browser Modal */}
|
|
458
|
+
<FolderBrowser
|
|
459
|
+
open={folderBrowserOpen}
|
|
460
|
+
onOpenChange={setFolderBrowserOpen}
|
|
461
|
+
currentPath={config?.defaultWorkingDir || '/'}
|
|
462
|
+
onSelect={(dir) => updateConfig('defaultWorkingDir', dir)}
|
|
463
|
+
/>
|
|
400
464
|
</div>
|
|
401
465
|
);
|
|
402
466
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.9",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. 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.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|