groove-dev 0.25.18 → 0.25.19
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/node_modules/@groove-dev/daemon/src/api.js +3 -2
- package/node_modules/@groove-dev/daemon/src/journalist.js +32 -2
- package/node_modules/@groove-dev/daemon/src/process.js +10 -4
- package/node_modules/@groove-dev/daemon/src/providers/ollama.js +7 -3
- package/package.json +1 -1
- package/packages/daemon/src/api.js +3 -2
- package/packages/daemon/src/journalist.js +32 -2
- package/packages/daemon/src/process.js +10 -4
- package/packages/daemon/src/providers/ollama.js +7 -3
|
@@ -403,11 +403,12 @@ export function createApi(app, daemon) {
|
|
|
403
403
|
const activity = daemon.classifier?.agentWindows?.[agent.id] || [];
|
|
404
404
|
const recentActivity = activity.slice(-20).map((e) => e.data || e.text || '').join('\n');
|
|
405
405
|
|
|
406
|
+
// Truncate the agent's original prompt to avoid massive payloads
|
|
407
|
+
const taskSummary = agent.prompt ? agent.prompt.slice(0, 500) : '';
|
|
406
408
|
const prompt = [
|
|
407
409
|
`You are answering a question about agent "${agent.name}" (role: ${agent.role}).`,
|
|
408
|
-
`This agent's file scope: ${(agent.scope || []).join(', ') || 'unrestricted'}`,
|
|
409
410
|
`Provider: ${agent.provider}, Tokens used: ${agent.tokensUsed || 0}`,
|
|
410
|
-
|
|
411
|
+
taskSummary ? `Task summary: ${taskSummary}` : '',
|
|
411
412
|
recentActivity ? `\nRecent activity:\n${recentActivity}` : '',
|
|
412
413
|
`\nUser question: ${message.trim()}`,
|
|
413
414
|
'\nAnswer concisely based on the agent context above.',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { execFile } from 'child_process';
|
|
6
|
+
import { execFile, spawn as cpSpawn } from 'child_process';
|
|
7
7
|
import { getProvider, getInstalledProviders } from './providers/index.js';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_INTERVAL = 120_000; // 2 minutes
|
|
@@ -325,9 +325,39 @@ export class Journalist {
|
|
|
325
325
|
|| provider.constructor.models?.[0];
|
|
326
326
|
const modelId = lightModel?.id || null;
|
|
327
327
|
|
|
328
|
-
const
|
|
328
|
+
const headlessCmd = provider.buildHeadlessCommand(prompt, modelId);
|
|
329
|
+
const { command, args, env, stdin: stdinData } = headlessCmd;
|
|
329
330
|
|
|
330
331
|
return new Promise((resolve, reject) => {
|
|
332
|
+
// Use spawn with stdin pipe if provider needs it (e.g., Ollama)
|
|
333
|
+
if (stdinData) {
|
|
334
|
+
let stdout = '';
|
|
335
|
+
const proc = cpSpawn(command, args, {
|
|
336
|
+
env: { ...process.env, ...env },
|
|
337
|
+
cwd: this.daemon.projectDir,
|
|
338
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
339
|
+
});
|
|
340
|
+
proc.stdin.write(stdinData);
|
|
341
|
+
proc.stdin.end();
|
|
342
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
343
|
+
const timer = setTimeout(() => { proc.kill(); reject(new Error('Headless timeout')); }, 60_000);
|
|
344
|
+
proc.on('exit', (code) => {
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
if (code !== 0) return reject(new Error(`Headless exited with code ${code}`));
|
|
347
|
+
// Process stdout same as execFile path below
|
|
348
|
+
const lines = stdout.split('\n');
|
|
349
|
+
for (const line of lines) {
|
|
350
|
+
try {
|
|
351
|
+
const json = JSON.parse(line);
|
|
352
|
+
if (json.result) return resolve(json.result);
|
|
353
|
+
if (json.content?.[0]?.text) return resolve(json.content[0].text);
|
|
354
|
+
} catch { /* not json */ }
|
|
355
|
+
}
|
|
356
|
+
resolve(stdout.trim());
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
331
361
|
const proc = execFile(command, args, {
|
|
332
362
|
env: { ...process.env, ...env },
|
|
333
363
|
cwd: this.daemon.projectDir,
|
|
@@ -324,7 +324,8 @@ For normal file edits within your scope, proceed without review.
|
|
|
324
324
|
integrationEnv = this.daemon.integrations.getSpawnEnv(config.integrations);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
const
|
|
327
|
+
const spawnCmd = provider.buildSpawnCommand(spawnConfig);
|
|
328
|
+
const { command, args, env, stdin: stdinData } = spawnCmd;
|
|
328
329
|
|
|
329
330
|
// Set up log capture
|
|
330
331
|
const logDir = resolve(this.daemon.grooveDir, 'logs');
|
|
@@ -347,15 +348,20 @@ For normal file edits within your scope, proceed without review.
|
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
|
|
350
|
-
// Spawn the process
|
|
351
|
+
// Spawn the process (use pipe for stdin if provider needs to send prompt via stdin)
|
|
351
352
|
const proc = cpSpawn(command, args, {
|
|
352
353
|
cwd: agent.workingDir || this.daemon.projectDir,
|
|
353
354
|
env: { ...process.env, ...env, ...integrationEnv, GROOVE_AGENT_ID: agent.id, GROOVE_AGENT_NAME: agent.name },
|
|
354
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
355
|
-
// Don't let agent process prevent daemon from exiting
|
|
355
|
+
stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
356
356
|
detached: false,
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
+
// Write prompt via stdin if provider requested it (e.g., Ollama avoids arg length limits)
|
|
360
|
+
if (stdinData && proc.stdin) {
|
|
361
|
+
proc.stdin.write(stdinData);
|
|
362
|
+
proc.stdin.end();
|
|
363
|
+
}
|
|
364
|
+
|
|
359
365
|
if (!proc.pid) {
|
|
360
366
|
registry.remove(agent.id);
|
|
361
367
|
locks.release(agent.id);
|
|
@@ -226,13 +226,17 @@ export class OllamaProvider extends Provider {
|
|
|
226
226
|
buildSpawnCommand(agent) {
|
|
227
227
|
const model = agent.model || 'qwen2.5-coder:7b';
|
|
228
228
|
const args = ['run', model];
|
|
229
|
-
|
|
230
|
-
return {
|
|
229
|
+
// Pass prompt via stdin to avoid OS arg length limits on long prompts
|
|
230
|
+
return {
|
|
231
|
+
command: 'ollama', args,
|
|
232
|
+
env: { OLLAMA_API_BASE: 'http://localhost:11434' },
|
|
233
|
+
stdin: agent.prompt || undefined,
|
|
234
|
+
};
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
buildHeadlessCommand(prompt, model) {
|
|
234
238
|
const m = model || 'qwen2.5-coder:7b';
|
|
235
|
-
return { command: 'ollama', args: ['run', m
|
|
239
|
+
return { command: 'ollama', args: ['run', m], env: {}, stdin: prompt };
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
switchModel(agent, newModel) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.19",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. 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.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -403,11 +403,12 @@ export function createApi(app, daemon) {
|
|
|
403
403
|
const activity = daemon.classifier?.agentWindows?.[agent.id] || [];
|
|
404
404
|
const recentActivity = activity.slice(-20).map((e) => e.data || e.text || '').join('\n');
|
|
405
405
|
|
|
406
|
+
// Truncate the agent's original prompt to avoid massive payloads
|
|
407
|
+
const taskSummary = agent.prompt ? agent.prompt.slice(0, 500) : '';
|
|
406
408
|
const prompt = [
|
|
407
409
|
`You are answering a question about agent "${agent.name}" (role: ${agent.role}).`,
|
|
408
|
-
`This agent's file scope: ${(agent.scope || []).join(', ') || 'unrestricted'}`,
|
|
409
410
|
`Provider: ${agent.provider}, Tokens used: ${agent.tokensUsed || 0}`,
|
|
410
|
-
|
|
411
|
+
taskSummary ? `Task summary: ${taskSummary}` : '',
|
|
411
412
|
recentActivity ? `\nRecent activity:\n${recentActivity}` : '',
|
|
412
413
|
`\nUser question: ${message.trim()}`,
|
|
413
414
|
'\nAnswer concisely based on the agent context above.',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
|
|
5
5
|
import { resolve } from 'path';
|
|
6
|
-
import { execFile } from 'child_process';
|
|
6
|
+
import { execFile, spawn as cpSpawn } from 'child_process';
|
|
7
7
|
import { getProvider, getInstalledProviders } from './providers/index.js';
|
|
8
8
|
|
|
9
9
|
const DEFAULT_INTERVAL = 120_000; // 2 minutes
|
|
@@ -325,9 +325,39 @@ export class Journalist {
|
|
|
325
325
|
|| provider.constructor.models?.[0];
|
|
326
326
|
const modelId = lightModel?.id || null;
|
|
327
327
|
|
|
328
|
-
const
|
|
328
|
+
const headlessCmd = provider.buildHeadlessCommand(prompt, modelId);
|
|
329
|
+
const { command, args, env, stdin: stdinData } = headlessCmd;
|
|
329
330
|
|
|
330
331
|
return new Promise((resolve, reject) => {
|
|
332
|
+
// Use spawn with stdin pipe if provider needs it (e.g., Ollama)
|
|
333
|
+
if (stdinData) {
|
|
334
|
+
let stdout = '';
|
|
335
|
+
const proc = cpSpawn(command, args, {
|
|
336
|
+
env: { ...process.env, ...env },
|
|
337
|
+
cwd: this.daemon.projectDir,
|
|
338
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
339
|
+
});
|
|
340
|
+
proc.stdin.write(stdinData);
|
|
341
|
+
proc.stdin.end();
|
|
342
|
+
proc.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
343
|
+
const timer = setTimeout(() => { proc.kill(); reject(new Error('Headless timeout')); }, 60_000);
|
|
344
|
+
proc.on('exit', (code) => {
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
if (code !== 0) return reject(new Error(`Headless exited with code ${code}`));
|
|
347
|
+
// Process stdout same as execFile path below
|
|
348
|
+
const lines = stdout.split('\n');
|
|
349
|
+
for (const line of lines) {
|
|
350
|
+
try {
|
|
351
|
+
const json = JSON.parse(line);
|
|
352
|
+
if (json.result) return resolve(json.result);
|
|
353
|
+
if (json.content?.[0]?.text) return resolve(json.content[0].text);
|
|
354
|
+
} catch { /* not json */ }
|
|
355
|
+
}
|
|
356
|
+
resolve(stdout.trim());
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
331
361
|
const proc = execFile(command, args, {
|
|
332
362
|
env: { ...process.env, ...env },
|
|
333
363
|
cwd: this.daemon.projectDir,
|
|
@@ -324,7 +324,8 @@ For normal file edits within your scope, proceed without review.
|
|
|
324
324
|
integrationEnv = this.daemon.integrations.getSpawnEnv(config.integrations);
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
-
const
|
|
327
|
+
const spawnCmd = provider.buildSpawnCommand(spawnConfig);
|
|
328
|
+
const { command, args, env, stdin: stdinData } = spawnCmd;
|
|
328
329
|
|
|
329
330
|
// Set up log capture
|
|
330
331
|
const logDir = resolve(this.daemon.grooveDir, 'logs');
|
|
@@ -347,15 +348,20 @@ For normal file edits within your scope, proceed without review.
|
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
|
|
350
|
-
// Spawn the process
|
|
351
|
+
// Spawn the process (use pipe for stdin if provider needs to send prompt via stdin)
|
|
351
352
|
const proc = cpSpawn(command, args, {
|
|
352
353
|
cwd: agent.workingDir || this.daemon.projectDir,
|
|
353
354
|
env: { ...process.env, ...env, ...integrationEnv, GROOVE_AGENT_ID: agent.id, GROOVE_AGENT_NAME: agent.name },
|
|
354
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
355
|
-
// Don't let agent process prevent daemon from exiting
|
|
355
|
+
stdio: [stdinData ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
356
356
|
detached: false,
|
|
357
357
|
});
|
|
358
358
|
|
|
359
|
+
// Write prompt via stdin if provider requested it (e.g., Ollama avoids arg length limits)
|
|
360
|
+
if (stdinData && proc.stdin) {
|
|
361
|
+
proc.stdin.write(stdinData);
|
|
362
|
+
proc.stdin.end();
|
|
363
|
+
}
|
|
364
|
+
|
|
359
365
|
if (!proc.pid) {
|
|
360
366
|
registry.remove(agent.id);
|
|
361
367
|
locks.release(agent.id);
|
|
@@ -226,13 +226,17 @@ export class OllamaProvider extends Provider {
|
|
|
226
226
|
buildSpawnCommand(agent) {
|
|
227
227
|
const model = agent.model || 'qwen2.5-coder:7b';
|
|
228
228
|
const args = ['run', model];
|
|
229
|
-
|
|
230
|
-
return {
|
|
229
|
+
// Pass prompt via stdin to avoid OS arg length limits on long prompts
|
|
230
|
+
return {
|
|
231
|
+
command: 'ollama', args,
|
|
232
|
+
env: { OLLAMA_API_BASE: 'http://localhost:11434' },
|
|
233
|
+
stdin: agent.prompt || undefined,
|
|
234
|
+
};
|
|
231
235
|
}
|
|
232
236
|
|
|
233
237
|
buildHeadlessCommand(prompt, model) {
|
|
234
238
|
const m = model || 'qwen2.5-coder:7b';
|
|
235
|
-
return { command: 'ollama', args: ['run', m
|
|
239
|
+
return { command: 'ollama', args: ['run', m], env: {}, stdin: prompt };
|
|
236
240
|
}
|
|
237
241
|
|
|
238
242
|
switchModel(agent, newModel) {
|