groove-dev 0.27.28 → 0.27.29

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.
@@ -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-Ch1N9G4Z.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CNsQ3n1t.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.28",
3
+ "version": "0.27.29",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -366,7 +366,7 @@ export function AgentConfig({ agent }) {
366
366
  <div className="space-y-1.5">
367
367
  {providers.map((p) => {
368
368
  const isActive = p.id === agent.provider;
369
- const available = p.installed || p.hasKey;
369
+ const available = p.authType === 'subscription' ? (p.installed || p.authStatus?.authenticated) : p.authType === 'local' ? p.installed : (p.installed && p.hasKey);
370
370
  const isExpanded = expandedProvider === p.id;
371
371
  const models = p.models || [];
372
372
  return (
@@ -381,7 +381,7 @@ export function AgentConfig({ agent }) {
381
381
  {p.name || p.id}
382
382
  </span>
383
383
  {isActive && <Badge variant="accent" className="text-2xs">Active</Badge>}
384
- {!available && <span className="text-2xs text-text-4 font-sans">{p.authType === 'local' ? 'Not installed' : 'No key'}</span>}
384
+ {!available && <span className="text-2xs text-text-4 font-sans">{!p.installed ? 'Not installed' : 'No key'}</span>}
385
385
  <ChevronDown size={12} className={cn('text-text-4 transition-transform', isExpanded && 'rotate-180')} />
386
386
  </button>
387
387
 
@@ -52,7 +52,7 @@ function ProviderCard({ provider, onKeyChange }) {
52
52
  const isSubscription = provider.authType === 'subscription';
53
53
  const isReady = isLocal ? provider.installed
54
54
  : isSubscription ? (provider.installed || provider.authStatus?.authenticated)
55
- : provider.hasKey;
55
+ : (provider.installed && provider.hasKey);
56
56
 
57
57
  async function handleSetKey() {
58
58
  if (!keyInput.trim()) return;
@@ -149,7 +149,7 @@ function ProviderCard({ provider, onKeyChange }) {
149
149
  {isReady ? (
150
150
  <Badge variant="success" className="text-2xs gap-1"><Check size={8} /> Ready</Badge>
151
151
  ) : (
152
- <Badge variant="default" className="text-2xs">{isSubscription ? 'Not installed' : 'No key'}</Badge>
152
+ <Badge variant="default" className="text-2xs">{!provider.installed ? 'Not installed' : isSubscription ? 'Not authenticated' : 'No key'}</Badge>
153
153
  )}
154
154
  </div>
155
155
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.28",
3
+ "version": "0.27.29",
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)",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.28",
3
+ "version": "0.27.29",
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.28",
3
+ "version": "0.27.29",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -166,7 +166,7 @@ export class Journalist {
166
166
  return false;
167
167
  }
168
168
 
169
- collectFilteredLogs(agents) {
169
+ collectFilteredLogs(agents, { since } = {}) {
170
170
  const result = {};
171
171
 
172
172
  for (const agent of agents) {
@@ -181,7 +181,7 @@ export class Journalist {
181
181
  const size = Buffer.byteLength(content);
182
182
  this.lastLogSizes[agent.id] = size;
183
183
 
184
- const { entries, explorationEntries } = this.filterLog(content, agent);
184
+ const { entries, explorationEntries } = this.filterLog(content, agent, { since });
185
185
  result[agent.id] = { agent, entries, explorationEntries };
186
186
  } catch {
187
187
  result[agent.id] = { agent, entries: [], explorationEntries: [] };
@@ -191,7 +191,7 @@ export class Journalist {
191
191
  return result;
192
192
  }
193
193
 
194
- filterLog(rawLog, agent) {
194
+ filterLog(rawLog, agent, { since } = {}) {
195
195
  // Parse stream-json lines and extract meaningful events.
196
196
  // Focus on PROGRESS (writes, edits, commands with results) not EXPLORATION (reads, greps).
197
197
  // Exploration tools are tracked separately so handoff briefs can include what was examined.
@@ -200,12 +200,15 @@ export class Journalist {
200
200
  const lines = rawLog.split('\n');
201
201
  const toolResults = new Map(); // tool_use_id -> result text
202
202
 
203
+ const sinceDate = since ? new Date(since) : null;
204
+
203
205
  // First pass: collect tool results (and error flags) so we can attach them to tool calls
204
206
  const toolErrors = new Set(); // tool_use_ids that returned errors
205
207
  for (const line of lines) {
206
208
  if (!line.trim() || line.startsWith('[')) continue;
207
209
  try {
208
210
  const data = JSON.parse(line);
211
+ if (sinceDate && data.timestamp && new Date(data.timestamp) < sinceDate) continue;
209
212
  if (data.type === 'user' && data.message?.content) {
210
213
  const content = Array.isArray(data.message.content) ? data.message.content : [];
211
214
  for (const block of content) {
@@ -227,6 +230,7 @@ export class Journalist {
227
230
 
228
231
  try {
229
232
  const data = JSON.parse(line);
233
+ if (sinceDate && data.timestamp && new Date(data.timestamp) < sinceDate) continue;
230
234
 
231
235
  // Tool use — only keep WRITES, EDITS, and COMMANDS (progress, not exploration)
232
236
  if (data.type === 'assistant' && data.message?.content) {
@@ -415,6 +419,56 @@ export class Journalist {
415
419
  return parts.join('\n');
416
420
  }
417
421
 
422
+ buildRotationSynthesisPrompt(agent, entries, options = {}) {
423
+ const dir = agent.workingDir ? `\nWorking directory: ${agent.workingDir}` : '';
424
+ const reason = options.reason || 'manual rotation';
425
+
426
+ const parts = [
427
+ 'You are briefing the next AI agent taking over this exact role.',
428
+ `The previous agent (${agent.name}, role: ${agent.role}) is being rotated out.`,
429
+ `Scope: ${agent.scope?.join(', ') || 'unrestricted'}${dir}`,
430
+ `Rotation reason: ${reason}`,
431
+ '',
432
+ 'Analyze the session log below and produce a structured handoff brief.',
433
+ '',
434
+ 'Output EXACTLY these sections:',
435
+ '',
436
+ '## Accomplishments',
437
+ '(What was completed. Name files, functions, and line numbers.)',
438
+ '',
439
+ '## In Progress',
440
+ '(What was actively being worked on when rotation happened.)',
441
+ '',
442
+ '## Key Decisions',
443
+ '(Architectural or implementation choices made and why.)',
444
+ '',
445
+ '## Blockers/Errors',
446
+ '(Unresolved errors, failed attempts, things that did not work.)',
447
+ '',
448
+ '## Next Steps',
449
+ '(What should be done next, in priority order.)',
450
+ '',
451
+ 'Be specific. Name files, functions, and line numbers. Do not summarize vaguely.',
452
+ 'Keep your response under 2000 characters.',
453
+ '',
454
+ '---',
455
+ '',
456
+ `### Session Log for ${agent.name} (${agent.role})`,
457
+ '',
458
+ ];
459
+
460
+ let totalChars = 0;
461
+ const cap = 30_000;
462
+ for (const entry of entries.slice(-200)) {
463
+ const line = this.formatEntry(entry);
464
+ if (totalChars + line.length > cap) break;
465
+ parts.push(line);
466
+ totalChars += line.length;
467
+ }
468
+
469
+ return parts.join('\n');
470
+ }
471
+
418
472
  formatEntry(entry) {
419
473
  switch (entry.type) {
420
474
  case 'tool': {
@@ -739,35 +793,10 @@ export class Journalist {
739
793
  // --- Handoff Brief for Context Rotation ---
740
794
 
741
795
  async generateHandoffBrief(agent, options = {}) {
742
- const filteredLogs = this.collectFilteredLogs([agent]);
796
+ const filteredLogs = this.collectFilteredLogs([agent], { since: agent.spawnedAt });
743
797
  const agentLog = filteredLogs[agent.id];
744
798
  const entries = agentLog?.entries || [];
745
799
 
746
- const errorSummary = entries
747
- .filter((e) => e.type === 'error')
748
- .map((e) => `- ${e.text}`)
749
- .slice(-10)
750
- .join('\n');
751
-
752
- const resultSummary = entries
753
- .filter((e) => e.type === 'result')
754
- .map((e) => e.text)
755
- .slice(-3)
756
- .join('\n');
757
-
758
- // Build file changes section — group Edit/Write operations by file path
759
- const fileChanges = {};
760
- for (const e of entries.filter((e) => e.type === 'tool' && (e.tool === 'Edit' || e.tool === 'Write'))) {
761
- const file = e.input || 'unknown';
762
- if (!fileChanges[file]) fileChanges[file] = [];
763
- if (e.diff) fileChanges[file].push(e.diff);
764
- else fileChanges[file].push(e.tool === 'Write' ? 'created' : 'modified');
765
- }
766
- const fileChangesSummary = Object.entries(fileChanges)
767
- .map(([file, changes]) => `- **${file}**: ${changes.slice(0, 3).join('; ')}`)
768
- .slice(0, 20)
769
- .join('\n');
770
-
771
800
  // Layer 7 memory: discoveries, constraints, specializations
772
801
  const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 2000) || '';
773
802
  const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
@@ -776,26 +805,58 @@ export class Journalist {
776
805
  ? `- Quality profile: ${specialization.avgQualityScore}/100 across ${specialization.sessionCount} sessions`
777
806
  : '';
778
807
 
779
- // Pull recent rotation history from persistent memory (Layer 7).
780
- // Gives the new agent causal continuity: what the last 3 agents struggled
781
- // with, decided, and solved — not just what the current session did.
782
808
  const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role, 3, 3000, agent.workingDir, agent.teamId) || '';
783
809
 
784
- // Pull the user's recent messages scoped to this agent
785
810
  const agentFeedback = this.getUserFeedback(agent.id).slice(-5);
786
811
  const conversationSummary = agentFeedback.length > 0
787
812
  ? agentFeedback.map((fb) => `- "${fb.message}"`).join('\n')
788
813
  : '';
789
814
 
790
- // Compact last 5 tool calls (not full output, just tool + target)
791
- const recentTools = entries
792
- .filter((e) => e.type === 'tool' || e.type === 'error')
793
- .slice(-5)
794
- .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 80)}`)
795
- .join('\n');
815
+ // Try AI-synthesized session summary
816
+ let sessionSummary = '';
817
+ try {
818
+ const prompt = this.buildRotationSynthesisPrompt(agent, entries, options);
819
+ sessionSummary = await this.callHeadless(prompt, { trackAs: '__rotation__' });
820
+ } catch {
821
+ // Fallback: structural summary from raw logs
822
+ const errorSummary = entries
823
+ .filter((e) => e.type === 'error')
824
+ .map((e) => `- ${e.text}`)
825
+ .slice(-10)
826
+ .join('\n');
827
+
828
+ const resultSummary = entries
829
+ .filter((e) => e.type === 'result')
830
+ .map((e) => e.text)
831
+ .slice(-3)
832
+ .join('\n');
833
+
834
+ const fileChanges = {};
835
+ for (const e of entries.filter((e) => e.type === 'tool' && (e.tool === 'Edit' || e.tool === 'Write'))) {
836
+ const file = e.input || 'unknown';
837
+ if (!fileChanges[file]) fileChanges[file] = [];
838
+ if (e.diff) fileChanges[file].push(e.diff);
839
+ else fileChanges[file].push(e.tool === 'Write' ? 'created' : 'modified');
840
+ }
841
+ const fileChangesSummary = Object.entries(fileChanges)
842
+ .map(([file, changes]) => `- **${file}**: ${changes.slice(0, 3).join('; ')}`)
843
+ .slice(0, 20)
844
+ .join('\n');
845
+
846
+ const recentTools = entries
847
+ .filter((e) => e.type === 'tool' || e.type === 'error')
848
+ .slice(-5)
849
+ .map((e) => `- ${e.type === 'error' ? 'ERROR ' : ''}${e.tool}: ${(e.input || '').slice(0, 80)}`)
850
+ .join('\n');
851
+
852
+ const fallbackParts = [];
853
+ if (errorSummary) fallbackParts.push(`## Unresolved Errors\n\n${errorSummary}`);
854
+ if (recentTools) fallbackParts.push(`## Last 5 Tool Calls\n\n${recentTools}`);
855
+ if (fileChangesSummary) fallbackParts.push(`## Files Modified\n\n${fileChangesSummary}`);
856
+ if (resultSummary) fallbackParts.push(`## Accomplishments\n\n${resultSummary}`);
857
+ sessionSummary = fallbackParts.join('\n\n');
858
+ }
796
859
 
797
- // Brief priority: errors > constraints > recent tools > accomplishments
798
- // The rotator already wraps this with session continuation context
799
860
  return [
800
861
  `# Handoff Brief — ${agent.name} (${agent.role})`,
801
862
  ``,
@@ -804,13 +865,10 @@ export class Journalist {
804
865
  `Rotation: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''} | Tokens: ${agent.tokensUsed}`,
805
866
  specLine,
806
867
  ``,
807
- errorSummary ? `## Unresolved Errors (fix these first)\n\n${errorSummary}\n` : '',
868
+ sessionSummary ? `## Session Summary\n\n${sessionSummary}\n` : '',
808
869
  constraints ? `## Project Constraints (must follow)\n\n${constraints}\n` : '',
809
870
  discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
810
871
  conversationSummary ? `## Recent User Messages\n\n${conversationSummary}\n` : '',
811
- recentTools ? `## Last 5 Tool Calls\n\n${recentTools}\n` : '',
812
- fileChangesSummary ? `## Files Modified\n\n${fileChangesSummary}\n` : '',
813
- resultSummary ? `## Accomplishments\n\n${resultSummary}\n` : '',
814
872
  recentChain ? `## Rotation History\n\n${recentChain}\n` : '',
815
873
  agent.prompt ? `## Original Task\n\n${agent.prompt}\n` : '',
816
874
  ``,