groove-dev 0.27.8 → 0.27.12
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-icon.png +0 -0
- package/node_modules/@groove-dev/daemon/src/api.js +460 -25
- package/node_modules/@groove-dev/daemon/src/index.js +7 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
- package/node_modules/@groove-dev/daemon/src/process.js +67 -7
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
- package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/app.css +14 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
- package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
- package/node_modules/@groove-dev/gui/src/lib/electron.js +25 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +139 -6
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +38 -39
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
- package/node_modules/@groove-dev/gui/vite.config.js +3 -0
- package/package.json +7 -2
- package/packages/daemon/src/api.js +460 -25
- package/packages/daemon/src/index.js +7 -0
- package/packages/daemon/src/introducer.js +72 -4
- package/packages/daemon/src/journalist.js +66 -11
- package/packages/daemon/src/process.js +67 -7
- package/packages/daemon/src/registry.js +1 -1
- package/packages/daemon/src/repo-import.js +541 -0
- package/packages/daemon/src/rotator.js +28 -1
- package/packages/daemon/src/supervisor.js +2 -1
- package/packages/daemon/src/tunnel-manager.js +504 -0
- package/packages/daemon/src/validate.js +13 -0
- package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
- package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
- package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
- package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
- package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
- package/packages/gui/src/app.css +14 -0
- package/packages/gui/src/app.jsx +13 -0
- package/packages/gui/src/components/agents/agent-config.jsx +130 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/packages/gui/src/components/dashboard/intel-panel.jsx +4 -4
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
- package/packages/gui/src/components/layout/app-shell.jsx +7 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/packages/gui/src/components/layout/command-palette.jsx +14 -4
- package/packages/gui/src/components/layout/status-bar.jsx +46 -11
- package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
- package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
- package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
- package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
- package/packages/gui/src/components/ui/toast.jsx +1 -1
- package/packages/gui/src/lib/edition.js +4 -0
- package/packages/gui/src/lib/electron.js +25 -0
- package/packages/gui/src/lib/status.js +1 -0
- package/packages/gui/src/stores/groove.js +139 -6
- package/packages/gui/src/views/dashboard.jsx +38 -39
- package/packages/gui/src/views/marketplace.jsx +82 -0
- package/packages/gui/src/views/settings.jsx +66 -0
- package/packages/gui/vite.config.js +3 -0
- package/integrations/FEDERATION_PLAN.md +0 -583
- package/integrations/VOICE_PLAN.md +0 -232
- package/node_modules/@groove-dev/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/dist/assets/index-CwmR3-HY.css +0 -1
- package/packages/gui/dist/assets/index-DiCjVtQL.js +0 -652
- package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
- package/test-slack.mjs +0 -28
- /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
|
@@ -36,8 +36,10 @@ import { MemoryStore } from './memory.js';
|
|
|
36
36
|
import { TerminalManager } from './terminal-pty.js';
|
|
37
37
|
import { GatewayManager } from './gateways/manager.js';
|
|
38
38
|
import { McpManager } from './mcp-manager.js';
|
|
39
|
+
import { TunnelManager } from './tunnel-manager.js';
|
|
39
40
|
import { ModelManager } from './model-manager.js';
|
|
40
41
|
import { LlamaServerManager } from './llama-server.js';
|
|
42
|
+
import { RepoImporter } from './repo-import.js';
|
|
41
43
|
import { isFirstRun, runFirstTimeSetup, loadConfig, saveConfig, printWelcome } from './firstrun.js';
|
|
42
44
|
|
|
43
45
|
const DEFAULT_PORT = 31415;
|
|
@@ -137,6 +139,8 @@ export class Daemon {
|
|
|
137
139
|
this.modelManager = new ModelManager(this);
|
|
138
140
|
this.llamaServer = new LlamaServerManager(this);
|
|
139
141
|
this.mcpManager = new McpManager(this);
|
|
142
|
+
this.tunnelManager = new TunnelManager(this);
|
|
143
|
+
this.repoImporter = new RepoImporter(this);
|
|
140
144
|
|
|
141
145
|
// HTTP + WebSocket server
|
|
142
146
|
this.app = express();
|
|
@@ -450,6 +454,9 @@ export class Daemon {
|
|
|
450
454
|
this.fileWatcher.unwatchAll();
|
|
451
455
|
this.terminalManager.killAll();
|
|
452
456
|
|
|
457
|
+
// Disconnect all SSH tunnels
|
|
458
|
+
this.tunnelManager.shutdown();
|
|
459
|
+
|
|
453
460
|
// Kill all agent processes, stop MCP servers, and stop inference servers
|
|
454
461
|
await this.processes.killAll();
|
|
455
462
|
this.mcpManager.stopAll();
|
|
@@ -37,6 +37,17 @@ export class Introducer {
|
|
|
37
37
|
lines.push(`You have no file scope restrictions.`);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// Sandbox boundary for imported repos
|
|
41
|
+
if (newAgent.workingDir) {
|
|
42
|
+
const sandboxPath = resolve(newAgent.workingDir, '.groove', 'sandbox.json');
|
|
43
|
+
if (existsSync(sandboxPath)) {
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push(`## HARD BOUNDARY`);
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push(`You MUST NOT read, write, or modify ANY file outside \`${newAgent.workingDir}/\`. This is a sandboxed imported repo. If setup instructions require changes outside this directory, ask the user first.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
lines.push('');
|
|
41
52
|
|
|
42
53
|
if (others.length === 0) {
|
|
@@ -249,7 +260,13 @@ export class Introducer {
|
|
|
249
260
|
lines.push('');
|
|
250
261
|
lines.push(`## Integrations (${integrationSections.length} connected)`);
|
|
251
262
|
lines.push('');
|
|
252
|
-
lines.push('
|
|
263
|
+
lines.push('These integrations are ALREADY INSTALLED, AUTHENTICATED, AND READY TO USE. You do NOT need to:');
|
|
264
|
+
lines.push('- Ask the user for any API keys, OAuth tokens, or credentials for these services');
|
|
265
|
+
lines.push('- Set up authentication or run any auth flows');
|
|
266
|
+
lines.push('- Direct the user to any external auth pages');
|
|
267
|
+
lines.push('The user has already configured everything. Just use the tools.');
|
|
268
|
+
lines.push('');
|
|
269
|
+
lines.push('To use them, make HTTP POST requests:');
|
|
253
270
|
lines.push('```');
|
|
254
271
|
lines.push('POST http://localhost:31415/api/integrations/{id}/exec');
|
|
255
272
|
lines.push('Body: {"tool": "tool_name", "params": {...}}');
|
|
@@ -257,14 +274,65 @@ export class Introducer {
|
|
|
257
274
|
lines.push('To discover available tools: `GET http://localhost:31415/api/integrations/{id}/tools`');
|
|
258
275
|
lines.push('');
|
|
259
276
|
lines.push('**Approval gates:** Some tools require human approval (e.g., sending emails, creating charges).');
|
|
260
|
-
lines.push('If you get a `requiresApproval: true` response
|
|
261
|
-
lines.push('
|
|
262
|
-
lines.push('
|
|
277
|
+
lines.push('If you get a `requiresApproval: true` response, the action has been queued for user approval.');
|
|
278
|
+
lines.push('GROOVE will show the user an approval modal and auto-execute the action once approved.');
|
|
279
|
+
lines.push('Do NOT tell the user to approve anything. Do NOT retry the request yourself. Just wait — you will receive a message confirming the result once the action is approved and executed.');
|
|
263
280
|
lines.push('');
|
|
264
281
|
lines.push(integrationSections.join('\n\n'));
|
|
265
282
|
}
|
|
266
283
|
}
|
|
267
284
|
|
|
285
|
+
// GitHub repo import — teach agents to use the tracked import API
|
|
286
|
+
// Attached repos — only inject repos explicitly attached to this agent
|
|
287
|
+
if (newAgent.repos && newAgent.repos.length > 0 && this.daemon.repoImporter) {
|
|
288
|
+
const repoSections = [];
|
|
289
|
+
for (const importId of newAgent.repos) {
|
|
290
|
+
const manifest = this.daemon.repoImporter.getImport(importId);
|
|
291
|
+
if (manifest && manifest.status === 'active') {
|
|
292
|
+
const stack = manifest.stackInfo ? ` (${manifest.stackInfo.runtime || 'unknown'})` : '';
|
|
293
|
+
repoSections.push(`- **${manifest.name || manifest.repo}**${stack}: \`${manifest.clonedTo}\` — import ID: ${manifest.id}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (repoSections.length > 0) {
|
|
297
|
+
lines.push('');
|
|
298
|
+
lines.push(`## Attached Repositories (${repoSections.length})`);
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push('These repos are cloned and attached to you. Use the paths below — do NOT re-clone them:');
|
|
301
|
+
lines.push(...repoSections);
|
|
302
|
+
lines.push('');
|
|
303
|
+
lines.push('If you spawn processes or modify config files for these repos, register them:');
|
|
304
|
+
lines.push('- `POST http://localhost:31415/api/repos/{importId}/process` with `{ "pid": <number>, "command": "description" }`');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Lightweight import API reference for cloning new repos
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push('## GitHub Repo Import');
|
|
311
|
+
lines.push('');
|
|
312
|
+
lines.push('To clone a NEW GitHub repo, use: `POST http://localhost:31415/api/repos/import` with `{ "repoUrl": "...", "targetPath": "~/Projects/name", "createTeam": true }`. Do NOT run `git clone` directly.');
|
|
313
|
+
|
|
314
|
+
// Surface stored API keys so agents know what's available in their environment
|
|
315
|
+
const KEY_MAP = { codex: 'OPENAI_API_KEY', gemini: 'GEMINI_API_KEY', ollama: 'OLLAMA_API_KEY' };
|
|
316
|
+
try {
|
|
317
|
+
const credProviders = this.daemon.credentials?.listProviders() || [];
|
|
318
|
+
if (credProviders.length > 0) {
|
|
319
|
+
lines.push('');
|
|
320
|
+
lines.push('## Available API Keys');
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push('GROOVE has API keys stored and injected into your environment. Do NOT ask the user for these:');
|
|
323
|
+
for (const cp of credProviders) {
|
|
324
|
+
const envVar = KEY_MAP[cp.provider];
|
|
325
|
+
if (envVar) {
|
|
326
|
+
lines.push(`- **${cp.provider}**: available as \`${envVar}\` in your environment`);
|
|
327
|
+
} else {
|
|
328
|
+
lines.push(`- **${cp.provider}**: stored in GROOVE credentials`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push('If a third-party tool needs one of these keys, it is already in your environment — do not ask the user to provide it.');
|
|
333
|
+
}
|
|
334
|
+
} catch { /* credentials not available */ }
|
|
335
|
+
|
|
268
336
|
return lines.join('\n');
|
|
269
337
|
}
|
|
270
338
|
|
|
@@ -138,7 +138,7 @@ export class Journalist {
|
|
|
138
138
|
for (const agent of agents) {
|
|
139
139
|
const logPath = resolve(this.daemon.grooveDir, 'logs', `${agent.name}.log`);
|
|
140
140
|
if (!existsSync(logPath)) {
|
|
141
|
-
result[agent.id] = { agent, entries: [] };
|
|
141
|
+
result[agent.id] = { agent, entries: [], explorationEntries: [] };
|
|
142
142
|
continue;
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -147,10 +147,10 @@ export class Journalist {
|
|
|
147
147
|
const size = Buffer.byteLength(content);
|
|
148
148
|
this.lastLogSizes[agent.id] = size;
|
|
149
149
|
|
|
150
|
-
const entries = this.filterLog(content, agent);
|
|
151
|
-
result[agent.id] = { agent, entries };
|
|
150
|
+
const { entries, explorationEntries } = this.filterLog(content, agent);
|
|
151
|
+
result[agent.id] = { agent, entries, explorationEntries };
|
|
152
152
|
} catch {
|
|
153
|
-
result[agent.id] = { agent, entries: [] };
|
|
153
|
+
result[agent.id] = { agent, entries: [], explorationEntries: [] };
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -160,7 +160,9 @@ export class Journalist {
|
|
|
160
160
|
filterLog(rawLog, agent) {
|
|
161
161
|
// Parse stream-json lines and extract meaningful events.
|
|
162
162
|
// Focus on PROGRESS (writes, edits, commands with results) not EXPLORATION (reads, greps).
|
|
163
|
+
// Exploration tools are tracked separately so handoff briefs can include what was examined.
|
|
163
164
|
const entries = [];
|
|
165
|
+
const explorationEntries = [];
|
|
164
166
|
const lines = rawLog.split('\n');
|
|
165
167
|
const toolResults = new Map(); // tool_use_id -> result text
|
|
166
168
|
|
|
@@ -198,8 +200,17 @@ export class Journalist {
|
|
|
198
200
|
if (block.type === 'tool_use') {
|
|
199
201
|
const tool = block.name;
|
|
200
202
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
+
// Track exploration tools separately — they're noise for synthesis
|
|
204
|
+
// but valuable for handoff briefs (what files/patterns were examined)
|
|
205
|
+
if (tool === 'Read' || tool === 'Glob' || tool === 'Grep') {
|
|
206
|
+
explorationEntries.push({
|
|
207
|
+
type: 'exploration',
|
|
208
|
+
tool,
|
|
209
|
+
input: this.summarizeToolInput(tool, block.input),
|
|
210
|
+
timestamp: data.timestamp,
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
203
214
|
|
|
204
215
|
const entry = {
|
|
205
216
|
type: 'tool',
|
|
@@ -223,10 +234,11 @@ export class Journalist {
|
|
|
223
234
|
|
|
224
235
|
entries.push(entry);
|
|
225
236
|
} else if (block.type === 'text' && block.text) {
|
|
226
|
-
//
|
|
227
|
-
//
|
|
237
|
+
// Keep reasoning that contains decisions or conclusions.
|
|
238
|
+
// Under 50 chars is noise ("Let me check...", "OK"), but 50-200
|
|
239
|
+
// often contains key decisions like "Using X instead of Y".
|
|
228
240
|
const text = block.text.trim();
|
|
229
|
-
if (text.length >
|
|
241
|
+
if (text.length > 50) {
|
|
230
242
|
entries.push({ type: 'thinking', text: text.slice(0, 2000), timestamp: data.timestamp });
|
|
231
243
|
}
|
|
232
244
|
}
|
|
@@ -250,7 +262,7 @@ export class Journalist {
|
|
|
250
262
|
}
|
|
251
263
|
}
|
|
252
264
|
|
|
253
|
-
return entries;
|
|
265
|
+
return { entries, explorationEntries };
|
|
254
266
|
}
|
|
255
267
|
|
|
256
268
|
summarizeToolInput(toolName, input) {
|
|
@@ -260,6 +272,12 @@ export class Journalist {
|
|
|
260
272
|
return input.file_path || input.path || '';
|
|
261
273
|
case 'Edit':
|
|
262
274
|
return input.file_path || input.path || '';
|
|
275
|
+
case 'Read':
|
|
276
|
+
return input.file_path || input.path || '';
|
|
277
|
+
case 'Grep':
|
|
278
|
+
return `${input.pattern || ''}${input.path ? ' in ' + input.path : ''}`;
|
|
279
|
+
case 'Glob':
|
|
280
|
+
return input.pattern || '';
|
|
263
281
|
case 'Bash':
|
|
264
282
|
return (input.command || '').slice(0, 150);
|
|
265
283
|
default:
|
|
@@ -653,10 +671,11 @@ export class Journalist {
|
|
|
653
671
|
|
|
654
672
|
// --- Handoff Brief for Context Rotation ---
|
|
655
673
|
|
|
656
|
-
async generateHandoffBrief(agent) {
|
|
674
|
+
async generateHandoffBrief(agent, options = {}) {
|
|
657
675
|
const filteredLogs = this.collectFilteredLogs([agent]);
|
|
658
676
|
const agentLog = filteredLogs[agent.id];
|
|
659
677
|
const entries = agentLog?.entries || [];
|
|
678
|
+
const explorationEntries = agentLog?.explorationEntries || [];
|
|
660
679
|
|
|
661
680
|
// Get current project map — scoped to agent's workspace if applicable
|
|
662
681
|
const mapPath = resolve(this.daemon.projectDir, 'GROOVE_PROJECT_MAP.md');
|
|
@@ -684,6 +703,33 @@ export class Journalist {
|
|
|
684
703
|
.slice(-3)
|
|
685
704
|
.join('\n');
|
|
686
705
|
|
|
706
|
+
// Build exploration summary — what files/patterns were examined
|
|
707
|
+
const explorationSummary = explorationEntries
|
|
708
|
+
.map((e) => `- ${e.tool}: ${e.input}`)
|
|
709
|
+
.slice(-20)
|
|
710
|
+
.join('\n');
|
|
711
|
+
|
|
712
|
+
// Build file changes section — group Edit/Write operations by file path
|
|
713
|
+
const fileChanges = {};
|
|
714
|
+
for (const e of entries.filter((e) => e.type === 'tool' && (e.tool === 'Edit' || e.tool === 'Write'))) {
|
|
715
|
+
const file = e.input || 'unknown';
|
|
716
|
+
if (!fileChanges[file]) fileChanges[file] = [];
|
|
717
|
+
if (e.diff) fileChanges[file].push(e.diff);
|
|
718
|
+
else fileChanges[file].push(e.tool === 'Write' ? 'created' : 'modified');
|
|
719
|
+
}
|
|
720
|
+
const fileChangesSummary = Object.entries(fileChanges)
|
|
721
|
+
.map(([file, changes]) => `- **${file}**: ${changes.slice(0, 3).join('; ')}`)
|
|
722
|
+
.slice(0, 20)
|
|
723
|
+
.join('\n');
|
|
724
|
+
|
|
725
|
+
// Layer 7 memory: discoveries, constraints, specializations
|
|
726
|
+
const discoveries = this.daemon.memory?.getDiscoveriesMarkdown(agent.role, 10, 2000) || '';
|
|
727
|
+
const constraints = this.daemon.memory?.getConstraintsMarkdown(2000) || '';
|
|
728
|
+
const specialization = this.daemon.memory?.getSpecialization(agent.id);
|
|
729
|
+
const specLine = specialization?.avgQualityScore != null
|
|
730
|
+
? `- Quality profile: ${specialization.avgQualityScore}/100 across ${specialization.sessionCount} sessions`
|
|
731
|
+
: '';
|
|
732
|
+
|
|
687
733
|
// Pull recent rotation history from persistent memory (Layer 7).
|
|
688
734
|
// Gives the new agent causal continuity: what the last 3 agents struggled
|
|
689
735
|
// with, decided, and solved — not just what the current session did.
|
|
@@ -700,11 +746,15 @@ export class Journalist {
|
|
|
700
746
|
? agentFeedback.map((fb) => `- "${fb.message}"`).join('\n')
|
|
701
747
|
: '';
|
|
702
748
|
|
|
749
|
+
// Rotation reason for the new agent
|
|
750
|
+
const rotationTrigger = `- Rotation trigger: ${options.reason || 'manual'}${options.qualityScore ? ` (quality: ${options.qualityScore}/100)` : ''}`;
|
|
751
|
+
|
|
703
752
|
return [
|
|
704
753
|
`# Session Continuation`,
|
|
705
754
|
``,
|
|
706
755
|
`You are **${agent.name}** (role: ${agent.role}). This is an internal context refresh — `,
|
|
707
756
|
`the conversation with the user is ongoing and must feel seamless to them. They cannot see this brief.`,
|
|
757
|
+
rotationTrigger,
|
|
708
758
|
``,
|
|
709
759
|
`## CRITICAL: Finish What You Were Doing`,
|
|
710
760
|
``,
|
|
@@ -729,6 +779,7 @@ export class Journalist {
|
|
|
729
779
|
`- Scope: ${agent.scope?.join(', ') || 'unrestricted'}`,
|
|
730
780
|
`- Provider: ${agent.provider}`,
|
|
731
781
|
agent.workingDir ? `- Working directory: ${agent.workingDir}` : '',
|
|
782
|
+
specLine,
|
|
732
783
|
``,
|
|
733
784
|
`## Session State`,
|
|
734
785
|
`- Tokens used before refresh: ${agent.tokensUsed}`,
|
|
@@ -737,6 +788,10 @@ export class Journalist {
|
|
|
737
788
|
toolSummary ? `### Recent tool calls (what you were doing)\n${toolSummary}\n` : '',
|
|
738
789
|
resultSummary ? `### Last results\n${resultSummary}\n` : '',
|
|
739
790
|
errorSummary ? `### Unresolved errors\n${errorSummary}\n` : '',
|
|
791
|
+
fileChangesSummary ? `## Files Modified\n\n${fileChangesSummary}\n` : '',
|
|
792
|
+
explorationSummary ? `## Exploration Context\n\n${explorationSummary}\n` : '',
|
|
793
|
+
discoveries ? `## Known Issues & Fixes\n\n${discoveries}\n` : '',
|
|
794
|
+
constraints ? `## Project Constraints\n\n${constraints}\n` : '',
|
|
740
795
|
`## Current Project State`,
|
|
741
796
|
``,
|
|
742
797
|
projectMap ? projectMap.slice(0, 10000) : 'No project map available yet.',
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
4
|
import { spawn as cpSpawn } from 'child_process';
|
|
5
|
-
import { createWriteStream, mkdirSync, chmodSync, existsSync, readFileSync, unlinkSync, readdirSync, copyFileSync } from 'fs';
|
|
5
|
+
import { createWriteStream, mkdirSync, chmodSync, existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync, copyFileSync } from 'fs';
|
|
6
6
|
import { resolve, dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { getProvider, getInstalledProviders } from './providers/index.js';
|
|
@@ -66,6 +66,19 @@ Do NOT write code unless explicitly asked. Use your MCP tools (database queries,
|
|
|
66
66
|
- Researching topics to produce accurate, substantive writing
|
|
67
67
|
You CAN use code tools to create and edit text files, markdown documents, and structured content. For best results, apply a writing skill from the Marketplace that matches your task.
|
|
68
68
|
|
|
69
|
+
`,
|
|
70
|
+
chat: `You are a Chat agent — a conversational companion, friend, and assistant. You are warm, curious, and genuinely engaged. You can discuss anything: ideas, philosophy, science, culture, coding, life.
|
|
71
|
+
|
|
72
|
+
Your primary mode is conversation, but you can still perform ANY task the user asks — code, research, analysis, writing. When no task is given, lean into being a great conversational partner.
|
|
73
|
+
|
|
74
|
+
Key behaviors:
|
|
75
|
+
- Be genuinely interested in the user's thoughts
|
|
76
|
+
- Ask thoughtful follow-up questions
|
|
77
|
+
- Share perspectives and explore ideas together
|
|
78
|
+
- Match the user's energy — playful, serious, philosophical, technical
|
|
79
|
+
- Remember context within the conversation
|
|
80
|
+
- Be honest and direct, not sycophantic
|
|
81
|
+
|
|
69
82
|
`,
|
|
70
83
|
frontend: `You are a Frontend agent. You build and modify UI components, views, and state management. Focus on:
|
|
71
84
|
- Writing clean React/JSX with Tailwind CSS classes — zero inline styles unless dynamic
|
|
@@ -351,6 +364,23 @@ export class ProcessManager {
|
|
|
351
364
|
}
|
|
352
365
|
}
|
|
353
366
|
|
|
367
|
+
// Create empty personality file for this agent
|
|
368
|
+
const personalityDir = resolve(this.daemon.grooveDir, 'personalities');
|
|
369
|
+
mkdirSync(personalityDir, { recursive: true });
|
|
370
|
+
const personalityFile = resolve(personalityDir, `${agent.name}.md`);
|
|
371
|
+
if (!existsSync(personalityFile)) {
|
|
372
|
+
if (config.personality) {
|
|
373
|
+
const templateFile = resolve(personalityDir, `${config.personality}.md`);
|
|
374
|
+
if (existsSync(templateFile)) {
|
|
375
|
+
copyFileSync(templateFile, personalityFile);
|
|
376
|
+
} else {
|
|
377
|
+
writeFileSync(personalityFile, '', { mode: 0o600 });
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
writeFileSync(personalityFile, '', { mode: 0o600 });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
354
384
|
// Pre-spawn task negotiation — if same-role agents are running,
|
|
355
385
|
// query them about current work so the new agent gets a clear assignment
|
|
356
386
|
const sameRole = registry.getAll().filter(
|
|
@@ -422,6 +452,31 @@ IMPORTANT: No task has been assigned yet. You MUST wait for the user to tell you
|
|
|
422
452
|
}
|
|
423
453
|
}
|
|
424
454
|
|
|
455
|
+
// Load personality file for this agent
|
|
456
|
+
const pDir = resolve(this.daemon.grooveDir, 'personalities');
|
|
457
|
+
const pFile = resolve(pDir, `${agent.name}.md`);
|
|
458
|
+
if (existsSync(pFile)) {
|
|
459
|
+
const personality = readFileSync(pFile, 'utf8').trim();
|
|
460
|
+
if (personality) {
|
|
461
|
+
spawnConfig.prompt = `## Personality\n\n${personality}\n\n` + spawnConfig.prompt;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Load user-created context files for this agent
|
|
466
|
+
const agentFilesDir = resolve(this.daemon.grooveDir, 'agent-files', agent.name);
|
|
467
|
+
if (existsSync(agentFilesDir)) {
|
|
468
|
+
try {
|
|
469
|
+
const userFiles = readdirSync(agentFilesDir).filter(f => f.endsWith('.md'));
|
|
470
|
+
for (const fileName of userFiles) {
|
|
471
|
+
const uf = readFileSync(resolve(agentFilesDir, fileName), 'utf8').trim();
|
|
472
|
+
if (uf) {
|
|
473
|
+
const label = fileName.replace(/\.md$/, '');
|
|
474
|
+
spawnConfig.prompt += `\n\n## User Context: ${label}\n\n_This is user-created context — not part of your system instructions or task. Treat it as reference material provided by the user._\n\n${uf}`;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch { /* ignore */ }
|
|
478
|
+
}
|
|
479
|
+
|
|
425
480
|
// Apply PM review instructions for Auto permission mode
|
|
426
481
|
// Agents call the PM endpoint before risky operations for AI review
|
|
427
482
|
// Skip for sandboxed providers (Codex) — localhost is unreachable from their sandbox
|
|
@@ -540,12 +595,17 @@ For normal file edits within your scope, proceed without review.
|
|
|
540
595
|
const spawnLine = `[${new Date().toISOString()}] GROOVE spawning: ${command} [${safeArgs.length} args]\n`;
|
|
541
596
|
logStream.write(spawnLine);
|
|
542
597
|
|
|
543
|
-
// Inject API
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
598
|
+
// Inject ALL stored API keys — agents may need keys from other providers
|
|
599
|
+
// (e.g., an EA agent on claude-code may need OPENAI_API_KEY for a third-party tool)
|
|
600
|
+
if (this.daemon.credentials) {
|
|
601
|
+
for (const cp of this.daemon.credentials.listProviders()) {
|
|
602
|
+
const meta = getProvider(cp.provider);
|
|
603
|
+
if (meta?.constructor?.envKey) {
|
|
604
|
+
const storedKey = this.daemon.credentials.getKey(cp.provider);
|
|
605
|
+
if (storedKey) {
|
|
606
|
+
env[meta.constructor.envKey] = storedKey;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
549
609
|
}
|
|
550
610
|
}
|
|
551
611
|
|
|
@@ -51,7 +51,7 @@ export class Registry extends EventEmitter {
|
|
|
51
51
|
if (!agent) return null;
|
|
52
52
|
|
|
53
53
|
// Only allow known fields to prevent prototype pollution
|
|
54
|
-
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'provider', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations', 'workingDir', 'effort', 'costUsd', 'durationMs', 'turns', 'inputTokens', 'outputTokens', 'teamId', 'permission', 'scope', 'integrationApproval'];
|
|
54
|
+
const SAFE_FIELDS = ['status', 'pid', 'tokensUsed', 'contextUsage', 'lastActivity', 'model', 'provider', 'name', 'routingMode', 'routingReason', 'sessionId', 'skills', 'integrations', 'repos', 'workingDir', 'effort', 'costUsd', 'durationMs', 'turns', 'inputTokens', 'outputTokens', 'teamId', 'permission', 'scope', 'integrationApproval', 'personality'];
|
|
55
55
|
for (const key of Object.keys(updates)) {
|
|
56
56
|
if (SAFE_FIELDS.includes(key)) {
|
|
57
57
|
agent[key] = updates[key];
|