groove-dev 0.25.13 → 0.25.15

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.
@@ -316,6 +316,12 @@ export class Daemon {
316
316
  // returns data on every startup (not just first run)
317
317
  this.journalist.seedFromInitMap();
318
318
 
319
+ // Feed project size to token tracker for dynamic cold-start estimation
320
+ const stats = this.indexer.getStatus().stats;
321
+ if (stats) {
322
+ this.tokens.setProjectStats(stats.totalFiles, stats.totalDirs);
323
+ }
324
+
319
325
  resolvePromise(this);
320
326
  });
321
327
  });
@@ -4,7 +4,7 @@
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
  import { execFile } from 'child_process';
7
- import { getProvider } from './providers/index.js';
7
+ import { getProvider, getInstalledProviders } from './providers/index.js';
8
8
 
9
9
  const DEFAULT_INTERVAL = 120_000; // 2 minutes
10
10
  const MAX_LOG_CHARS = 40_000; // ~10k tokens budget for synthesis input
@@ -309,13 +309,23 @@ export class Journalist {
309
309
  }
310
310
 
311
311
  async callHeadless(prompt) {
312
- const provider = getProvider('claude-code');
313
- if (!provider || !provider.constructor.isInstalled()) {
312
+ // Find the best available provider for headless synthesis
313
+ // Priority: claude-code (cheapest via Haiku) > gemini > codex > ollama
314
+ const priority = ['claude-code', 'gemini', 'codex', 'ollama'];
315
+ const installed = getInstalledProviders();
316
+ const providerId = priority.find((p) => installed.some((i) => i.id === p));
317
+ if (!providerId) {
314
318
  throw new Error('No provider available for synthesis');
315
319
  }
320
+ const provider = getProvider(providerId);
316
321
 
317
- // Use headless mode with Haiku — cheapest model, good enough for synthesis
318
- const { command, args, env } = provider.buildHeadlessCommand(prompt, 'claude-haiku-4-5-20251001');
322
+ // Pick the lightest model for synthesis (cheapest/fastest)
323
+ const lightModel = provider.constructor.models?.find((m) => m.tier === 'light')
324
+ || provider.constructor.models?.find((m) => m.tier === 'medium')
325
+ || provider.constructor.models?.[0];
326
+ const modelId = lightModel?.id || null;
327
+
328
+ const { command, args, env } = provider.buildHeadlessCommand(prompt, modelId);
319
329
 
320
330
  return new Promise((resolve, reject) => {
321
331
  const proc = execFile(command, args, {
@@ -4,9 +4,12 @@
4
4
  import { readFileSync, writeFileSync, existsSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
 
7
- // Estimated tokens wasted per cold-start without GROOVE context
8
- // (agent explores codebase, reads files, discovers structure, builds mental model)
9
- const COLD_START_OVERHEAD = 8000;
7
+ // Base tokens wasted per cold-start (minimum overhead for any project)
8
+ const COLD_START_BASE = 3000;
9
+ // Additional tokens per file the agent would scan during cold discovery
10
+ const COLD_START_PER_FILE = 15;
11
+ // Additional tokens per directory traversed
12
+ const COLD_START_PER_DIR = 40;
10
13
  // Estimated tokens wasted per file conflict (agent discovers, backs off, retries)
11
14
  const CONFLICT_OVERHEAD = 500;
12
15
 
@@ -15,9 +18,11 @@ export class TokenTracker {
15
18
  this.path = resolve(grooveDir, 'tokens.json');
16
19
  this.usage = {};
17
20
  this.sessionStart = Date.now();
18
- this.rotationSavings = 0; // Tokens saved by rotation (context that would have degraded)
21
+ this.rotationSavings = 0;
19
22
  this.conflictsPrevented = 0;
20
23
  this.coldStartsSkipped = 0;
24
+ this.projectFiles = 0; // Set from indexer stats
25
+ this.projectDirs = 0;
21
26
  this.load();
22
27
  }
23
28
 
@@ -145,6 +150,19 @@ export class TokenTracker {
145
150
  this.save();
146
151
  }
147
152
 
153
+ // Set project size from indexer for dynamic cold-start estimation
154
+ setProjectStats(totalFiles, totalDirs) {
155
+ this.projectFiles = totalFiles || 0;
156
+ this.projectDirs = totalDirs || 0;
157
+ }
158
+
159
+ // Calculate cold-start overhead based on project size
160
+ getColdStartOverhead() {
161
+ return COLD_START_BASE
162
+ + (this.projectFiles * COLD_START_PER_FILE)
163
+ + (this.projectDirs * COLD_START_PER_DIR);
164
+ }
165
+
148
166
  getAgent(agentId) {
149
167
  return this.usage[agentId] || { total: 0, sessions: [] };
150
168
  }
@@ -195,7 +213,8 @@ export class TokenTracker {
195
213
  }
196
214
 
197
215
  // Estimate what uncoordinated usage would have cost
198
- const coldStartWaste = this.coldStartsSkipped * COLD_START_OVERHEAD;
216
+ const coldStartOverhead = this.getColdStartOverhead();
217
+ const coldStartWaste = this.coldStartsSkipped * coldStartOverhead;
199
218
  const conflictWaste = this.conflictsPrevented * CONFLICT_OVERHEAD;
200
219
  const totalSavings = this.rotationSavings + coldStartWaste + conflictWaste;
201
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.25.13",
3
+ "version": "0.25.15",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. 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.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -316,6 +316,12 @@ export class Daemon {
316
316
  // returns data on every startup (not just first run)
317
317
  this.journalist.seedFromInitMap();
318
318
 
319
+ // Feed project size to token tracker for dynamic cold-start estimation
320
+ const stats = this.indexer.getStatus().stats;
321
+ if (stats) {
322
+ this.tokens.setProjectStats(stats.totalFiles, stats.totalDirs);
323
+ }
324
+
319
325
  resolvePromise(this);
320
326
  });
321
327
  });
@@ -4,7 +4,7 @@
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
  import { execFile } from 'child_process';
7
- import { getProvider } from './providers/index.js';
7
+ import { getProvider, getInstalledProviders } from './providers/index.js';
8
8
 
9
9
  const DEFAULT_INTERVAL = 120_000; // 2 minutes
10
10
  const MAX_LOG_CHARS = 40_000; // ~10k tokens budget for synthesis input
@@ -309,13 +309,23 @@ export class Journalist {
309
309
  }
310
310
 
311
311
  async callHeadless(prompt) {
312
- const provider = getProvider('claude-code');
313
- if (!provider || !provider.constructor.isInstalled()) {
312
+ // Find the best available provider for headless synthesis
313
+ // Priority: claude-code (cheapest via Haiku) > gemini > codex > ollama
314
+ const priority = ['claude-code', 'gemini', 'codex', 'ollama'];
315
+ const installed = getInstalledProviders();
316
+ const providerId = priority.find((p) => installed.some((i) => i.id === p));
317
+ if (!providerId) {
314
318
  throw new Error('No provider available for synthesis');
315
319
  }
320
+ const provider = getProvider(providerId);
316
321
 
317
- // Use headless mode with Haiku — cheapest model, good enough for synthesis
318
- const { command, args, env } = provider.buildHeadlessCommand(prompt, 'claude-haiku-4-5-20251001');
322
+ // Pick the lightest model for synthesis (cheapest/fastest)
323
+ const lightModel = provider.constructor.models?.find((m) => m.tier === 'light')
324
+ || provider.constructor.models?.find((m) => m.tier === 'medium')
325
+ || provider.constructor.models?.[0];
326
+ const modelId = lightModel?.id || null;
327
+
328
+ const { command, args, env } = provider.buildHeadlessCommand(prompt, modelId);
319
329
 
320
330
  return new Promise((resolve, reject) => {
321
331
  const proc = execFile(command, args, {
@@ -4,9 +4,12 @@
4
4
  import { readFileSync, writeFileSync, existsSync } from 'fs';
5
5
  import { resolve } from 'path';
6
6
 
7
- // Estimated tokens wasted per cold-start without GROOVE context
8
- // (agent explores codebase, reads files, discovers structure, builds mental model)
9
- const COLD_START_OVERHEAD = 8000;
7
+ // Base tokens wasted per cold-start (minimum overhead for any project)
8
+ const COLD_START_BASE = 3000;
9
+ // Additional tokens per file the agent would scan during cold discovery
10
+ const COLD_START_PER_FILE = 15;
11
+ // Additional tokens per directory traversed
12
+ const COLD_START_PER_DIR = 40;
10
13
  // Estimated tokens wasted per file conflict (agent discovers, backs off, retries)
11
14
  const CONFLICT_OVERHEAD = 500;
12
15
 
@@ -15,9 +18,11 @@ export class TokenTracker {
15
18
  this.path = resolve(grooveDir, 'tokens.json');
16
19
  this.usage = {};
17
20
  this.sessionStart = Date.now();
18
- this.rotationSavings = 0; // Tokens saved by rotation (context that would have degraded)
21
+ this.rotationSavings = 0;
19
22
  this.conflictsPrevented = 0;
20
23
  this.coldStartsSkipped = 0;
24
+ this.projectFiles = 0; // Set from indexer stats
25
+ this.projectDirs = 0;
21
26
  this.load();
22
27
  }
23
28
 
@@ -145,6 +150,19 @@ export class TokenTracker {
145
150
  this.save();
146
151
  }
147
152
 
153
+ // Set project size from indexer for dynamic cold-start estimation
154
+ setProjectStats(totalFiles, totalDirs) {
155
+ this.projectFiles = totalFiles || 0;
156
+ this.projectDirs = totalDirs || 0;
157
+ }
158
+
159
+ // Calculate cold-start overhead based on project size
160
+ getColdStartOverhead() {
161
+ return COLD_START_BASE
162
+ + (this.projectFiles * COLD_START_PER_FILE)
163
+ + (this.projectDirs * COLD_START_PER_DIR);
164
+ }
165
+
148
166
  getAgent(agentId) {
149
167
  return this.usage[agentId] || { total: 0, sessions: [] };
150
168
  }
@@ -195,7 +213,8 @@ export class TokenTracker {
195
213
  }
196
214
 
197
215
  // Estimate what uncoordinated usage would have cost
198
- const coldStartWaste = this.coldStartsSkipped * COLD_START_OVERHEAD;
216
+ const coldStartOverhead = this.getColdStartOverhead();
217
+ const coldStartWaste = this.coldStartsSkipped * coldStartOverhead;
199
218
  const conflictWaste = this.conflictsPrevented * CONFLICT_OVERHEAD;
200
219
  const totalSavings = this.rotationSavings + coldStartWaste + conflictWaste;
201
220