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.
- 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/api.js +33 -7
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +2 -2
- package/node_modules/@groove-dev/daemon/src/process.js +34 -2
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +10 -4
- package/node_modules/@groove-dev/gui/dist/assets/{index-CuFxAnNE.js → index-8gdXdRnq.js} +1743 -1743
- package/node_modules/@groove-dev/gui/dist/assets/index-C1ObKizg.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +199 -14
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +19 -6
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +33 -7
- package/packages/daemon/src/gateways/manager.js +2 -2
- package/packages/daemon/src/process.js +34 -2
- package/packages/daemon/src/providers/gemini.js +10 -4
- package/packages/gui/dist/assets/{index-CuFxAnNE.js → index-8gdXdRnq.js} +1743 -1743
- package/packages/gui/dist/assets/index-C1ObKizg.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-file-tree.jsx +199 -14
- package/packages/gui/src/components/agents/workspace-mode.jsx +19 -6
- package/node_modules/@groove-dev/gui/dist/assets/index-BvAGbs8U.css +0 -1
- package/packages/gui/dist/assets/index-BvAGbs8U.css +0 -1
|
@@ -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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
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;
|