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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.147",
3
+ "version": "0.27.149",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.147",
3
+ "version": "0.27.149",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -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) throw new Error('No launch config — use the assistant to set up this runtime first');
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
- if (config.model && config.model !== 'auto' && provider.constructor.models) {
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) {