groove-dev 0.27.26 → 0.27.28
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/.groove-staging/state.json +3 -0
- package/.groove-staging/timeline.json +13 -0
- package/CLAUDE.md +0 -10
- package/DECENTRALIZED_NET_WP_V1.md +871 -0
- package/README.md +28 -0
- package/SECURITY_SWEEP.md +228 -0
- package/decentralized-net/ACTION_PLAN.md +422 -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/introducer.js +7 -7
- package/node_modules/@groove-dev/daemon/src/journalist.js +36 -6
- package/node_modules/@groove-dev/daemon/src/memory.js +29 -10
- package/node_modules/@groove-dev/daemon/src/process.js +29 -12
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +26 -1
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +34 -11
- package/node_modules/@groove-dev/daemon/src/rotator.js +24 -1
- package/node_modules/@groove-dev/daemon/test/introducer.test.js +63 -0
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +106 -0
- package/node_modules/@groove-dev/daemon/test/memory.test.js +49 -0
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +99 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-DieCV-v1.js → index-Ch1N9G4Z.js} +1728 -1728
- 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 +147 -21
- 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/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/introducer.js +7 -7
- package/packages/daemon/src/journalist.js +36 -6
- package/packages/daemon/src/memory.js +29 -10
- package/packages/daemon/src/process.js +29 -12
- package/packages/daemon/src/providers/claude-code.js +26 -1
- package/packages/daemon/src/providers/codex.js +34 -11
- package/packages/daemon/src/rotator.js +24 -1
- package/packages/gui/dist/assets/{index-DieCV-v1.js → index-Ch1N9G4Z.js} +1728 -1728
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-config.jsx +147 -21
- 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/MUST_FIX_ISSUES.md +0 -305
|
@@ -4,42 +4,7 @@ import { cn } from '../../lib/cn';
|
|
|
4
4
|
import { Badge } from '../ui/badge';
|
|
5
5
|
import { fmtNum } from '../../lib/format';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export const INTEGRATION_LOGOS = {
|
|
9
|
-
'google-workspace': 'https://cdn.simpleicons.org/google/white',
|
|
10
|
-
github: 'https://cdn.simpleicons.org/github/white',
|
|
11
|
-
stripe: 'https://cdn.simpleicons.org/stripe/635BFF',
|
|
12
|
-
gmail: 'https://cdn.simpleicons.org/gmail/EA4335',
|
|
13
|
-
'google-calendar': 'https://cdn.simpleicons.org/googlecalendar/4285F4',
|
|
14
|
-
'google-drive': 'https://cdn.simpleicons.org/googledrive/4285F4',
|
|
15
|
-
'google-docs': 'https://cdn.simpleicons.org/googledocs/4285F4',
|
|
16
|
-
'google-sheets': 'https://cdn.simpleicons.org/googlesheets/34A853',
|
|
17
|
-
'google-slides': 'https://cdn.simpleicons.org/googleslides/FBBC04',
|
|
18
|
-
'google-maps': 'https://cdn.simpleicons.org/googlemaps/4285F4',
|
|
19
|
-
postgres: 'https://cdn.simpleicons.org/postgresql/4169E1',
|
|
20
|
-
notion: 'https://cdn.simpleicons.org/notion/white',
|
|
21
|
-
linear: 'https://cdn.simpleicons.org/linear/5E6AD2',
|
|
22
|
-
'brave-search': 'https://cdn.simpleicons.org/brave/FB542B',
|
|
23
|
-
'home-assistant': 'https://cdn.simpleicons.org/homeassistant/18BCF2',
|
|
24
|
-
sentry: 'https://cdn.simpleicons.org/sentry/362D59',
|
|
25
|
-
elevenlabs: 'https://cdn.simpleicons.org/elevenlabs/white',
|
|
26
|
-
hubspot: 'https://cdn.simpleicons.org/hubspot/FF7A59',
|
|
27
|
-
jira: 'https://cdn.simpleicons.org/jira/0052CC',
|
|
28
|
-
sendgrid: 'https://cdn.simpleicons.org/sendgrid/1A82E2',
|
|
29
|
-
resend: 'https://cdn.simpleicons.org/resend/white',
|
|
30
|
-
replicate: 'https://cdn.simpleicons.org/replicate/white',
|
|
31
|
-
vercel: 'https://cdn.simpleicons.org/vercel/white',
|
|
32
|
-
supabase: 'https://cdn.simpleicons.org/supabase/3FCF8E',
|
|
33
|
-
mixpanel: 'https://cdn.simpleicons.org/mixpanel/7856FF',
|
|
34
|
-
datadog: 'https://cdn.simpleicons.org/datadog/632CA6',
|
|
35
|
-
airtable: 'https://cdn.simpleicons.org/airtable/18BFFF',
|
|
36
|
-
zendesk: 'https://cdn.simpleicons.org/zendesk/03363D',
|
|
37
|
-
intercom: 'https://cdn.simpleicons.org/intercom/6AFDEF',
|
|
38
|
-
twilio: 'https://cdn.simpleicons.org/twilio/F22F46',
|
|
39
|
-
telnyx: 'https://cdn.simpleicons.org/telnyx/00C08B',
|
|
40
|
-
aws: 'https://cdn.simpleicons.org/amazonaws/FF9900',
|
|
41
|
-
plaid: 'https://cdn.simpleicons.org/plaid/white',
|
|
42
|
-
};
|
|
7
|
+
import { INTEGRATION_LOGOS } from '../../lib/integration-logos';
|
|
43
8
|
|
|
44
9
|
function ItemIcon({ item, size = 40 }) {
|
|
45
10
|
const logoUrl = INTEGRATION_LOGOS[item.id];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
|
|
3
|
+
export const INTEGRATION_LOGOS = {
|
|
4
|
+
'google-workspace': 'https://cdn.simpleicons.org/google/white',
|
|
5
|
+
slack: 'https://cdn.simpleicons.org/slack/E01E5A',
|
|
6
|
+
discord: 'https://cdn.simpleicons.org/discord/5865F2',
|
|
7
|
+
github: 'https://cdn.simpleicons.org/github/white',
|
|
8
|
+
stripe: 'https://cdn.simpleicons.org/stripe/635BFF',
|
|
9
|
+
gmail: 'https://cdn.simpleicons.org/gmail/EA4335',
|
|
10
|
+
'google-calendar': 'https://cdn.simpleicons.org/googlecalendar/4285F4',
|
|
11
|
+
'google-drive': 'https://cdn.simpleicons.org/googledrive/4285F4',
|
|
12
|
+
'google-docs': 'https://cdn.simpleicons.org/googledocs/4285F4',
|
|
13
|
+
'google-sheets': 'https://cdn.simpleicons.org/googlesheets/34A853',
|
|
14
|
+
'google-slides': 'https://cdn.simpleicons.org/googleslides/FBBC04',
|
|
15
|
+
'google-maps': 'https://cdn.simpleicons.org/googlemaps/4285F4',
|
|
16
|
+
postgres: 'https://cdn.simpleicons.org/postgresql/4169E1',
|
|
17
|
+
notion: 'https://cdn.simpleicons.org/notion/white',
|
|
18
|
+
linear: 'https://cdn.simpleicons.org/linear/5E6AD2',
|
|
19
|
+
'brave-search': 'https://cdn.simpleicons.org/brave/FB542B',
|
|
20
|
+
'home-assistant': 'https://cdn.simpleicons.org/homeassistant/18BCF2',
|
|
21
|
+
sentry: 'https://cdn.simpleicons.org/sentry/362D59',
|
|
22
|
+
elevenlabs: 'https://cdn.simpleicons.org/elevenlabs/white',
|
|
23
|
+
hubspot: 'https://cdn.simpleicons.org/hubspot/FF7A59',
|
|
24
|
+
jira: 'https://cdn.simpleicons.org/jira/0052CC',
|
|
25
|
+
sendgrid: 'https://cdn.simpleicons.org/sendgrid/1A82E2',
|
|
26
|
+
resend: 'https://cdn.simpleicons.org/resend/white',
|
|
27
|
+
replicate: 'https://cdn.simpleicons.org/replicate/white',
|
|
28
|
+
vercel: 'https://cdn.simpleicons.org/vercel/white',
|
|
29
|
+
supabase: 'https://cdn.simpleicons.org/supabase/3FCF8E',
|
|
30
|
+
mixpanel: 'https://cdn.simpleicons.org/mixpanel/7856FF',
|
|
31
|
+
datadog: 'https://cdn.simpleicons.org/datadog/632CA6',
|
|
32
|
+
airtable: 'https://cdn.simpleicons.org/airtable/18BFFF',
|
|
33
|
+
zendesk: 'https://cdn.simpleicons.org/zendesk/03363D',
|
|
34
|
+
intercom: 'https://cdn.simpleicons.org/intercom/6AFDEF',
|
|
35
|
+
twilio: 'https://cdn.simpleicons.org/twilio/F22F46',
|
|
36
|
+
telnyx: 'https://cdn.simpleicons.org/telnyx/00C08B',
|
|
37
|
+
aws: 'https://cdn.simpleicons.org/amazonaws/FF9900',
|
|
38
|
+
plaid: 'https://cdn.simpleicons.org/plaid/white',
|
|
39
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.28",
|
|
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)",
|
|
@@ -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) => {
|
|
@@ -14,7 +14,7 @@ export class Introducer {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
generateContext(newAgent, options = {}) {
|
|
17
|
-
const { taskNegotiation, hasTask } = options;
|
|
17
|
+
const { taskNegotiation, hasTask, isRotation } = options;
|
|
18
18
|
const agents = this.daemon.registry.getAll();
|
|
19
19
|
// Only include ACTIVE agents — not completed/killed ones from previous sessions
|
|
20
20
|
// Completed agents' work is captured in the journalist's project map, not here
|
|
@@ -353,13 +353,13 @@ export class Introducer {
|
|
|
353
353
|
parts.push(`### Constraints (read carefully)\n${constraints}`);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
if (hasTask) {
|
|
357
|
-
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role,
|
|
356
|
+
if (hasTask || isRotation) {
|
|
357
|
+
const discoveries = this.daemon.memory.getDiscoveriesMarkdown(newAgent.role, 8, 600, newAgent.scope);
|
|
358
358
|
if (discoveries) {
|
|
359
359
|
parts.push(`### Known Fixes for ${newAgent.role} Role\n${discoveries}`);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
const handoffs = this.daemon.memory.getRecentHandoffMarkdown(newAgent.role, 2, 1000, newAgent.workingDir);
|
|
362
|
+
const handoffs = this.daemon.memory.getRecentHandoffMarkdown(newAgent.role, 2, 1000, newAgent.workingDir, newAgent.teamId);
|
|
363
363
|
if (handoffs) {
|
|
364
364
|
parts.push(`### Recent Handoff History\n${handoffs}`);
|
|
365
365
|
}
|
|
@@ -367,9 +367,9 @@ export class Introducer {
|
|
|
367
367
|
|
|
368
368
|
if (parts.length > 0) {
|
|
369
369
|
memorySection = `\n## Project Memory (auto-generated)\n\n${parts.join('\n\n')}\n`;
|
|
370
|
-
// Hard budget:
|
|
371
|
-
if (memorySection.length >
|
|
372
|
-
memorySection = memorySection.slice(0,
|
|
370
|
+
// Hard budget: 3K chars total
|
|
371
|
+
if (memorySection.length > 3000) {
|
|
372
|
+
memorySection = memorySection.slice(0, 2997) + '...';
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
}
|
|
@@ -6,8 +6,9 @@ import { resolve } from 'path';
|
|
|
6
6
|
import { execFile, spawn as cpSpawn } from 'child_process';
|
|
7
7
|
import { getProvider, getInstalledProviders } from './providers/index.js';
|
|
8
8
|
|
|
9
|
-
const DEFAULT_INTERVAL =
|
|
9
|
+
const DEFAULT_INTERVAL = 300_000; // 5 minutes (safety-net fallback; event-driven triggers handle the normal case)
|
|
10
10
|
const MAX_LOG_CHARS = 100_000; // ~25k tokens budget for synthesis input (captures 80-90% of recent activity)
|
|
11
|
+
const DEBOUNCE_MS = 10_000; // requestSynthesis debounce window
|
|
11
12
|
|
|
12
13
|
export class Journalist {
|
|
13
14
|
constructor(daemon) {
|
|
@@ -19,6 +20,8 @@ export class Journalist {
|
|
|
19
20
|
this.synthesizing = false;
|
|
20
21
|
this.lastSynthesis = null; // last synthesis result text
|
|
21
22
|
this.history = []; // recent synthesis summaries
|
|
23
|
+
this._debounceTimer = null;
|
|
24
|
+
this._debounceReason = null;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
start(intervalMs = DEFAULT_INTERVAL) {
|
|
@@ -51,6 +54,33 @@ export class Journalist {
|
|
|
51
54
|
clearInterval(this.interval);
|
|
52
55
|
this.interval = null;
|
|
53
56
|
}
|
|
57
|
+
if (this._debounceTimer) {
|
|
58
|
+
clearTimeout(this._debounceTimer);
|
|
59
|
+
this._debounceTimer = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
requestSynthesis(reason = 'unknown') {
|
|
64
|
+
if (this._debounceTimer) {
|
|
65
|
+
this._debounceReason = reason;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this._debounceReason = reason;
|
|
69
|
+
this._debounceTimer = setTimeout(() => {
|
|
70
|
+
const r = this._debounceReason;
|
|
71
|
+
this._debounceTimer = null;
|
|
72
|
+
this._debounceReason = null;
|
|
73
|
+
this.cycle().catch((err) => {
|
|
74
|
+
console.error(` Journalist requestSynthesis(${r}) failed:`, err.message);
|
|
75
|
+
});
|
|
76
|
+
}, DEBOUNCE_MS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async ensureFresh(maxAgeMs = 30000) {
|
|
80
|
+
if (this.lastCycleAt && (Date.now() - this.lastCycleAt) < maxAgeMs) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await this.cycle();
|
|
54
84
|
}
|
|
55
85
|
|
|
56
86
|
async cycle() {
|
|
@@ -454,11 +484,11 @@ export class Journalist {
|
|
|
454
484
|
}
|
|
455
485
|
const provider = getProvider(providerId);
|
|
456
486
|
|
|
457
|
-
// Pick
|
|
458
|
-
const
|
|
459
|
-
|| provider.constructor.models?.find((m) => m.tier === '
|
|
487
|
+
// Pick medium tier for higher-quality synthesis (fewer but better cycles)
|
|
488
|
+
const selectedModel = provider.constructor.models?.find((m) => m.tier === 'medium')
|
|
489
|
+
|| provider.constructor.models?.find((m) => m.tier === 'light')
|
|
460
490
|
|| provider.constructor.models?.[0];
|
|
461
|
-
const modelId =
|
|
491
|
+
const modelId = selectedModel?.id || null;
|
|
462
492
|
|
|
463
493
|
const headlessCmd = provider.buildHeadlessCommand(prompt, modelId);
|
|
464
494
|
const { command, args, env, stdin: stdinData } = headlessCmd;
|
|
@@ -749,7 +779,7 @@ export class Journalist {
|
|
|
749
779
|
// Pull recent rotation history from persistent memory (Layer 7).
|
|
750
780
|
// Gives the new agent causal continuity: what the last 3 agents struggled
|
|
751
781
|
// with, decided, and solved — not just what the current session did.
|
|
752
|
-
const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role, 3, 3000, agent.workingDir) || '';
|
|
782
|
+
const recentChain = this.daemon.memory?.getRecentHandoffMarkdown(agent.role, 3, 3000, agent.workingDir, agent.teamId) || '';
|
|
753
783
|
|
|
754
784
|
// Pull the user's recent messages scoped to this agent
|
|
755
785
|
const agentFeedback = this.getUserFeedback(agent.id).slice(-5);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, appendFileSync, statSync } from 'fs';
|
|
13
13
|
import { resolve, relative } from 'path';
|
|
14
14
|
import { createHash } from 'crypto';
|
|
15
|
+
import { minimatch } from 'minimatch';
|
|
15
16
|
|
|
16
17
|
const MAX_CONSTRAINTS = 50;
|
|
17
18
|
const MAX_HANDOFF_ROTATIONS = 25;
|
|
@@ -141,7 +142,12 @@ export class MemoryStore {
|
|
|
141
142
|
return safeName(rel);
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
_chainPath(role, workingDir) {
|
|
145
|
+
_chainPath(role, workingDir, teamId) {
|
|
146
|
+
if (teamId) {
|
|
147
|
+
const dir = resolve(this.handoffDir, safeName(teamId));
|
|
148
|
+
mkdirSync(dir, { recursive: true });
|
|
149
|
+
return resolve(dir, `${safeName(role)}.md`);
|
|
150
|
+
}
|
|
145
151
|
const slug = this._workspaceSlug(workingDir);
|
|
146
152
|
if (slug) {
|
|
147
153
|
const dir = resolve(this.handoffDir, slug);
|
|
@@ -151,8 +157,8 @@ export class MemoryStore {
|
|
|
151
157
|
return resolve(this.handoffDir, `${safeName(role)}.md`);
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
getHandoffChain(role, workingDir) {
|
|
155
|
-
const path = this._chainPath(role, workingDir);
|
|
160
|
+
getHandoffChain(role, workingDir, teamId) {
|
|
161
|
+
const path = this._chainPath(role, workingDir, teamId);
|
|
156
162
|
if (!existsSync(path)) return [];
|
|
157
163
|
try {
|
|
158
164
|
const content = readFileSync(path, 'utf8');
|
|
@@ -173,9 +179,9 @@ export class MemoryStore {
|
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
|
|
176
|
-
appendHandoffBrief(role, entry, workingDir) {
|
|
182
|
+
appendHandoffBrief(role, entry, workingDir, teamId) {
|
|
177
183
|
if (!role || !entry) return false;
|
|
178
|
-
const chain = this.getHandoffChain(role, workingDir);
|
|
184
|
+
const chain = this.getHandoffChain(role, workingDir, teamId);
|
|
179
185
|
const nextN = (chain[0]?.rotationN || 0) + 1;
|
|
180
186
|
|
|
181
187
|
const block = [
|
|
@@ -203,15 +209,15 @@ export class MemoryStore {
|
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
try {
|
|
206
|
-
writeFileSync(this._chainPath(role, workingDir), lines.join('\n'));
|
|
212
|
+
writeFileSync(this._chainPath(role, workingDir, teamId), lines.join('\n'));
|
|
207
213
|
return true;
|
|
208
214
|
} catch {
|
|
209
215
|
return false;
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
getRecentHandoffMarkdown(role, count = 3, maxChars = 4000, workingDir) {
|
|
214
|
-
const chain = this.getHandoffChain(role, workingDir);
|
|
219
|
+
getRecentHandoffMarkdown(role, count = 3, maxChars = 4000, workingDir, teamId) {
|
|
220
|
+
const chain = this.getHandoffChain(role, workingDir, teamId);
|
|
215
221
|
if (chain.length === 0) return '';
|
|
216
222
|
const recent = chain.slice(0, count);
|
|
217
223
|
const out = recent.map((e) => e.body || '').join('\n\n---\n\n');
|
|
@@ -300,9 +306,22 @@ export class MemoryStore {
|
|
|
300
306
|
} catch { /* best-effort */ }
|
|
301
307
|
}
|
|
302
308
|
|
|
303
|
-
getDiscoveriesMarkdown(role, limit = 20, maxChars = 4000) {
|
|
304
|
-
|
|
309
|
+
getDiscoveriesMarkdown(role, limit = 20, maxChars = 4000, scope) {
|
|
310
|
+
let entries = this.listDiscoveries({ role, limit: limit * 3 });
|
|
305
311
|
if (entries.length === 0) return '';
|
|
312
|
+
|
|
313
|
+
if (scope && Array.isArray(scope) && scope.length > 0) {
|
|
314
|
+
const filtered = entries.filter((d) => {
|
|
315
|
+
const file = d.fix || '';
|
|
316
|
+
const rel = file.startsWith(this.projectDir + '/') ? file.slice(this.projectDir.length + 1) : file;
|
|
317
|
+
return scope.some((pattern) => minimatch(rel, pattern, { dot: true }));
|
|
318
|
+
});
|
|
319
|
+
if (filtered.length >= 3) {
|
|
320
|
+
entries = filtered;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
entries = entries.slice(0, limit);
|
|
306
325
|
const lines = entries.map((d) => `- When \`${d.trigger}\` → fix: ${d.fix}`);
|
|
307
326
|
return truncate(lines.join('\n'), maxChars);
|
|
308
327
|
}
|
|
@@ -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 = {
|
|
@@ -427,12 +439,18 @@ export class ProcessManager {
|
|
|
427
439
|
taskNegotiation = await this.negotiateTaskSplit(agent, sameRole);
|
|
428
440
|
}
|
|
429
441
|
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
//
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
const
|
|
442
|
+
// Compute hasTask from actual prompt content — agents spawned without a
|
|
443
|
+
// prompt should NOT receive handoff history (prevents cross-team contamination).
|
|
444
|
+
// Discoveries + constraints are always injected (project knowledge).
|
|
445
|
+
// Handoffs are injected only when the agent has a real task or is a rotation.
|
|
446
|
+
const hasTask = !!(config.prompt && config.prompt.trim().length > 0);
|
|
447
|
+
const isRotation = !!(config.isRotation);
|
|
448
|
+
const introContext = introducer.generateContext(agent, { taskNegotiation, hasTask, isRotation });
|
|
449
|
+
|
|
450
|
+
// Ensure the project map is fresh before the new agent reads CLAUDE.md
|
|
451
|
+
if (this.daemon.journalist) {
|
|
452
|
+
await this.daemon.journalist.ensureFresh(30000);
|
|
453
|
+
}
|
|
436
454
|
|
|
437
455
|
// Track cold-start savings — agent gets context from planner/journalist/team
|
|
438
456
|
// instead of exploring the codebase from scratch
|
|
@@ -597,7 +615,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
597
615
|
|
|
598
616
|
this.daemon.broadcast({ type: 'agent:exit', agentId: agent.id, code: code || 0, signal, status });
|
|
599
617
|
if (this.daemon.integrations) this.daemon.integrations.refreshMcpJson();
|
|
600
|
-
if (status === 'completed' && this.daemon.journalist) this.daemon.journalist.
|
|
618
|
+
if (status === 'completed' && this.daemon.journalist) this.daemon.journalist.requestSynthesis('completion');
|
|
601
619
|
this._checkPhase2(agent.id);
|
|
602
620
|
|
|
603
621
|
// Auto-trigger idle QC + process cross-scope handoffs
|
|
@@ -783,10 +801,9 @@ For normal file edits within your scope, proceed without review.
|
|
|
783
801
|
}
|
|
784
802
|
}
|
|
785
803
|
|
|
786
|
-
// Trigger journalist synthesis
|
|
787
|
-
// map is fresh for the next agent that spawns (don't wait for 120s cycle)
|
|
804
|
+
// Trigger journalist synthesis on completion (event-driven, debounced)
|
|
788
805
|
if (finalStatus === 'completed' && this.daemon.journalist) {
|
|
789
|
-
this.daemon.journalist.
|
|
806
|
+
this.daemon.journalist.requestSynthesis('completion');
|
|
790
807
|
}
|
|
791
808
|
|
|
792
809
|
// Phase 2 auto-spawn: check if all phase 1 agents for a team are done
|
|
@@ -1168,7 +1185,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1168
1185
|
oldTokens: agentData?.tokensUsed || 0,
|
|
1169
1186
|
contextUsage: agentData?.contextUsage || 0,
|
|
1170
1187
|
brief: brief.slice(0, 4000),
|
|
1171
|
-
}, agent.workingDir);
|
|
1188
|
+
}, agent.workingDir, agent.teamId);
|
|
1172
1189
|
} catch { /* best-effort */ }
|
|
1173
1190
|
}
|
|
1174
1191
|
|
|
@@ -1369,7 +1386,7 @@ For normal file edits within your scope, proceed without review.
|
|
|
1369
1386
|
registry.update(newAgent.id, { status: finalStatus, pid: null });
|
|
1370
1387
|
this.daemon.broadcast({ type: 'agent:exit', agentId: newAgent.id, code, signal, status: finalStatus });
|
|
1371
1388
|
if (finalStatus === 'completed' && this.daemon.journalist) {
|
|
1372
|
-
this.daemon.journalist.
|
|
1389
|
+
this.daemon.journalist.requestSynthesis('completion');
|
|
1373
1390
|
}
|
|
1374
1391
|
});
|
|
1375
1392
|
|
|
@@ -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
|
}
|