groove-dev 0.27.103 → 0.27.106
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 +52 -7
- package/moe-training/test/client/step-classifier.test.js +42 -0
- package/moe-training/test/client/trajectory-capture.test.js +199 -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 +21 -7
- package/node_modules/@groove-dev/daemon/src/process.js +54 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +7 -7
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +1 -1
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-8gdXdRnq.js → index-BN7fQKaF.js} +23 -23
- package/node_modules/@groove-dev/gui/dist/assets/{index-C1ObKizg.css → index-QwgLRN8B.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/components/onboarding/setup-wizard.jsx +6 -0
- package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +57 -7
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +35 -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 +52 -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 +199 -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 +21 -7
- package/packages/daemon/src/process.js +54 -0
- package/packages/daemon/src/providers/claude-code.js +7 -7
- package/packages/daemon/src/providers/codex.js +1 -1
- package/packages/daemon/src/providers/gemini.js +1 -1
- package/packages/gui/dist/assets/{index-8gdXdRnq.js → index-BN7fQKaF.js} +23 -23
- package/packages/gui/dist/assets/{index-C1ObKizg.css → index-QwgLRN8B.css} +1 -1
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/onboarding/setup-wizard.jsx +6 -0
- package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +57 -7
- package/packages/gui/src/views/settings.jsx +35 -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) {
|
|
@@ -155,6 +154,42 @@ export class TrajectoryCapture {
|
|
|
155
154
|
this._processStep(agentId, ctx, classified);
|
|
156
155
|
}
|
|
157
156
|
|
|
157
|
+
onParsedOutput(agentId, output) {
|
|
158
|
+
if (!this._enabled) return;
|
|
159
|
+
const ctx = this._contexts.get(agentId);
|
|
160
|
+
if (!ctx || !output || !output.type) return;
|
|
161
|
+
|
|
162
|
+
if (output.type === 'activity') {
|
|
163
|
+
if (output.subtype === 'assistant') {
|
|
164
|
+
this._processStep(agentId, ctx, { type: 'thought', content: output.data || '' });
|
|
165
|
+
} else if (output.subtype === 'tool_use' && Array.isArray(output.data)) {
|
|
166
|
+
for (const item of output.data) {
|
|
167
|
+
this._processStep(agentId, ctx, {
|
|
168
|
+
type: 'action',
|
|
169
|
+
tool: item.name || '',
|
|
170
|
+
arguments: item.input || {},
|
|
171
|
+
content: `Using ${item.name || 'tool'}`,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
} else if (output.subtype === 'tool_result' && Array.isArray(output.data)) {
|
|
175
|
+
for (const item of output.data) {
|
|
176
|
+
const isError = item.success === false;
|
|
177
|
+
this._processStep(agentId, ctx, {
|
|
178
|
+
type: isError ? 'error' : 'observation',
|
|
179
|
+
content: item.output || '',
|
|
180
|
+
tool: item.name || '',
|
|
181
|
+
is_error: isError,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else if (output.type === 'result') {
|
|
186
|
+
this._processStep(agentId, ctx, {
|
|
187
|
+
type: 'resolution',
|
|
188
|
+
content: typeof output.data === 'string' ? output.data : '',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
158
193
|
async onAgentComplete(agentId, outcome) {
|
|
159
194
|
await this._closeAgent(agentId, outcome?.status || 'SUCCESS', outcome);
|
|
160
195
|
}
|
|
@@ -198,6 +233,7 @@ export class TrajectoryCapture {
|
|
|
198
233
|
...ev,
|
|
199
234
|
};
|
|
200
235
|
|
|
236
|
+
ctx.totalTokens += ev.token_count;
|
|
201
237
|
if (ev.type === 'error') ctx.errorsEncountered++;
|
|
202
238
|
ctx.allSteps.push(step);
|
|
203
239
|
|
|
@@ -254,6 +290,14 @@ export class TrajectoryCapture {
|
|
|
254
290
|
const userInterventions = StepClassifier.countUserInterventions(ctx.allSteps);
|
|
255
291
|
const durationSeconds = Math.round((Date.now() - ctx.startTime) / 1000);
|
|
256
292
|
|
|
293
|
+
if (this._domainTagger) {
|
|
294
|
+
const role = ctx.metadata.agent_role || '';
|
|
295
|
+
const firstPrompt = ctx.allSteps.find((s) => s.type === 'thought')?.content || '';
|
|
296
|
+
const thoughtSteps = ctx.allSteps.filter((s) => s.type === 'thought');
|
|
297
|
+
const routingText = DomainTagger.buildRoutingText(role, firstPrompt, thoughtSteps);
|
|
298
|
+
ctx.metadata.domain_tags = await this._domainTagger.tag(routingText);
|
|
299
|
+
}
|
|
300
|
+
|
|
257
301
|
const { tier, reason: tierReason } = this._computeQualityTier(ctx, status, userInterventions);
|
|
258
302
|
const { eligible, exclusionReason } = this._computeTrainingEligibility(ctx, durationSeconds);
|
|
259
303
|
|
|
@@ -296,8 +340,9 @@ export class TrajectoryCapture {
|
|
|
296
340
|
|
|
297
341
|
_computeQualityTier(ctx, status, userInterventions) {
|
|
298
342
|
const quality = ctx.metadata.session_quality;
|
|
299
|
-
if (quality >= TIER_A_MIN_QUALITY && ctx.errorsEncountered === 0 && userInterventions === 0 && status === 'SUCCESS') {
|
|
300
|
-
|
|
343
|
+
if (quality >= TIER_A_MIN_QUALITY && (ctx.errorsEncountered === 0 || ctx.errorsEncountered <= ctx.errorsRecovered) && userInterventions === 0 && status === 'SUCCESS') {
|
|
344
|
+
const reason = ctx.errorsEncountered > 0 ? 'high_quality_errors_recovered' : 'high_quality_no_errors';
|
|
345
|
+
return { tier: 'TIER_A', reason };
|
|
301
346
|
}
|
|
302
347
|
if (status !== 'SUCCESS') {
|
|
303
348
|
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();
|
|
@@ -343,3 +438,107 @@ describe('TrajectoryCapture — _computeQuality', () => {
|
|
|
343
438
|
assert.equal(quality, 100);
|
|
344
439
|
});
|
|
345
440
|
});
|
|
441
|
+
|
|
442
|
+
describe('TrajectoryCapture — onParsedOutput', () => {
|
|
443
|
+
function makeEnabledTc() {
|
|
444
|
+
const tc = makeTc();
|
|
445
|
+
tc._enabled = true;
|
|
446
|
+
tc._scrubber = { scrub: (s) => s };
|
|
447
|
+
const ctx = makeCtx();
|
|
448
|
+
ctx.totalTokens = 0;
|
|
449
|
+
ctx.stepCount = 0;
|
|
450
|
+
ctx.allSteps = [];
|
|
451
|
+
ctx.builder = { addStep: () => null };
|
|
452
|
+
ctx.classifier = { onStep: (s) => s };
|
|
453
|
+
tc._contexts.set('agent-loop-1', ctx);
|
|
454
|
+
return { tc, ctx };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
it('converts assistant activity to thought step', () => {
|
|
458
|
+
const { tc, ctx } = makeEnabledTc();
|
|
459
|
+
tc.onParsedOutput('agent-loop-1', { type: 'activity', subtype: 'assistant', data: 'I will fix the bug' });
|
|
460
|
+
assert.equal(ctx.stepCount, 1);
|
|
461
|
+
assert.equal(ctx.allSteps[0].type, 'thought');
|
|
462
|
+
assert.equal(ctx.allSteps[0].content, 'I will fix the bug');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('converts tool_use activity to action step', () => {
|
|
466
|
+
const { tc, ctx } = makeEnabledTc();
|
|
467
|
+
tc.onParsedOutput('agent-loop-1', {
|
|
468
|
+
type: 'activity', subtype: 'tool_use',
|
|
469
|
+
data: [{ type: 'tool_use', name: 'Edit', input: { path: 'foo.js' } }],
|
|
470
|
+
});
|
|
471
|
+
assert.equal(ctx.stepCount, 1);
|
|
472
|
+
assert.equal(ctx.allSteps[0].type, 'action');
|
|
473
|
+
assert.equal(ctx.allSteps[0].tool, 'Edit');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('converts successful tool_result to observation step', () => {
|
|
477
|
+
const { tc, ctx } = makeEnabledTc();
|
|
478
|
+
tc.onParsedOutput('agent-loop-1', {
|
|
479
|
+
type: 'activity', subtype: 'tool_result',
|
|
480
|
+
data: [{ type: 'tool_result', name: 'Bash', success: true, output: 'tests passed' }],
|
|
481
|
+
});
|
|
482
|
+
assert.equal(ctx.stepCount, 1);
|
|
483
|
+
assert.equal(ctx.allSteps[0].type, 'observation');
|
|
484
|
+
assert.equal(ctx.allSteps[0].content, 'tests passed');
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('converts failed tool_result to error step', () => {
|
|
488
|
+
const { tc, ctx } = makeEnabledTc();
|
|
489
|
+
tc.onParsedOutput('agent-loop-1', {
|
|
490
|
+
type: 'activity', subtype: 'tool_result',
|
|
491
|
+
data: [{ type: 'tool_result', name: 'Bash', success: false, output: 'command not found' }],
|
|
492
|
+
});
|
|
493
|
+
assert.equal(ctx.stepCount, 1);
|
|
494
|
+
assert.equal(ctx.allSteps[0].type, 'error');
|
|
495
|
+
assert.equal(ctx.allSteps[0].is_error, true);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('converts result to resolution step', () => {
|
|
499
|
+
const { tc, ctx } = makeEnabledTc();
|
|
500
|
+
tc.onParsedOutput('agent-loop-1', { type: 'result', subtype: 'assistant', data: 'Task complete' });
|
|
501
|
+
assert.equal(ctx.stepCount, 1);
|
|
502
|
+
assert.equal(ctx.allSteps[0].type, 'resolution');
|
|
503
|
+
assert.equal(ctx.allSteps[0].content, 'Task complete');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('ignores stream activity (partial deltas)', () => {
|
|
507
|
+
const { tc, ctx } = makeEnabledTc();
|
|
508
|
+
tc.onParsedOutput('agent-loop-1', { type: 'activity', subtype: 'stream', data: 'partial' });
|
|
509
|
+
assert.equal(ctx.stepCount, 0);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('ignores token-only activity', () => {
|
|
513
|
+
const { tc, ctx } = makeEnabledTc();
|
|
514
|
+
tc.onParsedOutput('agent-loop-1', { type: 'activity', tokensUsed: 500, inputTokens: 400, outputTokens: 100 });
|
|
515
|
+
assert.equal(ctx.stepCount, 0);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('silently returns for unknown agent', () => {
|
|
519
|
+
const { tc } = makeEnabledTc();
|
|
520
|
+
tc.onParsedOutput('unknown-agent', { type: 'activity', subtype: 'assistant', data: 'hello' });
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('silently returns when disabled', () => {
|
|
524
|
+
const { tc, ctx } = makeEnabledTc();
|
|
525
|
+
tc._enabled = false;
|
|
526
|
+
tc.onParsedOutput('agent-loop-1', { type: 'activity', subtype: 'assistant', data: 'hello' });
|
|
527
|
+
assert.equal(ctx.stepCount, 0);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('accumulates tokens across multiple outputs', () => {
|
|
531
|
+
const { tc, ctx } = makeEnabledTc();
|
|
532
|
+
tc.onParsedOutput('agent-loop-1', { type: 'activity', subtype: 'assistant', data: 'thinking about the problem' });
|
|
533
|
+
tc.onParsedOutput('agent-loop-1', {
|
|
534
|
+
type: 'activity', subtype: 'tool_use',
|
|
535
|
+
data: [{ type: 'tool_use', name: 'Bash', input: { command: 'ls' } }],
|
|
536
|
+
});
|
|
537
|
+
tc.onParsedOutput('agent-loop-1', {
|
|
538
|
+
type: 'activity', subtype: 'tool_result',
|
|
539
|
+
data: [{ type: 'tool_result', name: 'Bash', success: true, output: 'file1.js\nfile2.js' }],
|
|
540
|
+
});
|
|
541
|
+
assert.equal(ctx.stepCount, 3);
|
|
542
|
+
assert.ok(ctx.totalTokens > 0);
|
|
543
|
+
});
|
|
544
|
+
});
|
|
@@ -631,9 +631,8 @@ export function createApi(app, daemon) {
|
|
|
631
631
|
|
|
632
632
|
write({ status: 'installing', output: `Installing ${pkg}...`, progress: 0 });
|
|
633
633
|
|
|
634
|
-
const proc = spawn('
|
|
634
|
+
const proc = spawn('bash', ['-lc', `npm install -g ${pkg}`], {
|
|
635
635
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
636
|
-
shell: true,
|
|
637
636
|
env: { ...process.env, NODE_ENV: undefined },
|
|
638
637
|
});
|
|
639
638
|
|
|
@@ -758,9 +757,25 @@ export function createApi(app, daemon) {
|
|
|
758
757
|
|
|
759
758
|
proc.on('close', (code) => {
|
|
760
759
|
clearTimeout(timeout);
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
760
|
+
if (code === 0) {
|
|
761
|
+
let hasKey = false;
|
|
762
|
+
try {
|
|
763
|
+
const authPath = resolve(homedir(), '.codex', 'auth.json');
|
|
764
|
+
if (existsSync(authPath)) {
|
|
765
|
+
const auth = JSON.parse(readFileSync(authPath, 'utf8'));
|
|
766
|
+
const token = auth.OPENAI_API_KEY
|
|
767
|
+
|| (auth.auth_mode === 'chatgpt' && auth.tokens?.id_token)
|
|
768
|
+
|| null;
|
|
769
|
+
if (token) {
|
|
770
|
+
daemon.credentials.setKey('codex', token);
|
|
771
|
+
hasKey = true;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
} catch { /* auth.json missing or malformed — login still succeeded */ }
|
|
775
|
+
respond({ status: 'authenticated', hasKey });
|
|
776
|
+
} else {
|
|
777
|
+
respond({ status: 'error', error: stderr.slice(-200) || `Login failed (exit ${code})` });
|
|
778
|
+
}
|
|
764
779
|
});
|
|
765
780
|
|
|
766
781
|
proc.on('error', (err) => {
|
|
@@ -4713,9 +4728,8 @@ Keep responses concise. Help them think, don't lecture them about the system the
|
|
|
4713
4728
|
|
|
4714
4729
|
write({ status: 'installing', output: `Installing ${pkg}...`, progress: 0 });
|
|
4715
4730
|
|
|
4716
|
-
const proc = spawn('
|
|
4731
|
+
const proc = spawn('bash', ['-lc', `npm install -g ${pkg}`], {
|
|
4717
4732
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4718
|
-
shell: true,
|
|
4719
4733
|
env: { ...process.env, NODE_ENV: undefined },
|
|
4720
4734
|
});
|
|
4721
4735
|
|
|
@@ -1760,6 +1760,15 @@ For normal file edits within your scope, proceed without review.
|
|
|
1760
1760
|
locks.register(newAgent.id, newAgent.scope, newAgent.workingDir);
|
|
1761
1761
|
}
|
|
1762
1762
|
|
|
1763
|
+
if (this.daemon.trajectoryCapture) {
|
|
1764
|
+
try {
|
|
1765
|
+
const teamSize = registry.getAll().filter(a => a.status === 'active' || a.status === 'running' || a.status === 'starting').length;
|
|
1766
|
+
this.daemon.trajectoryCapture.onAgentSpawn(
|
|
1767
|
+
newAgent.id, config.provider, config.model || null, config.role, teamSize
|
|
1768
|
+
).catch(() => {});
|
|
1769
|
+
} catch (e) { /* fail silent */ }
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1763
1772
|
// Spawn the resumed process
|
|
1764
1773
|
const resumeCwd = [config.workingDir, this.daemon.projectDir].find(d => d && existsSync(d)) || this.daemon.projectDir;
|
|
1765
1774
|
const proc = cpSpawn(command, args, {
|
|
@@ -1815,6 +1824,23 @@ For normal file edits within your scope, proceed without review.
|
|
|
1815
1824
|
|
|
1816
1825
|
const finalStatus = signal === 'SIGTERM' || signal === 'SIGKILL' ? 'killed' : code === 0 ? 'completed' : 'crashed';
|
|
1817
1826
|
registry.update(newAgent.id, { status: finalStatus, pid: null });
|
|
1827
|
+
|
|
1828
|
+
if (this.daemon.trajectoryCapture) {
|
|
1829
|
+
try {
|
|
1830
|
+
if (finalStatus === 'completed') {
|
|
1831
|
+
this.daemon.trajectoryCapture.onAgentComplete(newAgent.id, {
|
|
1832
|
+
status: 'SUCCESS', exit_code: code, signal,
|
|
1833
|
+
});
|
|
1834
|
+
} else {
|
|
1835
|
+
this.daemon.trajectoryCapture.onAgentCrash(newAgent.id,
|
|
1836
|
+
signal ? 'Killed by signal ' + signal : 'Exit code ' + code
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
const count = (this.daemon.state.get('training_sessions_captured') || 0) + 1;
|
|
1840
|
+
this.daemon.state.set('training_sessions_captured', count);
|
|
1841
|
+
} catch (e) { /* fail silent */ }
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1818
1844
|
this.daemon.broadcast({ type: 'agent:exit', agentId: newAgent.id, code, signal, status: finalStatus });
|
|
1819
1845
|
if (finalStatus === 'completed' && this.daemon.journalist) {
|
|
1820
1846
|
const a = registry.get(newAgent.id);
|
|
@@ -1930,8 +1956,20 @@ For normal file edits within your scope, proceed without review.
|
|
|
1930
1956
|
});
|
|
1931
1957
|
}
|
|
1932
1958
|
|
|
1959
|
+
if (this.daemon.trajectoryCapture) {
|
|
1960
|
+
try {
|
|
1961
|
+
const teamSize = registry.getAll().filter(a => a.status === 'active' || a.status === 'running' || a.status === 'starting').length;
|
|
1962
|
+
this.daemon.trajectoryCapture.onAgentSpawn(
|
|
1963
|
+
newAgent.id, config.provider, loopConfig.model || config.model || null, config.role, teamSize
|
|
1964
|
+
).catch(() => {});
|
|
1965
|
+
} catch (e) { /* fail silent */ }
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1933
1968
|
loop.on('output', (output) => {
|
|
1934
1969
|
this._handleAgentOutput(newAgent.id, output);
|
|
1970
|
+
if (this.daemon.trajectoryCapture) {
|
|
1971
|
+
try { this.daemon.trajectoryCapture.onParsedOutput(newAgent.id, output); } catch (e) { /* fail silent */ }
|
|
1972
|
+
}
|
|
1935
1973
|
});
|
|
1936
1974
|
|
|
1937
1975
|
loop.on('exit', ({ code, signal, status }) => {
|
|
@@ -1960,6 +1998,22 @@ For normal file edits within your scope, proceed without review.
|
|
|
1960
1998
|
});
|
|
1961
1999
|
}
|
|
1962
2000
|
|
|
2001
|
+
if (this.daemon.trajectoryCapture) {
|
|
2002
|
+
try {
|
|
2003
|
+
if (status === 'completed') {
|
|
2004
|
+
this.daemon.trajectoryCapture.onAgentComplete(newAgent.id, {
|
|
2005
|
+
status: 'SUCCESS', exit_code: code || 0, signal,
|
|
2006
|
+
});
|
|
2007
|
+
} else {
|
|
2008
|
+
this.daemon.trajectoryCapture.onAgentCrash(newAgent.id,
|
|
2009
|
+
signal ? 'Killed by signal ' + signal : 'Exit status ' + status
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
const count = (this.daemon.state.get('training_sessions_captured') || 0) + 1;
|
|
2013
|
+
this.daemon.state.set('training_sessions_captured', count);
|
|
2014
|
+
} catch (e) { /* fail silent */ }
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1963
2017
|
this.daemon.broadcast({ type: 'agent:exit', agentId: newAgent.id, code: code || 0, signal, status });
|
|
1964
2018
|
if (this.daemon.integrations) this.daemon.integrations.refreshMcpJson();
|
|
1965
2019
|
|
|
@@ -46,7 +46,7 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
46
46
|
|
|
47
47
|
static isInstalled() {
|
|
48
48
|
try {
|
|
49
|
-
const cmd = process.platform === 'win32' ? 'where claude' : 'which claude';
|
|
49
|
+
const cmd = process.platform === 'win32' ? 'where claude' : 'bash -lc "which claude"';
|
|
50
50
|
execSync(cmd, { stdio: 'ignore' });
|
|
51
51
|
return true;
|
|
52
52
|
} catch {
|
|
@@ -55,14 +55,14 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
static isAuthenticated() {
|
|
58
|
-
|
|
59
|
-
const settingsPath = resolve(home, '.claude', 'settings.json');
|
|
60
|
-
if (!existsSync(settingsPath)) return { authenticated: false, reason: 'Claude Code not configured' };
|
|
58
|
+
if (!ClaudeCodeProvider.isInstalled()) return { authenticated: false, reason: 'Claude Code not installed' };
|
|
61
59
|
try {
|
|
62
|
-
execSync('claude --
|
|
63
|
-
|
|
60
|
+
const out = execSync('bash -lc "claude auth status --json"', { encoding: 'utf8', timeout: 10_000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
61
|
+
const data = JSON.parse(out);
|
|
62
|
+
const method = data.authMethod || data.auth_method || 'subscription';
|
|
63
|
+
return { authenticated: true, method };
|
|
64
64
|
} catch {
|
|
65
|
-
return { authenticated: false, reason: '
|
|
65
|
+
return { authenticated: false, reason: 'Not logged in. Run: claude auth login' };
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -49,7 +49,7 @@ export class CodexProvider extends Provider {
|
|
|
49
49
|
|
|
50
50
|
static isInstalled() {
|
|
51
51
|
try {
|
|
52
|
-
const cmd = process.platform === 'win32' ? 'where codex' : 'which codex';
|
|
52
|
+
const cmd = process.platform === 'win32' ? 'where codex' : 'bash -lc "which codex"';
|
|
53
53
|
execSync(cmd, { stdio: 'ignore' });
|
|
54
54
|
return true;
|
|
55
55
|
} catch {
|
|
@@ -41,7 +41,7 @@ export class GeminiProvider extends Provider {
|
|
|
41
41
|
|
|
42
42
|
static isInstalled() {
|
|
43
43
|
try {
|
|
44
|
-
const cmd = process.platform === 'win32' ? 'where gemini' : 'which gemini';
|
|
44
|
+
const cmd = process.platform === 'win32' ? 'where gemini' : 'bash -lc "which gemini"';
|
|
45
45
|
execSync(cmd, { stdio: 'ignore' });
|
|
46
46
|
return true;
|
|
47
47
|
} catch {
|