groove-dev 0.25.14 → 0.25.16

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>Groove GUI</title>
8
- <script type="module" crossorigin src="/assets/index-Cg1mJi9s.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-BjUplOVu.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
@@ -294,18 +294,29 @@ function ActivityLine({ entry }) {
294
294
  );
295
295
  }
296
296
 
297
- function ActivityGroup({ entries }) {
297
+ function ActivityGroup({ entries, isLive }) {
298
298
  const [cycleIdx, setCycleIdx] = useState(0);
299
299
 
300
- // Cycle through entries every 1.5s
301
300
  useEffect(() => {
302
- if (entries.length <= 1) return;
301
+ if (!isLive || entries.length <= 1) return;
303
302
  const timer = setInterval(() => setCycleIdx((i) => (i + 1) % entries.length), 1500);
304
303
  return () => clearInterval(timer);
305
- }, [entries.length]);
304
+ }, [entries.length, isLive]);
305
+
306
+ if (!isLive) {
307
+ // Collapsed static summary for completed groups
308
+ const last = entries[entries.length - 1];
309
+ const meta = activityMeta(last.text);
310
+ const Icon = meta.icon;
311
+ return (
312
+ <div className="ml-7 flex items-center gap-2 px-3 py-1 text-[10px] text-text-4 font-mono">
313
+ <Icon size={10} className="opacity-50" />
314
+ <span className="truncate">{entries.length} tool call{entries.length !== 1 ? 's' : ''}</span>
315
+ </div>
316
+ );
317
+ }
306
318
 
307
319
  const current = entries[Math.min(cycleIdx, entries.length - 1)];
308
- const meta = activityMeta(current.text);
309
320
  const display = current.text?.length > 60 ? current.text.slice(0, 60) + '...' : current.text;
310
321
 
311
322
  return (
@@ -584,7 +595,9 @@ export function AgentFeed({ agent }) {
584
595
  )}
585
596
  {timeline.map((item, i) => {
586
597
  if (item.kind === 'activity-group') {
587
- return <ActivityGroup key={`grp-${i}`} entries={item.entries} />;
598
+ // Only the last activity group is "live" if agent is still running
599
+ const isLastGroup = !timeline.slice(i + 1).some((t) => t.kind === 'activity-group' || t.from === 'agent');
600
+ return <ActivityGroup key={`grp-${i}`} entries={item.entries} isLive={isAlive && isLastGroup} />;
588
601
  }
589
602
  if (item.from === 'user') return <UserMessage key={`msg-${i}`} msg={item} />;
590
603
  if (item.from === 'system') return <SystemMessage key={`msg-${i}`} msg={item} />;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.25.14",
3
+ "version": "0.25.16",
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)",
@@ -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, {