groove-dev 0.27.100 → 0.27.101

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 (27) 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 +33 -7
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +2 -2
  5. package/node_modules/@groove-dev/daemon/src/process.js +34 -2
  6. package/node_modules/@groove-dev/daemon/src/providers/gemini.js +10 -4
  7. package/node_modules/@groove-dev/gui/dist/assets/{index-CuFxAnNE.js → index-8gdXdRnq.js} +1743 -1743
  8. package/node_modules/@groove-dev/gui/dist/assets/index-C1ObKizg.css +1 -0
  9. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  10. package/node_modules/@groove-dev/gui/package.json +1 -1
  11. package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +199 -14
  12. package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +19 -6
  13. package/package.json +1 -1
  14. package/packages/cli/package.json +1 -1
  15. package/packages/daemon/package.json +1 -1
  16. package/packages/daemon/src/api.js +33 -7
  17. package/packages/daemon/src/gateways/manager.js +2 -2
  18. package/packages/daemon/src/process.js +34 -2
  19. package/packages/daemon/src/providers/gemini.js +10 -4
  20. package/packages/gui/dist/assets/{index-CuFxAnNE.js → index-8gdXdRnq.js} +1743 -1743
  21. package/packages/gui/dist/assets/index-C1ObKizg.css +1 -0
  22. package/packages/gui/dist/index.html +2 -2
  23. package/packages/gui/package.json +1 -1
  24. package/packages/gui/src/components/agents/agent-file-tree.jsx +199 -14
  25. package/packages/gui/src/components/agents/workspace-mode.jsx +19 -6
  26. package/node_modules/@groove-dev/gui/dist/assets/index-BvAGbs8U.css +0 -1
  27. package/packages/gui/dist/assets/index-BvAGbs8U.css +0 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.100",
