groove-dev 0.27.61 → 0.27.63
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/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 +117 -16
- package/node_modules/@groove-dev/gui/dist/assets/{index-DWao9glo.js → index-Zb6PcuaY.js} +12 -12
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/network/compute-header.jsx +98 -13
- package/node_modules/@groove-dev/gui/src/stores/groove.js +25 -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 +117 -16
- package/packages/gui/dist/assets/{index-DWao9glo.js → index-Zb6PcuaY.js} +12 -12
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/network/compute-header.jsx +98 -13
- package/packages/gui/src/stores/groove.js +25 -6
package/CLAUDE.md
CHANGED
|
@@ -263,3 +263,10 @@ 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: 0
|
|
270
|
+
See AGENTS_REGISTRY.md for full agent state.
|
|
271
|
+
**Memory policy:** GROOVE manages project memory automatically. Do not read or write MEMORY.md or .groove/memory/ files directly.
|
|
272
|
+
<!-- GROOVE:END -->
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import express from 'express';
|
|
5
|
-
import { resolve, dirname, join, sep } from 'path';
|
|
5
|
+
import { resolve, dirname, join, sep, relative } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, unlinkSync, renameSync, rmSync, createReadStream, copyFileSync, realpathSync } from 'fs';
|
|
8
|
-
import { spawn, execFile } from 'child_process';
|
|
8
|
+
import { spawn, execFile, execFileSync } from 'child_process';
|
|
9
9
|
import { createHash, randomUUID } from 'crypto';
|
|
10
10
|
import { hostname, networkInterfaces, homedir } from 'os';
|
|
11
11
|
import { lookup as mimeLookup } from './mimetypes.js';
|
|
@@ -2914,6 +2914,18 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
2914
2914
|
console.log(`[Groove] Project directory: ${projectWorkingDir}`);
|
|
2915
2915
|
}
|
|
2916
2916
|
|
|
2917
|
+
function normalizeScope(patterns, baseDir) {
|
|
2918
|
+
if (!patterns || !Array.isArray(patterns)) return patterns;
|
|
2919
|
+
return patterns.map((p) => {
|
|
2920
|
+
if (typeof p === 'string' && p.startsWith('/')) {
|
|
2921
|
+
const rel = relative(baseDir, p);
|
|
2922
|
+
if (!rel.startsWith('..')) return rel;
|
|
2923
|
+
return p.slice(1);
|
|
2924
|
+
}
|
|
2925
|
+
return p;
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2917
2929
|
// Separate phase 1 (builders) and phase 2 (QC/finisher)
|
|
2918
2930
|
const phase1 = agentConfigs.filter((a) => !a.phase || a.phase === 1);
|
|
2919
2931
|
let phase2 = agentConfigs.filter((a) => a.phase === 2);
|
|
@@ -2973,7 +2985,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
2973
2985
|
// Spawn fresh with the same name/team but new prompt + full context
|
|
2974
2986
|
const validated = validateAgentConfig({
|
|
2975
2987
|
role: existing.role,
|
|
2976
|
-
scope: config.scope || existing.scope || [],
|
|
2988
|
+
scope: normalizeScope(config.scope || existing.scope || [], existing.workingDir || projectWorkingDir),
|
|
2977
2989
|
prompt,
|
|
2978
2990
|
provider: config.provider || existing.provider || undefined,
|
|
2979
2991
|
model: config.model || existing.model || daemon.config?.defaultModel || 'auto',
|
|
@@ -2995,7 +3007,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
2995
3007
|
try {
|
|
2996
3008
|
const validated = validateAgentConfig({
|
|
2997
3009
|
role: config.role,
|
|
2998
|
-
scope: config.scope || [],
|
|
3010
|
+
scope: normalizeScope(config.scope || [], config.workingDir || projectWorkingDir),
|
|
2999
3011
|
prompt,
|
|
3000
3012
|
provider: config.provider || undefined,
|
|
3001
3013
|
model: config.model || daemon.config?.defaultModel || 'auto',
|
|
@@ -3015,6 +3027,10 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3015
3027
|
}
|
|
3016
3028
|
}
|
|
3017
3029
|
|
|
3030
|
+
if (failed.length > 0) {
|
|
3031
|
+
console.warn(`[Groove] Team launch had ${failed.length} failure(s):`, failed.map((f) => `${f.role}: ${f.error}`).join(', '));
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3018
3034
|
// Phase 2 agents also scoped to projectWorkingDir
|
|
3019
3035
|
if (phase2.length > 0 && phase1Ids.length > 0) {
|
|
3020
3036
|
// Dedup: if a running idle fullstack already exists in this team,
|
|
@@ -4619,9 +4635,37 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4619
4635
|
: join(base, 'venv', 'bin', 'python3');
|
|
4620
4636
|
}
|
|
4621
4637
|
|
|
4638
|
+
let _cachedGitBash = undefined;
|
|
4639
|
+
function findGitBash() {
|
|
4640
|
+
if (_cachedGitBash !== undefined) return _cachedGitBash;
|
|
4641
|
+
try {
|
|
4642
|
+
const gitPath = execFileSync('where', ['git'], { timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
4643
|
+
.toString().trim().split('\n')[0].trim();
|
|
4644
|
+
// git.exe is typically at <Git>\cmd\git.exe — navigate up to Git root
|
|
4645
|
+
const gitDir = dirname(dirname(gitPath));
|
|
4646
|
+
const candidate = join(gitDir, 'bin', 'bash.exe');
|
|
4647
|
+
if (existsSync(candidate)) { _cachedGitBash = candidate; return _cachedGitBash; }
|
|
4648
|
+
} catch { /* where failed — try common paths */ }
|
|
4649
|
+
const fallbacks = [
|
|
4650
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
4651
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
4652
|
+
];
|
|
4653
|
+
for (const p of fallbacks) {
|
|
4654
|
+
if (existsSync(p)) { _cachedGitBash = p; return _cachedGitBash; }
|
|
4655
|
+
}
|
|
4656
|
+
_cachedGitBash = null;
|
|
4657
|
+
return null;
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4622
4660
|
function spawnSetupSh(cwd) {
|
|
4623
4661
|
if (IS_WIN) {
|
|
4624
|
-
|
|
4662
|
+
const bashPath = findGitBash();
|
|
4663
|
+
if (!bashPath) {
|
|
4664
|
+
const err = new Error('Could not find bash. Ensure Git for Windows is installed from https://git-scm.com');
|
|
4665
|
+
err.code = 'BASH_NOT_FOUND';
|
|
4666
|
+
throw err;
|
|
4667
|
+
}
|
|
4668
|
+
return spawn(bashPath, ['setup.sh', '--json'], {
|
|
4625
4669
|
cwd,
|
|
4626
4670
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4627
4671
|
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
|
@@ -4681,10 +4725,13 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4681
4725
|
|
|
4682
4726
|
app.get('/api/network/install/status', networkGate, (req, res) => {
|
|
4683
4727
|
const installPath = networkRoot();
|
|
4684
|
-
const
|
|
4728
|
+
const dirExists = existsSync(installPath);
|
|
4729
|
+
const installed = dirExists && existsSync(resolve(installPath, 'setup.sh'));
|
|
4730
|
+
const stale = dirExists && !installed;
|
|
4685
4731
|
res.json({
|
|
4686
4732
|
installed,
|
|
4687
|
-
|
|
4733
|
+
stale,
|
|
4734
|
+
path: dirExists ? installPath : null,
|
|
4688
4735
|
version: installed ? getInstalledNetworkVersion() : null,
|
|
4689
4736
|
});
|
|
4690
4737
|
});
|
|
@@ -4702,9 +4749,17 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4702
4749
|
return res.status(500).json({ error: 'Invalid install path' });
|
|
4703
4750
|
}
|
|
4704
4751
|
|
|
4705
|
-
//
|
|
4752
|
+
// If directory exists from a previous failed install, clean it up automatically.
|
|
4706
4753
|
if (existsSync(installPath)) {
|
|
4707
|
-
|
|
4754
|
+
if (daemon.config?.networkBeta?.installed) {
|
|
4755
|
+
return res.status(400).json({ error: 'Install path already exists; uninstall first' });
|
|
4756
|
+
}
|
|
4757
|
+
try {
|
|
4758
|
+
rmSync(installPath, { recursive: true, force: true });
|
|
4759
|
+
daemon.audit?.log?.('network.install.stale-cleanup', { path: installPath });
|
|
4760
|
+
} catch (cleanupErr) {
|
|
4761
|
+
return res.status(500).json({ error: `Failed to clean stale install directory: ${cleanupErr.message}` });
|
|
4762
|
+
}
|
|
4708
4763
|
}
|
|
4709
4764
|
|
|
4710
4765
|
daemon.networkInstall = { running: true, startedAt: Date.now() };
|
|
@@ -4728,7 +4783,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4728
4783
|
};
|
|
4729
4784
|
|
|
4730
4785
|
try {
|
|
4731
|
-
const pat = daemon.credentials?.getKey?.('github-pat') || null;
|
|
4786
|
+
const pat = daemon.credentials?.getKey?.('github') || daemon.credentials?.getKey?.('github-pat') || null;
|
|
4732
4787
|
|
|
4733
4788
|
let installVersion;
|
|
4734
4789
|
try {
|
|
@@ -4739,6 +4794,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4739
4794
|
|
|
4740
4795
|
broadcastInstallProgress('cloning', `Cloning network package ${installVersion}...`, 0);
|
|
4741
4796
|
|
|
4797
|
+
// Pre-flight: verify git is installed before attempting clone.
|
|
4798
|
+
const gitInstalled = await new Promise((resolveGit) => {
|
|
4799
|
+
execFile('git', ['--version'], { timeout: 5000 }, (err) => resolveGit(!err));
|
|
4800
|
+
});
|
|
4801
|
+
if (!gitInstalled) {
|
|
4802
|
+
return fail('Git is not installed. Install Git from https://git-scm.com and restart Groove.');
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4742
4805
|
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1', NETWORK_REPO_URL, installPath];
|
|
4743
4806
|
const cloneEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
4744
4807
|
if (pat) {
|
|
@@ -4768,14 +4831,30 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4768
4831
|
});
|
|
4769
4832
|
|
|
4770
4833
|
if (cloneCode.code !== 0) {
|
|
4771
|
-
|
|
4834
|
+
let hint;
|
|
4835
|
+
const errMsg = cloneCode.err || '';
|
|
4836
|
+
const lastLine = cloneErr.trim().split('\n').slice(-1)[0] || '';
|
|
4837
|
+
if (errMsg.includes('ENOENT')) {
|
|
4838
|
+
hint = 'Git is not installed. Install Git from https://git-scm.com and restart Groove.';
|
|
4839
|
+
} else if (/Authentication failed|could not read Username/i.test(cloneErr)) {
|
|
4840
|
+
hint = 'Authentication failed — run "groove set-key github-pat <token>" to set a GitHub PAT.';
|
|
4841
|
+
} else if (/not found/i.test(cloneErr)) {
|
|
4842
|
+
hint = `Repository or tag not found (${installVersion}). Check NETWORK_REPO_URL and tag.`;
|
|
4843
|
+
} else {
|
|
4844
|
+
hint = stripCredentials(lastLine || errMsg || 'git clone failed');
|
|
4845
|
+
}
|
|
4772
4846
|
return fail(`Clone failed: ${hint}`);
|
|
4773
4847
|
}
|
|
4774
4848
|
|
|
4775
4849
|
broadcastInstallProgress('cloned', 'Repository cloned', 10);
|
|
4776
4850
|
|
|
4777
4851
|
// Run setup.sh --json from the install directory
|
|
4778
|
-
|
|
4852
|
+
let setup;
|
|
4853
|
+
try {
|
|
4854
|
+
setup = spawnSetupSh(installPath);
|
|
4855
|
+
} catch (spawnErr) {
|
|
4856
|
+
return fail(`Setup failed: ${spawnErr.message}`);
|
|
4857
|
+
}
|
|
4779
4858
|
|
|
4780
4859
|
daemon.networkInstall.proc = setup;
|
|
4781
4860
|
|
|
@@ -4809,7 +4888,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4809
4888
|
});
|
|
4810
4889
|
|
|
4811
4890
|
if (setupResult.code !== 0) {
|
|
4812
|
-
|
|
4891
|
+
let hint;
|
|
4892
|
+
if (setupResult.code === -1 || setupResult.err?.includes('ENOENT')) {
|
|
4893
|
+
hint = 'bash not found — ensure Git for Windows is installed from https://git-scm.com';
|
|
4894
|
+
} else {
|
|
4895
|
+
hint = stderrBuf.trim().split('\n').slice(-1)[0] || `setup.sh exited ${setupResult.code}`;
|
|
4896
|
+
}
|
|
4813
4897
|
return fail(`Setup failed: ${hint}`);
|
|
4814
4898
|
}
|
|
4815
4899
|
|
|
@@ -4883,9 +4967,16 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4883
4967
|
// surface that. Uses spawn with array args — no shell interpolation.
|
|
4884
4968
|
function fetchLatestNetworkTag() {
|
|
4885
4969
|
return new Promise((resolvePromise) => {
|
|
4970
|
+
const tagEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
4971
|
+
const tagPat = daemon.credentials?.getKey?.('github') || daemon.credentials?.getKey?.('github-pat') || null;
|
|
4972
|
+
if (tagPat) {
|
|
4973
|
+
tagEnv.GIT_CONFIG_COUNT = '1';
|
|
4974
|
+
tagEnv.GIT_CONFIG_KEY_0 = 'http.extraHeader';
|
|
4975
|
+
tagEnv.GIT_CONFIG_VALUE_0 = `Authorization: token ${tagPat}`;
|
|
4976
|
+
}
|
|
4886
4977
|
const proc = spawn('git', ['ls-remote', '--tags', NETWORK_REPO_URL], {
|
|
4887
4978
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4888
|
-
env:
|
|
4979
|
+
env: tagEnv,
|
|
4889
4980
|
});
|
|
4890
4981
|
let stdout = '';
|
|
4891
4982
|
let stderr = '';
|
|
@@ -5023,7 +5114,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
5023
5114
|
|
|
5024
5115
|
broadcastUpdateProgress('deps', 'Updating dependencies...', 30);
|
|
5025
5116
|
|
|
5026
|
-
|
|
5117
|
+
let setup;
|
|
5118
|
+
try {
|
|
5119
|
+
setup = spawnSetupSh(installPath);
|
|
5120
|
+
} catch (spawnErr) {
|
|
5121
|
+
return fail(`Setup failed: ${spawnErr.message}`);
|
|
5122
|
+
}
|
|
5027
5123
|
|
|
5028
5124
|
daemon.networkInstall.proc = setup;
|
|
5029
5125
|
|
|
@@ -5054,7 +5150,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
5054
5150
|
});
|
|
5055
5151
|
|
|
5056
5152
|
if (setupResult.code !== 0) {
|
|
5057
|
-
|
|
5153
|
+
let hint;
|
|
5154
|
+
if (setupResult.code === -1 || setupResult.err?.includes('ENOENT')) {
|
|
5155
|
+
hint = 'bash not found — ensure Git for Windows is installed from https://git-scm.com';
|
|
5156
|
+
} else {
|
|
5157
|
+
hint = stderrBuf.trim().split('\n').slice(-1)[0] || `setup.sh exited ${setupResult.code}`;
|
|
5158
|
+
}
|
|
5058
5159
|
return fail(`Setup failed: ${hint}`);
|
|
5059
5160
|
}
|
|
5060
5161
|
|