groove-dev 0.27.88 → 0.27.91
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 +0 -11
- package/moe-training/client/parsers/claude-code.js +0 -2
- package/moe-training/client/session-attestation.js +2 -1
- package/moe-training/client/trajectory-capture.js +6 -0
- package/moe-training/test/client/parsers/claude-code.test.js +2 -2
- 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 +20 -10
- package/node_modules/@groove-dev/daemon/src/conversations.js +32 -6
- package/node_modules/@groove-dev/daemon/src/preview.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +34 -5
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -1
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +1 -1
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +1 -1
- package/node_modules/@groove-dev/daemon/src/providers/grok.js +2 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-D4vJ_1ET.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-B_igwWvq.js → index-MLIZRMj1.js} +1734 -1734
- 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 +51 -3
- package/node_modules/@groove-dev/gui/src/components/agents/workspace-mode.jsx +24 -10
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +7 -5
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +2 -4
- package/node_modules/@groove-dev/gui/src/components/chat/conversation-list.jsx +14 -14
- package/node_modules/@groove-dev/gui/src/stores/groove.js +3 -0
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +7 -9
- 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 +20 -10
- package/packages/daemon/src/conversations.js +32 -6
- package/packages/daemon/src/preview.js +1 -1
- package/packages/daemon/src/process.js +34 -5
- package/packages/daemon/src/providers/claude-code.js +1 -1
- package/packages/daemon/src/providers/codex.js +1 -1
- package/packages/daemon/src/providers/gemini.js +1 -1
- package/packages/daemon/src/providers/grok.js +2 -2
- package/packages/gui/dist/assets/index-D4vJ_1ET.css +1 -0
- package/packages/gui/dist/assets/{index-B_igwWvq.js → index-MLIZRMj1.js} +1734 -1734
- 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 +51 -3
- package/packages/gui/src/components/agents/workspace-mode.jsx +24 -10
- package/packages/gui/src/components/chat/chat-messages.jsx +7 -5
- package/packages/gui/src/components/chat/chat-view.jsx +2 -4
- package/packages/gui/src/components/chat/conversation-list.jsx +14 -14
- package/packages/gui/src/stores/groove.js +3 -0
- package/packages/gui/src/views/agents.jsx +7 -9
- package/node_modules/@groove-dev/gui/dist/assets/index-BSqk8cbI.css +0 -1
- package/packages/gui/dist/assets/index-BSqk8cbI.css +0 -1
- package/test/doomsday-clock/index.html +0 -55
- package/test/doomsday-clock/script.js +0 -66
- package/test/doomsday-clock/style.css +0 -315
package/CLAUDE.md
CHANGED
|
@@ -263,14 +263,3 @@ Audit-driven release. Multi-agent orchestration system with 7 coordination layer
|
|
|
263
263
|
- Dashboard: routing donut, cache panel, context health gauges
|
|
264
264
|
- Monitor/QC agent mode (stay active, loop)
|
|
265
265
|
- Distribution: demo video, HN launch, Twitter content
|
|
266
|
-
|
|
267
|
-
<!-- GROOVE:START -->
|
|
268
|
-
## GROOVE Orchestration (auto-injected)
|
|
269
|
-
Active agents: 2
|
|
270
|
-
| Name | Role | Scope |
|
|
271
|
-
|------|------|-------|
|
|
272
|
-
| fullstack-23 | fullstack | - |
|
|
273
|
-
| fullstack-12 | fullstack | - |
|
|
274
|
-
See AGENTS_REGISTRY.md for full agent state.
|
|
275
|
-
**Memory policy:** GROOVE manages project memory automatically. Do not read or write MEMORY.md or .groove/memory/ files directly.
|
|
276
|
-
<!-- GROOVE:END -->
|
|
@@ -30,7 +30,6 @@ export class ClaudeCodeParser {
|
|
|
30
30
|
results.push({
|
|
31
31
|
type: 'thought',
|
|
32
32
|
content: block.text || '',
|
|
33
|
-
token_count: jsonEvent.message?.usage?.output_tokens || 0,
|
|
34
33
|
});
|
|
35
34
|
} else if (block.type === 'tool_use') {
|
|
36
35
|
this._pendingToolUse.set(block.id, block);
|
|
@@ -67,7 +66,6 @@ export class ClaudeCodeParser {
|
|
|
67
66
|
return {
|
|
68
67
|
type: 'resolution',
|
|
69
68
|
content: typeof jsonEvent.result === 'string' ? jsonEvent.result : JSON.stringify(jsonEvent.result),
|
|
70
|
-
token_count: jsonEvent.total_tokens_used || 0,
|
|
71
69
|
};
|
|
72
70
|
}
|
|
73
71
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { platform, arch, cpus, totalmem, hostname, release, endianness } from 'node:os';
|
|
4
4
|
import { createHash } from 'node:crypto';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { generateECDHKeypair, deriveSharedSecret, signEnvelope, computeAppHash } from '../shared/crypto.js';
|
|
6
7
|
|
|
7
8
|
export class SessionAttestation {
|
|
@@ -14,7 +15,7 @@ export class SessionAttestation {
|
|
|
14
15
|
const keypair = generateECDHKeypair();
|
|
15
16
|
let appVersionHash = '';
|
|
16
17
|
try {
|
|
17
|
-
appVersionHash = computeAppHash(
|
|
18
|
+
appVersionHash = computeAppHash(fileURLToPath(import.meta.url));
|
|
18
19
|
} catch {
|
|
19
20
|
appVersionHash = 'unknown';
|
|
20
21
|
}
|
|
@@ -172,6 +172,12 @@ export class TrajectoryCapture {
|
|
|
172
172
|
ev.arguments = this._scrubObject(ev.arguments);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
if (!ev.token_count || ev.token_count < 2) {
|
|
176
|
+
const text = ev.content || '';
|
|
177
|
+
const argsLen = ev.arguments ? JSON.stringify(ev.arguments).length : 0;
|
|
178
|
+
ev.token_count = Math.max(1, Math.ceil((text.length + argsLen) / 4));
|
|
179
|
+
}
|
|
180
|
+
|
|
175
181
|
const step = {
|
|
176
182
|
step: ++ctx.stepCount,
|
|
177
183
|
type: ev.type,
|
|
@@ -17,7 +17,7 @@ describe('ClaudeCodeParser', () => {
|
|
|
17
17
|
const result = parser.parseEvent(event);
|
|
18
18
|
assert.equal(result.type, 'thought');
|
|
19
19
|
assert.equal(result.content, 'I need to fix the bug');
|
|
20
|
-
assert.equal(result.token_count,
|
|
20
|
+
assert.equal(result.token_count, undefined);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('parses tool_use block as action', () => {
|
|
@@ -79,7 +79,7 @@ describe('ClaudeCodeParser', () => {
|
|
|
79
79
|
const result = parser.parseEvent(event);
|
|
80
80
|
assert.equal(result.type, 'resolution');
|
|
81
81
|
assert.equal(result.content, 'Task completed successfully');
|
|
82
|
-
assert.equal(result.token_count,
|
|
82
|
+
assert.equal(result.token_count, undefined);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it('extracts tokens from assistant event', () => {
|
|
@@ -146,7 +146,10 @@ export function createApi(app, daemon) {
|
|
|
146
146
|
const team = daemon.teams.get(config.teamId);
|
|
147
147
|
if (team?.workingDir) config.workingDir = team.workingDir;
|
|
148
148
|
}
|
|
149
|
-
// Inherit configured
|
|
149
|
+
// Inherit configured defaults if the request didn't pick them
|
|
150
|
+
if (!config.provider && daemon.config?.defaultProvider) {
|
|
151
|
+
config.provider = daemon.config.defaultProvider;
|
|
152
|
+
}
|
|
150
153
|
if (!config.model && daemon.config?.defaultModel) {
|
|
151
154
|
config.model = daemon.config.defaultModel;
|
|
152
155
|
}
|
|
@@ -1112,8 +1115,8 @@ export function createApi(app, daemon) {
|
|
|
1112
1115
|
app.post('/api/conversations', async (req, res) => {
|
|
1113
1116
|
try {
|
|
1114
1117
|
const { provider, model, title, mode, reasoning_effort, verbosity } = req.body;
|
|
1115
|
-
if (
|
|
1116
|
-
return res.status(400).json({ error: 'provider
|
|
1118
|
+
if (provider && typeof provider !== 'string') {
|
|
1119
|
+
return res.status(400).json({ error: 'provider must be a string' });
|
|
1117
1120
|
}
|
|
1118
1121
|
if (mode && mode !== 'api' && mode !== 'agent') {
|
|
1119
1122
|
return res.status(400).json({ error: 'mode must be "api" or "agent"' });
|
|
@@ -4501,8 +4504,10 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4501
4504
|
}
|
|
4502
4505
|
|
|
4503
4506
|
daemon.config.defaultProvider = provider;
|
|
4507
|
+
daemon.config.defaultChatProvider = provider;
|
|
4504
4508
|
if (model && typeof model === 'string' && model.length <= 100) {
|
|
4505
4509
|
daemon.config.defaultModel = model.trim();
|
|
4510
|
+
daemon.config.defaultChatModel = model.trim();
|
|
4506
4511
|
}
|
|
4507
4512
|
const { saveConfig } = await import('./firstrun.js');
|
|
4508
4513
|
saveConfig(daemon.grooveDir, daemon.config);
|
|
@@ -4534,15 +4539,20 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4534
4539
|
saveConfig(daemon.grooveDir, daemon.config);
|
|
4535
4540
|
|
|
4536
4541
|
if (enabled) {
|
|
4537
|
-
const userId = ConsentManager.getOrCreateUserId();
|
|
4538
|
-
const consent = new ConsentManager();
|
|
4539
4542
|
try {
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
+
const userId = ConsentManager.getOrCreateUserId();
|
|
4544
|
+
const consent = new ConsentManager();
|
|
4545
|
+
try {
|
|
4546
|
+
consent.recordConsent(userId, true, '1.0');
|
|
4547
|
+
} finally {
|
|
4548
|
+
consent.close();
|
|
4549
|
+
}
|
|
4550
|
+
await daemon._initTrajectoryCapture();
|
|
4551
|
+
daemon.state.set('training_enrolled_at', new Date().toISOString());
|
|
4552
|
+
} catch (e) {
|
|
4553
|
+
daemon.config.training_opt_in = false;
|
|
4554
|
+
return res.status(500).json({ error: 'Failed to enable data sharing', detail: e.message });
|
|
4543
4555
|
}
|
|
4544
|
-
await daemon._initTrajectoryCapture();
|
|
4545
|
-
daemon.state.set('training_enrolled_at', new Date().toISOString());
|
|
4546
4556
|
} else {
|
|
4547
4557
|
if (daemon.trajectoryCapture) {
|
|
4548
4558
|
try { await daemon.trajectoryCapture.shutdown(); } catch (e) { /* */ }
|
|
@@ -55,6 +55,22 @@ export class ConversationManager {
|
|
|
55
55
|
return null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
_resolveAutoProviderModel(preferredProvider) {
|
|
59
|
+
const priority = ['claude-code', 'codex', 'gemini', 'grok', 'ollama'];
|
|
60
|
+
const candidates = preferredProvider ? [preferredProvider] : priority;
|
|
61
|
+
|
|
62
|
+
for (const pid of candidates) {
|
|
63
|
+
if (!isProviderInstalled(pid)) continue;
|
|
64
|
+
const p = getProvider(pid);
|
|
65
|
+
if (!p) continue;
|
|
66
|
+
const models = p.constructor.models || [];
|
|
67
|
+
const chatModel = models.find((m) => m.type !== 'image') || models[0];
|
|
68
|
+
if (chatModel) return { provider: pid, model: chatModel.id };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { provider: 'claude-code', model: 'claude-sonnet-4-6' };
|
|
72
|
+
}
|
|
73
|
+
|
|
58
74
|
async create(provider, model, title, mode = 'api', options = {}) {
|
|
59
75
|
if (!provider && this.daemon.config?.defaultChatProvider) {
|
|
60
76
|
provider = this.daemon.config.defaultChatProvider;
|
|
@@ -63,6 +79,12 @@ export class ConversationManager {
|
|
|
63
79
|
model = this.daemon.config.defaultChatModel;
|
|
64
80
|
}
|
|
65
81
|
|
|
82
|
+
if (!provider || !model) {
|
|
83
|
+
const resolved = this._resolveAutoProviderModel(provider);
|
|
84
|
+
if (!provider) provider = resolved.provider;
|
|
85
|
+
if (!model) model = resolved.model;
|
|
86
|
+
}
|
|
87
|
+
|
|
66
88
|
const id = randomUUID().slice(0, 12);
|
|
67
89
|
const now = new Date().toISOString();
|
|
68
90
|
|
|
@@ -328,12 +350,16 @@ export class ConversationManager {
|
|
|
328
350
|
let providerName = conv.provider;
|
|
329
351
|
|
|
330
352
|
if (!provider || !isProviderInstalled(conv.provider)) {
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
if (!
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
353
|
+
const resolved = this._resolveAutoProviderModel(null);
|
|
354
|
+
provider = getProvider(resolved.provider);
|
|
355
|
+
if (!provider) throw new Error('No provider available for chat');
|
|
356
|
+
providerName = resolved.provider;
|
|
357
|
+
modelId = resolved.model;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!modelId) {
|
|
361
|
+
const resolved = this._resolveAutoProviderModel(providerName);
|
|
362
|
+
modelId = resolved.model;
|
|
337
363
|
}
|
|
338
364
|
|
|
339
365
|
// Build messages array for direct API call
|
|
@@ -191,7 +191,7 @@ export class PreviewService {
|
|
|
191
191
|
// expansion, and shell builtins work as the planner wrote them.
|
|
192
192
|
const proc = cpSpawn('bash', ['-lc', command], {
|
|
193
193
|
cwd: baseDir,
|
|
194
|
-
env: { ...process.env, FORCE_COLOR: '0', CI: '' },
|
|
194
|
+
env: { ...process.env, FORCE_COLOR: '0', CI: '', BROWSER: 'none' },
|
|
195
195
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
196
196
|
detached: false,
|
|
197
197
|
});
|
|
@@ -432,10 +432,17 @@ export class ProcessManager {
|
|
|
432
432
|
if (installed.length === 0) {
|
|
433
433
|
throw new Error('No AI providers installed. Install Claude Code, Gemini CLI, Codex, or Ollama first.');
|
|
434
434
|
}
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
435
|
+
// If a model is specified, find the provider that supports it
|
|
436
|
+
if (config.model && config.model !== 'auto') {
|
|
437
|
+
const match = installed.find((p) => p.models?.some((m) => m.id === config.model));
|
|
438
|
+
if (match) providerName = match.id;
|
|
439
|
+
}
|
|
440
|
+
// Fallback: priority-based selection
|
|
441
|
+
if (!providerName) {
|
|
442
|
+
const priority = ['claude-code', 'gemini', 'codex', 'local', 'ollama'];
|
|
443
|
+
const best = priority.find((p) => installed.some((i) => i.id === p)) || installed[0].id;
|
|
444
|
+
providerName = best;
|
|
445
|
+
}
|
|
439
446
|
}
|
|
440
447
|
|
|
441
448
|
const provider = getProvider(providerName);
|
|
@@ -1282,6 +1289,10 @@ For normal file edits within your scope, proceed without review.
|
|
|
1282
1289
|
}
|
|
1283
1290
|
}
|
|
1284
1291
|
|
|
1292
|
+
// Phase 2 spawns are async — _checkPhase2 may have consumed the pending
|
|
1293
|
+
// entry but the spawn() calls haven't resolved yet. Wait for them.
|
|
1294
|
+
if (this._phase2Spawning?.has(teamId)) return;
|
|
1295
|
+
|
|
1285
1296
|
const teamAgents = this.daemon.registry.getAll().filter((a) => a.teamId === teamId && a.role !== 'planner');
|
|
1286
1297
|
if (teamAgents.length === 0) return;
|
|
1287
1298
|
const terminal = new Set(['completed', 'crashed', 'stopped', 'killed']);
|
|
@@ -1367,6 +1378,12 @@ For normal file edits within your scope, proceed without review.
|
|
|
1367
1378
|
return files.length === 0;
|
|
1368
1379
|
});
|
|
1369
1380
|
|
|
1381
|
+
// Track that phase 2 spawns are in-flight for this team so
|
|
1382
|
+
// _checkPreviewReady doesn't race ahead of the async spawn calls.
|
|
1383
|
+
const groupTeamId = group.agents[0]?.teamId || this.daemon.teams.getDefault()?.id || null;
|
|
1384
|
+
if (!this._phase2Spawning) this._phase2Spawning = new Map();
|
|
1385
|
+
const spawnPromises = [];
|
|
1386
|
+
|
|
1370
1387
|
// Auto-spawn phase 2 agents — if phase 1 was idle, clear the prompt
|
|
1371
1388
|
// so QC also waits for instructions instead of auditing nothing
|
|
1372
1389
|
for (const config of group.agents) {
|
|
@@ -1406,7 +1423,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1406
1423
|
try {
|
|
1407
1424
|
const validated = validateAgentConfig(config);
|
|
1408
1425
|
if (!validated.teamId) validated.teamId = this.daemon.teams.getDefault()?.id || null;
|
|
1409
|
-
this.spawn(validated).then((agent) => {
|
|
1426
|
+
const p = this.spawn(validated).then((agent) => {
|
|
1410
1427
|
this.daemon.broadcast({
|
|
1411
1428
|
type: 'phase2:spawned',
|
|
1412
1429
|
agentId: agent.id,
|
|
@@ -1422,6 +1439,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1422
1439
|
error: err.message,
|
|
1423
1440
|
});
|
|
1424
1441
|
});
|
|
1442
|
+
spawnPromises.push(p);
|
|
1425
1443
|
} catch (err) {
|
|
1426
1444
|
console.error(`[Groove] Phase 2 config invalid for ${config.role}: ${err.message}`);
|
|
1427
1445
|
this.daemon.broadcast({
|
|
@@ -1431,6 +1449,17 @@ For normal file edits within your scope, proceed without review.
|
|
|
1431
1449
|
});
|
|
1432
1450
|
}
|
|
1433
1451
|
}
|
|
1452
|
+
|
|
1453
|
+
// Mark this team as having phase 2 spawns in-flight. Cleared once
|
|
1454
|
+
// all spawn promises settle so _checkPreviewReady won't race ahead.
|
|
1455
|
+
if (spawnPromises.length > 0 && groupTeamId) {
|
|
1456
|
+
this._phase2Spawning.set(groupTeamId, (this._phase2Spawning.get(groupTeamId) || 0) + spawnPromises.length);
|
|
1457
|
+
Promise.allSettled(spawnPromises).then(() => {
|
|
1458
|
+
const remaining = (this._phase2Spawning.get(groupTeamId) || 0) - spawnPromises.length;
|
|
1459
|
+
if (remaining <= 0) this._phase2Spawning.delete(groupTeamId);
|
|
1460
|
+
else this._phase2Spawning.set(groupTeamId, remaining);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1434
1463
|
}
|
|
1435
1464
|
}
|
|
1436
1465
|
}
|
|
@@ -282,7 +282,7 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
282
282
|
let finished = false;
|
|
283
283
|
const finish = () => { if (!finished) { finished = true; onDone(); } };
|
|
284
284
|
const body = JSON.stringify({
|
|
285
|
-
model
|
|
285
|
+
model,
|
|
286
286
|
messages,
|
|
287
287
|
max_tokens: 8192,
|
|
288
288
|
stream: true,
|
|
@@ -163,7 +163,7 @@ export class CodexProvider extends Provider {
|
|
|
163
163
|
const effort = reasoningEffort || 'medium';
|
|
164
164
|
const verb = verbosity || 'medium';
|
|
165
165
|
const body = {
|
|
166
|
-
model
|
|
166
|
+
model,
|
|
167
167
|
input: previousResponseId ? [messages[messages.length - 1]] : messages,
|
|
168
168
|
stream: true,
|
|
169
169
|
reasoning: { effort },
|
|
@@ -94,7 +94,7 @@ export class GeminiProvider extends Provider {
|
|
|
94
94
|
const controller = new AbortController();
|
|
95
95
|
let finished = false;
|
|
96
96
|
const finish = () => { if (!finished) { finished = true; onDone(); } };
|
|
97
|
-
const m = model
|
|
97
|
+
const m = model;
|
|
98
98
|
const contents = messages.map((msg) => ({
|
|
99
99
|
role: msg.role === 'assistant' ? 'model' : 'user',
|
|
100
100
|
parts: [{ text: msg.content }],
|
|
@@ -58,7 +58,7 @@ export class GrokProvider extends Provider {
|
|
|
58
58
|
getLoopConfig(agent) {
|
|
59
59
|
return {
|
|
60
60
|
apiBase: 'https://api.x.ai/v1',
|
|
61
|
-
model: agent.model
|
|
61
|
+
model: agent.model,
|
|
62
62
|
contextWindow: 131072,
|
|
63
63
|
temperature: 0.1,
|
|
64
64
|
maxResponseTokens: 16384,
|
|
@@ -97,7 +97,7 @@ export class GrokProvider extends Provider {
|
|
|
97
97
|
'Content-Type': 'application/json',
|
|
98
98
|
},
|
|
99
99
|
body: JSON.stringify({
|
|
100
|
-
model
|
|
100
|
+
model,
|
|
101
101
|
messages,
|
|
102
102
|
stream: true,
|
|
103
103
|
}),
|