groove-dev 0.26.32 → 0.26.35
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/CHANGELOG.md +55 -0
- package/node_modules/@groove-dev/daemon/src/api.js +3 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +13 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +146 -28
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -2
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +3 -3
- package/node_modules/@groove-dev/gui/dist/assets/index-86xvrzfI.js +638 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CEFKgLGB.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +11 -8
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +511 -0
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +3 -1
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +26 -4
- package/package.json +1 -1
- package/packages/daemon/src/api.js +3 -0
- package/packages/daemon/src/introducer.js +13 -0
- package/packages/daemon/src/journalist.js +146 -28
- package/packages/daemon/src/providers/claude-code.js +4 -2
- package/packages/gui/dist/assets/index-86xvrzfI.js +638 -0
- package/packages/gui/dist/assets/index-CEFKgLGB.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +11 -8
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +511 -0
- package/packages/gui/src/views/dashboard.jsx +3 -1
- package/packages/gui/src/views/marketplace.jsx +26 -4
- package/node_modules/@groove-dev/gui/dist/assets/index-BnNZzcsd.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-CwUZRfEx.js +0 -638
- package/packages/gui/dist/assets/index-BnNZzcsd.css +0 -1
- package/packages/gui/dist/assets/index-CwUZRfEx.js +0 -638
|
@@ -57,9 +57,18 @@ export class Journalist {
|
|
|
57
57
|
if (this.synthesizing) return; // Don't overlap
|
|
58
58
|
|
|
59
59
|
const agents = this.daemon.registry.getAll();
|
|
60
|
-
const
|
|
60
|
+
const running = agents.filter((a) => a.status === 'running');
|
|
61
|
+
|
|
62
|
+
// Include recently completed agents (last 30 min) so their work persists
|
|
63
|
+
// in the project map instead of vanishing the moment they finish
|
|
64
|
+
const thirtyMinAgo = new Date(Date.now() - 30 * 60 * 1000).toISOString();
|
|
65
|
+
const recentlyCompleted = agents.filter((a) =>
|
|
66
|
+
a.status === 'completed' && a.lastActivity && a.lastActivity > thirtyMinAgo
|
|
67
|
+
&& !running.some((r) => r.id === a.id)
|
|
68
|
+
);
|
|
69
|
+
const activeAgents = [...running, ...recentlyCompleted];
|
|
61
70
|
|
|
62
|
-
// Skip if no
|
|
71
|
+
// Skip if no agents to synthesize
|
|
63
72
|
if (activeAgents.length === 0) return;
|
|
64
73
|
|
|
65
74
|
// Smart scheduling: skip if no new log output since last cycle
|
|
@@ -149,33 +158,75 @@ export class Journalist {
|
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
filterLog(rawLog, agent) {
|
|
152
|
-
// Parse stream-json lines and extract meaningful events
|
|
161
|
+
// Parse stream-json lines and extract meaningful events.
|
|
162
|
+
// Focus on PROGRESS (writes, edits, commands with results) not EXPLORATION (reads, greps).
|
|
153
163
|
const entries = [];
|
|
154
164
|
const lines = rawLog.split('\n');
|
|
165
|
+
const toolResults = new Map(); // tool_use_id -> result text
|
|
155
166
|
|
|
167
|
+
// First pass: collect tool results so we can attach them to tool calls
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
if (!line.trim() || line.startsWith('[')) continue;
|
|
170
|
+
try {
|
|
171
|
+
const data = JSON.parse(line);
|
|
172
|
+
if (data.type === 'user' && data.message?.content) {
|
|
173
|
+
const content = Array.isArray(data.message.content) ? data.message.content : [];
|
|
174
|
+
for (const block of content) {
|
|
175
|
+
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
176
|
+
const text = typeof block.content === 'string' ? block.content
|
|
177
|
+
: Array.isArray(block.content) ? block.content.map((c) => c.text || '').join('').slice(0, 300)
|
|
178
|
+
: '';
|
|
179
|
+
if (text) toolResults.set(block.tool_use_id, text);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch { /* skip */ }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Second pass: extract meaningful events
|
|
156
187
|
for (const line of lines) {
|
|
157
|
-
// Skip empty lines and GROOVE spawn headers
|
|
158
188
|
if (!line.trim() || line.startsWith('[')) continue;
|
|
159
189
|
|
|
160
190
|
try {
|
|
161
191
|
const data = JSON.parse(line);
|
|
162
192
|
|
|
163
|
-
// Tool use
|
|
193
|
+
// Tool use — only keep WRITES, EDITS, and COMMANDS (progress, not exploration)
|
|
164
194
|
if (data.type === 'assistant' && data.message?.content) {
|
|
165
195
|
const content = data.message.content;
|
|
166
196
|
if (Array.isArray(content)) {
|
|
167
197
|
for (const block of content) {
|
|
168
198
|
if (block.type === 'tool_use') {
|
|
169
|
-
|
|
199
|
+
const tool = block.name;
|
|
200
|
+
|
|
201
|
+
// Skip exploration tools — reads/searches are noise for synthesis
|
|
202
|
+
if (tool === 'Read' || tool === 'Glob' || tool === 'Grep') continue;
|
|
203
|
+
|
|
204
|
+
const entry = {
|
|
170
205
|
type: 'tool',
|
|
171
|
-
tool
|
|
172
|
-
input: this.summarizeToolInput(
|
|
206
|
+
tool,
|
|
207
|
+
input: this.summarizeToolInput(tool, block.input),
|
|
173
208
|
timestamp: data.timestamp,
|
|
174
|
-
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Capture what was actually changed for Edit operations
|
|
212
|
+
if (tool === 'Edit' && block.input) {
|
|
213
|
+
const old = (block.input.old_string || '').slice(0, 150);
|
|
214
|
+
const nw = (block.input.new_string || '').slice(0, 150);
|
|
215
|
+
if (old && nw) entry.diff = `"${old}" → "${nw}"`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Capture Bash command output (exit code, first line of result)
|
|
219
|
+
if (tool === 'Bash' && block.id) {
|
|
220
|
+
const result = toolResults.get(block.id);
|
|
221
|
+
if (result) entry.output = result.slice(0, 200);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
entries.push(entry);
|
|
175
225
|
} else if (block.type === 'text' && block.text) {
|
|
176
|
-
// Only keep substantial
|
|
226
|
+
// Only keep substantial reasoning (decisions, conclusions)
|
|
227
|
+
// Short fragments like "Let me check..." are noise
|
|
177
228
|
const text = block.text.trim();
|
|
178
|
-
if (text.length >
|
|
229
|
+
if (text.length > 200) {
|
|
179
230
|
entries.push({ type: 'thinking', text: text.slice(0, 2000), timestamp: data.timestamp });
|
|
180
231
|
}
|
|
181
232
|
}
|
|
@@ -194,10 +245,8 @@ export class Journalist {
|
|
|
194
245
|
});
|
|
195
246
|
}
|
|
196
247
|
} catch {
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
entries.push({ type: 'error', text: line.trim().slice(0, 200) });
|
|
200
|
-
}
|
|
248
|
+
// Skip non-JSON lines entirely — transient stderr like ENOENT
|
|
249
|
+
// causes degradation when agents try to "fix" phantom errors
|
|
201
250
|
}
|
|
202
251
|
}
|
|
203
252
|
|
|
@@ -208,15 +257,11 @@ export class Journalist {
|
|
|
208
257
|
if (!input) return '';
|
|
209
258
|
switch (toolName) {
|
|
210
259
|
case 'Write':
|
|
211
|
-
case 'Edit':
|
|
212
260
|
return input.file_path || input.path || '';
|
|
213
|
-
case '
|
|
261
|
+
case 'Edit':
|
|
214
262
|
return input.file_path || input.path || '';
|
|
215
263
|
case 'Bash':
|
|
216
|
-
return (input.command || '').slice(0,
|
|
217
|
-
case 'Glob':
|
|
218
|
-
case 'Grep':
|
|
219
|
-
return input.pattern || '';
|
|
264
|
+
return (input.command || '').slice(0, 150);
|
|
220
265
|
default:
|
|
221
266
|
return JSON.stringify(input).slice(0, 100);
|
|
222
267
|
}
|
|
@@ -295,14 +340,16 @@ export class Journalist {
|
|
|
295
340
|
|
|
296
341
|
formatEntry(entry) {
|
|
297
342
|
switch (entry.type) {
|
|
298
|
-
case 'tool':
|
|
299
|
-
|
|
343
|
+
case 'tool': {
|
|
344
|
+
let line = `- [${entry.tool}] ${entry.input}`;
|
|
345
|
+
if (entry.diff) line += ` — changed: ${entry.diff}`;
|
|
346
|
+
if (entry.output) line += ` → ${entry.output.split('\n')[0]}`;
|
|
347
|
+
return line;
|
|
348
|
+
}
|
|
300
349
|
case 'thinking':
|
|
301
|
-
return `- [thought] ${entry.text.slice(0,
|
|
350
|
+
return `- [thought] ${entry.text.slice(0, 300)}`;
|
|
302
351
|
case 'result':
|
|
303
|
-
return `- [result] ${entry.text.slice(0,
|
|
304
|
-
case 'error':
|
|
305
|
-
return `- [error] ${entry.text}`;
|
|
352
|
+
return `- [result] ${entry.text.slice(0, 300)} (${entry.turns} turns, ${entry.duration}ms)`;
|
|
306
353
|
default:
|
|
307
354
|
return `- [${entry.type}] ${entry.text || ''}`;
|
|
308
355
|
}
|
|
@@ -515,8 +562,25 @@ export class Journalist {
|
|
|
515
562
|
existing = readFileSync(path, 'utf8').replace(/^# GROOVE Decisions Log[\s\S]*?\n\n/, '');
|
|
516
563
|
}
|
|
517
564
|
|
|
565
|
+
// Deduplicate — skip if the new decisions are substantially similar to the most recent entry.
|
|
566
|
+
// Extract the last cycle's decisions text for comparison.
|
|
567
|
+
if (existing) {
|
|
568
|
+
const lastEntry = existing.match(/^## Cycle[\s\S]*?\n\n([\s\S]*?)(?=\n---|\n\n\*Auto|\n## Cycle|$)/);
|
|
569
|
+
if (lastEntry) {
|
|
570
|
+
const lastText = lastEntry[1].trim().toLowerCase().replace(/\s+/g, ' ');
|
|
571
|
+
const newText = decisions.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
572
|
+
// If >60% of the new text appears in the last entry, skip (near-duplicate)
|
|
573
|
+
const newWords = newText.split(' ').filter((w) => w.length > 3);
|
|
574
|
+
const overlap = newWords.filter((w) => lastText.includes(w)).length;
|
|
575
|
+
if (newWords.length > 0 && overlap / newWords.length > 0.6) return;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
518
579
|
const entry = `## Cycle ${this.cycleCount} — ${new Date().toISOString()}\n\n${decisions}\n\n`;
|
|
519
|
-
|
|
580
|
+
|
|
581
|
+
// Keep last 20 entries max to prevent unbounded growth
|
|
582
|
+
const entries = (entry + existing).split(/(?=^## Cycle)/m).slice(0, 20).join('');
|
|
583
|
+
writeFileSync(path, header + entries);
|
|
520
584
|
}
|
|
521
585
|
|
|
522
586
|
writeAgentSessionLogs(agents, filteredLogs) {
|
|
@@ -724,6 +788,60 @@ export class Journalist {
|
|
|
724
788
|
}
|
|
725
789
|
}
|
|
726
790
|
|
|
791
|
+
// --- User Feedback Tracking ---
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Record user feedback/messages sent to agents.
|
|
795
|
+
* This gets included in the project map and handoff briefs so future
|
|
796
|
+
* agents know what the user said about previous work.
|
|
797
|
+
*/
|
|
798
|
+
recordUserFeedback(agent, message) {
|
|
799
|
+
if (!this._userFeedback) this._loadFeedback();
|
|
800
|
+
if (!this._userFeedback[agent.id]) this._userFeedback[agent.id] = [];
|
|
801
|
+
this._userFeedback[agent.id].push({
|
|
802
|
+
agentName: agent.name,
|
|
803
|
+
role: agent.role,
|
|
804
|
+
message: message.slice(0, 500),
|
|
805
|
+
timestamp: new Date().toISOString(),
|
|
806
|
+
});
|
|
807
|
+
// Keep last 20 per agent
|
|
808
|
+
if (this._userFeedback[agent.id].length > 20) {
|
|
809
|
+
this._userFeedback[agent.id] = this._userFeedback[agent.id].slice(-20);
|
|
810
|
+
}
|
|
811
|
+
this._saveFeedback();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
_loadFeedback() {
|
|
815
|
+
const p = resolve(this.daemon.grooveDir, 'user-feedback.json');
|
|
816
|
+
try {
|
|
817
|
+
if (existsSync(p)) this._userFeedback = JSON.parse(readFileSync(p, 'utf8'));
|
|
818
|
+
else this._userFeedback = {};
|
|
819
|
+
} catch { this._userFeedback = {}; }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
_saveFeedback() {
|
|
823
|
+
try {
|
|
824
|
+
writeFileSync(resolve(this.daemon.grooveDir, 'user-feedback.json'), JSON.stringify(this._userFeedback, null, 2));
|
|
825
|
+
} catch { /* non-fatal */ }
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Get user feedback for an agent (or all agents in a team).
|
|
830
|
+
* Used by the introducer to give agents context about previous attempts.
|
|
831
|
+
*/
|
|
832
|
+
getUserFeedback(agentOrTeamId) {
|
|
833
|
+
if (!this._userFeedback) this._loadFeedback();
|
|
834
|
+
if (!this._userFeedback) return [];
|
|
835
|
+
// Check if it's an agent ID
|
|
836
|
+
if (this._userFeedback[agentOrTeamId]) return this._userFeedback[agentOrTeamId];
|
|
837
|
+
// Check by team — collect feedback for all agents in the team
|
|
838
|
+
const all = [];
|
|
839
|
+
for (const [, entries] of Object.entries(this._userFeedback)) {
|
|
840
|
+
all.push(...entries);
|
|
841
|
+
}
|
|
842
|
+
return all.sort((a, b) => a.timestamp.localeCompare(b.timestamp)).slice(-30);
|
|
843
|
+
}
|
|
844
|
+
|
|
727
845
|
// --- Accessors ---
|
|
728
846
|
|
|
729
847
|
getLastSynthesis() {
|
|
@@ -75,9 +75,11 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
buildHeadlessCommand(prompt, model) {
|
|
78
|
-
|
|
78
|
+
// Pass prompt via stdin to avoid OS argument length limits.
|
|
79
|
+
// Long prompts (journalist synthesis with agent logs) can exceed ARG_MAX.
|
|
80
|
+
const args = ['-p', '--output-format', 'stream-json', '--verbose'];
|
|
79
81
|
if (model) args.push('--model', model);
|
|
80
|
-
return { command: 'claude', args, env: {} };
|
|
82
|
+
return { command: 'claude', args, env: {}, stdin: prompt };
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
buildFullPrompt(agent) {
|