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
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-De-OWmBX.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
|
|
@@ -607,9 +607,11 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
607
607
|
get().addToast('warning', 'Session expired', 'Please sign in again');
|
|
608
608
|
break;
|
|
609
609
|
|
|
610
|
-
case 'network:node:status':
|
|
611
|
-
|
|
610
|
+
case 'network:node:status': {
|
|
611
|
+
const { __proto__: _a, constructor: _b, prototype: _c, ...safeData } = msg.data || {};
|
|
612
|
+
set({ networkNode: { ...get().networkNode, ...safeData } });
|
|
612
613
|
break;
|
|
614
|
+
}
|
|
613
615
|
|
|
614
616
|
case 'network:node:event': {
|
|
615
617
|
const ev = msg.data || {};
|
|
@@ -620,13 +622,12 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
620
622
|
}
|
|
621
623
|
|
|
622
624
|
case 'signal_connected': {
|
|
623
|
-
const ev = msg.data ||
|
|
625
|
+
const ev = msg.data || {};
|
|
624
626
|
set((s) => ({
|
|
625
627
|
networkEvents: [...s.networkEvents, {
|
|
626
628
|
level: 'connected',
|
|
627
|
-
msg: ev.msg || ev.message || 'Connected to signal',
|
|
628
|
-
detail: ev.detail || ev.url,
|
|
629
|
-
...ev,
|
|
629
|
+
msg: String(ev.msg || ev.message || 'Connected to signal').slice(0, 500),
|
|
630
|
+
detail: ev.detail || ev.url || undefined,
|
|
630
631
|
timestamp: ev.timestamp || Date.now(),
|
|
631
632
|
}].slice(-100),
|
|
632
633
|
}));
|
|
@@ -634,13 +635,12 @@ export const useGrooveStore = create((set, get) => ({
|
|
|
634
635
|
}
|
|
635
636
|
|
|
636
637
|
case 'matched': {
|
|
637
|
-
const ev = msg.data ||
|
|
638
|
+
const ev = msg.data || {};
|
|
638
639
|
set((s) => ({
|
|
639
640
|
networkEvents: [...s.networkEvents, {
|
|
640
641
|
level: 'session',
|
|
641
|
-
msg: ev.msg || ev.message || 'Session matched',
|
|
642
|
-
detail: ev.detail || ev.peer || ev.nodeId,
|
|
643
|
-
...ev,
|
|
642
|
+
msg: String(ev.msg || ev.message || 'Session matched').slice(0, 500),
|
|
643
|
+
detail: ev.detail || ev.peer || ev.nodeId || undefined,
|
|
644
644
|
timestamp: ev.timestamp || Date.now(),
|
|
645
645
|
}].slice(-100),
|
|
646
646
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.53",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -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
|
}
|