groove-dev 0.27.104 → 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.
Files changed (33) hide show
  1. package/moe-training/client/trajectory-capture.js +36 -0
  2. package/moe-training/test/client/trajectory-capture.test.js +104 -0
  3. package/node_modules/@groove-dev/cli/package.json +1 -1
  4. package/node_modules/@groove-dev/daemon/package.json +1 -1
  5. package/node_modules/@groove-dev/daemon/src/api.js +2 -4
  6. package/node_modules/@groove-dev/daemon/src/process.js +54 -0
  7. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +7 -7
  8. package/node_modules/@groove-dev/daemon/src/providers/codex.js +1 -1
  9. package/node_modules/@groove-dev/daemon/src/providers/gemini.js +1 -1
  10. package/node_modules/@groove-dev/gui/dist/assets/{index-oUBAPJv6.js → index-BN7fQKaF.js} +22 -22
  11. package/node_modules/@groove-dev/gui/dist/assets/{index-C1ObKizg.css → index-QwgLRN8B.css} +1 -1
  12. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  13. package/node_modules/@groove-dev/gui/package.json +1 -1
  14. package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +6 -0
  15. package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +29 -5
  16. package/node_modules/@groove-dev/gui/src/views/settings.jsx +14 -2
  17. package/node_modules/moe-training/client/trajectory-capture.js +36 -0
  18. package/node_modules/moe-training/test/client/trajectory-capture.test.js +104 -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 +2 -4
  23. package/packages/daemon/src/process.js +54 -0
  24. package/packages/daemon/src/providers/claude-code.js +7 -7
  25. package/packages/daemon/src/providers/codex.js +1 -1
  26. package/packages/daemon/src/providers/gemini.js +1 -1
  27. package/packages/gui/dist/assets/{index-oUBAPJv6.js → index-BN7fQKaF.js} +22 -22
  28. package/packages/gui/dist/assets/{index-C1ObKizg.css → index-QwgLRN8B.css} +1 -1
  29. package/packages/gui/dist/index.html +2 -2
  30. package/packages/gui/package.json +1 -1
  31. package/packages/gui/src/components/onboarding/setup-wizard.jsx +6 -0
  32. package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +29 -5
  33. package/packages/gui/src/views/settings.jsx +14 -2
@@ -154,6 +154,42 @@ export class TrajectoryCapture {
154
154
  this._processStep(agentId, ctx, classified);
155
155
  }
156
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
+
157
193
  async onAgentComplete(agentId, outcome) {
158
194
  await this._closeAgent(agentId, outcome?.status || 'SUCCESS', outcome);
159
195
  }
@@ -438,3 +438,107 @@ describe('TrajectoryCapture — _computeQuality', () => {
438
438
  assert.equal(quality, 100);
439
439
  });
440
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
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.104",
3
+ "version": "0.27.106",
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.104",
3
+ "version": "0.27.106",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -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('npm', ['install', '-g', pkg], {
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
 
@@ -4729,9 +4728,8 @@ Keep responses concise. Help them think, don't lecture them about the system the
4729
4728
 
4730
4729
  write({ status: 'installing', output: `Installing ${pkg}...`, progress: 0 });
4731
4730
 
4732
- const proc = spawn('npm', ['install', '-g', pkg], {
4731
+ const proc = spawn('bash', ['-lc', `npm install -g ${pkg}`], {
4733
4732
  stdio: ['ignore', 'pipe', 'pipe'],
4734
- shell: true,
4735
4733
  env: { ...process.env, NODE_ENV: undefined },
4736
4734
  });
4737
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
- const home = homedir();
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 --version', { stdio: 'ignore', timeout: 5000 });
63
- return { authenticated: true, method: 'subscription' };
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: 'Claude CLI not responding' };
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 {