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.
- package/moe-training/client/index.js +1 -0
- package/moe-training/client/step-classifier.js +1 -1
- package/moe-training/client/trajectory-capture.js +16 -7
- package/moe-training/test/client/step-classifier.test.js +42 -0
- package/moe-training/test/client/trajectory-capture.test.js +95 -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 +19 -3
- package/node_modules/@groove-dev/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +23 -2
- package/node_modules/moe-training/client/index.js +1 -0
- package/node_modules/moe-training/client/step-classifier.js +1 -1
- package/node_modules/moe-training/client/trajectory-capture.js +16 -7
- package/node_modules/moe-training/test/client/step-classifier.test.js +42 -0
- package/node_modules/moe-training/test/client/trajectory-capture.test.js +95 -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 +19 -3
- package/packages/gui/dist/assets/{index-8gdXdRnq.js → index-oUBAPJv6.js} +15 -15
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +28 -2
- package/packages/gui/src/views/settings.jsx +23 -2
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-oUBAPJv6.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-CFF1Lrnz.js">
|
|
@@ -172,6 +172,8 @@ function AuthenticateStep({ providerId, meta, onSaveKey }) {
|
|
|
172
172
|
const [showKey, setShowKey] = useState(false);
|
|
173
173
|
const [saving, setSaving] = useState(false);
|
|
174
174
|
const [loginStarted, setLoginStarted] = useState(false);
|
|
175
|
+
const [verifying, setVerifying] = useState(false);
|
|
176
|
+
const [verifyError, setVerifyError] = useState('');
|
|
175
177
|
const [authMode, setAuthMode] = useState(meta.authType === 'subscription' ? 'subscription' : 'apikey');
|
|
176
178
|
|
|
177
179
|
async function handleSaveKey() {
|
|
@@ -287,9 +289,33 @@ function AuthenticateStep({ providerId, meta, onSaveKey }) {
|
|
|
287
289
|
<ExternalLink size={12} className="text-accent" />
|
|
288
290
|
<span className="text-xs text-accent font-sans">Sign-in opened in your browser</span>
|
|
289
291
|
</div>
|
|
290
|
-
<Button
|
|
291
|
-
|
|
292
|
+
<Button
|
|
293
|
+
variant="primary"
|
|
294
|
+
size="sm"
|
|
295
|
+
disabled={verifying}
|
|
296
|
+
onClick={async () => {
|
|
297
|
+
setVerifying(true);
|
|
298
|
+
setVerifyError('');
|
|
299
|
+
try {
|
|
300
|
+
const res = await api.post(`/providers/codex/verify`);
|
|
301
|
+
if (res.authenticated) {
|
|
302
|
+
onSaveKey();
|
|
303
|
+
} else {
|
|
304
|
+
setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
|
|
305
|
+
}
|
|
306
|
+
} catch {
|
|
307
|
+
setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
|
|
308
|
+
} finally {
|
|
309
|
+
setVerifying(false);
|
|
310
|
+
}
|
|
311
|
+
}}
|
|
312
|
+
className="gap-1.5"
|
|
313
|
+
>
|
|
314
|
+
{verifying ? <Loader2 size={11} className="animate-spin" /> : <Check size={11} />} I've signed in
|
|
292
315
|
</Button>
|
|
316
|
+
{verifyError && (
|
|
317
|
+
<p className="text-2xs text-danger font-sans">{verifyError}</p>
|
|
318
|
+
)}
|
|
293
319
|
</div>
|
|
294
320
|
)}
|
|
295
321
|
</div>
|
|
@@ -454,10 +454,31 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
454
454
|
<Button
|
|
455
455
|
variant="primary"
|
|
456
456
|
size="sm"
|
|
457
|
-
|
|
457
|
+
disabled={checking}
|
|
458
|
+
onClick={async () => {
|
|
459
|
+
if (provider.id === 'codex') {
|
|
460
|
+
setChecking(true);
|
|
461
|
+
try {
|
|
462
|
+
const res = await api.post(`/providers/codex/verify`);
|
|
463
|
+
if (res.authenticated) {
|
|
464
|
+
setLoginPending(false);
|
|
465
|
+
if (onKeyChange) onKeyChange();
|
|
466
|
+
} else {
|
|
467
|
+
addToast('error', 'Authentication not detected yet. Please complete sign-in in your browser and try again.');
|
|
468
|
+
}
|
|
469
|
+
} catch {
|
|
470
|
+
addToast('error', 'Authentication not detected yet. Please complete sign-in in your browser and try again.');
|
|
471
|
+
} finally {
|
|
472
|
+
setChecking(false);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
setLoginPending(false);
|
|
476
|
+
if (onKeyChange) onKeyChange();
|
|
477
|
+
}
|
|
478
|
+
}}
|
|
458
479
|
className="w-full h-8 text-xs gap-1.5"
|
|
459
480
|
>
|
|
460
|
-
<Check size={12} /> I've signed in
|
|
481
|
+
{checking ? <Loader2 size={12} className="animate-spin" /> : <Check size={12} />} I've signed in
|
|
461
482
|
</Button>
|
|
462
483
|
<button
|
|
463
484
|
onClick={() => setLoginPending(false)}
|
|
@@ -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
|
-
|
|
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();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.104",
|
|
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)",
|
|
@@ -758,9 +758,25 @@ export function createApi(app, daemon) {
|
|
|
758
758
|
|
|
759
759
|
proc.on('close', (code) => {
|
|
760
760
|
clearTimeout(timeout);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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) => {
|