3
+ "version": "0.27.101",
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.100",
3
+ "version": "0.27.101",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -174,10 +174,10 @@ export function createApi(app, daemon) {
174
174
  const agent = daemon.registry.get(req.params.id);
175
175
  if (!agent) return res.status(404).json({ error: 'Agent not found' });
176
176
 
177
- const isAlive = agent.status === 'running' || agent.status === 'starting';
178
- if (isAlive) {
179
- await daemon.processes.kill(req.params.id);
180
- }
177
+ // Always attempt kill handles race where GUI sees 'running' but daemon
178
+ // already marked the agent completed (common with fast non-interactive
179
+ // providers like Gemini). processes.kill() is a no-op when no handle exists.
180
+ await daemon.processes.kill(req.params.id);
181
181
 
182
182
  // Only purge from registry when explicitly requested.
183
183
  // Killed/completed agents stay visible so the user can review output.
@@ -1519,6 +1519,30 @@ export function createApi(app, daemon) {
1519
1519
  return res.json(newAgent);
1520
1520
  }
1521
1521
 
1522
+ // Non-interactive CLI providers (e.g. Gemini): respawn with the new
1523
+ // message as the prompt, preserving original introContext. These providers
1524
+ // run one prompt per spawn and cannot resume sessions.
1525
+ if (provider?.constructor?.nonInteractive && !daemon.processes.isRunning(req.params.id)) {
1526
+ const oldConfig = { ...agent };
1527
+ daemon.registry.remove(req.params.id);
1528
+ daemon.locks.release(req.params.id);
1529
+
1530
+ const newAgent = await daemon.processes.spawn({
1531
+ role: oldConfig.role,
1532
+ scope: oldConfig.scope,
1533
+ provider: oldConfig.provider,
1534
+ model: oldConfig.model,
1535
+ prompt: message.trim(),
1536
+ introContext: oldConfig.introContext,
1537
+ permission: oldConfig.permission || 'full',
1538
+ workingDir: oldConfig.workingDir,
1539
+ name: oldConfig.name,
1540
+ teamId: oldConfig.teamId,
1541
+ });
1542
+ daemon.audit.log('agent.instruct', { id: req.params.id, newId: newAgent.id, resumed: false });
1543
+ return res.json(newAgent);
1544
+ }
1545
+
1522
1546
  // Running CLI agent (no loop) — queue the message for delivery after
1523
1547
  // the current task completes instead of killing and respawning.
1524
1548
  if (daemon.processes.isRunning(req.params.id)) {
@@ -3364,6 +3388,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
3364
3388
  // Resolve base directory from the planner that wrote the file, not the daemon root
3365
3389
  const plannerAgent = found.agentId ? daemon.registry.get(found.agentId) : null;
3366
3390
  const baseDir = plannerAgent?.workingDir || daemon.config?.defaultWorkingDir || daemon.projectDir;
3391
+ const plannerProvider = plannerAgent?.provider || undefined;
3367
3392
 
3368
3393
  // Use the planner's teamId so launched agents join the correct team.
3369
3394
  // Priority: explicit from frontend > agent that wrote the file > most recent planner > default
@@ -3424,6 +3449,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
3424
3449
  phase2 = [{
3425
3450
  name: 'qc-agent',
3426
3451
  role: 'fullstack', phase: 2, scope: [],
3452
+ provider: teamProvider || plannerProvider || daemon.config?.defaultProvider || undefined,
3427
3453
  prompt: 'QC Senior Dev: All builder agents have completed. Audit their changes for correctness, fix any issues, run tests, and verify the project builds cleanly (npm run build). Do NOT start long-running dev servers — just verify the build succeeds. Commit all changes. IMPORTANT: Do NOT delete files from other projects or directories outside this project.',
3428
3454
  }];
3429
3455
  }
@@ -3476,7 +3502,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
3476
3502
  role: existing.role,
3477
3503
  scope: normalizeScope(config.scope || existing.scope || [], existing.workingDir || projectWorkingDir),
3478
3504
  prompt,
3479
- provider: config.provider || daemon.config?.defaultProvider || existing.provider || undefined,
3505
+ provider: config.provider || plannerProvider || daemon.config?.defaultProvider || existing.provider || undefined,
3480
3506
  model: config.model || existing.model || daemon.config?.defaultModel || 'auto',
3481
3507
  permission: config.permission || existing.permission || 'auto',
3482
3508
  workingDir: existing.workingDir || projectWorkingDir,
@@ -3501,7 +3527,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
3501
3527
  role: config.role,
3502
3528
  scope: normalizeScope(config.scope || [], config.workingDir || projectWorkingDir),
3503
3529
  prompt,
3504
- provider: config.provider || daemon.config?.defaultProvider || undefined,
3530
+ provider: config.provider || plannerProvider || daemon.config?.defaultProvider || undefined,
3505
3531
  model: config.model || daemon.config?.defaultModel || 'auto',
3506
3532
  permission: config.permission || 'auto',
3507
3533
  workingDir: config.workingDir || projectWorkingDir,
@@ -3544,7 +3570,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
3544
3570
  waitFor: phase1Ids,
3545
3571
  agents: phase2.map((c) => ({
3546
3572
  role: c.role, scope: c.scope || [], prompt: c.prompt || '',
3547
- provider: c.provider || daemon.config?.defaultProvider || undefined, model: c.model || daemon.config?.defaultModel || 'auto',
3573
+ provider: c.provider || plannerProvider || daemon.config?.defaultProvider || undefined, model: c.model || daemon.config?.defaultModel || 'auto',
3548
3574
  permission: c.permission || 'auto',
3549
3575
  reasoningEffort: c.reasoningEffort, temperature: c.temperature, verbosity: c.verbosity,
3550
3576
  workingDir: c.workingDir || projectWorkingDir,
@@ -799,7 +799,7 @@ export class GatewayManager {
799
799
  role: config.role,
800
800
  scope: config.scope || [],
801
801
  prompt: config.prompt || '',
802
- provider: config.provider || 'claude-code',
802
+ provider: config.provider || this.daemon.config?.defaultProvider || 'claude-code',
803
803
  model: config.model || 'auto',
804
804
  permission: config.permission || 'auto',
805
805
  workingDir: config.workingDir || defaultDir,
@@ -833,7 +833,7 @@ export class GatewayManager {
833
833
  waitFor: phase1Ids,
834
834
  agents: phase2.map((c) => ({
835
835
  role: c.role, scope: c.scope || [], prompt: c.prompt || '',
836
- provider: c.provider || 'claude-code', model: c.model || 'auto',
836
+ provider: c.provider || this.daemon.config?.defaultProvider || 'claude-code', model: c.model || 'auto',
837
837
  permission: c.permission || 'auto',
838
838
  workingDir: c.workingDir || defaultDir,
839
839
  name: c.name || undefined,
@@ -741,6 +741,10 @@ For normal file edits within your scope, proceed without review.
741
741
  // Clean up per-agent maps to prevent unbounded growth in long sessions
742
742
  this.peakContextUsage.delete(agent.id);
743
743
  this.pendingMessages.delete(agent.id);
744
+
745
+ // Release file-scope locks so they don't persist after agent death
746
+ if (this.daemon.locks) this.daemon.locks.release(agent.id);
747
+
744
748
  registry.update(agent.id, { status, pid: null });
745
749
 
746
750
  if (this.daemon.timeline) {
@@ -1351,6 +1355,13 @@ For normal file edits within your scope, proceed without review.
1351
1355
  if (block.type === 'text') textParts.push(block.text);
1352
1356
  }
1353
1357
  }
1358
+ if (evt.type === 'message' && evt.content) {
1359
+ const parts = Array.isArray(evt.content) ? evt.content : [evt.content];
1360
+ for (const p of parts) {
1361
+ if (typeof p === 'string') textParts.push(p);
1362
+ else if (p.text) textParts.push(p.text);
1363
+ }
1364
+ }
1354
1365
  } catch { textParts.push(line); }
1355
1366
  }
1356
1367
  const fullText = textParts.join('\n');
@@ -1798,6 +1809,10 @@ For normal file edits within your scope, proceed without review.
1798
1809
  logStream.end();
1799
1810
  this.handles.delete(newAgent.id);
1800
1811
  this._stalledAgents.delete(newAgent.id);
1812
+
1813
+ // Release file-scope locks so they don't persist after agent death
1814
+ if (this.daemon.locks) this.daemon.locks.release(newAgent.id);
1815
+
1801
1816
  const finalStatus = signal === 'SIGTERM' || signal === 'SIGKILL' ? 'killed' : code === 0 ? 'completed' : 'crashed';
1802
1817
  registry.update(newAgent.id, { status: finalStatus, pid: null });
1803
1818
  this.daemon.broadcast({ type: 'agent:exit', agentId: newAgent.id, code, signal, status: finalStatus });
@@ -1929,6 +1944,10 @@ For normal file edits within your scope, proceed without review.
1929
1944
  this._streamThrottle.delete(newAgent.id);
1930
1945
  this.peakContextUsage.delete(newAgent.id);
1931
1946
  this.pendingMessages.delete(newAgent.id);
1947
+
1948
+ // Release file-scope locks so they don't persist after agent death
1949
+ if (this.daemon.locks) this.daemon.locks.release(newAgent.id);
1950
+
1932
1951
  registry.update(newAgent.id, { status, pid: null });
1933
1952
 
1934
1953
  const agentData = registry.get(newAgent.id);
@@ -2077,14 +2096,26 @@ For normal file edits within your scope, proceed without review.
2077
2096
 
2078
2097
  // CLI process path — spawn's exit handler sets status='killed' for SIGTERM
2079
2098
  return new Promise((resolveKill) => {
2099
+ let resolved = false;
2100
+ const resolve = () => { if (!resolved) { resolved = true; resolveKill(); } };
2101
+
2080
2102
  const forceTimer = setTimeout(() => {
2081
2103
  try { proc.kill('SIGKILL'); } catch {}
2082
2104
  }, 5000);
2083
2105
 
2106
+ // Hard timeout: resolve even if exit event never fires (prevents HTTP hang)
2107
+ const hardTimer = setTimeout(() => {
2108
+ clearTimeout(forceTimer);
2109
+ this.handles.delete(agentId);
2110
+ this.daemon.locks.release(agentId);
2111
+ resolve();
2112
+ }, 10000);
2113
+
2084
2114
  proc.on('exit', () => {
2085
2115
  clearTimeout(forceTimer);
2116
+ clearTimeout(hardTimer);
2086
2117
  this.daemon.locks.release(agentId);
2087
- resolveKill();
2118
+ resolve();
2088
2119
  });
2089
2120
 
2090
2121
  try {
@@ -2092,9 +2123,10 @@ For normal file edits within your scope, proceed without review.
2092
2123
  } catch {
2093
2124
  // Already dead
2094
2125
  clearTimeout(forceTimer);
2126
+ clearTimeout(hardTimer);
2095
2127
  this.handles.delete(agentId);
2096
2128
  this.daemon.locks.release(agentId);
2097
- resolveKill();
2129
+ resolve();
2098
2130
  }
2099
2131
  });
2100
2132
  }
@@ -28,6 +28,7 @@ export class GeminiProvider extends Provider {
28
28
  static name = 'gemini';
29
29
  static displayName = 'Gemini CLI';
30
30
  static command = 'gemini';
31
+ static nonInteractive = true;
31
32
  static authType = 'api-key';
32
33
  static envKey = 'GEMINI_API_KEY';
33
34
  static models = [
@@ -59,7 +60,7 @@ export class GeminiProvider extends Provider {
59
60
 
60
61
  args.push('--yolo');
61
62
  args.push('--output-format', 'stream-json');
62
- args.push('-p', '');
63
+ args.push('-p');
63
64
 
64
65
  this._currentModel = agent.model;
65
66
 
@@ -84,8 +85,13 @@ export class GeminiProvider extends Provider {
84
85
  if (agent.role !== 'planner') {
85
86
  parts.push(
86
87
  `## Non-Interactive Commands\n\n` +
87
- `Always use non-interactive flags when running package manager commands to prevent timeout hangs: ` +
88
- `\`npx --yes\`, \`npm create --yes\`, \`npm init --yes\`. Never run these commands without the \`--yes\` flag.`
88
+ `Always use non-interactive flags when running package manager commands to prevent timeout hangs:\n` +
89
+ `- npx: always use \`npx --yes\`\n` +
90
+ `- create-vite: always add \`--no-interactive\` flag (e.g. \`npx --yes create-vite . --template react --no-interactive\`)\n` +
91
+ `- create-next-app: always add \`--yes\` flag\n` +
92
+ `- npm init/create: always add \`--yes\` flag\n` +
93
+ `- Any scaffolding tool: look for --no-interactive, --yes, or -y flags to skip prompts\n` +
94
+ `Never run these commands without non-interactive flags — they will hang and be auto-killed after 5 minutes.`
89
95
  );
90
96
  }
91
97
  return parts.join('\n\n');
@@ -155,7 +161,7 @@ export class GeminiProvider extends Provider {
155
161
 
156
162
  switch (event.type) {
157
163
  case 'init':
158
- return { type: 'activity', subtype: 'assistant', sessionId: event.session_id, model: event.model, data: [{ type: 'text', text: '' }] };
164
+ return { type: 'activity', subtype: 'assistant', model: event.model, data: [{ type: 'text', text: '' }] };
159
165
 
160
166
  case 'message': {
161
167
  if (event.role === 'user') return null;