groove-dev 0.27.51 → 0.27.53
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 +68 -13
- package/node_modules/@groove-dev/daemon/src/firstrun.js +1 -1
- package/node_modules/@groove-dev/daemon/src/process.js +2 -2
- package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +31 -12
- package/node_modules/@groove-dev/gui/dist/assets/{index-Dd4u8X70.js → index-De-OWmBX.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 +10 -10
- 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 +68 -13
- package/packages/daemon/src/firstrun.js +1 -1
- package/packages/daemon/src/process.js +2 -2
- package/packages/daemon/src/providers/groove-network.js +31 -12
- package/packages/gui/dist/assets/{index-Dd4u8X70.js → index-De-OWmBX.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 +10 -10
|
@@ -3866,7 +3866,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3866
3866
|
// so revoked or expired codes lock the feature automatically. Non-blocking.
|
|
3867
3867
|
daemon.revalidateBetaCode = async function revalidateBetaCode() {
|
|
3868
3868
|
const cfg = daemon.config?.networkBeta;
|
|
3869
|
-
if (!cfg?.unlocked
|
|
3869
|
+
if (!cfg?.unlocked) return;
|
|
3870
|
+
if (!cfg?.code) {
|
|
3871
|
+
daemon.config.networkBeta = { ...cfg, unlocked: false, expiresAt: null, features: [] };
|
|
3872
|
+
await persistConfig();
|
|
3873
|
+
daemon.audit.log('beta.revoked', { reason: 'missing code' });
|
|
3874
|
+
daemon.broadcast({ type: 'config:updated' });
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3870
3877
|
const remote = await validateCodeWithServer(cfg.code);
|
|
3871
3878
|
// If we couldn't reach the server, keep the current unlocked state —
|
|
3872
3879
|
// network failures must not lock out beta users.
|
|
@@ -4017,6 +4024,9 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4017
4024
|
if (!existsSync(deployPath)) {
|
|
4018
4025
|
return res.status(400).json({ error: `Deploy path not found: ${deployPath}` });
|
|
4019
4026
|
}
|
|
4027
|
+
if (!isInsideGrooveHome(deployPath) && !deployPath.startsWith(resolve(process.env.HOME || '', 'Desktop'))) {
|
|
4028
|
+
return res.status(400).json({ error: 'Deploy path outside allowed directories' });
|
|
4029
|
+
}
|
|
4020
4030
|
|
|
4021
4031
|
const signalFlag = supportsSignalFlag(cfg.version) ? '--signal' : '--relay';
|
|
4022
4032
|
const args = [
|
|
@@ -4205,11 +4215,57 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4205
4215
|
...(total_layers !== undefined ? { totalLayers: total_layers } : {}),
|
|
4206
4216
|
};
|
|
4207
4217
|
}) : [];
|
|
4218
|
+
const primaryModel = Array.isArray(data.models) && data.models[0] ? data.models[0] : {};
|
|
4219
|
+
|
|
4220
|
+
// Enrich local node state from signal's authoritative topology.
|
|
4221
|
+
// Signal truncates IDs (e.g. "0xf608fd..."), so match by prefix.
|
|
4222
|
+
if (daemon.networkNode?.active && daemon.networkNode.nodeId) {
|
|
4223
|
+
const selfId = daemon.networkNode.nodeId;
|
|
4224
|
+
const signalNodes = Array.isArray(data.nodes) ? data.nodes : [];
|
|
4225
|
+
const self = signalNodes.find((n) => {
|
|
4226
|
+
const nid = n.node_id || n.nodeId || '';
|
|
4227
|
+
const prefix = nid.replace(/\.{2,}$/, '');
|
|
4228
|
+
return selfId === nid || (prefix.length >= 6 && selfId.startsWith(prefix));
|
|
4229
|
+
});
|
|
4230
|
+
let changed = false;
|
|
4231
|
+
if (self) {
|
|
4232
|
+
if (Array.isArray(self.layers) && self.layers.length === 2) {
|
|
4233
|
+
daemon.networkNode.layers = self.layers;
|
|
4234
|
+
changed = true;
|
|
4235
|
+
}
|
|
4236
|
+
if (self.device) {
|
|
4237
|
+
daemon.networkNode.hardware = {
|
|
4238
|
+
device: self.device,
|
|
4239
|
+
memory: daemon.networkNode.hardware?.memory || null,
|
|
4240
|
+
gpu: daemon.networkNode.hardware?.gpu || null,
|
|
4241
|
+
};
|
|
4242
|
+
changed = true;
|
|
4243
|
+
}
|
|
4244
|
+
}
|
|
4245
|
+
const availModel = Array.isArray(data.models)
|
|
4246
|
+
? data.models.find((m) => m && m.available !== false)
|
|
4247
|
+
: null;
|
|
4248
|
+
if (availModel && !daemon.networkNode.model) {
|
|
4249
|
+
daemon.networkNode.model = availModel.name || null;
|
|
4250
|
+
changed = true;
|
|
4251
|
+
}
|
|
4252
|
+
if (changed) broadcastNodeStatus();
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
const capStr = (s, max = 200) => (typeof s === 'string' ? s.slice(0, max) : s);
|
|
4256
|
+
const safeNodes = (Array.isArray(data.nodes) ? data.nodes : []).map((n) => ({
|
|
4257
|
+
node_id: capStr(n.node_id || n.nodeId),
|
|
4258
|
+
device: capStr(n.device),
|
|
4259
|
+
layers: Array.isArray(n.layers) ? n.layers.slice(0, 2) : n.layers,
|
|
4260
|
+
status: capStr(n.status, 50),
|
|
4261
|
+
active_sessions: n.active_sessions ?? 0,
|
|
4262
|
+
}));
|
|
4263
|
+
|
|
4208
4264
|
return res.json({
|
|
4209
|
-
nodes:
|
|
4265
|
+
nodes: safeNodes,
|
|
4210
4266
|
models,
|
|
4211
|
-
coverage: data.covered_layers ?? data.coverage ?? 0,
|
|
4212
|
-
totalLayers: data.total_layers ?? data.totalLayers ?? 24,
|
|
4267
|
+
coverage: data.covered_layers ?? primaryModel.covered_layers ?? data.coverage ?? 0,
|
|
4268
|
+
totalLayers: data.total_layers ?? primaryModel.total_layers ?? data.totalLayers ?? 24,
|
|
4213
4269
|
activeSessions: data.active_sessions ?? data.activeSessions ?? 0,
|
|
4214
4270
|
totalNodes: data.total_nodes ?? data.totalNodes ?? (Array.isArray(data.nodes) ? data.nodes.length : 0),
|
|
4215
4271
|
});
|
|
@@ -4312,15 +4368,8 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4312
4368
|
};
|
|
4313
4369
|
|
|
4314
4370
|
try {
|
|
4315
|
-
// Build clone URL with optional PAT
|
|
4316
4371
|
const pat = daemon.credentials?.getKey?.('github-pat') || null;
|
|
4317
|
-
const cloneUrl = pat
|
|
4318
|
-
? NETWORK_REPO_URL.replace('https://', `https://${pat}@`)
|
|
4319
|
-
: NETWORK_REPO_URL;
|
|
4320
4372
|
|
|
4321
|
-
// Resolve the latest released tag so fresh installs track new releases
|
|
4322
|
-
// without requiring a code change. Falls back to NETWORK_VERSION if the
|
|
4323
|
-
// remote lookup fails (offline, rate-limited, no tags yet).
|
|
4324
4373
|
let installVersion;
|
|
4325
4374
|
try {
|
|
4326
4375
|
installVersion = (await getLatestNetworkTag()) || NETWORK_VERSION;
|
|
@@ -4330,10 +4379,16 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4330
4379
|
|
|
4331
4380
|
broadcastInstallProgress('cloning', `Cloning network package ${installVersion}...`, 0);
|
|
4332
4381
|
|
|
4333
|
-
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1',
|
|
4382
|
+
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1', NETWORK_REPO_URL, installPath];
|
|
4383
|
+
const cloneEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
4384
|
+
if (pat) {
|
|
4385
|
+
cloneEnv.GIT_CONFIG_COUNT = '1';
|
|
4386
|
+
cloneEnv.GIT_CONFIG_KEY_0 = 'http.extraHeader';
|
|
4387
|
+
cloneEnv.GIT_CONFIG_VALUE_0 = `Authorization: token ${pat}`;
|
|
4388
|
+
}
|
|
4334
4389
|
const clone = spawn('git', cloneArgs, {
|
|
4335
4390
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4336
|
-
env:
|
|
4391
|
+
env: cloneEnv,
|
|
4337
4392
|
});
|
|
4338
4393
|
|
|
4339
4394
|
const stripCredentials = (s) => s.replace(/https:\/\/[^@]+@/g, 'https://***@');
|
|
@@ -155,5 +155,5 @@ export function loadConfig(grooveDir) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function saveConfig(grooveDir, config) {
|
|
158
|
-
writeFileSync(resolve(grooveDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
158
|
+
writeFileSync(resolve(grooveDir, 'config.json'), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
159
159
|
}
|
|
@@ -740,7 +740,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
740
740
|
}
|
|
741
741
|
|
|
742
742
|
const spawnCmd = provider.buildSpawnCommand(spawnConfig);
|
|
743
|
-
const { command, args, env, stdin: stdinData } = spawnCmd;
|
|
743
|
+
const { command, args, env, stdin: stdinData, cwd: providerCwd } = spawnCmd;
|
|
744
744
|
|
|
745
745
|
// Log the spawn command (mask anything that looks like an API key)
|
|
746
746
|
const maskArg = (a) => /^(sk-|AIza|key-|token-)/.test(a) ? '***' : a;
|
|
@@ -765,7 +765,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
765
765
|
|
|
766
766
|
// Spawn the process (use pipe for stdin if provider needs to send prompt via stdin)
|
|
767
767
|
const proc = cpSpawn(command, args, {
|
|
768
|
-
cwd: agent.workingDir || this.daemon.projectDir,
|
|
768
|
+
cwd: providerCwd || agent.workingDir || this.daemon.projectDir,
|
|
769
769
|
env: { ...process.env, ...env, ...integrationEnv, GROOVE_AGENT_ID: agent.id, GROOVE_AGENT_NAME: agent.name, GROOVE_DAEMON_HOST: this.daemon.host || '127.0.0.1', GROOVE_DAEMON_PORT: String(this.daemon.port || 31415) },
|
|
770
770
|
stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
771
771
|
detached: false,
|
|
@@ -66,6 +66,11 @@ function stripScheme(url) {
|
|
|
66
66
|
return url.replace(/^wss?:\/\//i, '').replace(/\/.*$/, '');
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function isAllowedSignalHost(host) {
|
|
70
|
+
const h = (host || '').replace(/^(wss?|https?):\/\//i, '').replace(/\/.*$/, '').toLowerCase();
|
|
71
|
+
return h === 'signal.groovedev.ai' || h.endsWith('.groovedev.ai');
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
export class GrooveNetworkProvider extends Provider {
|
|
70
75
|
static name = 'groove-network';
|
|
71
76
|
static displayName = 'Groove Network';
|
|
@@ -88,6 +93,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
88
93
|
buildSpawnCommand(agent) {
|
|
89
94
|
const cfg = getConfig() || {};
|
|
90
95
|
const signal = stripScheme(cfg.signalUrl);
|
|
96
|
+
if (!isAllowedSignalHost(signal)) throw new Error('Invalid signal host');
|
|
91
97
|
const model = agent.model || GrooveNetworkProvider.models[0].id;
|
|
92
98
|
const maxTokens = agent.maxTokens || 500;
|
|
93
99
|
const prompt = agent.prompt || '';
|
|
@@ -115,6 +121,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
115
121
|
buildHeadlessCommand(prompt, model) {
|
|
116
122
|
const cfg = getConfig() || {};
|
|
117
123
|
const signal = stripScheme(cfg.signalUrl);
|
|
124
|
+
if (!isAllowedSignalHost(signal)) throw new Error('Invalid signal host');
|
|
118
125
|
const m = model || GrooveNetworkProvider.models[0].id;
|
|
119
126
|
const deployPath = expandHome(cfg.deployPath) || resolve(homedir(), 'Desktop/groove-deploy');
|
|
120
127
|
return {
|
|
@@ -145,19 +152,31 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
145
152
|
}
|
|
146
153
|
try {
|
|
147
154
|
const msg = JSON.parse(trimmed);
|
|
148
|
-
if (msg
|
|
149
|
-
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
nodes: msg.nodes,
|
|
158
|
-
raw: msg,
|
|
159
|
-
};
|
|
155
|
+
if (!msg || typeof msg !== 'object' || typeof msg.type !== 'string') {
|
|
156
|
+
return { type: 'activity', data: trimmed };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (msg.type === 'token' && msg.text) {
|
|
160
|
+
return { type: 'activity', subtype: 'text', data: msg.text, tokensGenerated: msg.tokens_generated };
|
|
161
|
+
}
|
|
162
|
+
if (msg.type === 'complete' || msg.type === 'result') {
|
|
163
|
+
return { type: 'result', text: msg.text || '', tokensGenerated: msg.tokens_generated, sessionId: msg.session_id };
|
|
160
164
|
}
|
|
165
|
+
if (msg.type === 'error') {
|
|
166
|
+
return { type: 'activity', subtype: 'error', data: msg.error || msg.message || 'Unknown error' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const labels = {
|
|
170
|
+
signal_connected: `Connected to ${msg.signal || 'signal'}`,
|
|
171
|
+
matched: `Matched with ${Array.isArray(msg.nodes) ? msg.nodes.length : '?'} nodes`,
|
|
172
|
+
connected: `Session ${(msg.session_id || '').slice(0, 8) || 'started'}`,
|
|
173
|
+
pipeline: `Pipeline ready — ${Array.isArray(msg.nodes) ? msg.nodes.length : '?'} nodes`,
|
|
174
|
+
};
|
|
175
|
+
if (labels[msg.type]) {
|
|
176
|
+
return { type: 'activity', data: labels[msg.type], sessionId: msg.session_id };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { type: 'activity', data: msg.text || msg.message || msg.type, raw: msg };
|
|
161
180
|
} catch { /* not JSON, fall through */ }
|
|
162
181
|
return { type: 'activity', data: trimmed };
|
|
163
182
|
}
|