groove-dev 0.27.103 → 0.27.104

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 (27) hide show
  1. package/moe-training/client/index.js +1 -0
  2. package/moe-training/client/step-classifier.js +1 -1
  3. package/moe-training/client/trajectory-capture.js +16 -7
  4. package/moe-training/test/client/step-classifier.test.js +42 -0
  5. package/moe-training/test/client/trajectory-capture.test.js +95 -0
  6. package/node_modules/@groove-dev/cli/package.json +1 -1
  7. package/node_modules/@groove-dev/daemon/package.json +1 -1
  8. package/node_modules/@groove-dev/daemon/src/api.js +19 -3
  9. package/node_modules/@groove-dev/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
  10. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  11. package/node_modules/@groove-dev/gui/package.json +1 -1
  12. package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
  13. package/node_modules/@groove-dev/gui/src/views/settings.jsx +23 -2
  14. package/node_modules/moe-training/client/index.js +1 -0
  15. package/node_modules/moe-training/client/step-classifier.js +1 -1
  16. package/node_modules/moe-training/client/trajectory-capture.js +16 -7
  17. package/node_modules/moe-training/test/client/step-classifier.test.js +42 -0
  18. package/node_modules/moe-training/test/client/trajectory-capture.test.js +95 -0
  19. package/package.json +1 -1
  20. package/packages/cli/package.json +1 -1
  21. package/packages/daemon/package.json +1 -1
  22. package/packages/daemon/src/api.js +19 -3
  23. package/packages/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
  24. package/packages/gui/dist/index.html +1 -1
  25. package/packages/gui/package.json +1 -1
  26. package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
  27. package/packages/gui/src/views/settings.jsx +23 -2
@@ -8,3 +8,4 @@ export { TransmissionQueue } from './transmission-queue.js';
8
8
  export { EnvelopeBuilder } from './envelope-builder.js';
9
9
  export { StepClassifier } from './step-classifier.js';
10
10
  export { getParser } from './parsers/index.js';
