groove-dev 0.27.50 → 0.27.51
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 +75 -13
- package/node_modules/@groove-dev/daemon/src/providers/groove-network.js +15 -5
- package/node_modules/@groove-dev/gui/package.json +1 -1
- 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 +75 -13
- package/packages/daemon/src/providers/groove-network.js +15 -5
- package/packages/gui/package.json +1 -1
|
@@ -3999,7 +3999,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3999
3999
|
}
|
|
4000
4000
|
|
|
4001
4001
|
const cfg = daemon.config.networkBeta || {};
|
|
4002
|
-
const signal = cfg.signalUrl
|
|
4002
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
4003
4003
|
if (!isAllowedSignalHost(signal)) {
|
|
4004
4004
|
return res.status(400).json({ error: 'Invalid signal host' });
|
|
4005
4005
|
}
|
|
@@ -4022,13 +4022,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4022
4022
|
const args = [
|
|
4023
4023
|
'-m', 'src.node.server',
|
|
4024
4024
|
signalFlag, signal,
|
|
4025
|
+
'--tls',
|
|
4025
4026
|
'--device', device,
|
|
4026
4027
|
'--max-context', String(maxContext),
|
|
4027
4028
|
];
|
|
4028
4029
|
|
|
4029
4030
|
let proc;
|
|
4030
4031
|
try {
|
|
4031
|
-
proc = spawn(join(deployPath, 'venv', 'bin', 'python3
|
|
4032
|
+
proc = spawn(join(deployPath, 'venv', 'bin', 'python3'), args, {
|
|
4032
4033
|
cwd: deployPath,
|
|
4033
4034
|
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
|
4034
4035
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -4063,7 +4064,33 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4063
4064
|
stderrBuf = stderrBuf.slice(idx + 1);
|
|
4064
4065
|
if (!line) continue;
|
|
4065
4066
|
if (line[0] !== '{') {
|
|
4067
|
+
// Python node emits plain-text logs like "Node identity: abc123",
|
|
4068
|
+
// "shard loaded: layers 0-12", "registered with signal". Parse those
|
|
4069
|
+
// here so the GUI reflects reality even without structured logging.
|
|
4070
|
+
let changed = false;
|
|
4071
|
+
const idMatch = line.match(/Node identity:\s*([A-Za-z0-9_\-:.]+)/i);
|
|
4072
|
+
if (idMatch && idMatch[1] !== daemon.networkNode.nodeId) {
|
|
4073
|
+
daemon.networkNode.nodeId = idMatch[1]; changed = true;
|
|
4074
|
+
}
|
|
4075
|
+
const layerMatch = line.match(/layers?\s*(\d+)\s*[-–to]+\s*(\d+)/i);
|
|
4076
|
+
if (layerMatch) {
|
|
4077
|
+
const start = parseInt(layerMatch[1], 10);
|
|
4078
|
+
const end = parseInt(layerMatch[2], 10);
|
|
4079
|
+
if (Number.isFinite(start) && Number.isFinite(end)) {
|
|
4080
|
+
daemon.networkNode.layers = [start, end]; changed = true;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
const modelMatch = line.match(/model[:\s]+([A-Za-z0-9_\-./]+\/[A-Za-z0-9_\-.]+)/i);
|
|
4084
|
+
if (modelMatch && modelMatch[1] !== daemon.networkNode.model) {
|
|
4085
|
+
daemon.networkNode.model = modelMatch[1]; changed = true;
|
|
4086
|
+
}
|
|
4087
|
+
if (/\bregistered\b/i.test(line) || /\bconnected\b/i.test(line)) {
|
|
4088
|
+
if (daemon.networkNode.status !== 'connected') {
|
|
4089
|
+
daemon.networkNode.status = 'connected'; changed = true;
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4066
4092
|
pushNodeEvent('log', { line });
|
|
4093
|
+
if (changed) broadcastNodeStatus();
|
|
4067
4094
|
continue;
|
|
4068
4095
|
}
|
|
4069
4096
|
let entry;
|
|
@@ -4138,10 +4165,18 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4138
4165
|
});
|
|
4139
4166
|
|
|
4140
4167
|
function isAllowedSignalHost(host) {
|
|
4141
|
-
const h = (host || '').replace(/^https
|
|
4168
|
+
const h = (host || '').replace(/^(wss?|https?):\/\//i, '').replace(/\/.*$/, '').toLowerCase();
|
|
4142
4169
|
return h === 'signal.groovedev.ai' || h.endsWith('.groovedev.ai');
|
|
4143
4170
|
}
|
|
4144
4171
|
|
|
4172
|
+
// The Python node/client code prepends the scheme itself from `--tls`.
|
|
4173
|
+
// Daemon must pass a BARE host to --relay/--signal; otherwise the Python
|
|
4174
|
+
// side ends up with a double-scheme URI like wss://wss://host.
|
|
4175
|
+
function stripScheme(url) {
|
|
4176
|
+
if (!url) return 'signal.groovedev.ai';
|
|
4177
|
+
return url.replace(/^wss?:\/\//i, '').replace(/\/.*$/, '');
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4145
4180
|
app.get('/api/network/status', networkGate, async (req, res) => {
|
|
4146
4181
|
const cfg = daemon.config.networkBeta || {};
|
|
4147
4182
|
const signalHost = cfg.signalUrl || 'signal.groovedev.ai';
|
|
@@ -4150,9 +4185,8 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4150
4185
|
return res.status(400).json({ error: 'Invalid signal host' });
|
|
4151
4186
|
}
|
|
4152
4187
|
|
|
4153
|
-
const
|
|
4154
|
-
|
|
4155
|
-
: `https://${signalHost}/status`;
|
|
4188
|
+
const bareHost = signalHost.replace(/^(wss?|https?):\/\//i, '').replace(/\/.*$/, '');
|
|
4189
|
+
const statusUrl = `https://${bareHost}/status`;
|
|
4156
4190
|
|
|
4157
4191
|
try {
|
|
4158
4192
|
const controller = new AbortController();
|
|
@@ -4161,7 +4195,24 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4161
4195
|
clearTimeout(timer);
|
|
4162
4196
|
if (r.ok) {
|
|
4163
4197
|
const data = await r.json();
|
|
4164
|
-
|
|
4198
|
+
// Signal service returns snake_case; GUI expects camelCase.
|
|
4199
|
+
const models = Array.isArray(data.models) ? data.models.map((m) => {
|
|
4200
|
+
if (!m || typeof m !== 'object') return m;
|
|
4201
|
+
const { covered_layers, total_layers, ...rest } = m;
|
|
4202
|
+
return {
|
|
4203
|
+
...rest,
|
|
4204
|
+
...(covered_layers !== undefined ? { coveredLayers: covered_layers } : {}),
|
|
4205
|
+
...(total_layers !== undefined ? { totalLayers: total_layers } : {}),
|
|
4206
|
+
};
|
|
4207
|
+
}) : [];
|
|
4208
|
+
return res.json({
|
|
4209
|
+
nodes: Array.isArray(data.nodes) ? data.nodes : [],
|
|
4210
|
+
models,
|
|
4211
|
+
coverage: data.covered_layers ?? data.coverage ?? 0,
|
|
4212
|
+
totalLayers: data.total_layers ?? data.totalLayers ?? 24,
|
|
4213
|
+
activeSessions: data.active_sessions ?? data.activeSessions ?? 0,
|
|
4214
|
+
totalNodes: data.total_nodes ?? data.totalNodes ?? (Array.isArray(data.nodes) ? data.nodes.length : 0),
|
|
4215
|
+
});
|
|
4165
4216
|
}
|
|
4166
4217
|
} catch { /* fall through to local snapshot */ }
|
|
4167
4218
|
|
|
@@ -4180,13 +4231,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4180
4231
|
coverage,
|
|
4181
4232
|
totalLayers: 24,
|
|
4182
4233
|
activeSessions: node.sessions || 0,
|
|
4234
|
+
totalNodes: selfNode.length,
|
|
4183
4235
|
});
|
|
4184
4236
|
});
|
|
4185
4237
|
|
|
4186
4238
|
// --- Network package install/uninstall ---
|
|
4187
4239
|
|
|
4188
4240
|
const NETWORK_REPO_URL = 'https://github.com/grooveai-dev/groove-network.git';
|
|
4189
|
-
const NETWORK_VERSION = 'v0.
|
|
4241
|
+
const NETWORK_VERSION = 'v0.2.0';
|
|
4190
4242
|
|
|
4191
4243
|
function networkRoot() {
|
|
4192
4244
|
return resolve(homedir(), '.groove', 'network');
|
|
@@ -4266,9 +4318,19 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4266
4318
|
? NETWORK_REPO_URL.replace('https://', `https://${pat}@`)
|
|
4267
4319
|
: NETWORK_REPO_URL;
|
|
4268
4320
|
|
|
4269
|
-
|
|
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
|
+
let installVersion;
|
|
4325
|
+
try {
|
|
4326
|
+
installVersion = (await getLatestNetworkTag()) || NETWORK_VERSION;
|
|
4327
|
+
} catch {
|
|
4328
|
+
installVersion = NETWORK_VERSION;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
broadcastInstallProgress('cloning', `Cloning network package ${installVersion}...`, 0);
|
|
4270
4332
|
|
|
4271
|
-
const cloneArgs = ['clone', '--branch',
|
|
4333
|
+
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1', cloneUrl, installPath];
|
|
4272
4334
|
const clone = spawn('git', cloneArgs, {
|
|
4273
4335
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4274
4336
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
@@ -4344,12 +4406,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4344
4406
|
...(daemon.config.networkBeta || {}),
|
|
4345
4407
|
installed: true,
|
|
4346
4408
|
deployPath: installPath,
|
|
4347
|
-
version:
|
|
4409
|
+
version: installVersion,
|
|
4348
4410
|
};
|
|
4349
4411
|
await persistConfig();
|
|
4350
4412
|
daemon.broadcast({ type: 'config:updated' });
|
|
4351
|
-
broadcastInstallProgress('done',
|
|
4352
|
-
daemon.audit.log('network.install', { path: installPath, version:
|
|
4413
|
+
broadcastInstallProgress('done', `Network package ${installVersion} installed`, 100);
|
|
4414
|
+
daemon.audit.log('network.install', { path: installPath, version: installVersion });
|
|
4353
4415
|
daemon.networkInstall = { running: false };
|
|
4354
4416
|
} catch (err) {
|
|
4355
4417
|
fail(err?.message || 'Install failed');
|
|
@@ -58,10 +58,18 @@ function signalFlagName() {
|
|
|
58
58
|
return supportsSignalFlag(cfg.version) ? '--signal' : '--relay';
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// The Python client prepends the scheme itself — daemon passes a bare host
|
|
62
|
+
// and adds `--tls` to request wss://. Strip any ws:// or wss:// a user may
|
|
63
|
+
// have left in the stored signalUrl (e.g. from an older daemon default).
|
|
64
|
+
function stripScheme(url) {
|
|
65
|
+
if (!url) return 'signal.groovedev.ai';
|
|
66
|
+
return url.replace(/^wss?:\/\//i, '').replace(/\/.*$/, '');
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
export class GrooveNetworkProvider extends Provider {
|
|
62
70
|
static name = 'groove-network';
|
|
63
71
|
static displayName = 'Groove Network';
|
|
64
|
-
static command = 'python3
|
|
72
|
+
static command = 'python3';
|
|
65
73
|
static authType = 'none';
|
|
66
74
|
|
|
67
75
|
static models = [
|
|
@@ -79,7 +87,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
79
87
|
|
|
80
88
|
buildSpawnCommand(agent) {
|
|
81
89
|
const cfg = getConfig() || {};
|
|
82
|
-
const signal = cfg.signalUrl
|
|
90
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
83
91
|
const model = agent.model || GrooveNetworkProvider.models[0].id;
|
|
84
92
|
const maxTokens = agent.maxTokens || 500;
|
|
85
93
|
const prompt = agent.prompt || '';
|
|
@@ -89,6 +97,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
89
97
|
const args = [
|
|
90
98
|
'-m', 'src.consumer.client',
|
|
91
99
|
signalFlagName(), signal,
|
|
100
|
+
'--tls',
|
|
92
101
|
'--model', model,
|
|
93
102
|
'--prompt', prompt,
|
|
94
103
|
'--max-tokens', String(maxTokens),
|
|
@@ -96,7 +105,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
96
105
|
];
|
|
97
106
|
|
|
98
107
|
return {
|
|
99
|
-
command: join(deployPath, 'venv', 'bin', 'python3
|
|
108
|
+
command: join(deployPath, 'venv', 'bin', 'python3'),
|
|
100
109
|
args,
|
|
101
110
|
env: { PYTHONUNBUFFERED: '1' },
|
|
102
111
|
cwd: deployPath,
|
|
@@ -105,14 +114,15 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
105
114
|
|
|
106
115
|
buildHeadlessCommand(prompt, model) {
|
|
107
116
|
const cfg = getConfig() || {};
|
|
108
|
-
const signal = cfg.signalUrl
|
|
117
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
109
118
|
const m = model || GrooveNetworkProvider.models[0].id;
|
|
110
119
|
const deployPath = expandHome(cfg.deployPath) || resolve(homedir(), 'Desktop/groove-deploy');
|
|
111
120
|
return {
|
|
112
|
-
command: join(deployPath, 'venv', 'bin', 'python3
|
|
121
|
+
command: join(deployPath, 'venv', 'bin', 'python3'),
|
|
113
122
|
args: [
|
|
114
123
|
'-m', 'src.consumer.client',
|
|
115
124
|
signalFlagName(), signal,
|
|
125
|
+
'--tls',
|
|
116
126
|
'--model', m,
|
|
117
127
|
'--prompt', prompt,
|
|
118
128
|
'--max-tokens', '500',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.51",
|
|
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)",
|
|
@@ -3999,7 +3999,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3999
3999
|
}
|
|
4000
4000
|
|
|
4001
4001
|
const cfg = daemon.config.networkBeta || {};
|
|
4002
|
-
const signal = cfg.signalUrl
|
|
4002
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
4003
4003
|
if (!isAllowedSignalHost(signal)) {
|
|
4004
4004
|
return res.status(400).json({ error: 'Invalid signal host' });
|
|
4005
4005
|
}
|
|
@@ -4022,13 +4022,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4022
4022
|
const args = [
|
|
4023
4023
|
'-m', 'src.node.server',
|
|
4024
4024
|
signalFlag, signal,
|
|
4025
|
+
'--tls',
|
|
4025
4026
|
'--device', device,
|
|
4026
4027
|
'--max-context', String(maxContext),
|
|
4027
4028
|
];
|
|
4028
4029
|
|
|
4029
4030
|
let proc;
|
|
4030
4031
|
try {
|
|
4031
|
-
proc = spawn(join(deployPath, 'venv', 'bin', 'python3
|
|
4032
|
+
proc = spawn(join(deployPath, 'venv', 'bin', 'python3'), args, {
|
|
4032
4033
|
cwd: deployPath,
|
|
4033
4034
|
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
|
4034
4035
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -4063,7 +4064,33 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4063
4064
|
stderrBuf = stderrBuf.slice(idx + 1);
|
|
4064
4065
|
if (!line) continue;
|
|
4065
4066
|
if (line[0] !== '{') {
|
|
4067
|
+
// Python node emits plain-text logs like "Node identity: abc123",
|
|
4068
|
+
// "shard loaded: layers 0-12", "registered with signal". Parse those
|
|
4069
|
+
// here so the GUI reflects reality even without structured logging.
|
|
4070
|
+
let changed = false;
|
|
4071
|
+
const idMatch = line.match(/Node identity:\s*([A-Za-z0-9_\-:.]+)/i);
|
|
4072
|
+
if (idMatch && idMatch[1] !== daemon.networkNode.nodeId) {
|
|
4073
|
+
daemon.networkNode.nodeId = idMatch[1]; changed = true;
|
|
4074
|
+
}
|
|
4075
|
+
const layerMatch = line.match(/layers?\s*(\d+)\s*[-–to]+\s*(\d+)/i);
|
|
4076
|
+
if (layerMatch) {
|
|
4077
|
+
const start = parseInt(layerMatch[1], 10);
|
|
4078
|
+
const end = parseInt(layerMatch[2], 10);
|
|
4079
|
+
if (Number.isFinite(start) && Number.isFinite(end)) {
|
|
4080
|
+
daemon.networkNode.layers = [start, end]; changed = true;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
const modelMatch = line.match(/model[:\s]+([A-Za-z0-9_\-./]+\/[A-Za-z0-9_\-.]+)/i);
|
|
4084
|
+
if (modelMatch && modelMatch[1] !== daemon.networkNode.model) {
|
|
4085
|
+
daemon.networkNode.model = modelMatch[1]; changed = true;
|
|
4086
|
+
}
|
|
4087
|
+
if (/\bregistered\b/i.test(line) || /\bconnected\b/i.test(line)) {
|
|
4088
|
+
if (daemon.networkNode.status !== 'connected') {
|
|
4089
|
+
daemon.networkNode.status = 'connected'; changed = true;
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4066
4092
|
pushNodeEvent('log', { line });
|
|
4093
|
+
if (changed) broadcastNodeStatus();
|
|
4067
4094
|
continue;
|
|
4068
4095
|
}
|
|
4069
4096
|
let entry;
|
|
@@ -4138,10 +4165,18 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4138
4165
|
});
|
|
4139
4166
|
|
|
4140
4167
|
function isAllowedSignalHost(host) {
|
|
4141
|
-
const h = (host || '').replace(/^https
|
|
4168
|
+
const h = (host || '').replace(/^(wss?|https?):\/\//i, '').replace(/\/.*$/, '').toLowerCase();
|
|
4142
4169
|
return h === 'signal.groovedev.ai' || h.endsWith('.groovedev.ai');
|
|
4143
4170
|
}
|
|
4144
4171
|
|
|
4172
|
+
// The Python node/client code prepends the scheme itself from `--tls`.
|
|
4173
|
+
// Daemon must pass a BARE host to --relay/--signal; otherwise the Python
|
|
4174
|
+
// side ends up with a double-scheme URI like wss://wss://host.
|
|
4175
|
+
function stripScheme(url) {
|
|
4176
|
+
if (!url) return 'signal.groovedev.ai';
|
|
4177
|
+
return url.replace(/^wss?:\/\//i, '').replace(/\/.*$/, '');
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4145
4180
|
app.get('/api/network/status', networkGate, async (req, res) => {
|
|
4146
4181
|
const cfg = daemon.config.networkBeta || {};
|
|
4147
4182
|
const signalHost = cfg.signalUrl || 'signal.groovedev.ai';
|
|
@@ -4150,9 +4185,8 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4150
4185
|
return res.status(400).json({ error: 'Invalid signal host' });
|
|
4151
4186
|
}
|
|
4152
4187
|
|
|
4153
|
-
const
|
|
4154
|
-
|
|
4155
|
-
: `https://${signalHost}/status`;
|
|
4188
|
+
const bareHost = signalHost.replace(/^(wss?|https?):\/\//i, '').replace(/\/.*$/, '');
|
|
4189
|
+
const statusUrl = `https://${bareHost}/status`;
|
|
4156
4190
|
|
|
4157
4191
|
try {
|
|
4158
4192
|
const controller = new AbortController();
|
|
@@ -4161,7 +4195,24 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4161
4195
|
clearTimeout(timer);
|
|
4162
4196
|
if (r.ok) {
|
|
4163
4197
|
const data = await r.json();
|
|
4164
|
-
|
|
4198
|
+
// Signal service returns snake_case; GUI expects camelCase.
|
|
4199
|
+
const models = Array.isArray(data.models) ? data.models.map((m) => {
|
|
4200
|
+
if (!m || typeof m !== 'object') return m;
|
|
4201
|
+
const { covered_layers, total_layers, ...rest } = m;
|
|
4202
|
+
return {
|
|
4203
|
+
...rest,
|
|
4204
|
+
...(covered_layers !== undefined ? { coveredLayers: covered_layers } : {}),
|
|
4205
|
+
...(total_layers !== undefined ? { totalLayers: total_layers } : {}),
|
|
4206
|
+
};
|
|
4207
|
+
}) : [];
|
|
4208
|
+
return res.json({
|
|
4209
|
+
nodes: Array.isArray(data.nodes) ? data.nodes : [],
|
|
4210
|
+
models,
|
|
4211
|
+
coverage: data.covered_layers ?? data.coverage ?? 0,
|
|
4212
|
+
totalLayers: data.total_layers ?? data.totalLayers ?? 24,
|
|
4213
|
+
activeSessions: data.active_sessions ?? data.activeSessions ?? 0,
|
|
4214
|
+
totalNodes: data.total_nodes ?? data.totalNodes ?? (Array.isArray(data.nodes) ? data.nodes.length : 0),
|
|
4215
|
+
});
|
|
4165
4216
|
}
|
|
4166
4217
|
} catch { /* fall through to local snapshot */ }
|
|
4167
4218
|
|
|
@@ -4180,13 +4231,14 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4180
4231
|
coverage,
|
|
4181
4232
|
totalLayers: 24,
|
|
4182
4233
|
activeSessions: node.sessions || 0,
|
|
4234
|
+
totalNodes: selfNode.length,
|
|
4183
4235
|
});
|
|
4184
4236
|
});
|
|
4185
4237
|
|
|
4186
4238
|
// --- Network package install/uninstall ---
|
|
4187
4239
|
|
|
4188
4240
|
const NETWORK_REPO_URL = 'https://github.com/grooveai-dev/groove-network.git';
|
|
4189
|
-
const NETWORK_VERSION = 'v0.
|
|
4241
|
+
const NETWORK_VERSION = 'v0.2.0';
|
|
4190
4242
|
|
|
4191
4243
|
function networkRoot() {
|
|
4192
4244
|
return resolve(homedir(), '.groove', 'network');
|
|
@@ -4266,9 +4318,19 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4266
4318
|
? NETWORK_REPO_URL.replace('https://', `https://${pat}@`)
|
|
4267
4319
|
: NETWORK_REPO_URL;
|
|
4268
4320
|
|
|
4269
|
-
|
|
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
|
+
let installVersion;
|
|
4325
|
+
try {
|
|
4326
|
+
installVersion = (await getLatestNetworkTag()) || NETWORK_VERSION;
|
|
4327
|
+
} catch {
|
|
4328
|
+
installVersion = NETWORK_VERSION;
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4331
|
+
broadcastInstallProgress('cloning', `Cloning network package ${installVersion}...`, 0);
|
|
4270
4332
|
|
|
4271
|
-
const cloneArgs = ['clone', '--branch',
|
|
4333
|
+
const cloneArgs = ['clone', '--branch', installVersion, '--depth', '1', cloneUrl, installPath];
|
|
4272
4334
|
const clone = spawn('git', cloneArgs, {
|
|
4273
4335
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4274
4336
|
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
@@ -4344,12 +4406,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4344
4406
|
...(daemon.config.networkBeta || {}),
|
|
4345
4407
|
installed: true,
|
|
4346
4408
|
deployPath: installPath,
|
|
4347
|
-
version:
|
|
4409
|
+
version: installVersion,
|
|
4348
4410
|
};
|
|
4349
4411
|
await persistConfig();
|
|
4350
4412
|
daemon.broadcast({ type: 'config:updated' });
|
|
4351
|
-
broadcastInstallProgress('done',
|
|
4352
|
-
daemon.audit.log('network.install', { path: installPath, version:
|
|
4413
|
+
broadcastInstallProgress('done', `Network package ${installVersion} installed`, 100);
|
|
4414
|
+
daemon.audit.log('network.install', { path: installPath, version: installVersion });
|
|
4353
4415
|
daemon.networkInstall = { running: false };
|
|
4354
4416
|
} catch (err) {
|
|
4355
4417
|
fail(err?.message || 'Install failed');
|
|
@@ -58,10 +58,18 @@ function signalFlagName() {
|
|
|
58
58
|
return supportsSignalFlag(cfg.version) ? '--signal' : '--relay';
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// The Python client prepends the scheme itself — daemon passes a bare host
|
|
62
|
+
// and adds `--tls` to request wss://. Strip any ws:// or wss:// a user may
|
|
63
|
+
// have left in the stored signalUrl (e.g. from an older daemon default).
|
|
64
|
+
function stripScheme(url) {
|
|
65
|
+
if (!url) return 'signal.groovedev.ai';
|
|
66
|
+
return url.replace(/^wss?:\/\//i, '').replace(/\/.*$/, '');
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
export class GrooveNetworkProvider extends Provider {
|
|
62
70
|
static name = 'groove-network';
|
|
63
71
|
static displayName = 'Groove Network';
|
|
64
|
-
static command = 'python3
|
|
72
|
+
static command = 'python3';
|
|
65
73
|
static authType = 'none';
|
|
66
74
|
|
|
67
75
|
static models = [
|
|
@@ -79,7 +87,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
79
87
|
|
|
80
88
|
buildSpawnCommand(agent) {
|
|
81
89
|
const cfg = getConfig() || {};
|
|
82
|
-
const signal = cfg.signalUrl
|
|
90
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
83
91
|
const model = agent.model || GrooveNetworkProvider.models[0].id;
|
|
84
92
|
const maxTokens = agent.maxTokens || 500;
|
|
85
93
|
const prompt = agent.prompt || '';
|
|
@@ -89,6 +97,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
89
97
|
const args = [
|
|
90
98
|
'-m', 'src.consumer.client',
|
|
91
99
|
signalFlagName(), signal,
|
|
100
|
+
'--tls',
|
|
92
101
|
'--model', model,
|
|
93
102
|
'--prompt', prompt,
|
|
94
103
|
'--max-tokens', String(maxTokens),
|
|
@@ -96,7 +105,7 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
96
105
|
];
|
|
97
106
|
|
|
98
107
|
return {
|
|
99
|
-
command: join(deployPath, 'venv', 'bin', 'python3
|
|
108
|
+
command: join(deployPath, 'venv', 'bin', 'python3'),
|
|
100
109
|
args,
|
|
101
110
|
env: { PYTHONUNBUFFERED: '1' },
|
|
102
111
|
cwd: deployPath,
|
|
@@ -105,14 +114,15 @@ export class GrooveNetworkProvider extends Provider {
|
|
|
105
114
|
|
|
106
115
|
buildHeadlessCommand(prompt, model) {
|
|
107
116
|
const cfg = getConfig() || {};
|
|
108
|
-
const signal = cfg.signalUrl
|
|
117
|
+
const signal = stripScheme(cfg.signalUrl);
|
|
109
118
|
const m = model || GrooveNetworkProvider.models[0].id;
|
|
110
119
|
const deployPath = expandHome(cfg.deployPath) || resolve(homedir(), 'Desktop/groove-deploy');
|
|
111
120
|
return {
|
|
112
|
-
command: join(deployPath, 'venv', 'bin', 'python3
|
|
121
|
+
command: join(deployPath, 'venv', 'bin', 'python3'),
|
|
113
122
|
args: [
|
|
114
123
|
'-m', 'src.consumer.client',
|
|
115
124
|
signalFlagName(), signal,
|
|
125
|
+
'--tls',
|
|
116
126
|
'--model', m,
|
|
117
127
|
'--prompt', prompt,
|
|
118
128
|
'--max-tokens', '500',
|