groove-dev 0.17.8 → 0.18.2
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 +4 -3
- package/node_modules/@groove-dev/daemon/google-oauth.json +5 -0
- package/node_modules/@groove-dev/daemon/integrations-registry.json +0 -40
- package/node_modules/@groove-dev/daemon/package.json +4 -3
- package/node_modules/@groove-dev/daemon/src/api.js +212 -21
- package/node_modules/@groove-dev/daemon/src/index.js +68 -1
- package/node_modules/@groove-dev/daemon/src/integrations.js +59 -20
- package/node_modules/@groove-dev/daemon/src/process.js +83 -11
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/gui/.groove/audit.log +1 -0
- package/node_modules/@groove-dev/gui/.groove/codebase-index.json +64 -0
- package/node_modules/@groove-dev/gui/.groove/config.json +10 -0
- package/node_modules/@groove-dev/gui/.groove/coordination.md +5 -0
- package/node_modules/@groove-dev/gui/.groove/credentials.json +6 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.port +1 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.key +3 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.pub +3 -0
- package/node_modules/@groove-dev/gui/.groove/integrations/package.json +6 -0
- package/node_modules/@groove-dev/gui/.groove/state.json +3 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-x5suAiK7.js +182 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +5 -4
- package/node_modules/@groove-dev/gui/src/App.jsx +149 -76
- package/node_modules/@groove-dev/gui/src/components/AgentActions.jsx +130 -1
- package/node_modules/@groove-dev/gui/src/components/AgentChat.jsx +47 -7
- package/node_modules/@groove-dev/gui/src/components/AgentNode.jsx +13 -83
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +918 -580
- package/node_modules/@groove-dev/gui/src/stores/groove.js +31 -2
- package/node_modules/@groove-dev/gui/src/views/AgentTree.jsx +133 -67
- package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +85 -1
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +121 -44
- package/package.json +1 -2
- package/packages/cli/package.json +4 -3
- package/packages/daemon/integrations-registry.json +0 -40
- package/packages/daemon/package.json +4 -3
- package/packages/daemon/src/api.js +212 -21
- package/packages/daemon/src/index.js +68 -1
- package/packages/daemon/src/integrations.js +59 -20
- package/packages/daemon/src/process.js +83 -11
- package/packages/daemon/src/providers/claude-code.js +4 -0
- package/packages/daemon/src/registry.js +1 -1
- package/packages/gui/dist/assets/index-x5suAiK7.js +182 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +68 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +1420 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +17 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +22 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +34 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +101 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +2534 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +789 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +115 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +1136 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +63 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +179 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +104 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +46 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +121 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +9237 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +9934 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/_metadata.json +184 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +5169 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +2000 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js +1115 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js +701 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +1776 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js +280 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js +1004 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js +292 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +1062 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +10985 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +3459 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/package.json +3 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js +20217 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react.js +5 -0
- package/packages/gui/node_modules/.vite/deps/react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js +56 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js.map +7 -0
- package/packages/gui/package.json +5 -4
- package/packages/gui/src/App.jsx +149 -76
- package/packages/gui/src/components/AgentActions.jsx +130 -1
- package/packages/gui/src/components/AgentChat.jsx +47 -7
- package/packages/gui/src/components/AgentNode.jsx +13 -83
- package/packages/gui/src/components/SpawnPanel.jsx +918 -580
- package/packages/gui/src/stores/groove.js +31 -2
- package/packages/gui/src/views/AgentTree.jsx +133 -67
- package/packages/gui/src/views/FileEditor.jsx +85 -1
- package/packages/gui/src/views/IntegrationsStore.jsx +121 -44
- package/docs/FILE-EDITOR-PLAN.md +0 -253
- package/docs/GUI_DESIGN_SPEC.md +0 -402
- package/docs/SKILLS-API-SPEC.md +0 -277
- package/node_modules/@groove-dev/gui/dist/assets/index-D5dtDQf0.js +0 -156
- package/packages/gui/dist/assets/index-D5dtDQf0.js +0 -156
|
@@ -380,10 +380,8 @@ export class IntegrationStore {
|
|
|
380
380
|
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
381
381
|
if (entry.authType !== 'oauth-google') throw new Error('Integration does not use OAuth');
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
386
|
-
if (!clientId || !clientSecret) {
|
|
383
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
384
|
+
if (!creds) {
|
|
387
385
|
throw new Error('Google OAuth not configured. Set up your Google Cloud project first.');
|
|
388
386
|
}
|
|
389
387
|
|
|
@@ -392,7 +390,7 @@ export class IntegrationStore {
|
|
|
392
390
|
const scopes = entry.oauthScopes || [];
|
|
393
391
|
|
|
394
392
|
const params = new URLSearchParams({
|
|
395
|
-
client_id: clientId,
|
|
393
|
+
client_id: creds.clientId,
|
|
396
394
|
redirect_uri: redirectUri,
|
|
397
395
|
response_type: 'code',
|
|
398
396
|
scope: scopes.join(' '),
|
|
@@ -408,11 +406,11 @@ export class IntegrationStore {
|
|
|
408
406
|
* Handle OAuth callback — exchange code for tokens.
|
|
409
407
|
*/
|
|
410
408
|
async handleOAuthCallback(code, integrationId) {
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
if (!clientId || !clientSecret) {
|
|
409
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
410
|
+
if (!creds) {
|
|
414
411
|
throw new Error('Google OAuth credentials not found');
|
|
415
412
|
}
|
|
413
|
+
const { clientId, clientSecret } = creds;
|
|
416
414
|
|
|
417
415
|
const port = this.daemon.port || 31415;
|
|
418
416
|
const redirectUri = `http://localhost:${port}/api/integrations/oauth/callback`;
|
|
@@ -451,10 +449,32 @@ export class IntegrationStore {
|
|
|
451
449
|
/**
|
|
452
450
|
* Check if Google OAuth is configured (user has set up their Cloud project).
|
|
453
451
|
*/
|
|
452
|
+
/**
|
|
453
|
+
* Get Google OAuth credentials from user config OR bundled defaults.
|
|
454
|
+
* User-configured credentials take priority over bundled defaults.
|
|
455
|
+
*/
|
|
456
|
+
_getGoogleOAuthCredentials() {
|
|
457
|
+
// 1. Check user-configured credentials (encrypted store)
|
|
458
|
+
let clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
|
|
459
|
+
let clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
460
|
+
if (clientId && clientSecret) return { clientId, clientSecret, source: 'user' };
|
|
461
|
+
|
|
462
|
+
// 2. Check bundled defaults (shipped with Groove)
|
|
463
|
+
try {
|
|
464
|
+
const defaultsPath = resolve(__dirname, '../google-oauth.json');
|
|
465
|
+
if (existsSync(defaultsPath)) {
|
|
466
|
+
const defaults = JSON.parse(readFileSync(defaultsPath, 'utf8'));
|
|
467
|
+
if (defaults.client_id && defaults.client_secret) {
|
|
468
|
+
return { clientId: defaults.client_id, clientSecret: defaults.client_secret, source: 'bundled' };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch { /* no bundled defaults */ }
|
|
472
|
+
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
454
476
|
isGoogleOAuthConfigured() {
|
|
455
|
-
|
|
456
|
-
const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
457
|
-
return !!(clientId && clientSecret);
|
|
477
|
+
return !!this._getGoogleOAuthCredentials();
|
|
458
478
|
}
|
|
459
479
|
|
|
460
480
|
/**
|
|
@@ -467,14 +487,18 @@ export class IntegrationStore {
|
|
|
467
487
|
const entry = this.registry.find((s) => s.id === integrationId);
|
|
468
488
|
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
469
489
|
|
|
490
|
+
console.log(`[Groove:Integrations] authenticate(${integrationId}) — authType: ${entry.authType}`);
|
|
491
|
+
|
|
470
492
|
// For google-autoauth integrations, write the gcp-oauth.keys.json file
|
|
471
493
|
// that the MCP server expects before it can start the OAuth browser flow
|
|
472
494
|
if (entry.authType === 'google-autoauth') {
|
|
495
|
+
console.log(`[Groove:Integrations] Writing gcp-oauth.keys.json for ${integrationId}`);
|
|
473
496
|
this._writeGoogleOAuthKeys(entry);
|
|
474
497
|
}
|
|
475
498
|
|
|
476
499
|
const command = entry.command || 'npx';
|
|
477
500
|
const args = entry.args || ['-y', entry.npmPackage];
|
|
501
|
+
console.log(`[Groove:Integrations] Spawning: ${command} ${args.join(' ')}`);
|
|
478
502
|
|
|
479
503
|
// Build env with any configured credentials
|
|
480
504
|
const env = {};
|
|
@@ -491,6 +515,8 @@ export class IntegrationStore {
|
|
|
491
515
|
detached: false,
|
|
492
516
|
});
|
|
493
517
|
|
|
518
|
+
console.log(`[Groove:Integrations] Process spawned, PID: ${proc.pid}`);
|
|
519
|
+
|
|
494
520
|
// Send MCP handshake to initialize the server — this triggers auth
|
|
495
521
|
const initMsg = JSON.stringify({
|
|
496
522
|
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
@@ -507,28 +533,38 @@ export class IntegrationStore {
|
|
|
507
533
|
jsonrpc: '2.0', method: 'notifications/initialized',
|
|
508
534
|
});
|
|
509
535
|
|
|
510
|
-
// Wait a moment for npx to download + start, then send handshake
|
|
511
536
|
proc.stdout.on('data', (chunk) => {
|
|
512
537
|
const text = chunk.toString();
|
|
538
|
+
console.log(`[Groove:Integrations] MCP stdout: ${text.slice(0, 200)}`);
|
|
513
539
|
// After initialize response, send initialized notification + tools/list
|
|
514
540
|
if (text.includes('"id":1') || text.includes('"id": 1')) {
|
|
541
|
+
console.log('[Groove:Integrations] Got initialize response, sending initialized + tools/list');
|
|
515
542
|
proc.stdin.write(initializedNotif + '\n');
|
|
516
543
|
setTimeout(() => proc.stdin.write(listToolsMsg + '\n'), 500);
|
|
517
544
|
}
|
|
518
545
|
});
|
|
519
546
|
|
|
547
|
+
proc.on('error', (err) => {
|
|
548
|
+
console.log(`[Groove:Integrations] Process error: ${err.message}`);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
proc.on('exit', (code, signal) => {
|
|
552
|
+
console.log(`[Groove:Integrations] Process exited: code=${code} signal=${signal}`);
|
|
553
|
+
clearTimeout(timeout);
|
|
554
|
+
});
|
|
555
|
+
|
|
520
556
|
// Send initialize after a brief delay for npx startup
|
|
521
557
|
setTimeout(() => {
|
|
522
|
-
|
|
558
|
+
console.log('[Groove:Integrations] Sending MCP initialize message');
|
|
559
|
+
try { proc.stdin.write(initMsg + '\n'); } catch (e) { console.log('[Groove:Integrations] stdin write failed:', e.message); }
|
|
523
560
|
}, 3000);
|
|
524
561
|
|
|
525
|
-
// Auto-kill after 2 minutes
|
|
562
|
+
// Auto-kill after 2 minutes
|
|
526
563
|
const timeout = setTimeout(() => {
|
|
564
|
+
console.log('[Groove:Integrations] Auth timeout — killing process');
|
|
527
565
|
try { proc.kill('SIGTERM'); } catch { /* ignore */ }
|
|
528
566
|
}, 120_000);
|
|
529
567
|
|
|
530
|
-
proc.on('exit', () => clearTimeout(timeout));
|
|
531
|
-
|
|
532
568
|
this.daemon.audit.log('integration.authenticate', { id: integrationId });
|
|
533
569
|
|
|
534
570
|
return {
|
|
@@ -543,11 +579,11 @@ export class IntegrationStore {
|
|
|
543
579
|
* before they can open the browser for user consent.
|
|
544
580
|
*/
|
|
545
581
|
_writeGoogleOAuthKeys(entry) {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
throw new Error('Google OAuth not configured. Click "Sign in with Google" to set up your Google Cloud credentials first.');
|
|
582
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
583
|
+
if (!creds) {
|
|
584
|
+
throw new Error('Google OAuth not configured. Set up your Google Cloud credentials first.');
|
|
550
585
|
}
|
|
586
|
+
const { clientId, clientSecret } = creds;
|
|
551
587
|
|
|
552
588
|
const keysContent = JSON.stringify({
|
|
553
589
|
installed: {
|
|
@@ -567,6 +603,9 @@ export class IntegrationStore {
|
|
|
567
603
|
mkdirSync(dirPath, { recursive: true });
|
|
568
604
|
const keysPath = resolve(dirPath, 'gcp-oauth.keys.json');
|
|
569
605
|
writeFileSync(keysPath, keysContent, { mode: 0o600 });
|
|
606
|
+
console.log(`[Groove:Integrations] Wrote OAuth keys to: ${keysPath}`);
|
|
607
|
+
} else {
|
|
608
|
+
console.log(`[Groove:Integrations] WARNING: No oauthKeysDir for ${entry.id}`);
|
|
570
609
|
}
|
|
571
610
|
}
|
|
572
611
|
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import { spawn as cpSpawn } from 'child_process';
|
|
5
|
-
import { createWriteStream, mkdirSync, chmodSync } from 'fs';
|
|
5
|
+
import { createWriteStream, mkdirSync, chmodSync, existsSync, readFileSync, unlinkSync } from 'fs';
|
|
6
6
|
import { resolve } from 'path';
|
|
7
7
|
import { getProvider } from './providers/index.js';
|
|
8
|
+
import { validateAgentConfig } from './validate.js';
|
|
8
9
|
|
|
9
10
|
// Role-specific prompt prefixes — applied during spawn regardless of entry point
|
|
10
11
|
// (SpawnPanel, chat continue, CLI, API) for consistency
|
|
@@ -58,11 +59,15 @@ Do NOT write code unless explicitly asked. Use your MCP tools (database queries,
|
|
|
58
59
|
Do NOT write code unless explicitly asked. Use your MCP tools to interact with Home Assistant.
|
|
59
60
|
|
|
60
61
|
`,
|
|
61
|
-
planner: `You are a
|
|
62
|
+
planner: `You are a PLANNING ONLY agent. You create plans. You do NOT write code, edit files, or run commands.
|
|
63
|
+
|
|
64
|
+
ABSOLUTE RULE: Never use the Edit, Write, or Bash tools to modify source code. You ONLY use Read, Glob, and Grep to understand the codebase, then output a written plan. If the user says "build this" or "redesign this", create a PLAN for how other agents should build it — do NOT build it yourself.
|
|
65
|
+
|
|
66
|
+
Focus on:
|
|
62
67
|
- Understanding requirements
|
|
63
|
-
- Exploring the codebase
|
|
68
|
+
- Exploring the codebase to understand current architecture
|
|
64
69
|
- Identifying approaches and trade-offs
|
|
65
|
-
- Writing structured plans
|
|
70
|
+
- Writing structured plans with agent assignments
|
|
66
71
|
|
|
67
72
|
After completing your plan, you MUST do two things:
|
|
68
73
|
|
|
@@ -70,15 +75,25 @@ After completing your plan, you MUST do two things:
|
|
|
70
75
|
|
|
71
76
|
2. Save a machine-readable team config to .groove/recommended-team.json using this EXACT format:
|
|
72
77
|
[
|
|
73
|
-
{ "role": "
|
|
74
|
-
{ "role": "backend", "
|
|
75
|
-
{ "role": "
|
|
78
|
+
{ "role": "frontend", "phase": 1, "scope": ["src/components/**", "src/views/**"], "prompt": "Build the frontend: [specific tasks]" },
|
|
79
|
+
{ "role": "backend", "phase": 1, "scope": ["src/api/**", "src/server/**"], "prompt": "Build the backend: [specific tasks]" },
|
|
80
|
+
{ "role": "fullstack", "phase": 2, "scope": [], "prompt": "QC Senior Dev: Audit all changes from phase 1 agents. Verify correctness, fix issues, run tests, build the project, commit, and launch. Output the localhost URL." }
|
|
76
81
|
]
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
MANDATORY RULES — NEVER SKIP THESE:
|
|
84
|
+
|
|
85
|
+
1. The LAST entry in the array MUST be: { "role": "fullstack", "phase": 2, ... }
|
|
86
|
+
This is the QC Senior Dev. It auto-spawns after all other agents finish.
|
|
87
|
+
Its prompt: audit changes, fix issues, run tests, build, commit, launch.
|
|
88
|
+
NEVER omit this agent. Every team needs a QC.
|
|
89
|
+
|
|
90
|
+
2. ALL other agents are phase: 1 — they run in parallel.
|
|
91
|
+
|
|
92
|
+
3. Do NOT tell any agent to "wait for" another agent. Phase 2 handles sequencing automatically.
|
|
93
|
+
|
|
94
|
+
4. Set appropriate scopes. Write detailed prompts so each agent knows exactly what to build.
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
5. If the project is a monorepo, set "workingDir" for agents that need specific subdirectories.
|
|
82
97
|
|
|
83
98
|
IMPORTANT: Do not use markdown formatting like ** or ### in your output. Write in plain text with clean formatting. Use line breaks, dashes, and indentation for structure.
|
|
84
99
|
|
|
@@ -105,6 +120,17 @@ export class ProcessManager {
|
|
|
105
120
|
async spawn(config) {
|
|
106
121
|
const { registry, locks, introducer } = this.daemon;
|
|
107
122
|
|
|
123
|
+
// Clean stale recommended-team.json when spawning a new planner
|
|
124
|
+
if (config.role === 'planner') {
|
|
125
|
+
const dirs = [this.daemon.grooveDir];
|
|
126
|
+
if (config.workingDir) dirs.push(resolve(config.workingDir, '.groove'));
|
|
127
|
+
if (this.daemon.config?.defaultWorkingDir) dirs.push(resolve(this.daemon.config.defaultWorkingDir, '.groove'));
|
|
128
|
+
for (const dir of dirs) {
|
|
129
|
+
const p = resolve(dir, 'recommended-team.json');
|
|
130
|
+
if (existsSync(p)) try { unlinkSync(p); } catch { /* */ }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
108
134
|
// Validate provider exists and is installed
|
|
109
135
|
const provider = getProvider(config.provider || 'claude-code');
|
|
110
136
|
if (!provider) {
|
|
@@ -325,6 +351,9 @@ For normal file edits within your scope, proceed without review.
|
|
|
325
351
|
if (finalStatus === 'completed' && this.daemon.journalist) {
|
|
326
352
|
this.daemon.journalist.cycle().catch(() => {});
|
|
327
353
|
}
|
|
354
|
+
|
|
355
|
+
// Phase 2 auto-spawn: check if all phase 1 agents for a team are done
|
|
356
|
+
this._checkPhase2(agent.id);
|
|
328
357
|
});
|
|
329
358
|
|
|
330
359
|
proc.on('error', (err) => {
|
|
@@ -338,6 +367,49 @@ For normal file edits within your scope, proceed without review.
|
|
|
338
367
|
return agent;
|
|
339
368
|
}
|
|
340
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Check if a completed/crashed agent was the last phase 1 agent in a team.
|
|
372
|
+
* If so, auto-spawn the phase 2 (QC/finisher) agents.
|
|
373
|
+
*/
|
|
374
|
+
_checkPhase2(completedAgentId) {
|
|
375
|
+
const pending = this.daemon._pendingPhase2;
|
|
376
|
+
if (!pending || pending.length === 0) return;
|
|
377
|
+
|
|
378
|
+
const registry = this.daemon.registry;
|
|
379
|
+
|
|
380
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
381
|
+
const group = pending[i];
|
|
382
|
+
if (!group.waitFor.includes(completedAgentId)) continue;
|
|
383
|
+
|
|
384
|
+
// Check if ALL phase 1 agents in this group are done
|
|
385
|
+
const allDone = group.waitFor.every((id) => {
|
|
386
|
+
const a = registry.get(id);
|
|
387
|
+
return !a || a.status === 'completed' || a.status === 'crashed' || a.status === 'stopped' || a.status === 'killed';
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (allDone) {
|
|
391
|
+
// Remove from pending
|
|
392
|
+
pending.splice(i, 1);
|
|
393
|
+
|
|
394
|
+
// Auto-spawn phase 2 agents
|
|
395
|
+
for (const config of group.agents) {
|
|
396
|
+
try {
|
|
397
|
+
const validated = validateAgentConfig(config);
|
|
398
|
+
this.spawn(validated).then((agent) => {
|
|
399
|
+
this.daemon.broadcast({
|
|
400
|
+
type: 'phase2:spawned',
|
|
401
|
+
agentId: agent.id,
|
|
402
|
+
name: agent.name,
|
|
403
|
+
role: agent.role,
|
|
404
|
+
});
|
|
405
|
+
this.daemon.audit.log('phase2.autoSpawn', { id: agent.id, name: agent.name, role: agent.role });
|
|
406
|
+
}).catch(() => {});
|
|
407
|
+
} catch { /* skip invalid configs */ }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
341
413
|
/**
|
|
342
414
|
* Resume a completed agent's session with a new message.
|
|
343
415
|
* Uses --resume SESSION_ID for zero cold-start continuation.
|
|
@@ -390,7 +462,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
390
462
|
model: config.model,
|
|
391
463
|
prompt: config.prompt,
|
|
392
464
|
permission: config.permission,
|
|
393
|
-
workingDir: config.workingDir,
|
|
465
|
+
workingDir: config.workingDir || this.daemon.config?.defaultWorkingDir || undefined,
|
|
394
466
|
name: config.name,
|
|
395
467
|
});
|
|
396
468
|
|
|
@@ -48,6 +48,10 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
48
48
|
args.push('--model', agent.model);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
if (agent.effort) {
|
|
52
|
+
args.push('--effort', agent.effort);
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
// Pass the initial prompt as positional arg (includes GROOVE context)
|
|
52
56
|
const fullPrompt = this.buildFullPrompt(agent);
|
|
53
57
|
if (fullPrompt) {
|
|
@@ -50,7 +50,7 @@ export class Registry extends EventEmitter {
|
|
|
50
50
|
if (!agent) return null;
|
|
51
51
|
|
|
52
52
|
// Only allow known fields to prevent prototype pollution
|
|
53
|
-
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations'];
|
|
53
|
+
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations', 'workingDir', 'effort'];
|
|
54
54
|
for (const key of Object.keys(updates)) {
|
|
55
55
|
if (SAFE_FIELDS.includes(key)) {
|
|
56
56
|
agent[key] = updates[key];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"t":"2026-04-08T02:20:01.108Z","action":"credential.set","provider":"anthropic-api"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projectName": "gui",
|
|
3
|
+
"scannedAt": "2026-04-08T01:26:36.485Z",
|
|
4
|
+
"stats": {
|
|
5
|
+
"totalFiles": 32,
|
|
6
|
+
"totalDirs": 5,
|
|
7
|
+
"treeDepth": 4
|
|
8
|
+
},
|
|
9
|
+
"workspaces": [],
|
|
10
|
+
"keyFiles": [
|
|
11
|
+
"package.json"
|
|
12
|
+
],
|
|
13
|
+
"tree": [
|
|
14
|
+
{
|
|
15
|
+
"path": ".",
|
|
16
|
+
"depth": 0,
|
|
17
|
+
"dirs": 2,
|
|
18
|
+
"files": 3,
|
|
19
|
+
"children": [
|
|
20
|
+
"public",
|
|
21
|
+
"src"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "public",
|
|
26
|
+
"depth": 1,
|
|
27
|
+
"dirs": 0,
|
|
28
|
+
"files": 3,
|
|
29
|
+
"children": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "src",
|
|
33
|
+
"depth": 1,
|
|
34
|
+
"dirs": 3,
|
|
35
|
+
"files": 3,
|
|
36
|
+
"children": [
|
|
37
|
+
"components",
|
|
38
|
+
"stores",
|
|
39
|
+
"views"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "src/components",
|
|
44
|
+
"depth": 2,
|
|
45
|
+
"dirs": 0,
|
|
46
|
+
"files": 15,
|
|
47
|
+
"children": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"path": "src/stores",
|
|
51
|
+
"depth": 2,
|
|
52
|
+
"dirs": 0,
|
|
53
|
+
"files": 1,
|
|
54
|
+
"children": []
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "src/views",
|
|
58
|
+
"depth": 2,
|
|
59
|
+
"dirs": 0,
|
|
60
|
+
"files": 7,
|
|
61
|
+
"children": []
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"anthropic-api": {
|
|
3
|
+
"key": "bee44e798e85b64b04e5198bd44074f0:a0de1b79cca1a00842ca83193e383128:fb7b942b82313c940f802fd2c54f5945756ce6b65dae652b1534acd38d9cca19cdfaad8eee267f56e11f2c2ec6cd8db16c4a4139ddfe0777517437a3a4b5beb53e89210e31c1cfc1f964bba8b26ca75b93f62d36ea1dbc19d3910a91c8a976a2c4369b233a5d00f799e33ebd",
|
|
4
|
+
"setAt": "2026-04-08T02:20:01.107Z"
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
31416
|