groove-dev 0.27.75 → 0.27.78

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.
Files changed (97) hide show
  1. package/MOE_TRAINING_PIPELINE.md +216 -12
  2. package/moe-training/DEPLOY_CENTRAL_COMMAND.md +413 -0
  3. package/moe-training/client/consent.js +96 -0
  4. package/moe-training/client/envelope-builder.js +56 -0
  5. package/moe-training/client/index.js +10 -0
  6. package/moe-training/client/parsers/claude-code.js +110 -0
  7. package/moe-training/client/parsers/codex.js +80 -0
  8. package/moe-training/client/parsers/gemini.js +80 -0
  9. package/moe-training/client/parsers/grok.js +16 -0
  10. package/moe-training/client/parsers/index.js +20 -0
  11. package/moe-training/client/scrubber.js +126 -0
  12. package/moe-training/client/session-attestation.js +114 -0
  13. package/moe-training/client/step-classifier.js +51 -0
  14. package/moe-training/client/trajectory-capture.js +227 -0
  15. package/moe-training/client/transmission-queue.js +93 -0
  16. package/moe-training/package-lock.json +1266 -0
  17. package/moe-training/package.json +20 -0
  18. package/moe-training/server/enrichment.js +24 -0
  19. package/moe-training/server/index.js +119 -0
  20. package/moe-training/server/ledger.js +110 -0
  21. package/moe-training/server/routes/ingest.js +96 -0
  22. package/moe-training/server/routes/sessions.js +43 -0
  23. package/moe-training/server/routes/stats.js +31 -0
  24. package/moe-training/server/scoring.js +63 -0
  25. package/moe-training/server/session-registry.js +156 -0
  26. package/moe-training/server/stats.js +129 -0
  27. package/moe-training/server/stitcher.js +69 -0
  28. package/moe-training/server/storage.js +147 -0
  29. package/moe-training/server/verifier.js +102 -0
  30. package/moe-training/shared/constants.js +30 -0
  31. package/moe-training/shared/crypto.js +45 -0
  32. package/moe-training/shared/envelope-schema.js +220 -0
  33. package/moe-training/test/client/consent.test.js +121 -0
  34. package/moe-training/test/client/envelope-builder.test.js +107 -0
  35. package/moe-training/test/client/parsers/claude-code.test.js +119 -0
  36. package/moe-training/test/client/parsers/codex.test.js +83 -0
  37. package/moe-training/test/client/parsers/gemini.test.js +99 -0
  38. package/moe-training/test/client/scrubber.test.js +133 -0
  39. package/moe-training/test/client/session-attestation-security.test.js +95 -0
  40. package/moe-training/test/client/step-classifier.test.js +88 -0
  41. package/moe-training/test/integration/handshake.test.js +260 -0
  42. package/moe-training/test/server/ingest-security.test.js +166 -0
  43. package/moe-training/test/server/ledger.test.js +131 -0
  44. package/moe-training/test/server/scoring.test.js +242 -0
  45. package/moe-training/test/server/session-registry.test.js +125 -0
  46. package/moe-training/test/server/stitcher.test.js +157 -0
  47. package/moe-training/test/server/verifier.test.js +232 -0
  48. package/moe-training/test/shared/crypto.test.js +87 -0
  49. package/moe-training/test/shared/envelope-schema.test.js +351 -0
  50. package/node_modules/@groove-dev/cli/package.json +1 -1
  51. package/node_modules/@groove-dev/daemon/package.json +1 -1
  52. package/node_modules/@groove-dev/daemon/src/agent-loop.js +48 -5
  53. package/node_modules/@groove-dev/daemon/src/api.js +77 -0
  54. package/node_modules/@groove-dev/daemon/src/index.js +61 -0
  55. package/node_modules/@groove-dev/daemon/src/journalist.js +64 -21
  56. package/node_modules/@groove-dev/daemon/src/preview.js +14 -0
  57. package/node_modules/@groove-dev/daemon/src/process.js +203 -1
  58. package/node_modules/@groove-dev/daemon/src/providers/grok.js +15 -0
  59. package/node_modules/@groove-dev/daemon/src/state.js +20 -1
  60. package/node_modules/@groove-dev/gui/dist/assets/{index-CAT9SCJi.js → index-BJgEJ9lZ.js} +1700 -1704
  61. package/node_modules/@groove-dev/gui/dist/assets/index-kbR5tOHu.css +1 -0
  62. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  63. package/node_modules/@groove-dev/gui/package.json +1 -1
  64. package/node_modules/@groove-dev/gui/src/app.css +12 -0
  65. package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +32 -27
  66. package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +26 -24
  67. package/node_modules/@groove-dev/gui/src/components/preview/preview-toolbar.jsx +34 -6
  68. package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +19 -4
  69. package/node_modules/@groove-dev/gui/src/components/preview/screenshot-overlay.jsx +91 -57
  70. package/node_modules/@groove-dev/gui/src/stores/groove.js +32 -0
  71. package/node_modules/@groove-dev/gui/src/views/settings.jsx +167 -1
  72. package/package.json +1 -1
  73. package/packages/cli/package.json +1 -1
  74. package/packages/daemon/package.json +1 -1
  75. package/packages/daemon/src/agent-loop.js +48 -5
  76. package/packages/daemon/src/api.js +77 -0
  77. package/packages/daemon/src/index.js +61 -0
  78. package/packages/daemon/src/journalist.js +64 -21
  79. package/packages/daemon/src/preview.js +14 -0
  80. package/packages/daemon/src/process.js +203 -1
  81. package/packages/daemon/src/providers/grok.js +15 -0
  82. package/packages/daemon/src/state.js +20 -1
  83. package/packages/gui/dist/assets/{index-CAT9SCJi.js → index-BJgEJ9lZ.js} +1700 -1704
  84. package/packages/gui/dist/assets/index-kbR5tOHu.css +1 -0
  85. package/packages/gui/dist/index.html +2 -2
  86. package/packages/gui/package.json +1 -1
  87. package/packages/gui/src/app.css +12 -0
  88. package/packages/gui/src/components/chat/chat-input.jsx +32 -27
  89. package/packages/gui/src/components/chat/chat-messages.jsx +26 -24
  90. package/packages/gui/src/components/preview/preview-toolbar.jsx +34 -6
  91. package/packages/gui/src/components/preview/preview-workspace.jsx +19 -4
  92. package/packages/gui/src/components/preview/screenshot-overlay.jsx +91 -57
  93. package/packages/gui/src/stores/groove.js +32 -0
  94. package/packages/gui/src/views/settings.jsx +167 -1
  95. package/welcome.png +0 -0
  96. package/node_modules/@groove-dev/gui/dist/assets/index-CVzz6zyb.css +0 -1
  97. package/packages/gui/dist/assets/index-CVzz6zyb.css +0 -1
