groove-dev 0.27.30 → 0.27.33
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/cli/src/commands/start.js +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +32 -2
- package/node_modules/@groove-dev/daemon/src/firstrun.js +1 -0
- package/node_modules/@groove-dev/daemon/src/index.js +14 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +16 -4
- package/node_modules/@groove-dev/daemon/src/memory.js +6 -1
- package/node_modules/@groove-dev/daemon/src/process.js +44 -28
- package/node_modules/@groove-dev/daemon/src/providers/local.js +4 -2
- package/node_modules/@groove-dev/daemon/src/providers/ollama.js +35 -3
- package/node_modules/@groove-dev/daemon/src/rotator.js +1 -0
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +19 -7
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-BwNjgBny.css → index-BnLiWvrh.css} +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-PxWmJjcJ.js → index-BoU6IhQI.js} +1635 -1635
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +16 -1
- package/node_modules/@groove-dev/gui/src/components/layout/project-picker.jsx +127 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +22 -15
- package/node_modules/@groove-dev/gui/src/stores/groove.js +39 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/start.js +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +32 -2
- package/packages/daemon/src/firstrun.js +1 -0
- package/packages/daemon/src/index.js +14 -0
- package/packages/daemon/src/journalist.js +16 -4
- package/packages/daemon/src/memory.js +6 -1
- package/packages/daemon/src/process.js +44 -28
- package/packages/daemon/src/providers/local.js +4 -2
- package/packages/daemon/src/providers/ollama.js +35 -3
- package/packages/daemon/src/rotator.js +1 -0
- package/packages/daemon/src/tunnel-manager.js +19 -7
- package/packages/gui/dist/assets/{index-BwNjgBny.css → index-BnLiWvrh.css} +1 -1
- package/packages/gui/dist/assets/{index-PxWmJjcJ.js → index-BoU6IhQI.js} +1635 -1635
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/layout/app-shell.jsx +2 -0
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +16 -1
- package/packages/gui/src/components/layout/project-picker.jsx +127 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +22 -15
- package/packages/gui/src/stores/groove.js +39 -2
|
@@ -296,6 +296,7 @@ export class ProcessManager {
|
|
|
296
296
|
this.peakContextUsage = new Map(); // agentId -> highest contextUsage seen
|
|
297
297
|
this.pendingMessages = new Map(); // agentId -> { message, timestamp }
|
|
298
298
|
this._streamThrottle = new Map(); // agentId -> { timer, pending }
|
|
299
|
+
this._rotatingAgents = new Set(); // agentIds currently being rotated (rotator wrote handoff)
|
|
299
300
|
|
|
300
301
|
}
|
|
301
302
|
|
|
@@ -623,7 +624,11 @@ For normal file edits within your scope, proceed without review.
|
|
|
623
624
|
const files = this.daemon.journalist?.getAgentFiles(agent) || [];
|
|
624
625
|
if (files.length > 0) this._triggerIdleQC(agent);
|
|
625
626
|
this._processHandoffs(agent);
|
|
626
|
-
this.
|
|
627
|
+
if (this._rotatingAgents.has(agent.id)) {
|
|
628
|
+
this._rotatingAgents.delete(agent.id);
|
|
629
|
+
} else {
|
|
630
|
+
this._writeCompletionHandoff(agent).catch(err => console.error(`[Groove] Completion handoff failed for ${agent.name}:`, err.message));
|
|
631
|
+
}
|
|
627
632
|
}
|
|
628
633
|
});
|
|
629
634
|
|
|
@@ -815,7 +820,11 @@ For normal file edits within your scope, proceed without review.
|
|
|
815
820
|
const files = this.daemon.journalist?.getAgentFiles(agent) || [];
|
|
816
821
|
if (files.length > 0) this._triggerIdleQC(agent);
|
|
817
822
|
this._processHandoffs(agent);
|
|
818
|
-
this.
|
|
823
|
+
if (this._rotatingAgents.has(agent.id)) {
|
|
824
|
+
this._rotatingAgents.delete(agent.id);
|
|
825
|
+
} else {
|
|
826
|
+
this._writeCompletionHandoff(agent).catch(err => console.error(`[Groove] Completion handoff failed for ${agent.name}:`, err.message));
|
|
827
|
+
}
|
|
819
828
|
}
|
|
820
829
|
|
|
821
830
|
// Update Layer 7 specialization profile for this agent's session
|
|
@@ -1149,34 +1158,41 @@ For normal file edits within your scope, proceed without review.
|
|
|
1149
1158
|
});
|
|
1150
1159
|
}
|
|
1151
1160
|
|
|
1152
|
-
_writeCompletionHandoff(agent) {
|
|
1161
|
+
async _writeCompletionHandoff(agent) {
|
|
1153
1162
|
if (!this.daemon.memory || !this.daemon.journalist) return;
|
|
1154
1163
|
try {
|
|
1155
1164
|
const agentData = this.daemon.registry.get(agent.id);
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
.
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1165
|
+
|
|
1166
|
+
let brief;
|
|
1167
|
+
try {
|
|
1168
|
+
brief = await this.daemon.journalist.generateHandoffBrief(agent, { reason: 'completed' });
|
|
1169
|
+
} catch {
|
|
1170
|
+
// Fallback to structural brief if AI synthesis fails
|
|
1171
|
+
const filteredLogs = this.daemon.journalist.collectFilteredLogs([agent]);
|
|
1172
|
+
const agentLog = filteredLogs[agent.id];
|
|
1173
|
+
const entries = agentLog?.entries || [];
|
|
1174
|
+
const files = this.daemon.journalist.getAgentFiles(agent) || [];
|
|
1175
|
+
|
|
1176
|
+
const toolSummary = entries
|
|
1177
|
+
.filter(e => e.type === 'tool')
|
|
1178
|
+
.map(e => `- ${e.tool}: ${e.input}`)
|
|
1179
|
+
.slice(-15)
|
|
1180
|
+
.join('\n');
|
|
1181
|
+
|
|
1182
|
+
const errorSummary = entries
|
|
1183
|
+
.filter(e => e.type === 'error')
|
|
1184
|
+
.map(e => `- ${e.text}`)
|
|
1185
|
+
.slice(-5)
|
|
1186
|
+
.join('\n');
|
|
1187
|
+
|
|
1188
|
+
brief = [
|
|
1189
|
+
`Agent ${agent.name} (${agent.role}) completed.`,
|
|
1190
|
+
agent.prompt ? `Task: ${agent.prompt.slice(0, 300)}` : '',
|
|
1191
|
+
files.length > 0 ? `\nFiles modified:\n${files.slice(0, 15).map(f => '- ' + f).join('\n')}` : '',
|
|
1192
|
+
toolSummary ? `\nRecent actions:\n${toolSummary}` : '',
|
|
1193
|
+
errorSummary ? `\nErrors encountered:\n${errorSummary}` : '',
|
|
1194
|
+
].filter(Boolean).join('\n');
|
|
1195
|
+
}
|
|
1180
1196
|
|
|
1181
1197
|
this.daemon.memory.appendHandoffBrief(agent.role, {
|
|
1182
1198
|
timestamp: new Date().toISOString(),
|
|
@@ -1184,7 +1200,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1184
1200
|
reason: 'completed',
|
|
1185
1201
|
oldTokens: agentData?.tokensUsed || 0,
|
|
1186
1202
|
contextUsage: agentData?.contextUsage || 0,
|
|
1187
|
-
brief: brief.slice(0, 4000),
|
|
1203
|
+
brief: (typeof brief === 'string' ? brief : '').slice(0, 4000),
|
|
1188
1204
|
}, agent.workingDir, agent.teamId);
|
|
1189
1205
|
} catch { /* best-effort */ }
|
|
1190
1206
|
}
|
|
@@ -93,14 +93,16 @@ export class LocalProvider extends Provider {
|
|
|
93
93
|
|
|
94
94
|
static _hasOllama() {
|
|
95
95
|
try {
|
|
96
|
-
|
|
96
|
+
const cmd = process.platform === 'win32' ? 'where ollama' : 'which ollama';
|
|
97
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
97
98
|
return true;
|
|
98
99
|
} catch { return false; }
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
static _hasLlamaServer() {
|
|
102
103
|
try {
|
|
103
|
-
|
|
104
|
+
const cmd = process.platform === 'win32' ? 'where llama-server' : 'which llama-server';
|
|
105
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
104
106
|
return true;
|
|
105
107
|
} catch { return false; }
|
|
106
108
|
}
|
|
@@ -52,7 +52,8 @@ export class OllamaProvider extends Provider {
|
|
|
52
52
|
|
|
53
53
|
static isInstalled() {
|
|
54
54
|
try {
|
|
55
|
-
|
|
55
|
+
const cmd = process.platform === 'win32' ? 'where ollama' : 'which ollama';
|
|
56
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
56
57
|
return true;
|
|
57
58
|
} catch {
|
|
58
59
|
return false;
|
|
@@ -67,6 +68,9 @@ export class OllamaProvider extends Provider {
|
|
|
67
68
|
if (platform === 'linux') {
|
|
68
69
|
return { command: 'curl -fsSL https://ollama.ai/install.sh | sh', platform: 'Linux' };
|
|
69
70
|
}
|
|
71
|
+
if (platform === 'win32') {
|
|
72
|
+
return { command: 'winget install Ollama.Ollama', alt: 'Or download from https://ollama.ai/download', platform: 'Windows' };
|
|
73
|
+
}
|
|
70
74
|
return { command: 'Download from https://ollama.ai/download', platform: 'other' };
|
|
71
75
|
}
|
|
72
76
|
|
|
@@ -98,6 +102,22 @@ export class OllamaProvider extends Provider {
|
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
}
|
|
105
|
+
if (platform === 'win32') {
|
|
106
|
+
try {
|
|
107
|
+
let cmd = 'ollama';
|
|
108
|
+
try {
|
|
109
|
+
execSync('where ollama', { stdio: 'ignore' });
|
|
110
|
+
} catch {
|
|
111
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
112
|
+
const fallback = localAppData + '\\Programs\\Ollama\\ollama.exe';
|
|
113
|
+
cmd = fallback;
|
|
114
|
+
}
|
|
115
|
+
execFile(cmd, ['serve'], { stdio: 'ignore', detached: true, shell: true }).unref();
|
|
116
|
+
return { started: true, method: 'ollama serve' };
|
|
117
|
+
} catch {
|
|
118
|
+
return { started: false, command: 'ollama serve' };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
101
121
|
// Linux / other
|
|
102
122
|
try {
|
|
103
123
|
execFile('ollama', ['serve'], { stdio: 'ignore', detached: true }).unref();
|
|
@@ -109,6 +129,14 @@ export class OllamaProvider extends Provider {
|
|
|
109
129
|
|
|
110
130
|
static stopServer() {
|
|
111
131
|
const platform = process.platform;
|
|
132
|
+
if (platform === 'win32') {
|
|
133
|
+
try {
|
|
134
|
+
execSync('taskkill /IM ollama.exe /F', { stdio: 'ignore', timeout: 5000 });
|
|
135
|
+
return { stopped: true, method: 'taskkill' };
|
|
136
|
+
} catch {
|
|
137
|
+
return { stopped: false };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
112
140
|
if (platform === 'darwin') {
|
|
113
141
|
try {
|
|
114
142
|
execSync('brew services stop ollama', { stdio: 'ignore', timeout: 10000 });
|
|
@@ -135,7 +163,11 @@ export class OllamaProvider extends Provider {
|
|
|
135
163
|
minRAM: 4,
|
|
136
164
|
recommendedRAM: 16,
|
|
137
165
|
gpuRecommended: true,
|
|
138
|
-
note:
|
|
166
|
+
note: process.platform === 'win32'
|
|
167
|
+
? 'NVIDIA or AMD GPUs recommended. Ensure GPU drivers are up to date.'
|
|
168
|
+
: process.platform === 'darwin'
|
|
169
|
+
? 'Apple Silicon Macs use unified memory — all RAM is GPU RAM.'
|
|
170
|
+
: 'NVIDIA GPUs recommended on Linux.',
|
|
139
171
|
};
|
|
140
172
|
}
|
|
141
173
|
|
|
@@ -151,7 +183,7 @@ export class OllamaProvider extends Provider {
|
|
|
151
183
|
let gpu = null;
|
|
152
184
|
if (isAppleSilicon) {
|
|
153
185
|
gpu = { type: 'apple-silicon', name: cpuModel.replace(/Apple /g, ''), vram: totalRamGb, note: 'Unified memory — all RAM available to GPU' };
|
|
154
|
-
} else if (platform === 'linux') {
|
|
186
|
+
} else if (platform === 'linux' || platform === 'win32') {
|
|
155
187
|
try {
|
|
156
188
|
const out = execSync('nvidia-smi --query-gpu=name,memory.total --format=csv,noheader,nounits', { encoding: 'utf8', timeout: 5000 });
|
|
157
189
|
const [name, vram] = out.trim().split(', ');
|
|
@@ -211,7 +211,7 @@ export class TunnelManager {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
async connect(id) {
|
|
214
|
+
async connect(id, opts = {}) {
|
|
215
215
|
const config = this.saved.get(id);
|
|
216
216
|
if (!config) throw new Error(`Remote ${id} not found`);
|
|
217
217
|
|
|
@@ -220,7 +220,14 @@ export class TunnelManager {
|
|
|
220
220
|
return { localPort: existing.localPort, pid: existing.pid };
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
this.daemon.broadcast({ type: 'tunnel.status', data: { id, step: 'testing' } });
|
|
224
|
+
|
|
225
|
+
let testResult;
|
|
226
|
+
if (opts.skipTest && opts.testResult) {
|
|
227
|
+
testResult = opts.testResult;
|
|
228
|
+
} else {
|
|
229
|
+
testResult = await this.test(id);
|
|
230
|
+
}
|
|
224
231
|
if (!testResult.reachable) {
|
|
225
232
|
throw new Error(testResult.error || 'Host unreachable');
|
|
226
233
|
}
|
|
@@ -233,6 +240,8 @@ export class TunnelManager {
|
|
|
233
240
|
await this.autoStart(id);
|
|
234
241
|
}
|
|
235
242
|
|
|
243
|
+
this.daemon.broadcast({ type: 'tunnel.status', data: { id, step: 'connecting' } });
|
|
244
|
+
|
|
236
245
|
const localPort = await this._findAvailablePort();
|
|
237
246
|
const target = `${config.user}@${config.host}`;
|
|
238
247
|
const keyArgs = config.sshKeyPath ? ['-i', config.sshKeyPath] : [];
|
|
@@ -257,13 +266,16 @@ export class TunnelManager {
|
|
|
257
266
|
let stderrBuf = '';
|
|
258
267
|
tunnel.stderr.on('data', (chunk) => { stderrBuf += chunk.toString(); });
|
|
259
268
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
let tunnelUp = false;
|
|
270
|
+
for (let elapsed = 0; elapsed < 8000; elapsed += 500) {
|
|
271
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
272
|
+
if (tunnel.exitCode !== null) {
|
|
273
|
+
throw new Error(`Tunnel failed to start: ${stderrBuf.trim() || 'unknown error'}`);
|
|
274
|
+
}
|
|
275
|
+
tunnelUp = await this._isPortInUse(localPort);
|
|
276
|
+
if (tunnelUp) break;
|
|
264
277
|
}
|
|
265
278
|
|
|
266
|
-
const tunnelUp = await this._isPortInUse(localPort);
|
|
267
279
|
if (!tunnelUp) {
|
|
268
280
|
try { process.kill(tunnel.pid); } catch { /* ignore */ }
|
|
269
281
|
throw new Error('Tunnel started but port forward not active');
|