groove-dev 0.27.27 → 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.
- package/CLAUDE.md +7 -0
- package/README.md +28 -0
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +99 -0
- package/node_modules/@groove-dev/daemon/src/journalist.js +103 -45
- package/node_modules/@groove-dev/daemon/src/process.js +12 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +26 -1
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +1 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-DieCV-v1.js → index-CNsQ3n1t.js} +1730 -1730
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +149 -23
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +206 -44
- package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +11 -24
- package/node_modules/@groove-dev/gui/src/components/marketplace/marketplace-card.jsx +1 -36
- package/node_modules/@groove-dev/gui/src/lib/integration-logos.js +39 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +2 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +99 -0
- package/packages/daemon/src/journalist.js +103 -45
- package/packages/daemon/src/process.js +12 -0
- package/packages/daemon/src/providers/claude-code.js +26 -1
- package/packages/gui/dist/assets/{index-DieCV-v1.js → index-CNsQ3n1t.js} +1730 -1730
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-config.jsx +149 -23
- package/packages/gui/src/components/agents/spawn-wizard.jsx +206 -44
- package/packages/gui/src/components/marketplace/integration-wizard.jsx +11 -24
- package/packages/gui/src/components/marketplace/marketplace-card.jsx +1 -36
- package/packages/gui/src/lib/integration-logos.js +39 -0
- package/packages/gui/src/views/settings.jsx +2 -2
package/CLAUDE.md
CHANGED
|
@@ -263,3 +263,10 @@ Audit-driven release. Multi-agent orchestration system with 7 coordination layer
|
|
|
263
263
|
- Dashboard: routing donut, cache panel, context health gauges
|
|
264
264
|
- Monitor/QC agent mode (stay active, loop)
|
|
265
265
|
- Distribution: demo video, HN launch, Twitter content
|
|
266
|
+
|
|
267
|
+
<!-- GROOVE:START -->
|
|
268
|
+
## GROOVE Orchestration (auto-injected)
|
|
269
|
+
Active agents: 0
|
|
270
|
+
See AGENTS_REGISTRY.md for full agent state.
|
|
271
|
+
**Memory policy:** GROOVE manages project memory automatically. Do not read or write MEMORY.md or .groove/memory/ files directly.
|
|
272
|
+
<!-- GROOVE:END -->
|
package/README.md
CHANGED
|
@@ -7,6 +7,20 @@ The open-source orchestration layer for AI coding tools. Spawn teams of agents,
|
|
|
7
7
|
[](https://www.npmjs.com/package/groove-dev)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
+
## Get Started
|
|
11
|
+
|
|
12
|
+
### Download the App
|
|
13
|
+
|
|
14
|
+
No terminal needed. Download, install, open a project folder.
|
|
15
|
+
|
|
16
|
+
- [**macOS** (.dmg)](https://github.com/grooveai-dev/groove/releases/latest) — Intel and Apple Silicon
|
|
17
|
+
- [**Windows** (.exe)](https://github.com/grooveai-dev/groove/releases/latest) — one-click installer
|
|
18
|
+
- [**Linux** (.AppImage)](https://github.com/grooveai-dev/groove/releases/latest) — x64 and ARM
|
|
19
|
+
|
|
20
|
+
Everything is bundled — daemon, GUI, auto-updates. No Node.js required.
|
|
21
|
+
|
|
22
|
+
### Developer Install
|
|
23
|
+
|
|
10
24
|
```bash
|
|
11
25
|
npm i -g groove-dev
|
|
12
26
|
groove start
|
|
@@ -16,6 +30,20 @@ The GUI opens at `http://localhost:31415`. On a VPS? groove detects it and tells
|
|
|
16
30
|
|
|
17
31
|
---
|
|
18
32
|
|
|
33
|
+
## Desktop App
|
|
34
|
+
|
|
35
|
+
The desktop app is the easiest way to use groove — no terminal, no dependencies, no setup.
|
|
36
|
+
|
|
37
|
+
- **Full GUI dashboard** — agent tree, chat, editor, telemetry, marketplace, all in one window
|
|
38
|
+
- **Bundled daemon** — starts automatically when you open a project, stops when you close it
|
|
39
|
+
- **System tray** — quick access to recent projects, daemon status, and controls
|
|
40
|
+
- **Automatic updates** — new versions install silently in the background
|
|
41
|
+
- **Multi-workspace** — open any project folder; manages one daemon per project
|
|
42
|
+
- **Platform support** — macOS (Intel + Apple Silicon), Windows (x64 + ARM64), Linux (x64 + ARM64)
|
|
43
|
+
- **No dependencies** — everything is bundled. No Node.js, no terminal knowledge needed
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
19
47
|
## The Problem
|
|
20
48
|
|
|
21
49
|
AI coding agents waste your money and lose their way:
|
|
@@ -9,7 +9,9 @@ import { spawn, execFile } from 'child_process';
|
|
|
9
9
|
import { lookup as mimeLookup } from './mimetypes.js';
|
|
10
10
|
import { listProviders, getProvider } from './providers/index.js';
|
|
11
11
|
import { OllamaProvider } from './providers/ollama.js';
|
|
12
|
+
import { ClaudeCodeProvider } from './providers/claude-code.js';
|
|
12
13
|
import { validateAgentConfig } from './validate.js';
|
|
14
|
+
import { ROLE_INTEGRATIONS } from './process.js';
|
|
13
15
|
|
|
14
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
17
|
const pkgVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
@@ -179,6 +181,88 @@ export function createApi(app, daemon) {
|
|
|
179
181
|
res.json({ ok: true });
|
|
180
182
|
});
|
|
181
183
|
|
|
184
|
+
// --- Role-to-Integration Mapping ---
|
|
185
|
+
|
|
186
|
+
app.get('/api/roles/integrations', (req, res) => {
|
|
187
|
+
const roleFilter = req.query.role;
|
|
188
|
+
const entries = roleFilter ? { [roleFilter]: ROLE_INTEGRATIONS[roleFilter] || [] } : ROLE_INTEGRATIONS;
|
|
189
|
+
const result = {};
|
|
190
|
+
for (const [role, ids] of Object.entries(entries)) {
|
|
191
|
+
result[role] = (ids || []).map((id) => {
|
|
192
|
+
const status = daemon.integrations.getStatus(id);
|
|
193
|
+
const entry = daemon.integrations.registry.find((r) => r.id === id);
|
|
194
|
+
return {
|
|
195
|
+
id,
|
|
196
|
+
name: entry?.name || id,
|
|
197
|
+
installed: status?.installed || false,
|
|
198
|
+
configured: status?.configured || false,
|
|
199
|
+
authenticated: status?.authenticated || false,
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (roleFilter) return res.json(result[roleFilter] || []);
|
|
204
|
+
res.json(result);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
app.post('/api/agents/preflight', (req, res) => {
|
|
208
|
+
const { role, integrations } = req.body || {};
|
|
209
|
+
if (!role || !Array.isArray(integrations)) {
|
|
210
|
+
return res.status(400).json({ error: 'role and integrations[] required' });
|
|
211
|
+
}
|
|
212
|
+
const issues = [];
|
|
213
|
+
for (const id of integrations) {
|
|
214
|
+
const status = daemon.integrations.getStatus(id);
|
|
215
|
+
const entry = daemon.integrations.registry.find((r) => r.id === id);
|
|
216
|
+
const name = entry?.name || id;
|
|
217
|
+
if (!status || !status.installed) {
|
|
218
|
+
issues.push({ integrationId: id, name, problem: 'not_installed' });
|
|
219
|
+
} else if (!status.configured) {
|
|
220
|
+
issues.push({ integrationId: id, name, problem: 'not_configured' });
|
|
221
|
+
} else if (!status.authenticated) {
|
|
222
|
+
issues.push({ integrationId: id, name, problem: 'not_authenticated' });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
res.json({ ready: issues.length === 0, issues });
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// --- Agent Integration Attach/Detach ---
|
|
229
|
+
|
|
230
|
+
app.post('/api/agents/:id/integrations/:integrationId', (req, res) => {
|
|
231
|
+
const agent = daemon.registry.get(req.params.id);
|
|
232
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
233
|
+
|
|
234
|
+
const integrationId = req.params.integrationId;
|
|
235
|
+
const status = daemon.integrations.getStatus(integrationId);
|
|
236
|
+
if (!status || !status.installed) {
|
|
237
|
+
return res.status(400).json({ error: `Integration not installed: ${integrationId}` });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const integrations = new Set(agent.integrations || []);
|
|
241
|
+
integrations.add(integrationId);
|
|
242
|
+
const updated = Array.from(integrations);
|
|
243
|
+
|
|
244
|
+
daemon.registry.update(req.params.id, { integrations: updated });
|
|
245
|
+
daemon.integrations.writeMcpJson(daemon.integrations.getActiveIntegrations());
|
|
246
|
+
daemon.integrations.refreshMcpJson();
|
|
247
|
+
daemon.audit.log('agent.integration.attach', { agentId: req.params.id, integrationId });
|
|
248
|
+
daemon.broadcast({ type: 'agent:integration:attach', agentId: req.params.id, integrationId });
|
|
249
|
+
res.json({ ok: true, integrations: updated });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
app.delete('/api/agents/:id/integrations/:integrationId', (req, res) => {
|
|
253
|
+
const agent = daemon.registry.get(req.params.id);
|
|
254
|
+
if (!agent) return res.status(404).json({ error: 'Agent not found' });
|
|
255
|
+
|
|
256
|
+
const integrationId = req.params.integrationId;
|
|
257
|
+
const integrations = (agent.integrations || []).filter((id) => id !== integrationId);
|
|
258
|
+
|
|
259
|
+
daemon.registry.update(req.params.id, { integrations });
|
|
260
|
+
daemon.integrations.refreshMcpJson();
|
|
261
|
+
daemon.audit.log('agent.integration.detach', { agentId: req.params.id, integrationId });
|
|
262
|
+
daemon.broadcast({ type: 'agent:integration:detach', agentId: req.params.id, integrationId });
|
|
263
|
+
res.json({ ok: true, integrations });
|
|
264
|
+
});
|
|
265
|
+
|
|
182
266
|
// Lock management
|
|
183
267
|
app.get('/api/locks', (req, res) => {
|
|
184
268
|
res.json(daemon.locks.getAll());
|
|
@@ -307,10 +391,25 @@ export function createApi(app, daemon) {
|
|
|
307
391
|
// Enrich with credential status
|
|
308
392
|
for (const p of providers) {
|
|
309
393
|
p.hasKey = daemon.credentials.hasKey(p.id);
|
|
394
|
+
if (p.id === 'claude-code') {
|
|
395
|
+
p.authStatus = ClaudeCodeProvider.getAuthStatus();
|
|
396
|
+
}
|
|
310
397
|
}
|
|
311
398
|
res.json(providers);
|
|
312
399
|
});
|
|
313
400
|
|
|
401
|
+
// --- Claude Code Auth ---
|
|
402
|
+
|
|
403
|
+
app.get('/api/providers/claude-code/auth', (req, res) => {
|
|
404
|
+
res.json(ClaudeCodeProvider.getAuthStatus());
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
app.post('/api/providers/claude-code/login', (req, res) => {
|
|
408
|
+
ClaudeCodeProvider.triggerLogin();
|
|
409
|
+
daemon.audit.log('claude-code.login.started', {});
|
|
410
|
+
res.json({ ok: true });
|
|
411
|
+
});
|
|
412
|
+
|
|
314
413
|
// --- Ollama ---
|
|
315
414
|
|
|
316
415
|
app.get('/api/providers/ollama/hardware', (req, res) => {
|
|
@@ -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
|
-
//
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
.
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
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
|
``,
|
|
@@ -266,6 +266,18 @@ IMPORTANT: Do not use markdown formatting like ** or ### in your output. Write i
|
|
|
266
266
|
`,
|
|
267
267
|
};
|
|
268
268
|
|
|
269
|
+
// Role-to-integration mapping — recommended integrations per role for onboarding preflight
|
|
270
|
+
export const ROLE_INTEGRATIONS = {
|
|
271
|
+
ea: ['gmail', 'google-calendar'],
|
|
272
|
+
cmo: ['gmail', 'slack', 'hubspot'],
|
|
273
|
+
cfo: ['stripe', 'google-sheets'],
|
|
274
|
+
support: ['gmail', 'slack', 'zendesk'],
|
|
275
|
+
analyst: ['google-sheets', 'postgres', 'mixpanel'],
|
|
276
|
+
home: ['home-assistant'],
|
|
277
|
+
slides: ['google-slides'],
|
|
278
|
+
creative: ['google-docs'],
|
|
279
|
+
};
|
|
280
|
+
|
|
269
281
|
// Permission-level prompt instructions
|
|
270
282
|
// "auto" = PM reviews risky ops via API. "full" = no reviews, max speed.
|
|
271
283
|
const PERMISSION_PROMPTS = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GROOVE — Claude Code Provider
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import { execSync } from 'child_process';
|
|
4
|
+
import { execSync, spawn as cpSpawn } from 'child_process';
|
|
5
5
|
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
6
6
|
import { resolve } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
@@ -223,4 +223,29 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
223
223
|
|
|
224
224
|
return merged;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
static getAuthStatus() {
|
|
228
|
+
try {
|
|
229
|
+
const out = execSync('claude auth status --json', { encoding: 'utf8', timeout: 10_000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
230
|
+
const data = JSON.parse(out);
|
|
231
|
+
return {
|
|
232
|
+
authenticated: true,
|
|
233
|
+
authMethod: data.authMethod || data.auth_method || 'unknown',
|
|
234
|
+
email: data.email || null,
|
|
235
|
+
subscriptionType: data.subscriptionType || data.subscription_type || null,
|
|
236
|
+
orgName: data.orgName || data.org_name || null,
|
|
237
|
+
};
|
|
238
|
+
} catch (err) {
|
|
239
|
+
return { authenticated: false, error: err.message };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static triggerLogin() {
|
|
244
|
+
const child = cpSpawn('claude', ['auth', 'login', '--claudeai'], {
|
|
245
|
+
detached: true,
|
|
246
|
+
stdio: 'ignore',
|
|
247
|
+
});
|
|
248
|
+
child.unref();
|
|
249
|
+
return { pid: child.pid };
|
|
250
|
+
}
|
|
226
251
|
}
|
|
@@ -201,7 +201,7 @@ describe('Journalist', () => {
|
|
|
201
201
|
assert.ok(brief.includes('src/api/**'));
|
|
202
202
|
assert.ok(brief.includes('5000'));
|
|
203
203
|
assert.ok(brief.includes('Build the auth API'));
|
|
204
|
-
assert.ok(brief.includes('Write'));
|
|
204
|
+
assert.ok(brief.includes('Session Summary') || brief.includes('Write'));
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
it('instructs the agent to deliver the output, not passively wait', async () => {
|