groove-dev 0.27.147 → 0.27.149
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/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/model-lab.js +28 -3
- package/node_modules/@groove-dev/daemon/src/process.js +4 -2
- package/node_modules/@groove-dev/gui/dist/assets/{index-BKbsE_hn.js → index-CReKPWhY.js} +24 -24
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +12 -4
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +30 -21
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +6 -1
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/model-lab.js +28 -3
- package/packages/daemon/src/process.js +4 -2
- package/packages/gui/dist/assets/{index-BKbsE_hn.js → index-CReKPWhY.js} +24 -24
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/lab/runtime-config.jsx +12 -4
- package/packages/gui/src/components/layout/status-bar.jsx +30 -21
- package/packages/gui/src/stores/slices/providers-slice.js +6 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-CReKPWhY.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
|
|
@@ -91,6 +91,7 @@ function AddRuntimeDialog({ open, onOpenChange }) {
|
|
|
91
91
|
|
|
92
92
|
function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onStart, testing }) {
|
|
93
93
|
const online = runtime.status === 'connected';
|
|
94
|
+
const starting = runtime.status === 'starting';
|
|
94
95
|
const managed = !!(runtime._localModelId || runtime._mlxModelId || runtime.launchConfig || runtime.type === 'mlx' || runtime.type === 'llama-cpp');
|
|
95
96
|
return (
|
|
96
97
|
<button
|
|
@@ -102,15 +103,15 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
|
|
|
102
103
|
>
|
|
103
104
|
<span className={cn(
|
|
104
105
|
'w-1.5 h-1.5 rounded-full flex-shrink-0',
|
|
105
|
-
online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
|
|
106
|
+
starting ? 'bg-warning animate-pulse' : online ? 'bg-success' : runtime.status === 'error' ? 'bg-danger' : 'bg-text-4',
|
|
106
107
|
)} />
|
|
107
108
|
<div className="flex-1 min-w-0">
|
|
108
109
|
<div className={cn('text-[11px] font-sans font-medium truncate', active ? 'text-text-0' : 'text-text-2')}>
|
|
109
110
|
{RUNTIME_TYPES.find((t) => t.value === runtime.type)?.label || runtime.type}
|
|
110
111
|
</div>
|
|
111
112
|
<div className="text-[10px] text-text-4 flex items-center gap-1.5">
|
|
112
|
-
<span className={cn('font-sans', online ? 'text-success' : 'text-danger')}>
|
|
113
|
-
{online ? 'Online' : 'Offline'}
|
|
113
|
+
<span className={cn('font-sans', starting ? 'text-warning' : online ? 'text-success' : 'text-danger')}>
|
|
114
|
+
{starting ? 'Starting...' : online ? 'Online' : 'Offline'}
|
|
114
115
|
</span>
|
|
115
116
|
{runtime.latency != null && online && (
|
|
116
117
|
<span className="font-mono">{Math.round(runtime.latency)}ms</span>
|
|
@@ -128,7 +129,14 @@ function RuntimeItem({ runtime, active, onSelect, onTest, onRemove, onStop, onSt
|
|
|
128
129
|
</button>
|
|
129
130
|
</Tooltip>
|
|
130
131
|
)}
|
|
131
|
-
{
|
|
132
|
+
{starting && (
|
|
133
|
+
<Tooltip content="Starting server...">
|
|
134
|
+
<span className="p-1 text-warning">
|
|
135
|
+
<Loader2 size={10} className="animate-spin" />
|
|
136
|
+
</span>
|
|
137
|
+
</Tooltip>
|
|
138
|
+
)}
|
|
139
|
+
{managed && !online && !starting && (
|
|
132
140
|
<Tooltip content="Start server">
|
|
133
141
|
<button
|
|
134
142
|
onClick={(e) => { e.stopPropagation(); onStart(runtime.id); }}
|
|
@@ -26,7 +26,7 @@ export function StatusBar({
|
|
|
26
26
|
const labRuntimes = useGrooveStore((s) => s.labRuntimes);
|
|
27
27
|
const stopLabRuntime = useGrooveStore((s) => s.stopLabRuntime);
|
|
28
28
|
const activeTunnels = savedTunnels.filter((t) => t.active);
|
|
29
|
-
const
|
|
29
|
+
const visibleRuntimes = (labRuntimes || []).filter((rt) => rt.status === 'connected' || rt.status === 'starting');
|
|
30
30
|
const electron = isElectron();
|
|
31
31
|
|
|
32
32
|
return (
|
|
@@ -113,26 +113,35 @@ export function StatusBar({
|
|
|
113
113
|
<span>Federation</span>
|
|
114
114
|
</button>
|
|
115
115
|
)}
|
|
116
|
-
{
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
{visibleRuntimes.map((rt) => {
|
|
117
|
+
const starting = rt.status === 'starting';
|
|
118
|
+
return (
|
|
119
|
+
<div key={rt.id} className="flex items-center gap-1">
|
|
120
|
+
<button
|
|
121
|
+
onClick={() => navigate('model-lab')}
|
|
122
|
+
className="flex items-center gap-1.5 text-text-3 hover:text-text-1 cursor-pointer transition-colors"
|
|
123
|
+
title={`${rt.name} — ${starting ? 'starting...' : 'running'}`}
|
|
124
|
+
>
|
|
125
|
+
<Cpu size={10} className={starting ? 'text-warning animate-pulse' : 'text-success'} />
|
|
126
|
+
<span>{rt.name}</span>
|
|
127
|
+
{starting ? (
|
|
128
|
+
<span className="w-1.5 h-1.5 rounded-full bg-warning animate-pulse" />
|
|
129
|
+
) : (
|
|
130
|
+
<span className="w-1.5 h-1.5 rounded-full bg-success" />
|
|
131
|
+
)}
|
|
132
|
+
</button>
|
|
133
|
+
{!starting && (
|
|
134
|
+
<button
|
|
135
|
+
onClick={() => stopLabRuntime(rt.id)}
|
|
136
|
+
className="p-0.5 text-text-4 hover:text-danger cursor-pointer transition-colors rounded"
|
|
137
|
+
title={`Stop ${rt.name}`}
|
|
138
|
+
>
|
|
139
|
+
<Square size={8} />
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
})}
|
|
136
145
|
</div>
|
|
137
146
|
|
|
138
147
|
<div className="flex-1" />
|
|
@@ -396,12 +396,17 @@ export const createProvidersSlice = (set, get) => ({
|
|
|
396
396
|
|
|
397
397
|
async startLabRuntime(id) {
|
|
398
398
|
try {
|
|
399
|
-
get().
|
|
399
|
+
const runtimes = get().labRuntimes.map((r) =>
|
|
400
|
+
r.id === id ? { ...r, status: 'starting' } : r,
|
|
401
|
+
);
|
|
402
|
+
set({ labRuntimes: runtimes });
|
|
403
|
+
persistJSON('groove:labRuntimes', runtimes);
|
|
400
404
|
await api.post(`/lab/runtimes/${id}/start`);
|
|
401
405
|
await get().fetchLabRuntimes();
|
|
402
406
|
get().setLabActiveRuntime(id);
|
|
403
407
|
get().addToast('success', 'Server started');
|
|
404
408
|
} catch (err) {
|
|
409
|
+
await get().fetchLabRuntimes();
|
|
405
410
|
get().addToast('error', 'Failed to start server', err.message);
|
|
406
411
|
}
|
|
407
412
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.149",
|
|
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)",
|
|
@@ -175,6 +175,12 @@ export class ModelLab {
|
|
|
175
175
|
const rt = this.runtimes.get(id);
|
|
176
176
|
if (!rt) throw new Error('Runtime not found');
|
|
177
177
|
|
|
178
|
+
// Clear disconnected flag on start/reconnect
|
|
179
|
+
if (rt._disconnected) {
|
|
180
|
+
delete rt._disconnected;
|
|
181
|
+
this._saveRuntimes();
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
// MLX runtimes — use built-in MLXServerManager
|
|
179
185
|
if (rt.type === 'mlx' && !rt.launchConfig) {
|
|
180
186
|
const modelId = rt._mlxModelId || this._deriveModelId(rt, 'MLX - ');
|
|
@@ -208,7 +214,16 @@ export class ModelLab {
|
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
// Generic launchConfig runtimes (vLLM, TGI, etc.)
|
|
211
|
-
if (!rt.launchConfig)
|
|
217
|
+
if (!rt.launchConfig) {
|
|
218
|
+
// Remote runtime — "start" means reconnect; verify it's reachable
|
|
219
|
+
const status = await this.getRuntimeStatus(rt);
|
|
220
|
+
if (status.online) {
|
|
221
|
+
this.daemon.broadcast({ type: 'lab:runtime:started', data: { id } });
|
|
222
|
+
this.daemon.audit.log('lab.runtime.start', { id, name: rt.name });
|
|
223
|
+
return rt;
|
|
224
|
+
}
|
|
225
|
+
throw new Error('Remote server is not reachable — start the server manually and try again');
|
|
226
|
+
}
|
|
212
227
|
if (this._processes.has(id)) throw new Error('Server already running');
|
|
213
228
|
|
|
214
229
|
const lc = rt.launchConfig;
|
|
@@ -268,12 +283,14 @@ export class ModelLab {
|
|
|
268
283
|
const rt = this.runtimes.get(id);
|
|
269
284
|
if (!rt) throw new Error('Runtime not found');
|
|
270
285
|
|
|
286
|
+
let killed = false;
|
|
287
|
+
|
|
271
288
|
if (rt._localModelId) {
|
|
272
289
|
const mm = this.daemon.modelManager;
|
|
273
290
|
const ls = this.daemon.llamaServer;
|
|
274
291
|
if (mm && ls) {
|
|
275
292
|
const modelPath = mm.getModelPath(rt._localModelId);
|
|
276
|
-
if (modelPath) await ls.stopServer(modelPath);
|
|
293
|
+
if (modelPath) { await ls.stopServer(modelPath); killed = true; }
|
|
277
294
|
}
|
|
278
295
|
}
|
|
279
296
|
|
|
@@ -281,7 +298,7 @@ export class ModelLab {
|
|
|
281
298
|
const ms = this.daemon.mlxServer;
|
|
282
299
|
if (ms) {
|
|
283
300
|
const hfId = rt._mlxModelId.startsWith('mlx:') ? rt._mlxModelId.slice(4) : rt._mlxModelId;
|
|
284
|
-
await ms.stopServer(hfId);
|
|
301
|
+
await ms.stopServer(hfId); killed = true;
|
|
285
302
|
}
|
|
286
303
|
}
|
|
287
304
|
|
|
@@ -293,6 +310,13 @@ export class ModelLab {
|
|
|
293
310
|
try { proc.kill('SIGTERM'); } catch { clearTimeout(timeout); resolve(); }
|
|
294
311
|
});
|
|
295
312
|
this._processes.delete(id);
|
|
313
|
+
killed = true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Remote/external runtimes: mark as disconnected so status checks treat it as offline
|
|
317
|
+
if (!killed) {
|
|
318
|
+
rt._disconnected = true;
|
|
319
|
+
this._saveRuntimes();
|
|
296
320
|
}
|
|
297
321
|
|
|
298
322
|
this.daemon.broadcast({ type: 'lab:runtime:stopped', data: { id } });
|
|
@@ -370,6 +394,7 @@ export class ModelLab {
|
|
|
370
394
|
}
|
|
371
395
|
|
|
372
396
|
async getRuntimeStatus(rt) {
|
|
397
|
+
if (rt._disconnected) return { online: false, latency: null };
|
|
373
398
|
try {
|
|
374
399
|
const start = Date.now();
|
|
375
400
|
const ep = localURL(rt.endpoint);
|
|
@@ -859,7 +859,9 @@ export class ProcessManager {
|
|
|
859
859
|
}
|
|
860
860
|
|
|
861
861
|
// Validate explicit model against provider's supported models
|
|
862
|
-
|
|
862
|
+
// Skip validation for runtime: and gguf: models — they're resolved by getLoopConfig()
|
|
863
|
+
const skipModelValidation = config.model && (config.model.startsWith('runtime:') || config.model.startsWith('gguf:'));
|
|
864
|
+
if (config.model && config.model !== 'auto' && provider.constructor.models && !skipModelValidation) {
|
|
863
865
|
const valid = provider.constructor.models.some(m => m.id === config.model);
|
|
864
866
|
if (!valid) {
|
|
865
867
|
const fallback = provider.constructor.models[0];
|
|
@@ -1128,7 +1130,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1128
1130
|
|
|
1129
1131
|
const loop = new AgentLoop({ daemon: this.daemon, agent, loopConfig, logStream });
|
|
1130
1132
|
this.handles.set(agent.id, { loop, logStream });
|
|
1131
|
-
registry.update(agent.id, { status: 'running' });
|
|
1133
|
+
registry.update(agent.id, { status: 'running', model: loopConfig.model, apiBase: loopConfig.apiBase });
|
|
1132
1134
|
|
|
1133
1135
|
// Record spawn lifecycle event
|
|
1134
1136
|
if (this.daemon.timeline) {
|