11
+ export { DomainTagger } from './domain-tagger.js';
@@ -38,7 +38,7 @@ export class StepClassifier {
38
38
 
39
39
  const content = step.content || '';
40
40
 
41
- if ((step.type === 'action' || step.type === 'observation') && ERROR_SIGNAL_RE.test(content)) {
41
+ if ((step.type === 'action' || step.type === 'observation') && step.is_error !== false && ERROR_SIGNAL_RE.test(content)) {
42
42
  step.type = 'error';
43
43
  }
44
44
 
@@ -8,6 +8,7 @@ import { StepClassifier } from './step-classifier.js';
8
8
  import { EnvelopeBuilder } from './envelope-builder.js';
9
9
  import { SessionAttestation } from './session-attestation.js';
10
10
  import { TransmissionQueue } from './transmission-queue.js';
11
+ import { DomainTagger } from './domain-tagger.js';
11
12
  import {
12
13
  CHUNK_TIMEOUT_MS,
13
14
  CENTRAL_COMMAND_URL,
@@ -33,7 +34,7 @@ export class TrajectoryCapture {
33
34
  this._contexts = new Map();
34
35
  }
35
36
 
36
- init() {
37
+ async init() {
37
38
  if (!ConsentManager.isCaptureEnabled()) {
38
39
  this._enabled = false;
39
40
  return;
@@ -43,6 +44,8 @@ export class TrajectoryCapture {
43
44
  this._attestation = new SessionAttestation(this._centralCommandUrl);
44
45
  this._transmissionQueue = new TransmissionQueue(this._centralCommandUrl);
45
46
  this._transmissionQueue.start();
47
+ this._domainTagger = new DomainTagger();
48
+ await this._domainTagger.init();
46
49
  this._offlineRetryTimer = setInterval(() => {
47
50
  this._retryOfflineQueue();
48
51
  }, OFFLINE_RETRY_INTERVAL_MS);
@@ -126,10 +129,6 @@ export class TrajectoryCapture {
126
129
  if (resolved) ctx.metadata.model_engine = resolved;
127
130
  }
128
131
 
129
- const tokens = ctx.parser.extractTokens(jsonEvent);
130
- if (tokens) {
131
- ctx.totalTokens += (tokens.input || 0) + (tokens.output || 0);
132
- }
133
132
  }
134
133
 
135
134
  onUserMessage(agentId, text) {
@@ -198,6 +197,7 @@ export class TrajectoryCapture {
198
197
  ...ev,
199
198
  };
200
199
 
200
+ ctx.totalTokens += ev.token_count;
201
201
  if (ev.type === 'error') ctx.errorsEncountered++;
202
202
  ctx.allSteps.push(step);
203
203
 
@@ -254,6 +254,14 @@ export class TrajectoryCapture {
254
254
  const userInterventions = StepClassifier.countUserInterventions(ctx.allSteps);
255
255
  const durationSeconds = Math.round((Date.now() - ctx.startTime) / 1000);
256
256
 
257
+ if (this._domainTagger) {
258
+ const role = ctx.metadata.agent_role || '';
259
+ const firstPrompt = ctx.allSteps.find((s) => s.type === 'thought')?.content || '';
260
+ const thoughtSteps = ctx.allSteps.filter((s) => s.type === 'thought');
261
+ const routingText = DomainTagger.buildRoutingText(role, firstPrompt, thoughtSteps);
262
+ ctx.metadata.domain_tags = await this._domainTagger.tag(routingText);
263
+ }
264
+
257
265
  const { tier, reason: tierReason } = this._computeQualityTier(ctx, status, userInterventions);
258
266
  const { eligible, exclusionReason } = this._computeTrainingEligibility(ctx, durationSeconds);
259
267
 
@@ -296,8 +304,9 @@ export class TrajectoryCapture {
296
304
 
297
305
  _computeQualityTier(ctx, status, userInterventions) {
298
306
  const quality = ctx.metadata.session_quality;
299
- if (quality >= TIER_A_MIN_QUALITY && ctx.errorsEncountered === 0 && userInterventions === 0 && status === 'SUCCESS') {
300
- return { tier: 'TIER_A', reason: 'high_quality_no_errors' };
307
+ if (quality >= TIER_A_MIN_QUALITY && (ctx.errorsEncountered === 0 || ctx.errorsEncountered <= ctx.errorsRecovered) && userInterventions === 0 && status === 'SUCCESS') {
308
+ const reason = ctx.errorsEncountered > 0 ? 'high_quality_errors_recovered' : 'high_quality_no_errors';
309
+ return { tier: 'TIER_A', reason };
301
310
  }
302
311
  if (status !== 'SUCCESS') {
303
312
  return { tier: 'TIER_C', reason: 'non_success_status' };
@@ -132,4 +132,46 @@ describe('StepClassifier', () => {
132
132
  assert.ok(result);
133
133
  assert.equal(result.type, 'action');
134
134
  });
135
+
136
+ it('preserves observation type when is_error is false despite error keywords', () => {
137
+ const classifier = new StepClassifier();
138
+ const step = { type: 'observation', content: 'Cannot find module foo', is_error: false };
139
+ const result = classifier.onStep(step);
140
+ assert.equal(result.type, 'observation');
141
+ });
142
+
143
+ it('preserves observation type when is_error:false and content has TypeError', () => {
144
+ const classifier = new StepClassifier();
145
+ const step = { type: 'observation', content: 'TypeError: something failed', is_error: false };
146
+ const result = classifier.onStep(step);
147
+ assert.equal(result.type, 'observation');
148
+ });
149
+
150
+ it('still reclassifies observation to error when is_error is true', () => {
151
+ const classifier = new StepClassifier();
152
+ const step = { type: 'observation', content: 'Command failed with exit code 1', is_error: true };
153
+ const result = classifier.onStep(step);
154
+ assert.equal(result.type, 'error');
155
+ });
156
+
157
+ it('still reclassifies observation to error when is_error is undefined', () => {
158
+ const classifier = new StepClassifier();
159
+ const step = { type: 'observation', content: 'ENOENT: no such file or directory' };
160
+ const result = classifier.onStep(step);
161
+ assert.equal(result.type, 'error');
162
+ });
163
+
164
+ it('preserves action type when is_error is false', () => {
165
+ const classifier = new StepClassifier();
166
+ const step = { type: 'action', content: 'Command failed with exit code 1', is_error: false };
167
+ const result = classifier.onStep(step);
168
+ assert.equal(result.type, 'action');
169
+ });
170
+
171
+ it('still reclassifies action to error when is_error is not set', () => {
172
+ const classifier = new StepClassifier();
173
+ const step = { type: 'action', content: 'Command failed with exit code 1' };
174
+ const result = classifier.onStep(step);
175
+ assert.equal(result.type, 'error');
176
+ });
135
177
  });
@@ -292,6 +292,101 @@ describe('TrajectoryCapture — user feedback emission', () => {
292
292
  });
293
293
  });
294
294
 
295
+ describe('TrajectoryCapture — token counting via _processStep', () => {
296
+ it('accumulates token_count from every step type', () => {
297
+ const tc = makeTc();
298
+ tc._scrubber = { scrub: (s) => s };
299
+ const ctx = makeCtx();
300
+ ctx.totalTokens = 0;
301
+ ctx.stepCount = 0;
302
+ ctx.allSteps = [];
303
+ ctx.builder = { addStep: () => null };
304
+ ctx.classifier = {
305
+ onStep: (s) => s,
306
+ };
307
+
308
+ tc._processStep('agent-1', ctx, { type: 'thought', content: 'thinking about it', token_count: 50 });
309
+ tc._processStep('agent-1', ctx, { type: 'action', content: 'run test', token_count: 30 });
310
+ tc._processStep('agent-1', ctx, { type: 'observation', content: 'test passed', token_count: 100 });
311
+ tc._processStep('agent-1', ctx, { type: 'thought', content: 'next step', token_count: 20 });
312
+
313
+ assert.equal(ctx.totalTokens, 200);
314
+ assert.equal(ctx.stepCount, 4);
315
+ });
316
+
317
+ it('estimates token_count when not provided', () => {
318
+ const tc = makeTc();
319
+ tc._scrubber = { scrub: (s) => s };
320
+ const ctx = makeCtx();
321
+ ctx.totalTokens = 0;
322
+ ctx.stepCount = 0;
323
+ ctx.allSteps = [];
324
+ ctx.builder = { addStep: () => null };
325
+ ctx.classifier = {
326
+ onStep: (s) => s,
327
+ };
328
+
329
+ tc._processStep('agent-1', ctx, { type: 'thought', content: 'a'.repeat(100) });
330
+ assert.equal(ctx.totalTokens, 25);
331
+ });
332
+
333
+ it('does not double-count tokens from onStdoutLine', () => {
334
+ const tc = makeTc();
335
+ tc._scrubber = { scrub: (s) => s };
336
+ tc._enabled = true;
337
+
338
+ const ctx = makeCtx();
339
+ ctx.totalTokens = 0;
340
+ ctx.stepCount = 0;
341
+ ctx.allSteps = [];
342
+ ctx.builder = { addStep: () => null };
343
+ ctx.classifier = {
344
+ onStep: (s) => s,
345
+ };
346
+ ctx.parser = {
347
+ parseEvent: () => ({ type: 'thought', content: 'hello', token_count: 10 }),
348
+ extractModel: () => null,
349
+ };
350
+ tc._contexts.set('agent-x', ctx);
351
+
352
+ tc.onStdoutLine('agent-x', '{"type":"assistant"}');
353
+ assert.equal(ctx.totalTokens, 10);
354
+ });
355
+ });
356
+
357
+ describe('TrajectoryCapture — TIER_A with recovered errors', () => {
358
+ it('TIER_A when all errors are recovered', () => {
359
+ const tc = makeTc();
360
+ const ctx = makeCtx({ quality: 80, errorsEncountered: 2, errorsRecovered: 2 });
361
+ const result = tc._computeQualityTier(ctx, 'SUCCESS', 0);
362
+ assert.equal(result.tier, 'TIER_A');
363
+ assert.equal(result.reason, 'high_quality_errors_recovered');
364
+ });
365
+
366
+ it('TIER_A when errors recovered exceed errors encountered', () => {
367
+ const tc = makeTc();
368
+ const ctx = makeCtx({ quality: 75, errorsEncountered: 1, errorsRecovered: 2 });
369
+ const result = tc._computeQualityTier(ctx, 'SUCCESS', 0);
370
+ assert.equal(result.tier, 'TIER_A');
371
+ assert.equal(result.reason, 'high_quality_errors_recovered');
372
+ });
373
+
374
+ it('not TIER_A when errors exceed recoveries', () => {
375
+ const tc = makeTc();
376
+ const ctx = makeCtx({ quality: 80, errorsEncountered: 3, errorsRecovered: 1 });
377
+ const result = tc._computeQualityTier(ctx, 'SUCCESS', 0);
378
+ assert.notEqual(result.tier, 'TIER_A');
379
+ });
380
+
381
+ it('TIER_A with zero errors still uses original reason', () => {
382
+ const tc = makeTc();
383
+ const ctx = makeCtx({ quality: 80, errorsEncountered: 0 });
384
+ const result = tc._computeQualityTier(ctx, 'SUCCESS', 0);
385
+ assert.equal(result.tier, 'TIER_A');
386
+ assert.equal(result.reason, 'high_quality_no_errors');
387
+ });
388
+ });
389
+
295
390
  describe('TrajectoryCapture — _computeQuality', () => {
296
391
  it('base score is 50', () => {
297
392
  const tc = makeTc();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.103",
3
+ "version": "0.27.104",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.103",
3
+ "version": "0.27.104",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -758,9 +758,25 @@ export function createApi(app, daemon) {
758
758
 
759
759
  proc.on('close', (code) => {
760
760
  clearTimeout(timeout);
761
- respond(code === 0
762
- ? { status: 'authenticated' }
763
- : { status: 'error', error: stderr.slice(-200) || `Login failed (exit ${code})` });
761
+ if (code === 0) {
762
+ let hasKey = false;
763
+ try {
764
+ const authPath = resolve(homedir(), '.codex', 'auth.json');
765
+ if (existsSync(authPath)) {
766
+ const auth = JSON.parse(readFileSync(authPath, 'utf8'));
767
+ const token = auth.OPENAI_API_KEY
768
+ || (auth.auth_mode === 'chatgpt' && auth.tokens?.id_token)
769
+ || null;
770
+ if (token) {
771
+ daemon.credentials.setKey('codex', token);
772
+ hasKey = true;
773
+ }
774
+ }
775
+ } catch { /* auth.json missing or malformed — login still succeeded */ }
776
+ respond({ status: 'authenticated', hasKey });
777
+ } else {
778
+ respond({ status: 'error', error: stderr.slice(-200) || `Login failed (exit ${code})` });
779
+ }
764
780
  });
765
781
 
766
782
  proc.on('error', (err) => {