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.
@@ -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
- agent.prompt ? `Original task: ${agent.prompt}` : '',
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 { command, args, env } = provider.buildHeadlessCommand(prompt, modelId);
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 { command, args, env } = provider.buildSpawnCommand(spawnConfig);
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
- if (agent.prompt) args.push(agent.prompt);
230
- return { command: 'ollama', args, env: { OLLAMA_API_BASE: 'http://localhost:11434' } };
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, prompt], env: {} };
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.18",
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
- agent.prompt ? `Original task: ${agent.prompt}` : '',
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 { command, args, env } = provider.buildHeadlessCommand(prompt, modelId);
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 { command, args, env } = provider.buildSpawnCommand(spawnConfig);
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
- if (agent.prompt) args.push(agent.prompt);
230
- return { command: 'ollama', args, env: { OLLAMA_API_BASE: 'http://localhost:11434' } };
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, prompt], env: {} };
239
+ return { command: 'ollama', args: ['run', m], env: {}, stdin: prompt };
236
240
  }
237
241
 
238
242
  switchModel(agent, newModel) {