groove-dev 0.27.94 → 0.27.96
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/CLAUDE.md +7 -0
- package/README.md +25 -52
- 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 +22 -14
- package/node_modules/@groove-dev/daemon/src/introducer.js +21 -6
- package/node_modules/@groove-dev/daemon/src/process.js +18 -2
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +27 -28
- package/node_modules/@groove-dev/gui/dist/assets/{index-B3GUKInH.js → index-BgTyFy4f.js} +50 -50
- package/node_modules/@groove-dev/gui/dist/assets/index-QADLyUj5.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 +68 -16
- package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +9 -1
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/stores/groove.js +26 -16
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +10 -3
- 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 +22 -14
- package/packages/daemon/src/introducer.js +21 -6
- package/packages/daemon/src/process.js +18 -2
- package/packages/daemon/src/providers/gemini.js +27 -28
- package/packages/gui/dist/assets/{index-B3GUKInH.js → index-BgTyFy4f.js} +50 -50
- package/packages/gui/dist/assets/index-QADLyUj5.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 +68 -16
- package/packages/gui/src/components/agents/code-review.jsx +9 -1
- package/packages/gui/src/components/agents/workspace-mode.jsx +2 -2
- package/packages/gui/src/components/editor/code-editor.jsx +1 -1
- package/packages/gui/src/stores/groove.js +26 -16
- package/packages/gui/src/views/agents.jsx +10 -3
- package/node_modules/@groove-dev/gui/dist/assets/index-C1k-GuDg.css +0 -1
- package/packages/gui/dist/assets/index-C1k-GuDg.css +0 -1
|
@@ -3694,18 +3694,26 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3694
3694
|
const baseDir = daemon.config?.defaultWorkingDir || daemon.projectDir;
|
|
3695
3695
|
|
|
3696
3696
|
if (mode === 'plan-first') {
|
|
3697
|
-
|
|
3697
|
+
const rolesList = roles.map(r => r.role || r.name || r).join(', ');
|
|
3698
|
+
const providerNote = teamProvider ? ` (provider: ${teamProvider})` : '';
|
|
3699
|
+
let plannerPrompt;
|
|
3700
|
+
if (task) {
|
|
3701
|
+
plannerPrompt = `The user wants these agents: ${rolesList}${providerNote}. Task: ${task}`;
|
|
3702
|
+
} else {
|
|
3703
|
+
plannerPrompt = '';
|
|
3704
|
+
}
|
|
3698
3705
|
const plannerConfig = validateAgentConfig({
|
|
3699
3706
|
role: 'planner',
|
|
3700
|
-
prompt:
|
|
3707
|
+
prompt: plannerPrompt,
|
|
3701
3708
|
provider: teamProvider,
|
|
3702
3709
|
model: teamModel,
|
|
3703
3710
|
workingDir: baseDir,
|
|
3704
3711
|
});
|
|
3705
3712
|
plannerConfig.teamId = defaultTeamId;
|
|
3713
|
+
plannerConfig.teamBuilderRoles = roles.map(r => ({ role: r.role || r, provider: r.provider || null }));
|
|
3706
3714
|
const planner = await daemon.processes.spawn(plannerConfig);
|
|
3707
3715
|
daemon.audit.log('team-builder.plan-first', { plannerId: planner.id, roles: roles.length });
|
|
3708
|
-
return res.status(202).json({ mode: 'plan-first', plannerId: planner.id, message: 'Planner spawned —
|
|
3716
|
+
return res.status(202).json({ mode: 'plan-first', plannerId: planner.id, message: 'Planner spawned — waiting for user instructions' });
|
|
3709
3717
|
}
|
|
3710
3718
|
|
|
3711
3719
|
const spawned = [];
|
|
@@ -4157,17 +4165,17 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4157
4165
|
// --- Federation ---
|
|
4158
4166
|
|
|
4159
4167
|
// Federation status (v1 — includes whitelist, connections, ambassadors)
|
|
4160
|
-
app.get('/api/federation',
|
|
4168
|
+
app.get('/api/federation', (req, res) => {
|
|
4161
4169
|
res.json(daemon.federation.getStatus());
|
|
4162
4170
|
});
|
|
4163
4171
|
|
|
4164
4172
|
// List peers
|
|
4165
|
-
app.get('/api/federation/peers',
|
|
4173
|
+
app.get('/api/federation/peers', (req, res) => {
|
|
4166
4174
|
res.json(daemon.federation.getPeers());
|
|
4167
4175
|
});
|
|
4168
4176
|
|
|
4169
4177
|
// Unpair a peer
|
|
4170
|
-
app.delete('/api/federation/peers/:id',
|
|
4178
|
+
app.delete('/api/federation/peers/:id', (req, res) => {
|
|
4171
4179
|
try {
|
|
4172
4180
|
daemon.federation.unpair(req.params.id);
|
|
4173
4181
|
res.json({ ok: true });
|
|
@@ -4177,7 +4185,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4177
4185
|
});
|
|
4178
4186
|
|
|
4179
4187
|
// Initiate pairing with a remote daemon
|
|
4180
|
-
app.post('/api/federation/initiate',
|
|
4188
|
+
app.post('/api/federation/initiate', async (req, res) => {
|
|
4181
4189
|
try {
|
|
4182
4190
|
const { remoteUrl } = req.body;
|
|
4183
4191
|
if (!remoteUrl || typeof remoteUrl !== 'string') {
|
|
@@ -4192,11 +4200,11 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4192
4200
|
|
|
4193
4201
|
// --- Federation v1: Whitelist ---
|
|
4194
4202
|
|
|
4195
|
-
app.get('/api/federation/whitelist',
|
|
4203
|
+
app.get('/api/federation/whitelist', (req, res) => {
|
|
4196
4204
|
res.json(daemon.federation.whitelist?.list() || []);
|
|
4197
4205
|
});
|
|
4198
4206
|
|
|
4199
|
-
app.post('/api/federation/whitelist',
|
|
4207
|
+
app.post('/api/federation/whitelist', (req, res) => {
|
|
4200
4208
|
try {
|
|
4201
4209
|
const { ip, port, name } = req.body;
|
|
4202
4210
|
if (!ip || typeof ip !== 'string') {
|
|
@@ -4210,7 +4218,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4210
4218
|
}
|
|
4211
4219
|
});
|
|
4212
4220
|
|
|
4213
|
-
app.delete('/api/federation/whitelist/:ip',
|
|
4221
|
+
app.delete('/api/federation/whitelist/:ip', (req, res) => {
|
|
4214
4222
|
try {
|
|
4215
4223
|
daemon.federation.whitelist.remove(req.params.ip);
|
|
4216
4224
|
daemon.broadcast({ type: 'federation:whitelist', data: daemon.federation.whitelist.list() });
|
|
@@ -4248,7 +4256,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4248
4256
|
|
|
4249
4257
|
// --- Federation v1: Connections ---
|
|
4250
4258
|
|
|
4251
|
-
app.get('/api/federation/connections',
|
|
4259
|
+
app.get('/api/federation/connections', (req, res) => {
|
|
4252
4260
|
res.json(daemon.federation.connections?.getStatus() || []);
|
|
4253
4261
|
});
|
|
4254
4262
|
|
|
@@ -4271,13 +4279,13 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4271
4279
|
}
|
|
4272
4280
|
});
|
|
4273
4281
|
|
|
4274
|
-
app.get('/api/federation/pouch/log',
|
|
4282
|
+
app.get('/api/federation/pouch/log', (req, res) => {
|
|
4275
4283
|
const limit = Math.min(parseInt(req.query.limit, 10) || 50, 200);
|
|
4276
4284
|
res.json(daemon.federation.ambassadors?.getPouchLog(limit) || []);
|
|
4277
4285
|
});
|
|
4278
4286
|
|
|
4279
4287
|
// Send a pouch message to a peer (local agents/GUI call this)
|
|
4280
|
-
app.post('/api/federation/pouch/send',
|
|
4288
|
+
app.post('/api/federation/pouch/send', async (req, res) => {
|
|
4281
4289
|
try {
|
|
4282
4290
|
const { peerId, contract } = req.body;
|
|
4283
4291
|
if (!peerId || !contract) {
|
|
@@ -4323,7 +4331,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4323
4331
|
}
|
|
4324
4332
|
});
|
|
4325
4333
|
|
|
4326
|
-
app.post('/api/federation/contract/send',
|
|
4334
|
+
app.post('/api/federation/contract/send', async (req, res) => {
|
|
4327
4335
|
try {
|
|
4328
4336
|
const { peerId, contract } = req.body;
|
|
4329
4337
|
if (!peerId || !contract) {
|
|
@@ -48,6 +48,17 @@ export class Introducer {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
if (newAgent.teamBuilderRoles && newAgent.teamBuilderRoles.length > 0) {
|
|
52
|
+
lines.push('');
|
|
53
|
+
lines.push('## Team Builder Pre-Selection');
|
|
54
|
+
lines.push('');
|
|
55
|
+
const roleDescs = newAgent.teamBuilderRoles.map(r => {
|
|
56
|
+
return r.provider ? `${r.role} (provider: ${r.provider})` : r.role;
|
|
57
|
+
});
|
|
58
|
+
lines.push(`The user selected these roles in the Team Builder UI: ${roleDescs.join(', ')}.`);
|
|
59
|
+
lines.push('When the user gives you a task, create a plan using EXACTLY these roles. Do not redesign the team composition.');
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
lines.push('');
|
|
52
63
|
|
|
53
64
|
if (others.length === 0) {
|
|
@@ -199,12 +210,16 @@ export class Introducer {
|
|
|
199
210
|
// CLAUDE.md parity — non-Claude providers don't read CLAUDE.md natively,
|
|
200
211
|
// so inject its project content (minus the GROOVE section) into introContext
|
|
201
212
|
if (newAgent.provider && newAgent.provider !== 'claude-code') {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
if (newAgent.role === 'planner') {
|
|
214
|
+
// Planners don't need full project context — codebase structure is injected separately
|
|
215
|
+
} else {
|
|
216
|
+
const claudeMdContent = this._loadClaudeMd(newAgent.workingDir);
|
|
217
|
+
if (claudeMdContent) {
|
|
218
|
+
lines.push('');
|
|
219
|
+
lines.push('## Project Context (from CLAUDE.md)');
|
|
220
|
+
lines.push('');
|
|
221
|
+
lines.push(claudeMdContent);
|
|
222
|
+
}
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
|
|
@@ -315,7 +315,7 @@ function sanitizeFilename(name) {
|
|
|
315
315
|
|
|
316
316
|
export function wrapWithRoleReminder(role, message) {
|
|
317
317
|
if (role === 'planner' && !message.startsWith('ROLE REMINDER:')) {
|
|
318
|
-
return 'ROLE REMINDER: You are a PLANNING ONLY agent. Do NOT write code, edit files, or
|
|
318
|
+
return 'ROLE REMINDER: You are a PLANNING ONLY agent. Do NOT write code, edit source files, or run build commands. Your ONLY file write should be .groove/recommended-team.json.\n\nUser message: ' + message;
|
|
319
319
|
}
|
|
320
320
|
return message;
|
|
321
321
|
}
|
|
@@ -1336,7 +1336,23 @@ For normal file edits within your scope, proceed without review.
|
|
|
1336
1336
|
if (existsSync(targetPath)) return;
|
|
1337
1337
|
|
|
1338
1338
|
const log = readFileSync(logPath, 'utf8');
|
|
1339
|
-
|
|
1339
|
+
|
|
1340
|
+
// Extract text from JSON log events (Codex agent_message, Claude content blocks)
|
|
1341
|
+
const textParts = [];
|
|
1342
|
+
for (const line of log.split('\n')) {
|
|
1343
|
+
try {
|
|
1344
|
+
const evt = JSON.parse(line.trim());
|
|
1345
|
+
if (evt.item?.type === 'agent_message' && evt.item?.text) textParts.push(evt.item.text);
|
|
1346
|
+
if (evt.type === 'assistant' && evt.message?.content) {
|
|
1347
|
+
for (const block of evt.message.content) {
|
|
1348
|
+
if (block.type === 'text') textParts.push(block.text);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
} catch { textParts.push(line); }
|
|
1352
|
+
}
|
|
1353
|
+
const fullText = textParts.join('\n');
|
|
1354
|
+
|
|
1355
|
+
const match = fullText.match(/\{[\s\S]*?"agents"\s*:\s*\[[\s\S]*?\]\s*\}/);
|
|
1340
1356
|
if (!match) return;
|
|
1341
1357
|
|
|
1342
1358
|
let parsed;
|
|
@@ -147,11 +147,8 @@ export class GeminiProvider extends Provider {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
switch (event.type) {
|
|
150
|
-
case '
|
|
151
|
-
return { type: 'activity', subtype: 'assistant', sessionId: event.
|
|
152
|
-
|
|
153
|
-
case 'session_update':
|
|
154
|
-
return null;
|
|
150
|
+
case 'init':
|
|
151
|
+
return { type: 'activity', subtype: 'assistant', sessionId: event.session_id, data: [{ type: 'text', text: '' }] };
|
|
155
152
|
|
|
156
153
|
case 'message': {
|
|
157
154
|
if (event.role === 'user') return null;
|
|
@@ -165,38 +162,39 @@ export class GeminiProvider extends Provider {
|
|
|
165
162
|
return { type: 'activity', subtype: 'assistant', data: blocks };
|
|
166
163
|
}
|
|
167
164
|
|
|
168
|
-
case '
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: (event.
|
|
165
|
+
case 'tool_use': {
|
|
166
|
+
const name = event.tool_name || '';
|
|
167
|
+
const toolName = name.includes('shell') || name.includes('exec')
|
|
168
|
+
? 'Bash' : name || 'Tool';
|
|
169
|
+
const input = toolName === 'Bash'
|
|
170
|
+
? { command: event.parameters?.command || (typeof event.parameters === 'string' ? event.parameters : JSON.stringify(event.parameters || {})) }
|
|
171
|
+
: (event.parameters || {});
|
|
174
172
|
return {
|
|
175
173
|
type: 'activity', subtype: 'assistant',
|
|
176
|
-
data: [{ type: 'tool_use', id: event.
|
|
174
|
+
data: [{ type: 'tool_use', id: event.tool_id || 'tool', name: toolName, input }],
|
|
177
175
|
};
|
|
178
176
|
}
|
|
179
177
|
|
|
180
|
-
case '
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const toolName =
|
|
185
|
-
? 'Bash' : event.name || 'Tool';
|
|
178
|
+
case 'tool_result': {
|
|
179
|
+
const content = typeof event.output === 'string' ? event.output.slice(0, 2000) : '';
|
|
180
|
+
const toolId = event.tool_id || '';
|
|
181
|
+
const isShell = toolId.includes('shell') || toolId.includes('exec');
|
|
182
|
+
const toolName = isShell ? 'Bash' : 'Tool';
|
|
186
183
|
return {
|
|
187
184
|
type: 'activity', subtype: 'assistant',
|
|
188
185
|
data: [
|
|
189
|
-
{ type: 'tool_use', id:
|
|
186
|
+
{ type: 'tool_use', id: toolId || 'tool', name: toolName, input: {} },
|
|
190
187
|
...(content ? [{ type: 'text', text: content }] : []),
|
|
191
188
|
],
|
|
192
189
|
};
|
|
193
190
|
}
|
|
194
191
|
|
|
195
|
-
case '
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
192
|
+
case 'result': {
|
|
193
|
+
const stats = event.stats || {};
|
|
194
|
+
const inputTokens = stats.input_tokens || 0;
|
|
195
|
+
const outputTokens = stats.output_tokens || 0;
|
|
196
|
+
const cachedTokens = stats.cached || 0;
|
|
197
|
+
const totalTokens = stats.total_tokens || (inputTokens + outputTokens);
|
|
200
198
|
|
|
201
199
|
const model = GeminiProvider.models.find((m) => m.id === this._currentModel);
|
|
202
200
|
const pricing = model?.pricing;
|
|
@@ -211,7 +209,8 @@ export class GeminiProvider extends Provider {
|
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
return {
|
|
214
|
-
type: '
|
|
212
|
+
type: 'result',
|
|
213
|
+
subtype: 'assistant',
|
|
215
214
|
data: [{ type: 'text', text: '' }],
|
|
216
215
|
tokensUsed: totalTokens,
|
|
217
216
|
inputTokens,
|
|
@@ -220,12 +219,12 @@ export class GeminiProvider extends Provider {
|
|
|
220
219
|
contextUsage: inputTokens / maxContext,
|
|
221
220
|
estimatedCostUsd,
|
|
222
221
|
costSource: pricing ? 'calculated' : 'estimated',
|
|
222
|
+
cost: estimatedCostUsd,
|
|
223
|
+
duration: stats.duration_ms,
|
|
224
|
+
turns: stats.tool_calls || 0,
|
|
223
225
|
};
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
case 'agent_end':
|
|
227
|
-
return { type: 'activity', subtype: 'assistant', data: [{ type: 'text', text: '' }] };
|
|
228
|
-
|
|
229
228
|
case 'error':
|
|
230
229
|
return { type: 'activity', subtype: 'assistant', data: [{ type: 'text', text: `Error: ${event.message || 'unknown'}` }] };
|
|
231
230
|
|