groove-dev 0.27.118 → 0.27.119
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/moe-training/client/trajectory-capture.js +7 -0
- package/moe-training/client/transmission-queue.js +6 -0
- 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 +13 -4
- package/node_modules/@groove-dev/daemon/src/index.js +4 -0
- package/node_modules/@groove-dev/daemon/src/teams.js +70 -39
- package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-BunEIVjD.js → index-BxPCaxlC.js} +131 -131
- package/node_modules/@groove-dev/gui/dist/assets/{index-DdN9RVnC.css → index-DT6Jbf_q.css} +1 -1
- 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/app.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +68 -2
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +80 -3
- package/node_modules/@groove-dev/gui/src/components/teams/team-removal-dialog.jsx +7 -3
- package/node_modules/@groove-dev/gui/src/components/ui/data-sharing-modal.jsx +151 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +29 -5
- package/node_modules/@groove-dev/gui/src/views/agents.jsx +47 -11
- package/node_modules/@groove-dev/gui/src/views/teams.jsx +4 -0
- package/node_modules/moe-training/client/trajectory-capture.js +7 -0
- package/node_modules/moe-training/client/transmission-queue.js +6 -0
- 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 +13 -4
- package/packages/daemon/src/index.js +4 -0
- package/packages/daemon/src/teams.js +70 -39
- package/packages/daemon/src/validate.js +10 -0
- package/packages/gui/dist/assets/{index-BunEIVjD.js → index-BxPCaxlC.js} +131 -131
- package/packages/gui/dist/assets/{index-DdN9RVnC.css → index-DT6Jbf_q.css} +1 -1
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.jsx +2 -0
- package/packages/gui/src/components/agents/agent-file-tree.jsx +68 -2
- package/packages/gui/src/components/editor/file-tree.jsx +80 -3
- package/packages/gui/src/components/teams/team-removal-dialog.jsx +7 -3
- package/packages/gui/src/components/ui/data-sharing-modal.jsx +151 -0
- package/packages/gui/src/stores/groove.js +29 -5
- package/packages/gui/src/views/agents.jsx +47 -11
- package/packages/gui/src/views/teams.jsx +4 -0
|
@@ -34,6 +34,12 @@ export class TrajectoryCapture {
|
|
|
34
34
|
this._offlineRetryTimer = null;
|
|
35
35
|
this._contexts = new Map();
|
|
36
36
|
this._shutdown = false;
|
|
37
|
+
this._onEnvelopeSent = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set onEnvelopeSent(fn) {
|
|
41
|
+
this._onEnvelopeSent = typeof fn === 'function' ? fn : null;
|
|
42
|
+
if (this._transmissionQueue) this._transmissionQueue.onSent = this._onEnvelopeSent;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
async init() {
|
|
@@ -46,6 +52,7 @@ export class TrajectoryCapture {
|
|
|
46
52
|
this._scrubber = new PIIScrubber();
|
|
47
53
|
this._attestation = new SessionAttestation(this._centralCommandUrl);
|
|
48
54
|
this._transmissionQueue = new TransmissionQueue(this._centralCommandUrl);
|
|
55
|
+
if (this._onEnvelopeSent) this._transmissionQueue.onSent = this._onEnvelopeSent;
|
|
49
56
|
this._transmissionQueue.start();
|
|
50
57
|
this._domainTagger = new DomainTagger();
|
|
51
58
|
await this._domainTagger.init();
|
|
@@ -10,6 +10,7 @@ export class TransmissionQueue {
|
|
|
10
10
|
this._offlineQueue = [];
|
|
11
11
|
this._running = false;
|
|
12
12
|
this._drainPromise = null;
|
|
13
|
+
this._onSent = null;
|
|
13
14
|
|
|
14
15
|
if (process.env.NODE_ENV === 'production' && !centralCommandUrl.startsWith('https://')) {
|
|
15
16
|
console.warn('[TransmissionQueue] WARNING: centralCommandUrl does not use HTTPS in production');
|
|
@@ -20,6 +21,10 @@ export class TransmissionQueue {
|
|
|
20
21
|
return this._offlineQueue.length;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
set onSent(fn) {
|
|
25
|
+
this._onSent = typeof fn === 'function' ? fn : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
enqueue(signedEnvelope) {
|
|
24
29
|
if (this._queue.length >= this._maxSize) return;
|
|
25
30
|
if (signedEnvelope?.attestation?.session_hmac === 'OFFLINE') {
|
|
@@ -86,6 +91,7 @@ export class TransmissionQueue {
|
|
|
86
91
|
});
|
|
87
92
|
if (res.ok) {
|
|
88
93
|
success = true;
|
|
94
|
+
if (this._onSent) this._onSent(envelope);
|
|
89
95
|
break;
|
|
90
96
|
}
|
|
91
97
|
} catch {
|
|
@@ -16,7 +16,7 @@ import { OllamaProvider } from './providers/ollama.js';
|
|
|
16
16
|
import { ClaudeCodeProvider } from './providers/claude-code.js';
|
|
17
17
|
import { supportsSignalFlag, compareSemver, parseSemver } from './providers/groove-network.js';
|
|
18
18
|
import { ConsentManager } from '../../../moe-training/client/index.js';
|
|
19
|
-
import { validateAgentConfig, validateReasoningEffort, validateVerbosity } from './validate.js';
|
|
19
|
+
import { validateAgentConfig, validateReasoningEffort, validateVerbosity, validateTeamMode } from './validate.js';
|
|
20
20
|
import { ROLE_INTEGRATIONS, wrapWithRoleReminder } from './process.js';
|
|
21
21
|
|
|
22
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1092,8 +1092,8 @@ export function createApi(app, daemon) {
|
|
|
1092
1092
|
|
|
1093
1093
|
app.post('/api/teams', (req, res) => {
|
|
1094
1094
|
try {
|
|
1095
|
-
const team = daemon.teams.create(req.body.name, req.body.
|
|
1096
|
-
daemon.audit.log('team.create', { id: team.id, name: team.name, workingDir: team.workingDir });
|
|
1095
|
+
const team = daemon.teams.create(req.body.name, { mode: req.body.mode });
|
|
1096
|
+
daemon.audit.log('team.create', { id: team.id, name: team.name, mode: team.mode, workingDir: team.workingDir });
|
|
1097
1097
|
res.status(201).json(team);
|
|
1098
1098
|
} catch (err) {
|
|
1099
1099
|
res.status(400).json({ error: err.message });
|
|
@@ -3447,9 +3447,17 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
3447
3447
|
}
|
|
3448
3448
|
const defaultTeamId = launchTeamId || daemon.teams.getDefault()?.id || null;
|
|
3449
3449
|
|
|
3450
|
+
// Determine team build mode
|
|
3451
|
+
let launchMode;
|
|
3452
|
+
try { launchMode = validateTeamMode(req.body?.mode || raw.mode); } catch { launchMode = 'sandbox'; }
|
|
3453
|
+
|
|
3450
3454
|
// If planner specified a project directory, create it and use it as workingDir
|
|
3455
|
+
// Production mode: always use projectDir directly, skip subdirectory creation
|
|
3451
3456
|
let projectWorkingDir = baseDir;
|
|
3452
|
-
if (
|
|
3457
|
+
if (launchMode === 'production') {
|
|
3458
|
+
projectWorkingDir = daemon.projectDir;
|
|
3459
|
+
console.log(`[Groove] Production mode — working in project root: ${projectWorkingDir}`);
|
|
3460
|
+
} else if (projectDir) {
|
|
3453
3461
|
// Sanitize: kebab-case, no path traversal
|
|
3454
3462
|
const safeName = String(projectDir).replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64);
|
|
3455
3463
|
projectWorkingDir = resolve(baseDir, safeName);
|
|
@@ -4990,6 +4998,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4990
4998
|
'port', 'journalistInterval', 'rotationThreshold', 'autoRotation',
|
|
4991
4999
|
'qcThreshold', 'maxAgents', 'defaultProvider', 'defaultWorkingDir',
|
|
4992
5000
|
'onboardingDismissed', 'defaultModel', 'defaultChatProvider', 'defaultChatModel',
|
|
5001
|
+
'dataSharingDismissed',
|
|
4993
5002
|
];
|
|
4994
5003
|
for (const key of Object.keys(req.body)) {
|
|
4995
5004
|
if (!ALLOWED_KEYS.includes(key)) {
|
|
@@ -686,6 +686,10 @@ export class Daemon {
|
|
|
686
686
|
centralCommandUrl: process.env.GROOVE_CENTRAL_URL || 'https://api.groovedev.ai',
|
|
687
687
|
grooveVersion: version,
|
|
688
688
|
});
|
|
689
|
+
this.trajectoryCapture.onEnvelopeSent = () => {
|
|
690
|
+
const count = (this.state.get('training_envelopes_sent') || 0) + 1;
|
|
691
|
+
this.state.set('training_envelopes_sent', count);
|
|
692
|
+
};
|
|
689
693
|
this.trajectoryCapture.init();
|
|
690
694
|
} catch (e) {
|
|
691
695
|
// Training capture is never critical
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, rmSync, readdirSync, cpSync } from 'fs';
|
|
5
|
-
import { resolve
|
|
5
|
+
import { resolve } from 'path';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
|
-
import { validateTeamName } from './validate.js';
|
|
7
|
+
import { validateTeamName, validateTeamMode } from './validate.js';
|
|
8
8
|
|
|
9
9
|
function slugify(name) {
|
|
10
10
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 64) || 'team';
|
|
@@ -44,6 +44,7 @@ export class Teams {
|
|
|
44
44
|
id,
|
|
45
45
|
name: 'Default',
|
|
46
46
|
isDefault: true,
|
|
47
|
+
mode: 'sandbox',
|
|
47
48
|
workingDir: defaultDir,
|
|
48
49
|
createdAt: new Date().toISOString(),
|
|
49
50
|
};
|
|
@@ -64,19 +65,25 @@ export class Teams {
|
|
|
64
65
|
/**
|
|
65
66
|
* Create a team with an auto-managed working directory.
|
|
66
67
|
*/
|
|
67
|
-
create(name) {
|
|
68
|
+
create(name, { mode = 'sandbox' } = {}) {
|
|
68
69
|
validateTeamName(name);
|
|
70
|
+
mode = validateTeamMode(mode);
|
|
69
71
|
const id = randomUUID().slice(0, 8);
|
|
70
|
-
const dirName = slugify(name);
|
|
71
|
-
const workingDir = resolve(this.daemon.projectDir, dirName);
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
let workingDir;
|
|
74
|
+
if (mode === 'production') {
|
|
75
|
+
workingDir = this.daemon.projectDir;
|
|
76
|
+
} else {
|
|
77
|
+
const dirName = slugify(name);
|
|
78
|
+
workingDir = resolve(this.daemon.projectDir, dirName);
|
|
79
|
+
mkdirSync(workingDir, { recursive: true });
|
|
80
|
+
}
|
|
75
81
|
|
|
76
82
|
const team = {
|
|
77
83
|
id,
|
|
78
84
|
name,
|
|
79
85
|
isDefault: false,
|
|
86
|
+
mode,
|
|
80
87
|
workingDir,
|
|
81
88
|
createdAt: new Date().toISOString(),
|
|
82
89
|
};
|
|
@@ -110,8 +117,9 @@ export class Teams {
|
|
|
110
117
|
const oldName = team.name;
|
|
111
118
|
team.name = name;
|
|
112
119
|
|
|
120
|
+
// Production teams use the project root — never rename directories
|
|
113
121
|
// Rename the directory if it was auto-managed (under projectDir)
|
|
114
|
-
if (team.workingDir && !team.isDefault) {
|
|
122
|
+
if (team.workingDir && !team.isDefault && team.mode !== 'production') {
|
|
115
123
|
const newDirName = slugify(name);
|
|
116
124
|
const newWorkingDir = resolve(this.daemon.projectDir, newDirName);
|
|
117
125
|
const oldWorkingDir = team.workingDir;
|
|
@@ -150,18 +158,30 @@ export class Teams {
|
|
|
150
158
|
|
|
151
159
|
const agents = this._killAndRemoveAgents(id);
|
|
152
160
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
161
|
+
try {
|
|
162
|
+
const archiveDir = resolve(this.daemon.grooveDir, 'archived-teams');
|
|
163
|
+
mkdirSync(archiveDir, { recursive: true });
|
|
164
|
+
const slug = slugify(team.name);
|
|
165
|
+
const archiveName = `${slug}-${Date.now()}`;
|
|
166
|
+
const archivePath = resolve(archiveDir, archiveName);
|
|
167
|
+
|
|
168
|
+
if (team.mode === 'production') {
|
|
169
|
+
// Production teams: metadata-only archive (no directory move)
|
|
170
|
+
mkdirSync(archivePath, { recursive: true });
|
|
171
|
+
const metadata = {
|
|
172
|
+
originalName: team.name,
|
|
173
|
+
originalId: team.id,
|
|
174
|
+
mode: team.mode,
|
|
175
|
+
deletedAt: new Date().toISOString(),
|
|
176
|
+
agentCount: agents.length,
|
|
177
|
+
originalWorkingDir: team.workingDir,
|
|
178
|
+
};
|
|
179
|
+
writeFileSync(resolve(archivePath, 'metadata.json'), JSON.stringify(metadata, null, 2));
|
|
180
|
+
} else if (
|
|
181
|
+
team.workingDir &&
|
|
182
|
+
team.workingDir !== this.daemon.projectDir &&
|
|
183
|
+
existsSync(team.workingDir)
|
|
184
|
+
) {
|
|
165
185
|
try {
|
|
166
186
|
renameSync(team.workingDir, archivePath);
|
|
167
187
|
} catch (err) {
|
|
@@ -176,14 +196,15 @@ export class Teams {
|
|
|
176
196
|
const metadata = {
|
|
177
197
|
originalName: team.name,
|
|
178
198
|
originalId: team.id,
|
|
199
|
+
mode: team.mode || 'sandbox',
|
|
179
200
|
deletedAt: new Date().toISOString(),
|
|
180
201
|
agentCount: agents.length,
|
|
181
202
|
originalWorkingDir: team.workingDir,
|
|
182
203
|
};
|
|
183
204
|
writeFileSync(resolve(archivePath, 'metadata.json'), JSON.stringify(metadata, null, 2));
|
|
184
|
-
} catch (err) {
|
|
185
|
-
console.log(`[Groove:Teams] Failed to archive directory: ${err.message}`);
|
|
186
205
|
}
|
|
206
|
+
} catch (err) {
|
|
207
|
+
console.log(`[Groove:Teams] Failed to archive directory: ${err.message}`);
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
this._removeTeamAndCleanup(team, id);
|
|
@@ -273,32 +294,42 @@ export class Teams {
|
|
|
273
294
|
try { meta = JSON.parse(readFileSync(metaPath, 'utf8')); } catch { /* use defaults */ }
|
|
274
295
|
|
|
275
296
|
const name = meta.originalName || archivedId;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
297
|
+
const mode = meta.mode || 'sandbox';
|
|
298
|
+
|
|
299
|
+
let workingDir;
|
|
300
|
+
if (mode === 'production') {
|
|
301
|
+
workingDir = this.daemon.projectDir;
|
|
302
|
+
// Production archive is metadata-only — just remove the archive directory
|
|
303
|
+
try { rmSync(archivePath, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
304
|
+
} else {
|
|
305
|
+
workingDir = meta.originalWorkingDir || resolve(this.daemon.projectDir, slugify(name));
|
|
306
|
+
|
|
307
|
+
if (existsSync(workingDir)) {
|
|
308
|
+
workingDir = resolve(this.daemon.projectDir, `${slugify(name)}-${Date.now()}`);
|
|
309
|
+
}
|
|
281
310
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
try {
|
|
312
|
+
renameSync(archivePath, workingDir);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (err.code === 'EXDEV') {
|
|
315
|
+
cpSync(archivePath, workingDir, { recursive: true });
|
|
316
|
+
rmSync(archivePath, { recursive: true, force: true });
|
|
317
|
+
} else {
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
290
320
|
}
|
|
291
|
-
}
|
|
292
321
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
322
|
+
// Remove the metadata file from the restored directory
|
|
323
|
+
const restoredMetaPath = resolve(workingDir, 'metadata.json');
|
|
324
|
+
try { rmSync(restoredMetaPath); } catch { /* may not exist */ }
|
|
325
|
+
}
|
|
296
326
|
|
|
297
327
|
const id = randomUUID().slice(0, 8);
|
|
298
328
|
const team = {
|
|
299
329
|
id,
|
|
300
330
|
name,
|
|
301
331
|
isDefault: false,
|
|
332
|
+
mode,
|
|
302
333
|
workingDir,
|
|
303
334
|
createdAt: new Date().toISOString(),
|
|
304
335
|
};
|
|
@@ -239,6 +239,16 @@ export function validateVerbosity(value) {
|
|
|
239
239
|
return value;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
const VALID_TEAM_MODES = ['sandbox', 'production'];
|
|
243
|
+
|
|
244
|
+
export function validateTeamMode(mode) {
|
|
245
|
+
if (!mode) return 'sandbox';
|
|
246
|
+
if (!VALID_TEAM_MODES.includes(mode)) {
|
|
247
|
+
throw new Error(`Invalid team mode: must be one of ${VALID_TEAM_MODES.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
return mode;
|
|
250
|
+
}
|
|
251
|
+
|
|
242
252
|
export function escapeMd(text) {
|
|
243
253
|
if (!text) return '';
|
|
244
254
|
// Escape markdown special chars that could break table rendering or inject formatting.
|