groove-dev 0.27.121 → 0.27.123

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.
Files changed (35) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +3 -3
  4. package/node_modules/@groove-dev/daemon/src/index.js +12 -4
  5. package/node_modules/@groove-dev/daemon/src/introducer.js +3 -4
  6. package/node_modules/@groove-dev/daemon/src/teams.js +11 -5
  7. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +20 -13
  8. package/node_modules/@groove-dev/gui/dist/assets/{index-bmkBX18f.js → index-BcmoHTm0.js} +1745 -1745
  9. package/node_modules/@groove-dev/gui/dist/assets/index-DWI-g_Sm.css +1 -0
  10. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  11. package/node_modules/@groove-dev/gui/package.json +1 -1
  12. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +9 -1
  13. package/node_modules/@groove-dev/gui/src/components/teams/team-removal-dialog.jsx +36 -2
  14. package/node_modules/@groove-dev/gui/src/stores/groove.js +21 -10
  15. package/node_modules/@groove-dev/gui/src/views/agents.jsx +62 -9
  16. package/node_modules/@groove-dev/gui/src/views/teams.jsx +2 -1
  17. package/package.json +1 -1
  18. package/packages/cli/package.json +1 -1
  19. package/packages/daemon/package.json +1 -1
  20. package/packages/daemon/src/api.js +3 -3
  21. package/packages/daemon/src/index.js +12 -4
  22. package/packages/daemon/src/introducer.js +3 -4
  23. package/packages/daemon/src/teams.js +11 -5
  24. package/packages/daemon/src/tunnel-manager.js +20 -13
  25. package/packages/gui/dist/assets/{index-bmkBX18f.js → index-BcmoHTm0.js} +1745 -1745
  26. package/packages/gui/dist/assets/index-DWI-g_Sm.css +1 -0
  27. package/packages/gui/dist/index.html +2 -2
  28. package/packages/gui/package.json +1 -1
  29. package/packages/gui/src/components/settings/quick-connect.jsx +9 -1
  30. package/packages/gui/src/components/teams/team-removal-dialog.jsx +36 -2
  31. package/packages/gui/src/stores/groove.js +21 -10
  32. package/packages/gui/src/views/agents.jsx +62 -9
  33. package/packages/gui/src/views/teams.jsx +2 -1
  34. package/node_modules/@groove-dev/gui/dist/assets/index-BLd3MON8.css +0 -1
  35. package/packages/gui/dist/assets/index-BLd3MON8.css +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.121",
