groove-dev 0.19.8 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/CLAUDE.md +1 -1
- package/node_modules/@groove-dev/cli/bin/groove.js +1 -1
- package/node_modules/@groove-dev/gui/.groove/audit.log +16 -0
- package/node_modules/@groove-dev/gui/.groove/codebase-index.json +1 -1
- package/node_modules/@groove-dev/gui/.groove/config.json +3 -3
- package/node_modules/@groove-dev/gui/.groove/timeline.json +240 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-B8ZmjJeV.css +1 -0
- package/{packages/gui/dist/assets/index-CF0k082p.js → node_modules/@groove-dev/gui/dist/assets/index-DKov-d0e.js} +111 -111
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +184 -113
- package/package.json +1 -1
- package/packages/cli/bin/groove.js +1 -1
- package/packages/gui/dist/assets/index-B8ZmjJeV.css +1 -0
- package/{node_modules/@groove-dev/gui/dist/assets/index-CF0k082p.js → packages/gui/dist/assets/index-DKov-d0e.js} +111 -111
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/views/settings.jsx +184 -113
- package/node_modules/@groove-dev/gui/.groove/daemon.host +0 -1
- package/node_modules/@groove-dev/gui/.groove/daemon.pid +0 -1
- package/node_modules/@groove-dev/gui/AGENTS_REGISTRY.md +0 -9
- 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-DKov-d0e.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-B8ZmjJeV.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 min-h-[120px]">
|
|
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-3">
|
|
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,56 +140,70 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
135
140
|
</div>
|
|
136
141
|
)}
|
|
137
142
|
|
|
138
|
-
{/*
|
|
139
|
-
{settingKey
|
|
140
|
-
<div className="
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
+
{/* Subscription info for Claude */}
|
|
144
|
+
{isSubscription && isReady && !provider.hasKey && !settingKey && (
|
|
145
|
+
<div className="flex items-center gap-1.5 h-8 px-2.5 bg-accent/8 border border-accent/20 rounded-md text-2xs font-sans text-accent mb-3">
|
|
146
|
+
<Check size={10} /> Subscription active
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Connected state */}
|
|
151
|
+
{provider.hasKey && !settingKey && (
|
|
152
|
+
<div className="flex items-center gap-2 mb-3">
|
|
153
|
+
<div className="flex-1 flex items-center gap-1.5 h-8 px-2.5 bg-success/8 border border-success/20 rounded-md 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
|
+
|
|
161
|
+
{/* Spacer */}
|
|
162
|
+
<div className="flex-1" />
|
|
163
|
+
|
|
164
|
+
{/* Key input form — takes over the bottom area */}
|
|
165
|
+
{settingKey && (
|
|
166
|
+
<div className="space-y-2.5 pt-1">
|
|
167
|
+
<div>
|
|
168
|
+
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">
|
|
169
|
+
{provider.hasKey ? 'Update API Key' : `${provider.name} API Key`}
|
|
170
|
+
</label>
|
|
171
|
+
<div className="relative">
|
|
143
172
|
<input
|
|
144
173
|
value={keyInput}
|
|
145
174
|
onChange={(e) => setKeyInput(e.target.value)}
|
|
146
175
|
onKeyDown={(e) => e.key === 'Enter' && handleSetKey()}
|
|
147
176
|
type={showKey ? 'text' : 'password'}
|
|
148
|
-
placeholder="
|
|
149
|
-
className="w-full h-
|
|
177
|
+
placeholder="sk-..."
|
|
178
|
+
className="w-full h-9 px-3 pr-9 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
150
179
|
autoFocus
|
|
151
180
|
/>
|
|
152
|
-
<button onClick={() => setShowKey(!showKey)} className="absolute right-2 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-2 cursor-pointer">
|
|
153
|
-
{showKey ? <EyeOff size={
|
|
181
|
+
<button onClick={() => setShowKey(!showKey)} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-2 cursor-pointer">
|
|
182
|
+
{showKey ? <EyeOff size={12} /> : <Eye size={12} />}
|
|
154
183
|
</button>
|
|
155
184
|
</div>
|
|
156
185
|
</div>
|
|
157
|
-
<div className="flex gap-
|
|
158
|
-
<Button variant="primary" size="sm" onClick={handleSetKey} disabled={!keyInput.trim()} className="flex-1 h-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
<div className="flex items-center gap-1.5">
|
|
165
|
-
<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">
|
|
166
|
-
<Check size={10} /> API Connected
|
|
167
|
-
</div>
|
|
168
|
-
<button onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }} className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans">Edit</button>
|
|
169
|
-
<button onClick={handleDeleteKey} className="text-2xs text-text-4 hover:text-danger cursor-pointer font-sans">Remove</button>
|
|
170
|
-
</div>
|
|
171
|
-
) : provider.authType === 'subscription' ? (
|
|
172
|
-
/* Subscription provider (Claude) — show subscription status + option to add API key */
|
|
173
|
-
<div className="space-y-1.5">
|
|
174
|
-
<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">
|
|
175
|
-
<Check size={10} /> Subscription active
|
|
186
|
+
<div className="flex gap-2">
|
|
187
|
+
<Button variant="primary" size="sm" onClick={handleSetKey} disabled={!keyInput.trim()} className="flex-1 h-8 text-xs">
|
|
188
|
+
Save Key
|
|
189
|
+
</Button>
|
|
190
|
+
<Button variant="ghost" size="sm" onClick={() => { setSettingKey(false); setKeyInput(''); }} className="h-8 text-xs px-3">
|
|
191
|
+
Cancel
|
|
192
|
+
</Button>
|
|
176
193
|
</div>
|
|
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
194
|
</div>
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
)}
|
|
196
|
+
|
|
197
|
+
{/* Bottom action — always at card bottom */}
|
|
198
|
+
{!settingKey && !provider.hasKey && (
|
|
199
|
+
<Button
|
|
200
|
+
variant={isSubscription ? 'secondary' : 'primary'}
|
|
201
|
+
size="sm"
|
|
202
|
+
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
203
|
+
className="w-full h-8 text-2xs gap-1.5 mt-2"
|
|
204
|
+
>
|
|
205
|
+
<Key size={11} />
|
|
206
|
+
{isSubscription ? 'Add API key for headless mode' : 'Add API Key'}
|
|
188
207
|
</Button>
|
|
189
208
|
)}
|
|
190
209
|
</div>
|
|
@@ -201,9 +220,7 @@ function ConfigCard({ icon: Icon, label, description, children }) {
|
|
|
201
220
|
<div className="w-6 h-6 rounded bg-accent/8 flex items-center justify-center flex-shrink-0">
|
|
202
221
|
<Icon size={12} className="text-accent" />
|
|
203
222
|
</div>
|
|
204
|
-
<div className="
|
|
205
|
-
<div className="text-[13px] font-medium text-text-0 font-sans leading-tight">{label}</div>
|
|
206
|
-
</div>
|
|
223
|
+
<div className="text-[13px] font-medium text-text-0 font-sans leading-tight">{label}</div>
|
|
207
224
|
</div>
|
|
208
225
|
<div className="text-2xs text-text-4 font-sans leading-relaxed">{description}</div>
|
|
209
226
|
<div className="mt-auto pt-1">{children}</div>
|
|
@@ -218,6 +235,7 @@ export default function SettingsView() {
|
|
|
218
235
|
const [config, setConfig] = useState(null);
|
|
219
236
|
const [daemonInfo, setDaemonInfo] = useState(null);
|
|
220
237
|
const [loading, setLoading] = useState(true);
|
|
238
|
+
const [folderBrowserOpen, setFolderBrowserOpen] = useState(false);
|
|
221
239
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
222
240
|
const marketplaceUser = useGrooveStore((s) => s.marketplaceUser);
|
|
223
241
|
const marketplaceAuthenticated = useGrooveStore((s) => s.marketplaceAuthenticated);
|
|
@@ -246,26 +264,33 @@ export default function SettingsView() {
|
|
|
246
264
|
if (loading) {
|
|
247
265
|
return (
|
|
248
266
|
<div className="flex flex-col h-full">
|
|
249
|
-
<div className="h-
|
|
267
|
+
<div className="h-12 bg-surface-1 border-b border-border" />
|
|
250
268
|
<div className="flex-1 p-4 space-y-4">
|
|
251
|
-
<div className="
|
|
269
|
+
<div className="grid grid-cols-4 gap-3">{[...Array(4)].map((_, i) => <Skeleton key={i} className="h-40 rounded-lg" />)}</div>
|
|
252
270
|
<div className="grid grid-cols-3 gap-3">{[...Array(6)].map((_, i) => <Skeleton key={i} className="h-28 rounded-lg" />)}</div>
|
|
253
271
|
</div>
|
|
254
272
|
</div>
|
|
255
273
|
);
|
|
256
274
|
}
|
|
257
275
|
|
|
258
|
-
const
|
|
276
|
+
const connectedCount = providers.filter((p) => {
|
|
277
|
+
if (p.authType === 'local') return p.installed;
|
|
278
|
+
if (p.authType === 'subscription') return p.installed;
|
|
279
|
+
return p.hasKey;
|
|
280
|
+
}).length;
|
|
281
|
+
|
|
282
|
+
// Rotation threshold display: 0 = auto, otherwise show as percentage
|
|
283
|
+
const rotationValue = config?.rotationThreshold || 0;
|
|
284
|
+
const rotationDisplay = rotationValue === 0 ? 'auto' : `${Math.round(rotationValue * 100)}%`;
|
|
259
285
|
|
|
260
286
|
return (
|
|
261
287
|
<div className="flex flex-col h-full">
|
|
262
288
|
|
|
263
|
-
{/* ═══════
|
|
289
|
+
{/* ═══════ HEADER BAR ═══════ */}
|
|
264
290
|
<div className="flex items-center gap-4 px-4 py-2.5 bg-surface-1 border-b border-border flex-shrink-0">
|
|
265
291
|
<h2 className="text-sm font-semibold text-text-0 font-sans">Settings</h2>
|
|
266
292
|
<div className="flex-1" />
|
|
267
293
|
|
|
268
|
-
{/* Daemon info */}
|
|
269
294
|
<div className="flex items-center gap-4 text-2xs text-text-3 font-sans">
|
|
270
295
|
{daemonInfo?.version && <span>v{daemonInfo.version}</span>}
|
|
271
296
|
{daemonInfo?.port && <span>:{daemonInfo.port}</span>}
|
|
@@ -274,7 +299,6 @@ export default function SettingsView() {
|
|
|
274
299
|
|
|
275
300
|
<div className="w-px h-4 bg-border-subtle" />
|
|
276
301
|
|
|
277
|
-
{/* Account */}
|
|
278
302
|
{marketplaceAuthenticated ? (
|
|
279
303
|
<div className="flex items-center gap-2.5">
|
|
280
304
|
{marketplaceUser?.avatar ? (
|
|
@@ -302,12 +326,12 @@ export default function SettingsView() {
|
|
|
302
326
|
<ScrollArea className="flex-1">
|
|
303
327
|
<div className="p-4 space-y-4">
|
|
304
328
|
|
|
305
|
-
{/* ═══════ PROVIDERS
|
|
329
|
+
{/* ═══════ PROVIDERS ═══════ */}
|
|
306
330
|
<div>
|
|
307
331
|
<div className="flex items-center gap-2 mb-2.5 px-0.5">
|
|
308
332
|
<span className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider">Providers</span>
|
|
309
333
|
<div className="flex-1 h-px bg-border-subtle" />
|
|
310
|
-
<span className="text-2xs text-text-4 font-sans">{
|
|
334
|
+
<span className="text-2xs text-text-4 font-sans">{connectedCount}/{providers.length} connected</span>
|
|
311
335
|
</div>
|
|
312
336
|
<div className="grid grid-cols-4 gap-3">
|
|
313
337
|
{providers.map((p) => (
|
|
@@ -316,7 +340,7 @@ export default function SettingsView() {
|
|
|
316
340
|
</div>
|
|
317
341
|
</div>
|
|
318
342
|
|
|
319
|
-
{/* ═══════ CONFIGURATION
|
|
343
|
+
{/* ═══════ CONFIGURATION ═══════ */}
|
|
320
344
|
{config && (
|
|
321
345
|
<div>
|
|
322
346
|
<div className="flex items-center gap-2 mb-2.5 px-0.5">
|
|
@@ -325,78 +349,125 @@ export default function SettingsView() {
|
|
|
325
349
|
<span className="text-2xs text-text-4 font-sans">Auto-saves</span>
|
|
326
350
|
</div>
|
|
327
351
|
<div className="grid grid-cols-3 gap-3">
|
|
352
|
+
|
|
328
353
|
<ConfigCard icon={Cpu} label="Default Provider" description="Provider used when spawning new agents.">
|
|
329
354
|
<select
|
|
330
355
|
value={config.defaultProvider || 'claude-code'}
|
|
331
356
|
onChange={(e) => updateConfig('defaultProvider', e.target.value)}
|
|
332
357
|
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
358
|
>
|
|
334
|
-
{
|
|
359
|
+
{providers.filter((p) => p.installed || p.hasKey).map((p) => (
|
|
335
360
|
<option key={p.id} value={p.id}>{p.name}</option>
|
|
336
361
|
))}
|
|
337
362
|
</select>
|
|
338
363
|
</ConfigCard>
|
|
339
364
|
|
|
340
365
|
<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)} />
|
|
366
|
+
<div className="flex items-center gap-1.5">
|
|
367
|
+
<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">
|
|
368
|
+
{config.defaultWorkingDir || 'Project root'}
|
|
369
|
+
</code>
|
|
370
|
+
<Button variant="secondary" size="sm" onClick={() => setFolderBrowserOpen(true)} className="h-8 px-2 flex-shrink-0">
|
|
371
|
+
<FolderSearch size={12} />
|
|
372
|
+
</Button>
|
|
350
373
|
</div>
|
|
351
374
|
</ConfigCard>
|
|
352
375
|
|
|
353
|
-
<ConfigCard icon={Gauge} label="Rotation Threshold" description="
|
|
354
|
-
<
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
376
|
+
<ConfigCard icon={Gauge} label="Rotation Threshold" description="Context usage that triggers auto-rotation.">
|
|
377
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
378
|
+
{['auto', '50%', '65%', '75%', '85%'].map((opt) => {
|
|
379
|
+
const val = opt === 'auto' ? 0 : parseInt(opt, 10) / 100;
|
|
380
|
+
const isActive = rotationValue === val;
|
|
381
|
+
return (
|
|
382
|
+
<button
|
|
383
|
+
key={opt}
|
|
384
|
+
onClick={() => updateConfig('rotationThreshold', val)}
|
|
385
|
+
className={cn(
|
|
386
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
387
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
388
|
+
)}
|
|
389
|
+
>
|
|
390
|
+
{opt === 'auto' ? 'Auto' : opt}
|
|
391
|
+
</button>
|
|
392
|
+
);
|
|
393
|
+
})}
|
|
394
|
+
</div>
|
|
361
395
|
</ConfigCard>
|
|
362
396
|
|
|
363
|
-
<ConfigCard icon={ShieldCheck} label="QC Threshold" description="
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
397
|
+
<ConfigCard icon={ShieldCheck} label="QC Threshold" description="Running agents count that triggers auto-QC.">
|
|
398
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
399
|
+
{[2, 3, 4, 6, 8].map((n) => {
|
|
400
|
+
const isActive = (config.qcThreshold || 2) === n;
|
|
401
|
+
return (
|
|
402
|
+
<button
|
|
403
|
+
key={n}
|
|
404
|
+
onClick={() => updateConfig('qcThreshold', n)}
|
|
405
|
+
className={cn(
|
|
406
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
407
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
408
|
+
)}
|
|
409
|
+
>
|
|
410
|
+
{n}
|
|
411
|
+
</button>
|
|
412
|
+
);
|
|
413
|
+
})}
|
|
414
|
+
</div>
|
|
371
415
|
</ConfigCard>
|
|
372
416
|
|
|
373
|
-
<ConfigCard icon={Users} label="Max Agents" description="
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
417
|
+
<ConfigCard icon={Users} label="Max Agents" description="Concurrent agent limit. 0 = unlimited.">
|
|
418
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
419
|
+
{[0, 4, 8, 12, 20].map((n) => {
|
|
420
|
+
const isActive = (config.maxAgents || 0) === n;
|
|
421
|
+
return (
|
|
422
|
+
<button
|
|
423
|
+
key={n}
|
|
424
|
+
onClick={() => updateConfig('maxAgents', n)}
|
|
425
|
+
className={cn(
|
|
426
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
427
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
428
|
+
)}
|
|
429
|
+
>
|
|
430
|
+
{n === 0 ? '\u221E' : n}
|
|
431
|
+
</button>
|
|
432
|
+
);
|
|
433
|
+
})}
|
|
434
|
+
</div>
|
|
381
435
|
</ConfigCard>
|
|
382
436
|
|
|
383
|
-
<ConfigCard icon={Newspaper} label="Journalist Interval" description="Seconds between
|
|
384
|
-
<div className="flex
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
437
|
+
<ConfigCard icon={Newspaper} label="Journalist Interval" description="Seconds between synthesis cycles.">
|
|
438
|
+
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
439
|
+
{[60, 120, 300, 600].map((n) => {
|
|
440
|
+
const isActive = (config.journalistInterval || 120) === n;
|
|
441
|
+
const label = n < 60 ? `${n}s` : `${n / 60}m`;
|
|
442
|
+
return (
|
|
443
|
+
<button
|
|
444
|
+
key={n}
|
|
445
|
+
onClick={() => updateConfig('journalistInterval', n)}
|
|
446
|
+
className={cn(
|
|
447
|
+
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
448
|
+
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
449
|
+
)}
|
|
450
|
+
>
|
|
451
|
+
{label}
|
|
452
|
+
</button>
|
|
453
|
+
);
|
|
454
|
+
})}
|
|
393
455
|
</div>
|
|
394
456
|
</ConfigCard>
|
|
457
|
+
|
|
395
458
|
</div>
|
|
396
459
|
</div>
|
|
397
460
|
)}
|
|
398
461
|
</div>
|
|
399
462
|
</ScrollArea>
|
|
463
|
+
|
|
464
|
+
{/* Folder Browser Modal */}
|
|
465
|
+
<FolderBrowser
|
|
466
|
+
open={folderBrowserOpen}
|
|
467
|
+
onOpenChange={setFolderBrowserOpen}
|
|
468
|
+
currentPath={config?.defaultWorkingDir || '/'}
|
|
469
|
+
onSelect={(dir) => updateConfig('defaultWorkingDir', dir)}
|
|
470
|
+
/>
|
|
400
471
|
</div>
|
|
401
472
|
);
|
|
402
473
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
127.0.0.1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
68556
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# AGENTS REGISTRY
|
|
2
|
-
|
|
3
|
-
*Auto-generated by GROOVE. Do not edit manually.*
|
|
4
|
-
|
|
5
|
-
| ID | Name | Role | Provider | Directory | Scope | Status |
|
|
6
|
-
|----|------|------|----------|-----------|-------|--------|
|
|
7
|
-
| 15f1e784 | planner-1 | planner | claude-code | /Users/rok/Desktop/groove/packages/gui | - | stopped |
|
|
8
|
-
|
|
9
|
-
*Updated: 2026-04-09T06:18:20.893Z*
|