groove-dev 0.27.61 → 0.27.62
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 +102 -12
- package/node_modules/@groove-dev/gui/dist/assets/{index-DWao9glo.js → index-Dvum7uoe.js} +1 -1
- 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/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 +102 -12
- package/packages/gui/dist/assets/{index-DWao9glo.js → index-Dvum7uoe.js} +1 -1
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/stores/groove.js +25 -6
|
@@ -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' },
|
|
@@ -4728,7 +4772,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4728
4772
|
};
|
|
4729
4773
|
|
|
4730
4774
|
try {
|
|
4731
|
-
const pat = daemon.credentials?.getKey?.('github-pat') || null;
|
|
4775
|
+
const pat = daemon.credentials?.getKey?.('github') || daemon.credentials?.getKey?.('github-pat') || null;
|
|
4732
4776
|
|
|
4733
4777
|
let installVersion;
|
|
4734
4778
|
try {
|
|
@@ -4739,6 +4783,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4739
4783
|
|
|
4740
4784
|
broadcastInstallProgress('cloning', `Cloning network package ${installVersion}...`, 0);
|
|
4741
4785
|
|
|
4786
|
+
// Pre-flight: verify git is installed before attempting clone.
|
|
4787
|
+
const gitInstalled = await new Promise((resolveGit) => {
|
|
4788
|
+
execFile('git', ['--version'], { timeout: 5000 }, (err) => resolveGit(!err));
|
|
4789
|
+
});
|
|
4790
|
+
if (!gitInstalled) {
|
|
4791
|
+
return fail('Git is not installed. Install Git from https://git-scm.com and restart Groove.');
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4742
4794
|
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1', NETWORK_REPO_URL, installPath];
|
|
4743
4795
|
const cloneEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
4744
4796
|
if (pat) {
|
|
@@ -4768,14 +4820,30 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4768
4820
|
});
|
|
4769
4821
|
|
|
4770
4822
|
if (cloneCode.code !== 0) {
|
|
4771
|
-
|
|
4823
|
+
let hint;
|
|
4824
|
+
const errMsg = cloneCode.err || '';
|
|
4825
|
+
const lastLine = cloneErr.trim().split('\n').slice(-1)[0] || '';
|
|
4826
|
+
if (errMsg.includes('ENOENT')) {
|
|
4827
|
+
hint = 'Git is not installed. Install Git from https://git-scm.com and restart Groove.';
|
|
4828
|
+
} else if (/Authentication failed|could not read Username/i.test(cloneErr)) {
|
|
4829
|
+
hint = 'Authentication failed — run "groove set-key github-pat <token>" to set a GitHub PAT.';
|
|
4830
|
+
} else if (/not found/i.test(cloneErr)) {
|
|
4831
|
+
hint = `Repository or tag not found (${installVersion}). Check NETWORK_REPO_URL and tag.`;
|
|
4832
|
+
} else {
|
|
4833
|
+
hint = stripCredentials(lastLine || errMsg || 'git clone failed');
|
|
4834
|
+
}
|
|
4772
4835
|
return fail(`Clone failed: ${hint}`);
|
|
4773
4836
|
}
|
|
4774
4837
|
|
|
4775
4838
|
broadcastInstallProgress('cloned', 'Repository cloned', 10);
|
|
4776
4839
|
|
|
4777
4840
|
// Run setup.sh --json from the install directory
|
|
4778
|
-
|
|
4841
|
+
let setup;
|
|
4842
|
+
try {
|
|
4843
|
+
setup = spawnSetupSh(installPath);
|
|
4844
|
+
} catch (spawnErr) {
|
|
4845
|
+
return fail(`Setup failed: ${spawnErr.message}`);
|
|
4846
|
+
}
|
|
4779
4847
|
|
|
4780
4848
|
daemon.networkInstall.proc = setup;
|
|
4781
4849
|
|
|
@@ -4809,7 +4877,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4809
4877
|
});
|
|
4810
4878
|
|
|
4811
4879
|
if (setupResult.code !== 0) {
|
|
4812
|
-
|
|
4880
|
+
let hint;
|
|
4881
|
+
if (setupResult.code === -1 || setupResult.err?.includes('ENOENT')) {
|
|
4882
|
+
hint = 'bash not found — ensure Git for Windows is installed from https://git-scm.com';
|
|
4883
|
+
} else {
|
|
4884
|
+
hint = stderrBuf.trim().split('\n').slice(-1)[0] || `setup.sh exited ${setupResult.code}`;
|
|
4885
|
+
}
|
|
4813
4886
|
return fail(`Setup failed: ${hint}`);
|
|
4814
4887
|
}
|
|
4815
4888
|
|
|
@@ -4883,9 +4956,16 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4883
4956
|
// surface that. Uses spawn with array args — no shell interpolation.
|
|
4884
4957
|
function fetchLatestNetworkTag() {
|
|
4885
4958
|
return new Promise((resolvePromise) => {
|
|
4959
|
+
const tagEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
4960
|
+
const tagPat = daemon.credentials?.getKey?.('github') || daemon.credentials?.getKey?.('github-pat') || null;
|
|
4961
|
+
if (tagPat) {
|
|
4962
|
+
tagEnv.GIT_CONFIG_COUNT = '1';
|
|
4963
|
+
tagEnv.GIT_CONFIG_KEY_0 = 'http.extraHeader';
|
|
4964
|
+
tagEnv.GIT_CONFIG_VALUE_0 = `Authorization: token ${tagPat}`;
|
|
4965
|
+
}
|
|
4886
4966
|
const proc = spawn('git', ['ls-remote', '--tags', NETWORK_REPO_URL], {
|
|
4887
4967
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4888
|
-
env:
|
|
4968
|
+
env: tagEnv,
|
|
4889
4969
|
});
|
|
4890
4970
|
let stdout = '';
|
|
4891
4971
|
let stderr = '';
|
|
@@ -5023,7 +5103,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
5023
5103
|
|
|
5024
5104
|
broadcastUpdateProgress('deps', 'Updating dependencies...', 30);
|
|
5025
5105
|
|
|
5026
|
-
|
|
5106
|
+
let setup;
|
|
5107
|
+
try {
|
|
5108
|
+
setup = spawnSetupSh(installPath);
|
|
5109
|
+
} catch (spawnErr) {
|
|
5110
|
+
return fail(`Setup failed: ${spawnErr.message}`);
|
|
5111
|
+
}
|
|
5027
5112
|
|
|
5028
5113
|
daemon.networkInstall.proc = setup;
|
|
5029
5114
|
|
|
@@ -5054,7 +5139,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
5054
5139
|
});
|
|
5055
5140
|
|
|
5056
5141
|
if (setupResult.code !== 0) {
|
|
5057
|
-
|
|
5142
|
+
let hint;
|
|
5143
|
+
if (setupResult.code === -1 || setupResult.err?.includes('ENOENT')) {
|
|
5144
|
+
hint = 'bash not found — ensure Git for Windows is installed from https://git-scm.com';
|
|
5145
|
+
} else {
|
|
5146
|
+
hint = stderrBuf.trim().split('\n').slice(-1)[0] || `setup.sh exited ${setupResult.code}`;
|
|
5147
|
+
}
|
|
5058
5148
|
return fail(`Setup failed: ${hint}`);
|
|
5059
5149
|
}
|
|
5060
5150
|
|