3
+ "version": "0.27.123",
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.121",
3
+ "version": "0.27.123",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1149,9 +1149,9 @@ export function createApi(app, daemon) {
1149
1149
 
1150
1150
  app.post('/api/teams/:id/promote', (req, res) => {
1151
1151
  try {
1152
- const team = daemon.teams.promote(req.params.id);
1153
- daemon.audit.log('team.promote', { id: team.id, name: team.name });
1154
- res.json(team);
1152
+ const result = daemon.teams.promote(req.params.id);
1153
+ daemon.audit.log('team.promote', { id: req.params.id, destination: result.destination });
1154
+ res.json(result);
1155
1155
  } catch (err) {
1156
1156
  res.status(400).json({ error: err.message });
1157
1157
  }
@@ -295,8 +295,12 @@ export class Daemon {
295
295
  if (this._registryIoTimer) return;
296
296
  this._registryIoTimer = setTimeout(() => {
297
297
  this._registryIoTimer = null;
298
- this.introducer.writeRegistryFile(this.projectDir);
299
- this.introducer.injectGrooveSection(this.projectDir);
298
+ try {
299
+ this.introducer.writeRegistryFile(this.projectDir);
300
+ this.introducer.injectGrooveSection(this.projectDir);
301
+ } catch (err) {
302
+ console.error('[registry-io] Failed to update registry files:', err.message);
303
+ }
300
304
  }, 2000);
301
305
  };
302
306
 
@@ -765,8 +769,12 @@ export class Daemon {
765
769
  // 5. Refresh journalist context — regenerate project map and registry
766
770
  // so stale agent references don't persist in GROOVE_PROJECT_MAP.md
767
771
  if (cleaned > 0) {
768
- this.introducer.writeRegistryFile(this.projectDir);
769
- this.introducer.injectGrooveSection(this.projectDir);
772
+ try {
773
+ this.introducer.writeRegistryFile(this.projectDir);
774
+ this.introducer.injectGrooveSection(this.projectDir);
775
+ } catch (err) {
776
+ console.error('[registry-io] Failed to refresh registry during GC:', err.message);
777
+ }
770
778
  // Clear journalist's stale in-memory state for removed agents
771
779
  this.journalist.lastLogSizes = Object.fromEntries(
772
780
  Object.entries(this.journalist.lastLogSizes).filter(([id]) => {
@@ -477,7 +477,7 @@ export class Introducer {
477
477
  if (agents.length === 0) {
478
478
  const regPath = resolve(projectDir, 'AGENTS_REGISTRY.md');
479
479
  if (existsSync(regPath)) {
480
- writeFileSync(regPath, '');
480
+ try { writeFileSync(regPath, ''); } catch { /* dir may be gone */ }
481
481
  }
482
482
  return;
483
483
  }
@@ -494,6 +494,7 @@ export class Introducer {
494
494
  for (const [teamId, teamAgents] of teamGroups) {
495
495
  const team = teamId !== '_default' ? this.daemon.teams?.get(teamId) : null;
496
496
  const dir = team?.workingDir || projectDir;
497
+ if (!existsSync(dir)) continue;
497
498
 
498
499
  const lines = [
499
500
  `# AGENTS REGISTRY`,
@@ -518,9 +519,7 @@ export class Introducer {
518
519
  }
519
520
 
520
521
  injectGrooveSection(projectDir) {
521
- // Inject a GROOVE section into the project's CLAUDE.md.
522
- // This section is delimited by markers so we can update it without
523
- // clobbering the user's content.
522
+ if (!existsSync(projectDir)) return;
524
523
  const claudeMdPath = resolve(projectDir, 'CLAUDE.md');
525
524
  const agents = this.daemon.registry.getAll();
526
525
 
@@ -376,9 +376,6 @@ export class Teams {
376
376
 
377
377
  rmSync(oldDir, { recursive: true, force: true });
378
378
 
379
- team.workingDir = targetDir;
380
- team.mode = 'production';
381
-
382
379
  const agents = this.daemon.registry.getAll().filter((a) => a.teamId === id);
383
380
  for (const agent of agents) {
384
381
  if (agent.workingDir === oldDir) {
@@ -386,9 +383,18 @@ export class Teams {
386
383
  }
387
384
  }
388
385
 
386
+ const wasDefault = team.isDefault;
387
+ this.teams.delete(id);
389
388
  this._save();
390
- this.daemon.broadcast({ type: 'team:updated', team });
391
- return team;
389
+ this.daemon.broadcast({ type: 'team:deleted', teamId: id });
390
+
391
+ if (wasDefault) {
392
+ this._ensureDefault();
393
+ const fresh = this.getDefault();
394
+ if (fresh) this.daemon.broadcast({ type: 'team:created', team: fresh });
395
+ }
396
+
397
+ return { promoted: true, destination: targetDir };
392
398
  }
393
399
 
394
400
  // Backward compat stubs
@@ -216,14 +216,14 @@ export class TunnelManager {
216
216
  const result = execFileSync('ssh', [
217
217
  ...keyArgs,
218
218
  '-p', String(config.port || 22),
219
- '-o', 'ConnectTimeout=10',
219
+ '-o', 'ConnectTimeout=5',
220
220
  '-o', 'StrictHostKeyChecking=accept-new',
221
221
  '-o', 'BatchMode=yes',
222
222
  target,
223
223
  sshCmd(`S=$(curl -sf http://localhost:${REMOTE_PORT}/api/status 2>/dev/null); if [ -n "$S" ]; then echo "__GROOVE_RUNNING__$S__GROOVE_END__"; else which groove >/dev/null 2>&1 && echo __GROOVE_VER__$(groove --version 2>/dev/null || echo unknown)__GROOVE_STOPPED__ || echo __GROOVE_NOT_INSTALLED__; fi`),
224
224
  ], {
225
225
  encoding: 'utf8',
226
- timeout: 20000,
226
+ timeout: 15000,
227
227
  stdio: ['pipe', 'pipe', 'pipe'],
228
228
  });
229
229
 
@@ -267,6 +267,8 @@ export class TunnelManager {
267
267
  let testResult;
268
268
  if (opts.skipTest && opts.testResult) {
269
269
  testResult = opts.testResult;
270
+ } else if (config.lastConnected && opts.skipTest !== false) {
271
+ testResult = { reachable: true, daemonRunning: true, grooveInstalled: true, remoteVersion: null };
270
272
  } else {
271
273
  testResult = await this.test(id);
272
274
  }
@@ -345,19 +347,20 @@ export class TunnelManager {
345
347
  failCount: 0,
346
348
  });
347
349
 
348
- if (!preConnectHandled) {
350
+ const skipUpgrade = testResult.daemonRunning && testResult.remoteVersion && testResult.remoteVersion === getLocalVersion();
351
+ if (!preConnectHandled && !skipUpgrade) {
349
352
  await this._checkAndUpgradeRunning(id, config, localPort);
350
353
  }
351
354
 
352
- try {
353
- const statusResp = await fetch(`http://localhost:${localPort}/api/status`, { signal: AbortSignal.timeout(5000) });
354
- if (statusResp.ok) {
355
- const statusData = await statusResp.json();
356
- const remoteVer = statusData.version;
357
- const localVer = getLocalVersion();
358
- this.daemon.broadcast({ type: 'tunnel.version-info', data: { id, localVersion: localVer, remoteVersion: remoteVer, match: remoteVer === localVer } });
359
- }
360
- } catch { /* non-fatal */ }
355
+ const remoteVer = testResult?.remoteVersion || null;
356
+ const localVer = getLocalVersion();
357
+ if (remoteVer) {
358
+ this.daemon.broadcast({ type: 'tunnel.version-info', data: { id, localVersion: localVer, remoteVersion: remoteVer, match: remoteVer === localVer } });
359
+ }
360
+
361
+ config.lastConnected = new Date().toISOString();
362
+ this.saved.set(id, config);
363
+ this._save();
361
364
 
362
365
  const url = `http://localhost:${localPort}?instance=${encodeURIComponent(config.name)}`;
363
366
 
@@ -446,7 +449,11 @@ export class TunnelManager {
446
449
  this.daemon.broadcast({ type: 'tunnel.version-mismatch', data: { id, localVersion: localVer, remoteVersion: installedVer, message: 'Pinned version not available on npm, installed latest' } });
447
450
  }
448
451
 
449
- const restartCmd = `kill $(lsof -t -i:${REMOTE_PORT}) 2>/dev/null || true; sleep 2; GROOVE_BIN=$(which groove) && nohup "$GROOVE_BIN" start > /tmp/groove-daemon.log 2>&1 < /dev/null & disown; sleep 4; curl -sf http://localhost:${REMOTE_PORT}/api/status`;
452
+ const cdPrefix = config.projectDir ? `cd "${config.projectDir}" && ` : '';
453
+ const setProjectDir = config.projectDir
454
+ ? `curl -sf -X POST -H 'Content-Type: application/json' --data '{"path":"${config.projectDir}"}' http://localhost:${REMOTE_PORT}/api/project-dir > /dev/null 2>&1 || true; `
455
+ : '';
456
+ const restartCmd = `kill $(lsof -t -i:${REMOTE_PORT}) 2>/dev/null || true; sleep 2; ${cdPrefix}GROOVE_BIN=$(which groove) && nohup "$GROOVE_BIN" start > /tmp/groove-daemon.log 2>&1 < /dev/null & disown; sleep 4; curl -sf http://localhost:${REMOTE_PORT}/api/status && (${setProjectDir}true) || true`;
450
457
  const restartResult = execFileSync('ssh', [...sshBase, sshCmd(restartCmd)], {
451
458
  encoding: 'utf8',
452
459
  timeout: 60000,