@@ -0,0 +1,114 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { platform, arch, cpus, totalmem, hostname, release, endianness } from 'node:os';
4
+ import { createHash } from 'node:crypto';
5
+ import { generateECDHKeypair, deriveSharedSecret, signEnvelope, computeAppHash } from '../shared/crypto.js';
6
+
7
+ export class SessionAttestation {
8
+ constructor(centralCommandUrl) {
9
+ this._centralCommandUrl = centralCommandUrl;
10
+ this._sessions = new Map();
11
+ }
12
+
13
+ async openSession(sessionId, metadata) {
14
+ const keypair = generateECDHKeypair();
15
+ let appVersionHash = '';
16
+ try {
17
+ appVersionHash = computeAppHash(new URL(import.meta.url).pathname);
18
+ } catch {
19
+ appVersionHash = 'unknown';
20
+ }
21
+
22
+ const body = {
23
+ session_id: sessionId,
24
+ public_key: keypair.publicKey,
25
+ app_version_hash: appVersionHash,
26
+ provider: metadata.provider,
27
+ model: metadata.model_engine,
28
+ machine_fingerprint: SessionAttestation.getMachineFingerprint(),
29
+ groove_version: metadata.groove_version,
30
+ };
31
+
32
+ try {
33
+ const res = await fetch(`${this._centralCommandUrl}/v1/sessions/open`, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify(body),
37
+ signal: AbortSignal.timeout(10_000),
38
+ });
39
+
40
+ if (!res.ok) {
41
+ this._sessions.set(sessionId, { keypair, sharedSecret: null, sequence: 0, appVersionHash, offline: true });
42
+ return false;
43
+ }
44
+
45
+ const data = await res.json();
46
+ const sharedSecret = deriveSharedSecret(keypair.privateKey, data.server_public_key);
47
+ this._sessions.set(sessionId, { keypair, sharedSecret, sequence: 0, appVersionHash, offline: false });
48
+ return true;
49
+ } catch {
50
+ this._sessions.set(sessionId, { keypair, sharedSecret: null, sequence: 0, appVersionHash, offline: true });
51
+ return false;
52
+ }
53
+ }
54
+
55
+ signEnvelope(sessionId, envelope) {
56
+ const session = this._sessions.get(sessionId);
57
+ if (!session) return envelope;
58
+
59
+ if (session.offline || !session.sharedSecret) {
60
+ envelope.attestation = {
61
+ session_hmac: 'OFFLINE',
62
+ sequence: session.sequence++,
63
+ app_version_hash: session.appVersionHash,
64
+ };
65
+ return envelope;
66
+ }
67
+
68
+ const forSigning = { ...envelope };
69
+ delete forSigning.attestation;
70
+ const envelopeBytes = JSON.stringify(forSigning);
71
+ const hmac = signEnvelope(session.sharedSecret, envelopeBytes, session.sequence);
72
+ envelope.attestation = {
73
+ session_hmac: hmac,
74
+ sequence: session.sequence++,
75
+ app_version_hash: session.appVersionHash,
76
+ };
77
+ return envelope;
78
+ }
79
+
80
+ async closeSession(sessionId) {
81
+ const session = this._sessions.get(sessionId);
82
+ if (!session || session.offline) {
83
+ this._sessions.delete(sessionId);
84
+ return;
85
+ }
86
+
87
+ try {
88
+ await fetch(`${this._centralCommandUrl}/v1/sessions/close`, {
89
+ method: 'POST',
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: JSON.stringify({ session_id: sessionId }),
92
+ signal: AbortSignal.timeout(10_000),
93
+ });
94
+ } catch {
95
+ // fail silent
96
+ }
97
+
98
+ this._sessions.delete(sessionId);
99
+ }
100
+
101
+ static getMachineFingerprint() {
102
+ const signals = [
103
+ platform(),
104
+ arch(),
105
+ cpus()[0]?.model || '',
106
+ String(totalmem()),
107
+ hostname(),
108
+ String(cpus().length),
109
+ release(),
110
+ endianness(),
111
+ ];
112
+ return createHash('sha256').update(signals.join('|')).digest('hex');
113
+ }
114
+ }
@@ -0,0 +1,51 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ export class StepClassifier {
4
+ constructor() {
5
+ this.hasAgentActed = false;
6
+ }
7
+
8
+ classifyUserMessage(text) {
9
+ if (!this.hasAgentActed) {
10
+ return null;
11
+ }
12
+ return {
13
+ type: 'correction',
14
+ content: text,
15
+ source: 'user',
16
+ };
17
+ }
18
+
19
+ classifyCoordinationEvent(event) {
20
+ return {
21
+ type: 'coordination',
22
+ coordination_id: event.coordination_id || event.id || '',
23
+ direction: event.direction || (event.source ? 'inbound' : 'outbound'),
24
+ target_agent: event.target_agent || event.targetAgent || '',
25
+ protocol: event.protocol || 'knock',
26
+ content: event.content || event.message || '',
27
+ };
28
+ }
29
+
30
+ onStep(step) {
31
+ if (step.type === 'action') {
32
+ this.hasAgentActed = true;
33
+ }
34
+ }
35
+
36
+ static detectErrorRecovery(steps) {
37
+ for (let i = 0; i < steps.length - 1; i++) {
38
+ if (steps[i].type === 'error') {
39
+ for (let j = i + 1; j < steps.length; j++) {
40
+ if (steps[j].type === 'resolution') return true;
41
+ if (steps[j].type === 'error') break;
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+
48
+ static countUserInterventions(steps) {
49
+ return steps.filter((s) => s.type === 'correction').length;
50
+ }
51
+ }
@@ -0,0 +1,227 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { randomUUID } from 'node:crypto';
4
+ import { ConsentManager } from './consent.js';
5
+ import { PIIScrubber } from './scrubber.js';
6
+ import { getParser } from './parsers/index.js';
7
+ import { StepClassifier } from './step-classifier.js';
8
+ import { EnvelopeBuilder } from './envelope-builder.js';
9
+ import { SessionAttestation } from './session-attestation.js';
10
+ import { TransmissionQueue } from './transmission-queue.js';
11
+ import { CHUNK_TIMEOUT_MS, CENTRAL_COMMAND_URL } from '../shared/constants.js';
12
+
13
+ export class TrajectoryCapture {
14
+ constructor(options = {}) {
15
+ this._centralCommandUrl = options.centralCommandUrl || CENTRAL_COMMAND_URL;
16
+ this._grooveVersion = options.grooveVersion || '0.0.0';
17
+ this._enabled = false;
18
+ this._scrubber = null;
19
+ this._attestation = null;
20
+ this._transmissionQueue = null;
21
+ this._contexts = new Map();
22
+ }
23
+
24
+ init() {
25
+ if (!ConsentManager.isCaptureEnabled()) {
26
+ this._enabled = false;
27
+ return;
28
+ }
29
+ this._enabled = true;
30
+ this._scrubber = new PIIScrubber();
31
+ this._attestation = new SessionAttestation(this._centralCommandUrl);
32
+ this._transmissionQueue = new TransmissionQueue(this._centralCommandUrl);
33
+ this._transmissionQueue.start();
34
+ }
35
+
36
+ async onAgentSpawn(agentId, provider, model, role, teamSize) {
37
+ if (!this._enabled) return;
38
+
39
+ const parser = getParser(provider);
40
+ if (!parser) return;
41
+
42
+ const sessionId = `sess_${randomUUID()}`;
43
+ const contributorId = ConsentManager.getOrCreateUserId();
44
+ const metadata = {
45
+ model_engine: model,
46
+ provider,
47
+ agent_role: role,
48
+ agent_id: agentId,
49
+ task_complexity: 'medium',
50
+ team_size: teamSize || 1,
51
+ session_quality: 0,
52
+ groove_version: this._grooveVersion,
53
+ };
54
+
55
+ const builder = new EnvelopeBuilder(sessionId, contributorId, metadata);
56
+ const classifier = new StepClassifier();
57
+ const startTime = Date.now();
58
+
59
+ const ctx = {
60
+ sessionId,
61
+ parser,
62
+ builder,
63
+ classifier,
64
+ metadata,
65
+ stepCount: 0,
66
+ chunkCount: 0,
67
+ totalTokens: 0,
68
+ errorsEncountered: 0,
69
+ errorsRecovered: 0,
70
+ filesModified: 0,
71
+ coordinationEvents: 0,
72
+ startTime,
73
+ chunkTimer: null,
74
+ allSteps: [],
75
+ };
76
+
77
+ ctx.chunkTimer = setInterval(() => {
78
+ this._flushContext(agentId);
79
+ }, CHUNK_TIMEOUT_MS);
80
+
81
+ this._contexts.set(agentId, ctx);
82
+
83
+ await this._attestation.openSession(sessionId, metadata);
84
+ }
85
+
86
+ onStdoutLine(agentId, jsonLine) {
87
+ if (!this._enabled) return;
88
+ const ctx = this._contexts.get(agentId);
89
+ if (!ctx) return;
90
+
91
+ let jsonEvent;
92
+ try {
93
+ jsonEvent = typeof jsonLine === 'string' ? JSON.parse(jsonLine) : jsonLine;
94
+ } catch {
95
+ return;
96
+ }
97
+
98
+ const parsed = ctx.parser.parseEvent(jsonEvent);
99
+ if (!parsed) return;
100
+
101
+ const events = Array.isArray(parsed) ? parsed : [parsed];
102
+ for (const event of events) {
103
+ this._processStep(agentId, ctx, event);
104
+ }
105
+
106
+ const tokens = ctx.parser.extractTokens(jsonEvent);
107
+ if (tokens) {
108
+ ctx.totalTokens += (tokens.input || 0) + (tokens.output || 0);
109
+ }
110
+ }
111
+
112
+ onUserMessage(agentId, text) {
113
+ if (!this._enabled) return;
114
+ const ctx = this._contexts.get(agentId);
115
+ if (!ctx) return;
116
+
117
+ const classified = ctx.classifier.classifyUserMessage(text);
118
+ if (!classified) return;
119
+
120
+ this._processStep(agentId, ctx, classified);
121
+ }
122
+
123
+ onCoordinationEvent(agentId, event) {
124
+ if (!this._enabled) return;
125
+ const ctx = this._contexts.get(agentId);
126
+ if (!ctx) return;
127
+
128
+ const classified = ctx.classifier.classifyCoordinationEvent(event);
129
+ ctx.coordinationEvents++;
130
+ this._processStep(agentId, ctx, classified);
131
+ }
132
+
133
+ async onAgentComplete(agentId, outcome) {
134
+ await this._closeAgent(agentId, outcome?.status || 'SUCCESS', outcome);
135
+ }
136
+
137
+ async onAgentCrash(agentId, error) {
138
+ await this._closeAgent(agentId, 'CRASH', { error: error?.message || String(error) });
139
+ }
140
+
141
+ async shutdown() {
142
+ for (const agentId of this._contexts.keys()) {
143
+ await this._closeAgent(agentId, 'SHUTDOWN');
144
+ }
145
+ if (this._transmissionQueue) {
146
+ await this._transmissionQueue.stop();
147
+ }
148
+ }
149
+
150
+ _processStep(agentId, ctx, event) {
151
+ ctx.classifier.onStep(event);
152
+
153
+ if (event.content && typeof event.content === 'string') {
154
+ event.content = this._scrubber.scrub(event.content);
155
+ }
156
+
157
+ const step = {
158
+ step: ++ctx.stepCount,
159
+ type: event.type,
160
+ timestamp: Date.now() / 1000,
161
+ ...event,
162
+ };
163
+
164
+ if (event.type === 'error') ctx.errorsEncountered++;
165
+ ctx.allSteps.push(step);
166
+
167
+ const envelope = ctx.builder.addStep(step);
168
+ if (envelope) {
169
+ this._signAndTransmit(ctx.sessionId, envelope);
170
+ ctx.chunkCount++;
171
+ }
172
+ }
173
+
174
+ _flushContext(agentId) {
175
+ const ctx = this._contexts.get(agentId);
176
+ if (!ctx) return;
177
+ const envelope = ctx.builder.flush();
178
+ if (envelope) {
179
+ this._signAndTransmit(ctx.sessionId, envelope);
180
+ ctx.chunkCount++;
181
+ }
182
+ }
183
+
184
+ async _closeAgent(agentId, status, extra) {
185
+ const ctx = this._contexts.get(agentId);
186
+ if (!ctx) return;
187
+
188
+ if (ctx.chunkTimer) clearInterval(ctx.chunkTimer);
189
+
190
+ this._flushContext(agentId);
191
+
192
+ const hasRecovery = StepClassifier.detectErrorRecovery(ctx.allSteps);
193
+ if (hasRecovery) ctx.errorsRecovered++;
194
+
195
+ const closeEnvelope = ctx.builder.buildSessionClose({
196
+ status,
197
+ user_interventions: StepClassifier.countUserInterventions(ctx.allSteps),
198
+ total_steps: ctx.stepCount,
199
+ total_chunks: ctx.chunkCount,
200
+ total_tokens: ctx.totalTokens,
201
+ duration_seconds: Math.round((Date.now() - ctx.startTime) / 1000),
202
+ files_modified: extra?.files_modified || ctx.filesModified,
203
+ errors_encountered: ctx.errorsEncountered,
204
+ errors_recovered: ctx.errorsRecovered,
205
+ coordination_events: ctx.coordinationEvents,
206
+ });
207
+
208
+ this._signAndTransmit(ctx.sessionId, closeEnvelope);
209
+
210
+ try {
211
+ await this._attestation.closeSession(ctx.sessionId);
212
+ } catch {
213
+ // fail silent
214
+ }
215
+
216
+ this._contexts.delete(agentId);
217
+ }
218
+
219
+ _signAndTransmit(sessionId, envelope) {
220
+ try {
221
+ const signed = this._attestation.signEnvelope(sessionId, envelope);
222
+ this._transmissionQueue.enqueue(signed);
223
+ } catch {
224
+ // fail silent
225
+ }
226
+ }
227
+ }
@@ -0,0 +1,93 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+
3
+ import { MAX_QUEUE_SIZE } from '../shared/constants.js';
4
+
5
+ export class TransmissionQueue {
6
+ constructor(centralCommandUrl, maxSize = MAX_QUEUE_SIZE) {
7
+ this._centralCommandUrl = centralCommandUrl;
8
+ this._maxSize = maxSize;
9
+ this._queue = [];
10
+ this._offlineQueue = [];
11
+ this._running = false;
12
+ this._drainPromise = null;
13
+
14
+ if (process.env.NODE_ENV === 'production' && !centralCommandUrl.startsWith('https://')) {
15
+ console.warn('[TransmissionQueue] WARNING: centralCommandUrl does not use HTTPS in production');
16
+ }
17
+ }
18
+
19
+ enqueue(signedEnvelope) {
20
+ if (this._queue.length >= this._maxSize) return;
21
+ if (signedEnvelope?.attestation?.session_hmac === 'OFFLINE') {
22
+ this._offlineQueue.push(signedEnvelope);
23
+ return;
24
+ }
25
+ this._queue.push(signedEnvelope);
26
+ if (this._running) this._kick();
27
+ }
28
+
29
+ replayOfflineQueue(sessionAttestation) {
30
+ const pending = this._offlineQueue.splice(0);
31
+ for (const envelope of pending) {
32
+ const sessionId = envelope.session_id;
33
+ const resigned = sessionAttestation.signEnvelope(sessionId, envelope);
34
+ if (resigned?.attestation?.session_hmac === 'OFFLINE') {
35
+ this._offlineQueue.push(resigned);
36
+ continue;
37
+ }
38
+ this._queue.push(resigned);
39
+ }
40
+ if (this._running && this._queue.length > 0) this._kick();
41
+ }
42
+
43
+ start() {
44
+ if (this._running) return;
45
+ this._running = true;
46
+ this._kick();
47
+ }
48
+
49
+ async stop() {
50
+ this._running = false;
51
+ if (this._drainPromise) {
52
+ await this._drainPromise;
53
+ }
54
+ }
55
+
56
+ _kick() {
57
+ if (this._drainPromise) return;
58
+ this._drainPromise = this._drain().finally(() => {
59
+ this._drainPromise = null;
60
+ });
61
+ }
62
+
63
+ async _drain() {
64
+ while (this._running && this._queue.length > 0) {
65
+ const envelope = this._queue[0];
66
+ let success = false;
67
+
68
+ for (let attempt = 0; attempt < 5; attempt++) {
69
+ try {
70
+ const res = await fetch(`${this._centralCommandUrl}/v1/training/ingest`, {
71
+ method: 'POST',
72
+ headers: { 'Content-Type': 'application/json' },
73
+ body: JSON.stringify(envelope),
74
+ signal: AbortSignal.timeout(30_000),
75
+ });
76
+ if (res.ok) {
77
+ success = true;
78
+ break;
79
+ }
80
+ } catch {
81
+ // network error
82
+ }
83
+
84
+ if (!this._running) return;
85
+ const delay = Math.min(1000 * Math.pow(2, attempt), 60_000);
86
+ await new Promise((r) => setTimeout(r, delay));
87
+ if (!this._running) return;
88
+ }
89
+
90
+ this._queue.shift();
91
+ }
92
+ }
93
+ }